Skip to content

Commit 1911b1e

Browse files
committed
internal(neon): Implement wrap and unwrap as doc hidden for use in macros
1 parent 5793b62 commit 1911b1e

File tree

5 files changed

+227
-0
lines changed

5 files changed

+227
-0
lines changed

crates/neon/src/object/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ use crate::{
5151
#[cfg(feature = "napi-6")]
5252
use crate::{result::JsResult, types::JsArray};
5353

54+
#[doc(hidden)]
55+
pub use self::wrap::{unwrap, wrap};
56+
57+
mod wrap;
58+
5459
/// A property key in a JavaScript object.
5560
pub trait PropertyKey: Copy {
5661
unsafe fn get_from<'c, C: Context<'c>>(

crates/neon/src/object/wrap.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use std::{any::Any, error, ffi::c_void, fmt, mem::MaybeUninit, ptr};
2+
3+
use crate::{
4+
context::{
5+
internal::{ContextInternal, Env},
6+
Context, Cx,
7+
},
8+
handle::Handle,
9+
object::Object,
10+
result::{NeonResult, ResultExt, Throw},
11+
sys,
12+
types::Finalize,
13+
};
14+
15+
type BoxAny = Box<dyn Any + 'static>;
16+
17+
#[derive(Debug)]
18+
pub struct WrapError(WrapErrorType);
19+
20+
impl WrapError {
21+
fn already_wrapped() -> Self {
22+
Self(WrapErrorType::AlreadyWrapped)
23+
}
24+
25+
fn not_wrapped() -> Self {
26+
Self(WrapErrorType::NotWrapped)
27+
}
28+
29+
#[cfg(feature = "napi-8")]
30+
fn foreign_type() -> Self {
31+
Self(WrapErrorType::ForeignType)
32+
}
33+
}
34+
35+
impl fmt::Display for WrapError {
36+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37+
write!(f, "{}", self.0)
38+
}
39+
}
40+
41+
impl error::Error for WrapError {}
42+
43+
impl<T> ResultExt<T> for Result<T, WrapError> {
44+
fn or_throw<'cx, C>(self, cx: &mut C) -> NeonResult<T>
45+
where
46+
C: Context<'cx>,
47+
{
48+
match self {
49+
Ok(v) => Ok(v),
50+
Err(err) => cx.throw_error(err.to_string()),
51+
}
52+
}
53+
}
54+
55+
#[derive(Debug)]
56+
enum WrapErrorType {
57+
AlreadyWrapped,
58+
NotWrapped,
59+
#[cfg(feature = "napi-8")]
60+
ForeignType,
61+
}
62+
63+
impl fmt::Display for WrapErrorType {
64+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65+
match self {
66+
Self::AlreadyWrapped => write!(f, "Object is already wrapped"),
67+
Self::NotWrapped => write!(f, "Object is not wrapped"),
68+
#[cfg(feature = "napi-8")]
69+
Self::ForeignType => write!(f, "Object is wrapped by another addon"),
70+
}
71+
}
72+
}
73+
74+
pub fn wrap<T, V>(cx: &mut Cx, o: Handle<V>, v: T) -> NeonResult<Result<(), WrapError>>
75+
where
76+
T: Finalize + 'static,
77+
V: Object,
78+
{
79+
let env = cx.env().to_raw();
80+
let o = o.to_local();
81+
let v = Box::into_raw(Box::new(Box::new(v) as BoxAny));
82+
83+
// # Safety
84+
// The `finalize` function will be called when the JavaScript object is garbage
85+
// collected. The `data` pointer is guaranteed to be the same pointer passed when
86+
// wrapping.
87+
unsafe extern "C" fn finalize<T>(env: sys::Env, data: *mut c_void, _hint: *mut c_void)
88+
where
89+
T: Finalize + 'static,
90+
{
91+
let data = Box::from_raw(data.cast::<BoxAny>());
92+
let data = *data.downcast::<T>().unwrap();
93+
let env = Env::from(env);
94+
95+
Cx::with_context(env, move |mut cx| data.finalize(&mut cx));
96+
}
97+
98+
// # Safety
99+
// The `env` value was obtained from a valid `Cx` and the `o` handle has
100+
// already been verified to be an object.
101+
unsafe {
102+
match sys::wrap(
103+
env,
104+
o,
105+
v.cast(),
106+
Some(finalize::<T>),
107+
ptr::null_mut(),
108+
ptr::null_mut(),
109+
) {
110+
Err(sys::Status::InvalidArg) => {
111+
// Wrap failed, we can safely free the value
112+
let _ = Box::from_raw(v);
113+
114+
return Ok(Err(WrapError::already_wrapped()));
115+
}
116+
Err(sys::Status::PendingException) => {
117+
// Wrap failed, we can safely free the value
118+
let _ = Box::from_raw(v);
119+
120+
return Err(Throw::new());
121+
}
122+
// If an unexpected error occurs, we cannot safely free the value
123+
// because `finalize` may be called later.
124+
res => res.unwrap(),
125+
}
126+
127+
#[cfg(feature = "napi-8")]
128+
match sys::type_tag_object(env, o, &*crate::MODULE_TAG) {
129+
Err(sys::Status::InvalidArg) => return Ok(Err(WrapError::foreign_type())),
130+
res => res.unwrap(),
131+
}
132+
}
133+
134+
Ok(Ok(()))
135+
}
136+
137+
pub fn unwrap<'cx, T, V>(cx: &mut Cx, o: Handle<'cx, V>) -> NeonResult<Result<&'cx T, WrapError>>
138+
where
139+
T: Finalize + 'static,
140+
V: Object,
141+
{
142+
let env = cx.env().to_raw();
143+
let o = o.to_local();
144+
145+
#[cfg(feature = "napi-8")]
146+
// # Safety
147+
// The `env` value was obtained from a valid `Cx` and the `o` handle has
148+
// already been verified to be an object.
149+
unsafe {
150+
let mut is_tagged = false;
151+
152+
match sys::check_object_type_tag(env, o, &*crate::MODULE_TAG, &mut is_tagged) {
153+
Err(sys::Status::PendingException) => return Err(Throw::new()),
154+
res => res.unwrap(),
155+
}
156+
157+
if !is_tagged {
158+
return Ok(Err(WrapError::not_wrapped()));
159+
}
160+
}
161+
162+
// # Safety
163+
// The `env` value was obtained from a valid `Cx` and the `o` handle has
164+
// already been verified to be an object.
165+
let data = unsafe {
166+
let mut data = MaybeUninit::<*mut BoxAny>::uninit();
167+
168+
match sys::unwrap(env, o, data.as_mut_ptr().cast()) {
169+
Err(sys::Status::PendingException) => return Err(Throw::new()),
170+
res => res.unwrap(),
171+
}
172+
173+
// # Safety
174+
// Since `unwrap` was successful, we know this is a valid pointer. On Node-API
175+
// versions 8 and higher, we are also guaranteed it is a `BoxAny`.
176+
&*data.assume_init()
177+
};
178+
179+
Ok(data.downcast_ref().ok_or_else(WrapError::not_wrapped))
180+
}

