avm2: Share scope stack across AVM2
This commit is contained in:
parent
16805a350b
commit
d46b2c39ca
|
@ -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<Value<'gc>>,
|
||||
|
||||
/// Scopes currently present of the scope stack.
|
||||
scope_stack: Vec<Scope<'gc>>,
|
||||
|
||||
/// 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,
|
||||
|
|
|
@ -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<Value<'gc>>,
|
||||
|
||||
/// 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<Option<Object<'gc>>, 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<Option<Value<'gc>>, 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<FrameControl<'gc>, 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<FrameControl<'gc>, 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<FrameControl<'gc>, 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<FrameControl<'gc>, 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());
|
||||
|
|
|
@ -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<Scope<'gc>>,
|
||||
}
|
||||
/// 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<Option<Object<'gc>>, 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<Scope<'gc>> {
|
||||
self.scopes.pop()
|
||||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Option<Scope<'gc>> {
|
||||
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<Option<Object<'gc>>, 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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue