diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 1f3b284f9..7871c68ca 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,6 +1,7 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::Activation; +use crate::avm2::object::TObject; use crate::avm2::value::Value; use crate::context::UpdateContext; use crate::tag_utils::SwfSlice; @@ -84,6 +85,36 @@ impl<'gc> Avm2<'gc> { self.stack_frames.push(frame) } + /// Destroy the current stack frame (if there is one). + /// + /// The given return value will be pushed on the stack if there is a + /// function to return it to. Otherwise, it will be discarded. + /// + /// NOTE: This means that if you are starting a brand new AVM stack just to + /// get it's return value, you won't get that value. Instead, retain a cell + /// referencing the oldest activation frame and use that to retrieve the + /// return value. + fn retire_stack_frame( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + return_value: Value<'gc>, + ) -> Result<(), Error> { + if let Some(frame) = self.current_stack_frame() { + self.stack_frames.pop(); + + let can_return = !self.stack_frames.is_empty(); + if can_return { + frame + .write(context.gc_context) + .set_return_value(return_value.clone()); + + self.push(return_value); + } + } + + Ok(()) + } + /// Perform some action with the current stack frame's reader. /// /// This function constructs a reader based off the current stack frame's @@ -166,6 +197,37 @@ impl<'gc> Avm2<'gc> { Ok(()) } + /// Execute the AVM stack until a given activation returns. + pub fn run_current_frame( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + stop_frame: GcCell<'gc, Activation<'gc>>, + ) -> Result<(), Error> { + let mut stop_frame_id = None; + for (index, frame) in self.stack_frames.iter().enumerate() { + if GcCell::ptr_eq(stop_frame, *frame) { + stop_frame_id = Some(index); + } + } + + if let Some(stop_frame_id) = stop_frame_id { + while self + .stack_frames + .get(stop_frame_id) + .map(|fr| GcCell::ptr_eq(stop_frame, *fr)) + .unwrap_or(false) + { + self.with_current_reader_mut(context, |this, r, context| { + this.do_next_opcode(context, r) + })?; + } + + Ok(()) + } else { + Err("Attempted to run a frame not on the current interpreter stack".into()) + } + } + /// Push a value onto the operand stack. fn push(&mut self, value: impl Into>) { let value = value.into(); @@ -264,6 +326,9 @@ impl<'gc> Avm2<'gc> { Op::PushUndefined => self.op_push_undefined(), Op::GetLocal { index } => self.op_get_local(index), Op::SetLocal { index } => self.op_set_local(context, index), + Op::Call { num_args } => self.op_call(context, num_args), + Op::ReturnValue => self.op_return_value(context), + Op::ReturnVoid => self.op_return_void(context), _ => self.unknown_op(op), }; @@ -349,4 +414,31 @@ impl<'gc> Avm2<'gc> { let value = self.pop(); self.set_register_value(context, register_index, value) } + + fn op_call( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + arg_count: u32, + ) -> Result<(), Error> { + let function = self.pop().as_object()?; + let receiver = self.pop().as_object()?; + let mut args = Vec::new(); + for _ in 0..arg_count { + args.push(self.pop()); + } + + function.call(receiver, &args, self, context)?.push(self); + + Ok(()) + } + + fn op_return_value(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { + let return_value = self.pop(); + + self.retire_stack_frame(context, return_value) + } + + fn op_return_void(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { + self.retire_stack_frame(context, Value::Undefined) + } } diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 031b35022..be8f83214 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -72,6 +72,12 @@ pub struct Activation<'gc> { /// All activations have local registers, but it is possible for multiple /// activations (such as a rescope) to execute from the same register set. local_registers: GcCell<'gc, RegisterSet<'gc>>, + + /// What was returned from the function. + /// + /// A return value of `None` indicates that the called function is still + /// executing. Functions that do not return instead return `Undefined`. + return_value: Option>, } impl<'gc> Activation<'gc> { @@ -97,6 +103,7 @@ impl<'gc> Activation<'gc> { context.gc_context, RegisterSet::new(method_body.num_locals), ), + return_value: None, }) } @@ -156,4 +163,15 @@ impl<'gc> Activation<'gc> { false } } + + /// Retrieve the return value from a completed activation, if the function + /// has already returned. + pub fn return_value(&self) -> Option> { + self.return_value.clone() + } + + /// Set the return value. + pub fn set_return_value(&mut self, value: Value<'gc>) { + self.return_value = Some(value); + } } diff --git a/core/src/avm2/return_value.rs b/core/src/avm2/return_value.rs index 58e138878..9246eabe4 100644 --- a/core/src/avm2/return_value.rs +++ b/core/src/avm2/return_value.rs @@ -65,7 +65,7 @@ impl<'gc> ReturnValue<'gc> { use ReturnValue::*; match self { - Immediate(val) => {} //TODO: avm.push(val) + Immediate(val) => avm.push(val), ResultOf(_frame) => {} }; } @@ -82,8 +82,9 @@ impl<'gc> ReturnValue<'gc> { match self { Immediate(val) => Ok(val), ResultOf(frame) => { - //TODO: Execute AVM frame, pop return value - Ok(Value::Undefined) + avm.run_current_frame(context, frame)?; + + Ok(avm.pop()) } } } diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 6451b7c58..bc1d1020f 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -1,6 +1,7 @@ //! AVM2 values use crate::avm2::object::Object; +use crate::avm2::Error; use gc_arena::Collect; /// An AVM2 value. @@ -124,3 +125,13 @@ impl PartialEq for Value<'_> { } } } + +impl<'gc> Value<'gc> { + pub fn as_object(&self) -> Result, Error> { + if let Value::Object(object) = self { + Ok(*object) + } else { + Err(format!("Expected Object, found {:?}", self).into()) + } + } +}