Skip to content

Commit a0c5aab

Browse files
authored
Merge branch 'main' into fix/v8-error-panic
2 parents c917fdb + 1ac34fd commit a0c5aab

File tree

10 files changed

+422
-32
lines changed

10 files changed

+422
-32
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ repository = "https://github.com/denoland/deno_core"
1919

2020
[workspace.dependencies]
2121
# Local dependencies
22-
deno_core = { version = "0.278.0", path = "./core" }
23-
deno_ops = { version = "0.154.0", path = "./ops" }
24-
serde_v8 = { version = "0.187.0", path = "./serde_v8" }
22+
deno_core = { version = "0.279.0", path = "./core" }
23+
deno_ops = { version = "0.155.0", path = "./ops" }
24+
serde_v8 = { version = "0.188.0", path = "./serde_v8" }
2525
deno_core_testing = { path = "./testing" }
2626

2727
v8 = { version = "0.91.0", default-features = false }

core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[package]
44
name = "deno_core"
5-
version = "0.278.0"
5+
version = "0.279.0"
66
authors.workspace = true
77
edition.workspace = true
88
license.workspace = true

core/convert.rs

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
3+
use crate::error::StdAnyError;
4+
use crate::runtime::ops;
5+
use std::convert::Infallible;
6+
7+
/// A conversion from a rust value to a v8 value.
8+
///
9+
/// When passing data from Rust into JS, either
10+
/// via an op or by calling a JS function directly,
11+
/// you need to serialize the data into a native
12+
/// V8 value. When using the [`op2`][deno_core::op2] macro, the return
13+
/// value is converted to a `v8::Local<Value>` automatically,
14+
/// and the strategy for conversion is controlled by attributes
15+
/// like `#[smi]`, `#[number]`, `#[string]`. For types with support
16+
/// built-in to the op2 macro, like primitives, strings, and buffers,
17+
/// these attributes are sufficient and you don't need to worry about this trait.
18+
///
19+
/// If, however, you want to return a custom type from an op, or
20+
/// simply want more control over the conversion process,
21+
/// you can implement the `ToV8` trait. This allows you the
22+
/// choose the best serialization strategy for your specific use case.
23+
/// You can then use the `#[to_v8]` attribute to indicate
24+
/// that the `#[op2]` macro should call your implementation for the conversion.
25+
///
26+
/// # Example
27+
///
28+
/// ```ignore
29+
/// use deno_core::ToV8;
30+
/// use deno_core::convert::Smi;
31+
/// use deno_core::op2;
32+
///
33+
/// struct Foo(i32);
34+
///
35+
/// impl<'a> ToV8<'a> for Foo {
36+
/// // This conversion can never fail, so we use `Infallible` as the error type.
37+
/// // Any error type that implements `std::error::Error` can be used here.
38+
/// type Error = std::convert::Infallible;
39+
///
40+
/// fn to_v8(self, scope: &mut v8::HandleScope<'a>) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
41+
/// // For performance, pass this value as a `v8::Integer` (i.e. a `smi`).
42+
/// // The `Smi` wrapper type implements this conversion for you.
43+
/// Smi(self.0).to_v8(scope)
44+
/// }
45+
/// }
46+
///
47+
/// // using the `#[to_v8]` attribute tells the `op2` macro to call this implementation.
48+
/// #[op2]
49+
/// #[to_v8]
50+
/// fn op_foo() -> Foo {
51+
/// Foo(42)
52+
/// }
53+
/// ```
54+
///
55+
/// # Performance Notes
56+
/// ## Structs
57+
/// The natural representation of a struct in JS is an object with fields
58+
/// corresponding the struct. This, however, is a performance footgun and
59+
/// you should avoid creating and passing objects to V8 whenever possible.
60+
/// In general, if you need to pass a compound type to JS, it is more performant to serialize
61+
/// to a tuple (a `v8::Array`) rather than an object.
62+
/// Object keys are V8 strings, and strings are expensive to pass to V8
63+
/// and they have to be managed by the V8 garbage collector.
64+
/// Tuples, on the other hand, are keyed by `smi`s, which are immediates
65+
/// and don't require allocation or garbage collection.
66+
pub trait ToV8<'a> {
67+
type Error: std::error::Error + Send + Sync + 'static;
68+
69+
/// Converts the value to a V8 value.
70+
fn to_v8(
71+
self,
72+
scope: &mut v8::HandleScope<'a>,
73+
) -> Result<v8::Local<'a, v8::Value>, Self::Error>;
74+
}
75+
76+
/// A conversion from a v8 value to a rust value.
77+
///
78+
/// When writing a op, or otherwise writing a function in Rust called
79+
/// from JS, arguments passed from JS are represented as [`v8::Local<v8::Value>>`][deno_core::v8::Value].
80+
/// To convert these values into custom Rust types, you can implement the [`FromV8`] trait.
81+
///
82+
/// Once you've implemented this trait, you can use the `#[from_v8]` attribute
83+
/// to tell the [`op2`][deno_core::op2] macro to use your implementation to convert the argument
84+
/// to the desired type.
85+
///
86+
/// # Example
87+
///
88+
/// ```ignore
89+
/// use deno_core::FromV8;
90+
/// use deno_core::convert::Smi;
91+
/// use deno_core::op2;
92+
///
93+
/// struct Foo(i32);
94+
///
95+
/// impl<'a> FromV8<'a> for Foo {
96+
/// // This conversion can fail, so we use `deno_core::error::StdAnyError` as the error type.
97+
/// // Any error type that implements `std::error::Error` can be used here.
98+
/// type Error = deno_core::error::StdAnyError;
99+
///
100+
/// fn from_v8(scope: &mut v8::HandleScope<'a>, value: v8::Local<'a, v8::Value>) -> Result<Self, Self::Error> {
101+
/// /// We expect this value to be a `v8::Integer`, so we use the [`Smi`][deno_core::convert::Smi] wrapper type to convert it.
102+
/// Smi::from_v8(scope, value).map(|Smi(v)| Foo(v))
103+
/// }
104+
/// }
105+
///
106+
/// // using the `#[from_v8]` attribute tells the `op2` macro to call this implementation.
107+
/// #[op2]
108+
/// fn op_foo(#[from_v8] foo: Foo) {
109+
/// let Foo(_) = foo;
110+
/// }
111+
/// ```
112+
pub trait FromV8<'a>: Sized {
113+
type Error: std::error::Error + Send + Sync + 'static;
114+
115+
/// Converts a V8 value to a Rust value.
116+
fn from_v8(
117+
scope: &mut v8::HandleScope<'a>,
118+
value: v8::Local<'a, v8::Value>,
119+
) -> Result<Self, Self::Error>;
120+
}
121+
122+
// impls
123+
124+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
125+
/// Marks a numeric type as being serialized as a v8 `smi` in a `v8::Integer`.
126+
#[repr(transparent)]
127+
pub struct Smi<T: SmallInt>(pub T);
128+
129+
/// A trait for types that can represent a JS `smi`.
130+
pub trait SmallInt {
131+
const NAME: &'static str;
132+
133+
#[allow(clippy::wrong_self_convention)]
134+
fn as_i32(self) -> i32;
135+
fn from_i32(value: i32) -> Self;
136+
}
137+
138+
macro_rules! impl_smallint {
139+
(for $($t:ty),*) => {
140+
$(
141+
impl SmallInt for $t {
142+
const NAME: &'static str = stringify!($t);
143+
#[allow(clippy::wrong_self_convention)]
144+
#[inline(always)]
145+
fn as_i32(self) -> i32 {
146+
self as _
147+
}
148+
149+
#[inline(always)]
150+
fn from_i32(value: i32) -> Self {
151+
value as _
152+
}
153+
}
154+
)*
155+
};
156+
}
157+
158+
impl_smallint!(for u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
159+
160+
impl<'a, T: SmallInt> ToV8<'a> for Smi<T> {
161+
type Error = Infallible;
162+
163+
#[inline]
164+
fn to_v8(
165+
self,
166+
scope: &mut v8::HandleScope<'a>,
167+
) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
168+
Ok(v8::Integer::new(scope, self.0.as_i32()).into())
169+
}
170+
}
171+
172+
impl<'a, T: SmallInt> FromV8<'a> for Smi<T> {
173+
type Error = StdAnyError;
174+
175+
#[inline]
176+
fn from_v8(
177+
_scope: &mut v8::HandleScope<'a>,
178+
value: v8::Local<'a, v8::Value>,
179+
) -> Result<Self, Self::Error> {
180+
let v = crate::runtime::ops::to_i32_option(&value).ok_or_else(|| {
181+
crate::error::type_error(format!("Expected {}", T::NAME))
182+
})?;
183+
Ok(Smi(T::from_i32(v)))
184+
}
185+
}
186+
187+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
188+
/// Marks a numeric type as being serialized as a v8 `number` in a `v8::Number`.
189+
#[repr(transparent)]
190+
pub struct Number<T: Numeric>(pub T);
191+
192+
/// A trait for types that can represent a JS `number`.
193+
pub trait Numeric: Sized {
194+
const NAME: &'static str;
195+
#[allow(clippy::wrong_self_convention)]
196+
fn as_f64(self) -> f64;
197+
fn from_value(value: &v8::Value) -> Option<Self>;
198+
}
199+
200+
macro_rules! impl_numeric {
201+
($($t:ty : $from: path ),*) => {
202+
$(
203+
impl Numeric for $t {
204+
const NAME: &'static str = stringify!($t);
205+
#[inline(always)]
206+
fn from_value(value: &v8::Value) -> Option<Self> {
207+
$from(value).map(|v| v as _)
208+
}
209+
210+
#[allow(clippy::wrong_self_convention)]
211+
#[inline(always)]
212+
fn as_f64(self) -> f64 {
213+
self as _
214+
}
215+
}
216+
)*
217+
};
218+
}
219+
220+
impl_numeric!(
221+
f32 : ops::to_f32_option,
222+
f64 : ops::to_f64_option,
223+
u32 : ops::to_u32_option,
224+
u64 : ops::to_u64_option,
225+
usize : ops::to_u64_option,
226+
i32 : ops::to_i32_option,
227+
i64 : ops::to_i64_option,
228+
isize : ops::to_i64_option
229+
);
230+
231+
impl<'a, T: Numeric> ToV8<'a> for Number<T> {
232+
type Error = Infallible;
233+
#[inline]
234+
fn to_v8(
235+
self,
236+
scope: &mut v8::HandleScope<'a>,
237+
) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
238+
Ok(v8::Number::new(scope, self.0.as_f64()).into())
239+
}
240+
}
241+
242+
impl<'a, T: Numeric> FromV8<'a> for Number<T> {
243+
type Error = StdAnyError;
244+
#[inline]
245+
fn from_v8(
246+
_scope: &mut v8::HandleScope<'a>,
247+
value: v8::Local<'a, v8::Value>,
248+
) -> Result<Self, Self::Error> {
249+
T::from_value(&value).map(Number).ok_or_else(|| {
250+
crate::error::type_error(format!("Expected {}", T::NAME)).into()
251+
})
252+
}
253+
}
254+
255+
impl<'a> ToV8<'a> for bool {
256+
type Error = Infallible;
257+
#[inline]
258+
fn to_v8(
259+
self,
260+
scope: &mut v8::HandleScope<'a>,
261+
) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
262+
Ok(v8::Boolean::new(scope, self).into())
263+
}
264+
}
265+
266+
impl<'a> FromV8<'a> for bool {
267+
type Error = Infallible;
268+
#[inline]
269+
fn from_v8(
270+
_scope: &mut v8::HandleScope<'a>,
271+
value: v8::Local<'a, v8::Value>,
272+
) -> Result<Self, Self::Error> {
273+
Ok(value.is_true())
274+
}
275+
}

