avm2: Remove all classless objects

This commit is contained in:
Lord-McSweeney 2024-06-21 10:03:59 -07:00 committed by Lord-McSweeney
parent 953a02533f
commit 6cc1488f73
9 changed files with 70 additions and 48 deletions

View File

@ -81,8 +81,8 @@ pub use crate::avm2::globals::flash::ui::context_menu::make_context_menu_state;
pub use crate::avm2::multiname::Multiname;
pub use crate::avm2::namespace::Namespace;
pub use crate::avm2::object::{
ArrayObject, BitmapDataObject, ClassObject, EventObject, Object, ScriptObject,
SoundChannelObject, StageObject, TObject,
ArrayObject, BitmapDataObject, ClassObject, EventObject, Object, SoundChannelObject,
StageObject, TObject,
};
pub use crate::avm2::qname::QName;
pub use crate::avm2::value::Value;

View File

@ -1589,9 +1589,9 @@ impl<'a, 'gc> Activation<'a, 'gc> {
let vname = ex.variable_name;
let so = if let Some(vname) = vname {
ScriptObject::catch_scope(self.context.gc_context, &vname)
ScriptObject::catch_scope(self, &vname)
} else {
// for `finally` scopes, FP just creates a bare object.
// for `finally` scopes, FP just creates a normal object.
self.avm2().classes().object.construct(self, &[])?
};

View File

@ -472,12 +472,7 @@ pub fn load_player_globals<'gc>(
) -> Result<(), Error<'gc>> {
let mc = activation.context.gc_context;
let globals = ScriptObject::custom_object(mc, None, None);
let gs = ScopeChain::new(domain).chain(mc, &[Scope::new(globals)]);
let script = Script::empty_script(mc, globals, domain);
// Set the outer scope of this activation to the global scope.
activation.set_outer(gs);
// public / root package
//
@ -510,22 +505,31 @@ pub fn load_player_globals<'gc>(
class_i_class.set_c_class(mc, class_c_class);
class_c_class.set_i_class(mc, class_i_class);
domain.export_class(object_i_class.name(), object_i_class, mc);
domain.export_class(class_i_class.name(), class_i_class, mc);
// Function is more of a "normal" class than the other two, so we can create it normally.
let fn_classdef = function::create_class(activation, object_i_class, class_i_class);
// Register the classes in the domain, now
domain.export_class(object_i_class.name(), object_i_class, mc);
domain.export_class(class_i_class.name(), class_i_class, mc);
domain.export_class(fn_classdef.name(), fn_classdef, mc);
// Initialize the script
let globals = ScriptObject::custom_object(mc, object_i_class, None, None);
let script = Script::empty_script(mc, globals, domain);
let gs = ScopeChain::new(domain).chain(mc, &[Scope::new(globals)]);
activation.set_outer(gs);
let object_class = ClassObject::from_class_partial(activation, object_i_class, None)?;
let object_proto = ScriptObject::custom_object(mc, Some(object_class), None);
let object_proto = ScriptObject::custom_object(mc, object_i_class, Some(object_class), None);
let class_class =
ClassObject::from_class_partial(activation, class_i_class, Some(object_class))?;
let class_proto = ScriptObject::custom_object(mc, Some(object_class), Some(object_proto));
let class_proto =
ScriptObject::custom_object(mc, object_i_class, Some(object_class), Some(object_proto));
let fn_class = ClassObject::from_class_partial(activation, fn_classdef, Some(object_class))?;
let fn_proto = ScriptObject::custom_object(mc, Some(fn_class), Some(object_proto));
let fn_proto = ScriptObject::custom_object(mc, fn_classdef, Some(fn_class), Some(object_proto));
// Now to weave the Gordian knot...
object_class.link_prototype(activation, object_proto)?;

View File

