avm2: Add support for pushwith
This commit is contained in:
parent
388fdbd513
commit
7e251cfe05
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue