avm2: `newactivation` should return an object which implements the traits listed in the associated method `body`'s trait list

This commit is contained in:
David Wendt 2021-04-06 22:31:05 -04:00 committed by Mike Welsh
parent 6c5098d3c7
commit 9318028b52
3 changed files with 93 additions and 7 deletions

View File

@ -107,6 +107,16 @@ pub struct Activation<'a, 'gc: 'a, 'gc_context: 'a> {
/// This will not be available if this is not a method call. /// This will not be available if this is not a method call.
base_proto: Option<Object<'gc>>, base_proto: Option<Object<'gc>>,
/// The proto of all objects returned from `newactivation`.
///
/// In method calls that call for an activation object, this will be
/// configured as the anonymous class whose traits match the method's
/// declared traits.
///
/// If this is `None`, then the method did not ask for an activation object
/// and we will not construct a prototype for one.
activation_proto: Option<Object<'gc>>,
pub context: UpdateContext<'a, 'gc, 'gc_context>, pub context: UpdateContext<'a, 'gc, 'gc_context>,
} }
@ -131,6 +141,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
local_scope: ScriptObject::bare_object(context.gc_context), local_scope: ScriptObject::bare_object(context.gc_context),
scope: None, scope: None,
base_proto: None, base_proto: None,
activation_proto: None,
context, context,
} }
} }
@ -170,6 +181,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
local_scope: ScriptObject::bare_object(context.gc_context), local_scope: ScriptObject::bare_object(context.gc_context),
scope, scope,
base_proto: None, base_proto: None,
activation_proto: None,
context, context,
}) })
} }
@ -188,7 +200,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let body: Result<_, Error> = method let body: Result<_, Error> = method
.body() .body()
.ok_or_else(|| "Cannot execute non-native method without body".into()); .ok_or_else(|| "Cannot execute non-native method without body".into());
let num_locals = body?.num_locals; let body = body?;
let num_locals = body.num_locals;
let has_rest_or_args = method.method().needs_arguments_object || method.method().needs_rest; let has_rest_or_args = method.method().needs_arguments_object || method.method().needs_rest;
let arg_register = if has_rest_or_args { 1 } else { 0 }; let arg_register = if has_rest_or_args { 1 } else { 0 };
let num_declared_arguments = method.method().params.len() as u32; let num_declared_arguments = method.method().params.len() as u32;
@ -209,6 +222,26 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} }
} }
let activation_proto = if method.method().needs_activation {
let translation_unit = method.translation_unit();
let abc_method = method.method();
let activation_class = Class::from_method_body(
context.avm2,
context.gc_context,
translation_unit,
abc_method,
body,
)?;
Some(ScriptObject::bare_prototype(
context.gc_context,
activation_class,
scope,
))
} else {
None
};
let mut activation = Self { let mut activation = Self {
this, this,
arguments: None, arguments: None,
@ -218,6 +251,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
local_scope: ScriptObject::bare_object(context.gc_context), local_scope: ScriptObject::bare_object(context.gc_context),
scope, scope,
base_proto, base_proto,
activation_proto,
context, context,
}; };
@ -291,6 +325,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
local_scope: ScriptObject::bare_object(context.gc_context), local_scope: ScriptObject::bare_object(context.gc_context),
scope, scope,
base_proto, base_proto,
activation_proto: None,
context, context,
}) })
} }
@ -1440,9 +1475,16 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} }
fn op_new_activation(&mut self) -> Result<FrameControl<'gc>, Error> { fn op_new_activation(&mut self) -> Result<FrameControl<'gc>, Error> {
self.context if let Some(activation_proto) = self.activation_proto {
.avm2 self.context.avm2.push(ScriptObject::object(
.push(ScriptObject::bare_object(self.context.gc_context)); self.context.gc_context,
activation_proto,
));
} else {
self.context
.avm2
.push(ScriptObject::bare_object(self.context.gc_context));
}
Ok(FrameControl::Continue) Ok(FrameControl::Continue)
} }

View File

@ -9,7 +9,9 @@ use crate::avm2::{Avm2, Error};
use crate::collect::CollectWrapper; use crate::collect::CollectWrapper;
use bitflags::bitflags; use bitflags::bitflags;
use gc_arena::{Collect, GcCell, MutationContext}; use gc_arena::{Collect, GcCell, MutationContext};
use swf::avm2::types::{Class as AbcClass, Instance as AbcInstance}; use swf::avm2::types::{
Class as AbcClass, Instance as AbcInstance, Method as AbcMethod, MethodBody as AbcMethodBody,
};
bitflags! { bitflags! {
/// All possible attributes for a given class. /// All possible attributes for a given class.
@ -270,6 +272,46 @@ impl<'gc> Class<'gc> {
Ok(()) Ok(())
} }
pub fn from_method_body(
avm2: &mut Avm2<'gc>,
mc: MutationContext<'gc, '_>,
translation_unit: TranslationUnit<'gc>,
method: &AbcMethod,
body: &AbcMethodBody,
) -> Result<GcCell<'gc, Self>, Error> {
let name = translation_unit.pool_string(method.name.as_u30(), mc)?;
let mut traits = Vec::new();
for trait_entry in body.traits.iter() {
traits.push(Trait::from_abc_trait(
translation_unit,
&trait_entry,
avm2,
mc,
)?);
}
Ok(GcCell::allocate(
mc,
Self {
name: QName::dynamic_name(name),
super_class: None,
attributes: CollectWrapper(ClassAttributes::empty()),
protected_namespace: None,
interfaces: Vec::new(),
instance_init: Method::from_builtin(|_, _, _| {
Err("Do not call activation initializers!".into())
}),
instance_traits: traits,
class_init: Method::from_builtin(|_, _, _| {
Err("Do not call activation class initializers!".into())
}),
class_traits: Vec::new(),
traits_loaded: true,
},
))
}
pub fn name(&self) -> &QName<'gc> { pub fn name(&self) -> &QName<'gc> {
&self.name &self.name
} }

View File

@ -365,8 +365,10 @@ impl<'gc> ScriptObject<'gc> {
/// Construct a bare class prototype with no base class. /// Construct a bare class prototype with no base class.
/// ///
/// This appears to be used specifically for interfaces, which have no base /// This is used in cases where a prototype needs to exist, but it does not
/// class. /// need to extend `Object`. This is the case for interfaces and activation
/// objects, both of which need to participate in the class mechanism but
/// are not `Object`s.
pub fn bare_prototype( pub fn bare_prototype(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>, class: GcCell<'gc, Class<'gc>>,