crates/neon/src/sys/bindings/functions.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,17 @@ mod napi1 {
258258
message: *const c_char,
259259
message_len: usize,
260260
);
261+
262+
fn wrap(
263+
env: Env,
264+
js_object: Value,
265+
native_object: *mut c_void,
266+
finalize_cb: Finalize,
267+
finalize_hint: *mut c_void,
268+
result: *mut Ref,
269+
) -> Status;
270+
271+
fn unwrap(env: Env, js_object: Value, result: *mut *mut c_void) -> Status;
261272
}
262273
);
263274
}

test/napi/lib/boxed.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,25 @@ describe("boxed", function () {
7272

7373
assert.throws(() => addon.person_greet(unit), /failed to downcast/);
7474
});
75+
76+
it("should be able to wrap a Rust value in an object", () => {
77+
const msg = "Hello, World!";
78+
const o = {};
79+
80+
addon.wrapString(o, msg);
81+
assert.strictEqual(addon.unwrapString(o), msg);
82+
});
83+
84+
it("should not be able to wrap an object twice", () => {
85+
const o = {};
86+
87+
addon.wrapString(o, "Hello, World!");
88+
assert.throws(() => addon.wrapString(o, "nope"), /already wrapped/);
89+
});
90+
91+
it("should not be able to unwrap an object that was not wrapped", () => {
92+
const o = {};
93+
94+
assert.throws(() => addon.unwrapString(o), /not wrapped/);
95+
});
7596
});

test/napi/src/js/boxed.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,13 @@ fn boxed_string_concat(Boxed(this): Boxed<String>, rhs: String) -> String {
8888
fn boxed_string_repeat(_cx: &mut FunctionContext, this: Boxed<String>, n: f64) -> String {
8989
this.0.repeat(n as usize)
9090
}
91+
92+
#[neon::export]
93+
fn wrap_string(cx: &mut Cx, o: Handle<JsObject>, s: String) -> NeonResult<()> {
94+
neon::object::wrap(cx, o, s)?.or_throw(cx)
95+
}
96+
97+
#[neon::export]
98+
fn unwrap_string(cx: &mut Cx, o: Handle<JsObject>) -> NeonResult<String> {
99+
neon::object::unwrap(cx, o)?.map(String::clone).or_throw(cx)
100+
}

0 commit comments

Comments
 (0)