Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions mozjs/benches/latin1_string_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use criterion::{
use mozjs::context::JSContext;
use mozjs::conversions::jsstr_to_string;
use mozjs::glue::{CreateJSExternalStringCallbacks, JSExternalStringCallbacksTraps};
use mozjs::jsapi::{JSAutoRealm, OnNewGlobalHookOption};
use mozjs::jsapi::OnNewGlobalHookOption;
use mozjs::realm::AutoRealm;
use mozjs::rooted;
use mozjs::rust::wrappers2::{JS_NewExternalStringLatin1, JS_NewGlobalObject};
use mozjs::rust::{JSEngine, RealmOptions, Runtime, SIMPLE_GLOBAL_CLASS};
Expand Down Expand Up @@ -72,17 +73,17 @@ fn external_string(c: &mut Criterion) {
h_option,
&*c_option,
)});
let _ac = JSAutoRealm::new(unsafe { context.raw_cx() }, global.get());
let mut realm = AutoRealm::new_from_handle(context, global.handle());

let mut group = c.benchmark_group("Latin1 conversion");

let ascii_example = b"test latin-1 tes";
bench_str_repetition(&mut group, context, "ascii a-z", ascii_example);
bench_str_repetition(&mut group, &mut realm, "ascii a-z", ascii_example);
// fastpath for the first few characters, then slowpath for the remaining (long part)
// TODO: make generator functions, so we can define at which percentage of the size
// the first high byte shows up (which forces the slow path).
let ascii_with_high = b"test latin-1 \xD6\xC0\xFF";
bench_str_repetition(&mut group, context, "ascii with high", ascii_with_high);
bench_str_repetition(&mut group, &mut realm, "ascii with high", ascii_with_high);
}

static EXTERNAL_STRING_CALLBACKS_TRAPS: JSExternalStringCallbacksTraps =
Expand Down
6 changes: 4 additions & 2 deletions mozjs/examples/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ use ::std::ptr;
use mozjs::jsapi::OnNewGlobalHookOption;
use mozjs::jsval::UndefinedValue;
use mozjs::rooted;
use mozjs::rust::evaluate_script;
use mozjs::rust::wrappers2::*;
use mozjs::rust::CompileOptionsWrapper;
use mozjs::rust::SIMPLE_GLOBAL_CLASS;
use mozjs::rust::{JSEngine, RealmOptions, Runtime};

Expand All @@ -45,8 +47,8 @@ fn run(mut rt: Runtime) {
*/
let source: &'static str = "40 + 2";

let options = rt.new_compile_options(filename, lineno);
let res = rt.evaluate_script(global.handle(), source, rval.handle_mut(), options);
let options = CompileOptionsWrapper::new(rt.cx_no_gc(), filename, lineno);
let res = evaluate_script(rt.cx(), global.handle(), source, rval.handle_mut(), options);

if res.is_ok() {
/* Should get a number back from the example source. */
Expand Down
4 changes: 3 additions & 1 deletion mozjs/examples/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use ::std::ptr::null_mut;
use mozjs::jsapi::*;
use mozjs::jsval::ObjectValue;
use mozjs::jsval::UndefinedValue;
use mozjs::realm::AutoRealm;
use mozjs::rooted;
use mozjs::rust::wrappers2::{
Call, Construct1, JS_DefineFunction, JS_GetProperty, JS_NewGlobalObject, JS_NewPlainObject,
Expand Down Expand Up @@ -58,7 +59,8 @@ fn run(mut rt: Runtime) {
OnNewGlobalHookOption::FireOnNewGlobalHook,
&*options)
});
let _ac = JSAutoRealm::new(unsafe { cx.raw_cx() }, global.get());
let mut realm = AutoRealm::new_from_handle(cx, global.handle());
let cx = &mut realm;

// Get WebAssembly.Module and WebAssembly.Instance constructors.
rooted!(&in(cx) let mut wasm = UndefinedValue());
Expand Down
2 changes: 1 addition & 1 deletion mozjs/src/generate_wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def replace_in_line(fn: tuple[str, str | None]) -> str:
.replace("*mut JSContext", "&mut JSContext")
.replace("*const JSContext", "&JSContext")
)
if link_name in no_gc:
if link_name in no_gc or "NewCompileOptions" in sig:
sig = sig.replace("&mut JSContext", "&JSContext")
return sig

