avm2: Wrap all player globals in a separate appdomain and script.

This entirely abolishes the "global scope object" in AVM2. I even had to redefine several global object functions to work with the bottom of the scope stack, which seems to be where ASC likes to stick the script scope.
This commit is contained in:
David Wendt 2020-09-23 23:35:11 -04:00 committed by Mike Welsh
parent add7c4d43d
commit 6a736b0d2b
14 changed files with 232 additions and 124 deletions

View File

@ -2,7 +2,6 @@
use crate::avm2::domain::Domain; use crate::avm2::domain::Domain;
use crate::avm2::globals::SystemPrototypes; use crate::avm2::globals::SystemPrototypes;
use crate::avm2::object::ScriptObject;
use crate::avm2::scope::Scope; use crate::avm2::scope::Scope;
use crate::avm2::script::{Script, TranslationUnit}; use crate::avm2::script::{Script, TranslationUnit};
use crate::context::UpdateContext; use crate::context::UpdateContext;
@ -58,7 +57,7 @@ pub struct Avm2<'gc> {
stack: Vec<Value<'gc>>, stack: Vec<Value<'gc>>,
/// Global scope object. /// Global scope object.
globals: Object<'gc>, globals: GcCell<'gc, Domain<'gc>>,
/// System prototypes. /// System prototypes.
system_prototypes: Option<SystemPrototypes<'gc>>, system_prototypes: Option<SystemPrototypes<'gc>>,
@ -70,7 +69,7 @@ pub struct Avm2<'gc> {
impl<'gc> Avm2<'gc> { impl<'gc> Avm2<'gc> {
/// Construct a new AVM interpreter. /// Construct a new AVM interpreter.
pub fn new(mc: MutationContext<'gc, '_>) -> Self { pub fn new(mc: MutationContext<'gc, '_>) -> Self {
let globals = ScriptObject::bare_object(mc); let globals = Domain::global_domain(mc);
Self { Self {
stack: Vec::new(), stack: Vec::new(),
@ -83,8 +82,9 @@ impl<'gc> Avm2<'gc> {
} }
pub fn load_player_globals(context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { pub fn load_player_globals(context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let globals = context.avm2.globals;
let mut activation = Activation::from_nothing(context.reborrow()); let mut activation = Activation::from_nothing(context.reborrow());
globals::load_player_globals(&mut activation) globals::load_player_globals(&mut activation, globals)
} }
/// Return the current set of system prototypes. /// Return the current set of system prototypes.
@ -99,8 +99,7 @@ impl<'gc> Avm2<'gc> {
script: GcCell<'gc, Script<'gc>>, script: GcCell<'gc, Script<'gc>>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let globals = context.avm2.globals; let mut init_activation = Activation::from_script(context.reborrow(), script)?;
let mut init_activation = Activation::from_script(context.reborrow(), script, globals)?;
init_activation.run_stack_frame_for_script(script) init_activation.run_stack_frame_for_script(script)
} }
@ -162,7 +161,7 @@ impl<'gc> Avm2<'gc> {
Ok(()) Ok(())
} }
pub fn globals(&self) -> Object<'gc> { pub fn global_domain(&self) -> GcCell<'gc, Domain<'gc>> {
self.globals self.globals
} }

View File

@ -134,10 +134,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
pub fn from_script( pub fn from_script(
context: UpdateContext<'a, 'gc, 'gc_context>, context: UpdateContext<'a, 'gc, 'gc_context>,
script: GcCell<'gc, Script<'gc>>, script: GcCell<'gc, Script<'gc>>,
global: Object<'gc>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let script_scope = script.read().globals();
let method = script.read().init().into_bytecode()?; let method = script.read().init().into_bytecode()?;
let scope = Some(Scope::push_scope(None, global, context.gc_context)); let scope = Some(Scope::push_scope(None, script_scope, context.gc_context));
let body: Result<_, Error> = method let body: Result<_, Error> = method
.body() .body()
.ok_or_else(|| "Cannot execute non-native method (for script) without body".into()); .ok_or_else(|| "Cannot execute non-native method (for script) without body".into());
@ -148,10 +148,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
*local_registers *local_registers
.write(context.gc_context) .write(context.gc_context)
.get_mut(0) .get_mut(0)
.unwrap() = global.into(); .unwrap() = script_scope.into();
Ok(Self { Ok(Self {
this: Some(global), this: Some(script_scope),
arguments: None, arguments: None,
is_executing: false, is_executing: false,
local_registers, local_registers,
@ -1224,7 +1224,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} }
fn op_get_global_slot(&mut self, index: u32) -> Result<FrameControl<'gc>, Error> { fn op_get_global_slot(&mut self, index: u32) -> Result<FrameControl<'gc>, Error> {
let value = self.context.avm2.globals().get_slot(index)?; let value = self.scope.unwrap().read().globals().get_slot(index)?;
self.context.avm2.push(value); self.context.avm2.push(value);
@ -1234,8 +1234,9 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn op_set_global_slot(&mut self, index: u32) -> Result<FrameControl<'gc>, Error> { fn op_set_global_slot(&mut self, index: u32) -> Result<FrameControl<'gc>, Error> {
let value = self.context.avm2.pop(); let value = self.context.avm2.pop();
self.context self.scope
.avm2 .unwrap()
.read()
.globals() .globals()
.set_slot(index, value, self.context.gc_context)?; .set_slot(index, value, self.context.gc_context)?;

View File

@ -1,7 +1,10 @@
//! Application Domains //! Application Domains
use crate::avm2::activation::Activation;
use crate::avm2::names::QName; use crate::avm2::names::QName;
use crate::avm2::object::TObject;
use crate::avm2::script::Script; use crate::avm2::script::Script;
use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use gc_arena::{Collect, GcCell, MutationContext}; use gc_arena::{Collect, GcCell, MutationContext};
use std::collections::HashMap; use std::collections::HashMap;
@ -73,6 +76,20 @@ impl<'gc> Domain<'gc> {
None None
} }
/// Retrieve a value from this domain.
pub fn get_defined_value(
&self,
activation: &mut Activation<'_, 'gc, '_>,
name: QName<'gc>,
) -> Result<Value<'gc>, Error> {
let script = self
.get_defining_script(name.clone())
.ok_or_else(|| format!("MovieClip Symbol {} does not exist", name.local_name()))?;
let mut globals = script.read().globals();
globals.get_property(globals, &name, activation)
}
/// Export a definition from a script into the current application domain. /// Export a definition from a script into the current application domain.
/// ///
/// This returns an error if the name is already defined in the current or /// This returns an error if the name is already defined in the current or

View File

@ -2,6 +2,7 @@
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::class::Class; use crate::avm2::class::Class;
use crate::avm2::domain::Domain;
use crate::avm2::method::NativeMethod; use crate::avm2::method::NativeMethod;
use crate::avm2::names::{Namespace, QName}; use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{ use crate::avm2::object::{
@ -9,6 +10,7 @@ use crate::avm2::object::{
ScriptObject, StageObject, TObject, ScriptObject, StageObject, TObject,
}; };
use crate::avm2::scope::Scope; use crate::avm2::scope::Scope;
use crate::avm2::script::Script;
use crate::avm2::string::AvmString; use crate::avm2::string::AvmString;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
@ -97,19 +99,23 @@ impl<'gc> SystemPrototypes<'gc> {
/// Add a free-function builtin to the global scope. /// Add a free-function builtin to the global scope.
fn function<'gc>( fn function<'gc>(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
mut global_scope: Object<'gc>,
package: impl Into<AvmString<'gc>>, package: impl Into<AvmString<'gc>>,
name: impl Into<AvmString<'gc>>, name: impl Into<AvmString<'gc>>,
nf: NativeMethod<'gc>, nf: NativeMethod<'gc>,
fn_proto: Object<'gc>, fn_proto: Object<'gc>,
) { domain: GcCell<'gc, Domain<'gc>>,
global_scope script: GcCell<'gc, Script<'gc>>,
.install_dynamic_property( ) -> Result<(), Error> {
mc, let name = QName::new(Namespace::package(package), name);
QName::new(Namespace::package(package), name), let as3fn = FunctionObject::from_builtin(mc, nf, fn_proto).into();
FunctionObject::from_builtin(mc, nf, fn_proto).into(), domain.write(mc).export_definition(name.clone(), script)?;
) script
.unwrap() .read()
.globals()
.install_dynamic_property(mc, name, as3fn)
.unwrap();
Ok(())
} }
/// Add a class builtin with prototype methods to the global scope. /// Add a class builtin with prototype methods to the global scope.
@ -119,17 +125,18 @@ fn function<'gc>(
/// `install_foreign_trait` with such a class. /// `install_foreign_trait` with such a class.
fn dynamic_class<'gc>( fn dynamic_class<'gc>(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
mut global_scope: Object<'gc>,
constr: Object<'gc>, constr: Object<'gc>,
) { class: GcCell<'gc, Class<'gc>>,
let name = constr domain: GcCell<'gc, Domain<'gc>>,
.as_class() script: GcCell<'gc, Script<'gc>>,
.expect("constrs have classes in them") ) -> Result<(), Error> {
.read() let name = class.read().name().clone();
.name()
.clone();
global_scope.install_const(mc, name, 0, constr.into()); script
.read()
.globals()
.install_const(mc, name.clone(), 0, constr.into());
domain.write(mc).export_definition(name, script)
} }
/// Add a class builtin to the global scope. /// Add a class builtin to the global scope.
@ -140,9 +147,10 @@ fn dynamic_class<'gc>(
/// impls. /// impls.
fn class<'gc, Deriver>( fn class<'gc, Deriver>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
mut global: Object<'gc>,
class_def: GcCell<'gc, Class<'gc>>, class_def: GcCell<'gc, Class<'gc>>,
custom_derive: Deriver, custom_derive: Deriver,
domain: GcCell<'gc, Domain<'gc>>,
script: GcCell<'gc, Script<'gc>>,
) -> Result<Object<'gc>, Error> ) -> Result<Object<'gc>, Error>
where where
Deriver: FnOnce( Deriver: FnOnce(
@ -152,10 +160,8 @@ where
Option<GcCell<'gc, Scope<'gc>>>, Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error>, ) -> Result<Object<'gc>, Error>,
{ {
let mut global = script.read().globals();
let global_scope = Scope::push_scope(global.get_scope(), global, activation.context.gc_context); let global_scope = Scope::push_scope(global.get_scope(), global, activation.context.gc_context);
/*let mut constr = global
.install_foreign_trait(activation, class_trait, Some(global_scope), global)?
.coerce_to_object(activation)?;*/
let class_read = class_def.read(); let class_read = class_def.read();
let super_class = if let Some(sc_name) = class_read.super_class_name() { let super_class = if let Some(sc_name) = class_read.super_class_name() {
@ -188,6 +194,9 @@ where
0, 0,
constr.into(), constr.into(),
); );
domain
.write(activation.context.gc_context)
.export_definition(class_read.name().clone(), script)?;
constr constr
.get_property( .get_property(
@ -237,32 +246,45 @@ fn stage_deriver<'gc>(
/// Add a builtin constant to the global scope. /// Add a builtin constant to the global scope.
fn constant<'gc>( fn constant<'gc>(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
mut global_scope: Object<'gc>,
package: impl Into<AvmString<'gc>>, package: impl Into<AvmString<'gc>>,
name: impl Into<AvmString<'gc>>, name: impl Into<AvmString<'gc>>,
value: Value<'gc>, value: Value<'gc>,
) { domain: GcCell<'gc, Domain<'gc>>,
global_scope.install_const(mc, QName::new(Namespace::package(package), name), 0, value) script: GcCell<'gc, Script<'gc>>,
) -> Result<(), Error> {
let name = QName::new(Namespace::package(package), name);
domain.write(mc).export_definition(name.clone(), script)?;
script.read().globals().install_const(mc, name, 0, value);
Ok(())
} }
/// Initialize all remaining builtin classes. /// Initialize the player global domain.
/// ///
/// This should be called only once, to construct the global scope of the /// This should be called only once, to construct the global scope of the
/// player. It will return a list of prototypes it has created, which should be /// player. It will return a list of prototypes it has created, which should be
/// stored on the AVM. /// stored on the AVM. All relevant declarations will also be attached to the
pub fn load_player_globals<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Result<(), Error> { /// given domain.
let gs = activation.avm2().globals(); pub fn load_player_globals<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
domain: GcCell<'gc, Domain<'gc>>,
) -> Result<(), Error> {
let mc = activation.context.gc_context;
let gs = ScriptObject::bare_object(mc);
let script = Script::empty_script(mc, gs);
// public / root package // public / root package
let object_proto = object::create_proto(activation); let (object_proto, object_class) = object::create_proto(activation, gs);
let (function_constr, fn_proto) = function::create_class(activation, object_proto); let (function_constr, fn_proto, fn_class) =
let (class_constr, class_proto) = class::create_class(activation, object_proto, fn_proto); function::create_class(activation, gs, object_proto);
let (class_constr, class_proto, class_class) =
class::create_class(activation, gs, object_proto, fn_proto);
let object_constr = object::fill_proto(activation.context.gc_context, object_proto, fn_proto); let object_constr = object::fill_proto(mc, object_proto, fn_proto);
dynamic_class(activation.context.gc_context, gs, object_constr); dynamic_class(mc, object_constr, object_class, domain, script)?;
dynamic_class(activation.context.gc_context, gs, function_constr); dynamic_class(mc, function_constr, fn_class, domain, script)?;
dynamic_class(activation.context.gc_context, gs, class_constr); dynamic_class(mc, class_constr, class_class, domain, script)?;
// At this point, we need at least a partial set of system prototypes in // At this point, we need at least a partial set of system prototypes in
// order to continue initializing the player. The rest of the prototypes // order to continue initializing the player. The rest of the prototypes
@ -271,7 +293,7 @@ pub fn load_player_globals<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Res
object_proto, object_proto,
fn_proto, fn_proto,
class_proto, class_proto,
ScriptObject::bare_object(activation.context.gc_context), ScriptObject::bare_object(mc),
)); ));
// Even sillier: for the sake of clarity and the borrow checker we need to // Even sillier: for the sake of clarity and the borrow checker we need to
@ -282,118 +304,117 @@ pub fn load_player_globals<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Res
sp.global = class( sp.global = class(
activation, activation,
gs, global_scope::create_class(mc),
global_scope::create_class(activation.context.gc_context),
implicit_deriver, implicit_deriver,
domain,
script,
)?; )?;
sp.string = class( sp.string = class(
activation, activation,
gs, string::create_class(mc),
string::create_class(activation.context.gc_context),
primitive_deriver, primitive_deriver,
domain,
script,
)?; )?;
sp.boolean = class( sp.boolean = class(
activation, activation,
gs, boolean::create_class(mc),
boolean::create_class(activation.context.gc_context),
primitive_deriver, primitive_deriver,
domain,
script,
)?; )?;
sp.number = class( sp.number = class(
activation, activation,
gs, number::create_class(mc),
number::create_class(activation.context.gc_context),
primitive_deriver, primitive_deriver,
domain,
script,
)?; )?;
sp.int = class( sp.int = class(
activation, activation,
gs, int::create_class(mc),
int::create_class(activation.context.gc_context),
primitive_deriver, primitive_deriver,
domain,
script,
)?; )?;
sp.uint = class( sp.uint = class(
activation, activation,
gs, uint::create_class(mc),
uint::create_class(activation.context.gc_context),
primitive_deriver, primitive_deriver,
domain,
script,
)?; )?;
sp.namespace = class( sp.namespace = class(
activation, activation,
gs, namespace::create_class(mc),
namespace::create_class(activation.context.gc_context),
namespace_deriver, namespace_deriver,
domain,
script,
)?; )?;
sp.array = class( sp.array = class(
activation, activation,
gs, array::create_class(mc),
array::create_class(activation.context.gc_context),
array_deriver, array_deriver,
domain,
script,
)?; )?;
// At this point we have to hide the fact that we had to create the player
// globals scope *before* the `Object` class
gs.set_proto(mc, sp.global);
activation.context.avm2.system_prototypes = Some(sp); activation.context.avm2.system_prototypes = Some(sp);
function( function(mc, "", "trace", trace, fn_proto, domain, script)?;
activation.context.gc_context, constant(mc, "", "undefined", Value::Undefined, domain, script)?;
gs, constant(mc, "", "null", Value::Null, domain, script)?;
"", constant(mc, "", "NaN", NAN.into(), domain, script)?;
"trace", constant(mc, "", "Infinity", f64::INFINITY.into(), domain, script)?;
trace,
fn_proto,
);
constant(
activation.context.gc_context,
gs,
"",
"undefined",
Value::Undefined,
);
constant(activation.context.gc_context, gs, "", "null", Value::Null);
constant(activation.context.gc_context, gs, "", "NaN", NAN.into());
constant(
activation.context.gc_context,
gs,
"",
"Infinity",
f64::INFINITY.into(),
);
// package `flash.events` // package `flash.events`
class( class(
activation, activation,
gs, flash::events::ieventdispatcher::create_interface(mc),
flash::events::ieventdispatcher::create_interface(activation.context.gc_context),
implicit_deriver, implicit_deriver,
domain,
script,
)?; )?;
class( class(
activation, activation,
gs, flash::events::eventdispatcher::create_class(mc),
flash::events::eventdispatcher::create_class(activation.context.gc_context),
implicit_deriver, implicit_deriver,
domain,
script,
)?; )?;
// package `flash.display` // package `flash.display`
class( class(
activation, activation,
gs, flash::display::displayobject::create_class(mc),
flash::display::displayobject::create_class(activation.context.gc_context),
stage_deriver, stage_deriver,
domain,
script,
)?; )?;
class( class(
activation, activation,
gs, flash::display::interactiveobject::create_class(mc),
flash::display::interactiveobject::create_class(activation.context.gc_context),
implicit_deriver, implicit_deriver,
domain,
script,
)?; )?;
class( class(
activation, activation,
gs, flash::display::displayobjectcontainer::create_class(mc),
flash::display::displayobjectcontainer::create_class(activation.context.gc_context),
implicit_deriver, implicit_deriver,
domain,
script,
)?; )?;
class( class(
activation, activation,
gs, flash::display::sprite::create_class(mc),
flash::display::sprite::create_class(activation.context.gc_context),
implicit_deriver, implicit_deriver,
domain,
script,
)?; )?;
activation activation
.context .context
@ -403,9 +424,10 @@ pub fn load_player_globals<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Res
.unwrap() .unwrap()
.movieclip = class( .movieclip = class(
activation, activation,
gs, flash::display::movieclip::create_class(mc),
flash::display::movieclip::create_class(activation.context.gc_context),
implicit_deriver, implicit_deriver,
domain,
script,
)?; )?;
activation activation
.context .context
@ -415,9 +437,10 @@ pub fn load_player_globals<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Res
.unwrap() .unwrap()
.framelabel = class( .framelabel = class(
activation, activation,
gs, flash::display::framelabel::create_class(mc),
flash::display::framelabel::create_class(activation.context.gc_context),
implicit_deriver, implicit_deriver,
domain,
script,
)?; )?;
activation activation
.context .context
@ -427,9 +450,10 @@ pub fn load_player_globals<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Res
.unwrap() .unwrap()
.scene = class( .scene = class(
activation, activation,
gs, flash::display::scene::create_class(mc),
flash::display::scene::create_class(activation.context.gc_context),
implicit_deriver, implicit_deriver,
domain,
script,
)?; )?;
Ok(()) Ok(())

View File

@ -8,6 +8,7 @@ use crate::avm2::object::{FunctionObject, Object, ScriptObject, TObject};
use crate::avm2::scope::Scope; use crate::avm2::scope::Scope;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use gc_arena::GcCell;
/// Implements `Class`'s instance initializer. /// Implements `Class`'s instance initializer.
/// ///
@ -33,9 +34,10 @@ pub fn class_init<'gc>(
/// Construct `Class` and `Class.prototype`, respectively. /// Construct `Class` and `Class.prototype`, respectively.
pub fn create_class<'gc>( pub fn create_class<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
globals: Object<'gc>,
super_proto: Object<'gc>, super_proto: Object<'gc>,
fn_proto: Object<'gc>, fn_proto: Object<'gc>,
) -> (Object<'gc>, Object<'gc>) { ) -> (Object<'gc>, Object<'gc>, GcCell<'gc, Class<'gc>>) {
let class_class = Class::new( let class_class = Class::new(
QName::new(Namespace::public_namespace(), "Class"), QName::new(Namespace::public_namespace(), "Class"),
Some(QName::new(Namespace::public_namespace(), "Object").into()), Some(QName::new(Namespace::public_namespace(), "Object").into()),
@ -44,7 +46,6 @@ pub fn create_class<'gc>(
activation.context.gc_context, activation.context.gc_context,
); );
let globals = activation.avm2().globals();
let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context); let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context);
let proto = ScriptObject::prototype( let proto = ScriptObject::prototype(
activation.context.gc_context, activation.context.gc_context,
@ -61,5 +62,5 @@ pub fn create_class<'gc>(
) )
.unwrap(); .unwrap();
(constr, proto) (constr, proto, class_class)
} }

View File

@ -8,6 +8,7 @@ use crate::avm2::object::{FunctionObject, Object, ScriptObject, TObject};
use crate::avm2::scope::Scope; use crate::avm2::scope::Scope;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use gc_arena::GcCell;
/// Implements `Function`'s instance initializer. /// Implements `Function`'s instance initializer.
pub fn instance_init<'gc>( pub fn instance_init<'gc>(
@ -52,8 +53,9 @@ fn call<'gc>(
/// Construct `Function` and `Function.prototype`, respectively. /// Construct `Function` and `Function.prototype`, respectively.
pub fn create_class<'gc>( pub fn create_class<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
globals: Object<'gc>,
proto: Object<'gc>, proto: Object<'gc>,
) -> (Object<'gc>, Object<'gc>) { ) -> (Object<'gc>, Object<'gc>, GcCell<'gc, Class<'gc>>) {
let function_class = Class::new( let function_class = Class::new(
QName::new(Namespace::public_namespace(), "Function"), QName::new(Namespace::public_namespace(), "Function"),
Some(QName::new(Namespace::public_namespace(), "Object").into()), Some(QName::new(Namespace::public_namespace(), "Object").into()),
@ -62,7 +64,6 @@ pub fn create_class<'gc>(
activation.context.gc_context, activation.context.gc_context,
); );
let globals = activation.avm2().globals();
let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context); let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context);
let mut function_proto = ScriptObject::prototype( let mut function_proto = ScriptObject::prototype(
activation.context.gc_context, activation.context.gc_context,
@ -86,5 +87,5 @@ pub fn create_class<'gc>(
) )
.unwrap(); .unwrap();
(constr, function_proto) (constr, function_proto, function_class)
} }

