rquickjs_core/context/
base.rs

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