Skip to content

Commit e2f9d90

Browse files
ssrliveCopilot
andcommitted
Fix cross-realm apply wrappers
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e89b6ba commit e2f9d90

3 files changed

Lines changed: 68 additions & 197 deletions

File tree

src/core/vm.rs

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,7 @@ unsafe impl<'gc> Collect<'gc> for VM<'gc> {
837837
self.new_target_stack.trace(cc);
838838
self.fn_props.trace(cc);
839839
self.native_fn_props.trace(cc);
840+
self.cross_realm_native_fn_props.trace(cc);
840841
self.fn_home_objects.trace(cc);
841842
self.global_this.trace(cc);
842843
self.symbol_registry.trace(cc);
@@ -904,6 +905,8 @@ pub struct VM<'gc> {
904905
fn_realm: HashMap<usize, usize>,
905906
// Property storage for VmNativeFunction values, keyed by builtin id
906907
native_fn_props: HashMap<FunctionID, VmObjectHandle<'gc>>,
908+
// Parent-side wrappers for child-realm native functions, keyed by (realm_id, builtin id).
909+
cross_realm_native_fn_props: HashMap<(usize, FunctionID), VmObjectHandle<'gc>>,
907910
// Method home objects keyed by function IP, used to resolve `super` correctly.
908911
fn_home_objects: HashMap<usize, Value<'gc>>,
909912
// Global this object — top-level `this` refers to this; SetProperty on it writes to globals
@@ -1268,6 +1271,7 @@ impl<'gc> VM<'gc> {
12681271
fn_props: HashMap::new(),
12691272
fn_realm: HashMap::new(),
12701273
native_fn_props: HashMap::new(),
1274+
cross_realm_native_fn_props: HashMap::new(),
12711275
fn_home_objects: HashMap::new(),
12721276
global_this,
12731277
symbol_counter: 0,
@@ -12548,6 +12552,25 @@ impl<'gc> VM<'gc> {
1254812552
props_rc
1254912553
}
1255012554

12555+
fn get_cross_realm_native_fn_props(
12556+
&mut self,
12557+
ctx: &GcContext<'gc>,
12558+
child: &mut VM<'gc>,
12559+
id: FunctionID,
12560+
realm_id: usize,
12561+
) -> VmObjectHandle<'gc> {
12562+
if let Some(existing) = self.cross_realm_native_fn_props.get(&(realm_id, id)) {
12563+
return *existing;
12564+
}
12565+
12566+
let mut props = child.get_native_fn_props(ctx, id).borrow().clone();
12567+
props.insert("__realm_id__".to_string(), Value::Number(realm_id as f64));
12568+
props.insert("__origin_global__".to_string(), Value::VmObject(child.global_this));
12569+
let props_rc = new_gc_cell_ptr(ctx, props);
12570+
self.cross_realm_native_fn_props.insert((realm_id, id), props_rc);
12571+
props_rc
12572+
}
12573+
1255112574
/// When OrdinarySetWithOwnDescriptor reaches the data-descriptor path (no
1255212575
/// accessor found on the prototype chain), spec step 2c calls
1255312576
/// `Receiver.[[GetOwnProperty]](P)`. For module namespace exotic objects,
@@ -24164,26 +24187,7 @@ impl<'gc> VM<'gc> {
2416424187
}
2416524188
}
2416624189
BUILTIN_CTOR_FUNCTION => {
24167-
let mut out = self.call_builtin(ctx, id, args);
24168-
if let Value::VmObject(recv_obj) = receiver
24169-
&& let Value::VmObject(out_obj) = &mut out
24170-
{
24171-
let recv_borrow = recv_obj.borrow();
24172-
let recv_proto = recv_borrow.get("__proto__").cloned();
24173-
let recv_realm_id = recv_borrow.get("__realm_id__").cloned();
24174-
drop(recv_borrow);
24175-
let mut out_borrow = out_obj.borrow_mut(ctx);
24176-
if let Some(proto) = recv_proto {
24177-
out_borrow.insert("__proto__".to_string(), proto);
24178-
}
24179-
if let Some(realm_id) = recv_realm_id {
24180-
out_borrow.insert("__realm_id__".to_string(), realm_id);
24181-
}
24182-
if let Some(origin_global) = self.constructor_origin_global(ctx, receiver) {
24183-
out_borrow.insert("__origin_global".to_string(), origin_global);
24184-
}
24185-
}
24186-
return out;
24190+
return self.call_builtin(ctx, id, args);
2418724191
}
2418824192
BUILTIN_CTOR_NUMBER => {
2418924193
if let Value::VmObject(obj) = receiver {
@@ -34120,6 +34124,15 @@ impl<'gc> VM<'gc> {
3412034124
.insert("__realm_id__".to_string(), Value::Number(realm_id as f64));
3412134125
Value::VmArray(arr)
3412234126
}
34127+
Value::VmNativeFunction(id) => {
34128+
if let Some(mut child) = self.child_realms.get_mut(realm_id).and_then(Option::take) {
34129+
let props = self.get_cross_realm_native_fn_props(ctx, &mut child, id, realm_id);
34130+
self.child_realms[realm_id] = Some(child);
34131+
Value::VmObject(props)
34132+
} else {
34133+
Value::VmNativeFunction(id)
34134+
}
34135+
}
3412334136
_ => value,
3412434137
}
3412534138
}

