rquickjs_core/context/
base.rs

1use super::{ctx::RefCountHeader, intrinsic, r#ref::ContextRef, ContextBuilder, Intrinsic};
2use crate::{qjs, Ctx, Error, Result, Runtime};
3use std::{mem, ptr::NonNull};
4
5pub(crate) struct Inner {
6    pub(crate) ctx: NonNull<qjs::JSContext>,
7    pub(crate) rt: Runtime,
8}
9
10impl Clone for Inner {
11    fn clone(&self) -> Inner {
12        let ctx = unsafe { NonNull::new_unchecked(qjs::JS_DupContext(self.ctx.as_ptr())) };
13        let rt = self.rt.clone();
14        Self { ctx, rt }
15    }
16}
17
18/// A single execution context with its own global variables and stack.
19///
20/// Can share objects with other contexts of the same runtime.
21#[derive(Clone)]
22pub struct Context(pub(crate) ContextRef<Inner>);
23
24impl Context {
25    /// Create a unused context from a raw context pointer.
26    ///
27    /// # Safety
28    /// Pointer must point to a context from the given runtime.
29    /// The context must also have valid reference count, one which can be decremented when this
30    /// object is dropped without going negative.
31    pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>, rt: Runtime) -> Self {
32        Context(ContextRef::new(Inner { ctx, rt }))
33    }
34
35    pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
36        self.0.ctx
37    }
38
39    /// Creates a base context with only the required functions registered.
40    /// If additional functions are required use [`Context::custom`],
41    /// [`Context::builder`] or [`Context::full`].
42    pub fn base(runtime: &Runtime) -> Result<Self> {
43        Self::custom::<intrinsic::None>(runtime)
44    }
45
46    /// Creates a context with only the required intrinsics registered.
47    /// If additional functions are required use [`Context::custom`],
48    /// [`Context::builder`] or [`Context::full`].
49    pub fn custom<I: Intrinsic>(runtime: &Runtime) -> Result<Self> {
50        let guard = runtime.inner.lock();
51        let ctx = NonNull::new(unsafe { qjs::JS_NewContextRaw(guard.rt.as_ptr()) })
52            .ok_or_else(|| Error::Allocation)?;
53        // rquickjs assumes the base objects exist, so we allways need to add this.
54        unsafe { qjs::JS_AddIntrinsicBaseObjects(ctx.as_ptr()) };
55        unsafe { I::add_intrinsic(ctx) };
56        let res = Inner {
57            ctx,
58            rt: runtime.clone(),
59        };
60        mem::drop(guard);
61
62        Ok(Context(ContextRef::new(res)))
63    }
64
65    /// Creates a context with all standard available intrinsics registered.
66    /// If precise control is required of which functions are available use
67    /// [`Context::custom`] or [`Context::builder`].
68    pub fn full(runtime: &Runtime) -> Result<Self> {
69        let guard = runtime.inner.lock();
70        let ctx = NonNull::new(unsafe { qjs::JS_NewContext(guard.rt.as_ptr()) })
71            .ok_or_else(|| Error::Allocation)?;
72        let res = Inner {
73            ctx,
74            rt: runtime.clone(),
75        };
76        // Explicitly drop the guard to ensure it is valid during the entire use of runtime
77        mem::drop(guard);
78
79        Ok(Context(ContextRef::new(res)))
80    }
81
82    /// Create a context builder for creating a context with a specific set of intrinsics
83    pub fn builder() -> ContextBuilder<()> {
84        ContextBuilder::default()
85    }
86
87    /// Returns the associated runtime
88    pub fn runtime(&self) -> &Runtime {
89        &self.0.rt
90    }
91
92    #[allow(dead_code)]
93    pub fn get_runtime_ptr(&self) -> *mut qjs::JSRuntime {
94        unsafe { qjs::JS_GetRuntime(self.0.ctx.as_ptr()) }
95    }
96
97    /// A entry point for manipulating and using JavaScript objects and scripts.
98    /// The api is structured this way to avoid repeated locking the runtime when ever
99    /// any function is called. This way the runtime is locked once before executing the callback.
100    /// Furthermore, this way it is impossible to use values from different runtimes in this
101    /// context which would otherwise be undefined behavior.
102    ///
103    ///
104    /// This is the only way to get a [`Ctx`] object.
105    pub fn with<F, R>(&self, f: F) -> R
106    where
107        F: FnOnce(Ctx) -> R,
108    {
109        let guard = self.0.rt.inner.lock();
110        guard.update_stack_top();
111        let ctx = unsafe { Ctx::new(self) };
112        f(ctx)
113    }
114}
115
116impl Drop for Context {
117    fn drop(&mut self) {
118        //TODO
119        let guard = match self.0.rt.inner.try_lock() {
120            Some(x) => x,
121            None => {
122                let p = unsafe { &mut *(self.0.ctx.as_ptr() as *mut RefCountHeader) };
123                if p.ref_count <= 1 {
124                    // Lock was poisoned, this should only happen on a panic.
125                    // We should still free the context.
126                    // TODO see if there is a way to recover from a panic which could cause the
127                    // following assertion to trigger
128                    assert!(std::thread::panicking());
129                }
130                unsafe { qjs::JS_FreeContext(self.0.ctx.as_ptr()) }
131                return;
132            }
133        };
134        guard.update_stack_top();
135        unsafe { qjs::JS_FreeContext(self.0.ctx.as_ptr()) }
136        // Explicitly drop the guard to ensure it is valid during the entire use of runtime
137        mem::drop(guard);
138    }
139}
140
141// Since the reference to runtime is behind a Arc this object is send
142//
143#[cfg(feature = "parallel")]
144unsafe impl Send for Context {}
145
146// Since all functions lock the global runtime lock access is synchronized so
147// this object is sync
148#[cfg(feature = "parallel")]
149unsafe impl Sync for Context {}
150
151#[cfg(test)]
152mod test {
153    use super::*;
154    use crate::*;
155
156    #[test]
157    fn basic() {
158        test_with(|ctx| {
159            let val: Value = ctx.eval(r#"1+1"#).unwrap();
160
161            assert_eq!(val.type_of(), Type::Int);
162            assert_eq!(i32::from_js(&ctx, val).unwrap(), 2);
163            println!("{:?}", ctx.globals());
164        });
165    }
166
167    #[test]
168    fn minimal() {
169        let rt = Runtime::new().unwrap();
170        let ctx = Context::builder()
171            .with::<intrinsic::Eval>()
172            .build(&rt)
173            .unwrap();
174        ctx.with(|ctx| {
175            let val: i32 = ctx.eval(r#"1+1"#).unwrap();
176
177            assert_eq!(val, 2);
178            println!("{:?}", ctx.globals());
179        });
180    }
181
182    #[test]
183    fn base() {
184        let rt = Runtime::new().unwrap();
185        let _ = Context::base(&rt).unwrap();
186    }
187
188    #[test]
189    fn module() {
190        test_with(|ctx| {
191            Module::evaluate(
192                ctx,
193                "test_mod",
194                r#"
195                    let t = "3";
196                    let b = (a) => a + 3;
197                    export { b, t}
198                "#,
199            )
200            .unwrap()
201            .finish::<()>()
202            .unwrap();
203        });
204    }
205
206    #[test]
207    #[cfg(feature = "parallel")]
208    fn parallel() {
209        use std::thread;
210
211        let rt = Runtime::new().unwrap();
212        let ctx = Context::full(&rt).unwrap();
213        ctx.with(|ctx| {
214            let _: () = ctx.eval("this.foo = 42").unwrap();
215        });
216        thread::spawn(move || {
217            ctx.with(|ctx| {
218                let i: i32 = ctx.eval("foo + 8").unwrap();
219                assert_eq!(i, 50);
220            });
221        })
222        .join()
223        .unwrap();
224    }
225
226    #[test]
227    #[cfg(feature = "parallel")]
228    fn parallel_drop() {
229        use std::{
230            sync::{Arc, Barrier},
231            thread,
232        };
233
234        let wait_for_entry = Arc::new(Barrier::new(2));
235
236        let rt = Runtime::new().unwrap();
237        let ctx_1 = Context::full(&rt).unwrap();
238        let ctx_2 = Context::full(&rt).unwrap();
239        let wait_for_entry_c = wait_for_entry.clone();
240        thread::spawn(move || {
241            wait_for_entry_c.wait();
242            std::mem::drop(ctx_1);
243            println!("done");
244        });
245
246        ctx_2.with(|ctx| {
247            wait_for_entry.wait();
248            let i: i32 = ctx.eval("2 + 8").unwrap();
249            assert_eq!(i, 10);
250        });
251        println!("done");
252    }
253
254    // Will be improved by https://github.com/quickjs-ng/quickjs/pull/406
255    #[test]
256    #[should_panic(
257        expected = "Error: invalid first character of private name\n    at eval_script:1:1\n"
258    )]
259    fn exception() {
260        test_with(|ctx| {
261            let val = ctx.eval::<(), _>("bla?#@!@ ").catch(&ctx);
262            if let Err(e) = val {
263                assert!(e.is_exception());
264                panic!("{}", e);
265            }
266        });
267    }
268}