Skip to content

Commit 8bf5bf8

Browse files
authored
Create safe realm wrappers (#650)
* deal with realm stuff Signed-off-by: sagudev <[email protected]> * fmt Signed-off-by: sagudev <[email protected]> * There is only one CurrentRealm anyway Signed-off-by: sagudev <[email protected]> * docs Signed-off-by: sagudev <[email protected]> * more docs Signed-off-by: sagudev <[email protected]> * use Deref and DerefMut instead of cx,cx_no_gc This allows us to use realm in place of cx without calling any methods as compiler will deref automatically. Signed-off-by: sagudev <[email protected]> --------- Signed-off-by: sagudev <[email protected]>
1 parent b4c210b commit 8bf5bf8

26 files changed

+409
-152
lines changed

mozjs/benches/latin1_string_conversion.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ use criterion::{
55
use mozjs::context::JSContext;
66
use mozjs::conversions::jsstr_to_string;
77
use mozjs::glue::{CreateJSExternalStringCallbacks, JSExternalStringCallbacksTraps};
8-
use mozjs::jsapi::{JSAutoRealm, OnNewGlobalHookOption};
8+
use mozjs::jsapi::OnNewGlobalHookOption;
9+
use mozjs::realm::AutoRealm;
910
use mozjs::rooted;
1011
use mozjs::rust::wrappers2::{JS_NewExternalStringLatin1, JS_NewGlobalObject};
1112
use mozjs::rust::{JSEngine, RealmOptions, Runtime, SIMPLE_GLOBAL_CLASS};
@@ -72,17 +73,17 @@ fn external_string(c: &mut Criterion) {
7273
h_option,
7374
&*c_option,
7475
)});
75-
let _ac = JSAutoRealm::new(unsafe { context.raw_cx() }, global.get());
76+
let mut realm = AutoRealm::new_from_handle(context, global.handle());
7677

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

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

8889
static EXTERNAL_STRING_CALLBACKS_TRAPS: JSExternalStringCallbacksTraps =

mozjs/examples/eval.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ use ::std::ptr;
1818
use mozjs::jsapi::OnNewGlobalHookOption;
1919
use mozjs::jsval::UndefinedValue;
2020
use mozjs::rooted;
21+
use mozjs::rust::evaluate_script;
2122
use mozjs::rust::wrappers2::*;
23+
use mozjs::rust::CompileOptionsWrapper;
2224
use mozjs::rust::SIMPLE_GLOBAL_CLASS;
2325
use mozjs::rust::{JSEngine, RealmOptions, Runtime};
2426

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

48-
let options = rt.new_compile_options(filename, lineno);
49-
let res = rt.evaluate_script(global.handle(), source, rval.handle_mut(), options);
50+
let options = CompileOptionsWrapper::new(rt.cx_no_gc(), filename, lineno);
51+
let res = evaluate_script(rt.cx(), global.handle(), source, rval.handle_mut(), options);
5052

5153
if res.is_ok() {
5254
/* Should get a number back from the example source. */

mozjs/examples/wasm.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use ::std::ptr::null_mut;
1515
use mozjs::jsapi::*;
1616
use mozjs::jsval::ObjectValue;
1717
use mozjs::jsval::UndefinedValue;
18+
use mozjs::realm::AutoRealm;
1819
use mozjs::rooted;
1920
use mozjs::rust::wrappers2::{
2021
Call, Construct1, JS_DefineFunction, JS_GetProperty, JS_NewGlobalObject, JS_NewPlainObject,
@@ -58,7 +59,8 @@ fn run(mut rt: Runtime) {
5859
OnNewGlobalHookOption::FireOnNewGlobalHook,
5960
&*options)
6061
});
61-
let _ac = JSAutoRealm::new(unsafe { cx.raw_cx() }, global.get());
62+
let mut realm = AutoRealm::new_from_handle(cx, global.handle());
63+
let cx = &mut realm;
6264

6365
// Get WebAssembly.Module and WebAssembly.Instance constructors.
6466
rooted!(&in(cx) let mut wasm = UndefinedValue());