Expand Down
2 changes: 1 addition & 1 deletion mozjs/src/glue2_wrappers.in.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ wrap!(glue: pub fn InvokeHasOwn(handler: *const ::std::os::raw::c_void, cx: &mut
wrap!(glue: pub fn CallJitGetterOp(info: *const JSJitInfo, cx: &mut JSContext, thisObj: HandleObject, specializedThis: *mut ::std::os::raw::c_void, argc: ::std::os::raw::c_uint, vp: *mut Value) -> bool);
wrap!(glue: pub fn CallJitSetterOp(info: *const JSJitInfo, cx: &mut JSContext, thisObj: HandleObject, specializedThis: *mut ::std::os::raw::c_void, argc: ::std::os::raw::c_uint, vp: *mut Value) -> bool);
wrap!(glue: pub fn CallJitMethodOp(info: *const JSJitInfo, cx: &mut JSContext, thisObj: HandleObject, specializedThis: *mut ::std::os::raw::c_void, argc: u32, vp: *mut Value) -> bool);
wrap!(glue: pub fn NewCompileOptions(aCx: &mut JSContext, aFile: *const ::std::os::raw::c_char, aLine: ::std::os::raw::c_uint) -> *mut ReadOnlyCompileOptions);
wrap!(glue: pub fn NewCompileOptions(aCx: &JSContext, aFile: *const ::std::os::raw::c_char, aLine: ::std::os::raw::c_uint) -> *mut ReadOnlyCompileOptions);
wrap!(glue: pub fn NewProxyObject(aCx: &mut JSContext, aHandler: *const ::std::os::raw::c_void, aPriv: HandleValue, proto: *mut JSObject, aClass: *const JSClass, aLazyProto: bool) -> *mut JSObject);
wrap!(glue: pub fn WrapperNew(aCx: &mut JSContext, aObj: HandleObject, aHandler: *const ::std::os::raw::c_void, aClass: *const JSClass) -> *mut JSObject);
wrap!(glue: pub fn NewWindowProxy(aCx: &mut JSContext, aObj: HandleObject, aHandler: *const ::std::os::raw::c_void) -> *mut JSObject);
Expand Down
1 change: 1 addition & 0 deletions mozjs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub mod conversions;
pub mod error;
pub mod gc;
pub mod panic;
pub mod realm;
pub mod typedarray;

pub use crate::consts::*;
Expand Down
196 changes: 196 additions & 0 deletions mozjs/src/realm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;

use crate::jsapi::JS::Realm;
use crate::jsapi::{JSAutoRealm, JSObject};

use crate::context::JSContext;
use crate::gc::Handle;
use crate::rust::wrappers2::{CurrentGlobalOrNull, GetCurrentRealmOrNull};

/// Safe wrapper around [JSAutoRealm].
///
/// On creation it enters the realm of the target object,
/// realm becomes current (it's on top of the realm stack).
/// Drop exits realm.
///
/// While creating [AutoRealm] will not trigger GC,
/// it still takes `&mut JSContext`, because it can be used in place of [JSContext] (by [Deref]/[DerefMut]).
/// with additional information of entered/current realm:
/// ```compile_fail
/// use mozjs::context::JSContext;
/// use mozjs::jsapi::JSObject;
/// use mozjs::realm::AutoRealm;
/// use std::ptr::NonNull;
///
/// fn f(cx: &mut JSContext, target: NonNull<JSObject>) {
/// let realm = AutoRealm::new(cx, target);
/// f(cx, target); // one cannot use JSContext here,
/// // because that could allow out of order realm drops.
/// }
/// ```
/// instead do this:
/// ```
/// use mozjs::context::JSContext;
/// use mozjs::jsapi::JSObject;
/// use mozjs::realm::AutoRealm;
/// use std::ptr::NonNull;
///
/// fn f(cx: &mut JSContext, target: NonNull<JSObject>) {
/// let mut realm = AutoRealm::new(cx, target);
/// let cx = &mut realm; // this JSContext is bounded to AutoRealm
/// // which in turn is bounded to original JSContext
/// f(cx, target);
/// }
/// ```
///
/// This also enforces LIFO entering/exiting realms, which is not enforced by [JSAutoRealm]:
/// ```compile_fail
/// use mozjs::context::JSContext;
/// use mozjs::jsapi::JSObject;
/// use mozjs::realm::AutoRealm;
/// use std::ptr::NonNull;
///
/// fn f(cx: &mut JSContext, t1: NonNull<JSObject>, t2: NonNull<JSObject>) {
/// let mut realm1 = AutoRealm::new(cx, t1);
/// let cx = &mut realm1;
/// let realm2 = AutoRealm::new(cx, t2);
/// drop(realm1); // it's not possible to drop realm1 before realm2
/// }
/// ```
pub struct AutoRealm<'cx> {
cx: JSContext,
realm: JSAutoRealm,
phantom: PhantomData<&'cx mut ()>,
}

impl<'cx> AutoRealm<'cx> {
/// Enters the realm of the given target object.
/// The realm becomes the current realm (it's on top of the realm stack).
/// The realm is exited when the [AutoRealm] is dropped.
///
/// While this function will not trigger GC (it will in fact root the object)
/// but because [AutoRealm] can act as a [JSContext] we need to take `&mut JSContext`.
pub fn new(cx: &'cx mut JSContext, target: NonNull<JSObject>) -> AutoRealm<'cx> {
let realm = JSAutoRealm::new(unsafe { cx.raw_cx_no_gc() }, target.as_ptr());
AutoRealm {
cx: unsafe { JSContext::from_ptr(NonNull::new_unchecked(cx.raw_cx())) },
realm,
phantom: PhantomData,
}
}

/// Enters the realm of the given target object.
/// The realm becomes the current realm (it's on top of the realm stack).
/// The realm is exited when the [AutoRealm] is dropped.
///
/// While this function will not trigger GC (it will in fact root the object)
/// but because [AutoRealm] can act as a [JSContext] we need to take `&mut JSContext`.
pub fn new_from_handle(
cx: &'cx mut JSContext,
target: Handle<*mut JSObject>,
) -> AutoRealm<'cx> {
Self::new(cx, NonNull::new(target.get()).unwrap())
}

/// If we can get &mut AutoRealm then we are current realm,
/// because if there existed other current realm, we couldn't get &mut AutoRealm.
pub fn current_realm(&'cx mut self) -> CurrentRealm<'cx> {
CurrentRealm::assert(self)
}

/// Obtain the handle to the global object of the current realm.
pub fn global(&'_ self) -> Handle<'_, *mut JSObject> {
// SAFETY: object is rooted by realm
unsafe { Handle::from_marked_location(CurrentGlobalOrNull(&*self) as _) }
}

/// Erase the lifetime of this [AutoRealm].
///
/// # Safety
/// - The caller must ensure that the [AutoRealm] does not outlive the [JSContext] it was created with.
pub unsafe fn erase_lifetime(self) -> AutoRealm<'static> {
std::mem::transmute(self)
}

pub fn realm(&self) -> &JSAutoRealm {
&self.realm
}
}

impl<'cx> Deref for AutoRealm<'cx> {
type Target = JSContext;

fn deref(&'_ self) -> &'_ Self::Target {
&self.cx
}
}

impl<'cx> DerefMut for AutoRealm<'cx> {
fn deref_mut(&'_ mut self) -> &'_ mut Self::Target {
&mut self.cx
}
}

impl<'cx> Drop for AutoRealm<'cx> {
// if we do not implement this rust can shorten lifetime of cx,
// without effecting JSAutoRealm (realm drops after we lose lifetime of cx)
fn drop(&mut self) {}
}

/// Represents the current realm of [JSContext] (top realm on realm stack).
///
/// Similarly to [AutoRealm], while you can access this type via `&mut`/`&mut`
/// we know that this realm is current (on top of realm stack).
///
/// ```compile_fail
/// use mozjs::context::JSContext;
/// use mozjs::jsapi::JSObject;
/// use mozjs::realm::{AutoRealm, CurrentRealm};
/// use std::ptr::NonNull;
///
/// fn f(current_realm: &mut CurrentRealm, target: NonNull<JSObject>) {
/// let mut realm = AutoRealm::new(current_realm, target);
/// let cx: &mut JSContext = &mut *current_realm; // we cannot use current realm while it's not current
/// }
/// ```
pub struct CurrentRealm<'cx> {
cx: &'cx mut JSContext,
realm: NonNull<Realm>,
}

impl<'cx> CurrentRealm<'cx> {
/// Asserts that the current realm is valid and returns it.
pub fn assert(cx: &'cx mut JSContext) -> CurrentRealm<'cx> {
let realm = unsafe { GetCurrentRealmOrNull(cx) };
CurrentRealm {
cx,
realm: NonNull::new(realm).unwrap(),
}
}

/// Obtain the handle to the global object of the current realm.
pub fn global(&'_ self) -> Handle<'_, *mut JSObject> {
// SAFETY: object is rooted by realm
unsafe { Handle::from_marked_location(CurrentGlobalOrNull(&*self) as _) }
}

pub fn realm(&self) -> &NonNull<Realm> {
&self.realm
}
}

impl<'cx> Deref for CurrentRealm<'cx> {
type Target = JSContext;

fn deref(&'_ self) -> &'_ Self::Target {
&self.cx
}
}

impl<'cx> DerefMut for CurrentRealm<'cx> {
fn deref_mut(&'_ mut self) -> &'_ mut Self::Target {
&mut self.cx
}
}
39 changes: 14 additions & 25 deletions mozjs/src/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,26 @@ use crate::jsapi::JS_AddExtraGCRootsTracer;
use crate::jsapi::MutableHandleIdVector as RawMutableHandleIdVector;
use crate::jsapi::{already_AddRefed, jsid};
use crate::jsapi::{BuildStackString, CaptureCurrentStack, StackFormat};
use crate::jsapi::{Evaluate2, HandleValueArray, StencilRelease};
use crate::jsapi::{HandleValueArray, StencilRelease};
use crate::jsapi::{InitSelfHostedCode, IsWindowSlow};
use crate::jsapi::{
JSAutoRealm, JS_SetGCParameter, JS_SetNativeStackQuota, JS_WrapObject, JS_WrapValue,
};
use crate::jsapi::{JSAutoStructuredCloneBuffer, JSStructuredCloneCallbacks, StructuredCloneScope};
use crate::jsapi::{JSClass, JSClassOps, JSContext, Realm, JSCLASS_RESERVED_SLOTS_SHIFT};
use crate::jsapi::{JSErrorReport, JSFunctionSpec, JSGCParamKey};
use crate::jsapi::{JSObject, JSPropertySpec, JSRuntime};
use crate::jsapi::{JSString, Object, PersistentRootedIdVector};
use crate::jsapi::{JS_DefineFunctions, JS_DefineProperties, JS_DestroyContext, JS_ShutDown};
use crate::jsapi::{JS_EnumerateStandardClasses, JS_GetRuntime, JS_GlobalObjectTraceHook};
use crate::jsapi::{JS_EnumerateStandardClasses, JS_GlobalObjectTraceHook};
use crate::jsapi::{JS_MayResolveStandardClass, JS_NewContext, JS_ResolveStandardClass};
use crate::jsapi::{JS_RequestInterruptCallback, JS_RequestInterruptCallbackCanWait};
use crate::jsapi::{JS_SetGCParameter, JS_SetNativeStackQuota, JS_WrapObject, JS_WrapValue};
use crate::jsapi::{JS_StackCapture_AllFrames, JS_StackCapture_MaxFrames};
use crate::jsapi::{PersistentRootedObjectVector, ReadOnlyCompileOptions, RootingContext};
use crate::jsapi::{SetWarningReporter, SourceText, ToBooleanSlow};
use crate::jsapi::{ToInt32Slow, ToInt64Slow, ToNumberSlow, ToStringSlow, ToUint16Slow};
use crate::jsapi::{ToUint32Slow, ToUint64Slow, ToWindowProxyIfWindowSlow};
use crate::jsval::{JSVal, ObjectValue};
use crate::panic::maybe_resume_unwind;
use crate::realm::AutoRealm;
use log::{debug, warn};
use mozjs_sys::jsapi::JS::SavedFrameResult;
pub use mozjs_sys::jsgc::{GCMethods, IntoHandle, IntoMutableHandle};
Expand Down Expand Up @@ -397,8 +396,7 @@ impl Runtime {

/// Returns the `JSRuntime` object.
pub fn rt(&self) -> *mut JSRuntime {
// SAFETY: JS_GetRuntime does not trigger GC
unsafe { JS_GetRuntime(self.cx.raw_cx_no_gc()) }
unsafe { wrappers2::JS_GetRuntime(self.cx_no_gc()) }
}

/// Returns the `JSContext` object.
Expand All @@ -410,21 +408,6 @@ impl Runtime {
pub fn cx_no_gc<'rt>(&'rt self) -> &'rt crate::context::JSContext {
&self.cx
}

pub fn evaluate_script(
&mut self,
glob: HandleObject,
script: &str,
rval: MutableHandleValue,
options: CompileOptionsWrapper,
) -> Result<(), ()> {
evaluate_script(self.cx(), glob, script, rval, options)
}

pub fn new_compile_options(&self, filename: &str, line: u32) -> CompileOptionsWrapper {
// SAFETY: `cx` argument points to a non-null, valid JSContext
unsafe { CompileOptionsWrapper::new(self.cx_no_gc().raw_cx_no_gc(), filename, line) }
}
}

pub fn evaluate_script(
Expand All @@ -440,11 +423,11 @@ pub fn evaluate_script(
script
);

let _ac = JSAutoRealm::new(unsafe { cx.raw_cx() }, glob.get());
let mut realm = AutoRealm::new_from_handle(cx, glob);

unsafe {
let mut source = transform_str_to_source_text(&script);
if !Evaluate2(cx.raw_cx(), options.ptr, &mut source, rval.into()) {
if !wrappers2::Evaluate2(&mut realm, options.ptr, &mut source, rval.into()) {
debug!("...err!");
maybe_resume_unwind();
Err(())
Expand Down Expand Up @@ -550,10 +533,16 @@ pub struct CompileOptionsWrapper {
}

impl CompileOptionsWrapper {
pub fn new(cx: &crate::context::JSContext, filename: &str, line: u32) -> Self {
let filename = CString::new(filename.as_bytes()).unwrap();
let ptr = unsafe { wrappers2::NewCompileOptions(cx, filename.as_ptr(), line) };
assert!(!ptr.is_null());
Self { ptr, filename }
}
/// # Safety
/// `cx` must point to a non-null, valid [`JSContext`].
/// To create an instance from safe code, use [`Runtime::new_compile_options`].
pub unsafe fn new(cx: *mut JSContext, filename: &str, line: u32) -> Self {
pub unsafe fn new_raw(cx: *mut JSContext, filename: &str, line: u32) -> Self {
let filename = CString::new(filename.as_bytes()).unwrap();
let ptr = NewCompileOptions(cx, filename.as_ptr(), line);
assert!(!ptr.is_null());
Expand Down
Loading