avm2: Add support for pushwith

This commit is contained in:
EmperorBale 2021-09-07 15:17:38 -07:00 committed by kmeisthax
parent 388fdbd513
commit 7e251cfe05
3 changed files with 97 additions and 52 deletions

View File

@ -9,7 +9,7 @@ use crate::avm2::object::{
ArrayObject, ByteArrayObject, ClassObject, FunctionObject, NamespaceObject, ScriptObject, ArrayObject, ByteArrayObject, ClassObject, FunctionObject, NamespaceObject, ScriptObject,
}; };
use crate::avm2::object::{Object, TObject}; 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::script::Script;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::{value, Avm2, Error}; 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<Option<Object<'gc>>, Error> { pub fn find_definition(&mut self, name: &Multiname<'gc>) -> Result<Option<Object<'gc>>, Error> {
let outer_scope = self.outer; 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)) Ok(Some(obj))
} else if let Some(obj) = outer_scope.find(name, self)? { } else if let Some(obj) = outer_scope.find(name, self)? {
Ok(Some(obj)) 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( pub fn resolve_definition(
&mut self, &mut self,
name: &Multiname<'gc>, name: &Multiname<'gc>,
) -> Result<Option<Value<'gc>>, Error> { ) -> Result<Option<Value<'gc>>, Error> {
let outer_scope = self.outer; 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.get_property(obj, &name, self)?)) Ok(Some(obj.get_property(obj, name, self)?))
} else if let Some(obj) = outer_scope.resolve(name, self)? { } else if let Some(result) = outer_scope.resolve(name, self)? {
Ok(Some(obj)) Ok(Some(result))
} else { } else {
Ok(None) Ok(None)
} }
@ -586,7 +586,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
pub fn global_scope(&self) -> Option<Object<'gc>> { pub fn global_scope(&self) -> Option<Object<'gc>> {
let outer_scope = self.outer; 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> { 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<FrameControl<'gc>, Error> { fn op_push_scope(&mut self) -> Result<FrameControl<'gc>, Error> {
let object = self.context.avm2.pop().coerce_to_object(self)?; 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) Ok(FrameControl::Continue)
} }
fn op_push_with(&mut self) -> Result<FrameControl<'gc>, Error> { fn op_push_with(&mut self) -> Result<FrameControl<'gc>, Error> {
let object = self.context.avm2.pop().coerce_to_object(self)?; 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) 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); let scope = self.outer.get(index as usize);
if let Some(scope) = scope { if let Some(scope) = scope {
self.context.avm2.push(scope); self.context.avm2.push(scope.values());
} else { } else {
self.context.avm2.push(Value::Undefined); 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); let scope = self.scope_stack.get(index as usize);
if let Some(scope) = scope { if let Some(scope) = scope {
self.context.avm2.push(scope); self.context.avm2.push(scope.values());
} else { } else {
self.context.avm2.push(Value::Undefined); self.context.avm2.push(Value::Undefined);
}; };

View File

@ -6,7 +6,7 @@ use crate::avm2::domain::Domain;
use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::method::{Method, NativeMethodImpl};
use crate::avm2::names::{Namespace, QName}; use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{ClassObject, FunctionObject, Object, ScriptObject, TObject}; 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::script::Script;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
@ -421,7 +421,7 @@ pub fn load_player_globals<'gc>(
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let globals = ScriptObject::bare_object(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); let script = Script::empty_script(mc, globals, domain);
// public / root package // public / root package

View File

@ -9,18 +9,41 @@ use crate::avm2::Error;
use gc_arena::{Collect, Gc, MutationContext}; use gc_arena::{Collect, Gc, MutationContext};
use std::ops::Deref; use std::ops::Deref;
/// Finds a scope containing a definition /// Represents a Scope that can be on either a ScopeChain or local ScopeStack.
fn find_scopes<'gc>( #[derive(Debug, Collect, Clone, Copy)]
scopes: &[Object<'gc>], #[collect(no_drop)]
name: &Multiname<'gc>, pub struct Scope<'gc> {
) -> Result<Option<Object<'gc>>, Error> { /// The underlying object of this Scope
for scope in scopes.iter().rev() { values: Object<'gc>,
if let Some(_) = scope.resolve_multiname(name)? {
return Ok(Some(*scope)); /// 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. /// A ScopeChain "chains" scopes together.
@ -39,7 +62,7 @@ fn find_scopes<'gc>(
#[derive(Debug, Collect, Clone, Copy)] #[derive(Debug, Collect, Clone, Copy)]
#[collect(no_drop)] #[collect(no_drop)]
pub struct ScopeChain<'gc> { pub struct ScopeChain<'gc> {
scopes: Option<Gc<'gc, Vec<Object<'gc>>>>, scopes: Option<Gc<'gc, Vec<Scope<'gc>>>>,
domain: Domain<'gc>, domain: Domain<'gc>,
} }
@ -53,7 +76,7 @@ impl<'gc> ScopeChain<'gc> {
} }
/// Creates a new ScopeChain by chaining new scopes on top of this ScopeChain /// 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 new_scopes.is_empty() {
// If we are not actually adding any new scopes, we don't need to do anything. // If we are not actually adding any new scopes, we don't need to do anything.
return *self; return *self;
@ -81,11 +104,15 @@ impl<'gc> ScopeChain<'gc> {
} }
} }
pub fn get(&self, index: usize) -> Option<Object<'gc>> { pub fn get(&self, index: usize) -> Option<Scope<'gc>> {
self.scopes self.scopes
.and_then(|scopes| scopes.deref().get(index).cloned()) .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. /// Returns the domain associated with this ScopeChain.
pub fn domain(&self) -> Domain<'gc> { pub fn domain(&self) -> Domain<'gc> {
self.domain self.domain
@ -98,8 +125,19 @@ impl<'gc> ScopeChain<'gc> {
) -> Result<Option<Object<'gc>>, Error> { ) -> Result<Option<Object<'gc>>, Error> {
// First search our scopes // First search our scopes
if let Some(scopes) = self.scopes { if let Some(scopes) = self.scopes {
if let Some(scope) = find_scopes(scopes.deref(), name)? { for (depth, scope) in scopes.iter().enumerate().rev() {
return Ok(Some(scope)); 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. // That didn't work... let's try searching the domain now.
@ -114,22 +152,8 @@ impl<'gc> ScopeChain<'gc> {
name: &Multiname<'gc>, name: &Multiname<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Option<Value<'gc>>, Error> { ) -> Result<Option<Value<'gc>>, Error> {
// First search our scopes if let Some(object) = self.find(name, activation)? {
if let Some(scopes) = self.scopes { Ok(Some(object.get_property(object, name, activation)?))
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,
)?))
} else { } else {
Ok(None) Ok(None)
} }
@ -142,7 +166,7 @@ impl<'gc> ScopeChain<'gc> {
#[derive(Debug, Collect, Clone)] #[derive(Debug, Collect, Clone)]
#[collect(no_drop)] #[collect(no_drop)]
pub struct ScopeStack<'gc> { pub struct ScopeStack<'gc> {
scopes: Vec<Object<'gc>>, scopes: Vec<Scope<'gc>>,
} }
impl<'gc> ScopeStack<'gc> { impl<'gc> ScopeStack<'gc> {
@ -150,23 +174,41 @@ impl<'gc> ScopeStack<'gc> {
Self { scopes: Vec::new() } Self { scopes: Vec::new() }
} }
pub fn push(&mut self, scope: Object<'gc>) { pub fn push(&mut self, scope: Scope<'gc>) {
self.scopes.push(scope); self.scopes.push(scope);
} }
pub fn pop(&mut self) -> Option<Object<'gc>> { pub fn pop(&mut self) -> Option<Scope<'gc>> {
self.scopes.pop() self.scopes.pop()
} }
pub fn get(&self, index: usize) -> Option<Object<'gc>> { pub fn get(&self, index: usize) -> Option<Scope<'gc>> {
self.scopes.get(index).cloned() self.scopes.get(index).cloned()
} }
pub fn scopes(&self) -> &[Object<'gc>] { pub fn scopes(&self) -> &[Scope<'gc>] {
&self.scopes &self.scopes
} }
pub fn find(&self, name: &Multiname<'gc>) -> Result<Option<Object<'gc>>, Error> { /// Searches for a scope in this ScopeStack by a multiname.
find_scopes(&self.scopes, name) ///
/// 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<Option<Object<'gc>>, 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)
} }
} }