neon/object/
wrap.rs

1use std::{any::Any, error, ffi::c_void, fmt, mem::MaybeUninit, ptr};
2
3use crate::{
4    context::{
5        internal::{ContextInternal, Env},
6        Context, Cx,
7    },
8    handle::Handle,
9    object::Object,
10    result::{JsResult, NeonResult, ResultExt, Throw},
11    sys,
12    types::{extract::TryIntoJs, Finalize, Value},
13};
14
15type BoxAny = Box<dyn Any + 'static>;
16
17#[derive(Debug)]
18pub struct WrapError(WrapErrorType);
19
20impl WrapError {
21    fn object_expected() -> Self {
22        Self(WrapErrorType::ObjectExpected)
23    }
24
25    fn already_wrapped() -> Self {
26        Self(WrapErrorType::AlreadyWrapped)
27    }
28
29    #[cfg(feature = "napi-8")]
30    fn not_wrapped() -> Self {
31        Self(WrapErrorType::NotWrapped)
32    }
33
34    fn wrong_type(expected: &'static str) -> Self {
35        Self(WrapErrorType::WrongType(expected))
36    }
37
38    #[cfg(feature = "napi-8")]
39    fn foreign_type() -> Self {
40        Self(WrapErrorType::ForeignType)
41    }
42}
43
44impl fmt::Display for WrapError {
45    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46        write!(f, "{}", self.0)
47    }
48}
49
50impl error::Error for WrapError {}
51
52impl crate::types::extract::private::Sealed for WrapError {}
53
54impl<'cx> TryIntoJs<'cx> for WrapError {
55    type Value = crate::types::JsError;
56
57    fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
58        match self.0 {
59            WrapErrorType::ObjectExpected => cx.type_error("object expected"),
60            _ => cx.type_error(self.to_string()),
61        }
62    }
63}
64
65impl<T> ResultExt<T> for Result<T, WrapError> {
66    fn or_throw<'cx, C>(self, cx: &mut C) -> NeonResult<T>
67    where
68        C: Context<'cx>,
69    {
70        match self {
71            Ok(v) => Ok(v),
72            Err(WrapError(WrapErrorType::ObjectExpected)) => cx.throw_type_error("object expected"),
73            Err(err) => cx.throw_type_error(err.to_string()),
74        }
75    }
76}
77
78#[derive(Debug)]
79enum WrapErrorType {
80    ObjectExpected,
81    AlreadyWrapped,
82    #[cfg(feature = "napi-8")]
83    NotWrapped,
84    WrongType(&'static str),
85    #[cfg(feature = "napi-8")]
86    ForeignType,
87}
88
89fn ref_cell_target_type_name(s: &str) -> Option<String> {
90    if let Some(start) = s.find('<') {
91        let s = &s[start + 1..];
92        if let Some(end) = s.find('>') {
93            return Some(s[0..end].to_string());
94        }
95    }
96    None
97}
98
99impl fmt::Display for WrapErrorType {
100    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101        match self {
102            Self::ObjectExpected => write!(f, "object expected"),
103            Self::AlreadyWrapped => write!(f, "non-class instance expected"),
104            #[cfg(feature = "napi-8")]
105            Self::NotWrapped => write!(f, "class instance expected"),
106            Self::WrongType(expected) => {
107                let target_type_name =
108                    ref_cell_target_type_name(expected).unwrap_or(expected.to_string());
109                write!(f, "expected instance of {}", target_type_name)
110            }
111            #[cfg(feature = "napi-8")]
112            Self::ForeignType => write!(f, "Neon object expected"),
113        }
114    }
115}
116
117pub fn wrap<T, V>(cx: &mut Cx, o: Handle<V>, v: T) -> NeonResult<Result<(), WrapError>>
118where
119    T: Finalize + 'static,
120    V: Object,
121{
122    let env = cx.env().to_raw();
123    let o = o.to_local();
124    let v = Box::into_raw(Box::new(Box::new(v) as BoxAny));
125
126    // # Safety
127    // The `finalize` function will be called when the JavaScript object is garbage
128    // collected. The `data` pointer is guaranteed to be the same pointer passed when
129    // wrapping.
130    unsafe extern "C" fn finalize<T>(env: sys::Env, data: *mut c_void, _hint: *mut c_void)
131    where
132        T: Finalize + 'static,
133    {
134        let data = Box::from_raw(data.cast::<BoxAny>());
135        let data = *data.downcast::<T>().unwrap();
136        let env = Env::from(env);
137
138        Cx::with_context(env, move |mut cx| data.finalize(&mut cx));
139    }
140
141    // # Safety
142    // The `env` value was obtained from a valid `Cx` and the `o` handle has
143    // already been verified to be an object.
144    unsafe {
145        match sys::wrap(
146            env,
147            o,
148            v.cast(),
149            Some(finalize::<T>),
150            ptr::null_mut(),
151            ptr::null_mut(),
152        ) {
153            Err(sys::Status::InvalidArg) => {
154                // Wrap failed, we can safely free the value
155                let _ = Box::from_raw(v);
156
157                return Ok(Err(WrapError::already_wrapped()));
158            }
159            Err(sys::Status::PendingException) => {
160                // Wrap failed, we can safely free the value
161                let _ = Box::from_raw(v);
162
163                return Err(Throw::new());
164            }
165            // If an unexpected error occurs, we cannot safely free the value
166            // because `finalize` may be called later.
167            res => res.unwrap(),
168        }
169
170        #[cfg(feature = "napi-8")]
171        match sys::type_tag_object(env, o, &*crate::MODULE_TAG) {
172            Err(sys::Status::InvalidArg) => {
173                sys::remove_wrap(env, o, ptr::null_mut()).unwrap();
174
175                // Unwrap succeeded, we can safely free the value
176                let _ = Box::from_raw(v);
177
178                return Ok(Err(WrapError::foreign_type()));
179            }
180            res => res.unwrap(),
181        }
182    }
183
184    Ok(Ok(()))
185}
186
187pub fn unwrap<'cx, T, V>(cx: &mut Cx, o: Handle<'cx, V>) -> NeonResult<Result<&'cx T, WrapError>>
188where
189    T: Finalize + 'static,
190    V: Value,
191{
192    let env = cx.env().to_raw();
193    let o = o.to_local();
194
195    #[cfg(feature = "napi-8")]
196    // # Safety
197    // The `env` value was obtained from a valid `Cx`.
198    unsafe {
199        let mut is_tagged = false;
200
201        match sys::check_object_type_tag(env, o, &*crate::MODULE_TAG, &mut is_tagged) {
202            Err(sys::Status::PendingException) => return Err(Throw::new()),
203            Err(sys::Status::ObjectExpected) => return Ok(Err(WrapError::object_expected())),
204            res => res.unwrap(),
205        }
206
207        if !is_tagged {
208            return Ok(Err(WrapError::not_wrapped()));
209        }
210    }
211
212    // # Safety
213    // The `env` value was obtained from a valid `Cx`.
214    let data = unsafe {
215        let mut data = MaybeUninit::<*mut BoxAny>::uninit();
216
217        match sys::unwrap(env, o, data.as_mut_ptr().cast()) {
218            Err(sys::Status::PendingException) => return Err(Throw::new()),
219            Err(sys::Status::ObjectExpected) => return Ok(Err(WrapError::object_expected())),
220            res => res.unwrap(),
221        }
222
223        // # Safety
224        // Since `unwrap` was successful, we know this is a valid pointer. On Node-API
225        // versions 8 and higher, we are also guaranteed it is a `BoxAny`.
226        &*data.assume_init()
227    };
228
229    match data.downcast_ref() {
230        Some(result) => Ok(Ok(result)),
231        None => Ok(Err(WrapError::wrong_type(std::any::type_name::<T>()))),
232    }
233}