use crate::{ callback::new_fn, conversion::RustFunction, module::Module, Atom, ClassID, Error, Promise, Result, Runtime, Value, ValueRef, ValueResult, }; use bitflags::bitflags; use oden_js_sys as sys; use std::ffi::CString; use std::ops::{Deref, DerefMut}; bitflags! { pub struct EvalFlags: u32 { const NONE = 0; /// Force evaluation in "strict" mode, as if "use strict" were at the /// top of the file. const STRICT = sys::JS_EVAL_FLAG_STRICT; /// Force "strip" mode. const STRIP = sys::JS_EVAL_FLAG_STRIP; /// Compile but do not run. The result is a value with a value type /// of `ValueType::FunctionBytecode`, and which can be executed with /// `value.eval_function`. const COMPILE_ONLY = sys::JS_EVAL_FLAG_COMPILE_ONLY; /// Don't include the stack frames before this eval in the Error() /// backtraces. const BACKTRACE_BARRIER = sys::JS_EVAL_FLAG_BACKTRACE_BARRIER; } } #[derive(Debug)] pub struct ContextRef { pub(crate) ctx: *mut sys::JSContext, } // TODO: Should all these require mutability to enforce single-threadedness? impl ContextRef { pub(crate) fn from_raw(ctx: *mut sys::JSContext) -> Self { ContextRef { ctx } } pub fn get_runtime(&self) -> Runtime { Runtime::from_raw(unsafe { sys::JS_GetRuntime(self.ctx) }) } pub fn is_registered_class(&self, id: &ClassID) -> bool { let id = id.get(); let is_registered = unsafe { sys::JS_IsRegisteredClass(sys::JS_GetRuntime(self.ctx), id) }; is_registered != 0 } pub fn add_intrinsic_base_objects(&mut self) { unsafe { sys::JS_AddIntrinsicBaseObjects(self.ctx) } } pub fn add_intrinsic_date(&mut self) { unsafe { sys::JS_AddIntrinsicDate(self.ctx) } } pub fn add_intrinsic_eval(&mut self) { unsafe { sys::JS_AddIntrinsicEval(self.ctx) } } pub fn add_intrinsic_string_normalize(&mut self) { unsafe { sys::JS_AddIntrinsicStringNormalize(self.ctx) } } pub fn add_intrinsic_regexp_compiler(&mut self) { unsafe { sys::JS_AddIntrinsicRegExpCompiler(self.ctx) } } pub fn add_intrinsic_regexp(&mut self) { unsafe { sys::JS_AddIntrinsicRegExp(self.ctx) } } pub fn add_intrinsic_json(&mut self) { unsafe { sys::JS_AddIntrinsicJSON(self.ctx) } } pub fn add_intrinsic_proxy(&mut self) { unsafe { sys::JS_AddIntrinsicProxy(self.ctx) } } pub fn add_intrinsic_map_set(&mut self) { unsafe { sys::JS_AddIntrinsicMapSet(self.ctx) } } pub fn add_intrinsic_typed_arrays(&mut self) { unsafe { sys::JS_AddIntrinsicTypedArrays(self.ctx) } } pub fn add_intrinsic_promise(&mut self) { unsafe { sys::JS_AddIntrinsicPromise(self.ctx) } } pub fn add_intrinsic_bigint(&mut self) { unsafe { sys::JS_AddIntrinsicBigInt(self.ctx) } } pub fn add_intrinsic_bigfloat(&mut self) { unsafe { sys::JS_AddIntrinsicBigFloat(self.ctx) } } pub fn add_intrinsic_bigdecimal(&mut self) { unsafe { sys::JS_AddIntrinsicBigDecimal(self.ctx) } } pub fn add_intrinsic_operators(&mut self) { unsafe { sys::JS_AddIntrinsicOperators(self.ctx) } } pub fn enable_bignum_ext(&mut self, enable: bool) { unsafe { sys::JS_EnableBignumExt(self.ctx, if enable { 1 } else { 0 }) } } /// Evaluate the specified JavaScript code. pub fn eval(&self, input: &str, filename: &str, flags: EvalFlags) -> ValueResult { self.eval_internal(input, filename, sys::JS_EVAL_TYPE_GLOBAL, flags) } /// Evaluate the specified JavaScript code as a module. pub fn eval_module(&self, input: &str, filename: &str) -> Result { let val = self.eval_internal( input, filename, sys::JS_EVAL_TYPE_MODULE, EvalFlags::COMPILE_ONLY, )?; assert!(val.is_module()); unsafe { // NOTE: This might be stupid but we're trying to // intentionally leak the value here; since the module // itself has a lifetime unconstrained by traditional value // semantics. (This is a weird edge in QuickJS.) sys::JS_DupValue(self.ctx, val.val); Ok(Module::from_raw( sys::JS_VALUE_GET_PTR(val.val) as *mut sys::JSModuleDef, self.get_runtime(), )) } } fn eval_internal( &self, input: &str, filename: &str, eval_type: u32, flags: EvalFlags, ) -> ValueResult { let c_input = CString::new(input)?; let c_filename = CString::new(filename)?; let flags_bits: i32 = (eval_type | flags.bits).try_into().unwrap(); unsafe { self.check_exception(sys::JS_Eval( self.ctx, c_input.into_raw(), input.len(), c_filename.into_raw(), flags_bits, )) } } /// Import a module by name. pub fn import_module(&self, name: &str, base: &str) -> Result { let name = CString::new(name)?; let base = CString::new(base)?; let module = unsafe { sys::JS_RunModule(self.ctx, base.as_ptr(), name.as_ptr()) }; if module.is_null() { return Err(self.exception_error()); } Ok(Module::from_raw(module, self.get_runtime())) } /// Construct a new string atom. pub fn new_atom(&self, value: &str) -> Result { let c_value = CString::new(value)?; let atom = unsafe { sys::JS_NewAtomLen(self.ctx, c_value.as_ptr(), value.len()) }; if atom == sys::JS_ATOM_NULL { return Err(self.exception_error()); } Ok(Atom::from_raw(atom, self)) } /// Construct a new value of type object. pub fn new_object(&self) -> ValueResult { self.check_exception(unsafe { sys::JS_NewObject(self.ctx) }) } /// Construct a new value from a boolean. pub fn new_bool(&self, value: T) -> ValueResult where T: Into, { self.check_exception(unsafe { sys::JS_NewBool(self.ctx, value.into()) }) } /// Construct a new value that wraps a strongly-typed closure. pub fn new_fn(&self, func: impl RustFunction + 'static) -> ValueResult { self.new_dynamic_fn(move |c, _, a| func.call(c, a)) } /// Construct a new value that wraps a dynamically-typed closure. pub fn new_dynamic_fn(&self, func: F) -> ValueResult where F: 'static + Fn(&ContextRef, &ValueRef, &[&ValueRef]) -> ValueResult, { // Constructing a new function is complicated enough that it needs to // be out of line. self.check_exception(new_fn(self, func)) } /// Construct a new value from an int32. pub fn new_i32(&self, value: T) -> ValueResult where T: Into, { self.check_exception(unsafe { sys::JS_NewInt32(self.ctx, value.into()) }) } /// Construct a new value from a u32. /// /// This returns a value of type Int32 if the value fits into an int32, /// otherwise it returns a value of type Float64. pub fn new_u32(&self, value: T) -> ValueResult where T: Into, { self.check_exception(unsafe { sys::JS_NewUint32(self.ctx, value.into()) }) } /// Construct a new value from a float64. pub fn new_f64(&self, value: T) -> ValueResult where T: Into, { self.check_exception(unsafe { sys::JS_NewFloat64(self.ctx, value.into()) }) } /// Construct a new BigInt from an i64 pub fn new_i64(&self, value: T) -> ValueResult where T: Into, { self.check_exception(unsafe { sys::JS_NewBigInt64(self.ctx, value.into()) }) } /// Construct a new BigInt from an u64 pub fn new_u64(&self, value: T) -> ValueResult where T: Into, { self.check_exception(unsafe { sys::JS_NewBigUint64(self.ctx, value.into()) }) } /// Construct a new array value. pub fn new_array(&self) -> ValueResult { self.check_exception(unsafe { sys::JS_NewArray(self.ctx) }) } /// Construct a new value from a string. pub fn new_string(&self, value: &str) -> ValueResult { let c_value = CString::new(value)?; self.check_exception(unsafe { sys::JS_NewStringLen(self.ctx, c_value.as_ptr(), value.len()) }) } /// Get the null value. pub fn null(&self) -> Value { let v = sys::JSValue { u: sys::JSValueUnion { ptr: std::ptr::null_mut(), }, tag: sys::JS_TAG_NULL as i64, }; Value::from_raw(v, self) } /// Get the undefined value. pub fn undefined(&self) -> Value { let v = sys::JSValue { u: sys::JSValueUnion { ptr: std::ptr::null_mut(), }, tag: sys::JS_TAG_UNDEFINED as i64, }; Value::from_raw(v, self) } /// Construct a new promise. pub fn new_promise(&self) -> Result<(Value, Promise)> { self.get_runtime().new_promise(self) } /// Construct a new exception object, suitable for throwing. pub fn new_error(&self, message: &str) -> Value { let e = match self.new_string(message) { Ok(e) => e, Err(_) => match self.new_string("INTERNAL ERROR: Embedded NUL in message") { Ok(e) => e, // Faulting this hard is inexcusable. Err(_) => return self.exception(), }, }; unsafe { let err = Value::from_raw(sys::JS_NewError(self.ctx), self); // NOTE: Throughout this function we work at the lower-level // error handling stuff because the errors are easier to // manage. (We know how it can fail!) if sys::JS_ValueGetTag(err.val) == sys::JS_TAG_EXCEPTION { // GIVE UP; This is out of memory anyway things probably // went wrong because of that. We'll return *that* // exception. return self.exception(); } sys::JS_DupValue(self.ctx, e.val); // SetProperty takes ownership. let prop = CString::new("message").unwrap(); if sys::JS_SetPropertyStr(self.ctx, err.val, prop.as_ptr(), e.val) == -1 { // As before, we're just going to take the exception from // the context, and drop the one we were trying to create // on the floor. return self.exception(); } // We put the message in, we can return the value. err } } /// Fetch the global object for the context. pub fn global_object(&self) -> ValueResult { self.check_exception(unsafe { sys::JS_GetGlobalObject(self.ctx) }) } /// Check the value to see if it is the special marker for an exception, /// and if so grab the exception from the context and return it in an /// error value. Otherwise, just return success with the value, wrapped. pub(crate) fn check_exception(&self, val: sys::JSValue) -> ValueResult { if unsafe { sys::JS_ValueGetTag(val) } == sys::JS_TAG_EXCEPTION { Err(self.exception_error()) } else { Ok(Value::from_raw(val, self)) } } /// Fetch the exception value from the context, if any. This is not /// public because anything that might raise an exception should be /// returning a Result<> instead, to separate the exception flow from the /// value flow. pub(crate) fn exception(&self) -> Value { Value::from_raw(unsafe { sys::JS_GetException(self.ctx) }, self) } /// Fetch the exception value from the context, if any. This is not /// public because anything that might raise an exception should be /// returning a Result<> instead, to separate the exception flow from the /// value flow. pub(crate) fn exception_error(&self) -> Error { let exc = self.exception(); let desc = exc.to_string(&self).unwrap_or_else(|_| String::new()); let stack = exc .get_property(&self, "stack") .and_then(|stack| stack.to_string(&self)) .unwrap_or_else(|_| String::new()); Error::Exception(exc, desc, stack) } /// Process all pending async jobs. This includes all promise resolutions. pub fn process_all_jobs(&self) -> Result<()> { // TODO: SAFETY // This is unsafe because multiple contexts can be sharing the same runtime and cause // a race condition on the underlying runtime. loop { let mut ctx1: *mut sys::JSContext = std::ptr::null_mut(); let err = unsafe { sys::JS_ExecutePendingJob(sys::JS_GetRuntime(self.ctx), &mut ctx1) }; if err == 0 { break; } else if err < 0 { return Err(ContextRef::from_raw(ctx1).exception_error()); } } Ok(()) } } #[derive(Debug)] pub struct Context { value: ContextRef, runtime: Runtime, } impl Context { /// Construct a new JavaScript context with many of the useful instrinisc /// functions defined. Objects created by this context can be shared /// across contexts belonging to the same runtime. /// /// This function behaves as if the following methods have been called: /// /// - `add_intrinsic_base_objects` /// - `add_intrinsic_date` /// - `add_intrinsic_eval` /// - `add_intrinsic_string_normalize` /// - `add_intrinsic_regexp` /// - `add_intrinsic_json` /// - `add_intrinsic_proxy` /// - `add_intrinsic_map_set` /// - `add_intrinsic_typed_arrays` /// - `add_intrinsic_promise` /// - `add_intrinsic_bignum` /// /// If you don't want those objects, call `new_raw`, and then add the /// intrinsics that you want. pub fn new(runtime: Runtime) -> Context { let value = unsafe { ContextRef::from_raw(sys::JS_NewContext(runtime.rt)) }; Context { value, runtime } } /// Construct a new JavaScript context without any intrinsic /// objects. Objects created by this context can be shared across /// contexts belonging to the same runtime. /// /// You will probably want to call one or more of the `add_intrinsic_` /// functions to add useful types to the runtime. pub fn new_raw(runtime: Runtime) -> Context { let value = unsafe { ContextRef::from_raw(sys::JS_NewContextRaw(runtime.rt)) }; Context { value, runtime } } /// Get the runtime underlying this context. pub fn runtime(&self) -> &Runtime { &self.runtime } } impl Deref for Context { type Target = ContextRef; fn deref(&self) -> &Self::Target { &self.value } } impl DerefMut for Context { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.value } } impl Drop for Context { fn drop(&mut self) { unsafe { sys::JS_FreeContext(self.value.ctx); } } } #[cfg(test)] mod tests { use super::*; use crate::{ValueRef, ValueType}; #[test] fn basic_evaluation() { let rt = Runtime::new(); let ctx = Context::new(rt); let val = ctx.eval("1+1", "script", EvalFlags::NONE).unwrap(); assert_eq!(String::from("2"), val.to_string(&ctx).unwrap()); } #[test] fn eval_compiled() { let rt = Runtime::new(); let ctx = Context::new(rt); let compiled = ctx .eval("1 + 1", "script", EvalFlags::COMPILE_ONLY) .expect("Unable to compile code"); let result = compiled .eval_function(&ctx) .expect("Unable to execute compiled function"); assert_eq!(String::from("2"), result.to_string(&ctx).unwrap()); } #[test] fn global_object() { let rt = Runtime::new(); let ctx = Context::new(rt); let mut go = ctx.global_object().unwrap(); assert_eq!(ValueType::Object, go.value_type()); let val = ctx.new_i32(12).unwrap(); go.set_property(&ctx, "foo", &val).unwrap(); let result = ctx.eval("foo + 3", "script", EvalFlags::NONE).unwrap(); assert_eq!(String::from("15"), result.to_string(&ctx).unwrap()); } fn cb(ctx: &ContextRef, _this: &ValueRef, args: &[&ValueRef]) -> ValueResult { Ok(args[1].dup(ctx)) } #[test] fn new_objects() { let rt = Runtime::new(); let ctx = Context::new(rt); let mut go = ctx.global_object().unwrap(); let tests = &[ (ctx.new_u32(300u32), "val - 2", "298"), (ctx.new_i32(12), "val + 10", "22"), (ctx.new_f64(12.3), "val + .2", "12.5"), (ctx.new_string("asdf"), "val.toUpperCase()", "ASDF"), (ctx.new_bool(true), "val ? 'yes' : 'no'", "yes"), (ctx.new_bool(false), "val ? 'yes' : 'no'", "no"), (ctx.new_i64(-222), "val", "-222"), (ctx.new_u64(222u64), "val", "222"), (ctx.new_array(), "val.length", "0"), ( ctx.new_dynamic_fn(|c, _, a: &[&ValueRef]| Ok(a[1].dup(c))), "val(1,2)", "2", ), (ctx.new_dynamic_fn(cb), "val(1,2)", "2"), ( ctx.new_fn(|_: &ContextRef, x: i32, y: i32| x * y), "val(3,3)", "9", ), ]; for (vr, expr, expected) in tests.into_iter() { let val = vr.as_ref().unwrap(); go.set_property(&ctx, "val", val).unwrap(); let result = ctx.eval(expr, "script", EvalFlags::NONE).unwrap(); assert_eq!(String::from(*expected), result.to_string(&ctx).unwrap()); } } #[test] fn real_closures() { let rt = Runtime::new(); let ctx = Context::new(rt); let return_value = ctx.new_string("unsafe").unwrap(); { let mut go = ctx.global_object().unwrap(); go.set_property( &ctx, "foo", &ctx.new_dynamic_fn(move |c, _, _| Ok(return_value.dup(c))) .unwrap(), ) .unwrap(); } let result = ctx .eval("foo().toUpperCase()", "script", EvalFlags::NONE) .unwrap(); assert_eq!(String::from("UNSAFE"), result.to_string(&ctx).unwrap()); } #[test] fn modules_with_exports() { let ctx = Context::new(Runtime::new()); let module = ctx .eval_module("const foo = 123; export { foo };", "main.js") .expect("Could not load!"); let foo = module .get_export(&ctx, "foo") .expect("Could not get export"); assert_eq!(String::from("123"), foo.to_string(&ctx).unwrap()); let bar = module .get_export(&ctx, "bar") .expect("Could not get export"); assert!(bar.is_undefined()); } }