Give AVM1 the notion of a stack frame.

This necessitates the use of a copy of SWF data into the GC arena, along with unsafe (and possibly unsound) pointer manipulation to get the action reader to hold a GC pointer.
This commit is contained in:
David Wendt 2019-09-15 13:25:34 -06:00 committed by Mike Welsh
parent 693e791d75
commit 617b493733
3 changed files with 177 additions and 58 deletions

View File

@ -3,8 +3,9 @@ use crate::avm1::object::Object;
use crate::backend::navigator::NavigationMethod; use crate::backend::navigator::NavigationMethod;
use crate::prelude::*; use crate::prelude::*;
use gc_arena::{GcCell, MutationContext}; use gc_arena::{Gc, GcCell, MutationContext};
use rand::{rngs::SmallRng, Rng}; use rand::{rngs::SmallRng, Rng};
use std::slice;
use std::collections::HashMap; use std::collections::HashMap;
use swf::avm1::read::Reader; use swf::avm1::read::Reader;
@ -51,33 +52,115 @@ pub struct ActionContext<'a, 'gc, 'gc_context> {
pub navigator: &'a mut dyn crate::backend::navigator::NavigatorBackend, pub navigator: &'a mut dyn crate::backend::navigator::NavigatorBackend,
} }
pub struct Avm1<'gc> { /// Represents a single activation of a given AVM1 function or keyframe.
pub struct Avm1StackFrame<'gc> {
/// Represents the SWF version of a given function.
///
/// Certain AVM1 operations change behavior based on the version of the SWF
/// file they were defined in. For example, case sensitivity changes based
/// on the SWF version.
swf_version: u8, swf_version: u8,
/// Action data being executed by the reader below.
data: Gc<'gc, Vec<u8>>,
/// Represents the current location of the instruction stream this stack
/// frame is executing.
pc: Reader<'gc>,
/// The opcode stack values for the current stack frame.
stack: Vec<Value<'gc>>, stack: Vec<Value<'gc>>,
constant_pool: Vec<String>,
/// All defined local variables in this stack frame.
locals: HashMap<String, Value<'gc>>, locals: HashMap<String, Value<'gc>>,
}
unsafe impl<'gc> gc_arena::Collect for Avm1StackFrame<'gc> {
#[inline]
fn trace(&self, cc: gc_arena::CollectionContext) {
self.data.trace(cc);
self.stack.trace(cc);
self.locals.trace(cc);
}
}
impl<'gc> Avm1StackFrame<'gc> {
pub fn from_action(swf_version: u8, code: &[u8], mc: MutationContext<'gc, '_>) -> Avm1StackFrame<'gc> {
let data = Gc::allocate(mc, code.to_vec());
//TODO: Figure out how to get Reader to hold a GC pointer directly.
//If gc_arena moves data around this is completely unsound.
let evil_slice = unsafe {
slice::from_raw_parts(data.as_ptr(), data.len())
};
Avm1StackFrame {
swf_version: swf_version,
data: data,
pc: Reader::new(evil_slice, swf_version),
stack: Vec::new(),
locals: HashMap::new()
}
}
/// Returns the SWF version of the action or function being executed.
pub fn swf_version(&self) -> u8 {
self.swf_version
}
/// Returns the current ActionScript reader.
pub fn pc(&self) -> &Reader<'gc> {
&self.pc
}
/// Returns the current ActionScript reader for mutation.
pub fn pc_mut(&mut self) -> &mut Reader<'gc> {
&mut self.pc
}
/// Returns the value stack.
pub fn stack(&self) -> &Vec<Value<'gc>> {
&self.stack
}
/// Returns the value stack for mutation.
pub fn stack_mut(&mut self) -> &mut Vec<Value<'gc>> {
&mut self.stack
}
/// Returns AVM locals.
pub fn locals(&self) -> &HashMap<String, Value<'gc>> {
&self.locals
}
/// Returns AVM locals for mutation.
pub fn locals_mut(&mut self) -> &mut HashMap<String, Value<'gc>> {
&mut self.locals
}
}
pub struct Avm1<'gc> {
constant_pool: Vec<String>,
globals: GcCell<'gc, Object<'gc>>, globals: GcCell<'gc, Object<'gc>>,
stack_frames: Vec<Avm1StackFrame<'gc>>
} }
unsafe impl<'gc> gc_arena::Collect for Avm1<'gc> { unsafe impl<'gc> gc_arena::Collect for Avm1<'gc> {
#[inline] #[inline]
fn trace(&self, cc: gc_arena::CollectionContext) { fn trace(&self, cc: gc_arena::CollectionContext) {
self.stack.trace(cc);
self.locals.trace(cc);
self.globals.trace(cc); self.globals.trace(cc);
self.stack_frames.trace(cc);
} }
} }
type Error = Box<dyn std::error::Error>; type Error = Box<dyn std::error::Error>;
impl<'gc> Avm1<'gc> { impl<'gc> Avm1<'gc> {
pub fn new(gc_context: MutationContext<'gc, '_>, swf_version: u8) -> Self { pub fn new(gc_context: MutationContext<'gc, '_>) -> Self {
Self { Self {
swf_version,
stack: vec![],
constant_pool: vec![], constant_pool: vec![],
locals: HashMap::new(),
globals: GcCell::allocate(gc_context, create_globals(gc_context)), globals: GcCell::allocate(gc_context, create_globals(gc_context)),
stack_frames: vec![],
} }
} }
@ -88,24 +171,63 @@ impl<'gc> Avm1<'gc> {
pub fn locals_into_form_values(&self) -> HashMap<String, String> { pub fn locals_into_form_values(&self) -> HashMap<String, String> {
let mut form_values = HashMap::new(); let mut form_values = HashMap::new();
for (k, v) in self.locals.iter() { for (k, v) in self.current_stack_frame().unwrap().locals().iter() {
form_values.insert(k.clone(), v.clone().into_string()); form_values.insert(k.clone(), v.clone().into_string());
} }
form_values form_values
} }
pub fn do_action( /// Add a stack frame to the current AVM stack.
&mut self, ///
context: &mut ActionContext<'_, 'gc, '_>, /// The code pointer given will be copied into the GC arena.
code: &[u8], pub fn insert_stack_frame(&mut self, swf_version: u8, code: &[u8], mc: MutationContext<'gc, '_>) {
) -> Result<(), Error> { self.stack_frames.push(Avm1StackFrame::from_action(swf_version, code, mc));
let mut pc_stack = vec![Reader::new(code, self.swf_version)]; }
while pc_stack.len() > 0 { /// Retrieve the current AVM execution frame.
let mut reader = pc_stack.last_mut().unwrap(); ///
/// Yields None if there is no stack frame.
pub fn current_stack_frame(&self) -> Option<&Avm1StackFrame<'gc>> {
self.stack_frames.last()
}
if let Some(action) = reader.read_action()? { /// Retrieve the current AVM execution frame for mutation.
///
/// Yields None if there is no stack frame.
pub fn current_stack_frame_mut(&mut self) -> Option<&mut Avm1StackFrame<'gc>> {
self.stack_frames.last_mut()
}
/// Get the currently executing SWF version, if there is one.
fn current_swf_version(&self) -> Option<u8> {
self.current_stack_frame().map(|sf| sf.swf_version())
}
/// Get the current stack frame's action reader (PC), if there is one.
fn current_reader_mut(&mut self) -> Option<&mut Reader<'gc>> {
self.current_stack_frame_mut().map(|sf| sf.pc_mut())
}
/// Get the current locals, if there are any.
fn current_locals(&self) -> Option<&HashMap<String, Value<'gc>>> {
self.current_stack_frame().map(|sf| sf.locals())
}
/// Get the current locals, for mutation, if there are any.
fn current_locals_mut(&mut self) -> Option<&mut HashMap<String, Value<'gc>>> {
self.current_stack_frame_mut().map(|sf| sf.locals_mut())
}
/// Destroy the current stack frame (if there is one).
fn retire_stack_frame(&mut self) {
self.stack_frames.pop();
}
/// Execute the AVM stack until it is exhausted.
pub fn do_action(&mut self, context: &mut ActionContext<'_, 'gc, '_>) -> Result<(), Error> {
while self.stack_frames.len() > 0 {
if let Some(action) = self.current_reader_mut().unwrap().read_action()? {
use swf::avm1::types::Action; use swf::avm1::types::Action;
let result = match action { let result = match action {
Action::Add => self.action_add(context), Action::Add => self.action_add(context),
@ -159,11 +281,11 @@ impl<'gc> Avm1<'gc> {
scene_offset, scene_offset,
} => self.action_goto_frame_2(context, set_playing, scene_offset), } => self.action_goto_frame_2(context, set_playing, scene_offset),
Action::GotoLabel(label) => self.action_goto_label(context, &label), Action::GotoLabel(label) => self.action_goto_label(context, &label),
Action::If { offset } => self.action_if(context, offset, &mut reader), Action::If { offset } => self.action_if(context, offset),
Action::Increment => self.action_increment(context), Action::Increment => self.action_increment(context),
Action::InitArray => self.action_init_array(context), Action::InitArray => self.action_init_array(context),
Action::InitObject => self.action_init_object(context), Action::InitObject => self.action_init_object(context),
Action::Jump { offset } => self.action_jump(context, offset, &mut reader), Action::Jump { offset } => self.action_jump(context, offset),
Action::Less => self.action_less(context), Action::Less => self.action_less(context),
Action::Less2 => self.action_less_2(context), Action::Less2 => self.action_less_2(context),
Action::MBAsciiToChar => self.action_mb_ascii_to_char(context), Action::MBAsciiToChar => self.action_mb_ascii_to_char(context),
@ -211,10 +333,10 @@ impl<'gc> Avm1<'gc> {
Action::WaitForFrame { Action::WaitForFrame {
frame, frame,
num_actions_to_skip, num_actions_to_skip,
} => self.action_wait_for_frame(context, frame, num_actions_to_skip, &mut reader), } => self.action_wait_for_frame(context, frame, num_actions_to_skip),
Action::WaitForFrame2 { Action::WaitForFrame2 {
num_actions_to_skip, num_actions_to_skip,
} => self.action_wait_for_frame_2(context, num_actions_to_skip, &mut reader), } => self.action_wait_for_frame_2(context, num_actions_to_skip),
Action::With { .. } => self.action_with(context), Action::With { .. } => self.action_with(context),
_ => self.unknown_op(context, action), _ => self.unknown_op(context, action),
}; };
@ -224,7 +346,7 @@ impl<'gc> Avm1<'gc> {
} }
} else { } else {
//Out of code. Return to the parent function. //Out of code. Return to the parent function.
pc_stack.pop(); self.retire_stack_frame();
} }
} }
@ -283,11 +405,11 @@ impl<'gc> Avm1<'gc> {
} }
fn push(&mut self, value: impl Into<Value<'gc>>) { fn push(&mut self, value: impl Into<Value<'gc>>) {
self.stack.push(value.into()); self.current_stack_frame_mut().unwrap().stack_mut().push(value.into());
} }
fn pop(&mut self) -> Result<Value<'gc>, Error> { fn pop(&mut self) -> Result<Value<'gc>, Error> {
self.stack.pop().ok_or_else(|| "Stack underflow".into()) self.current_stack_frame_mut().unwrap().stack_mut().pop().ok_or_else(|| "Stack underflow".into())
} }
fn unknown_op( fn unknown_op(
@ -329,7 +451,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?; let a = self.pop()?;
let b = self.pop()?; let b = self.pop()?;
let result = b.into_number_v1() != 0.0 && a.into_number_v1() != 0.0; let result = b.into_number_v1() != 0.0 && a.into_number_v1() != 0.0;
self.push(Value::from_bool_v1(result, self.swf_version)); self.push(Value::from_bool_v1(result, self.current_swf_version().unwrap()));
Ok(()) Ok(())
} }
@ -418,7 +540,7 @@ impl<'gc> Avm1<'gc> {
self.pop()?; self.pop()?;
} }
self.stack.push(Value::Undefined); self.current_stack_frame_mut().unwrap().stack_mut().push(Value::Undefined);
// TODO(Herschel) // TODO(Herschel)
Err("Unimplemented action: CallFunction".into()) Err("Unimplemented action: CallFunction".into())
} }
@ -439,13 +561,13 @@ impl<'gc> Avm1<'gc> {
Value::Undefined | Value::Null => { Value::Undefined | Value::Null => {
let this = context.active_clip.read().object().as_object()?.to_owned(); let this = context.active_clip.read().object().as_object()?.to_owned();
let return_value = object.call(self, context, this, &args)?; let return_value = object.call(self, context, this, &args)?;
self.stack.push(return_value); self.current_stack_frame_mut().unwrap().stack_mut().push(return_value);
} }
Value::String(name) => { Value::String(name) => {
if name.is_empty() { if name.is_empty() {
let return_value = let return_value =
object.call(self, context, object.as_object()?.to_owned(), &args)?; object.call(self, context, object.as_object()?.to_owned(), &args)?;
self.stack.push(return_value); self.current_stack_frame_mut().unwrap().stack_mut().push(return_value);
} else { } else {
let callable = object.as_object()?.read().get( let callable = object.as_object()?.read().get(
&name, &name,
@ -461,7 +583,7 @@ impl<'gc> Avm1<'gc> {
let return_value = let return_value =
callable.call(self, context, object.as_object()?.to_owned(), &args)?; callable.call(self, context, object.as_object()?.to_owned(), &args)?;
self.stack.push(return_value); self.current_stack_frame_mut().unwrap().stack_mut().push(return_value);
} }
} }
_ => { _ => {
@ -505,13 +627,13 @@ impl<'gc> Avm1<'gc> {
fn action_define_local(&mut self, _context: &mut ActionContext) -> Result<(), Error> { fn action_define_local(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
let value = self.pop()?; let value = self.pop()?;
let name = self.pop()?; let name = self.pop()?;
self.locals.insert(name.as_string()?.clone(), value); self.current_locals_mut().unwrap().insert(name.as_string()?.clone(), value);
Ok(()) Ok(())
} }
fn action_define_local_2(&mut self, _context: &mut ActionContext) -> Result<(), Error> { fn action_define_local_2(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
let name = self.pop()?; let name = self.pop()?;
self.locals self.current_locals_mut().unwrap()
.insert(name.as_string()?.clone(), Value::Undefined); .insert(name.as_string()?.clone(), Value::Undefined);
Ok(()) Ok(())
} }
@ -561,7 +683,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?; let a = self.pop()?;
let b = self.pop()?; let b = self.pop()?;
let result = b.into_number_v1() == a.into_number_v1(); let result = b.into_number_v1() == a.into_number_v1();
self.push(Value::from_bool_v1(result, self.swf_version)); self.push(Value::from_bool_v1(result, self.current_swf_version().unwrap()));
Ok(()) Ok(())
} }
@ -641,7 +763,7 @@ impl<'gc> Avm1<'gc> {
} }
fn action_get_time(&mut self, context: &mut ActionContext) -> Result<(), Error> { fn action_get_time(&mut self, context: &mut ActionContext) -> Result<(), Error> {
self.stack.push(Value::Number(context.global_time as f64)); self.current_stack_frame_mut().unwrap().stack_mut().push(Value::Number(context.global_time as f64));
Ok(()) Ok(())
} }
@ -663,7 +785,7 @@ impl<'gc> Avm1<'gc> {
return Ok(()); return Ok(());
} }
let mut result = self.locals.get(path).map(|v| v.to_owned()); let mut result = self.current_locals().unwrap().get(path).map(|v| v.to_owned());
if result.is_none() { if result.is_none() {
if let Some((node, var_name)) = if let Some((node, var_name)) =
@ -821,12 +943,11 @@ impl<'gc> Avm1<'gc> {
fn action_if( fn action_if(
&mut self, &mut self,
_context: &mut ActionContext, _context: &mut ActionContext,
jump_offset: i16, jump_offset: i16
reader: &mut Reader<'_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let val = self.pop()?; let val = self.pop()?;
if val.as_bool() { if val.as_bool() {
reader.seek(jump_offset.into()); self.current_reader_mut().unwrap().seek(jump_offset.into());
} }
Ok(()) Ok(())
} }
@ -861,11 +982,10 @@ impl<'gc> Avm1<'gc> {
fn action_jump( fn action_jump(
&mut self, &mut self,
_context: &mut ActionContext, _context: &mut ActionContext,
jump_offset: i16, jump_offset: i16
reader: &mut Reader<'_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
// TODO(Herschel): Handle out-of-bounds. // TODO(Herschel): Handle out-of-bounds.
reader.seek(jump_offset.into()); self.current_reader_mut().unwrap().seek(jump_offset.into());
Ok(()) Ok(())
} }
@ -874,7 +994,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?; let a = self.pop()?;
let b = self.pop()?; let b = self.pop()?;
let result = b.into_number_v1() < a.into_number_v1(); let result = b.into_number_v1() < a.into_number_v1();
self.push(Value::from_bool_v1(result, self.swf_version)); self.push(Value::from_bool_v1(result, self.current_swf_version().unwrap()));
Ok(()) Ok(())
} }
@ -945,7 +1065,7 @@ impl<'gc> Avm1<'gc> {
// AS1 logical not // AS1 logical not
let val = self.pop()?; let val = self.pop()?;
let result = val.into_number_v1() == 0.0; let result = val.into_number_v1() == 0.0;
self.push(Value::from_bool_v1(result, self.swf_version)); self.push(Value::from_bool_v1(result, self.current_swf_version().unwrap()));
Ok(()) Ok(())
} }
@ -988,7 +1108,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?; let a = self.pop()?;
let b = self.pop()?; let b = self.pop()?;
let result = b.into_number_v1() != 0.0 || a.into_number_v1() != 0.0; let result = b.into_number_v1() != 0.0 || a.into_number_v1() != 0.0;
self.push(Value::from_bool_v1(result, self.swf_version)); self.push(Value::from_bool_v1(result, self.current_swf_version().unwrap()));
Ok(()) Ok(())
} }
@ -1063,7 +1183,7 @@ impl<'gc> Avm1<'gc> {
} }
fn action_push_duplicate(&mut self, _context: &mut ActionContext) -> Result<(), Error> { fn action_push_duplicate(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
let val = self.stack.last().ok_or("Stack underflow")?.clone(); let val = self.current_stack_frame().unwrap().stack().last().ok_or("Stack underflow")?.clone();
self.push(val); self.push(val);
Ok(()) Ok(())
} }
@ -1242,7 +1362,7 @@ impl<'gc> Avm1<'gc> {
_register: u8, _register: u8,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Does NOT pop the value from the stack. // Does NOT pop the value from the stack.
let _val = self.stack.last().ok_or("Stack underflow")?; let _val = self.current_stack_frame().unwrap().stack().last().ok_or("Stack underflow")?;
Err("Unimplemented action: StoreRegister".into()) Err("Unimplemented action: StoreRegister".into())
} }
@ -1261,7 +1381,7 @@ impl<'gc> Avm1<'gc> {
let a = self.pop()?; let a = self.pop()?;
let b = self.pop()?; let b = self.pop()?;
let result = b.into_string() == a.into_string(); let result = b.into_string() == a.into_string();
self.push(Value::from_bool_v1(result, self.swf_version)); self.push(Value::from_bool_v1(result, self.current_swf_version().unwrap()));
Ok(()) Ok(())
} }
@ -1298,7 +1418,7 @@ impl<'gc> Avm1<'gc> {
let b = self.pop()?; let b = self.pop()?;
// This is specifically a non-UTF8 aware comparison. // This is specifically a non-UTF8 aware comparison.
let result = b.into_string().bytes().lt(a.into_string().bytes()); let result = b.into_string().bytes().lt(a.into_string().bytes());
self.push(Value::from_bool_v1(result, self.swf_version)); self.push(Value::from_bool_v1(result, self.current_swf_version().unwrap()));
Ok(()) Ok(())
} }
@ -1358,14 +1478,14 @@ impl<'gc> Avm1<'gc> {
&mut self, &mut self,
_context: &mut ActionContext, _context: &mut ActionContext,
_frame: u16, _frame: u16,
num_actions_to_skip: u8, num_actions_to_skip: u8
reader: &mut Reader<'_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
// TODO(Herschel): Always true for now. // TODO(Herschel): Always true for now.
let loaded = true; let loaded = true;
if !loaded { if !loaded {
// Note that the offset is given in # of actions, NOT in bytes. // Note that the offset is given in # of actions, NOT in bytes.
// Read the actions and toss them away. // Read the actions and toss them away.
let reader = self.current_reader_mut().unwrap();
for _ in 0..num_actions_to_skip { for _ in 0..num_actions_to_skip {
reader.read_action()?; reader.read_action()?;
} }
@ -1376,8 +1496,7 @@ impl<'gc> Avm1<'gc> {
fn action_wait_for_frame_2( fn action_wait_for_frame_2(
&mut self, &mut self,
_context: &mut ActionContext, _context: &mut ActionContext,
num_actions_to_skip: u8, num_actions_to_skip: u8
reader: &mut Reader<'_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
// TODO(Herschel): Always true for now. // TODO(Herschel): Always true for now.
let _frame_num = self.pop()?.as_f64()? as u16; let _frame_num = self.pop()?.as_f64()? as u16;
@ -1385,6 +1504,7 @@ impl<'gc> Avm1<'gc> {
if !loaded { if !loaded {
// Note that the offset is given in # of actions, NOT in bytes. // Note that the offset is given in # of actions, NOT in bytes.
// Read the actions and toss them away. // Read the actions and toss them away.
let reader = self.current_reader_mut().unwrap();
for _ in 0..num_actions_to_skip { for _ in 0..num_actions_to_skip {
reader.read_action()?; reader.read_action()?;
} }

View File

@ -113,7 +113,7 @@ mod tests {
F: for<'a, 'gc> FnOnce(&mut Avm1<'gc>, &mut ActionContext<'a, 'gc, '_>) -> R, F: for<'a, 'gc> FnOnce(&mut Avm1<'gc>, &mut ActionContext<'a, 'gc, '_>) -> R,
{ {
rootless_arena(|gc_context| { rootless_arena(|gc_context| {
let mut avm = Avm1::new(gc_context, swf_version); let mut avm = Avm1::new(gc_context);
let movie_clip: Box<dyn DisplayObject> = Box::new(MovieClip::new(gc_context)); let movie_clip: Box<dyn DisplayObject> = Box::new(MovieClip::new(gc_context));
let root = GcCell::allocate(gc_context, movie_clip); let root = GcCell::allocate(gc_context, movie_clip);
let mut context = ActionContext { let mut context = ActionContext {

View File

@ -110,7 +110,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
)), )),
), ),
mouse_hover_node: GcCell::allocate(gc_context, None), mouse_hover_node: GcCell::allocate(gc_context, None),
avm: GcCell::allocate(gc_context, Avm1::new(gc_context, header.version)), avm: GcCell::allocate(gc_context, Avm1::new(gc_context)),
}), }),
frame_rate: header.frame_rate.into(), frame_rate: header.frame_rate.into(),
@ -515,9 +515,8 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
action_context.start_clip = active_clip; action_context.start_clip = active_clip;
action_context.active_clip = active_clip; action_context.active_clip = active_clip;
action_context.target_clip = Some(active_clip); action_context.target_clip = Some(active_clip);
let _ = update_context update_context.avm.insert_stack_frame(update_context.swf_version, action.as_ref(), action_context.gc_context);
.avm let _ = update_context.avm.do_action(&mut action_context);
.do_action(&mut action_context, action.as_ref());
} }
} }