core/error.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,33 @@ pub fn get_custom_error_class(error: &Error) -> Option<&'static str> {
9696
error.downcast_ref::<CustomError>().map(|e| e.class)
9797
}
9898

99+
/// A wrapper around `anyhow::Error` that implements `std::error::Error`
100+
#[repr(transparent)]
101+
pub struct StdAnyError(pub Error);
102+
impl std::fmt::Debug for StdAnyError {
103+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104+
write!(f, "{:?}", self.0)
105+
}
106+
}
107+
108+
impl std::fmt::Display for StdAnyError {
109+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110+
write!(f, "{}", self.0)
111+
}
112+
}
113+
114+
impl std::error::Error for StdAnyError {
115+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
116+
self.0.source()
117+
}
118+
}
119+
120+
impl From<Error> for StdAnyError {
121+
fn from(err: Error) -> Self {
122+
Self(err)
123+
}
124+
}
125+
99126
pub fn to_v8_error<'a>(
100127
scope: &mut v8::HandleScope<'a>,
101128
get_class: GetErrorClassFn,

core/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
pub mod arena;
88
mod async_cancel;
99
mod async_cell;
10+
pub mod convert;
1011
pub mod cppgc;
1112
pub mod error;
1213
mod error_codes;
@@ -31,7 +32,6 @@ mod path;
3132
mod runtime;
3233
mod source_map;
3334
mod tasks;
34-
mod to_from_v8;
3535
mod web_timeout;
3636

3737
// Re-exports
@@ -67,6 +67,8 @@ pub use crate::async_cell::AsyncRefCell;
6767
pub use crate::async_cell::AsyncRefFuture;
6868
pub use crate::async_cell::RcLike;
6969
pub use crate::async_cell::RcRef;
70+
pub use crate::convert::FromV8;
71+
pub use crate::convert::ToV8;
7072
pub use crate::error::GetErrorClassFn;
7173
pub use crate::error::JsErrorCreateFn;
7274
pub use crate::extensions::Extension;
@@ -156,8 +158,6 @@ pub use crate::source_map::SourceMapData;
156158
pub use crate::source_map::SourceMapGetter;
157159
pub use crate::tasks::V8CrossThreadTaskSpawner;
158160
pub use crate::tasks::V8TaskSpawner;
159-
pub use crate::to_from_v8::FromV8;
160-
pub use crate::to_from_v8::ToV8;
161161

162162
// Ensure we can use op2 in deno_core without any hackery.
163163
extern crate self as deno_core;

0 commit comments

Comments
 (0)