avm2: Add a vtable to Class, support getting vtable of static methods in optimizer
This commit is contained in:
parent
cc70686592
commit
43549ab1d7
|
@ -7,6 +7,7 @@ use crate::avm2::object::{ClassObject, Object};
|
|||
use crate::avm2::script::TranslationUnit;
|
||||
use crate::avm2::traits::{Trait, TraitKind};
|
||||
use crate::avm2::value::Value;
|
||||
use crate::avm2::vtable::VTable;
|
||||
use crate::avm2::Error;
|
||||
use crate::avm2::Multiname;
|
||||
use crate::avm2::Namespace;
|
||||
|
@ -17,9 +18,9 @@ use fnv::FnvHashMap;
|
|||
use gc_arena::{Collect, GcCell, Mutation};
|
||||
|
||||
use std::cell::Ref;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
|
||||
use swf::avm2::types::{
|
||||
Class as AbcClass, Instance as AbcInstance, Method as AbcMethod, MethodBody as AbcMethodBody,
|
||||
|
@ -101,7 +102,7 @@ pub struct ClassData<'gc> {
|
|||
|
||||
/// The list of interfaces this class directly implements. This does not include any
|
||||
/// superinterfaces, nor interfaces implemented by the superclass.
|
||||
direct_interfaces: Vec<Multiname<'gc>>,
|
||||
direct_interfaces: Vec<Class<'gc>>,
|
||||
|
||||
/// The instance allocator for this class.
|
||||
///
|
||||
|
@ -137,6 +138,8 @@ pub struct ClassData<'gc> {
|
|||
/// properties that would match.
|
||||
instance_traits: Vec<Trait<'gc>>,
|
||||
|
||||
instance_vtable: VTable<'gc>,
|
||||
|
||||
/// The class initializer for this class.
|
||||
///
|
||||
/// Must be called once and only once prior to any use of this class.
|
||||
|
@ -238,6 +241,7 @@ impl<'gc> Class<'gc> {
|
|||
instance_init,
|
||||
native_instance_init,
|
||||
instance_traits: Vec::new(),
|
||||
instance_vtable: VTable::empty(mc),
|
||||
class_init,
|
||||
class_initializer_called: false,
|
||||
call_handler: None,
|
||||
|
@ -302,6 +306,9 @@ impl<'gc> Class<'gc> {
|
|||
|
||||
new_class.set_param(mc, Some(Some(param)));
|
||||
new_class.0.write(mc).call_handler = object_vector_cls.call_handler();
|
||||
new_class
|
||||
.init_vtable(context)
|
||||
.expect("Vector class doesn't have any interfaces, so `init_vtable` cannot error");
|
||||
|
||||
drop(this_read);
|
||||
|
||||
|
@ -383,10 +390,18 @@ impl<'gc> Class<'gc> {
|
|||
|
||||
let mut interfaces = Vec::with_capacity(abc_instance.interfaces.len());
|
||||
for interface_name in &abc_instance.interfaces {
|
||||
let multiname = unit.pool_multiname_static(*interface_name, &mut activation.context)?;
|
||||
|
||||
interfaces.push(
|
||||
unit.pool_multiname_static(*interface_name, &mut activation.context)?
|
||||
.deref()
|
||||
.clone(),
|
||||
activation
|
||||
.domain()
|
||||
.get_class(&mut activation.context, &multiname)
|
||||
.ok_or_else(|| {
|
||||
make_error_1014(
|
||||
activation,
|
||||
multiname.to_qualified_name(activation.context.gc_context),
|
||||
)
|
||||
})?,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -453,6 +468,7 @@ impl<'gc> Class<'gc> {
|
|||
instance_init,
|
||||
native_instance_init,
|
||||
instance_traits: Vec::new(),
|
||||
instance_vtable: VTable::empty(activation.context.gc_context),
|
||||
class_init,
|
||||
class_initializer_called: false,
|
||||
call_handler: native_call_handler,
|
||||
|
@ -588,6 +604,79 @@ impl<'gc> Class<'gc> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init_vtable(self, context: &mut UpdateContext<'_, 'gc>) -> Result<(), Error<'gc>> {
|
||||
let read = self.0.read();
|
||||
|
||||
if !read.traits_loaded {
|
||||
panic!(
|
||||
"Attempted to initialize vtable on a class that did not have its traits loaded yet"
|
||||
);
|
||||
}
|
||||
|
||||
read.instance_vtable.init_vtable(
|
||||
None,
|
||||
self.protected_namespace(),
|
||||
&read.instance_traits,
|
||||
None,
|
||||
read.super_class.map(|c| c.instance_vtable()),
|
||||
context,
|
||||
);
|
||||
drop(read);
|
||||
|
||||
self.link_interfaces(context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn link_interfaces(self, context: &mut UpdateContext<'_, 'gc>) -> Result<(), Error<'gc>> {
|
||||
let mut interfaces = Vec::with_capacity(self.direct_interfaces().len());
|
||||
|
||||
let mut dedup = HashSet::new();
|
||||
let mut queue = vec![self];
|
||||
while let Some(cls) = queue.pop() {
|
||||
for interface in &*cls.direct_interfaces() {
|
||||
if !interface.is_interface() {
|
||||
return Err(format!(
|
||||
"Class {:?} is not an interface and cannot be implemented by classes",
|
||||
interface.name().local_name()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
if dedup.insert(ClassHashWrapper(*interface)) {
|
||||
queue.push(*interface);
|
||||
interfaces.push(*interface);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(super_class) = cls.super_class() {
|
||||
queue.push(super_class);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME - we should only be copying properties for newly-implemented
|
||||
// interfaces (i.e. those that were not already implemented by the superclass)
|
||||
// Otherwise, our behavior diverges from Flash Player in certain cases.
|
||||
// See the ignored test 'tests/tests/swfs/avm2/weird_superinterface_properties/'
|
||||
for interface in interfaces {
|
||||
for interface_trait in &*interface.instance_traits() {
|
||||
if !interface_trait.name().namespace().is_public() {
|
||||
let public_name = QName::new(
|
||||
context.avm2.public_namespace_vm_internal,
|
||||
interface_trait.name().local_name(),
|
||||
);
|
||||
self.0.read().instance_vtable.copy_property_for_interface(
|
||||
context.gc_context,
|
||||
public_name,
|
||||
interface_trait.name(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn for_activation(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
translation_unit: TranslationUnit<'gc>,
|
||||
|
@ -606,7 +695,7 @@ impl<'gc> Class<'gc> {
|
|||
)?);
|
||||
}
|
||||
|
||||
Ok(Class(GcCell::new(
|
||||
let class = Class(GcCell::new(
|
||||
activation.context.gc_context,
|
||||
ClassData {
|
||||
name: QName::new(activation.avm2().public_namespace_base_version, name),
|
||||
|
@ -627,6 +716,7 @@ impl<'gc> Class<'gc> {
|
|||
activation.context.gc_context,
|
||||
),
|
||||
instance_traits: traits,
|
||||
instance_vtable: VTable::empty(activation.context.gc_context),
|
||||
class_init: Method::from_builtin(
|
||||
|_, _, _| Ok(Value::Undefined),
|
||||
"<Activation object class constructor>",
|
||||
|
@ -640,7 +730,15 @@ impl<'gc> Class<'gc> {
|
|||
applications: Default::default(),
|
||||
class_objects: Vec::new(),
|
||||
},
|
||||
)))
|
||||
));
|
||||
|
||||
class.init_vtable(&mut activation.context)?;
|
||||
|
||||
Ok(class)
|
||||
}
|
||||
|
||||
pub fn instance_vtable(self) -> VTable<'gc> {
|
||||
self.0.read().instance_vtable
|
||||
}
|
||||
|
||||
pub fn name(self) -> QName<'gc> {
|
||||
|
@ -667,6 +765,42 @@ impl<'gc> Class<'gc> {
|
|||
self.0.read().protected_namespace
|
||||
}
|
||||
|
||||
pub fn mark_traits_loaded(self, mc: &Mutation<'gc>) {
|
||||
self.0.write(mc).traits_loaded = true;
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn define_constant_class_instance_trait(
|
||||
self,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
name: QName<'gc>,
|
||||
value: Value<'gc>,
|
||||
) {
|
||||
self.define_instance_trait(
|
||||
activation.context.gc_context,
|
||||
Trait::from_const(
|
||||
name,
|
||||
Multiname::new(activation.avm2().public_namespace_base_version, "Class"),
|
||||
Some(value),
|
||||
),
|
||||
);
|
||||
}
|
||||
#[inline(never)]
|
||||
pub fn define_constant_function_instance_trait(
|
||||
self,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
name: QName<'gc>,
|
||||
value: Value<'gc>,
|
||||
) {
|
||||
self.define_instance_trait(
|
||||
activation.context.gc_context,
|
||||
Trait::from_const(
|
||||
name,
|
||||
Multiname::new(activation.avm2().public_namespace_base_version, "Function"),
|
||||
Some(value),
|
||||
),
|
||||
);
|
||||
}
|
||||
#[inline(never)]
|
||||
pub fn define_constant_number_class_traits(
|
||||
self,
|
||||
|
@ -941,14 +1075,10 @@ impl<'gc> Class<'gc> {
|
|||
self.0.write(mc).class_initializer_called = true;
|
||||
}
|
||||
|
||||
pub fn direct_interfaces(&self) -> Ref<Vec<Multiname<'gc>>> {
|
||||
pub fn direct_interfaces(&self) -> Ref<Vec<Class<'gc>>> {
|
||||
Ref::map(self.0.read(), |c| &c.direct_interfaces)
|
||||
}
|
||||
|
||||
pub fn implements(self, mc: &Mutation<'gc>, iface: Multiname<'gc>) {
|
||||
self.0.write(mc).direct_interfaces.push(iface)
|
||||
}
|
||||
|
||||
/// Determine if this class is sealed (no dynamic properties)
|
||||
pub fn is_sealed(self) -> bool {
|
||||
self.0.read().attributes.contains(ClassAttributes::SEALED)
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::avm2::Namespace;
|
|||
use crate::avm2::QName;
|
||||
use crate::string::AvmString;
|
||||
use crate::tag_utils::{self, ControlFlow, SwfMovie, SwfSlice, SwfStream};
|
||||
use gc_arena::{Collect, Mutation};
|
||||
use gc_arena::Collect;
|
||||
use std::sync::Arc;
|
||||
use swf::TagCode;
|
||||
|
||||
|
@ -335,6 +335,9 @@ fn define_fn_on_global<'gc>(
|
|||
func,
|
||||
activation.avm2().classes().function,
|
||||
);
|
||||
script
|
||||
.global_class()
|
||||
.define_constant_function_instance_trait(activation, qname, func);
|
||||
}
|
||||
|
||||
/// Add a fully-formed class object builtin to the global scope.
|
||||
|
@ -342,7 +345,7 @@ fn define_fn_on_global<'gc>(
|
|||
/// This allows the caller to pre-populate the class's prototype with dynamic
|
||||
/// properties, if necessary.
|
||||
fn dynamic_class<'gc>(
|
||||
mc: &Mutation<'gc>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
class_object: ClassObject<'gc>,
|
||||
script: Script<'gc>,
|
||||
// The `ClassObject` of the `Class` class
|
||||
|
@ -352,8 +355,18 @@ fn dynamic_class<'gc>(
|
|||
let class = class_object.inner_class_definition();
|
||||
let name = class.name();
|
||||
|
||||
global.install_const_late(mc, name, class_object.into(), class_class);
|
||||
domain.export_definition(name, script, mc)
|
||||
global.install_const_late(
|
||||
activation.context.gc_context,
|
||||
name,
|
||||
class_object.into(),
|
||||
class_class,
|
||||
);
|
||||
script.global_class().define_constant_class_instance_trait(
|
||||
activation,
|
||||
name,
|
||||
class_object.into(),
|
||||
);
|
||||
domain.export_definition(name, script, activation.context.gc_context)
|
||||
}
|
||||
|
||||
/// Add a class builtin to the global scope.
|
||||
|
@ -387,6 +400,11 @@ fn class<'gc>(
|
|||
class_object.into(),
|
||||
activation.avm2().classes().class,
|
||||
);
|
||||
script.global_class().define_constant_class_instance_trait(
|
||||
activation,
|
||||
class_name,
|
||||
class_object.into(),
|
||||
);
|
||||
domain.export_definition(class_name, script, mc);
|
||||
domain.export_class(class_name, class_def, mc);
|
||||
Ok(class_object)
|
||||
|
@ -421,6 +439,11 @@ fn vector_class<'gc>(
|
|||
vector_cls.into(),
|
||||
activation.avm2().classes().class,
|
||||
);
|
||||
script.global_class().define_constant_class_instance_trait(
|
||||
activation,
|
||||
legacy_name,
|
||||
vector_cls.into(),
|
||||
);
|
||||
domain.export_definition(legacy_name, script, mc);
|
||||
Ok(vector_cls)
|
||||
}
|
||||
|
@ -531,18 +554,20 @@ pub fn load_player_globals<'gc>(
|
|||
let class_class = class_class.into_finished_class(activation)?;
|
||||
let object_class = object_class.into_finished_class(activation)?;
|
||||
let fn_class = fn_class.into_finished_class(activation)?;
|
||||
let _global_class = global_class.into_finished_class(activation)?;
|
||||
let global_class = global_class.into_finished_class(activation)?;
|
||||
|
||||
globals.set_proto(mc, global_proto);
|
||||
globals.set_instance_of(mc, global_class);
|
||||
|
||||
activation.context.avm2.toplevel_global_object = Some(globals);
|
||||
|
||||
script.set_global_class(mc, global_classdef);
|
||||
|
||||
// From this point, `globals` is safe to be modified
|
||||
|
||||
dynamic_class(mc, object_class, script, class_class);
|
||||
dynamic_class(mc, fn_class, script, class_class);
|
||||
dynamic_class(mc, class_class, script, class_class);
|
||||
dynamic_class(activation, object_class, script, class_class);
|
||||
dynamic_class(activation, fn_class, script, class_class);
|
||||
dynamic_class(activation, class_class, script, class_class);
|
||||
|
||||
// After this point, it is safe to initialize any other classes.
|
||||
// Make sure to initialize superclasses *before* their subclasses!
|
||||
|
@ -624,6 +649,9 @@ pub fn load_player_globals<'gc>(
|
|||
define_fn_on_global(activation, "", "parseFloat", script);
|
||||
define_fn_on_global(activation, "", "parseInt", script);
|
||||
|
||||
global_classdef.mark_traits_loaded(mc);
|
||||
global_classdef.init_vtable(&mut activation.context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1300,5 +1300,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
|
|||
activation,
|
||||
);
|
||||
|
||||
class.mark_traits_loaded(activation.context.gc_context);
|
||||
class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class
|
||||
}
|
||||
|
|
|
@ -383,12 +383,13 @@ fn describe_internal_body<'gc>(
|
|||
let declared_by = method.class;
|
||||
|
||||
if flags.contains(DescribeTypeFlags::HIDE_OBJECT)
|
||||
&& declared_by == activation.avm2().classes().object
|
||||
&& declared_by == Some(activation.avm2().classes().object)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let declared_by_name = declared_by
|
||||
.unwrap()
|
||||
.inner_class_definition()
|
||||
.name()
|
||||
.to_qualified_name(activation.context.gc_context);
|
||||
|
@ -476,6 +477,7 @@ fn describe_internal_body<'gc>(
|
|||
let accessor_type =
|
||||
method_type.to_qualified_name_or_star(activation.context.gc_context);
|
||||
let declared_by = defining_class
|
||||
.unwrap()
|
||||
.inner_class_definition()
|
||||
.name()
|
||||
.to_qualified_name(activation.context.gc_context);
|
||||
|
|
|
@ -170,5 +170,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
|
|||
activation,
|
||||
);
|
||||
|
||||
class.mark_traits_loaded(activation.context.gc_context);
|
||||
class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class
|
||||
}
|
||||
|
|
|
@ -75,5 +75,10 @@ pub fn create_class<'gc>(
|
|||
PUBLIC_INSTANCE_PROPERTIES,
|
||||
);
|
||||
|
||||
class_class.mark_traits_loaded(activation.context.gc_context);
|
||||
class_class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class_class
|
||||
}
|
||||
|
|
|
@ -1392,5 +1392,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
|
|||
activation,
|
||||
);
|
||||
|
||||
class.mark_traits_loaded(activation.context.gc_context);
|
||||
class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class
|
||||
}
|
||||
|
|
|
@ -268,5 +268,10 @@ pub fn create_class<'gc>(
|
|||
Method::from_builtin(class_call, "<Function call handler>", gc_context),
|
||||
);
|
||||
|
||||
function_class.mark_traits_loaded(activation.context.gc_context);
|
||||
function_class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
function_class
|
||||
}
|
||||
|
|
|
@ -36,11 +36,18 @@ pub fn create_class<'gc>(
|
|||
object_class: Class<'gc>,
|
||||
) -> Class<'gc> {
|
||||
let mc = activation.context.gc_context;
|
||||
Class::new(
|
||||
let class = Class::new(
|
||||
QName::new(activation.avm2().public_namespace_base_version, "global"),
|
||||
Some(object_class),
|
||||
Method::from_builtin(instance_init, "<global instance initializer>", mc),
|
||||
Method::from_builtin(class_init, "<global class initializer>", mc),
|
||||
mc,
|
||||
)
|
||||
);
|
||||
|
||||
class.mark_traits_loaded(activation.context.gc_context);
|
||||
class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class
|
||||
}
|
||||
|
|
|
@ -273,5 +273,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
|
|||
AS3_INSTANCE_METHODS,
|
||||
);
|
||||
|
||||
class.mark_traits_loaded(activation.context.gc_context);
|
||||
class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class
|
||||
}
|
||||
|
|
|
@ -217,5 +217,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
|
|||
PUBLIC_INSTANCE_AND_PROTO_METHODS,
|
||||
);
|
||||
|
||||
class.mark_traits_loaded(activation.context.gc_context);
|
||||
class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class
|
||||
}
|
||||
|
|
|
@ -432,5 +432,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
|
|||
AS3_INSTANCE_METHODS,
|
||||
);
|
||||
|
||||
class.mark_traits_loaded(activation.context.gc_context);
|
||||
class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class
|
||||
}
|
||||
|
|
|
@ -321,5 +321,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
|
|||
INTERNAL_INIT_METHOD,
|
||||
);
|
||||
|
||||
object_class.mark_traits_loaded(activation.context.gc_context);
|
||||
object_class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
object_class
|
||||
}
|
||||
|
|
|
@ -750,6 +750,11 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
|
|||
PUBLIC_CLASS_METHODS,
|
||||
);
|
||||
|
||||
class.mark_traits_loaded(activation.context.gc_context);
|
||||
class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class
|
||||
}
|
||||
|
||||
|
|
|
@ -276,5 +276,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
|
|||
AS3_INSTANCE_METHODS,
|
||||
);
|
||||
|
||||
class.mark_traits_loaded(activation.context.gc_context);
|
||||
class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class
|
||||
}
|
||||
|
|
|
@ -922,6 +922,12 @@ pub fn create_generic_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<
|
|||
|
||||
class.set_attributes(mc, ClassAttributes::GENERIC | ClassAttributes::FINAL);
|
||||
class.set_instance_allocator(mc, generic_vector_allocator);
|
||||
|
||||
class.mark_traits_loaded(activation.context.gc_context);
|
||||
class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class
|
||||
}
|
||||
|
||||
|
@ -1007,5 +1013,10 @@ pub fn create_builtin_class<'gc>(
|
|||
AS3_INSTANCE_METHODS,
|
||||
);
|
||||
|
||||
class.mark_traits_loaded(activation.context.gc_context);
|
||||
class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class
|
||||
}
|
||||
|
|
|
@ -29,5 +29,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
|
|||
mc,
|
||||
);
|
||||
|
||||
class.mark_traits_loaded(activation.context.gc_context);
|
||||
class
|
||||
.init_vtable(&mut activation.context)
|
||||
.expect("Native class's vtable should initialize");
|
||||
|
||||
class
|
||||
}
|
||||
|
|
|
@ -602,12 +602,12 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
|
||||
return exec(
|
||||
method,
|
||||
scope,
|
||||
scope.expect("Scope should exist here"),
|
||||
self.into(),
|
||||
Some(class),
|
||||
class,
|
||||
arguments,
|
||||
activation,
|
||||
class.into(), //Callee deliberately invalid.
|
||||
ScriptObject::custom_object(activation.context.gc_context, None, None), // Callee deliberately invalid.
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -248,12 +248,13 @@ impl<'gc> ClassObject<'gc> {
|
|||
class.validate_class(self.superclass_object())?;
|
||||
|
||||
self.instance_vtable().init_vtable(
|
||||
self,
|
||||
Some(self),
|
||||
class.protected_namespace(),
|
||||
&class.instance_traits(),
|
||||
self.instance_scope(),
|
||||
Some(self.instance_scope()),
|
||||
self.superclass_object().map(|cls| cls.instance_vtable()),
|
||||
activation,
|
||||
)?;
|
||||
&mut activation.context,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -283,12 +284,13 @@ impl<'gc> ClassObject<'gc> {
|
|||
|
||||
// class vtable == class traits + Class instance traits
|
||||
self.class_vtable().init_vtable(
|
||||
self,
|
||||
Some(self),
|
||||
class.protected_namespace(),
|
||||
&class.class_traits(),
|
||||
self.class_scope(),
|
||||
Some(self.class_scope()),
|
||||
Some(self.instance_of().unwrap().instance_vtable()),
|
||||
activation,
|
||||
)?;
|
||||
&mut activation.context,
|
||||
);
|
||||
|
||||
self.link_interfaces(activation)?;
|
||||
self.install_class_vtable_and_slots(activation.context.gc_context);
|
||||
|
@ -327,22 +329,13 @@ impl<'gc> ClassObject<'gc> {
|
|||
pub fn link_interfaces(self, activation: &mut Activation<'_, 'gc>) -> Result<(), Error<'gc>> {
|
||||
let mut write = self.0.write(activation.context.gc_context);
|
||||
let class = write.class;
|
||||
let scope = write.class_scope;
|
||||
|
||||
let interface_names = class.direct_interfaces().to_vec();
|
||||
let mut interfaces = Vec::with_capacity(interface_names.len());
|
||||
let mut interfaces = Vec::with_capacity(class.direct_interfaces().len());
|
||||
|
||||
let mut dedup = HashSet::new();
|
||||
let mut queue = vec![class];
|
||||
while let Some(cls) = queue.pop() {
|
||||
for interface_name in &*cls.direct_interfaces() {
|
||||
let interface = scope
|
||||
.domain()
|
||||
.get_class(&mut activation.context, interface_name)
|
||||
.ok_or_else(|| {
|
||||
Error::from(format!("Could not resolve interface {interface_name:?}"))
|
||||
})?;
|
||||
|
||||
for interface in &*cls.direct_interfaces() {
|
||||
if !interface.is_interface() {
|
||||
return Err(format!(
|
||||
"Class {:?} is not an interface and cannot be implemented by classes",
|
||||
|
@ -351,9 +344,9 @@ impl<'gc> ClassObject<'gc> {
|
|||
.into());
|
||||
}
|
||||
|
||||
if dedup.insert(ClassHashWrapper(interface)) {
|
||||
queue.push(interface);
|
||||
interfaces.push(interface);
|
||||
if dedup.insert(ClassHashWrapper(*interface)) {
|
||||
queue.push(*interface);
|
||||
interfaces.push(*interface);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -569,8 +562,13 @@ impl<'gc> ClassObject<'gc> {
|
|||
scope,
|
||||
method,
|
||||
} = self.instance_vtable().get_full_method(disp_id).unwrap();
|
||||
let callee =
|
||||
FunctionObject::from_method(activation, method, scope, Some(receiver), Some(class));
|
||||
let callee = FunctionObject::from_method(
|
||||
activation,
|
||||
method,
|
||||
scope.expect("Scope should exist here"),
|
||||
Some(receiver),
|
||||
class,
|
||||
);
|
||||
|
||||
callee.call(receiver.into(), arguments, activation)
|
||||
} else {
|
||||
|
@ -626,9 +624,9 @@ impl<'gc> ClassObject<'gc> {
|
|||
let callee = FunctionObject::from_method(
|
||||
activation,
|
||||
method,
|
||||
scope,
|
||||
scope.expect("Scope should exist here"),
|
||||
Some(receiver),
|
||||
Some(class),
|
||||
class,
|
||||
);
|
||||
|
||||
// We call getters, but return the actual function object for normal methods
|
||||
|
@ -706,7 +704,7 @@ impl<'gc> ClassObject<'gc> {
|
|||
method,
|
||||
} = self.instance_vtable().get_full_method(disp_id).unwrap();
|
||||
let callee =
|
||||
FunctionObject::from_method(activation, method, scope, Some(receiver), Some(class));
|
||||
FunctionObject::from_method(activation, method, scope.expect("Scope should exist here"), Some(receiver), class);
|
||||
|
||||
callee.call(receiver.into(), &[value], activation)?;
|
||||
Ok(())
|
||||
|
|
|
@ -6,11 +6,12 @@ use crate::avm2::object::{ClassObject, TObject};
|
|||
use crate::avm2::op::Op;
|
||||
use crate::avm2::property::Property;
|
||||
use crate::avm2::verify::JumpSources;
|
||||
use crate::avm2::vtable::VTable;
|
||||
|
||||
use gc_arena::Gc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct OptValue<'gc> {
|
||||
// This corresponds to the compile-time assumptions about the type:
|
||||
// - primitive types can't be undefined or null,
|
||||
|
@ -21,7 +22,9 @@ struct OptValue<'gc> {
|
|||
// (say, a Value::Number above hardcoded int-range that's still representable as i32).
|
||||
// Note that `null is Object` is still `false`. So think of this type more in terms of
|
||||
// "could this value be a possible value of `var t: T`"
|
||||
pub class: Option<ClassObject<'gc>>,
|
||||
pub class: Option<Class<'gc>>,
|
||||
|
||||
pub vtable: Option<VTable<'gc>>,
|
||||
|
||||
// true if the value is guaranteed to be Value::Integer
|
||||
// should only be set if class is numeric.
|
||||
|
@ -39,33 +42,59 @@ impl<'gc> OptValue<'gc> {
|
|||
pub fn any() -> Self {
|
||||
Self {
|
||||
class: None,
|
||||
vtable: None,
|
||||
contains_valid_integer: false,
|
||||
contains_valid_unsigned: false,
|
||||
guaranteed_null: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn null() -> Self {
|
||||
Self {
|
||||
class: None,
|
||||
vtable: None,
|
||||
guaranteed_null: true,
|
||||
..Self::any()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn of_type(class: ClassObject<'gc>) -> Self {
|
||||
let class = class.inner_class_definition();
|
||||
Self {
|
||||
class: Some(class),
|
||||
vtable: Some(class.instance_vtable()),
|
||||
..Self::any()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn of_type_from_class(class: Class<'gc>) -> Self {
|
||||
// FIXME: Getting the ClassObject this way should be unnecessary
|
||||
// after the ClassObject refactor
|
||||
if let Some(cls) = class.class_object() {
|
||||
Self::of_type(cls)
|
||||
} else {
|
||||
Self::any()
|
||||
Self {
|
||||
class: Some(class),
|
||||
vtable: Some(class.instance_vtable()),
|
||||
..Self::any()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vtable(self) -> Option<VTable<'gc>> {
|
||||
if let Some(class) = self.class {
|
||||
if class.is_interface() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
self.vtable
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> std::fmt::Debug for OptValue<'gc> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
f.debug_struct("OptValue")
|
||||
.field("class", &self.class)
|
||||
.field("contains_valid_integer", &self.contains_valid_integer)
|
||||
.field("contains_valid_unsigned", &self.contains_valid_unsigned)
|
||||
.field("guaranteed_null", &self.guaranteed_null)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -196,14 +225,32 @@ 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() {
|
||||
let (this_class, this_vtable) = if let Some(this_class) = activation.subclass_object() {
|
||||
if this_value.is_of_type(activation, this_class.inner_class_definition()) {
|
||||
Some(this_class)
|
||||
(Some(this_class), Some(this_class.inner_class_definition().instance_vtable()))
|
||||
} else if this_value
|
||||
.as_object()
|
||||
.and_then(|o| o.as_class_object())
|
||||
.map(|c| c.inner_class_definition() == this_class.inner_class_definition())
|
||||
.unwrap_or(false) {
|
||||
// Static method
|
||||
(
|
||||
Some(activation.avm2().classes().class),
|
||||
Some(this_class.class_vtable()),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
(None, None)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let this_value = OptValue {
|
||||
class: this_class.map(|c| c.inner_class_definition()),
|
||||
vtable: this_vtable,
|
||||
contains_valid_integer: false,
|
||||
contains_valid_unsigned: false,
|
||||
guaranteed_null: false,
|
||||
};
|
||||
|
||||
let argument_types = resolved_parameters
|
||||
|
@ -213,9 +260,7 @@ pub fn optimize<'gc>(
|
|||
|
||||
// Initial set of local types
|
||||
let mut initial_local_types = Locals::new(method_body.num_locals as usize);
|
||||
if let Some(this_class) = this_class {
|
||||
initial_local_types.set(0, OptValue::of_type(this_class));
|
||||
}
|
||||
initial_local_types.set(0, this_value);
|
||||
|
||||
for (i, argument_type) in argument_types.iter().enumerate() {
|
||||
if let Some(argument_type) = argument_type {
|
||||
|
@ -303,10 +348,11 @@ pub fn optimize<'gc>(
|
|||
if let (Some(source_local_class), Some(target_local_class)) =
|
||||
(source_local.class, target_local.class)
|
||||
{
|
||||
if source_local_class.inner_class_definition()
|
||||
== target_local_class.inner_class_definition()
|
||||
{
|
||||
merged_types.set(i, OptValue::of_type(source_local_class));
|
||||
if source_local_class == target_local_class {
|
||||
merged_types.set(
|
||||
i,
|
||||
OptValue::of_type_from_class(source_local_class),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -337,16 +383,16 @@ pub fn optimize<'gc>(
|
|||
}
|
||||
Op::CoerceB => {
|
||||
let stack_value = stack.pop_or_any();
|
||||
if stack_value.class == Some(types.boolean) {
|
||||
if stack_value.class == Some(types.boolean.inner_class_definition()) {
|
||||
*op = Op::Nop;
|
||||
}
|
||||
stack.push_class_object(types.boolean);
|
||||
}
|
||||
Op::CoerceD => {
|
||||
let stack_value = stack.pop_or_any();
|
||||
if stack_value.class == Some(types.number)
|
||||
|| stack_value.class == Some(types.int)
|
||||
|| stack_value.class == Some(types.uint)
|
||||
if stack_value.class == Some(types.number.inner_class_definition())
|
||||
|| stack_value.class == Some(types.int.inner_class_definition())
|
||||
|| stack_value.class == Some(types.uint.inner_class_definition())
|
||||
{
|
||||
*op = Op::Nop;
|
||||
}
|
||||
|
@ -508,12 +554,12 @@ pub fn optimize<'gc>(
|
|||
Op::Add => {
|
||||
let value2 = stack.pop_or_any();
|
||||
let value1 = stack.pop_or_any();
|
||||
if (value1.class == Some(types.int)
|
||||
|| value1.class == Some(types.uint)
|
||||
|| value1.class == Some(types.number))
|
||||
&& (value2.class == Some(types.int)
|
||||
|| value2.class == Some(types.uint)
|
||||
|| value2.class == Some(types.number))
|
||||
if (value1.class == Some(types.int.inner_class_definition())
|
||||
|| value1.class == Some(types.uint.inner_class_definition())
|
||||
|| value1.class == Some(types.number.inner_class_definition()))
|
||||
&& (value2.class == Some(types.int.inner_class_definition())
|
||||
|| value2.class == Some(types.uint.inner_class_definition())
|
||||
|| value2.class == Some(types.number.inner_class_definition()))
|
||||
{
|
||||
stack.push_class_object(types.number);
|
||||
} else {
|
||||
|
@ -668,8 +714,8 @@ pub fn optimize<'gc>(
|
|||
// if T is non-nullable, we can assume the result is typed T
|
||||
new_value = OptValue::of_type_from_class(*class);
|
||||
}
|
||||
if let Some(class_object) = stack_value.class {
|
||||
if *class == class_object.inner_class_definition() {
|
||||
if let Some(stack_class) = stack_value.class {
|
||||
if *class == stack_class {
|
||||
// If type check is guaranteed, preserve original type
|
||||
// TODO: there are more cases when this can succeed,
|
||||
// like inheritance and numbers (`x: Number = 1; x as int;`)
|
||||
|
@ -696,9 +742,9 @@ pub fn optimize<'gc>(
|
|||
{
|
||||
*op = Op::Nop;
|
||||
}
|
||||
} else if let Some(class_object) = stack_value.class {
|
||||
} else if let Some(stack_class) = stack_value.class {
|
||||
// TODO: this could check for inheritance
|
||||
if *class == class_object.inner_class_definition() {
|
||||
if *class == stack_class {
|
||||
*op = Op::Nop;
|
||||
}
|
||||
}
|
||||
|
@ -760,12 +806,12 @@ pub fn optimize<'gc>(
|
|||
if !multiname.has_lazy_component() && has_simple_scoping {
|
||||
let outer_scope = activation.outer();
|
||||
if !outer_scope.is_empty() {
|
||||
if let Some(this_class) = this_class {
|
||||
if this_class.instance_vtable().has_trait(&multiname) {
|
||||
if let Some(this_vtable) = this_vtable {
|
||||
if this_vtable.has_trait(&multiname) {
|
||||
*op = Op::GetScopeObject { index: 0 };
|
||||
|
||||
stack_push_done = true;
|
||||
stack.push_class_object(this_class);
|
||||
stack.push(this_value);
|
||||
}
|
||||
} else {
|
||||
stack_push_done = true;
|
||||
|
@ -859,10 +905,10 @@ pub fn optimize<'gc>(
|
|||
let mut stack_push_done = false;
|
||||
let stack_value = stack.pop_or_any();
|
||||
|
||||
if let Some(class) = stack_value.class {
|
||||
if !class.inner_class_definition().is_interface() {
|
||||
let mut value_class =
|
||||
class.instance_vtable().slot_classes()[*slot_id as usize];
|
||||
if let Some(vtable) = stack_value.vtable() {
|
||||
let slot_classes = vtable.slot_classes();
|
||||
let value_class = slot_classes.get(*slot_id as usize).copied();
|
||||
if let Some(mut value_class) = value_class {
|
||||
let resolved_value_class = value_class.get_class(activation);
|
||||
if let Ok(class) = resolved_value_class {
|
||||
stack_push_done = true;
|
||||
|
@ -874,7 +920,8 @@ pub fn optimize<'gc>(
|
|||
}
|
||||
}
|
||||
|
||||
class.instance_vtable().set_slot_class(
|
||||
drop(slot_classes);
|
||||
vtable.set_slot_class(
|
||||
activation.context.gc_context,
|
||||
*slot_id as usize,
|
||||
value_class,
|
||||
|
@ -896,43 +943,40 @@ pub fn optimize<'gc>(
|
|||
let stack_value = stack.pop_or_any();
|
||||
|
||||
if !multiname.has_lazy_component() {
|
||||
if let Some(class) = stack_value.class {
|
||||
if !class.inner_class_definition().is_interface() {
|
||||
match class.instance_vtable().get_trait(multiname) {
|
||||
Some(Property::Slot { slot_id })
|
||||
| Some(Property::ConstSlot { slot_id }) => {
|
||||
*op = Op::GetSlot { index: slot_id };
|
||||
if let Some(vtable) = stack_value.vtable() {
|
||||
match vtable.get_trait(multiname) {
|
||||
Some(Property::Slot { slot_id })
|
||||
| Some(Property::ConstSlot { slot_id }) => {
|
||||
*op = Op::GetSlot { index: slot_id };
|
||||
|
||||
let mut value_class =
|
||||
class.instance_vtable().slot_classes()[slot_id as usize];
|
||||
let resolved_value_class = value_class.get_class(activation);
|
||||
if let Ok(class) = resolved_value_class {
|
||||
stack_push_done = true;
|
||||
let mut value_class = vtable.slot_classes()[slot_id as usize];
|
||||
let resolved_value_class = value_class.get_class(activation);
|
||||
if let Ok(class) = resolved_value_class {
|
||||
stack_push_done = true;
|
||||
|
||||
if let Some(class) = class {
|
||||
stack.push_class(class);
|
||||
} else {
|
||||
stack.push_any();
|
||||
}
|
||||
if let Some(class) = class {
|
||||
stack.push_class(class);
|
||||
} else {
|
||||
stack.push_any();
|
||||
}
|
||||
}
|
||||
|
||||
class.instance_vtable().set_slot_class(
|
||||
activation.context.gc_context,
|
||||
slot_id as usize,
|
||||
value_class,
|
||||
);
|
||||
}
|
||||
Some(Property::Virtual {
|
||||
get: Some(disp_id), ..
|
||||
}) => {
|
||||
*op = Op::CallMethod {
|
||||
num_args: 0,
|
||||
index: disp_id,
|
||||
push_return_value: true,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
vtable.set_slot_class(
|
||||
activation.context.gc_context,
|
||||
slot_id as usize,
|
||||
value_class,
|
||||
);
|
||||
}
|
||||
Some(Property::Virtual {
|
||||
get: Some(disp_id), ..
|
||||
}) => {
|
||||
*op = Op::CallMethod {
|
||||
num_args: 0,
|
||||
index: disp_id,
|
||||
push_return_value: true,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -948,45 +992,40 @@ pub fn optimize<'gc>(
|
|||
stack.pop_for_multiname(*multiname);
|
||||
let stack_value = stack.pop_or_any();
|
||||
if !multiname.has_lazy_component() {
|
||||
if let Some(class) = stack_value.class {
|
||||
if !class.inner_class_definition().is_interface() {
|
||||
match class.instance_vtable().get_trait(multiname) {
|
||||
Some(Property::Slot { slot_id })
|
||||
| Some(Property::ConstSlot { slot_id }) => {
|
||||
*op = Op::SetSlot { index: slot_id };
|
||||
if let Some(vtable) = stack_value.vtable() {
|
||||
match vtable.get_trait(multiname) {
|
||||
Some(Property::Slot { slot_id })
|
||||
| Some(Property::ConstSlot { slot_id }) => {
|
||||
*op = Op::SetSlot { index: slot_id };
|
||||
|
||||
// If the set value's type is the same as the type of the slot,
|
||||
// a SetSlotNoCoerce can be emitted.
|
||||
let mut value_class =
|
||||
class.instance_vtable().slot_classes()[slot_id as usize];
|
||||
let resolved_value_class = value_class.get_class(activation);
|
||||
// If the set value's type is the same as the type of the slot,
|
||||
// a SetSlotNoCoerce can be emitted.
|
||||
let mut value_class = vtable.slot_classes()[slot_id as usize];
|
||||
let resolved_value_class = value_class.get_class(activation);
|
||||
|
||||
if let Ok(slot_class) = resolved_value_class {
|
||||
if let Some(slot_class) = slot_class {
|
||||
if let Some(set_value_class) = set_value.class {
|
||||
if set_value_class.inner_class_definition()
|
||||
== slot_class
|
||||
{
|
||||
*op = Op::SetSlotNoCoerce { index: slot_id };
|
||||
}
|
||||
if let Ok(slot_class) = resolved_value_class {
|
||||
if let Some(slot_class) = slot_class {
|
||||
if let Some(set_value_class) = set_value.class {
|
||||
if set_value_class == slot_class {
|
||||
*op = Op::SetSlotNoCoerce { index: slot_id };
|
||||
}
|
||||
} else {
|
||||
// Slot type was Any, no coercion will be done anyways
|
||||
*op = Op::SetSlotNoCoerce { index: slot_id };
|
||||
}
|
||||
} else {
|
||||
// Slot type was Any, no coercion will be done anyways
|
||||
*op = Op::SetSlotNoCoerce { index: slot_id };
|
||||
}
|
||||
}
|
||||
Some(Property::Virtual {
|
||||
set: Some(disp_id), ..
|
||||
}) => {
|
||||
*op = Op::CallMethod {
|
||||
num_args: 1,
|
||||
index: disp_id,
|
||||
push_return_value: false,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(Property::Virtual {
|
||||
set: Some(disp_id), ..
|
||||
}) => {
|
||||
*op = Op::CallMethod {
|
||||
num_args: 1,
|
||||
index: disp_id,
|
||||
push_return_value: false,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -998,44 +1037,39 @@ pub fn optimize<'gc>(
|
|||
stack.pop_for_multiname(*multiname);
|
||||
let stack_value = stack.pop_or_any();
|
||||
if !multiname.has_lazy_component() {
|
||||
if let Some(class) = stack_value.class {
|
||||
if !class.inner_class_definition().is_interface() {
|
||||
match class.instance_vtable().get_trait(multiname) {
|
||||
Some(Property::Slot { slot_id }) => {
|
||||
*op = Op::SetSlot { index: slot_id };
|
||||
if let Some(vtable) = stack_value.vtable() {
|
||||
match vtable.get_trait(multiname) {
|
||||
Some(Property::Slot { slot_id }) => {
|
||||
*op = Op::SetSlot { index: slot_id };
|
||||
|
||||
// If the set value's type is the same as the type of the slot,
|
||||
// a SetSlotNoCoerce can be emitted.
|
||||
let mut value_class =
|
||||
class.instance_vtable().slot_classes()[slot_id as usize];
|
||||
let resolved_value_class = value_class.get_class(activation);
|
||||
// If the set value's type is the same as the type of the slot,
|
||||
// a SetSlotNoCoerce can be emitted.
|
||||
let mut value_class = vtable.slot_classes()[slot_id as usize];
|
||||
let resolved_value_class = value_class.get_class(activation);
|
||||
|
||||
if let Ok(slot_class) = resolved_value_class {
|
||||
if let Some(slot_class) = slot_class {
|
||||
if let Some(set_value_class) = set_value.class {
|
||||
if set_value_class.inner_class_definition()
|
||||
== slot_class
|
||||
{
|
||||
*op = Op::SetSlotNoCoerce { index: slot_id };
|
||||
}
|
||||
if let Ok(slot_class) = resolved_value_class {
|
||||
if let Some(slot_class) = slot_class {
|
||||
if let Some(set_value_class) = set_value.class {
|
||||
if set_value_class == slot_class {
|
||||
*op = Op::SetSlotNoCoerce { index: slot_id };
|
||||
}
|
||||
} else {
|
||||
// Slot type was Any, no coercion will be done anyways
|
||||
*op = Op::SetSlotNoCoerce { index: slot_id };
|
||||
}
|
||||
} else {
|
||||
// Slot type was Any, no coercion will be done anyways
|
||||
*op = Op::SetSlotNoCoerce { index: slot_id };
|
||||
}
|
||||
}
|
||||
Some(Property::Virtual {
|
||||
set: Some(disp_id), ..
|
||||
}) => {
|
||||
*op = Op::CallMethod {
|
||||
num_args: 1,
|
||||
index: disp_id,
|
||||
push_return_value: false,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(Property::Virtual {
|
||||
set: Some(disp_id), ..
|
||||
}) => {
|
||||
*op = Op::CallMethod {
|
||||
num_args: 1,
|
||||
index: disp_id,
|
||||
push_return_value: false,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1128,18 +1162,16 @@ pub fn optimize<'gc>(
|
|||
let stack_value = stack.pop_or_any();
|
||||
|
||||
if !multiname.has_lazy_component() {
|
||||
if let Some(class) = stack_value.class {
|
||||
if !class.inner_class_definition().is_interface() {
|
||||
match class.instance_vtable().get_trait(multiname) {
|
||||
Some(Property::Method { disp_id }) => {
|
||||
*op = Op::CallMethod {
|
||||
num_args: *num_args,
|
||||
index: disp_id,
|
||||
push_return_value: true,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
if let Some(vtable) = stack_value.vtable() {
|
||||
match vtable.get_trait(multiname) {
|
||||
Some(Property::Method { disp_id }) => {
|
||||
*op = Op::CallMethod {
|
||||
num_args: *num_args,
|
||||
index: disp_id,
|
||||
push_return_value: true,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1161,18 +1193,16 @@ pub fn optimize<'gc>(
|
|||
let stack_value = stack.pop_or_any();
|
||||
|
||||
if !multiname.has_lazy_component() {
|
||||
if let Some(class) = stack_value.class {
|
||||
if !class.inner_class_definition().is_interface() {
|
||||
match class.instance_vtable().get_trait(multiname) {
|
||||
Some(Property::Method { disp_id }) => {
|
||||
*op = Op::CallMethod {
|
||||
num_args: *num_args,
|
||||
index: disp_id,
|
||||
push_return_value: false,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
if let Some(vtable) = stack_value.vtable() {
|
||||
match vtable.get_trait(multiname) {
|
||||
Some(Property::Method { disp_id }) => {
|
||||
*op = Op::CallMethod {
|
||||
num_args: *num_args,
|
||||
index: disp_id,
|
||||
push_return_value: false,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1246,26 +1276,24 @@ pub fn optimize<'gc>(
|
|||
let global_scope = outer_scope.get_unchecked(0);
|
||||
|
||||
if let Some(class) = global_scope.values().instance_of() {
|
||||
if !class.inner_class_definition().is_interface() {
|
||||
let mut value_class =
|
||||
class.instance_vtable().slot_classes()[*slot_id as usize];
|
||||
let resolved_value_class = value_class.get_class(activation);
|
||||
if let Ok(class) = resolved_value_class {
|
||||
stack_push_done = true;
|
||||
let mut value_class =
|
||||
class.instance_vtable().slot_classes()[*slot_id as usize];
|
||||
let resolved_value_class = value_class.get_class(activation);
|
||||
if let Ok(class) = resolved_value_class {
|
||||
stack_push_done = true;
|
||||
|
||||
if let Some(class) = class {
|
||||
stack.push_class(class);
|
||||
} else {
|
||||
stack.push_any();
|
||||
}
|
||||
if let Some(class) = class {
|
||||
stack.push_class(class);
|
||||
} else {
|
||||
stack.push_any();
|
||||
}
|
||||
|
||||
class.instance_vtable().set_slot_class(
|
||||
activation.context.gc_context,
|
||||
*slot_id as usize,
|
||||
value_class,
|
||||
);
|
||||
}
|
||||
|
||||
class.instance_vtable().set_slot_class(
|
||||
activation.context.gc_context,
|
||||
*slot_id as usize,
|
||||
value_class,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1348,7 +1376,7 @@ pub fn optimize<'gc>(
|
|||
|
||||
if let Some(return_type) = return_type {
|
||||
if let Some(stack_value_class) = stack_value.class {
|
||||
if stack_value_class.inner_class_definition() == return_type {
|
||||
if stack_value_class == return_type {
|
||||
*op = Op::ReturnValueNoCoerce;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -229,6 +229,7 @@ impl<'gc> TranslationUnit<'gc> {
|
|||
self.0.write(activation.context.gc_context).classes[class_index as usize] = Some(class);
|
||||
|
||||
class.load_traits(activation, self, class_index)?;
|
||||
class.init_vtable(&mut activation.context)?;
|
||||
|
||||
Ok(class)
|
||||
}
|
||||
|
@ -258,8 +259,14 @@ impl<'gc> TranslationUnit<'gc> {
|
|||
|
||||
let global_obj = global_class.construct(activation, &[])?;
|
||||
|
||||
let mut script =
|
||||
Script::from_abc_index(self, script_index, global_obj, domain, activation)?;
|
||||
let mut script = Script::from_abc_index(
|
||||
self,
|
||||
script_index,
|
||||
global_obj,
|
||||
global_classdef,
|
||||
domain,
|
||||
activation,
|
||||
)?;
|
||||
self.0.write(activation.context.gc_context).scripts[script_index as usize] = Some(script);
|
||||
|
||||
script.load_traits(self, script_index, activation)?;
|
||||
|
@ -416,6 +423,9 @@ pub struct ScriptData<'gc> {
|
|||
/// The global object for the script.
|
||||
globals: Object<'gc>,
|
||||
|
||||
/// The class of this script's global object.
|
||||
global_class: Option<Class<'gc>>,
|
||||
|
||||
/// The domain associated with this script.
|
||||
domain: Domain<'gc>,
|
||||
|
||||
|
@ -451,6 +461,7 @@ impl<'gc> Script<'gc> {
|
|||
mc,
|
||||
ScriptData {
|
||||
globals,
|
||||
global_class: None,
|
||||
domain,
|
||||
init: Method::from_builtin(
|
||||
|_, _, _| Ok(Value::Undefined),
|
||||
|
@ -478,6 +489,7 @@ impl<'gc> Script<'gc> {
|
|||
unit: TranslationUnit<'gc>,
|
||||
script_index: u32,
|
||||
globals: Object<'gc>,
|
||||
global_class: Class<'gc>,
|
||||
domain: Domain<'gc>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
) -> Result<Self, Error<'gc>> {
|
||||
|
@ -494,6 +506,7 @@ impl<'gc> Script<'gc> {
|
|||
activation.context.gc_context,
|
||||
ScriptData {
|
||||
globals,
|
||||
global_class: Some(global_class),
|
||||
domain,
|
||||
init,
|
||||
traits: Vec::new(),
|
||||
|
@ -542,9 +555,19 @@ impl<'gc> Script<'gc> {
|
|||
.export_class(newtrait.name(), *class, activation.context.gc_context);
|
||||
}
|
||||
|
||||
write.traits.push(newtrait);
|
||||
write.traits.push(newtrait.clone());
|
||||
write
|
||||
.global_class
|
||||
.expect("Global class should be initialized")
|
||||
.define_instance_trait(activation.context.gc_context, newtrait);
|
||||
}
|
||||
|
||||
drop(write);
|
||||
|
||||
self.global_class()
|
||||
.mark_traits_loaded(activation.context.gc_context);
|
||||
self.global_class().init_vtable(&mut activation.context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -562,6 +585,17 @@ impl<'gc> Script<'gc> {
|
|||
self.0.read().translation_unit
|
||||
}
|
||||
|
||||
pub fn global_class(self) -> Class<'gc> {
|
||||
self.0
|
||||
.read()
|
||||
.global_class
|
||||
.expect("Global class should be initialized if it is accessed")
|
||||
}
|
||||
|
||||
pub fn set_global_class(self, mc: &Mutation<'gc>, global_class: Class<'gc>) {
|
||||
self.0.write(mc).global_class = Some(global_class);
|
||||
}
|
||||
|
||||
/// Return the global scope for the script.
|
||||
///
|
||||
/// If the script has not yet been initialized, this will initialize it on
|
||||
|
@ -581,12 +615,17 @@ impl<'gc> Script<'gc> {
|
|||
let scope = ScopeChain::new(domain);
|
||||
|
||||
globals.vtable().unwrap().init_vtable(
|
||||
globals.instance_of().unwrap(),
|
||||
globals.instance_of(),
|
||||
globals
|
||||
.instance_of()
|
||||
.unwrap()
|
||||
.inner_class_definition()
|
||||
.protected_namespace(),
|
||||
&self.traits()?,
|
||||
scope,
|
||||
Some(scope),
|
||||
None,
|
||||
&mut null_activation,
|
||||
)?;
|
||||
&mut null_activation.context,
|
||||
);
|
||||
globals.install_instance_slots(context.gc_context);
|
||||
|
||||
Avm2::run_script_initializer(*self, context)?;
|
||||
|
|
|
@ -11,6 +11,7 @@ use crate::avm2::Error;
|
|||
use crate::avm2::Multiname;
|
||||
use crate::avm2::Namespace;
|
||||
use crate::avm2::QName;
|
||||
use crate::context::UpdateContext;
|
||||
use crate::string::AvmString;
|
||||
use gc_arena::{Collect, GcCell, Mutation};
|
||||
use std::cell::Ref;
|
||||
|
@ -24,10 +25,6 @@ pub struct VTable<'gc>(GcCell<'gc, VTableData<'gc>>);
|
|||
#[derive(Collect, Clone)]
|
||||
#[collect(no_drop)]
|
||||
pub struct VTableData<'gc> {
|
||||
/// should always be Some post-initialization
|
||||
defining_class: Option<ClassObject<'gc>>,
|
||||
|
||||
/// should always be Some post-initialization
|
||||
scope: Option<ScopeChain<'gc>>,
|
||||
|
||||
protected_namespace: Option<Namespace<'gc>>,
|
||||
|
@ -55,8 +52,8 @@ pub struct VTableData<'gc> {
|
|||
#[derive(Collect, Clone)]
|
||||
#[collect(no_drop)]
|
||||
pub struct ClassBoundMethod<'gc> {
|
||||
pub class: ClassObject<'gc>,
|
||||
pub scope: ScopeChain<'gc>,
|
||||
pub class: Option<ClassObject<'gc>>,
|
||||
pub scope: Option<ScopeChain<'gc>>,
|
||||
pub method: Method<'gc>,
|
||||
}
|
||||
|
||||
|
@ -65,7 +62,6 @@ impl<'gc> VTable<'gc> {
|
|||
VTable(GcCell::new(
|
||||
mc,
|
||||
VTableData {
|
||||
defining_class: None,
|
||||
scope: None,
|
||||
protected_namespace: None,
|
||||
resolved_traits: PropertyMap::new(),
|
||||
|
@ -87,7 +83,6 @@ impl<'gc> VTable<'gc> {
|
|||
let vt = VTable(GcCell::new(
|
||||
mc,
|
||||
VTableData {
|
||||
defining_class: None,
|
||||
scope: None,
|
||||
protected_namespace: None,
|
||||
resolved_traits: rt,
|
||||
|
@ -106,10 +101,6 @@ impl<'gc> VTable<'gc> {
|
|||
vt
|
||||
}
|
||||
|
||||
pub fn duplicate(self, mc: &Mutation<'gc>) -> Self {
|
||||
VTable(GcCell::new(mc, self.0.read().clone()))
|
||||
}
|
||||
|
||||
pub fn resolved_traits(&self) -> Ref<'_, PropertyMap<'gc, Property>> {
|
||||
Ref::map(self.0.read(), |v| &v.resolved_traits)
|
||||
}
|
||||
|
@ -220,12 +211,13 @@ impl<'gc> VTable<'gc> {
|
|||
#[allow(clippy::if_same_then_else)]
|
||||
pub fn init_vtable(
|
||||
self,
|
||||
defining_class: ClassObject<'gc>,
|
||||
defining_class: Option<ClassObject<'gc>>,
|
||||
protected_namespace: Option<Namespace<'gc>>,
|
||||
traits: &[Trait<'gc>],
|
||||
scope: ScopeChain<'gc>,
|
||||
scope: Option<ScopeChain<'gc>>,
|
||||
superclass_vtable: Option<Self>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
context: &mut UpdateContext<'_, 'gc>,
|
||||
) {
|
||||
// Let's talk about slot_ids and disp_ids.
|
||||
// Specification is one thing, but reality is another.
|
||||
|
||||
|
@ -277,15 +269,12 @@ impl<'gc> VTable<'gc> {
|
|||
// so long-term it's still something we should verify.
|
||||
// (and it's far from the only verification check we lack anyway)
|
||||
|
||||
let mut write = self.0.write(activation.context.gc_context);
|
||||
let mut write = self.0.write(context.gc_context);
|
||||
let write = write.deref_mut();
|
||||
|
||||
write.defining_class = Some(defining_class);
|
||||
write.scope = Some(scope);
|
||||
write.scope = scope;
|
||||
|
||||
write.protected_namespace = defining_class
|
||||
.inner_class_definition()
|
||||
.protected_namespace();
|
||||
write.protected_namespace = protected_namespace;
|
||||
|
||||
if let Some(superclass_vtable) = superclass_vtable {
|
||||
write.resolved_traits = superclass_vtable.0.read().resolved_traits.clone();
|
||||
|
@ -434,11 +423,10 @@ impl<'gc> VTable<'gc> {
|
|||
}
|
||||
TraitKind::Slot { slot_id, .. }
|
||||
| TraitKind::Const { slot_id, .. }
|
||||
| TraitKind::Function { slot_id, .. }
|
||||
| TraitKind::Class { slot_id, .. } => {
|
||||
let slot_id = *slot_id;
|
||||
|
||||
let value = trait_to_default_value(scope, trait_data, activation);
|
||||
let value = trait_to_default_value(trait_data);
|
||||
let value = Some(value);
|
||||
|
||||
let new_slot_id = if slot_id == 0 {
|
||||
|
@ -467,36 +455,18 @@ impl<'gc> VTable<'gc> {
|
|||
type_name, unit, ..
|
||||
} => (
|
||||
Property::new_slot(new_slot_id),
|
||||
PropertyClass::name(
|
||||
activation.context.gc_context,
|
||||
type_name.clone(),
|
||||
*unit,
|
||||
),
|
||||
),
|
||||
TraitKind::Function { .. } => (
|
||||
Property::new_slot(new_slot_id),
|
||||
PropertyClass::Class(
|
||||
activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.function
|
||||
.inner_class_definition(),
|
||||
),
|
||||
PropertyClass::name(context.gc_context, type_name.clone(), *unit),
|
||||
),
|
||||
TraitKind::Const {
|
||||
type_name, unit, ..
|
||||
} => (
|
||||
Property::new_const_slot(new_slot_id),
|
||||
PropertyClass::name(
|
||||
activation.context.gc_context,
|
||||
type_name.clone(),
|
||||
*unit,
|
||||
),
|
||||
PropertyClass::name(context.gc_context, type_name.clone(), *unit),
|
||||
),
|
||||
TraitKind::Class { .. } => (
|
||||
Property::new_const_slot(new_slot_id),
|
||||
PropertyClass::Class(
|
||||
activation.avm2().classes().class.inner_class_definition(),
|
||||
context.avm2.classes().class.inner_class_definition(),
|
||||
),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
|
@ -510,10 +480,9 @@ impl<'gc> VTable<'gc> {
|
|||
|
||||
slot_classes[new_slot_id as usize] = new_class;
|
||||
}
|
||||
TraitKind::Function { .. } => panic!("TraitKind::Function shouldn't appear"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve a bound instance method suitable for use as a value.
|
||||
|
@ -548,7 +517,13 @@ impl<'gc> VTable<'gc> {
|
|||
method,
|
||||
} = method;
|
||||
|
||||
FunctionObject::from_method(activation, method, scope, Some(receiver), Some(class))
|
||||
FunctionObject::from_method(
|
||||
activation,
|
||||
method,
|
||||
scope.expect("Scope should exist here"),
|
||||
Some(receiver),
|
||||
class,
|
||||
)
|
||||
}
|
||||
|
||||
/// Install a const trait on the global object.
|
||||
|
@ -606,20 +581,11 @@ impl<'gc> VTable<'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
fn trait_to_default_value<'gc>(
|
||||
scope: ScopeChain<'gc>,
|
||||
trait_data: &Trait<'gc>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
) -> Value<'gc> {
|
||||
fn trait_to_default_value<'gc>(trait_data: &Trait<'gc>) -> Value<'gc> {
|
||||
match trait_data.kind() {
|
||||
TraitKind::Slot { default_value, .. } => *default_value,
|
||||
TraitKind::Const { default_value, .. } => *default_value,
|
||||
TraitKind::Function { function, .. } => {
|
||||
FunctionObject::from_function(activation, *function, scope)
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
TraitKind::Class { .. } => Value::Undefined,
|
||||
TraitKind::Class { .. } => Value::Null,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue