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<Domain>`, and panic
when calling `caller_domain` with a `None` domain. Several places
in the codebase have been adjusted to pass in the correct domain.
This commit is contained in:
Aaron Hill 2023-03-26 14:12:19 -05:00 committed by Mike Welsh
parent da6384b30e
commit 1a352aa453
17 changed files with 137 additions and 136 deletions

View File

@ -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<Object<'gc>>,
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))?;

View File

@ -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<Domain<'gc>>,
/// 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.

View File

@ -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),

View File

@ -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;
}
}

View File

@ -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<Object<'gc>>,
_args: &[Value<'gc>],
args: &[Value<'gc>],
) -> Result<Value<'gc>, 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<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, 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<Object<'gc>>,
_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<Object<'gc>>,
_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<Object<'gc>>,
_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,
"<ApplicationDomain instance initializer>",
mc,
),
Method::from_builtin(class_init, "<ApplicationDomain class initializer>", mc),
mc,
);
let mut write = class.write(mc);
write.set_instance_allocator(appdomain_allocator);
const PUBLIC_CLASS_PROPERTIES: &[(&str, Option<NativeMethodImpl>, Option<NativeMethodImpl>)] =
&[("currentDomain", Some(current_domain), None)];
write.define_builtin_class_properties(
mc,
activation.avm2().public_namespace,
PUBLIC_CLASS_PROPERTIES,
);
const PUBLIC_INSTANCE_PROPERTIES: &[(
&str,
Option<NativeMethodImpl>,
Option<NativeMethodImpl>,
)] = &[
("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
}

View File

@ -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"

View File

@ -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"

View File

@ -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<Object<'gc>> + 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<Domain<'gc>> {
None

View File

@ -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<Object<'gc>, 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<Value<'gc>, Error<'gc>> {
let this: Object<'gc> = Object::DomainObject(*self);

View File

@ -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);

View File

@ -479,13 +479,6 @@ pub enum ActionType<'gc> {
method: &'static str,
args: Vec<Avm1Value<'gc>>,
},
/// An AVM2 callable, e.g. a frame script or event handler.
Callable2 {
callable: Avm2Object<'gc>,
reciever: Option<Avm2Object<'gc>>,
args: Vec<Avm2Value<'gc>>,
},
}
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(),
}
}
}

View File

@ -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!(

View File

@ -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(),

View File

@ -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,

View File

@ -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<Avm2Value> = args
.into_iter()
.map(|v| v.into_avm2(&mut activation))

View File

@ -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()))?

View File

@ -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