View File

@ -8,7 +8,7 @@ use crate::avm2::object::{FunctionObject, Object, ScriptObject, TObject};
use crate::avm2::scope::Scope; use crate::avm2::scope::Scope;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use gc_arena::MutationContext; use gc_arena::{GcCell, MutationContext};
/// Implements `Object`'s instance initializer. /// Implements `Object`'s instance initializer.
pub fn instance_init<'gc>( pub fn instance_init<'gc>(
@ -157,7 +157,10 @@ pub fn set_property_is_enumerable<'gc>(
/// This function creates a suitable class and object prototype attached to it, /// This function creates a suitable class and object prototype attached to it,
/// but does not actually fill it with methods. That requires a valid function /// but does not actually fill it with methods. That requires a valid function
/// prototype, and is thus done by `fill_proto` below. /// prototype, and is thus done by `fill_proto` below.
pub fn create_proto<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Object<'gc> { pub fn create_proto<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
globals: Object<'gc>,
) -> (Object<'gc>, GcCell<'gc, Class<'gc>>) {
let object_class = Class::new( let object_class = Class::new(
QName::new(Namespace::public_namespace(), "Object"), QName::new(Namespace::public_namespace(), "Object"),
None, None,
@ -166,9 +169,11 @@ pub fn create_proto<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Object<'gc
activation.context.gc_context, activation.context.gc_context,
); );
let globals = activation.avm2().globals();
let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context); let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context);
ScriptObject::bare_prototype(activation.context.gc_context, object_class, Some(scope)) let proto =
ScriptObject::bare_prototype(activation.context.gc_context, object_class, Some(scope));
(proto, object_class)
} }
/// Finish constructing `Object.prototype`, and also construct `Object`. /// Finish constructing `Object.prototype`, and also construct `Object`.