src/core/vm/runner.rs

Lines changed: 9 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -3929,6 +3929,12 @@ impl<'gc> VM<'gc> {
39293929
return Ok(OpcodeAction::Continue);
39303930
}
39313931
let borrow = map.borrow();
3932+
if matches!(borrow.get("__realm_id__"), Some(Value::Number(_))) {
3933+
drop(borrow);
3934+
let result = self.read_named_property(ctx, &obj, &key);
3935+
self.stack.push(result);
3936+
return Ok(OpcodeAction::Continue);
3937+
}
39323938
// Check for getter first
39333939
let getter_key = format!("__get_{}", key);
39343940
if let Some(Value::VmFunction(ip, _) | Value::VmClosure(ip, _, _)) = borrow.get(&getter_key) {
@@ -4484,9 +4490,7 @@ impl<'gc> VM<'gc> {
44844490
} else {
44854491
let proto = lookup("__proto__");
44864492
match key.as_str() {
4487-
"call" => Value::VmNativeFunction(BUILTIN_FN_CALL),
4488-
"apply" => Value::VmNativeFunction(BUILTIN_FN_APPLY),
4489-
"bind" => Value::VmNativeFunction(BUILTIN_FN_BIND),
4493+
"call" | "apply" | "bind" => self.read_named_property(ctx, &obj, &key),
44904494
"caller" | "arguments" => {
44914495
// %ThrowTypeError% accessor on Function.prototype
44924496
let err = self.make_type_error_object(
@@ -6333,177 +6337,7 @@ impl<'gc> VM<'gc> {
63336337
return Ok(OpcodeAction::Continue);
63346338
}
63356339
let method = match &obj {
6336-
Value::VmObject(map) => {
6337-
let borrow = map.borrow();
6338-
let getter_key = format!("__get_{}", key);
6339-
if let Some(getter_fn) = borrow.get(&getter_key).cloned() {
6340-
drop(borrow);
6341-
self.invoke_getter_with_receiver(ctx, &getter_fn, &obj)
6342-
} else if let Some(v) = borrow.get(&key).cloned() {
6343-
match v {
6344-
Value::Property { getter: Some(g), .. } => {
6345-
drop(borrow);
6346-
self.invoke_getter_with_receiver(ctx, &g, &obj)
6347-
}
6348-
other => other,
6349-
}
6350-
} else {
6351-
let is_callable_obj = borrow.contains_key("__host_fn__")
6352-
|| borrow.contains_key("__bound_target__")
6353-
|| borrow.contains_key("__fn_body__")
6354-
|| borrow.contains_key("__native_id__");
6355-
if is_callable_obj {
6356-
match key.as_str() {
6357-
"call" => {
6358-
drop(borrow);
6359-
self.stack.push(Value::VmNativeFunction(BUILTIN_FN_CALL));
6360-
return Ok(OpcodeAction::Continue);
6361-
}
6362-
"apply" => {
6363-
drop(borrow);
6364-
self.stack.push(Value::VmNativeFunction(BUILTIN_FN_APPLY));
6365-
return Ok(OpcodeAction::Continue);
6366-
}
6367-
"bind" => {
6368-
drop(borrow);
6369-
self.stack.push(Value::VmNativeFunction(BUILTIN_FN_BIND));
6370-
return Ok(OpcodeAction::Continue);
6371-
}
6372-
_ => {}
6373-
}
6374-
}
6375-
// Check WeakRef
6376-
let is_weakref = borrow.contains_key("__weakref__");
6377-
// Check typed wrapper methods first
6378-
let type_name = borrow.get("__type__").map(|v| value_to_string(v));
6379-
let mut proto = borrow.get("__proto__").cloned();
6380-
drop(borrow);
6381-
if proto.is_none()
6382-
&& let Some(type_name) = type_name.as_deref()
6383-
&& let Some(Value::VmObject(ctor)) = self.globals.get(type_name)
6384-
&& let Some(type_proto) = ctor.borrow().get("prototype").cloned()
6385-
{
6386-
proto = Some(type_proto);
6387-
}
6388-
if matches!(type_name.as_deref(), Some("Boolean"))
6389-
&& let Some(Value::VmObject(boolean_ctor)) = self.globals.get("Boolean")
6390-
&& let Some(bool_proto) = boolean_ctor.borrow().get("prototype").cloned()
6391-
{
6392-
proto = Some(bool_proto);
6393-
}
6394-
if matches!(type_name.as_deref(), Some("String"))
6395-
&& let Some(Value::VmObject(string_ctor)) = self.globals.get("String")
6396-
&& let Some(string_proto) = string_ctor.borrow().get("prototype").cloned()
6397-
{
6398-
proto = Some(string_proto);
6399-
}
6400-
if is_weakref && key == "deref" {
6401-
Value::VmNativeFunction(BUILTIN_WEAKREF_DEREF)
6402-
} else {
6403-
let typed_result = match type_name.as_deref() {
6404-
Some("Number") => match key.as_str() {
6405-
"toFixed" | "toExponential" | "toPrecision" | "toString" | "toLocaleString" | "valueOf" | "constructor" => {
6406-
if let Some(Value::VmObject(num_ctor)) = self.globals.get("Number")
6407-
&& let Some(Value::VmObject(num_proto)) = num_ctor.borrow().get("prototype").cloned()
6408-
{
6409-
num_proto.borrow().get(&key).cloned()
6410-
} else {
6411-
None
6412-
}
6413-
}
6414-
_ => None,
6415-
},
6416-
Some("BigInt") => {
6417-
// Check if BigInt.prototype has a getter override
6418-
if let Some(Value::VmObject(bi_ctor)) = self.globals.get("BigInt")
6419-
&& let Some(Value::VmObject(bi_proto)) = bi_ctor.borrow().get("prototype").cloned()
6420-
{
6421-
let getter_key = format!("__get_{}", key);
6422-
let bp = bi_proto.borrow();
6423-
if bp.contains_key(&getter_key) || bp.contains_key(&key) {
6424-
drop(bp);
6425-
let val = self.read_named_property(ctx, &Value::VmObject(bi_proto), &key);
6426-
Some(val)
6427-
} else {
6428-
match key.as_str() {
6429-
"toString" => Some(Value::VmNativeFunction(BUILTIN_BIGINT_TOSTRING)),
6430-
"toLocaleString" => Some(Value::VmNativeFunction(BUILTIN_BIGINT_TOLOCALESTRING)),
6431-
"valueOf" => Some(Value::VmNativeFunction(BUILTIN_BIGINT_VALUEOF)),
6432-
"constructor" => bp.get(&key).cloned(),
6433-
_ => None,
6434-
}
6435-
}
6436-
} else {
6437-
match key.as_str() {
6438-
"toString" => Some(Value::VmNativeFunction(BUILTIN_BIGINT_TOSTRING)),
6439-
"toLocaleString" => Some(Value::VmNativeFunction(BUILTIN_BIGINT_TOLOCALESTRING)),
6440-
"valueOf" => Some(Value::VmNativeFunction(BUILTIN_BIGINT_VALUEOF)),
6441-
_ => None,
6442-
}
6443-
}
6444-
}
6445-
Some("String") => match key.as_str() {
6446-
"toString" | "valueOf" => Some(Value::VmNativeFunction(BUILTIN_STRING_VALUEOF)),
6447-
"constructor" => self.globals.get("String").cloned(),
6448-
"length" => {
6449-
let b = map.borrow();
6450-
match b.get("__value__") {
6451-
Some(Value::String(sv)) => Some(Value::Number(sv.len() as f64)),
6452-
_ => Some(Value::Number(0.0)),
6453-
}
6454-
}
6455-
"split" => Some(Value::VmNativeFunction(BUILTIN_STRING_SPLIT)),
6456-
"indexOf" => Some(Value::VmNativeFunction(BUILTIN_STRING_INDEXOF)),
6457-
"slice" => Some(Value::VmNativeFunction(BUILTIN_STRING_SLICE)),
6458-
"toUpperCase" => Some(Value::VmNativeFunction(BUILTIN_STRING_TOUPPERCASE)),
6459-
"toLowerCase" => Some(Value::VmNativeFunction(BUILTIN_STRING_TOLOWERCASE)),
6460-
"trim" => Some(Value::VmNativeFunction(BUILTIN_STRING_TRIM)),
6461-
"charAt" => Some(Value::VmNativeFunction(BUILTIN_STRING_CHARAT)),
6462-
"includes" => Some(Value::VmNativeFunction(BUILTIN_STRING_INCLUDES)),
6463-
"replace" => Some(Value::VmNativeFunction(BUILTIN_STRING_REPLACE)),
6464-
"replaceAll" => Some(Value::VmNativeFunction(BUILTIN_STRING_REPLACEALL)),
6465-
"match" => Some(Value::VmNativeFunction(BUILTIN_STRING_MATCH)),
6466-
"search" => Some(Value::VmNativeFunction(BUILTIN_STRING_SEARCH)),
6467-
"startsWith" => Some(Value::VmNativeFunction(BUILTIN_STRING_STARTSWITH)),
6468-
"endsWith" => Some(Value::VmNativeFunction(BUILTIN_STRING_ENDSWITH)),
6469-
"substring" => Some(Value::VmNativeFunction(BUILTIN_STRING_SUBSTRING)),
6470-
"padStart" => Some(Value::VmNativeFunction(BUILTIN_STRING_PADSTART)),
6471-
"padEnd" => Some(Value::VmNativeFunction(BUILTIN_STRING_PADEND)),
6472-
"repeat" => Some(Value::VmNativeFunction(BUILTIN_STRING_REPEAT)),
6473-
"charCodeAt" => Some(Value::VmNativeFunction(BUILTIN_STRING_CHARCODEAT)),
6474-
"trimStart" => Some(Value::VmNativeFunction(BUILTIN_STRING_TRIMSTART)),
6475-
"trimEnd" => Some(Value::VmNativeFunction(BUILTIN_STRING_TRIMEND)),
6476-
"lastIndexOf" => Some(Value::VmNativeFunction(BUILTIN_STRING_LASTINDEXOF)),
6477-
_ => None,
6478-
},
6479-
Some("Boolean") => {
6480-
let effective_proto = proto.clone().or_else(|| {
6481-
if let Some(Value::VmObject(obj_global)) = self.globals.get("Object") {
6482-
obj_global.borrow().get("prototype").cloned()
6483-
} else {
6484-
None
6485-
}
6486-
});
6487-
if self.lookup_proto_chain(effective_proto.as_ref(), &key).is_none() {
6488-
match key.as_str() {
6489-
"toString" => Some(Self::make_host_fn(ctx, "boolean.toString")),
6490-
"valueOf" => Some(Self::make_host_fn(ctx, "boolean.valueOf")),
6491-
_ => None,
6492-
}
6493-
} else {
6494-
None
6495-
}
6496-
}
6497-
Some("RegExp") => match key.as_str() {
6498-
"toString" => Some(Self::make_bound_host_fn(ctx, "regexp.toString", &obj)),
6499-
_ => None,
6500-
},
6501-
_ => None,
6502-
};
6503-
typed_result.unwrap_or_else(|| self.read_named_property_with_receiver(ctx, &obj, &key, &obj))
6504-
}
6505-
}
6506-
}
6340+
Value::VmObject(_) => self.read_named_property(ctx, &obj, &key),
65076341
Value::VmArray(arr) => {
65086342
let borrow = arr.borrow();
65096343
let is_generator = matches!(borrow.props.get("__generator__"), Some(Value::Boolean(true)));
@@ -6624,9 +6458,7 @@ impl<'gc> VM<'gc> {
66246458
} else {
66256459
let proto = lookup("__proto__");
66266460
match key.as_str() {
6627-
"call" => Value::VmNativeFunction(BUILTIN_FN_CALL),
6628-
"apply" => Value::VmNativeFunction(BUILTIN_FN_APPLY),
6629-
"bind" => Value::VmNativeFunction(BUILTIN_FN_BIND),
6461+
"call" | "apply" | "bind" => self.read_named_property(ctx, &obj, &key),
66306462
_ => self.lookup_proto_chain(proto.as_ref(), &key).unwrap_or(Value::Undefined),
66316463
}
66326464
}

tests/functions.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,32 @@ mod function_tests {
212212
assert_eq!(result, "true");
213213
}
214214

215+
#[test]
216+
fn test_cross_realm_function_apply_uses_foreign_type_errors() {
217+
let script = r#"
218+
let other = __createRealm__().global;
219+
let otherApply = other.Function.prototype.apply;
220+
let otherFn = other.Function();
221+
let protoOk = otherFn.__proto__ === other.Function.prototype;
222+
let callWorks = otherApply.call(otherFn, null, []) === undefined;
223+
let thisNotCallable = false;
224+
let badArgArray = false;
225+
try {
226+
otherApply.call(undefined, {}, []);
227+
} catch (e) {
228+
thisNotCallable = e.constructor === other.TypeError && e.constructor !== TypeError;
229+
}
230+
try {
231+
otherFn.apply(null, true);
232+
} catch (e) {
233+
badArgArray = e.constructor === other.TypeError && e.constructor !== TypeError;
234+
}
235+
otherApply !== Function.prototype.apply && protoOk && callWorks && thisNotCallable && badArgArray
236+
"#;
237+
let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
238+
assert_eq!(result, "true");
239+
}
240+
215241
#[test]
216242
fn test_function_call_and_bind_require_callable_receiver() {
217243
let script = r#"

0 commit comments

Comments
 (0)