mozjs/src/generate_wrappers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def replace_in_line(fn: tuple[str, str | None]) -> str:
124124
.replace("*mut JSContext", "&mut JSContext")
125125
.replace("*const JSContext", "&JSContext")
126126
)
127-
if link_name in no_gc:
127+
if link_name in no_gc or "NewCompileOptions" in sig:
128128
sig = sig.replace("&mut JSContext", "&JSContext")
129129
return sig
130130

mozjs/src/glue2_wrappers.in.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ wrap!(glue: pub fn InvokeHasOwn(handler: *const ::std::os::raw::c_void, cx: &mut
33
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);
44
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);
55
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);
6-
wrap!(glue: pub fn NewCompileOptions(aCx: &mut JSContext, aFile: *const ::std::os::raw::c_char, aLine: ::std::os::raw::c_uint) -> *mut ReadOnlyCompileOptions);
6+
wrap!(glue: pub fn NewCompileOptions(aCx: &JSContext, aFile: *const ::std::os::raw::c_char, aLine: ::std::os::raw::c_uint) -> *mut ReadOnlyCompileOptions);
77
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);
88
wrap!(glue: pub fn WrapperNew(aCx: &mut JSContext, aObj: HandleObject, aHandler: *const ::std::os::raw::c_void, aClass: *const JSClass) -> *mut JSObject);
99
wrap!(glue: pub fn NewWindowProxy(aCx: &mut JSContext, aObj: HandleObject, aHandler: *const ::std::os::raw::c_void) -> *mut JSObject);

mozjs/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub mod conversions;
5353
pub mod error;
5454
pub mod gc;
5555
pub mod panic;
56+
pub mod realm;
5657
pub mod typedarray;
5758

5859
pub use crate::consts::*;

