avm2: Allow small mutations to ScopeChain

This commit is contained in:
EmperorBale 2021-09-14 15:57:52 -07:00 committed by kmeisthax
parent 9de7d7ba7a
commit 1dd899a76f
5 changed files with 92 additions and 158 deletions

View File

@ -402,10 +402,11 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let translation_unit = method.translation_unit();
let abc_method = method.method();
let mut dummy_activation = Activation::from_nothing(context.reborrow());
dummy_activation.set_outer(outer);
let activation_class =
Class::for_activation(&mut dummy_activation, translation_unit, abc_method, body)?;
let activation_class_object =
ClassObject::from_class(&mut dummy_activation, activation_class, None, outer)?;
ClassObject::from_class(&mut dummy_activation, activation_class, None)?;
drop(dummy_activation);
@ -579,6 +580,11 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
}
}
/// Sets the outer scope of this activation
pub fn set_outer(&mut self, new_outer: ScopeChain<'gc>) {
self.outer = new_outer;
}
/// Creates a new ScopeChain by chaining the current state of this
/// activation's scope stack with the outer scope.
pub fn create_scopechain(&self) -> ScopeChain<'gc> {
@ -1726,9 +1732,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
};
let class_entry = self.table_class(method, index)?;
let scope = self.create_scopechain();
let new_class = ClassObject::from_class(self, class_entry, base_class, scope)?;
let new_class = ClassObject::from_class(self, class_entry, base_class)?;
self.context.avm2.push(new_class);

View File