View File

@ -333,6 +333,13 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// `get`. /// `get`.
fn proto(&self) -> Option<Object<'gc>>; fn proto(&self) -> Option<Object<'gc>>;
/// Change the `__proto__` on this object.
///
/// This method primarily exists so that the global scope that player
/// globals loads into can be created before it's superclasses are. It
/// should be used sparingly, if at all.
fn set_proto(self, mc: MutationContext<'gc, '_>, proto: Object<'gc>);
/// Retrieve a given enumerable name by index. /// Retrieve a given enumerable name by index.
/// ///
/// Enumerants are listed by index, starting from zero. A value of `None` /// Enumerants are listed by index, starting from zero. A value of `None`

View File

@ -160,6 +160,10 @@ macro_rules! impl_avm2_custom_object {
self.0.read().$field.proto() self.0.read().$field.proto()
} }
fn set_proto(self, mc: MutationContext<'gc, '_>, proto: Object<'gc>) {
self.0.write(mc).$field.set_proto(proto)
}
fn get_enumerant_name(&self, index: u32) -> Option<QName<'gc>> { fn get_enumerant_name(&self, index: u32) -> Option<QName<'gc>> {
self.0.read().$field.get_enumerant_name(index) self.0.read().$field.get_enumerant_name(index)
} }