mozjs/src/realm.rs

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
use std::marker::PhantomData;
2+
use std::ops::{Deref, DerefMut};
3+
use std::ptr::NonNull;
4+
5+
use crate::jsapi::JS::Realm;
6+
use crate::jsapi::{JSAutoRealm, JSObject};
7+
8+
use crate::context::JSContext;
9+
use crate::gc::Handle;
10+
use crate::rust::wrappers2::{CurrentGlobalOrNull, GetCurrentRealmOrNull};
11+
12+
/// Safe wrapper around [JSAutoRealm].
13+
///
14+
/// On creation it enters the realm of the target object,
15+
/// realm becomes current (it's on top of the realm stack).
16+
/// Drop exits realm.
17+
///
18+
/// While creating [AutoRealm] will not trigger GC,
19+
/// it still takes `&mut JSContext`, because it can be used in place of [JSContext] (by [Deref]/[DerefMut]).
20+
/// with additional information of entered/current realm:
21+
/// ```compile_fail
22+
/// use mozjs::context::JSContext;
23+
/// use mozjs::jsapi::JSObject;
24+
/// use mozjs::realm::AutoRealm;
25+
/// use std::ptr::NonNull;
26+
///
27+
/// fn f(cx: &mut JSContext, target: NonNull<JSObject>) {
28+
/// let realm = AutoRealm::new(cx, target);
29+
/// f(cx, target); // one cannot use JSContext here,
30+
/// // because that could allow out of order realm drops.
31+
/// }
32+
/// ```
33+
/// instead do this:
34+
/// ```
35+
/// use mozjs::context::JSContext;
36+
/// use mozjs::jsapi::JSObject;
37+
/// use mozjs::realm::AutoRealm;
38+
/// use std::ptr::NonNull;
39+
///
40+
/// fn f(cx: &mut JSContext, target: NonNull<JSObject>) {
41+
/// let mut realm = AutoRealm::new(cx, target);
42+
/// let cx = &mut realm; // this JSContext is bounded to AutoRealm
43+
/// // which in turn is bounded to original JSContext
44+
/// f(cx, target);
45+
/// }
46+
/// ```
47+
///
48+
/// This also enforces LIFO entering/exiting realms, which is not enforced by [JSAutoRealm]:
49+
/// ```compile_fail
50+
/// use mozjs::context::JSContext;
51+
/// use mozjs::jsapi::JSObject;
52+
/// use mozjs::realm::AutoRealm;
53+
/// use std::ptr::NonNull;
54+
///
55+
/// fn f(cx: &mut JSContext, t1: NonNull<JSObject>, t2: NonNull<JSObject>) {
56+
/// let mut realm1 = AutoRealm::new(cx, t1);
57+
/// let cx = &mut realm1;
58+
/// let realm2 = AutoRealm::new(cx, t2);
59+
/// drop(realm1); // it's not possible to drop realm1 before realm2
60+
/// }
61+
/// ```
62+
pub struct AutoRealm<'cx> {
63+
cx: JSContext,
64+
realm: JSAutoRealm,
65+
phantom: PhantomData<&'cx mut ()>,
66+
}
67+
68+
impl<'cx> AutoRealm<'cx> {
69+
/// Enters the realm of the given target object.
70+
/// The realm becomes the current realm (it's on top of the realm stack).
71+
/// The realm is exited when the [AutoRealm] is dropped.
72+
///
73+
/// While this function will not trigger GC (it will in fact root the object)
74+
/// but because [AutoRealm] can act as a [JSContext] we need to take `&mut JSContext`.
75+
pub fn new(cx: &'cx mut JSContext, target: NonNull<JSObject>) -> AutoRealm<'cx> {
76+
let realm = JSAutoRealm::new(unsafe { cx.raw_cx_no_gc() }, target.as_ptr());
77+
AutoRealm {
78+
cx: unsafe { JSContext::from_ptr(NonNull::new_unchecked(cx.raw_cx())) },
79+
realm,
80+
phantom: PhantomData,
81+
}
82+
}
83+
84+
/// Enters the realm of the given target object.
85+
/// The realm becomes the current realm (it's on top of the realm stack).
86+
/// The realm is exited when the [AutoRealm] is dropped.
87+
///
88+
/// While this function will not trigger GC (it will in fact root the object)
89+
/// but because [AutoRealm] can act as a [JSContext] we need to take `&mut JSContext`.
90+
pub fn new_from_handle(
91+
cx: &'cx mut JSContext,
92+
target: Handle<*mut JSObject>,
93+
) -> AutoRealm<'cx> {
94+
Self::new(cx, NonNull::new(target.get()).unwrap())
95+
}
96+
97+
/// If we can get &mut AutoRealm then we are current realm,
98+
/// because if there existed other current realm, we couldn't get &mut AutoRealm.
99+
pub fn current_realm(&'cx mut self) -> CurrentRealm<'cx> {
100+
CurrentRealm::assert(self)
101+
}
102+
103+
/// Obtain the handle to the global object of the current realm.
104+
pub fn global(&'_ self) -> Handle<'_, *mut JSObject> {
105+
// SAFETY: object is rooted by realm
106+
unsafe { Handle::from_marked_location(CurrentGlobalOrNull(&*self) as _) }
107+
}
108+
109+
/// Erase the lifetime of this [AutoRealm].
110+
///
111+
/// # Safety
112+
/// - The caller must ensure that the [AutoRealm] does not outlive the [JSContext] it was created with.
113+
pub unsafe fn erase_lifetime(self) -> AutoRealm<'static> {
114+
std::mem::transmute(self)
115+
}
116+
117+
pub fn realm(&self) -> &JSAutoRealm {
118+
&self.realm
119+
}
120+
}
121+
122+
impl<'cx> Deref for AutoRealm<'cx> {
123+
type Target = JSContext;
124+
125+
fn deref(&'_ self) -> &'_ Self::Target {
126+
&self.cx
127+
}
128+
}
129+
130+
impl<'cx> DerefMut for AutoRealm<'cx> {
131+
fn deref_mut(&'_ mut self) -> &'_ mut Self::Target {
132+
&mut self.cx
133+
}
134+
}
135+
136+
impl<'cx> Drop for AutoRealm<'cx> {
137+
// if we do not implement this rust can shorten lifetime of cx,
138+
// without effecting JSAutoRealm (realm drops after we lose lifetime of cx)
139+
fn drop(&mut self) {}
140+
}
141+
142+
/// Represents the current realm of [JSContext] (top realm on realm stack).
143+
///
144+
/// Similarly to [AutoRealm], while you can access this type via `&mut`/`&mut`
145+
/// we know that this realm is current (on top of realm stack).
146+
///
147+
/// ```compile_fail
148+
/// use mozjs::context::JSContext;
149+
/// use mozjs::jsapi::JSObject;
150+
/// use mozjs::realm::{AutoRealm, CurrentRealm};
151+
/// use std::ptr::NonNull;
152+
///
153+
/// fn f(current_realm: &mut CurrentRealm, target: NonNull<JSObject>) {
154+
/// let mut realm = AutoRealm::new(current_realm, target);
155+
/// let cx: &mut JSContext = &mut *current_realm; // we cannot use current realm while it's not current
156+
/// }
157+
/// ```
158+
pub struct CurrentRealm<'cx> {
159+
cx: &'cx mut JSContext,
160+
realm: NonNull<Realm>,
161+
}
162+
163+
impl<'cx> CurrentRealm<'cx> {
164+
/// Asserts that the current realm is valid and returns it.
165+
pub fn assert(cx: &'cx mut JSContext) -> CurrentRealm<'cx> {
166+
let realm = unsafe { GetCurrentRealmOrNull(cx) };
167+
CurrentRealm {
168+
cx,
169+
realm: NonNull::new(realm).unwrap(),
170+
}
171+
}
172+
173+
/// Obtain the handle to the global object of the current realm.
174+
pub fn global(&'_ self) -> Handle<'_, *mut JSObject> {
175+
// SAFETY: object is rooted by realm
176+
unsafe { Handle::from_marked_location(CurrentGlobalOrNull(&*self) as _) }
177+
}
178+
179+
pub fn realm(&self) -> &NonNull<Realm> {
180+
&self.realm
181+
}
182+
}
183+
184+
impl<'cx> Deref for CurrentRealm<'cx> {
185+
type Target = JSContext;
186+
187+
fn deref(&'_ self) -> &'_ Self::Target {
188+
&self.cx
189+
}
190+
}
191+
192+
impl<'cx> DerefMut for CurrentRealm<'cx> {
193+
fn deref_mut(&'_ mut self) -> &'_ mut Self::Target {
194+
&mut self.cx
195+
}
196+
}

mozjs/src/rust.rs

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,27 +47,26 @@ use crate::jsapi::JS_AddExtraGCRootsTracer;
4747
use crate::jsapi::MutableHandleIdVector as RawMutableHandleIdVector;
4848
use crate::jsapi::{already_AddRefed, jsid};
4949
use crate::jsapi::{BuildStackString, CaptureCurrentStack, StackFormat};
50-
use crate::jsapi::{Evaluate2, HandleValueArray, StencilRelease};
50+
use crate::jsapi::{HandleValueArray, StencilRelease};
5151
use crate::jsapi::{InitSelfHostedCode, IsWindowSlow};
52-
use crate::jsapi::{
53-
JSAutoRealm, JS_SetGCParameter, JS_SetNativeStackQuota, JS_WrapObject, JS_WrapValue,
54-
};
5552
use crate::jsapi::{JSAutoStructuredCloneBuffer, JSStructuredCloneCallbacks, StructuredCloneScope};
5653
use crate::jsapi::{JSClass, JSClassOps, JSContext, Realm, JSCLASS_RESERVED_SLOTS_SHIFT};
5754
use crate::jsapi::{JSErrorReport, JSFunctionSpec, JSGCParamKey};
5855
use crate::jsapi::{JSObject, JSPropertySpec, JSRuntime};
5956
use crate::jsapi::{JSString, Object, PersistentRootedIdVector};
6057
use crate::jsapi::{JS_DefineFunctions, JS_DefineProperties, JS_DestroyContext, JS_ShutDown};
61-
use crate::jsapi::{JS_EnumerateStandardClasses, JS_GetRuntime, JS_GlobalObjectTraceHook};
58+
use crate::jsapi::{JS_EnumerateStandardClasses, JS_GlobalObjectTraceHook};
6259
use crate::jsapi::{JS_MayResolveStandardClass, JS_NewContext, JS_ResolveStandardClass};
6360
use crate::jsapi::{JS_RequestInterruptCallback, JS_RequestInterruptCallbackCanWait};
61+
use crate::jsapi::{JS_SetGCParameter, JS_SetNativeStackQuota, JS_WrapObject, JS_WrapValue};
6462
use crate::jsapi::{JS_StackCapture_AllFrames, JS_StackCapture_MaxFrames};
6563
use crate::jsapi::{PersistentRootedObjectVector, ReadOnlyCompileOptions, RootingContext};
6664
use crate::jsapi::{SetWarningReporter, SourceText, ToBooleanSlow};
6765
use crate::jsapi::{ToInt32Slow, ToInt64Slow, ToNumberSlow, ToStringSlow, ToUint16Slow};
6866
use crate::jsapi::{ToUint32Slow, ToUint64Slow, ToWindowProxyIfWindowSlow};
6967
use crate::jsval::{JSVal, ObjectValue};
7068
use crate::panic::maybe_resume_unwind;
69+
use crate::realm::AutoRealm;
7170
use log::{debug, warn};
7271
use mozjs_sys::jsapi::JS::SavedFrameResult;
7372
pub use mozjs_sys::jsgc::{GCMethods, IntoHandle, IntoMutableHandle};
@@ -397,8 +396,7 @@ impl Runtime {
397396

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

404402
/// Returns the `JSContext` object.
@@ -410,21 +408,6 @@ impl Runtime {
410408
pub fn cx_no_gc<'rt>(&'rt self) -> &'rt crate::context::JSContext {
411409
&self.cx
412410
}
413-
414-
pub fn evaluate_script(
415-
&mut self,
416-
glob: HandleObject,
417-
script: &str,
418-
rval: MutableHandleValue,
419-
options: CompileOptionsWrapper,
420-
) -> Result<(), ()> {
421-
evaluate_script(self.cx(), glob, script, rval, options)
422-
}
423-
424-
pub fn new_compile_options(&self, filename: &str, line: u32) -> CompileOptionsWrapper {
425-
// SAFETY: `cx` argument points to a non-null, valid JSContext
426-
unsafe { CompileOptionsWrapper::new(self.cx_no_gc().raw_cx_no_gc(), filename, line) }
427-
}
428411
}
429412

430413
pub fn evaluate_script(
@@ -440,11 +423,11 @@ pub fn evaluate_script(
440423
script
441424
);
442425

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

445428
unsafe {
446429
let mut source = transform_str_to_source_text(&script);
447-
if !Evaluate2(cx.raw_cx(), options.ptr, &mut source, rval.into()) {
430+
if !wrappers2::Evaluate2(&mut realm, options.ptr, &mut source, rval.into()) {
448431
debug!("...err!");
449432
maybe_resume_unwind();
450433
Err(())
@@ -550,10 +533,16 @@ pub struct CompileOptionsWrapper {
550533
}
551534

552535
impl CompileOptionsWrapper {
536+
pub fn new(cx: &crate::context::JSContext, filename: &str, line: u32) -> Self {
537+
let filename = CString::new(filename.as_bytes()).unwrap();
538+
let ptr = unsafe { wrappers2::NewCompileOptions(cx, filename.as_ptr(), line) };
539+
assert!(!ptr.is_null());
540+
Self { ptr, filename }
541+
}
553542
/// # Safety
554543
/// `cx` must point to a non-null, valid [`JSContext`].
555544
/// To create an instance from safe code, use [`Runtime::new_compile_options`].
556-
pub unsafe fn new(cx: *mut JSContext, filename: &str, line: u32) -> Self {
545+
pub unsafe fn new_raw(cx: *mut JSContext, filename: &str, line: u32) -> Self {
557546
let filename = CString::new(filename.as_bytes()).unwrap();
558547
let ptr = NewCompileOptions(cx, filename.as_ptr(), line);
559548
assert!(!ptr.is_null());

0 commit comments

Comments
 (0)