@ -288,10 +288,10 @@ fn function<'gc>(
name: &'static str,
nf: NativeMethodImpl,
script: Script<'gc>,
scope: ScopeChain<'gc>,
) -> Result<(), Error> {
let (_, _, mut domain) = script.init();
let mc = activation.context.gc_context;
let scope = activation.create_scopechain();
let qname = QName::new(Namespace::package(package), name);
let method = Method::from_builtin(nf, name, mc);
let as3fn = FunctionObject::from_method(activation, method, scope, None).into();
@ -330,7 +330,6 @@ fn class<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
class_def: GcCell<'gc, Class<'gc>>,
script: Script<'gc>,
scope: ScopeChain<'gc>,
) -> Result<(ClassObject<'gc>, Object<'gc>), Error> {
let (_, mut global, mut domain) = script.init();
@ -359,7 +358,7 @@ fn class<'gc>(
let class_name = class_read.name().clone();
drop(class_read);
let class_object = ClassObject::from_class(activation, class_def, super_class, scope)?;
let class_object = ClassObject::from_class(activation, class_def, super_class)?;
global.install_const(
activation.context.gc_context,
class_name.clone(),
@ -397,8 +396,8 @@ fn constant<'gc>(
}
macro_rules! avm2_system_class {
($field:ident, $activation:ident, $class:expr, $script:expr, $scope:expr) => {
let (class_object, proto) = class($activation, $class, $script, $scope)?;
($field:ident, $activation:ident, $class:expr, $script:expr) => {
let (class_object, proto) = class($activation, $class, $script)?;
let sc = $activation.avm2().system_classes.as_mut().unwrap();
sc.$field = class_object;
@ -424,6 +423,9 @@ pub fn load_player_globals<'gc>(
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
//
// This part of global initialization is very complicated, because
@ -439,17 +441,17 @@ pub fn load_player_globals<'gc>(
// Hence, this ridiculously complicated dance of classdef, type allocation,
// and partial initialization.
let object_classdef = object::create_class(mc);
let object_class = ClassObject::from_class_partial(activation, object_classdef, None, gs)?;
let object_class = ClassObject::from_class_partial(activation, object_classdef, None)?;
let object_proto = ScriptObject::bare_object(mc);
let fn_classdef = function::create_class(mc);
let fn_class =
ClassObject::from_class_partial(activation, fn_classdef, Some(object_class.into()), gs)?;
ClassObject::from_class_partial(activation, fn_classdef, Some(object_class.into()))?;
let fn_proto = ScriptObject::object(mc, object_proto);
let class_classdef = class::create_class(mc);
let class_class =
ClassObject::from_class_partial(activation, class_classdef, Some(object_class.into()), gs)?;
ClassObject::from_class_partial(activation, class_classdef, Some(object_class.into()))?;
let class_proto = ScriptObject::object(mc, object_proto);
// Now to weave the Gordian knot...
@ -489,14 +491,7 @@ pub fn load_player_globals<'gc>(
// After this point, it is safe to initialize any other classes.
// Make sure to initialize superclasses *before* their subclasses!
avm2_system_class!(
global,
activation,
global_scope::create_class(mc),
script,
gs
);
avm2_system_class!(global, activation, global_scope::create_class(mc), script);
// Oh, one more small hitch: the domain everything gets put into was
// actually made *before* the core class weave, so let's fix that up now
@ -504,132 +499,104 @@ pub fn load_player_globals<'gc>(
globals.set_proto(mc, activation.avm2().prototypes().global);
globals.set_instance_of(mc, activation.avm2().classes().global);
avm2_system_class!(string, activation, string::create_class(mc), script, gs);
avm2_system_class!(boolean, activation, boolean::create_class(mc), script, gs);
avm2_system_class!(number, activation, number::create_class(mc), script, gs);
avm2_system_class!(int, activation, int::create_class(mc), script, gs);
avm2_system_class!(uint, activation, uint::create_class(mc), script, gs);
avm2_system_class!(
namespace,
activation,
namespace::create_class(mc),
script,
gs
);
avm2_system_class!(qname, activation, qname::create_class(mc), script, gs);
avm2_system_class!(array, activation, array::create_class(mc), script, gs);
avm2_system_class!(string, activation, string::create_class(mc), script);
avm2_system_class!(boolean, activation, boolean::create_class(mc), script);
avm2_system_class!(number, activation, number::create_class(mc), script);
avm2_system_class!(int, activation, int::create_class(mc), script);
avm2_system_class!(uint, activation, uint::create_class(mc), script);
avm2_system_class!(namespace, activation, namespace::create_class(mc), script);
avm2_system_class!(qname, activation, qname::create_class(mc), script);
avm2_system_class!(array, activation, array::create_class(mc), script);
function(activation, "", "trace", trace, script, gs)?;
function(activation, "", "isFinite", is_finite, script, gs)?;
function(activation, "", "isNaN", is_nan, script, gs)?;
function(activation, "", "trace", trace, script)?;
function(activation, "", "isFinite", is_finite, script)?;
function(activation, "", "isNaN", is_nan, script)?;
constant(mc, "", "undefined", Value::Undefined, script)?;
constant(mc, "", "null", Value::Null, script)?;
constant(mc, "", "NaN", f64::NAN.into(), script)?;
constant(mc, "", "Infinity", f64::INFINITY.into(), script)?;
class(activation, math::create_class(mc), script, gs)?;
avm2_system_class!(regexp, activation, regexp::create_class(mc), script, gs);
avm2_system_class!(vector, activation, vector::create_class(mc), script, gs);
avm2_system_class!(xml, activation, xml::create_class(mc), script, gs);
avm2_system_class!(xml_list, activation, xml_list::create_class(mc), script, gs);
class(activation, math::create_class(mc), script)?;
avm2_system_class!(regexp, activation, regexp::create_class(mc), script);
avm2_system_class!(vector, activation, vector::create_class(mc), script);
avm2_system_class!(xml, activation, xml::create_class(mc), script);
avm2_system_class!(xml_list, activation, xml_list::create_class(mc), script);
avm2_system_class!(date, activation, date::create_class(mc), script, gs);
avm2_system_class!(date, activation, date::create_class(mc), script);
// package `flash.system`
avm2_system_class!(
application_domain,
activation,
flash::system::application_domain::create_class(mc),
script,
gs
script
);
class(
activation,
flash::system::capabilities::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::system::security::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::system::system::create_class(mc),
script,
gs,
)?;
class(activation, flash::system::system::create_class(mc), script)?;
// package `flash.events`
avm2_system_class!(
event,
activation,
flash::events::event::create_class(mc),
script,
gs
script
);
class(
activation,
flash::events::ieventdispatcher::create_interface(mc),
script,
gs,
)?;
class(
activation,
flash::events::eventdispatcher::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::events::mouseevent::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::events::keyboardevent::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::events::progressevent::create_class(mc),
script,
gs,
)?;
// package `flash.utils`
avm2_system_class!(
bytearray,
activation,
flash::utils::bytearray::create_class(mc),
script,
gs
script
);
domain.init_default_domain_memory(activation)?;
class(
activation,
flash::utils::endian::create_class(mc),
script,
gs,
)?;
class(activation, flash::utils::endian::create_class(mc), script)?;
class(
activation,
flash::utils::compression_algorithm::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::utils::dictionary::create_class(mc),
script,
gs,
)?;
function(
@ -638,7 +605,6 @@ pub fn load_player_globals<'gc>(
"getTimer",
flash::utils::get_timer,
script,
gs,
)?;
function(
@ -647,7 +613,6 @@ pub fn load_player_globals<'gc>(
"getQualifiedClassName",
flash::utils::get_qualified_class_name,
script,
gs,
)?;
function(
@ -656,7 +621,6 @@ pub fn load_player_globals<'gc>(
"getQualifiedSuperclassName",
flash::utils::get_qualified_super_class_name,
script,
gs,
)?;
function(
@ -665,7 +629,6 @@ pub fn load_player_globals<'gc>(
"getDefinitionByName",
flash::utils::get_definition_by_name,
script,
gs,
)?;
// package `flash.display`
@ -673,157 +636,133 @@ pub fn load_player_globals<'gc>(
activation,
flash::display::ibitmapdrawable::create_interface(mc),
script,
gs,
)?;
avm2_system_class!(
display_object,
activation,
flash::display::displayobject::create_class(mc),
script,
gs
script
);
avm2_system_class!(
shape,
activation,
flash::display::shape::create_class(mc),
script,
gs
script
);
class(
activation,
flash::display::interactiveobject::create_class(mc),
script,
gs,
)?;
avm2_system_class!(
simplebutton,
activation,
flash::display::simplebutton::create_class(mc),
script,
gs
script
);
class(
activation,
flash::display::displayobjectcontainer::create_class(mc),
script,
gs,
)?;
avm2_system_class!(
sprite,
activation,
flash::display::sprite::create_class(mc),
script,
gs
script
);
avm2_system_class!(
movieclip,
activation,
flash::display::movieclip::create_class(mc),
script,
gs
script
);
avm2_system_class!(
framelabel,
activation,
flash::display::framelabel::create_class(mc),
script,
gs
script
);
avm2_system_class!(
scene,
activation,
flash::display::scene::create_class(mc),
script,
gs
script
);
avm2_system_class!(
graphics,
activation,
flash::display::graphics::create_class(mc),
script,
gs
script
);
class(
activation,
flash::display::jointstyle::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::display::linescalemode::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::display::capsstyle::create_class(mc),
script,
gs,
)?;
avm2_system_class!(
loaderinfo,
activation,
flash::display::loaderinfo::create_class(mc),
script,
gs
script
);
class(
activation,
flash::display::actionscriptversion::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::display::swfversion::create_class(mc),
script,
gs,
)?;
avm2_system_class!(
stage,
activation,
flash::display::stage::create_class(mc),
script,
gs
script
);
class(
activation,
flash::display::stagescalemode::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::display::stagealign::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::display::stagedisplaystate::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::display::stagequality::create_class(mc),
script,
gs,
)?;
avm2_system_class!(
bitmap,
activation,
flash::display::bitmap::create_class(mc),
script,
gs
script
);
avm2_system_class!(
bitmapdata,
activation,
flash::display::bitmapdata::create_class(mc),
script,
gs
script
);
// package `flash.geom`
@ -831,15 +770,13 @@ pub fn load_player_globals<'gc>(
point,
activation,
flash::geom::point::create_class(mc),
script,
gs
script
);
avm2_system_class!(
rectangle,
activation,
flash::geom::rectangle::create_class(mc),
script,
gs
script
);
// package `flash.media`
@ -847,34 +784,25 @@ pub fn load_player_globals<'gc>(
video,
activation,
flash::media::video::create_class(mc),
script,
gs
script
);
class(
activation,
flash::media::sound::create_class(mc),
script,
gs,
)?;
class(activation, flash::media::sound::create_class(mc), script)?;
avm2_system_class!(
soundtransform,
activation,
flash::media::soundtransform::create_class(mc),
script,
gs
script
);
class(
activation,
flash::media::soundmixer::create_class(mc),
script,
gs,
)?;
avm2_system_class!(
soundchannel,
activation,
flash::media::soundchannel::create_class(mc),
script,
gs
script
);
// package `flash.text`
@ -882,35 +810,30 @@ pub fn load_player_globals<'gc>(
textfield,
activation,
flash::text::textfield::create_class(mc),
script,
gs
script
);
avm2_system_class!(
textformat,
activation,
flash::text::textformat::create_class(mc),
script,
gs
script
);
class(
activation,
flash::text::textfieldautosize::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::text::textformatalign::create_class(mc),
script,
gs,
)?;
class(
activation,
flash::text::textfieldtype::create_class(mc),
script,
gs,
)?;
class(activation, flash::text::font::create_class(mc), script, gs)?;
class(activation, flash::text::font::create_class(mc), script)?;
// package `flash.crypto`
function(
@ -919,7 +842,6 @@ pub fn load_player_globals<'gc>(
"generateRandomBytes",
flash::crypto::generate_random_bytes,
script,
gs,
)?;
Ok(())

View File

@ -85,9 +85,8 @@ impl<'gc> ClassObject<'gc> {
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
superclass_object: Option<ClassObject<'gc>>,
scope: ScopeChain<'gc>,
) -> Result<ClassObject<'gc>, Error> {
let class_object = Self::from_class_partial(activation, class, superclass_object, scope)?;
) -> Result<Self, Error> {
let class_object = Self::from_class_partial(activation, class, superclass_object)?;
//TODO: Class prototypes are *not* instances of their class and should
//not be allocated by the class allocator, but instead should be
@ -130,8 +129,8 @@ impl<'gc> ClassObject<'gc> {
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
superclass_object: Option<ClassObject<'gc>>,
scope: ScopeChain<'gc>,
) -> Result<Self, Error> {
let scope = activation.create_scopechain();
if let Some(base_class) = superclass_object.map(|b| b.inner_class_definition()) {
if base_class.read().is_final() {
return Err(format!(
@ -174,7 +173,7 @@ impl<'gc> ClassObject<'gc> {
ClassObjectData {
base: ScriptObjectData::base_new(None, None),
class,
scope,
scope: scope,
superclass_object,
instance_allocator: Allocator(instance_allocator),
constructor,
@ -204,7 +203,7 @@ impl<'gc> ClassObject<'gc> {
pub fn into_finished_class(
mut self,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<ClassObject<'gc>, Error> {
) -> Result<Self, Error> {
let class = self.inner_class_definition();
let class_class = self.instance_of().ok_or(
"Cannot finish initialization of core class without it being linked to a type!",
@ -301,8 +300,8 @@ impl<'gc> ClassObject<'gc> {
) -> Result<(), Error> {
let object: Object<'gc> = self.into();
let class = self.0.read().class;
let scope = self.0.read().scope;
let class = self.0.read().class;
let class_read = class.read();
if !class_read.is_class_initialized() {

View File

@ -6,7 +6,7 @@ use crate::avm2::names::Multiname;
use crate::avm2::object::{Object, TObject};
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::{Collect, Gc, MutationContext};
use gc_arena::{Collect, GcCell, MutationContext};
use std::ops::Deref;
/// Represents a Scope that can be on either a ScopeChain or local ScopeStack.
@ -59,10 +59,13 @@ impl<'gc> Scope<'gc> {
/// ScopeChain's are copy-on-write, meaning when we chain new scopes on top of a ScopeChain, we
/// actually create a completely brand new ScopeChain. The Domain of the ScopeChain we are chaining
/// on top of will be used for the new ScopeChain.
///
/// Alternatively, the `add` method allows mutating the ScopeChain, but it should be used with caution.
/// Mutating a ScopeChain that functions/classes saved could cause bizzare bugs.
#[derive(Debug, Collect, Clone, Copy)]
#[collect(no_drop)]
pub struct ScopeChain<'gc> {
scopes: Option<Gc<'gc, Vec<Scope<'gc>>>>,
scopes: Option<GcCell<'gc, Vec<Scope<'gc>>>>,
domain: Domain<'gc>,
}
@ -77,19 +80,15 @@ impl<'gc> ScopeChain<'gc> {
/// Creates a new ScopeChain by chaining new scopes on top of this ScopeChain
pub fn chain(&self, mc: MutationContext<'gc, '_>, new_scopes: &[Scope<'gc>]) -> Self {
if new_scopes.is_empty() {
// If we are not actually adding any new scopes, we don't need to do anything.
return *self;
}
// TODO: This current implementation is a bit expensive, but it is exactly what avmplus does, so it's good enough for now.
match self.scopes {
Some(scopes) => {
// The new ScopeChain is created by cloning the scopes of this ScopeChain,
// and pushing the new scopes on top of that.
let mut cloned = scopes.deref().clone();
let mut cloned = scopes.read().deref().clone();
cloned.extend_from_slice(new_scopes);
Self {
scopes: Some(Gc::allocate(mc, cloned)),
scopes: Some(GcCell::allocate(mc, cloned)),
domain: self.domain,
}
}
@ -97,20 +96,33 @@ impl<'gc> ScopeChain<'gc> {
// We are chaining on top of an empty ScopeChain, so we don't actually
// need to chain anything.
Self {
scopes: Some(Gc::allocate(mc, new_scopes.to_vec())),
scopes: Some(GcCell::allocate(mc, new_scopes.to_vec())),
domain: self.domain,
}
}
}
}
/// Adds a new scope to this ScopeChain. Unlike `chain`, this will actually mutate the
/// underlying scopes.
///
/// If this ScopeChain is empty (self.scopes is None), this is a no-op.
///
/// WARNING: This can cause bizarre bugs, because it can change the saved ScopeChain
/// of functions/classes that are using it.
pub fn add(&self, mc: MutationContext<'gc, '_>, scope: Scope<'gc>) {
self.scopes.map(|scopes| scopes.write(mc).push(scope));
}
pub fn get(&self, index: usize) -> Option<Scope<'gc>> {
self.scopes
.and_then(|scopes| scopes.deref().get(index).cloned())
.and_then(|scopes| scopes.read().get(index).cloned())
}
pub fn is_empty(&self) -> bool {
self.scopes.map(|scopes| scopes.is_empty()).unwrap_or(true)
self.scopes
.map(|scopes| scopes.read().is_empty())
.unwrap_or(true)
}
/// Returns the domain associated with this ScopeChain.
@ -125,7 +137,7 @@ impl<'gc> ScopeChain<'gc> {
) -> Result<Option<Object<'gc>>, Error> {
// First search our scopes
if let Some(scopes) = self.scopes {
for (depth, scope) in scopes.iter().enumerate().rev() {
for (depth, scope) in scopes.read().iter().enumerate().rev() {
let values = scope.values();
if let Some(qname) = values.resolve_multiname(name)? {
// We search the dynamic properties if either conditions are met:

View File

@ -342,11 +342,7 @@ impl<'gc> Script<'gc> {
let script = script?;
for abc_trait in script.traits.iter() {
drop(write);
let newtrait = Trait::from_abc_trait(unit, abc_trait, activation)?;
write = self.0.write(activation.context.gc_context);
write.domain.export_definition(
newtrait.name().clone(),
*self,