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)
}
}