use bao_engine::context::{JsContext, RawValueRootGuard, thread_realm_global};
use mozjs::jsapi::*;
use mozjs::jsval::{JSVal, UndefinedValue};
use mozjs::realm::AutoRealm;
use mozjs::rooted;
fn make_marker_object(ctx: &mut JsContext, marker: i32) -> JSVal {
ctx.eval(
&format!("globalThis.__rvrg_obj = {{ marker: {marker} }};"),
"<guard-test>",
)
.expect("setup eval must succeed");
let mut cx = ctx.cx();
let raw = unsafe { cx.raw_cx_no_gc() };
let global = thread_realm_global().expect("realm global after eval");
rooted!(&in(cx) let g = global);
let mut realm = AutoRealm::new_from_handle(&mut cx, g.handle());
rooted!(&in(realm) let mut out = UndefinedValue());
let got = unsafe {
JS_GetProperty(
raw,
g.handle().into(),
c"__rvrg_obj".as_ptr(),
out.handle_mut().into(),
)
};
assert!(got, "JS_GetProperty(__rvrg_obj) must succeed");
assert!(out.get().is_object(), "planted value must be an object");
out.get()
}
fn read_marker(cx: &mut mozjs::context::JSContext, val: JSVal) -> Option<i32> {
let raw = unsafe { cx.raw_cx_no_gc() };
rooted!(&in(cx) let obj = val.to_object());
let mut realm = AutoRealm::new_from_handle(cx, obj.handle());
rooted!(&in(realm) let mut out = UndefinedValue());
let got = unsafe {
JS_GetProperty(
raw,
obj.handle().into(),
c"marker".as_ptr(),
out.handle_mut().into(),
)
};
if got && out.get().is_int32() {
Some(out.get().to_int32())
} else {
None
}
}
#[test]
fn guard_roots_survive_gc_and_drop_unroots() {
let mut ctx = JsContext::for_test().expect("JsContext::for_test");
let raw = ctx.raw_cx();
let val = make_marker_object(&mut ctx, 7);
let guard = unsafe {
RawValueRootGuard::new(raw, ::std::slice::from_ref(&val), c"guard_test_obj")
}
.expect("RawValueRootGuard::new must succeed");
assert_eq!(guard.len(), 1);
unsafe { JS_GC(raw, JS::GCReason::API) };
let live = guard.get(0);
assert!(live.is_object(), "guarded value must survive GC");
let mut cx = ctx.cx();
assert_eq!(read_marker(&mut cx, live), Some(7));
drop(guard);
unsafe { JS_GC(raw, JS::GCReason::API) };
}
#[test]
fn into_inner_unroots_and_transfers_values() {
let mut ctx = JsContext::for_test().expect("JsContext::for_test");
let raw = ctx.raw_cx();
let v1 = make_marker_object(&mut ctx, 11);
let v2 = make_marker_object(&mut ctx, 22);
let guard = unsafe { RawValueRootGuard::new(raw, &[v1, v2], c"guard_test_pair") }
.expect("RawValueRootGuard::new must succeed");
assert_eq!(guard.len(), 2);
let vals = guard
.into_inner()
.expect("into_inner on live cx must unroot");
assert_eq!(vals.len(), 2);
assert!(vals[0].is_object() && vals[1].is_object());
unsafe { JS_GC(raw, JS::GCReason::API) };
}
#[test]
fn drop_on_foreign_thread_leaks_without_dangling() {
let mut ctx = JsContext::for_test().expect("JsContext::for_test");
let raw = ctx.raw_cx();
let val = make_marker_object(&mut ctx, 33);
let guard = unsafe {
RawValueRootGuard::new(raw, ::std::slice::from_ref(&val), c"guard_foreign_drop")
}
.expect("RawValueRootGuard::new must succeed");
let ptr = ::std::boxed::Box::into_raw(::std::boxed::Box::new(guard)) as usize;
let handle = ::std::thread::spawn(move || unsafe {
drop(::std::boxed::Box::from_raw(ptr as *mut RawValueRootGuard));
});
handle.join().expect("foreign-thread drop must not panic");
unsafe { JS_GC(raw, JS::GCReason::API) };
}
#[test]
fn drop_after_runtime_teardown_does_not_crash() {
let mut ctx = JsContext::for_test().expect("JsContext::for_test");
let raw = ctx.raw_cx();
let val = make_marker_object(&mut ctx, 44);
let guard = unsafe {
RawValueRootGuard::new(raw, ::std::slice::from_ref(&val), c"guard_teardown")
}
.expect("RawValueRootGuard::new must succeed");
drop(ctx);
JsContext::shutdown_thread_sm();
drop(guard);
}