Skip to content

Commit b4111ef

Browse files
committed
avm1: properly handle native object subclassing
This involves 4 changes, done as a single commit to avoid transitory broken tests: - make `super()` call the constructor variant of the superclass's executable; - remove `TObject::create_bare_object`, as all remaining implementations are identical; - ensure that all built-in constructors: - operate on the `this` parameter - return `undefined` when called as a normal function, unless required otherwise; - ensure that other native constructors (those that aren't built-ins on FP) behave as standard AS-defined functions. Fixes ruffle-rs#701, ruffle-rs#9179.
1 parent 81e4996 commit b4111ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+208
-452
lines changed

core/src/avm1/activation.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,16 +1103,11 @@ impl<'a, 'gc> Activation<'a, 'gc> {
11031103

11041104
//TODO: What happens if we try to extend an object which has no `prototype`?
11051105
//e.g. `class Whatever extends Object.prototype` or `class Whatever extends 5`
1106-
1107-
// Use `create_bare_object` to ensure the proper underlying object type when
1108-
// extending native objects.
1109-
// TODO: This doesn't work if the user manually wires up `prototype`/`__proto__`.
1110-
// The native object needs to be created later by the superclass's constructor.
1111-
// (see #701)
11121106
let super_prototype = superclass
11131107
.get(istr!(self, "prototype"), self)?
11141108
.coerce_to_object(self);
1115-
let sub_prototype = super_prototype.create_bare_object(self, super_prototype)?;
1109+
1110+
let sub_prototype = ScriptObject::new(self.strings(), Some(super_prototype));
11161111

11171112
sub_prototype.define_value(
11181113
self.gc(),

core/src/avm1/function.rs

Lines changed: 63 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,6 @@ use swf::{avm1::types::FunctionFlags, SwfStr};
1818
use super::NativeObject;
1919

2020
/// Represents a function defined in Ruffle's code.
21-
///
22-
/// Parameters are as follows:
23-
///
24-
/// * The AVM1 runtime
25-
/// * The action context
26-
/// * The current `this` object
27-
/// * The arguments this function was called with
28-
///
29-
/// Native functions are allowed to return a value or `None`. `None` indicates
30-
/// that the given value will not be returned on the stack and instead will
31-
/// resolve on the AVM stack, as if you had called a non-native function. If
32-
/// your function yields `None`, you must ensure that the top-most activation
33-
/// in the AVM1 runtime will return with the value of this function.
3421
pub type NativeFunction = for<'gc> fn(
3522
&mut Activation<'_, 'gc>,
3623
Object<'gc>,
@@ -336,6 +323,9 @@ impl<'gc> From<AvmString<'gc>> for ExecutionName<'gc> {
336323
}
337324

338325
impl<'gc> Executable<'gc> {
326+
/// A dummy `Executable` that does nothing, and returns `undefined`.
327+
const EMPTY: Self = Self::Native(|_, _, _| Ok(Value::Undefined));
328+
339329
/// Execute the given code.
340330
///
341331
/// Execution is not guaranteed to have completed when this function
@@ -489,6 +479,8 @@ pub struct FunctionObject<'gc> {
489479
/// The code that will be invoked when this object is called.
490480
function: Executable<'gc>,
491481
/// The code that will be invoked when this object is constructed.
482+
///
483+
/// If `None`, falls back to `function`.
492484
#[collect(require_static)]
493485
constructor: Option<NativeFunction>,
494486
}
@@ -546,27 +538,56 @@ impl<'gc> FunctionObject<'gc> {
546538
function
547539
}
548540

549-
/// Construct a regular function from an executable and associated protos.
541+
/// Constructs a function that does nothing.
542+
///
543+
/// This can also serve as a no-op constructor.
544+
pub fn empty(
545+
context: &StringContext<'gc>,
546+
fn_proto: Object<'gc>,
547+
prototype: Object<'gc>,
548+
) -> Object<'gc> {
549+
Self::allocate_function(context, Executable::EMPTY, None, fn_proto, prototype)
550+
}
551+
552+
/// Construct a function from AVM1 bytecode and associated protos.
550553
pub fn function(
551554
context: &StringContext<'gc>,
552-
function: impl Into<Executable<'gc>>,
555+
function: Gc<'gc, Avm1Function<'gc>>,
553556
fn_proto: Object<'gc>,
554557
prototype: Object<'gc>,
555558
) -> Object<'gc> {
556559
Self::allocate_function(context, function.into(), None, fn_proto, prototype)
557560
}
558561

