avm2: Add a notion of native instance initializers.

Native initializers are a separate, parallel initialization chain intended for all object construction that is not directly triggered by `Op::Construct` and friends. This allows us to implement classes that cannot be directly constructed by user code, but can be constructed by native code or supercalled into from non-native.
This commit is contained in:
David Wendt 2021-05-28 22:24:09 -04:00
parent 4bc1d37029
commit dcbb5e4284
4 changed files with 95 additions and 3 deletions

View File

@ -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.

View File

@ -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()

View File

@ -704,6 +704,32 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
Err("Object is not callable".into())
}
/// Call the instance initializer.
fn call_initializer(
self,
_reciever: Option<Object<'gc>>,
_arguments: &[Value<'gc>],
_activation: &mut Activation<'_, 'gc, '_>,
_base_constr: Option<Object<'gc>>,
) -> Result<Value<'gc>, 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<Object<'gc>>,
_arguments: &[Value<'gc>],
_activation: &mut Activation<'_, 'gc, '_>,
_base_constr: Option<Object<'gc>>,
) -> Result<Value<'gc>, 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

View File

@ -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<Object<'gc>, 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<Object<'gc>>,
arguments: &[Value<'gc>],
activation: &mut Activation<'_, 'gc, '_>,
base_constr: Option<Object<'gc>>,
) -> Result<Value<'gc>, 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<Object<'gc>>,
arguments: &[Value<'gc>],
activation: &mut Activation<'_, 'gc, '_>,
base_constr: Option<Object<'gc>>,
) -> Result<Value<'gc>, 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)
}