diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 969a77575..13c0ada7a 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::{ScopeChain, ScopeStack}; +use crate::avm2::scope::{Scope, ScopeChain, ScopeStack}; use crate::avm2::script::Script; use crate::avm2::value::Value; use crate::avm2::{value, Avm2, Error}; @@ -206,11 +206,11 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { }) } - /// Finds an object on either the current or outer scope that contains a property. + /// Finds an object on either the current or outer scope of this activation by definition. pub fn find_definition(&mut self, name: &Multiname<'gc>) -> Result>, Error> { let outer_scope = self.outer; - if let Some(obj) = self.scope_stack.find(name)? { + if let Some(obj) = self.scope_stack.find(name, outer_scope.is_empty())? { Ok(Some(obj)) } else if let Some(obj) = outer_scope.find(name, self)? { Ok(Some(obj)) @@ -219,17 +219,17 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { } } - /// Resolves a property using either the current or outer scope. + /// Resolves a definition using either the current or outer scope of this activation. pub fn resolve_definition( &mut self, name: &Multiname<'gc>, ) -> Result>, Error> { let outer_scope = self.outer; - if let Some(obj) = self.scope_stack.find(name)? { - Ok(Some(obj.get_property(obj, &name, self)?)) - } else if let Some(obj) = outer_scope.resolve(name, self)? { - Ok(Some(obj)) + if let Some(obj) = self.scope_stack.find(name, outer_scope.is_empty())? { + Ok(Some(obj.get_property(obj, name, self)?)) + } else if let Some(result) = outer_scope.resolve(name, self)? { + Ok(Some(result)) } else { Ok(None) } @@ -586,7 +586,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { pub fn global_scope(&self) -> Option> { let outer_scope = self.outer; - outer_scope.get(0).or_else(|| self.scope_stack.get(0)) + outer_scope + .get(0) + .or_else(|| self.scope_stack.get(0)) + .map(|scope| scope.values()) } pub fn avm2(&mut self) -> &mut Avm2<'gc> { @@ -1463,14 +1466,14 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { fn op_push_scope(&mut self) -> Result, Error> { let object = self.context.avm2.pop().coerce_to_object(self)?; - self.scope_stack.push(object); + self.scope_stack.push(Scope::new(object)); Ok(FrameControl::Continue) } fn op_push_with(&mut self) -> Result, Error> { let object = self.context.avm2.pop().coerce_to_object(self)?; - self.scope_stack.push(object); // TEMP + self.scope_stack.push(Scope::new_with(object)); Ok(FrameControl::Continue) } @@ -1485,7 +1488,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let scope = self.outer.get(index as usize); if let Some(scope) = scope { - self.context.avm2.push(scope); + self.context.avm2.push(scope.values()); } else { self.context.avm2.push(Value::Undefined); }; @@ -1497,7 +1500,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let scope = self.scope_stack.get(index as usize); if let Some(scope) = scope { - self.context.avm2.push(scope); + self.context.avm2.push(scope.values()); } else { self.context.avm2.push(Value::Undefined); }; diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index f18f8d9d3..0fc5405f5 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -6,7 +6,7 @@ use crate::avm2::domain::Domain; use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{ClassObject, FunctionObject, Object, ScriptObject, TObject}; -use crate::avm2::scope::ScopeChain; +use crate::avm2::scope::{Scope, ScopeChain}; use crate::avm2::script::Script; use crate::avm2::value::Value; use crate::avm2::Error; @@ -421,7 +421,7 @@ pub fn load_player_globals<'gc>( let mc = activation.context.gc_context; let globals = ScriptObject::bare_object(activation.context.gc_context); - let gs = ScopeChain::new(domain).chain(mc, &[globals]); + let gs = ScopeChain::new(domain).chain(mc, &[Scope::new(globals)]); let script = Script::empty_script(mc, globals, domain); // public / root package diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index 28a32be50..ea6f977cb 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -9,18 +9,41 @@ use crate::avm2::Error; use gc_arena::{Collect, Gc, MutationContext}; use std::ops::Deref; -/// Finds a scope containing a definition -fn find_scopes<'gc>( - scopes: &[Object<'gc>], - name: &Multiname<'gc>, -) -> Result>, Error> { - for scope in scopes.iter().rev() { - if let Some(_) = scope.resolve_multiname(name)? { - return Ok(Some(*scope)); +/// Represents a Scope that can be on either a ScopeChain or local ScopeStack. +#[derive(Debug, Collect, Clone, Copy)] +#[collect(no_drop)] +pub struct Scope<'gc> { + /// The underlying object of this Scope + values: Object<'gc>, + + /// Indicates whether or not this is a `with` scope. + /// + /// A `with` scope allows searching the dynamic properties of + /// this scope. + with: bool, +} + +impl<'gc> Scope<'gc> { + /// Creates a new regular Scope + pub fn new(values: Object<'gc>) -> Self { + Self { + values, + with: false, } } - Ok(None) + /// Creates a new `with` Scope + pub fn new_with(values: Object<'gc>) -> Self { + Self { values, with: true } + } + + pub fn with(&self) -> bool { + self.with + } + + pub fn values(&self) -> Object<'gc> { + self.values + } } /// A ScopeChain "chains" scopes together. @@ -39,7 +62,7 @@ fn find_scopes<'gc>( #[derive(Debug, Collect, Clone, Copy)] #[collect(no_drop)] pub struct ScopeChain<'gc> { - scopes: Option>>>, + scopes: Option>>>, domain: Domain<'gc>, } @@ -53,7 +76,7 @@ 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: &[Object<'gc>]) -> Self { + 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; @@ -81,11 +104,15 @@ impl<'gc> ScopeChain<'gc> { } } - pub fn get(&self, index: usize) -> Option> { + pub fn get(&self, index: usize) -> Option> { self.scopes .and_then(|scopes| scopes.deref().get(index).cloned()) } + pub fn is_empty(&self) -> bool { + self.scopes.map(|scopes| scopes.is_empty()).unwrap_or(true) + } + /// Returns the domain associated with this ScopeChain. pub fn domain(&self) -> Domain<'gc> { self.domain @@ -98,8 +125,19 @@ impl<'gc> ScopeChain<'gc> { ) -> Result>, Error> { // First search our scopes if let Some(scopes) = self.scopes { - if let Some(scope) = find_scopes(scopes.deref(), name)? { - return Ok(Some(scope)); + for (depth, scope) in scopes.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: + // 1. Scope is a `with` scope + // 2. We are at depth 0 (global scope) + // But no matter what, we always search traits first. + if values.has_trait(&qname)? + || ((scope.with() || depth == 0) && values.has_property(&qname)?) + { + return Ok(Some(values)); + } + } } } // That didn't work... let's try searching the domain now. @@ -114,22 +152,8 @@ impl<'gc> ScopeChain<'gc> { name: &Multiname<'gc>, activation: &mut Activation<'_, 'gc, '_>, ) -> Result>, Error> { - // First search our scopes - if let Some(scopes) = self.scopes { - if let Some(scope) = find_scopes(scopes.deref(), name)? { - return Ok(Some(scope.get_property(scope, &name, activation)?)); - } - } - - // That didn't work... let's try searching the domain now. - if let Some((qname, mut script)) = self.domain.get_defining_script(name)? { - let script_scope = script.globals(&mut activation.context)?; - - Ok(Some(script_scope.get_property( - script_scope, - &qname.into(), - activation, - )?)) + if let Some(object) = self.find(name, activation)? { + Ok(Some(object.get_property(object, name, activation)?)) } else { Ok(None) } @@ -142,7 +166,7 @@ impl<'gc> ScopeChain<'gc> { #[derive(Debug, Collect, Clone)] #[collect(no_drop)] pub struct ScopeStack<'gc> { - scopes: Vec>, + scopes: Vec>, } impl<'gc> ScopeStack<'gc> { @@ -150,23 +174,41 @@ impl<'gc> ScopeStack<'gc> { Self { scopes: Vec::new() } } - pub fn push(&mut self, scope: Object<'gc>) { + pub fn push(&mut self, scope: Scope<'gc>) { self.scopes.push(scope); } - pub fn pop(&mut self) -> Option> { + pub fn pop(&mut self) -> Option> { self.scopes.pop() } - pub fn get(&self, index: usize) -> Option> { + pub fn get(&self, index: usize) -> Option> { self.scopes.get(index).cloned() } - pub fn scopes(&self) -> &[Object<'gc>] { + pub fn scopes(&self) -> &[Scope<'gc>] { &self.scopes } - pub fn find(&self, name: &Multiname<'gc>) -> Result>, Error> { - find_scopes(&self.scopes, name) + /// 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. + pub fn find(&self, name: &Multiname<'gc>, global: bool) -> Result>, Error> { + for (depth, scope) in self.scopes.iter().enumerate().rev() { + let values = scope.values(); + if let Some(qname) = values.resolve_multiname(name)? { + // We search the dynamic properties if these 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_trait(&qname)? + || ((scope.with() || (global && depth == 0)) && values.has_property(&qname)?) + { + return Ok(Some(values)); + } + } + } + Ok(None) } }