559-
/// Construct a regular and constructor function from an executable and associated protos.
562+
/// Construct a function from a native executable and associated protos.
563+
pub fn native(
564+
context: &StringContext<'gc>,
565+
function: NativeFunction,
566+
fn_proto: Object<'gc>,
567+
prototype: Object<'gc>,
568+
) -> Object<'gc> {
569+
let function = Executable::Native(function);
570+
Self::allocate_function(context, function, None, fn_proto, prototype)
571+
}
572+
573+
/// Construct a native constructor from native executables and associated protos.
574+
///
575+
/// This differs from [`Self::native`] in two important ways:
576+
/// - When called through `new`, the return value will always become the result of the
577+
/// operation. Native constructors should therefore generally return either `this`,
578+
/// if the object was successfully constructed, or `undefined` if not.
579+
/// - When called as a normal function, `function` will be called instead of `constructor`;
580+
/// if it is `None`, the return value will be `undefined`.
560581
pub fn constructor(
561582
context: &StringContext<'gc>,
562583
constructor: NativeFunction,
563-
function: impl Into<Executable<'gc>>,
584+
function: Option<NativeFunction>,
564585
fn_proto: Object<'gc>,
565586
prototype: Object<'gc>,
566587
) -> Object<'gc> {
567588
Self::allocate_function(
568589
context,
569-
function.into(),
590+
Executable::Native(function.unwrap_or(|_, _, _| Ok(Value::Undefined))),
570591
Some(constructor),
571592
fn_proto,
572593
prototype,
@@ -585,6 +606,10 @@ impl<'gc> FunctionObject<'gc> {
585606
}
586607
}
587608

609+
pub fn is_native_constructor(&self) -> bool {
610+
self.constructor.is_some()
611+
}
612+
588613
pub fn call(
589614
&self,
590615
name: impl Into<ExecutionName<'gc>>,
@@ -611,6 +636,7 @@ impl<'gc> FunctionObject<'gc> {
611636
this: Object<'gc>,
612637
args: &[Value<'gc>],
613638
) -> Result<(), Error<'gc>> {
639+
// TODO: de-duplicate code.
614640
this.define_value(
615641
activation.gc(),
616642
istr!("__constructor__"),
@@ -625,7 +651,8 @@ impl<'gc> FunctionObject<'gc> {
625651
Attribute::DONT_ENUM,
626652
);
627653
}
628-
// TODO: de-duplicate code.
654+
655+
// Always ignore the constructor's return value.
629656
let _ = self.as_constructor().exec(
630657
ExecutionName::Static("[ctor]"),
631658
activation,
@@ -635,6 +662,7 @@ impl<'gc> FunctionObject<'gc> {
635662
ExecutionReason::FunctionCall,
636663
callee,
637664
)?;
665+
638666
Ok(())
639667
}
640668

@@ -647,8 +675,9 @@ impl<'gc> FunctionObject<'gc> {
647675
let prototype = callee
648676
.get(istr!("prototype"), activation)?
649677
.coerce_to_object(activation);
650-
let this = prototype.create_bare_object(activation, prototype)?;
678+
let this = ScriptObject::new(activation.strings(), Some(prototype));
651679

680+
// TODO: de-duplicate code.
652681
this.define_value(
653682
activation.gc(),
654683
istr!("__constructor__"),
@@ -663,42 +692,22 @@ impl<'gc> FunctionObject<'gc> {
663692
Attribute::DONT_ENUM,
664693
);
665694
}
666-
// TODO: de-duplicate code.
667-
if let Some(constr) = &self.constructor {
668-
// Native constructors will return the constructed `this`.
669-
// This allows for `new Object` etc. returning different types.
670-
constr(activation, this, args)
695+
696+
let result = self.as_constructor().exec(
697+
ExecutionName::Static("[ctor]"),
698+
activation,
699+
this.into(),
700+
1,
701+
args,
702+
ExecutionReason::FunctionCall,
703+
callee,
704+
)?;
705+
706+
if self.is_native_constructor() {
707+
// Propagate the native method's return value.
708+
Ok(result)
671709
} else {
672-
let _ = self.function.exec(
673-
ExecutionName::Static("[ctor]"),
674-
activation,
675-
this.into(),
676-
1,
677-
args,
678-
ExecutionReason::FunctionCall,
679-
callee,
680-
)?;
681710
Ok(this.into())
682711
}
683712
}
684713
}
685-
686-
/// Turns a simple built-in constructor into a function that discards
687-
/// the constructor return value.
688-
/// Use with `FunctionObject::constructor` when defining constructor of
689-
/// built-in objects.
690-
#[macro_export]
691-
macro_rules! constructor_to_fn {
692-
($f:expr) => {{
693-
fn _constructor_fn<'gc>(
694-
activation: &mut $crate::avm1::activation::Activation<'_, 'gc>,
695-
this: $crate::avm1::Object<'gc>,
696-
args: &[$crate::avm1::Value<'gc>],
697-
) -> Result<$crate::avm1::Value<'gc>, $crate::avm1::error::Error<'gc>> {
698-
#[allow(clippy::redundant_closure_call)]
699-
let _ = $f(activation, this, args)?;
700-
Ok($crate::avm1::Value::Undefined)
701-
}
702-
$crate::avm1::function::Executable::Native(_constructor_fn)
703-
}};
704-
}

0 commit comments

Comments
 (0)