diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index fa1e611d8..5ad0cfeb4 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -372,7 +372,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { }); let base_constr = base_constr?; - base_constr.call(Some(receiver), args, self, Some(base_constr)) + base_constr.call_native_initializer(Some(receiver), args, self, Some(base_constr)) } /// Attempts to lock the activation frame for execution. diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 28f84ca74..84e2edd03 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -137,6 +137,20 @@ pub struct Class<'gc> { /// Must be called each time a new class instance is constructed. instance_init: Method<'gc>, + /// The native instance initializer for this class. + /// + /// This may be provided to allow natively-constructed classes to + /// initialize themselves in a different manner from user-constructed ones. + /// For example, the user-accessible constructor may error out (as it's not + /// a valid class to construct for users), but native code may still call + /// it's constructor stack. + /// + /// By default, a class's `native_instance_init` will be initialized to the + /// same method as the regular one. You must specify a separate native + /// initializer to change initialization behavior based on what code is + /// constructing the class. + native_instance_init: Method<'gc>, + /// Instance traits for a given class. /// /// These are accessed as normal instance properties; they should not be @@ -232,6 +246,8 @@ impl<'gc> Class<'gc> { class_init: Method<'gc>, mc: MutationContext<'gc, '_>, ) -> GcCell<'gc, Self> { + let native_instance_init = instance_init.clone(); + GcCell::allocate( mc, Self { @@ -242,6 +258,7 @@ impl<'gc> Class<'gc> { interfaces: Vec::new(), instance_deriver: Deriver(implicit_deriver), instance_init, + native_instance_init, instance_traits: Vec::new(), class_init, class_traits: Vec::new(), @@ -310,6 +327,7 @@ impl<'gc> Class<'gc> { } let instance_init = unit.load_method(abc_instance.init_method.0, mc)?; + let native_instance_init = instance_init.clone(); let class_init = unit.load_method(abc_class.init_method.0, mc)?; let mut attributes = ClassAttributes::empty(); @@ -327,6 +345,7 @@ impl<'gc> Class<'gc> { interfaces, instance_deriver: Deriver(implicit_deriver), instance_init, + native_instance_init, instance_traits: Vec::new(), class_init, class_traits: Vec::new(), @@ -402,6 +421,7 @@ impl<'gc> Class<'gc> { interfaces: Vec::new(), instance_deriver: Deriver(implicit_deriver), instance_init: Method::from_builtin(|_, _, _| Ok(Value::Undefined)), + native_instance_init: Method::from_builtin(|_, _, _| Ok(Value::Undefined)), instance_traits: traits, class_init: Method::from_builtin(|_, _, _| Ok(Value::Undefined)), class_traits: Vec::new(), @@ -650,6 +670,16 @@ impl<'gc> Class<'gc> { self.instance_init.clone() } + /// Get this class's native-code instance initializer. + pub fn native_instance_init(&self) -> Method<'gc> { + self.native_instance_init.clone() + } + + /// Set a native-code instance initializer for this class. + pub fn set_native_instance_init(&mut self, new_native_init: Method<'gc>) { + self.native_instance_init = new_native_init; + } + /// Get this class's class initializer. pub fn class_init(&self) -> Method<'gc> { self.class_init.clone() diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 97e50c404..302793241 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -704,6 +704,32 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy Err("Object is not callable".into()) } + /// Call the instance initializer. + fn call_initializer( + self, + _reciever: Option>, + _arguments: &[Value<'gc>], + _activation: &mut Activation<'_, 'gc, '_>, + _base_constr: Option>, + ) -> Result, Error> { + Err("Object is not a Class constructor".into()) + } + + /// Call the instance's native initializer. + /// + /// The native initializer is called when native code needs to construct an + /// object, or when supercalling into a parent constructor (as there are + /// classes that cannot be constructed but can be supercalled). + fn call_native_initializer( + self, + _reciever: Option>, + _arguments: &[Value<'gc>], + _activation: &mut Activation<'_, 'gc, '_>, + _base_constr: Option>, + ) -> Result, Error> { + Err("Object is not a Class constructor".into()) + } + /// Call an instance method by name. /// /// Intended to be called on the current base constructor used for diff --git a/core/src/avm2/object/class_object.rs b/core/src/avm2/object/class_object.rs index 04da202cc..6a655a4a1 100644 --- a/core/src/avm2/object/class_object.rs +++ b/core/src/avm2/object/class_object.rs @@ -40,6 +40,9 @@ pub struct ClassObjectData<'gc> { /// The instance constructor function instance_constr: Executable<'gc>, + + /// The native instance constructor function + native_instance_constr: Executable<'gc>, } impl<'gc> ClassObject<'gc> { @@ -126,6 +129,12 @@ impl<'gc> ClassObject<'gc> { None, activation.context.gc_context, ); + let native_instance_constr = Executable::from_method( + class.read().native_instance_init(), + scope, + None, + activation.context.gc_context, + ); let mut constr: Object<'gc> = ClassObject(GcCell::allocate( activation.context.gc_context, @@ -138,6 +147,7 @@ impl<'gc> ClassObject<'gc> { scope, base_class_constr, instance_constr, + native_instance_constr, }, )) .into(); @@ -176,6 +186,8 @@ impl<'gc> ClassObject<'gc> { ) -> Result, Error> { let instance_constr = Executable::from_method(class.read().instance_init(), scope, None, mc); + let native_instance_constr = + Executable::from_method(class.read().native_instance_init(), scope, None, mc); let mut base: Object<'gc> = ClassObject(GcCell::allocate( mc, ClassObjectData { @@ -187,6 +199,7 @@ impl<'gc> ClassObject<'gc> { scope, base_class_constr, instance_constr, + native_instance_constr, }, )) .into(); @@ -238,6 +251,30 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> { instance_constr.exec(receiver, arguments, activation, base_constr, self.into()) } + fn call_initializer( + self, + receiver: Option>, + arguments: &[Value<'gc>], + activation: &mut Activation<'_, 'gc, '_>, + base_constr: Option>, + ) -> Result, Error> { + let instance_constr = self.0.read().instance_constr; + + instance_constr.exec(receiver, arguments, activation, base_constr, self.into()) + } + + fn call_native_initializer( + self, + receiver: Option>, + arguments: &[Value<'gc>], + activation: &mut Activation<'_, 'gc, '_>, + base_constr: Option>, + ) -> Result, Error> { + let native_instance_constr = self.0.read().native_instance_constr; + + native_instance_constr.exec(receiver, arguments, activation, base_constr, self.into()) + } + fn construct( mut self, activation: &mut Activation<'_, 'gc, '_>, @@ -255,9 +292,8 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> { .coerce_to_object(activation)?; let instance = deriver(constr, prototype, activation)?; - let instance_constr = self.0.read().instance_constr; - instance_constr.exec(Some(instance), arguments, activation, Some(constr), constr)?; + self.call_initializer(Some(instance), arguments, activation, Some(constr))?; Ok(instance) }