View File

@ -216,6 +216,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
self.0.read().proto self.0.read().proto
} }
fn set_proto(self, mc: MutationContext<'gc, '_>, proto: Object<'gc>) {
self.0.write(mc).set_proto(proto)
}
fn get_enumerant_name(&self, index: u32) -> Option<QName<'gc>> { fn get_enumerant_name(&self, index: u32) -> Option<QName<'gc>> {
self.0.read().get_enumerant_name(index) self.0.read().get_enumerant_name(index)
} }
@ -723,6 +727,10 @@ impl<'gc> ScriptObjectData<'gc> {
self.proto self.proto
} }
pub fn set_proto(&mut self, proto: Object<'gc>) {
self.proto = Some(proto)
}
pub fn get_enumerant_name(&self, index: u32) -> Option<QName<'gc>> { pub fn get_enumerant_name(&self, index: u32) -> Option<QName<'gc>> {
// NOTE: AVM2 object enumeration is one of the weakest parts of an // NOTE: AVM2 object enumeration is one of the weakest parts of an
// otherwise well-designed VM. Notably, because of the way they // otherwise well-designed VM. Notably, because of the way they

View File

@ -216,6 +216,10 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
self.0.read().base.proto() self.0.read().base.proto()
} }
fn set_proto(self, mc: MutationContext<'gc, '_>, proto: Object<'gc>) {
self.0.write(mc).base.set_proto(proto)
}
fn get_enumerant_name(&self, index: u32) -> Option<QName<'gc>> { fn get_enumerant_name(&self, index: u32) -> Option<QName<'gc>> {
self.0.read().base.get_enumerant_name(index) self.0.read().base.get_enumerant_name(index)
} }

