avm2: Fix many issues with supercalling methods

Supercalling on the global object now works;
supercalling in the class initializer now works; and
supercalling in static methods now uses the correct superclass.
This commit is contained in:
Lord-McSweeney 2024-08-08 14:16:26 -07:00 committed by Lord-McSweeney
parent d885697d9b
commit f68baf0f76
24 changed files with 257 additions and 211 deletions

View File

@ -645,13 +645,8 @@ impl<'gc> Avm2<'gc> {
}
/// Pushes an executable on the call stack
pub fn push_call(
&self,
mc: &Mutation<'gc>,
method: Method<'gc>,
superclass: Option<ClassObject<'gc>>,
) {
self.call_stack.borrow_mut(mc).push(method, superclass)
pub fn push_call(&self, mc: &Mutation<'gc>, method: Method<'gc>, class: Option<Class<'gc>>) {
self.call_stack.borrow_mut(mc).push(method, class)
}
/// Pushes script initializer (global init) on the call stack

View File

@ -105,7 +105,7 @@ pub struct Activation<'a, 'gc: 'a> {
/// is a bytecode method, the movie will instead be the movie that the bytecode method came from.
caller_movie: Option<Arc<SwfMovie>>,
/// The class that yielded the currently executing method.
/// The superclass of the class that yielded the currently executing method.
///
/// This is used to maintain continuity when multiple methods supercall
/// into one another. For example, if a class method supercalls a
@ -115,7 +115,9 @@ pub struct Activation<'a, 'gc: 'a> {
/// the same method again.
///
/// This will not be available outside of method, setter, or getter calls.
subclass_object: Option<ClassObject<'gc>>,
superclass_object: Option<ClassObject<'gc>>,
subclass: Option<Class<'gc>>,
/// The class of all objects returned from `newactivation`.
///
@ -168,7 +170,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
outer: ScopeChain::new(context.avm2.stage_domain),
caller_domain: None,
caller_movie: None,
subclass_object: None,
superclass_object: None,
subclass: None,
activation_class: None,
stack_depth: context.avm2.stack.len(),
scope_depth: context.avm2.scope_stack.len(),
@ -197,7 +200,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
outer: ScopeChain::new(context.avm2.stage_domain),
caller_domain: Some(domain),
caller_movie: None,
subclass_object: None,
superclass_object: None,
subclass: None,
activation_class: None,
stack_depth: context.avm2.stack.len(),
scope_depth: context.avm2.scope_stack.len(),
@ -262,7 +266,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
outer: ScopeChain::new(domain),
caller_domain: Some(domain),
caller_movie: script.translation_unit().map(|t| t.movie()),
subclass_object: None,
superclass_object: Some(context.avm2.classes().object), // The script global class extends Object
subclass: Some(script.global_class()),
activation_class,
stack_depth: context.avm2.stack.len(),
scope_depth: context.avm2.scope_stack.len(),
@ -315,7 +320,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
value: Option<&Value<'gc>>,
param_config: &ResolvedParamConfig<'gc>,
user_arguments: &[Value<'gc>],
callee: Option<Object<'gc>>,
bound_class: Option<Class<'gc>>,
) -> Result<Value<'gc>, Error<'gc>> {
let arg = if let Some(value) = value {
value
@ -330,7 +335,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
self,
method,
user_arguments,
callee,
bound_class,
)?));
};
@ -353,7 +358,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
method: Method<'gc>,
user_arguments: &[Value<'gc>],
signature: &[ResolvedParamConfig<'gc>],
callee: Option<Object<'gc>>,
bound_class: Option<Class<'gc>>,
) -> Result<Vec<Value<'gc>>, Error<'gc>> {
let mut arguments_list = Vec::new();
for (arg, param_config) in user_arguments.iter().zip(signature.iter()) {
@ -362,7 +367,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
Some(arg),
param_config,
user_arguments,
callee,
bound_class,
)?);
}
@ -379,7 +384,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
None,
param_config,
user_arguments,
callee,
bound_class,
)?);
}
}
@ -399,7 +404,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
outer: ScopeChain<'gc>,
this: Object<'gc>,
user_arguments: &[Value<'gc>],
subclass_object: Option<ClassObject<'gc>>,
superclass_object: Option<ClassObject<'gc>>,
bound_class: Option<Class<'gc>>,
callee: Object<'gc>,
) -> Result<(), Error<'gc>> {
let body: Result<_, Error<'gc>> = method
@ -433,7 +439,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
self.outer = outer;
self.caller_domain = Some(outer.domain());
self.caller_movie = Some(method.owner_movie());
self.subclass_object = subclass_object;
self.superclass_object = superclass_object;
self.activation_class = activation_class;
self.stack_depth = self.context.avm2.stack.len();
self.scope_depth = self.context.avm2.scope_stack.len();
@ -453,7 +459,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
self,
Method::Bytecode(method),
user_arguments,
Some(callee),
bound_class,
)?));
}
@ -462,7 +468,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
Method::Bytecode(method),
user_arguments,
signature,
Some(callee),
bound_class,
)?;
{
@ -523,7 +529,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
/// properly supercall.
pub fn from_builtin(
context: &'a mut UpdateContext<'gc>,
subclass_object: Option<ClassObject<'gc>>,
superclass_object: Option<ClassObject<'gc>>,
subclass: Option<Class<'gc>>,
outer: ScopeChain<'gc>,
caller_domain: Option<Domain<'gc>>,
caller_movie: Option<Arc<SwfMovie>>,
@ -537,7 +544,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
outer,
caller_domain,
caller_movie,
subclass_object,
superclass_object,
subclass,
activation_class: None,
stack_depth: context.avm2.stack.len(),
scope_depth: context.avm2.scope_stack.len(),
@ -554,12 +562,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let superclass_object = self
.subclass_object()
.and_then(|c| c.superclass_object())
.ok_or_else(|| {
Error::from("Attempted to call super constructor without a superclass.")
});
let superclass_object = superclass_object?;
.superclass_object
.expect("Superclass object is required to run super_init");
superclass_object.call_native_init(receiver.into(), args, self)
}
@ -636,15 +640,6 @@ impl<'a, 'gc> Activation<'a, 'gc> {
self.context.borrow_gc()
}
/// Get the class that defined the currently-executing method, if it
/// exists.
///
/// If the currently-executing method is not part of an ES4 class, then
/// this yields `None`.
pub fn subclass_object(&self) -> Option<ClassObject<'gc>> {
self.subclass_object
}
pub fn scope_frame(&self) -> &[Scope<'gc>] {
&self.context.avm2.scope_stack[self.scope_depth..]
}
@ -711,20 +706,22 @@ impl<'a, 'gc> Activation<'a, 'gc> {
/// Get the superclass of the class that defined the currently-executing
/// method, if it exists.
///
/// If the currently-executing method is not part of an ES4 class, or the
/// class does not have a superclass, then this yields an error. The `name`
/// parameter allows you to provide the name of a property you were
/// attempting to access on the object.
pub fn superclass_object(&self, name: &Multiname<'gc>) -> Result<ClassObject<'gc>, Error<'gc>> {
self.subclass_object
.and_then(|bc| bc.superclass_object())
.ok_or_else(|| {
format!(
"Cannot call supermethod (void) {} without a superclass",
name.to_qualified_name(self.context.gc_context)
)
.into()
})
/// If the currently-executing method is not part of a class, or the class
/// does not have a superclass, then this panics. The `name` parameter
/// allows you to provide the name of a property you were attempting to
/// access on the object.
pub fn superclass_object(&self, name: &Multiname<'gc>) -> ClassObject<'gc> {
self.superclass_object.unwrap_or_else(|| {
panic!(
"Cannot call supermethod {} without a superclass",
name.to_qualified_name(self.context.gc_context),
)
})
}
/// Get the class that defined the currently-executing method, if it exists.
pub fn subclass(&self) -> Option<Class<'gc>> {
self.subclass
}
/// Retrieve a namespace from the current constant pool.
@ -1263,7 +1260,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
let method = self.table_method(method, index, false)?;
// TODO: What scope should the function be executed with?
let scope = self.create_scopechain();
let function = FunctionObject::from_method(self, method, scope, None, None);
let function = FunctionObject::from_method(self, method, scope, None, None, None);
let value = function.call(receiver, &args, self)?;
self.push_stack(value);
@ -1282,7 +1279,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
.pop_stack()
.coerce_to_object_or_typeerror(self, Some(&multiname))?;
let superclass_object = self.superclass_object(&multiname)?;
let superclass_object = self.superclass_object(&multiname);
let value = superclass_object.call_super(&multiname, receiver, &args, self)?;
@ -1302,7 +1299,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
.pop_stack()
.coerce_to_object_or_typeerror(self, Some(&multiname))?;
let superclass_object = self.superclass_object(&multiname)?;
let superclass_object = self.superclass_object(&multiname);
superclass_object.call_super(&multiname, receiver, &args, self)?;
@ -1534,7 +1531,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
.pop_stack()
.coerce_to_object_or_typeerror(self, Some(&multiname))?;
let superclass_object = self.superclass_object(&multiname)?;
let superclass_object = self.superclass_object(&multiname);
let value = superclass_object.get_super(&multiname, object, self)?;
@ -1553,7 +1550,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
.pop_stack()
.coerce_to_object_or_typeerror(self, Some(&multiname))?;
let superclass_object = self.superclass_object(&multiname)?;
let superclass_object = self.superclass_object(&multiname);
superclass_object.set_super(&multiname, value, object, self)?;

View File

@ -1,6 +1,6 @@
use crate::avm2::class::Class;
use crate::avm2::function::display_function;
use crate::avm2::method::Method;
use crate::avm2::object::ClassObject;
use crate::string::WString;
use gc_arena::Collect;
@ -12,7 +12,7 @@ pub enum CallNode<'gc> {
GlobalInit(Script<'gc>),
Method {
method: Method<'gc>,
superclass: Option<ClassObject<'gc>>,
class: Option<Class<'gc>>,
},
}
@ -27,8 +27,8 @@ impl<'gc> CallStack<'gc> {
Self { stack: Vec::new() }
}
pub fn push(&mut self, method: Method<'gc>, superclass: Option<ClassObject<'gc>>) {
self.stack.push(CallNode::Method { method, superclass })
pub fn push(&mut self, method: Method<'gc>, class: Option<Class<'gc>>) {
self.stack.push(CallNode::Method { method, class })
}
pub fn push_global_init(&mut self, script: Script<'gc>) {
@ -59,9 +59,7 @@ impl<'gc> CallStack<'gc> {
// added by Ruffle
output.push_utf8(&format!("global$init() [TU={}]", name));
}
CallNode::Method { method, superclass } => {
display_function(output, method, *superclass)
}
CallNode::Method { method, class } => display_function(output, method, *class),
}
}
}

View File

@ -9,7 +9,6 @@ use std::mem::size_of;
use super::function::display_function;
use super::method::Method;
use super::ClassObject;
use super::Object;
/// An error generated while handling AVM2 logic
pub enum Error<'gc> {
@ -778,7 +777,7 @@ pub fn make_mismatch_error<'gc>(
activation: &mut Activation<'_, 'gc>,
method: Method<'gc>,
user_arguments: &[Value<'gc>],
callee: Option<Object<'gc>>,
bound_class: Option<Class<'gc>>,
) -> Result<Value<'gc>, Error<'gc>> {
let expected_num_params = method
.signature()
@ -787,17 +786,8 @@ pub fn make_mismatch_error<'gc>(
.count();
let mut function_name = WString::new();
let bound_superclass = callee.and_then(|callee| {
if let Some(cls) = callee.as_class_object() {
Some(cls)
} else {
callee
.as_function_object()
.and_then(|f| f.as_executable().and_then(|e| e.bound_superclass()))
}
});
display_function(&mut function_name, &method, bound_superclass);
display_function(&mut function_name, &method, bound_class);
return Err(Error::AvmError(argument_error(
activation,

View File

@ -1,4 +1,5 @@
use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::method::{Method, ParamConfig};
use crate::avm2::object::{ClassObject, Object};
use crate::avm2::scope::ScopeChain;
@ -25,12 +26,14 @@ pub struct BoundMethod<'gc> {
/// `Some` value indicates a bound executable.
bound_receiver: Option<Object<'gc>>,
/// The bound class for this method.
/// The superclass of the bound class for this method.
///
/// The `class` is the class that defined this method. If `None`,
/// then there is no defining class and `super` operations should fall
/// back to the `receiver`.
bound_class: Option<ClassObject<'gc>>,
/// The `bound_superclass` is the superclass of the class that defined
/// this method. If `None`, then there is no defining class and `super`
/// operations should be invalid.
bound_superclass: Option<ClassObject<'gc>>,
bound_class: Option<Class<'gc>>,
}
impl<'gc> BoundMethod<'gc> {
@ -39,12 +42,14 @@ impl<'gc> BoundMethod<'gc> {
scope: ScopeChain<'gc>,
receiver: Option<Object<'gc>>,
superclass: Option<ClassObject<'gc>>,
class: Option<Class<'gc>>,
) -> Self {
Self {
method,
scope,
bound_receiver: receiver,
bound_class: superclass,
bound_superclass: superclass,
bound_class: class,
}
}
@ -70,6 +75,7 @@ impl<'gc> BoundMethod<'gc> {
self.method,
self.scope,
receiver,
self.bound_superclass,
self.bound_class,
arguments,
activation,
@ -77,7 +83,7 @@ impl<'gc> BoundMethod<'gc> {
)
}
pub fn bound_superclass(&self) -> Option<ClassObject<'gc>> {
pub fn bound_class(&self) -> Option<Class<'gc>> {
self.bound_class
}
@ -87,7 +93,7 @@ impl<'gc> BoundMethod<'gc> {
pub fn debug_full_name(&self) -> WString {
let mut output = WString::new();
display_function(&mut output, &self.as_method(), self.bound_superclass());
display_function(&mut output, &self.as_method(), self.bound_class());
output
}
@ -135,7 +141,8 @@ pub fn exec<'gc>(
method: Method<'gc>,
scope: ScopeChain<'gc>,
receiver: Object<'gc>,
bound_class: Option<ClassObject<'gc>>,
bound_superclass: Option<ClassObject<'gc>>,
bound_class: Option<Class<'gc>>,
mut arguments: &[Value<'gc>],
activation: &mut Activation<'_, 'gc>,
callee: Object<'gc>,
@ -146,6 +153,7 @@ pub fn exec<'gc>(
let caller_movie = activation.caller_movie();
let mut activation = Activation::from_builtin(
activation.context,
bound_superclass,
bound_class,
scope,
caller_domain,
@ -173,7 +181,7 @@ pub fn exec<'gc>(
method,
arguments,
resolved_signature,
Some(callee),
bound_class,
)?;
activation
.context
@ -192,7 +200,15 @@ pub fn exec<'gc>(
// This used to be a one step called Activation::from_method,
// but avoiding moving an Activation around helps perf
let mut activation = Activation::from_nothing(activation.context);
activation.init_from_method(bm, scope, receiver, arguments, bound_class, callee)?;
activation.init_from_method(
bm,
scope,
receiver,
arguments,
bound_superclass,
bound_class,
callee,
)?;
activation
.context
.avm2
@ -229,20 +245,12 @@ impl<'gc> fmt::Debug for BoundMethod<'gc> {
pub fn display_function<'gc>(
output: &mut WString,
method: &Method<'gc>,
superclass: Option<ClassObject<'gc>>,
bound_class: Option<Class<'gc>>,
) {
let class_defs = superclass.map(|superclass| {
let i_class = superclass.inner_class_definition();
let name = i_class.name().to_qualified_name_no_mc();
if let Some(bound_class) = bound_class {
let name = bound_class.name().to_qualified_name_no_mc();
output.push_str(&name);
(
i_class,
i_class
.c_class()
.expect("inner_class_definition should be an i_class"),
)
});
}
match method {
Method::Native(method) => {
@ -252,27 +260,24 @@ pub fn display_function<'gc>(
Method::Bytecode(method) => {
// NOTE: The name of a bytecode method refers to the name of the trait that contains the method,
// rather than the name of the method itself.
if let Some((i_class, c_class)) = class_defs {
if c_class
if let Some(bound_class) = bound_class {
if bound_class
.instance_init()
.into_bytecode()
.map(|b| Gc::ptr_eq(b, *method))
.unwrap_or(false)
{
output.push_utf8("$cinit");
} else if !i_class
.instance_init()
.into_bytecode()
.map(|b| Gc::ptr_eq(b, *method))
.unwrap_or(false)
{
// TODO: Ideally, the declaring trait of this executable should already be attached here, that way
// we can avoid needing to lookup the trait like this.
if bound_class.c_class().is_none() {
// If the associated class has no c_class, it is a c_class,
// and the instance initializer is the class initializer.
output.push_utf8("cinit");
}
// We purposely do nothing for instance initializers
} else {
let mut method_trait = None;
// First search instance traits for the method
let instance_traits = i_class.traits();
for t in &*instance_traits {
let traits = bound_class.traits();
for t in &*traits {
if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok()) {
if Gc::ptr_eq(b, *method) {
method_trait = Some(t);
@ -281,21 +286,6 @@ pub fn display_function<'gc>(
}
}
let class_traits = c_class.traits();
if method_trait.is_none() {
// If we can't find it in instance traits, search class traits instead
for t in class_traits.iter() {
if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok()) {
if Gc::ptr_eq(b, *method) {
// Class traits always start with $
output.push_char('$');
method_trait = Some(t);
break;
}
}
}
}
if let Some(method_trait) = method_trait {
output.push_char('/');
match method_trait.kind() {
@ -317,7 +307,6 @@ pub fn display_function<'gc>(
}
// TODO: What happens if we can't find the trait?
}
// We purposely do nothing for instance initializers
} else if method.is_function && !method.method_name().is_empty() {
output.push_utf8("Function/");
output.push_utf8(&method.method_name());

View File

@ -133,7 +133,8 @@ pub fn class_init<'gc>(
Method::from_builtin(*method, name, gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,

View File

@ -58,7 +58,8 @@ fn class_init<'gc>(
Method::from_builtin(to_string, "toString", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -70,7 +71,8 @@ fn class_init<'gc>(
Method::from_builtin(value_of, "valueOf", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,

View File

@ -20,6 +20,17 @@ pub fn instance_init<'gc>(
Err("Classes cannot be constructed.".into())
}
/// Implements `Class`'s native instance initializer.
///
/// This exists so that super() calls in class initializers will work.
fn native_instance_init<'gc>(
_activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
Ok(Value::Undefined)
}
/// Implement's `Class`'s class initializer.
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc>,
@ -54,6 +65,15 @@ pub fn create_i_class<'gc>(
gc_context,
);
class_i_class.set_native_instance_init(
gc_context,
Method::from_builtin(
native_instance_init,
"<Class native instance initializer>",
gc_context,
),
);
const PUBLIC_INSTANCE_PROPERTIES: &[(
&str,
Option<NativeMethodImpl>,

View File

@ -298,7 +298,8 @@ pub fn class_init<'gc>(
Method::from_builtin(*method, name, gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,

View File

@ -59,7 +59,8 @@ pub fn class_init<'gc>(
Method::from_builtin(call, "call", activation.context.gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -71,7 +72,8 @@ pub fn class_init<'gc>(
Method::from_builtin(apply, "apply", activation.context.gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -83,7 +85,8 @@ pub fn class_init<'gc>(
Method::from_builtin(to_string, "toString", activation.context.gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -95,7 +98,8 @@ pub fn class_init<'gc>(
Method::from_builtin(to_string, "toLocaleString", activation.context.gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,

View File

@ -58,7 +58,8 @@ fn class_init<'gc>(
Method::from_builtin(to_exponential, "toExponential", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -70,7 +71,8 @@ fn class_init<'gc>(
Method::from_builtin(to_fixed, "toFixed", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -82,7 +84,8 @@ fn class_init<'gc>(
Method::from_builtin(to_precision, "toPrecision", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -94,7 +97,8 @@ fn class_init<'gc>(
Method::from_builtin(to_string, "toLocaleString", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -106,7 +110,8 @@ fn class_init<'gc>(
Method::from_builtin(to_string, "toString", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -118,7 +123,8 @@ fn class_init<'gc>(
Method::from_builtin(value_of, "valueOf", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,

View File

@ -130,7 +130,8 @@ pub fn class_init<'gc>(
Method::from_builtin(*method, name, gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,

View File

@ -58,7 +58,8 @@ fn class_init<'gc>(
Method::from_builtin(to_exponential, "toExponential", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -70,7 +71,8 @@ fn class_init<'gc>(
Method::from_builtin(to_fixed, "toFixed", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -82,7 +84,8 @@ fn class_init<'gc>(
Method::from_builtin(to_precision, "toPrecision", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -94,7 +97,8 @@ fn class_init<'gc>(
Method::from_builtin(to_string, "toLocaleString", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -106,7 +110,8 @@ fn class_init<'gc>(
Method::from_builtin(to_string, "toString", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -118,7 +123,8 @@ fn class_init<'gc>(
Method::from_builtin(value_of, "valueOf", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,

View File

@ -24,14 +24,14 @@ fn class_call<'gc>(
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let this_class = activation.subclass_object().unwrap();
let object_class = activation.avm2().classes().object;
if args.is_empty() {
return this_class.construct(activation, args).map(|o| o.into());
return object_class.construct(activation, args).map(|o| o.into());
}
let arg = args.get(0).cloned().unwrap();
if matches!(arg, Value::Undefined) || matches!(arg, Value::Null) {
return this_class.construct(activation, args).map(|o| o.into());
return object_class.construct(activation, args).map(|o| o.into());
}
Ok(arg)
}
@ -54,7 +54,8 @@ pub fn class_init<'gc>(
Method::from_builtin(has_own_property, "hasOwnProperty", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -66,7 +67,8 @@ pub fn class_init<'gc>(
Method::from_builtin(property_is_enumerable, "propertyIsEnumerable", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -82,7 +84,8 @@ pub fn class_init<'gc>(
),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -94,7 +97,8 @@ pub fn class_init<'gc>(
Method::from_builtin(is_prototype_of, "isPrototypeOf", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -106,7 +110,8 @@ pub fn class_init<'gc>(
Method::from_builtin(to_string, "toString", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -118,7 +123,8 @@ pub fn class_init<'gc>(
Method::from_builtin(to_locale_string, "toLocaleString", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -130,7 +136,8 @@ pub fn class_init<'gc>(
Method::from_builtin(value_of, "valueOf", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,

View File

@ -69,7 +69,7 @@ pub fn call_handler<'gc>(
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let this_class = activation.subclass_object().unwrap();
let this_class = activation.avm2().classes().regexp;
if args.len() == 1 {
let arg = args.get(0).cloned().unwrap();

View File

@ -76,7 +76,8 @@ pub fn class_init<'gc>(
Method::from_builtin(*method, name, gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,

View File

@ -58,7 +58,8 @@ fn class_init<'gc>(
Method::from_builtin(to_exponential, "toExponential", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -70,7 +71,8 @@ fn class_init<'gc>(
Method::from_builtin(to_fixed, "toFixed", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -82,7 +84,8 @@ fn class_init<'gc>(
Method::from_builtin(to_precision, "toPrecision", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -94,7 +97,8 @@ fn class_init<'gc>(
Method::from_builtin(to_string, "toLocaleString", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -106,7 +110,8 @@ fn class_init<'gc>(
Method::from_builtin(to_string, "toString", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,
@ -118,7 +123,8 @@ fn class_init<'gc>(
Method::from_builtin(value_of, "valueOf", gc_context),
scope,
None,
Some(this_class),
None,
None,
)
.into(),
activation,

View File

@ -72,16 +72,18 @@ fn class_call<'gc>(
)?));
}
let this_class = activation.subclass_object().unwrap();
let this_class = activation
.subclass()
.expect("Method call without bound class?");
let value_type = this_class
.inner_class_definition()
.param()
.ok_or("Cannot convert to unparametrized Vector")?; // technically unreachable
.expect("Cannot convert to unparametrized Vector"); // technically unreachable
let arg = args.get(0).cloned().unwrap();
let arg = arg.as_object().ok_or("Cannot convert to Vector")?;
if arg.instance_class() == this_class.inner_class_definition() {
if arg.instance_class() == this_class {
return Ok(arg.into());
}
@ -256,9 +258,9 @@ pub fn concat<'gc>(
// this is Vector.<int/uint/Number/*>
let my_base_vector_class = activation
.subclass_object()
.expect("Method call without bound class?")
.inner_class_definition();
.subclass()
.expect("Method call without bound class?");
if !arg.is_of_type(activation, my_base_vector_class) {
let base_vector_name = my_base_vector_class
.name()

View File

@ -600,17 +600,18 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
// Execute immediately if this method doesn't require binding
if !full_method.method.needs_arguments_object() {
let ClassBoundMethod {
method,
class,
super_class_obj,
scope,
class_obj,
..
method,
} = full_method;
return exec(
method,
scope.expect("Scope should exist here"),
self.into(),
class_obj,
super_class_obj,
Some(class),
arguments,
activation,
self.into(), // Callee deliberately invalid.

View File

@ -210,7 +210,7 @@ impl<'gc> ClassObject<'gc> {
self.instance_vtable().init_vtable(
class,
Some(self),
self.superclass_object(),
&class.traits(),
Some(self.instance_scope()),
self.superclass_object().map(|cls| cls.instance_vtable()),
@ -247,14 +247,16 @@ impl<'gc> ClassObject<'gc> {
.c_class()
.expect("ClassObject should have an i_class");
let class_classobject = activation.avm2().classes().class;
// class vtable == class traits + Class instance traits
let class_vtable = VTable::empty(activation.context.gc_context);
class_vtable.init_vtable(
c_class,
Some(self),
Some(class_classobject),
&c_class.traits(),
Some(self.class_scope()),
Some(activation.avm2().classes().class.instance_vtable()),
Some(class_classobject.instance_vtable()),
activation.context.gc_context,
);
@ -327,6 +329,7 @@ impl<'gc> ClassObject<'gc> {
activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> {
let object: Object<'gc> = self.into();
let class_classobject = activation.avm2().classes().class;
let scope = self.0.class_scope;
let c_class = self
@ -340,7 +343,8 @@ impl<'gc> ClassObject<'gc> {
class_initializer,
scope,
Some(object),
Some(self),
Some(class_classobject),
Some(c_class),
);
class_init_fn.call(object.into(), &[], activation)?;
@ -361,7 +365,8 @@ impl<'gc> ClassObject<'gc> {
method,
scope,
receiver.coerce_to_object(activation)?,
Some(self),
self.superclass_object(),
Some(self.inner_class_definition()),
arguments,
activation,
self.into(),
@ -385,7 +390,8 @@ impl<'gc> ClassObject<'gc> {
method,
scope,
receiver.coerce_to_object(activation)?,
Some(self),
self.superclass_object(),
Some(self.inner_class_definition()),
arguments,
activation,
self.into(),
@ -444,17 +450,18 @@ impl<'gc> ClassObject<'gc> {
if let Some(Property::Method { disp_id, .. }) = property {
// todo: handle errors
let ClassBoundMethod {
class_obj,
class,
super_class_obj,
scope,
method,
..
} = self.instance_vtable().get_full_method(disp_id).unwrap();
let callee = FunctionObject::from_method(
activation,
method,
scope.expect("Scope should exist here"),
Some(receiver),
class_obj,
super_class_obj,
Some(class),
);
callee.call(receiver.into(), arguments, activation)
@ -504,17 +511,18 @@ impl<'gc> ClassObject<'gc> {
) => {
// todo: handle errors
let ClassBoundMethod {
class_obj,
class,
super_class_obj,
scope,
method,
..
} = self.instance_vtable().get_full_method(disp_id).unwrap();
let callee = FunctionObject::from_method(
activation,
method,
scope.expect("Scope should exist here"),
Some(receiver),
class_obj,
super_class_obj,
Some(class),
);
// We call getters, but return the actual function object for normal methods
@ -587,13 +595,13 @@ impl<'gc> ClassObject<'gc> {
}) => {
// todo: handle errors
let ClassBoundMethod {
class_obj,
class,
super_class_obj,
scope,
method,
..
} = self.instance_vtable().get_full_method(disp_id).unwrap();
let callee =
FunctionObject::from_method(activation, method, scope.expect("Scope should exist here"), Some(receiver), class_obj);
FunctionObject::from_method(activation, method, scope.expect("Scope should exist here"), Some(receiver), super_class_obj, Some(class));
callee.call(receiver.into(), &[value], activation)?;
Ok(())
@ -765,7 +773,8 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> {
call_handler,
scope,
self.into(),
Some(self),
self.superclass_object(),
Some(self.inner_class_definition()),
arguments,
activation,
self.into(),

View File

@ -1,6 +1,7 @@
//! Function object impl
use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::function::BoundMethod;
use crate::avm2::method::{Method, NativeMethod};
use crate::avm2::object::script_object::{ScriptObject, ScriptObjectData};
@ -50,6 +51,7 @@ pub fn function_allocator<'gc>(
activation.create_scopechain(),
None,
None,
None,
)),
prototype: Lock::new(None),
},
@ -106,7 +108,7 @@ impl<'gc> FunctionObject<'gc> {
method: Method<'gc>,
scope: ScopeChain<'gc>,
) -> Result<FunctionObject<'gc>, Error<'gc>> {
let this = Self::from_method(activation, method, scope, None, None);
let this = Self::from_method(activation, method, scope, None, None, None);
let es3_proto = activation
.avm2()
.classes()
@ -127,10 +129,17 @@ impl<'gc> FunctionObject<'gc> {
method: Method<'gc>,
scope: ScopeChain<'gc>,
receiver: Option<Object<'gc>>,
subclass_object: Option<ClassObject<'gc>>,
superclass_object: Option<ClassObject<'gc>>,
subclass: Option<Class<'gc>>,
) -> FunctionObject<'gc> {
let fn_class = activation.avm2().classes().function;
let exec = BoundMethod::from_method(method, scope, receiver, subclass_object);
let exec = BoundMethod::from_method(
method,
scope,
receiver,
bound_superclass_object,
bound_class,
);
FunctionObject(Gc::new(
activation.context.gc_context,

View File

@ -360,13 +360,13 @@ pub fn optimize<'gc>(
// but this works since it's guaranteed to be set in `Activation::from_method`.
let this_value = activation.local_register(0);
let this_class = if let Some(this_class) = activation.subclass_object() {
if this_value.is_of_type(activation, this_class.inner_class_definition()) {
Some(this_class.inner_class_definition())
let this_class = if let Some(this_class) = activation.subclass() {
if this_value.is_of_type(activation, this_class) {
Some(this_class)
} else if let Some(this_object) = this_value.as_object() {
if this_object
.as_class_object()
.map(|c| c.inner_class_definition() == this_class.inner_class_definition())
.map(|c| c.inner_class_definition() == this_class)
.unwrap_or(false)
{
// Static method

View File

@ -641,7 +641,7 @@ impl<'gc> Script<'gc> {
globals.vtable().init_vtable(
globals.instance_class(),
self.0.read().global_class_obj,
Some(context.avm2.classes().object),
&self.traits()?,
Some(scope),
None,

View File

@ -55,7 +55,7 @@ impl PartialEq for VTable<'_> {
#[collect(no_drop)]
pub struct ClassBoundMethod<'gc> {
pub class: Class<'gc>,
pub class_obj: Option<ClassObject<'gc>>,
pub super_class_obj: Option<ClassObject<'gc>>,
pub scope: Option<ScopeChain<'gc>>,
pub method: Method<'gc>,
}
@ -211,7 +211,7 @@ impl<'gc> VTable<'gc> {
pub fn init_vtable(
self,
defining_class_def: Class<'gc>,
defining_class: Option<ClassObject<'gc>>,
super_class_obj: Option<ClassObject<'gc>>,
traits: &[Trait<'gc>],
scope: Option<ScopeChain<'gc>>,
superclass_vtable: Option<Self>,
@ -321,7 +321,7 @@ impl<'gc> VTable<'gc> {
TraitKind::Method { method, .. } => {
let entry = ClassBoundMethod {
class: defining_class_def,
class_obj: defining_class,
super_class_obj,
scope,
method: *method,
};
@ -350,7 +350,7 @@ impl<'gc> VTable<'gc> {
TraitKind::Getter { method, .. } => {
let entry = ClassBoundMethod {
class: defining_class_def,
class_obj: defining_class,
super_class_obj,
scope,
method: *method,
};
@ -388,7 +388,7 @@ impl<'gc> VTable<'gc> {
TraitKind::Setter { method, .. } => {
let entry = ClassBoundMethod {
class: defining_class_def,
class_obj: defining_class,
super_class_obj,
scope,
method: *method,
};
@ -518,10 +518,10 @@ impl<'gc> VTable<'gc> {
method: ClassBoundMethod<'gc>,
) -> FunctionObject<'gc> {
let ClassBoundMethod {
class_obj,
class,
super_class_obj,
scope,
method,
..
} = method;
FunctionObject::from_method(
@ -529,7 +529,8 @@ impl<'gc> VTable<'gc> {
method,
scope.expect("Scope should exist here"),
Some(receiver),
class_obj,
super_class_obj,
Some(class),
)
}