diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 5c7a7688e..90f3cdfda 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,6 +1,7 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::{Activation, Avm2ScriptEntry}; +use crate::avm2::globals::SystemPrototypes; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::return_value::ReturnValue; @@ -54,15 +55,21 @@ pub struct Avm2<'gc> { /// Global scope object. globals: Object<'gc>, + + /// System prototypes. + system_prototypes: SystemPrototypes<'gc>, } impl<'gc> Avm2<'gc> { /// Construct a new AVM interpreter. pub fn new(mc: MutationContext<'gc, '_>) -> Self { + let (globals, system_prototypes) = globals::construct_global_scope(mc); + Self { stack_frames: Vec::new(), stack: Vec::new(), - globals: globals::construct_global_scope(mc), + globals, + system_prototypes, } } @@ -85,13 +92,12 @@ impl<'gc> Avm2<'gc> { let scope = Scope::push_scope(None, self.globals(), context.gc_context); for trait_entry in entrypoint.script().traits.iter() { - //TODO: Actually stick the Function proto here self.globals.install_trait( context.gc_context, entrypoint.abc(), trait_entry, Some(scope), - self.globals, + self.system_prototypes.function, )?; } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 3929a0a24..bf8078248 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -1,7 +1,7 @@ //! AVM2 executables. use crate::avm2::activation::Activation; -use crate::avm2::names::QName; +use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; @@ -269,6 +269,48 @@ impl<'gc> FunctionObject<'gc> { )) .into() } + + /// Construct a builtin function object from a Rust function. + pub fn from_builtin( + mc: MutationContext<'gc, '_>, + nf: NativeFunction<'gc>, + fn_proto: Object<'gc>, + ) -> Object<'gc> { + FunctionObject(GcCell::allocate( + mc, + FunctionObjectData { + base: ScriptObjectData::base_new(Some(fn_proto)), + exec: nf.into(), + class: None, + }, + )) + .into() + } + + /// Construct a builtin type from a Rust constructor and prototype. + pub fn from_builtin_constr( + mc: MutationContext<'gc, '_>, + constr: NativeFunction<'gc>, + prototype: Object<'gc>, + fn_proto: Object<'gc>, + ) -> Result, Error> { + let mut base = ScriptObjectData::base_new(Some(fn_proto)); + + base.install_dynamic_property( + QName::new(Namespace::public_namespace(), "prototype"), + prototype.into(), + )?; + + Ok(FunctionObject(GcCell::allocate( + mc, + FunctionObjectData { + base, + exec: constr.into(), + class: None, + }, + )) + .into()) + } } impl<'gc> TObject<'gc> for FunctionObject<'gc> { @@ -332,4 +374,17 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { .base .install_trait(mc, abc, trait_entry, scope, fn_proto) } + + fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) { + self.0.write(mc).base.install_method(name, function) + } + + fn install_dynamic_property( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + value: Value<'gc>, + ) -> Result<(), Error> { + self.0.write(mc).base.install_dynamic_property(name, value) + } } diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 24119d5c8..52e99f8a0 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -1,11 +1,69 @@ //! Global scope built-ins +use crate::avm2::function::FunctionObject; +use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::script_object::ScriptObject; -use gc_arena::MutationContext; +use gc_arena::{Collect, MutationContext}; -pub fn construct_global_scope<'gc>(mc: MutationContext<'gc, '_>) -> Object<'gc> { - let global_scope = ScriptObject::bare_object(mc); +mod function; +mod object; + +/// This structure represents all system builtins' prototypes. +#[derive(Clone, Collect)] +#[collect(no_drop)] +pub struct SystemPrototypes<'gc> { + pub object: Object<'gc>, + pub function: Object<'gc>, +} + +/// Construct a new global scope. +/// +/// This function returns both the global scope object, as well as all builtin +/// prototypes that other parts of the VM will need to use. +pub fn construct_global_scope<'gc>( + mc: MutationContext<'gc, '_>, +) -> (Object<'gc>, SystemPrototypes<'gc>) { + let mut global_scope = ScriptObject::bare_object(mc); + + let object_proto = ScriptObject::bare_object(mc); + let function_proto = function::create_proto(mc, object_proto); + + object::fill_proto(mc, object_proto, function_proto); + + let system_prototypes = SystemPrototypes { + object: object_proto, + function: function_proto, + }; global_scope + .install_dynamic_property( + mc, + QName::new(Namespace::public_namespace(), "Object"), + FunctionObject::from_builtin_constr( + mc, + object::constructor, + object_proto, + function_proto, + ) + .unwrap() + .into(), + ) + .unwrap(); + global_scope + .install_dynamic_property( + mc, + QName::new(Namespace::public_namespace(), "Function"), + FunctionObject::from_builtin_constr( + mc, + function::constructor, + function_proto, + function_proto, + ) + .unwrap() + .into(), + ) + .unwrap(); + + (global_scope, system_prototypes) } diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs new file mode 100644 index 000000000..9eb58c8a2 --- /dev/null +++ b/core/src/avm2/globals/function.rs @@ -0,0 +1,50 @@ +//! Function builtin and prototype + +use crate::avm2::function::FunctionObject; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::{Object, TObject}; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::script_object::ScriptObject; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::MutationContext; + +/// Implements `Function` +pub fn constructor<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined.into()) +} + +/// Implements `Function.prototype.toString` +fn to_string<'gc>( + _: &mut Avm2<'gc>, + _: &mut UpdateContext<'_, 'gc, '_>, + _: Object<'gc>, + _: &[Value<'gc>], +) -> Result, Error> { + Ok(ReturnValue::Immediate("[type Function]".into())) +} + +/// Partially construct `Function.prototype`. +/// +/// `__proto__` and other cross-linked properties of this object will *not* +/// be defined here. The caller of this function is responsible for linking +/// them in order to obtain a valid ECMAScript `Function` prototype. The +/// returned object is also a bare object, which will need to be linked into +/// the prototype of `Object`. +pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> { + let mut function_proto = ScriptObject::object(gc_context, proto); + + function_proto.install_method( + gc_context, + QName::new(Namespace::public_namespace(), "toString"), + FunctionObject::from_builtin(gc_context, to_string, function_proto), + ); + + function_proto +} diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs new file mode 100644 index 000000000..9982feaa5 --- /dev/null +++ b/core/src/avm2/globals/object.rs @@ -0,0 +1,34 @@ +//! Object builtin and prototype + +use crate::avm2::object::Object; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::MutationContext; + +/// Implements `Object` +pub fn constructor<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined.into()) +} + +/// Partially construct `Object.prototype`. +/// +/// `__proto__` and other cross-linked properties of this object will *not* +/// be defined here. The caller of this function is responsible for linking +/// them in order to obtain a valid ECMAScript `Object` prototype. +/// +/// Since Object and Function are so heavily intertwined, this function does +/// not allocate an object to store either proto. Instead, you must allocate +/// bare objects for both and let this function fill Object for you. +pub fn fill_proto<'gc>( + _gc_context: MutationContext<'gc, '_>, + _object_proto: Object<'gc>, + _fn_proto: Object<'gc>, +) { +} diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index ec145f3bf..29b02f047 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -71,6 +71,13 @@ pub struct QName { } impl QName { + pub fn new(ns: Namespace, name: &str) -> Self { + Self { + ns, + name: name.to_string(), + } + } + pub fn qualified(ns: &Namespace, name: &str) -> Self { Self { ns: ns.clone(), diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 08355f86b..44512e662 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -9,7 +9,7 @@ use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use enumset::{EnumSet, EnumSetType}; +use enumset::EnumSet; use gc_arena::{Collect, GcCell, MutationContext}; use ruffle_macros::enum_trait_object; use std::fmt::Debug; @@ -116,6 +116,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn_proto: Object<'gc>, ) -> Result<(), Error>; + /// Install a method (not necessarily from an ABC file) on an object. + fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>); + + /// Install a dynamic or built-in value property on an object. + fn install_dynamic_property( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + value: Value<'gc>, + ) -> Result<(), Error>; + /// Call the object. fn call( self, diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 2d3fe11c8..dc895e606 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -78,6 +78,19 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { .write(mc) .install_trait(mc, abc, trait_entry, scope, fn_proto) } + + fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) { + self.0.write(mc).install_method(name, function) + } + + fn install_dynamic_property( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + value: Value<'gc>, + ) -> Result<(), Error> { + self.0.write(mc).install_dynamic_property(name, value) + } } impl<'gc> ScriptObject<'gc> { @@ -89,6 +102,15 @@ impl<'gc> ScriptObject<'gc> { ScriptObject(GcCell::allocate(mc, ScriptObjectData::base_new(None))).into() } + /// Construct an object with a base class. + pub fn object(mc: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> { + ScriptObject(GcCell::allocate( + mc, + ScriptObjectData::base_new(Some(proto)), + )) + .into() + } + /// Construct the instance prototype half of a class. pub fn instance_prototype( mc: MutationContext<'gc, '_>, @@ -190,7 +212,7 @@ impl<'gc> ScriptObjectData<'gc> { } /// Install a method into the object. - fn install_method(&mut self, name: QName, function: Object<'gc>) { + pub fn install_method(&mut self, name: QName, function: Object<'gc>) { self.values.insert(name, Property::new_method(function)); } @@ -225,4 +247,15 @@ impl<'gc> ScriptObjectData<'gc> { .unwrap() .install_virtual_setter(function) } + + pub fn install_dynamic_property( + &mut self, + name: QName, + value: Value<'gc>, + ) -> Result<(), Error> { + self.values + .insert(name, Property::new_dynamic_property(value)); + + Ok(()) + } }