@ -19,7 +19,7 @@ fn dispatch_list<'gc>(
)? {
Value::Object(o) => Ok(o),
_ => {
let dispatch_list = DispatchObject::empty_list(activation.context.gc_context);
let dispatch_list = DispatchObject::empty_list(activation);
this.init_property(
&Multiname::new(activation.avm2().flash_events_internal, "_dispatchList"),
dispatch_list.into(),

View File

@ -163,7 +163,7 @@ impl<'gc> ClassObject<'gc> {
let class_object = ClassObject(GcCell::new(
activation.context.gc_context,
ClassObjectData {
base: ScriptObjectData::custom_new(None, None),
base: ScriptObjectData::custom_new(c_class, None, None),
class,
prototype: None,
class_scope: scope,
@ -186,8 +186,6 @@ impl<'gc> ClassObject<'gc> {
.instance_scope = instance_scope;
class_object.init_instance_vtable(activation)?;
class_object.set_instance_class(activation.context.gc_context, c_class);
class.add_class_object(activation.context.gc_context, class_object);
Ok(class_object)

View File

@ -63,12 +63,11 @@ pub struct DispatchObjectData<'gc> {
impl<'gc> DispatchObject<'gc> {
/// Construct an empty dispatch list.
pub fn empty_list(mc: &Mutation<'gc>) -> Object<'gc> {
// TODO: we might want this to be a proper Object instance, just in case
let base = ScriptObjectData::custom_new(None, None);
pub fn empty_list(activation: &mut Activation<'_, 'gc>) -> Object<'gc> {
let base = ScriptObjectData::new(activation.avm2().classes().object);
DispatchObject(GcCell::new(
mc,
activation.context.gc_context,
DispatchObjectData {
base,
dispatch: DispatchList::new(),

View File

@ -95,13 +95,11 @@ impl<'gc> FunctionObject<'gc> {
scope: ScopeChain<'gc>,
) -> Result<FunctionObject<'gc>, Error<'gc>> {
let this = Self::from_method(activation, method, scope, None, None);
let es3_proto = ScriptObject::custom_object(
activation.context.gc_context,
// TODO: is this really a class-less object?
// (also: how much of "ES3 class-less object" is even true?)
None,
Some(activation.avm2().classes().object.prototype()),
);
let es3_proto = activation
.avm2()
.classes()
.object
.construct(activation, &[])?;
this.0.write(activation.context.gc_context).prototype = Some(es3_proto);
@ -204,6 +202,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
let instance = ScriptObject::custom_object(
activation.context.gc_context,
activation.avm2().classes().object.inner_class_definition(),
Some(activation.avm2().classes().object),
Some(prototype),
);

View File

@ -105,23 +105,33 @@ impl<'gc> ScriptObject<'gc> {
/// is technically also equivalent and faster, but not recommended outside lower-level Core code)
pub fn custom_object(
mc: &Mutation<'gc>,
class: Option<ClassObject<'gc>>,
class: Class<'gc>,
class_obj: Option<ClassObject<'gc>>,
proto: Option<Object<'gc>>,
) -> Object<'gc> {
ScriptObject(GcCell::new(mc, ScriptObjectData::custom_new(proto, class))).into()
ScriptObject(GcCell::new(
mc,
ScriptObjectData::custom_new(class, proto, class_obj),
))
.into()
}
/// A special case for `newcatch` implementation. Basically a variable (q)name
/// which maps to slot 1.
pub fn catch_scope(mc: &Mutation<'gc>, qname: &QName<'gc>) -> Object<'gc> {
pub fn catch_scope(activation: &mut Activation<'_, 'gc>, qname: &QName<'gc>) -> Object<'gc> {
// TODO: use a proper ClassObject here; purposefully crafted bytecode
// can observe (the lack of) it.
let mut base = ScriptObjectData::custom_new(None, None);
let vt = VTable::newcatch(mc, qname);
let mut base = ScriptObjectData::custom_new(
activation.avm2().classes().object.inner_class_definition(),
None,
None,
);
let vt = VTable::newcatch(activation.context.gc_context, qname);
base.set_vtable(vt);
base.install_instance_slots();
ScriptObject(GcCell::new(mc, base)).into()
ScriptObject(GcCell::new(activation.context.gc_context, base)).into()
}
}
@ -129,7 +139,11 @@ impl<'gc> ScriptObjectData<'gc> {
/// Create new object data of a given class.
/// This is a low-level function used to implement things like object allocators.
pub fn new(instance_of: ClassObject<'gc>) -> Self {
Self::custom_new(Some(instance_of.prototype()), Some(instance_of))
Self::custom_new(
instance_of.inner_class_definition(),
Some(instance_of.prototype()),
Some(instance_of),
)
}
/// Create new custom object data of a given possibly-none class and prototype.
@ -137,13 +151,17 @@ impl<'gc> ScriptObjectData<'gc> {
/// This should *not* be used, unless you really need
/// to do something weird or lazily initialize the object.
/// You shouldn't let scripts observe this weirdness.
pub fn custom_new(proto: Option<Object<'gc>>, instance_of: Option<ClassObject<'gc>>) -> Self {
pub fn custom_new(
class: Class<'gc>,
proto: Option<Object<'gc>>,
instance_of: Option<ClassObject<'gc>>,
) -> Self {
ScriptObjectData {
values: Default::default(),
slots: Vec::new(),
bound_methods: Vec::new(),
proto,
instance_class: instance_of.map(|cls| cls.inner_class_definition()),
instance_class: Some(class),
vtable: instance_of.map(|cls| cls.instance_vtable()),
}
}

View File

@ -4,7 +4,7 @@ use crate::avm1::Object as Avm1Object;
use crate::avm2::object::TObject;
use crate::avm2::{
Activation as Avm2Activation, Avm2, EventObject as Avm2EventObject, Object as Avm2Object,
ScriptObject as Avm2ScriptObject, StageObject as Avm2StageObject, Value as Avm2Value,
StageObject as Avm2StageObject, Value as Avm2Value,
};
use crate::backend::ui::MouseCursor;
use crate::config::Letterbox;
@ -130,10 +130,10 @@ pub struct StageData<'gc> {
show_menu: bool,
/// The AVM2 view of this stage object.
avm2_object: Avm2Object<'gc>,
avm2_object: Option<Avm2Object<'gc>>,
/// The AVM2 'LoaderInfo' object for this stage object
loader_info: Avm2Object<'gc>,
loader_info: Option<Avm2Object<'gc>>,
/// An array of AVM2 'Stage3D' instances
stage3ds: Vec<Avm2Object<'gc>>,
@ -183,8 +183,8 @@ impl<'gc> Stage<'gc> {
window_mode: Default::default(),
show_menu: true,
stage_focus_rect: true,
avm2_object: Avm2ScriptObject::custom_object(gc_context, None, None),
loader_info: Avm2ScriptObject::custom_object(gc_context, None, None),
avm2_object: None,
loader_info: None,
stage3ds: vec![],
movie,
viewport_matrix: Matrix::IDENTITY,
@ -242,7 +242,7 @@ impl<'gc> Stage<'gc> {
}
pub fn set_loader_info(self, gc_context: &Mutation<'gc>, loader_info: Avm2Object<'gc>) {
self.0.write(gc_context).loader_info = loader_info;
self.0.write(gc_context).loader_info = Some(loader_info);
}
// Get the invalidation state
@ -787,7 +787,7 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
match avm2_stage {
Ok(avm2_stage) => {
let mut write = self.0.write(activation.context.gc_context);
write.avm2_object = avm2_stage.into();
write.avm2_object = Some(avm2_stage.into());
write.stage3ds = vec![stage3d];
}
Err(e) => tracing::error!("Unable to construct AVM2 Stage: {}", e),
@ -864,11 +864,15 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
}
fn object2(&self) -> Avm2Value<'gc> {
self.0.read().avm2_object.into()
self.0
.read()
.avm2_object
.expect("Attempted to access Stage::object2 before initialization")
.into()
}
fn loader_info(&self) -> Option<Avm2Object<'gc>> {
Some(self.0.read().loader_info)
self.0.read().loader_info
}
fn movie(&self) -> Arc<SwfMovie> {