View File

@ -76,6 +76,17 @@ impl<'gc> Scope<'gc> {
&self.values &self.values
} }
/// Returns a reference to the current global scope object.
///
/// By convention, the global scope is at the bottom of the scope stack.
pub fn globals(&self) -> Object<'gc> {
if let Some(parent) = self.parent {
parent.read().globals()
} else {
self.values
}
}
/// Returns a reference to the current local scope object for mutation. /// Returns a reference to the current local scope object for mutation.
pub fn locals_mut(&mut self) -> &mut Object<'gc> { pub fn locals_mut(&mut self) -> &mut Object<'gc> {
&mut self.values &mut self.values

View File

@ -6,6 +6,7 @@ use crate::avm2::method::{BytecodeMethod, Method};
use crate::avm2::object::{Object, ScriptObject}; use crate::avm2::object::{Object, ScriptObject};
use crate::avm2::string::AvmString; use crate::avm2::string::AvmString;
use crate::avm2::traits::Trait; use crate::avm2::traits::Trait;
use crate::avm2::value::Value;
use crate::avm2::{Avm2, Error}; use crate::avm2::{Avm2, Error};
use crate::collect::CollectWrapper; use crate::collect::CollectWrapper;
use fnv::FnvHashMap; use fnv::FnvHashMap;
@ -228,6 +229,28 @@ pub struct Script<'gc> {
} }
impl<'gc> Script<'gc> { impl<'gc> Script<'gc> {
/// Create an empty script.
///
/// This method is intended for builtin script initialization, such as our
/// implementation of player globals. The builtin script initializer will
/// be responsible for actually installing traits into both the script
/// globals as well as the domain that this script is supposed to be a part
/// of.
///
/// The `globals` object should be constructed using the `global`
/// prototype.
pub fn empty_script(mc: MutationContext<'gc, '_>, globals: Object<'gc>) -> GcCell<'gc, Self> {
GcCell::allocate(
mc,
Self {
globals,
init: Method::from_builtin(|_, _, _| Ok(Value::Undefined)),
traits: Vec::new(),
traits_loaded: true,
},
)
}
/// Construct a script from a `TranslationUnit` and it's script index. /// Construct a script from a `TranslationUnit` and it's script index.
/// ///
/// The returned script will be allocated, but no traits will be loaded. /// The returned script will be allocated, but no traits will be loaded.

View File

@ -508,11 +508,14 @@ impl<'gc> MovieClip<'gc> {
if let Some(name) = if let Some(name) =
Avm2QName::from_symbol_class(&class_name, activation.context.gc_context) Avm2QName::from_symbol_class(&class_name, activation.context.gc_context)
{ {
let mut globals = activation.context.avm2.globals(); //TODO: Store a domain per movie & grab symbols out of that
match globals let globals = activation.context.avm2.global_domain();
.get_property(globals, &name, &mut activation) let proto = globals
.and_then(|v| v.coerce_to_object(&mut activation)) .read()
{ .get_defined_value(&mut activation, name.clone())
.and_then(|v| v.coerce_to_object(&mut activation));
match proto {
Ok(proto) => { Ok(proto) => {
let library = activation let library = activation
.context .context