Skip to main content

rquickjs_core/context/
base.rs

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