Stack continuations can now directly manipulate the return value of an ActionScript function.

This commit is contained in:
David Wendt 2019-10-17 14:40:55 -04:00
parent 8485e919db
commit a95861d596
3 changed files with 41 additions and 23 deletions

View File

@ -23,6 +23,7 @@ pub mod object;
mod scope; mod scope;
mod value; mod value;
mod scope_continuation;
#[cfg(test)] #[cfg(test)]
mod test_utils; mod test_utils;
@ -248,16 +249,23 @@ impl<'gc> Avm1<'gc> {
/// This function will run functions scheduled on the current stack frame's /// This function will run functions scheduled on the current stack frame's
/// `and_then` field. This is intended to allow the runtime to act on the /// `and_then` field. This is intended to allow the runtime to act on the
/// results of AVM code to implement things like custom getters/setters. /// results of AVM code to implement things like custom getters/setters.
fn retire_stack_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) { /// Due to this, this function is given the intended return value and may
/// return a different one.
fn retire_stack_frame(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
mut return_value: Value<'gc>,
) -> Result<Value<'gc>, Error> {
if let Some(frame) = self.current_stack_frame() { if let Some(frame) = self.current_stack_frame() {
let this = frame.read().this_cell(); let this = frame.read().this_cell();
if let Some(func) = frame.read().get_then_func() { if let Some(func) = frame.read().get_then_func() {
let ret_args = vec![self.stack.last().unwrap().to_owned()]; return_value = func(self, context, this, return_value)?;
func(self, context, this, &ret_args);
} }
} }
self.stack_frames.pop(); self.stack_frames.pop();
Ok(return_value)
} }
/// Execute the AVM stack until it is exhausted. /// Execute the AVM stack until it is exhausted.
@ -285,16 +293,15 @@ 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.
if self let impl_return = self
.current_stack_frame() .current_stack_frame()
.unwrap() .unwrap()
.read() .read()
.can_implicit_return() .can_implicit_return();
{ let return_value = self.retire_stack_frame(context, Value::Undefined)?;
self.push(Value::Undefined); if impl_return {
self.push(return_value);
} }
self.retire_stack_frame(context);
} 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),
@ -418,16 +425,15 @@ impl<'gc> Avm1<'gc> {
} }
} else { } else {
//The explicit end opcode was encountered so return here //The explicit end opcode was encountered so return here
if self let impl_return = self
.current_stack_frame() .current_stack_frame()
.unwrap() .unwrap()
.read() .read()
.can_implicit_return() .can_implicit_return();
{ let return_value = self.retire_stack_frame(context, Value::Undefined)?;
self.push(Value::Undefined); if impl_return {
self.push(return_value);
} }
self.retire_stack_frame(context);
} }
Ok(()) Ok(())
@ -1499,11 +1505,11 @@ impl<'gc> Avm1<'gc> {
} }
fn action_return(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { fn action_return(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let result = self.pop()?; let return_value = self.pop()?;
let return_value = self.retire_stack_frame(context, return_value)?;
if self.stack_frames.len() > 1 { if !self.stack_frames.is_empty() {
self.push(result); self.push(return_value);
self.retire_stack_frame(context);
} }
Ok(()) Ok(())

View File

@ -1,8 +1,8 @@
//! Activation records //! Activation records
use crate::avm1::function::NativeFunction;
use crate::avm1::object::Object; use crate::avm1::object::Object;
use crate::avm1::scope::Scope; use crate::avm1::scope::Scope;
use crate::avm1::scope_continuation::ScopeContinuation;
use crate::avm1::{Avm1, Value}; use crate::avm1::{Avm1, Value};
use crate::context::UpdateContext; use crate::context::UpdateContext;
use crate::tag_utils::SwfSlice; use crate::tag_utils::SwfSlice;
@ -95,7 +95,7 @@ pub struct Activation<'gc> {
/// ///
/// This facility exists primarily to allow native code to handle the result /// This facility exists primarily to allow native code to handle the result
/// of an AVM function call. /// of an AVM function call.
then_func: Option<NativeFunction<'gc>>, then_func: Option<ScopeContinuation<'gc>>,
} }
unsafe impl<'gc> gc_arena::Collect for Activation<'gc> { unsafe impl<'gc> gc_arena::Collect for Activation<'gc> {
@ -330,14 +330,14 @@ impl<'gc> Activation<'gc> {
} }
/// Return the function scheduled to be executed, if any. /// Return the function scheduled to be executed, if any.
pub fn get_then_func(&self) -> Option<NativeFunction<'gc>> { pub fn get_then_func(&self) -> Option<ScopeContinuation<'gc>> {
self.then_func self.then_func
} }
/// Schedule a native function to execute when this stack frame returns. /// Schedule a native function to execute when this stack frame returns.
/// ///
/// Only one native function may be scheduled per activation. /// Only one native function may be scheduled per activation.
pub fn and_then(&mut self, func: NativeFunction<'gc>) { pub fn and_then(&mut self, func: ScopeContinuation<'gc>) {
self.then_func = Some(func); self.then_func = Some(func);
} }
} }

View File

@ -0,0 +1,12 @@
//! GC-compatible scope continuations
use crate::avm1::{Avm1, Error, Object, Value};
use crate::context::UpdateContext;
use gc_arena::GcCell;
pub type ScopeContinuation<'gc> = fn(
&mut Avm1<'gc>,
&mut UpdateContext<'_, 'gc, '_>,
GcCell<'gc, Object<'gc>>,
Value<'gc>,
) -> Result<Value<'gc>, Error>;