Fix a number of bugs preventing the with-scope test from working at all:

1. We no longer implicitly return Undefined unless we're specifically returning from a function (this also keeps actions from filling the stack with Undefined)
2. With scopes are now always inserted behind the current set of locals rather than overriding them
3. `ActionSubtract` now subtracts (instead of adds)
This commit is contained in:
David Wendt 2019-09-22 22:47:55 -04:00 committed by Mike Welsh
parent fc1ce7692b
commit 9b81a92516
3 changed files with 62 additions and 9 deletions

View File

@ -124,7 +124,7 @@ impl<'gc> Avm1<'gc> {
args: GcCell<'gc, Object<'gc>>, args: GcCell<'gc, Object<'gc>>,
action_context: &mut ActionContext<'_, 'gc, '_>) { action_context: &mut ActionContext<'_, 'gc, '_>) {
let child_scope = GcCell::allocate(action_context.gc_context, Scope::new_local_scope(avm1func.scope(), action_context.gc_context)); let child_scope = GcCell::allocate(action_context.gc_context, Scope::new_local_scope(avm1func.scope(), action_context.gc_context));
self.stack_frames.push(Activation::from_action(avm1func.swf_version(), avm1func.data(), child_scope, this, Some(args))); self.stack_frames.push(Activation::from_function(avm1func.swf_version(), avm1func.data(), child_scope, this, Some(args)));
} }
/// Retrieve the current AVM execution frame. /// Retrieve the current AVM execution frame.
@ -193,8 +193,11 @@ impl<'gc> Avm1<'gc> {
if reader.pos() >= (data.end - data.start) { if reader.pos() >= (data.end - data.start) {
//Executing beyond the end of a function constitutes an implicit return. //Executing beyond the end of a function constitutes an implicit return.
self.retire_stack_frame(); if self.current_stack_frame().unwrap().can_implicit_return() {
self.push(Value::Undefined); self.push(Value::Undefined);
}
self.retire_stack_frame();
} else if let Some(action) = reader.read_action()? { } else if let Some(action) = reader.read_action()? {
let result = match action { let result = match action {
Action::Add => self.action_add(context), Action::Add => self.action_add(context),
@ -312,10 +315,13 @@ impl<'gc> Avm1<'gc> {
} }
} else { } else {
//The explicit end opcode was encountered so return here //The explicit end opcode was encountered so return here
self.retire_stack_frame(); if self.current_stack_frame().unwrap().can_implicit_return() {
self.push(Value::Undefined); self.push(Value::Undefined);
} }
self.retire_stack_frame();
}
Ok(()) Ok(())
} }
@ -402,6 +408,7 @@ impl<'gc> Avm1<'gc> {
// ECMA-262 s. 11.6.1 // ECMA-262 s. 11.6.1
let a = self.pop()?; let a = self.pop()?;
let b = self.pop()?; let b = self.pop()?;
// TODO(Herschel): // TODO(Herschel):
if let Value::String(a) = a { if let Value::String(a) = a {
let mut s = b.into_string(); let mut s = b.into_string();
@ -1538,9 +1545,8 @@ impl<'gc> Avm1<'gc> {
fn action_with(&mut self, context: &mut ActionContext<'_, 'gc, '_>, actions: &[u8]) -> Result<(), Error> { fn action_with(&mut self, context: &mut ActionContext<'_, 'gc, '_>, actions: &[u8]) -> Result<(), Error> {
let object = self.pop()?.as_object()?; let object = self.pop()?.as_object()?;
let block = self.current_stack_frame().unwrap().data().to_subslice(actions).unwrap(); let block = self.current_stack_frame().unwrap().data().to_subslice(actions).unwrap();
let with_scope = Scope::new(self.current_stack_frame().unwrap().scope_cell(), scope::ScopeClass::With, object); let with_scope = Scope::new_with_scope(self.current_stack_frame().unwrap().scope_cell(), object, context.gc_context);
let scope_cell = GcCell::allocate(context.gc_context, with_scope); let new_activation = self.current_stack_frame().unwrap().to_rescope(block, with_scope);
let new_activation = self.current_stack_frame().unwrap().to_rescope(block, scope_cell);
self.stack_frames.push(new_activation); self.stack_frames.push(new_activation);
Ok(()) Ok(())
} }

View File

@ -31,7 +31,11 @@ pub struct Activation<'gc> {
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
/// The arguments this function was called by. /// The arguments this function was called by.
arguments: Option<GcCell<'gc, Object<'gc>>> arguments: Option<GcCell<'gc, Object<'gc>>>,
/// Indicates if this activation object represents a function or embedded
/// block (e.g. ActionWith).
is_function: bool
} }
unsafe impl<'gc> gc_arena::Collect for Activation<'gc> { unsafe impl<'gc> gc_arena::Collect for Activation<'gc> {
@ -51,7 +55,20 @@ impl<'gc> Activation<'gc> {
pc: 0, pc: 0,
scope: scope, scope: scope,
this: this, this: this,
arguments: arguments arguments: arguments,
is_function: false
}
}
pub fn from_function(swf_version: u8, code: SwfSlice, scope: GcCell<'gc, Scope<'gc>>, this: GcCell<'gc, Object<'gc>>, arguments: Option<GcCell<'gc, Object<'gc>>>) -> Activation<'gc> {
Activation {
swf_version: swf_version,
data: code,
pc: 0,
scope: scope,
this: this,
arguments: arguments,
is_function: true
} }
} }
@ -63,7 +80,8 @@ impl<'gc> Activation<'gc> {
pc: 0, pc: 0,
scope: scope, scope: scope,
this: self.this, this: self.this,
arguments: self.arguments arguments: self.arguments,
is_function: false
} }
} }
@ -113,6 +131,12 @@ impl<'gc> Activation<'gc> {
self.scope.clone() self.scope.clone()
} }
/// Indicates whether or not the end of this scope should be handled as an
/// implicit function return or the end of a block.
pub fn can_implicit_return(&self) -> bool {
self.is_function
}
/// Resolve a particular named local variable within this activation. /// Resolve a particular named local variable within this activation.
pub fn resolve(&self, name: &str) -> Value<'gc> { pub fn resolve(&self, name: &str) -> Value<'gc> {
if name == "this" { if name == "this" {

View File

@ -92,6 +92,29 @@ impl<'gc> Scope<'gc> {
} }
} }
/// Construct a with scope to be used as the scope during a with block.
///
/// A with block inserts the values of a particular object into the scope
/// of currently running code, while still maintaining the same local
/// scope. This requires some scope chain juggling.
pub fn new_with_scope(locals: GcCell<'gc, Self>,
with_object: GcCell<'gc, Object<'gc>>,
mc: MutationContext<'gc, '_>) -> GcCell<'gc, Self> {
let parent_scope = locals.read().parent.clone();
let local_values = locals.read().values.clone();
let with_scope = GcCell::allocate(mc, Scope {
parent: parent_scope,
class: ScopeClass::With,
values: with_object
});
GcCell::allocate(mc, Scope {
parent: Some(with_scope),
class: ScopeClass::Local,
values: local_values
})
}
/// Construct an arbitrary scope /// Construct an arbitrary scope
pub fn new(parent: GcCell<'gc, Self>, class: ScopeClass, with_object: GcCell<'gc, Object<'gc>>) -> Scope<'gc> { pub fn new(parent: GcCell<'gc, Self>, class: ScopeClass, with_object: GcCell<'gc, Object<'gc>>) -> Scope<'gc> {
Scope { Scope {