From 1a352aa453708d420f275e3efd2fbba07bd44e97 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sun, 26 Mar 2023 14:12:19 -0500 Subject: [PATCH] avm2: Implement ApplicationDomain constructor and fix parent handling Previously, the `ApplicationDomain` constructor ignored its argument, instead of constructing a new domain with the specified domain as the parent. Additionally, we were incorrectly executing code with `Activation::from_nothing` in several places, causing `ApplicationDomain.currentDomain` to return the system domain instead of the correct parent domain. I've introduced a new method `Activation::from_domain`, which allows explicitly passing in the domain. Internally, we now store an `Option`, and panic when calling `caller_domain` with a `None` domain. Several places in the codebase have been adjusted to pass in the correct domain. --- core/src/avm2.rs | 5 +- core/src/avm2/activation.rs | 46 +++++++-- core/src/avm2/globals.rs | 9 +- .../globals/flash/system/ApplicationDomain.as | 20 +++- .../flash/system/application_domain.rs | 97 +++++-------------- core/src/avm2/globals/globals.as | 1 + core/src/avm2/globals/stubs.as | 1 - core/src/avm2/object.rs | 6 +- core/src/avm2/object/domain_object.rs | 14 ++- core/src/avm2/script.rs | 2 +- core/src/context.rs | 17 ---- core/src/display_object/movie_clip.rs | 9 ++ core/src/display_object/stage.rs | 3 +- core/src/display_object/text.rs | 7 +- core/src/external.rs | 7 +- core/src/loader.rs | 7 +- core/src/player.rs | 22 ++--- 17 files changed, 137 insertions(+), 136 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index e3dbe8454..3b5190bf5 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -180,7 +180,7 @@ impl<'gc> Avm2<'gc> { pub fn load_player_globals(context: &mut UpdateContext<'_, 'gc>) -> Result<(), Error<'gc>> { let globals = context.avm2.globals; - let mut activation = Activation::from_nothing(context.reborrow()); + let mut activation = Activation::from_domain(context.reborrow(), globals); globals::load_player_globals(&mut activation, globals) } @@ -400,9 +400,10 @@ impl<'gc> Avm2<'gc> { callable: Object<'gc>, reciever: Option>, args: &[Value<'gc>], + domain: Domain<'gc>, context: &mut UpdateContext<'_, 'gc>, ) -> Result<(), String> { - let mut evt_activation = Activation::from_nothing(context.reborrow()); + let mut evt_activation = Activation::from_domain(context.reborrow(), domain); callable .call(reciever, args, &mut evt_activation) .map_err(|e| e.detailed_message(&mut evt_activation))?; diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index dfc1aa44f..7d6181d0e 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -117,7 +117,7 @@ pub struct Activation<'a, 'gc: 'a> { /// /// If this activation was not made for a builtin method, this will be the /// current domain instead. - caller_domain: Domain<'gc>, + caller_domain: Option>, /// The class that yielded the currently executing method. /// @@ -176,7 +176,38 @@ impl<'a, 'gc> Activation<'a, 'gc> { local_registers, return_value: None, outer: ScopeChain::new(context.avm2.globals), - caller_domain: context.avm2.globals, + caller_domain: None, + subclass_object: None, + activation_class: None, + stack_depth: context.avm2.stack.len(), + scope_depth: context.avm2.scope_stack.len(), + max_stack_size: 0, + max_scope_size: 0, + context, + } + } + + /// Like `from_nothing`, but with a specified domain. + /// + /// This should be used when you actually need to run AVM2 code, but + /// don't have a particular scope to run it in. For example, this is + /// used to run frame scripts for AVM2 movies. + /// + /// The 'Domain' should come from the SwfMovie associated with whatever + /// action you're performing. When running frame scripts, this is the + /// `SwfMovie` associated with the `MovieClip` being processed. + pub fn from_domain(context: UpdateContext<'a, 'gc>, domain: Domain<'gc>) -> Self { + let local_registers = RegisterSet::new(0); + + Self { + this: None, + arguments: None, + is_executing: false, + actions_since_timeout_check: 0, + local_registers, + return_value: None, + outer: ScopeChain::new(context.avm2.globals), + caller_domain: Some(domain), subclass_object: None, activation_class: None, stack_depth: context.avm2.stack.len(), @@ -220,7 +251,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { local_registers, return_value: None, outer: ScopeChain::new(domain), - caller_domain: domain, + caller_domain: Some(domain), subclass_object: None, activation_class: None, stack_depth: context.avm2.stack.len(), @@ -443,7 +474,8 @@ impl<'a, 'gc> Activation<'a, 'gc> { drop(cached_cls); let translation_unit = method.translation_unit(); let abc_method = method.method(); - let mut dummy_activation = Activation::from_nothing(context.reborrow()); + let mut dummy_activation = + Activation::from_domain(context.reborrow(), outer.domain()); dummy_activation.set_outer(outer); let activation_class = Class::for_activation( &mut dummy_activation, @@ -472,7 +504,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { local_registers, return_value: None, outer, - caller_domain: outer.domain(), + caller_domain: Some(outer.domain()), subclass_object, activation_class, stack_depth: context.avm2.stack.len(), @@ -555,7 +587,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { local_registers, return_value: None, outer, - caller_domain, + caller_domain: Some(caller_domain), subclass_object, activation_class: None, stack_depth: context.avm2.stack.len(), @@ -641,7 +673,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { /// Returns the domain of the original AS3 caller. pub fn caller_domain(&self) -> Domain<'gc> { - self.caller_domain + self.caller_domain.expect("No caller domain available - use Activation::from_domain when constructing your domain") } /// Returns the global scope of this activation. diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 8aa612ef3..d0d8f4e8e 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -526,14 +526,6 @@ pub fn load_player_globals<'gc>( avm2_system_class!(date, activation, date::create_class(activation), script); - // package `flash.system` - avm2_system_class!( - application_domain, - activation, - flash::system::application_domain::create_class(activation), - script - ); - // package `flash.text` class( flash::text::font::create_class(activation), @@ -687,6 +679,7 @@ fn load_playerglobal<'gc>( ("flash.media", "SoundTransform", soundtransform), ("flash.net", "URLVariables", urlvariables), ("flash.utils", "ByteArray", bytearray), + ("flash.system", "ApplicationDomain", application_domain), ("flash.text", "StaticText", statictext), ("flash.text", "TextFormat", textformat), ("flash.text", "TextField", textfield), diff --git a/core/src/avm2/globals/flash/system/ApplicationDomain.as b/core/src/avm2/globals/flash/system/ApplicationDomain.as index c4d8f33a2..216bfa9cb 100644 --- a/core/src/avm2/globals/flash/system/ApplicationDomain.as +++ b/core/src/avm2/globals/flash/system/ApplicationDomain.as @@ -1,5 +1,21 @@ -// This is a stub - the actual class is defined in `application_domain.rs` package flash.system { - public class ApplicationDomain { + import flash.utils.ByteArray; + + [Ruffle(InstanceAllocator)] + public final class ApplicationDomain { + public static native function get currentDomain():ApplicationDomain; + + public function ApplicationDomain(parentDomain:ApplicationDomain = null) { + this.init(parentDomain) + } + + private native function init(parentDomain:ApplicationDomain):void; + + public native function get domainMemory():ByteArray; + public native function set domainMemory(value:ByteArray):void; + public native function get parentDomain():ApplicationDomain; + + public native function getDefinition(name:String):Object; + public native function hasDefinition(name:String):Boolean; } } diff --git a/core/src/avm2/globals/flash/system/application_domain.rs b/core/src/avm2/globals/flash/system/application_domain.rs index 28cfbfcf5..b39bf2a8c 100644 --- a/core/src/avm2/globals/flash/system/application_domain.rs +++ b/core/src/avm2/globals/flash/system/application_domain.rs @@ -1,40 +1,40 @@ //! `flash.system.ApplicationDomain` class use crate::avm2::activation::Activation; -use crate::avm2::class::Class; -use crate::avm2::method::{Method, NativeMethodImpl}; -use crate::avm2::object::{appdomain_allocator, DomainObject, Object, TObject}; +use crate::avm2::object::{DomainObject, Object, TObject}; +use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; -use crate::avm2::Error; -use crate::avm2::Multiname; -use crate::avm2::Namespace; use crate::avm2::QName; -use gc_arena::GcCell; +use crate::avm2::{Domain, Error}; -/// Implements `flash.system.ApplicationDomain`'s instance constructor. -pub fn instance_init<'gc>( +pub use crate::avm2::object::application_domain_allocator; + +/// Implements `flash.system.ApplicationDomain`'s init method, which +/// is called from the constructor +pub fn init<'gc>( activation: &mut Activation<'_, 'gc>, this: Option>, - _args: &[Value<'gc>], + args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(this) = this { activation.super_init(this, &[])?; + + let parent_domain = if matches!(args[0], Value::Null) { + activation.avm2().global_domain() + } else { + args.get_object(activation, 0, "parentDomain")? + .as_application_domain() + .expect("Invalid parent domain") + }; + let fresh_domain = Domain::movie_domain(activation, parent_domain); + this.init_application_domain(activation.context.gc_context, fresh_domain); } Ok(Value::Undefined) } -/// Implements `flash.system.ApplicationDomain`'s class constructor. -pub fn class_init<'gc>( - _activation: &mut Activation<'_, 'gc>, - _this: Option>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(Value::Undefined) -} - /// `currentDomain` static property. -pub fn current_domain<'gc>( +pub fn get_current_domain<'gc>( activation: &mut Activation<'_, 'gc>, _this: Option>, _args: &[Value<'gc>], @@ -45,7 +45,7 @@ pub fn current_domain<'gc>( } /// `parentDomain` property -pub fn parent_domain<'gc>( +pub fn get_parent_domain<'gc>( activation: &mut Activation<'_, 'gc>, this: Option>, _args: &[Value<'gc>], @@ -120,7 +120,7 @@ pub fn set_domain_memory<'gc>( } /// `domainMemory` property getter -pub fn domain_memory<'gc>( +pub fn get_domain_memory<'gc>( _activation: &mut Activation<'_, 'gc>, this: Option>, _args: &[Value<'gc>], @@ -132,56 +132,3 @@ pub fn domain_memory<'gc>( Ok(Value::Undefined) } - -/// Construct `ApplicationDomain`'s class. -pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { - let mc = activation.context.gc_context; - let class = Class::new( - QName::new(Namespace::package("flash.system", mc), "ApplicationDomain"), - Some(Multiname::new(activation.avm2().public_namespace, "Object")), - Method::from_builtin( - instance_init, - "", - mc, - ), - Method::from_builtin(class_init, "", mc), - mc, - ); - - let mut write = class.write(mc); - write.set_instance_allocator(appdomain_allocator); - - const PUBLIC_CLASS_PROPERTIES: &[(&str, Option, Option)] = - &[("currentDomain", Some(current_domain), None)]; - write.define_builtin_class_properties( - mc, - activation.avm2().public_namespace, - PUBLIC_CLASS_PROPERTIES, - ); - - const PUBLIC_INSTANCE_PROPERTIES: &[( - &str, - Option, - Option, - )] = &[ - ("domainMemory", Some(domain_memory), Some(set_domain_memory)), - ("parentDomain", Some(parent_domain), None), - ]; - write.define_builtin_instance_properties( - mc, - activation.avm2().public_namespace, - PUBLIC_INSTANCE_PROPERTIES, - ); - - const PUBLIC_INSTANCE_METHODS: &[(&str, NativeMethodImpl)] = &[ - ("getDefinition", get_definition), - ("hasDefinition", has_definition), - ]; - write.define_builtin_instance_methods( - mc, - activation.avm2().public_namespace, - PUBLIC_INSTANCE_METHODS, - ); - - class -} diff --git a/core/src/avm2/globals/globals.as b/core/src/avm2/globals/globals.as index aa103c7c0..20860602b 100644 --- a/core/src/avm2/globals/globals.as +++ b/core/src/avm2/globals/globals.as @@ -264,6 +264,7 @@ include "flash/printing/PrintJobOrientation.as" include "flash/profiler.as" include "flash/security/CertificateStatus.as" +include "flash/system/ApplicationDomain.as" include "flash/system/Capabilities.as" include "flash/system/IME.as" include "flash/system/IMEConversionMode.as" diff --git a/core/src/avm2/globals/stubs.as b/core/src/avm2/globals/stubs.as index 09eeeb348..2b8667335 100644 --- a/core/src/avm2/globals/stubs.as +++ b/core/src/avm2/globals/stubs.as @@ -9,7 +9,6 @@ include "Array.as" include "Boolean.as" include "Date.as" -include "flash/system/ApplicationDomain.as" include "Function.as" include "Number.as" include "String.as" diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index b18da115e..7f8377722 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -70,7 +70,7 @@ pub use crate::avm2::object::context3d_object::Context3DObject; pub use crate::avm2::object::date_object::{date_allocator, DateObject}; pub use crate::avm2::object::dictionary_object::{dictionary_allocator, DictionaryObject}; pub use crate::avm2::object::dispatch_object::DispatchObject; -pub use crate::avm2::object::domain_object::{appdomain_allocator, DomainObject}; +pub use crate::avm2::object::domain_object::{application_domain_allocator, DomainObject}; pub use crate::avm2::object::error_object::{error_allocator, ErrorObject}; pub use crate::avm2::object::event_object::{event_allocator, EventObject}; pub use crate::avm2::object::function_object::{function_allocator, FunctionObject}; @@ -1155,6 +1155,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn init_display_object(&self, _context: &mut UpdateContext<'_, 'gc>, _obj: DisplayObject<'gc>) { } + fn init_application_domain(&self, _mc: MutationContext<'gc, '_>, _domain: Domain<'gc>) { + panic!("Tried to init an application domain on a non-ApplicationDomain object!") + } + /// Unwrap this object as an ApplicationDomain. fn as_application_domain(&self) -> Option> { None diff --git a/core/src/avm2/object/domain_object.rs b/core/src/avm2/object/domain_object.rs index d970cfac3..5501996a7 100644 --- a/core/src/avm2/object/domain_object.rs +++ b/core/src/avm2/object/domain_object.rs @@ -11,7 +11,7 @@ use gc_arena::{Collect, GcCell, MutationContext}; use std::cell::{Ref, RefMut}; /// A class instance allocator that allocates AppDomain objects. -pub fn appdomain_allocator<'gc>( +pub fn application_domain_allocator<'gc>( class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { @@ -65,8 +65,12 @@ impl<'gc> DomainObject<'gc> { .into(); this.install_instance_slots(activation); - class.call_init(Some(this), &[], activation)?; - + // Note - we do *not* call the normal constructor, since that + // creates a new domain using the system domain as a parent. + class + .superclass_object() + .unwrap() + .call_native_init(Some(this), &[], activation)?; Ok(this) } } @@ -88,6 +92,10 @@ impl<'gc> TObject<'gc> for DomainObject<'gc> { Some(self.0.read().domain) } + fn init_application_domain(&self, mc: MutationContext<'gc, '_>, domain: Domain<'gc>) { + self.0.write(mc).domain = domain; + } + fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result, Error<'gc>> { let this: Object<'gc> = Object::DomainObject(*self); diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index 3c8681ede..33fa2ac18 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -203,7 +203,7 @@ impl<'gc> TranslationUnit<'gc> { drop(read); - let mut activation = Activation::from_nothing(uc.reborrow()); + let mut activation = Activation::from_domain(uc.reborrow(), domain); let global_class = activation.avm2().classes().global; let global_obj = global_class.construct(&mut activation, &[])?; global_obj.fork_vtable(activation.context.gc_context); diff --git a/core/src/context.rs b/core/src/context.rs index 8c02b0d3e..35912a5ed 100644 --- a/core/src/context.rs +++ b/core/src/context.rs @@ -479,13 +479,6 @@ pub enum ActionType<'gc> { method: &'static str, args: Vec>, }, - - /// An AVM2 callable, e.g. a frame script or event handler. - Callable2 { - callable: Avm2Object<'gc>, - reciever: Option>, - args: Vec>, - }, } impl ActionType<'_> { @@ -533,16 +526,6 @@ impl fmt::Debug for ActionType<'_> { .field("method", method) .field("args", args) .finish(), - ActionType::Callable2 { - callable, - reciever, - args, - } => f - .debug_struct("ActionType::Callable2") - .field("callable", callable) - .field("reciever", reciever) - .field("args", args) - .finish(), } } } diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 4c3533739..ca46929c7 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -2490,10 +2490,19 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { .insert(MovieClipFlags::EXECUTING_AVM2_FRAME_SCRIPT); drop(write); + + let movie = self.movie(); + let domain = context + .library + .library_for_movie(movie) + .unwrap() + .avm2_domain(); + if let Err(e) = Avm2::run_stack_frame_for_callable( callable, Some(avm2_object), &[], + domain, context, ) { tracing::error!( diff --git a/core/src/display_object/stage.rs b/core/src/display_object/stage.rs index e4960be7c..e4d5b7abd 100644 --- a/core/src/display_object/stage.rs +++ b/core/src/display_object/stage.rs @@ -745,7 +745,8 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> { // TODO: Replace this when we have a convenience method for constructing AVM2 native objects. // TODO: We should only do this if the movie is actually an AVM2 movie. // This is necessary for EventDispatcher super-constructor to run. - let mut activation = Avm2Activation::from_nothing(context.reborrow()); + let global_domain = context.avm2.global_domain(); + let mut activation = Avm2Activation::from_domain(context.reborrow(), global_domain); let avm2_stage = Avm2StageObject::for_display_object_childless( &mut activation, (*self).into(), diff --git a/core/src/display_object/text.rs b/core/src/display_object/text.rs index e837eaadf..c51de7758 100644 --- a/core/src/display_object/text.rs +++ b/core/src/display_object/text.rs @@ -243,7 +243,12 @@ impl<'gc> TDisplayObject<'gc> for Text<'gc> { _run_frame: bool, ) { if context.is_action_script_3() { - let mut activation = Avm2Activation::from_nothing(context.reborrow()); + let domain = context + .library + .library_for_movie(self.movie()) + .unwrap() + .avm2_domain(); + let mut activation = Avm2Activation::from_domain(context.reborrow(), domain); let statictext = activation.avm2().classes().statictext; match Avm2StageObject::for_display_object_childless( &mut activation, diff --git a/core/src/external.rs b/core/src/external.rs index 0197dcc31..4ce567eac 100644 --- a/core/src/external.rs +++ b/core/src/external.rs @@ -279,7 +279,12 @@ impl<'gc> Callback<'gc> { Value::Null } Callback::Avm2 { method } => { - let mut activation = Avm2Activation::from_nothing(context.reborrow()); + let domain = context + .library + .library_for_movie(context.swf.clone()) + .unwrap() + .avm2_domain(); + let mut activation = Avm2Activation::from_domain(context.reborrow(), domain); let args: Vec = args .into_iter() .map(|v| v.into_avm2(&mut activation)) diff --git a/core/src/loader.rs b/core/src/loader.rs index fc2d83658..efe27cbba 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -678,7 +678,12 @@ impl<'gc> Loader<'gc> { .set_skip_next_enter_frame(true); if let Some(MovieLoaderEventHandler::Avm2LoaderInfo(loader_info)) = event_handler { - let mut activation = Avm2Activation::from_nothing(context.reborrow()); + let domain = context + .library + .library_for_movie(mc.movie()) + .unwrap() + .avm2_domain(); + let mut activation = Avm2Activation::from_domain(context.reborrow(), domain); let mut loader = loader_info .get_public_property("loader", &mut activation) .map_err(|e| Error::Avm2Error(e.to_string()))? diff --git a/core/src/player.rs b/core/src/player.rs index fe04eb7e4..abff6f99e 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -358,9 +358,13 @@ impl Player { .stage .set_movie(context.gc_context, context.swf.clone()); - let mut activation = Avm2Activation::from_nothing(context.reborrow()); - let global_domain = activation.avm2().global_domain(); - let domain = Avm2Domain::movie_domain(&mut activation, global_domain); + let global_domain = context.avm2.global_domain(); + let mut global_activation = + Avm2Activation::from_domain(context.reborrow(), global_domain); + let domain = Avm2Domain::movie_domain(&mut global_activation, global_domain); + + let mut activation = + Avm2Activation::from_domain(global_activation.context.reborrow(), domain); activation .context @@ -1686,18 +1690,6 @@ impl Player { &args, ); } - - ActionType::Callable2 { - callable, - reciever, - args, - } => { - if let Err(e) = - Avm2::run_stack_frame_for_callable(callable, reciever, &args[..], context) - { - tracing::error!("Unhandled AVM2 exception in event handler: {}", e); - } - } } // AVM1 bytecode may leave the stack unbalanced, so do not let garbage values accumulate