From d46b2c39ca2fa8bdf0cf54569dc06973b00a71c6 Mon Sep 17 00:00:00 2001 From: EmperorBale Date: Thu, 8 Sep 2022 16:11:49 -0700 Subject: [PATCH] avm2: Share scope stack across AVM2 --- core/src/avm2.rs | 6 +++ core/src/avm2/activation.rs | 40 +++++++++---------- core/src/avm2/scope.rs | 77 ++++++++++--------------------------- 3 files changed, 46 insertions(+), 77 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 91862251c..c8a754138 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -63,6 +63,8 @@ pub use crate::avm2::object::{ pub use crate::avm2::qname::QName; pub use crate::avm2::value::Value; +use self::scope::Scope; + const BROADCAST_WHITELIST: [&str; 3] = ["enterFrame", "exitFrame", "frameConstructed"]; /// The state of an AVM2 interpreter. @@ -72,6 +74,9 @@ pub struct Avm2<'gc> { /// Values currently present on the operand stack. stack: Vec>, + /// Scopes currently present of the scope stack. + scope_stack: Vec>, + /// The current call stack of the player. call_stack: GcCell<'gc, CallStack<'gc>>, @@ -111,6 +116,7 @@ impl<'gc> Avm2<'gc> { Self { stack: Vec::new(), + scope_stack: Vec::new(), call_stack: GcCell::allocate(mc, CallStack::new()), globals, system_classes: None, diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 4ee9dbbf5..79e688bee 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -9,7 +9,7 @@ use crate::avm2::object::{ ArrayObject, ByteArrayObject, ClassObject, FunctionObject, NamespaceObject, ScriptObject, }; use crate::avm2::object::{Object, TObject}; -use crate::avm2::scope::{Scope, ScopeChain, ScopeStack}; +use crate::avm2::scope::{search_scope_stack, Scope, ScopeChain}; use crate::avm2::script::Script; use crate::avm2::value::Value; use crate::avm2::Multiname; @@ -100,9 +100,6 @@ pub struct Activation<'a, 'gc: 'a, 'gc_context: 'a> { #[allow(dead_code)] return_value: Option>, - /// The current scope stack. - scope_stack: ScopeStack<'gc>, - /// This represents the outer scope of the method that is executing. /// /// The outer scope gives an activation access to the "outer world", including @@ -174,13 +171,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { actions_since_timeout_check: 0, local_registers, return_value: None, - scope_stack: ScopeStack::new(), outer: ScopeChain::new(context.avm2.globals), caller_domain: context.avm2.globals, subclass_object: None, activation_class: None, stack_depth: context.avm2.stack.len(), - scope_depth: 0, + scope_depth: context.avm2.scope_stack.len(), max_stack_size: 0, max_scope_size: 0, context, @@ -219,13 +215,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { actions_since_timeout_check: 0, local_registers, return_value: None, - scope_stack: ScopeStack::new(), outer: ScopeChain::new(domain), caller_domain: domain, subclass_object: None, activation_class: None, stack_depth: context.avm2.stack.len(), - scope_depth: 0, + scope_depth: context.avm2.scope_stack.len(), max_stack_size: max_stack as usize, max_scope_size: max_scope as usize, context, @@ -239,7 +234,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { ) -> Result>, Error<'gc>> { let outer_scope = self.outer; - if let Some(obj) = self.scope_stack.find(name, outer_scope.is_empty())? { + if let Some(obj) = search_scope_stack(self.scope_frame(), name, outer_scope.is_empty())? { Ok(Some(obj)) } else if let Some(obj) = outer_scope.find(name, self)? { Ok(Some(obj)) @@ -255,7 +250,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { ) -> Result>, Error<'gc>> { let outer_scope = self.outer; - if let Some(obj) = self.scope_stack.find(name, outer_scope.is_empty())? { + if let Some(obj) = search_scope_stack(self.scope_frame(), name, outer_scope.is_empty())? { Ok(Some(obj.get_property(name, self)?)) } else if let Some(result) = outer_scope.resolve(name, self)? { Ok(Some(result)) @@ -455,13 +450,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { actions_since_timeout_check: 0, local_registers, return_value: None, - scope_stack: ScopeStack::new(), outer, caller_domain: outer.domain(), subclass_object, activation_class, stack_depth: context.avm2.stack.len(), - scope_depth: 0, + scope_depth: context.avm2.scope_stack.len(), max_stack_size: body.max_stack as usize, max_scope_size: (body.max_scope_depth - body.init_scope_depth) as usize, context, @@ -542,13 +536,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { actions_since_timeout_check: 0, local_registers, return_value: None, - scope_stack: ScopeStack::new(), outer, caller_domain, subclass_object, activation_class: None, stack_depth: context.avm2.stack.len(), - scope_depth: 0, + scope_depth: context.avm2.scope_stack.len(), max_stack_size: 0, max_scope_size: 0, context, @@ -625,7 +618,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { /// activation's scope stack with the outer scope. pub fn create_scopechain(&self) -> ScopeChain<'gc> { self.outer - .chain(self.context.gc_context, self.scope_stack.scopes()) + .chain(self.context.gc_context, self.scope_frame()) } /// Returns the domain of the original AS3 caller. @@ -645,7 +638,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let outer_scope = self.outer; outer_scope .get(0) - .or_else(|| self.scope_stack.get(0)) + .or_else(|| self.scope_frame().first().copied()) .map(|scope| scope.values()) } @@ -667,6 +660,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { self.subclass_object } + pub fn scope_frame(&self) -> &[Scope<'gc>] { + &self.context.avm2.scope_stack[self.scope_depth..] + } + /// Get the superclass of the class that defined the currently-executing /// method, if it exists. /// @@ -868,7 +865,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { if matches { self.context.avm2.push(error); - self.scope_stack.clear(); + let scope_depth = self.scope_depth; + self.avm2().scope_stack.truncate(scope_depth); reader.seek_absolute(full_data, e.target_offset as usize); return Ok(FrameControl::Continue); } @@ -1659,20 +1657,20 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { fn op_push_scope(&mut self) -> Result, Error<'gc>> { let object = self.context.avm2.pop().coerce_to_object(self)?; - self.scope_stack.push(Scope::new(object)); + self.avm2().scope_stack.push(Scope::new(object)); Ok(FrameControl::Continue) } fn op_push_with(&mut self) -> Result, Error<'gc>> { let object = self.context.avm2.pop().coerce_to_object(self)?; - self.scope_stack.push(Scope::new_with(object)); + self.avm2().scope_stack.push(Scope::new_with(object)); Ok(FrameControl::Continue) } fn op_pop_scope(&mut self) -> Result, Error<'gc>> { - self.scope_stack.pop(); + self.avm2().scope_stack.pop(); Ok(FrameControl::Continue) } @@ -1690,7 +1688,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { } fn op_get_scope_object(&mut self, index: u8) -> Result, Error<'gc>> { - let scope = self.scope_stack.get(index as usize); + let scope = self.avm2().scope_stack.get(index as usize).copied(); if let Some(scope) = scope { self.context.avm2.push(scope.values()); diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index b983b5717..2fe6424c3 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -162,65 +162,30 @@ impl<'gc> ScopeChain<'gc> { } } -/// Represents a ScopeStack to be used in the AVM2 activation. A new ScopeStack should be created -/// per activation. A ScopeStack allows mutations, such as pushing new scopes, or popping scopes off. -/// A ScopeStack should only ever be accessed by the activation it was created in. -#[derive(Debug, Collect, Clone)] -#[collect(no_drop)] -pub struct ScopeStack<'gc> { - scopes: Vec>, -} +/// Searches for a scope in the scope stack by a multiname. +/// +/// The `global` parameter indicates whether we are on global$init (script initializer). +/// When the `global` parameter is true, the scope at depth 0 is considered the global scope, and is +/// searched for dynamic properties. +#[allow(clippy::collapsible_if)] +pub fn search_scope_stack<'gc>( + scopes: &[Scope<'gc>], + multiname: &Multiname<'gc>, + global: bool, +) -> Result>, Error<'gc>> { + for (depth, scope) in scopes.iter().enumerate().rev() { + let values = scope.values(); -impl<'gc> ScopeStack<'gc> { - pub fn new() -> Self { - Self { scopes: Vec::new() } - } - - pub fn clear(&mut self) { - self.scopes.clear(); - } - - pub fn push(&mut self, scope: Scope<'gc>) { - self.scopes.push(scope); - } - - pub fn pop(&mut self) -> Option> { - self.scopes.pop() - } - - pub fn get(&self, index: usize) -> Option> { - self.scopes.get(index).cloned() - } - - pub fn scopes(&self) -> &[Scope<'gc>] { - &self.scopes - } - - /// Searches for a scope in this ScopeStack by a multiname. - /// - /// The `global` parameter indicates whether we are on global$init (script initializer). - /// When the `global` parameter is true, the scope at depth 0 is considered the global scope, and is - /// searched for dynamic properties. - #[allow(clippy::collapsible_if)] - pub fn find( - &self, - multiname: &Multiname<'gc>, - global: bool, - ) -> Result>, Error<'gc>> { - for (depth, scope) in self.scopes.iter().enumerate().rev() { - let values = scope.values(); - - if values.has_trait(multiname) { + if values.has_trait(multiname) { + return Ok(Some(values)); + } else if scope.with() || (global && depth == 0) { + // We search the dynamic properties if either conditions are met: + // 1. Scope is a `with` scope + // 2. We are at depth 0 AND we are at global$init (script initializer). + if values.has_own_property(multiname) { return Ok(Some(values)); - } else if scope.with() || (global && depth == 0) { - // We search the dynamic properties if either conditions are met: - // 1. Scope is a `with` scope - // 2. We are at depth 0 AND we are at global$init (script initializer). - if values.has_own_property(multiname) { - return Ok(Some(values)); - } } } - Ok(None) } + Ok(None) }