chore: Fix clippy lints

* Remove clone calls from Copy objects
 * Used Iterator::cloned() instead of manually cloning
 * Pass swf::Function into AvmFunction2::new()
 * Use action_clone_sprite
This commit is contained in:
Mike Welsh 2019-10-06 14:45:14 -07:00
parent 2d365856a7
commit 1c3e4406b3
4 changed files with 512 additions and 275 deletions

View File

@ -1,31 +1,31 @@
use crate::avm1::function::Avm1Function2;
use crate::avm1::globals::create_globals; use crate::avm1::globals::create_globals;
use crate::avm1::object::Object; use crate::avm1::object::Object;
use crate::avm1::function::Avm1Function2;
use crate::backend::navigator::NavigationMethod; use crate::backend::navigator::NavigationMethod;
use crate::prelude::*; use crate::prelude::*;
use gc_arena::{GcCell, MutationContext}; use gc_arena::{GcCell, MutationContext};
use rand::{rngs::SmallRng, Rng}; use rand::{rngs::SmallRng, Rng};
use std::convert::TryInto;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto;
use swf::avm1::read::Reader; use swf::avm1::read::Reader;
use swf::avm1::types::{Action, Function}; use swf::avm1::types::{Action, Function};
use crate::tag_utils::SwfSlice; use crate::tag_utils::SwfSlice;
mod activation;
mod fscommand; mod fscommand;
mod function; mod function;
mod scope;
mod activation;
mod globals; mod globals;
pub mod movie_clip; pub mod movie_clip;
pub mod object; pub mod object;
mod scope;
mod value; mod value;
pub use value::Value;
use scope::Scope;
use activation::Activation; use activation::Activation;
use scope::Scope;
pub use value::Value;
pub struct ActionContext<'a, 'gc, 'gc_context> { pub struct ActionContext<'a, 'gc, 'gc_context> {
pub gc_context: gc_arena::MutationContext<'gc, 'gc_context>, pub gc_context: gc_arena::MutationContext<'gc, 'gc_context>,
@ -80,7 +80,7 @@ pub struct Avm1<'gc> {
/// Used to enforce the restriction that no more than one mutable current /// Used to enforce the restriction that no more than one mutable current
/// reader be active at once. /// reader be active at once.
is_reading: bool is_reading: bool,
} }
unsafe impl<'gc> gc_arena::Collect for Avm1<'gc> { unsafe impl<'gc> gc_arena::Collect for Avm1<'gc> {
@ -101,8 +101,13 @@ impl<'gc> Avm1<'gc> {
globals: GcCell::allocate(gc_context, create_globals(gc_context)), globals: GcCell::allocate(gc_context, create_globals(gc_context)),
stack_frames: vec![], stack_frames: vec![],
stack: vec![], stack: vec![],
registers: [Value::Undefined, Value::Undefined, Value::Undefined, Value::Undefined], registers: [
is_reading: false Value::Undefined,
Value::Undefined,
Value::Undefined,
Value::Undefined,
],
is_reading: false,
} }
} }
@ -113,7 +118,13 @@ 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.current_stack_frame().unwrap().scope().locals().iter_values() { for (k, v) in self
.current_stack_frame()
.unwrap()
.scope()
.locals()
.iter_values()
{
form_values.insert(k.clone(), v.clone().into_string()); form_values.insert(k.clone(), v.clone().into_string());
} }
@ -121,11 +132,34 @@ impl<'gc> Avm1<'gc> {
} }
/// Add a stack frame that executes code in timeline scope /// Add a stack frame that executes code in timeline scope
pub fn insert_stack_frame_for_action(&mut self, swf_version: u8, code: SwfSlice, action_context: &mut ActionContext<'_, 'gc, '_>) { pub fn insert_stack_frame_for_action(
let global_scope = GcCell::allocate(action_context.gc_context, Scope::from_global_object(self.globals)); &mut self,
let clip_obj = action_context.active_clip.read().object().as_object().unwrap().to_owned(); swf_version: u8,
let child_scope = GcCell::allocate(action_context.gc_context, Scope::new(global_scope, scope::ScopeClass::Target, clip_obj)); code: SwfSlice,
self.stack_frames.push(Activation::from_action(swf_version, code, child_scope, clip_obj, None)); action_context: &mut ActionContext<'_, 'gc, '_>,
) {
let global_scope = GcCell::allocate(
action_context.gc_context,
Scope::from_global_object(self.globals),
);
let clip_obj = action_context
.active_clip
.read()
.object()
.as_object()
.unwrap()
.to_owned();
let child_scope = GcCell::allocate(
action_context.gc_context,
Scope::new(global_scope, scope::ScopeClass::Target, clip_obj),
);
self.stack_frames.push(Activation::from_action(
swf_version,
code,
child_scope,
clip_obj,
None,
));
} }
/// Add a stack frame for any arbitrary code. /// Add a stack frame for any arbitrary code.
@ -167,20 +201,26 @@ impl<'gc> Avm1<'gc> {
/// It is incorrect to call this function multiple times in the same stack. /// It is incorrect to call this function multiple times in the same stack.
/// Doing so will result in any changes in duplicate readers being ignored. /// Doing so will result in any changes in duplicate readers being ignored.
/// Always pass the borrowed reader into functions that need it. /// Always pass the borrowed reader into functions that need it.
fn with_current_reader_mut<F, R>(&mut self, func: F) -> Option<R> where F: FnOnce(&mut Self, &mut Reader<'_>) -> R { fn with_current_reader_mut<F, R>(&mut self, func: F) -> Option<R>
where
F: FnOnce(&mut Self, &mut Reader<'_>) -> R,
{
if self.is_reading { if self.is_reading {
log::error!("Two mutable readers are open at the same time. Please correct this error."); log::error!(
"Two mutable readers are open at the same time. Please correct this error."
);
} }
self.is_reading = true; self.is_reading = true;
let current_stack_id = self.stack_frames.len() - 1; let current_stack_id = self.stack_frames.len() - 1;
let (swf_version, data, pc) = self.stack_frames.last().map(|frame| (frame.swf_version(), frame.data(), frame.pc()))?; let (swf_version, data, pc) = self
.stack_frames
.last()
.map(|frame| (frame.swf_version(), frame.data(), frame.pc()))?;
let mut read = Reader::new(data.as_ref(), swf_version); let mut read = Reader::new(data.as_ref(), swf_version);
read.seek(pc.try_into().unwrap()); read.seek(pc.try_into().unwrap());
let r = func(self, &mut read); let r = func(self, &mut read);
//this took an hour of fighting borrowck to figure out was necessary //this took an hour of fighting borrowck to figure out was necessary
if let Some(new_stack) = self.stack_frames.get_mut(current_stack_id) { if let Some(new_stack) = self.stack_frames.get_mut(current_stack_id) {
if new_stack.is_identical_fn(&data) { if new_stack.is_identical_fn(&data) {
@ -199,16 +239,24 @@ impl<'gc> Avm1<'gc> {
} }
/// Execute the AVM stack until it is exhausted. /// Execute the AVM stack until it is exhausted.
pub fn run_stack_till_empty(&mut self, context: &mut ActionContext<'_, 'gc, '_>) -> Result<(), Error> { pub fn run_stack_till_empty(
while self.stack_frames.len() > 0 { &mut self,
self.with_current_reader_mut(|this, r| this.do_next_action(context, r)).unwrap()?; context: &mut ActionContext<'_, 'gc, '_>,
) -> Result<(), Error> {
while !self.stack_frames.is_empty() {
self.with_current_reader_mut(|this, r| this.do_next_action(context, r))
.unwrap()?;
} }
Ok(()) Ok(())
} }
/// Run a single action from a given action reader. /// Run a single action from a given action reader.
fn do_next_action(&mut self, context: &mut ActionContext<'_, 'gc, '_>, reader: &mut Reader<'_>) -> Result<(), Error> { fn do_next_action(
&mut self,
context: &mut ActionContext<'_, 'gc, '_>,
reader: &mut Reader<'_>,
) -> Result<(), Error> {
let data = self.current_stack_frame().unwrap().data(); let data = self.current_stack_frame().unwrap().data();
if reader.pos() >= (data.end - data.start) { if reader.pos() >= (data.end - data.start) {
@ -234,6 +282,7 @@ impl<'gc> Avm1<'gc> {
Action::CallFunction => self.action_call_function(context), Action::CallFunction => self.action_call_function(context),
Action::CallMethod => self.action_call_method(context), Action::CallMethod => self.action_call_method(context),
Action::CharToAscii => self.action_char_to_ascii(context), Action::CharToAscii => self.action_char_to_ascii(context),
Action::CloneSprite => self.action_clone_sprite(context),
Action::ConstantPool(constant_pool) => { Action::ConstantPool(constant_pool) => {
self.action_constant_pool(context, &constant_pool[..]) self.action_constant_pool(context, &constant_pool[..])
} }
@ -346,8 +395,8 @@ impl<'gc> Avm1<'gc> {
Ok(()) Ok(())
} }
pub fn variable_name_is_slash_path<'s>(path: &'s str) -> bool { pub fn variable_name_is_slash_path(path: &str) -> bool {
path.contains(":") || path.contains(".") path.contains(':') || path.contains('.')
} }
pub fn resolve_slash_path( pub fn resolve_slash_path(
@ -414,21 +463,41 @@ impl<'gc> Avm1<'gc> {
/// If a given register does not exist, this function yields /// If a given register does not exist, this function yields
/// Value::Undefined, which is also a valid register value. /// Value::Undefined, which is also a valid register value.
pub fn current_register(&self, id: u8) -> Value<'gc> { pub fn current_register(&self, id: u8) -> Value<'gc> {
if self.current_stack_frame().map(|sf| sf.has_local_registers()).unwrap_or(false) { if self
.current_stack_frame()
.map(|sf| sf.has_local_registers())
.unwrap_or(false)
{
self.current_stack_frame().unwrap().local_register(id) self.current_stack_frame().unwrap().local_register(id)
} else { } else {
self.registers.get(id as usize).map(|v| v.clone()).unwrap_or(Value::Undefined) self.registers
.get(id as usize)
.cloned()
.unwrap_or(Value::Undefined)
} }
} }
/// Set a register to a given value. /// Set a register to a given value.
/// ///
/// If a given register does not exist, this function does nothing. /// If a given register does not exist, this function does nothing.
pub fn set_current_register(&mut self, id: u8, value: Value<'gc>, context: &mut ActionContext<'_, 'gc, '_>) { pub fn set_current_register(
if self.current_stack_frame().map(|sf| sf.has_local_registers()).unwrap_or(false) { &mut self,
self.current_stack_frame_mut().unwrap().set_local_register(id, value, context.gc_context); id: u8,
} else { value: Value<'gc>,
self.registers.get_mut(id as usize).map(|v| *v = value); context: &mut ActionContext<'_, 'gc, '_>,
) {
if self
.current_stack_frame()
.map(|sf| sf.has_local_registers())
.unwrap_or(false)
{
self.current_stack_frame_mut().unwrap().set_local_register(
id,
value,
context.gc_context,
);
} else if let Some(v) = self.registers.get_mut(id as usize) {
*v = value;
} }
} }
@ -472,7 +541,10 @@ 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.current_swf_version().unwrap())); self.push(Value::from_bool_v1(
result,
self.current_swf_version().unwrap(),
));
Ok(()) Ok(())
} }
@ -554,7 +626,10 @@ impl<'gc> Avm1<'gc> {
Err("Unimplemented action: Call".into()) Err("Unimplemented action: Call".into())
} }
fn action_call_function(&mut self, context: &mut ActionContext<'_, 'gc, '_>) -> Result<(), Error> { fn action_call_function(
&mut self,
context: &mut ActionContext<'_, 'gc, '_>,
) -> Result<(), Error> {
let fn_name = self.pop()?; let fn_name = self.pop()?;
let mut args = Vec::new(); let mut args = Vec::new();
let num_args = self.pop()?.as_i64()?; // TODO(Herschel): max arg count? let num_args = self.pop()?.as_i64()?; // TODO(Herschel): max arg count?
@ -562,7 +637,10 @@ impl<'gc> Avm1<'gc> {
args.push(self.pop()?); args.push(self.pop()?);
} }
let target_fn = self.current_stack_frame_mut().unwrap().resolve(fn_name.as_string()?); let target_fn = self
.current_stack_frame_mut()
.unwrap()
.resolve(fn_name.as_string()?);
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 = target_fn.call(self, context, this, &args)?; let return_value = target_fn.call(self, context, this, &args)?;
if let Some(instant_return) = return_value { if let Some(instant_return) = return_value {
@ -572,7 +650,10 @@ impl<'gc> Avm1<'gc> {
Ok(()) Ok(())
} }
fn action_call_method(&mut self, context: &mut ActionContext<'_, 'gc, '_>) -> Result<(), Error> { fn action_call_method(
&mut self,
context: &mut ActionContext<'_, 'gc, '_>,
) -> Result<(), Error> {
let method_name = self.pop()?; let method_name = self.pop()?;
let object = self.pop()?; let object = self.pop()?;
let num_args = self.pop()?.as_i64()?; // TODO(Herschel): max arg count? let num_args = self.pop()?.as_i64()?; // TODO(Herschel): max arg count?
@ -591,7 +672,8 @@ impl<'gc> Avm1<'gc> {
} }
Value::String(name) => { Value::String(name) => {
if name.is_empty() { if name.is_empty() {
let return_value = object.call(self, context, object.as_object()?.to_owned(), &args)?; let return_value =
object.call(self, context, object.as_object()?.to_owned(), &args)?;
if let Some(instant_return) = return_value { if let Some(instant_return) = return_value {
self.push(instant_return); self.push(instant_return);
} }
@ -607,7 +689,8 @@ impl<'gc> Avm1<'gc> {
return Err(format!("Object method {} is not defined", name).into()); return Err(format!("Object method {} is not defined", name).into());
} }
let return_value = callable.call(self, context, object.as_object()?.to_owned(), &args)?; let return_value =
callable.call(self, context, object.as_object()?.to_owned(), &args)?;
if let Some(instant_return) = return_value { if let Some(instant_return) = return_value {
self.push(instant_return); self.push(instant_return);
} }
@ -648,14 +731,26 @@ impl<'gc> Avm1<'gc> {
actions: &[u8], actions: &[u8],
) -> Result<(), Error> { ) -> Result<(), Error> {
let swf_version = self.current_stack_frame().unwrap().swf_version(); let swf_version = self.current_stack_frame().unwrap().swf_version();
let func_data = self.current_stack_frame().unwrap().data().to_subslice(actions).unwrap(); let func_data = self
let scope = Scope::new_closure_scope(self.current_stack_frame().unwrap().scope_cell(), context.gc_context); .current_stack_frame()
let func = Value::Object(GcCell::allocate(context.gc_context, Object::action_function(swf_version, func_data, name, params, scope))); .unwrap()
.data()
.to_subslice(actions)
.unwrap();
let scope = Scope::new_closure_scope(
self.current_stack_frame().unwrap().scope_cell(),
context.gc_context,
);
let func = Value::Object(GcCell::allocate(
context.gc_context,
Object::action_function(swf_version, func_data, name, params, scope),
));
if name == "" { if name == "" {
self.push(func); self.push(func);
} else { } else {
self.current_stack_frame_mut().unwrap().define(name, func, context.gc_context); self.current_stack_frame_mut()
.unwrap()
.define(name, func, context.gc_context);
} }
Ok(()) Ok(())
@ -664,49 +759,61 @@ impl<'gc> Avm1<'gc> {
fn action_define_function_2( fn action_define_function_2(
&mut self, &mut self,
context: &mut ActionContext<'_, 'gc, '_>, context: &mut ActionContext<'_, 'gc, '_>,
action_func: &Function action_func: &Function,
) -> Result<(), Error> { ) -> Result<(), Error> {
let swf_version = self.current_stack_frame().unwrap().swf_version(); let swf_version = self.current_stack_frame().unwrap().swf_version();
let func_data = self.current_stack_frame().unwrap().data().to_subslice(action_func.actions).unwrap(); let func_data = self
let scope = Scope::new_closure_scope(self.current_stack_frame().unwrap().scope_cell(), context.gc_context); .current_stack_frame()
let func2 = Avm1Function2::new( .unwrap()
swf_version, .data()
func_data, .to_subslice(action_func.actions)
action_func.name, .unwrap();
action_func.params.capacity() as u8, //TODO: this needs to be refactored let scope = Scope::new_closure_scope(
action_func.preload_parent, self.current_stack_frame().unwrap().scope_cell(),
action_func.preload_root, context.gc_context,
action_func.suppress_super,
action_func.preload_super,
action_func.suppress_arguments,
action_func.preload_arguments,
action_func.suppress_this,
action_func.preload_this,
action_func.preload_global,
&action_func.params,
scope
); );
let func_obj = Value::Object(GcCell::allocate(context.gc_context, Object::action_function2(func2))); let func2 = Avm1Function2::new(swf_version, func_data, action_func, scope);
let func_obj = Value::Object(GcCell::allocate(
context.gc_context,
Object::action_function2(func2),
));
if action_func.name == "" { if action_func.name == "" {
self.push(func_obj); self.push(func_obj);
} else { } else {
self.current_stack_frame_mut().unwrap().define(action_func.name, func_obj, context.gc_context); self.current_stack_frame_mut().unwrap().define(
action_func.name,
func_obj,
context.gc_context,
);
} }
Ok(()) Ok(())
} }
fn action_define_local(&mut self, context: &mut ActionContext<'_, 'gc, '_>) -> Result<(), Error> { fn action_define_local(
&mut self,
context: &mut ActionContext<'_, 'gc, '_>,
) -> Result<(), Error> {
let value = self.pop()?; let value = self.pop()?;
let name = self.pop()?; let name = self.pop()?;
self.current_stack_frame_mut().unwrap().define(name.as_string()?, value, context.gc_context); self.current_stack_frame_mut().unwrap().define(
name.as_string()?,
value,
context.gc_context,
);
Ok(()) Ok(())
} }
fn action_define_local_2(&mut self, context: &mut ActionContext<'_, 'gc, '_>) -> Result<(), Error> { fn action_define_local_2(
&mut self,
context: &mut ActionContext<'_, 'gc, '_>,
) -> Result<(), Error> {
let name = self.pop()?; let name = self.pop()?;
self.current_stack_frame_mut().unwrap().define(name.as_string()?, Value::Undefined, context.gc_context); self.current_stack_frame_mut().unwrap().define(
name.as_string()?,
Value::Undefined,
context.gc_context,
);
Ok(()) Ok(())
} }
@ -718,7 +825,6 @@ impl<'gc> Avm1<'gc> {
//Fun fact: This isn't in the Adobe SWF19 spec, but this opcode returns //Fun fact: This isn't in the Adobe SWF19 spec, but this opcode returns
//a boolean based on if the delete actually deleted something. //a boolean based on if the delete actually deleted something.
let did_exist = Value::Bool(object.read().has_property(name)); let did_exist = Value::Bool(object.read().has_property(name));
object.write(context.gc_context).delete(name); object.write(context.gc_context).delete(name);
self.push(did_exist); self.push(did_exist);
@ -733,7 +839,10 @@ impl<'gc> Avm1<'gc> {
//a boolean based on if the delete actually deleted something. //a boolean based on if the delete actually deleted something.
let did_exist = Value::Bool(self.current_stack_frame().unwrap().is_defined(name)); let did_exist = Value::Bool(self.current_stack_frame().unwrap().is_defined(name));
self.current_stack_frame().unwrap().scope().delete(name, context.gc_context); self.current_stack_frame()
.unwrap()
.scope()
.delete(name, context.gc_context);
self.push(did_exist); self.push(did_exist);
Ok(()) Ok(())
@ -762,13 +871,12 @@ impl<'gc> Avm1<'gc> {
let name = self.pop()?; let name = self.pop()?;
let name = name.as_string()?; let name = name.as_string()?;
self.push(Value::Null); // Sentinel that indicates end of enumeration self.push(Value::Null); // Sentinel that indicates end of enumeration
let ob = match self.current_stack_frame().unwrap().resolve(name) { let ob = match self.current_stack_frame().unwrap().resolve(name) {
Value::Object(ob) => ob, Value::Object(ob) => ob,
_ => { _ => {
log::error!("Cannot enumerate properties of {}", name); log::error!("Cannot enumerate properties of {}", name);
return Ok(()) //TODO: This is NOT OK(()). return Ok(()); //TODO: This is NOT OK(()).
} }
}; };
@ -785,7 +893,10 @@ 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.current_swf_version().unwrap())); self.push(Value::from_bool_v1(
result,
self.current_swf_version().unwrap(),
));
Ok(()) Ok(())
} }
@ -906,7 +1017,6 @@ impl<'gc> Avm1<'gc> {
let is_slashpath = Self::variable_name_is_slash_path(path); let is_slashpath = Self::variable_name_is_slash_path(path);
let mut result = None; let mut result = None;
if is_slashpath { if is_slashpath {
if let Some((node, var_name)) = if let Some((node, var_name)) =
Self::resolve_slash_path_variable(context.target_clip, context.root, path) Self::resolve_slash_path_variable(context.target_clip, context.root, path)
@ -1063,7 +1173,7 @@ impl<'gc> Avm1<'gc> {
&mut self, &mut self,
_context: &mut ActionContext, _context: &mut ActionContext,
jump_offset: i16, jump_offset: i16,
reader: &mut Reader<'_> reader: &mut Reader<'_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let val = self.pop()?; let val = self.pop()?;
if val.as_bool() { if val.as_bool() {
@ -1103,7 +1213,7 @@ impl<'gc> Avm1<'gc> {
&mut self, &mut self,
_context: &mut ActionContext, _context: &mut ActionContext,
jump_offset: i16, jump_offset: i16,
reader: &mut Reader<'_> reader: &mut Reader<'_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
// TODO(Herschel): Handle out-of-bounds. // TODO(Herschel): Handle out-of-bounds.
reader.seek(jump_offset.into()); reader.seek(jump_offset.into());
@ -1115,7 +1225,10 @@ 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.current_swf_version().unwrap())); self.push(Value::from_bool_v1(
result,
self.current_swf_version().unwrap(),
));
Ok(()) Ok(())
} }
@ -1186,7 +1299,10 @@ 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.current_swf_version().unwrap())); self.push(Value::from_bool_v1(
result,
self.current_swf_version().unwrap(),
));
Ok(()) Ok(())
} }
@ -1229,7 +1345,10 @@ 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.current_swf_version().unwrap())); self.push(Value::from_bool_v1(
result,
self.current_swf_version().unwrap(),
));
Ok(()) Ok(())
} }
@ -1340,8 +1459,9 @@ impl<'gc> Avm1<'gc> {
let object = self.pop()?.as_object()?; let object = self.pop()?.as_object()?;
let this = self.current_stack_frame().unwrap().this_cell(); let this = self.current_stack_frame().unwrap().this_cell();
object.write(context.gc_context).set(name, value, self, context, this); object
.write(context.gc_context)
.set(name, value, self, context, this);
Ok(()) Ok(())
} }
@ -1397,10 +1517,13 @@ impl<'gc> Avm1<'gc> {
Self::resolve_slash_path_variable(context.target_clip, context.root, var_path) Self::resolve_slash_path_variable(context.target_clip, context.root, var_path)
{ {
if let Some(clip) = node.write(context.gc_context).as_movie_clip_mut() { if let Some(clip) = node.write(context.gc_context).as_movie_clip_mut() {
clip.object() clip.object().as_object()?.write(context.gc_context).set(
.as_object()? var_name,
.write(context.gc_context) value.clone(),
.set(var_name, value.clone(), self, context, this); self,
context,
this,
);
} }
} }
} else { } else {
@ -1408,7 +1531,9 @@ impl<'gc> Avm1<'gc> {
let scope = self.current_stack_frame().unwrap().scope_cell(); let scope = self.current_stack_frame().unwrap().scope_cell();
let unused_value = scope.read().overwrite(var_path, value, self, context, this); let unused_value = scope.read().overwrite(var_path, value, self, context, this);
if let Some(value) = unused_value { if let Some(value) = unused_value {
self.current_stack_frame().unwrap().define(var_path, value, context.gc_context); self.current_stack_frame()
.unwrap()
.define(var_path, value, context.gc_context);
} }
} }
@ -1444,13 +1569,24 @@ impl<'gc> Avm1<'gc> {
} }
let scope = self.current_stack_frame().unwrap().scope_cell(); let scope = self.current_stack_frame().unwrap().scope_cell();
let clip_obj = context.active_clip.read().object().as_object().unwrap().to_owned(); let clip_obj = context
.active_clip
.read()
.object()
.as_object()
.unwrap()
.to_owned();
self.current_stack_frame_mut().unwrap().set_scope(Scope::new_target_scope(scope, clip_obj, context.gc_context)); self.current_stack_frame_mut()
.unwrap()
.set_scope(Scope::new_target_scope(scope, clip_obj, context.gc_context));
Ok(()) Ok(())
} }
fn action_set_target2(&mut self, context: &mut ActionContext<'_, 'gc, '_>) -> Result<(), Error> { fn action_set_target2(
&mut self,
context: &mut ActionContext<'_, 'gc, '_>,
) -> Result<(), Error> {
let target = self.pop()?; let target = self.pop()?;
if let Ok(target) = target.as_string() { if let Ok(target) = target.as_string() {
self.action_set_target(context, target)?; self.action_set_target(context, target)?;
@ -1508,7 +1644,6 @@ impl<'gc> Avm1<'gc> {
) -> 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")?.clone(); let val = self.stack.last().ok_or("Stack underflow")?.clone();
self.set_current_register(register, val, context); self.set_current_register(register, val, context);
Ok(()) Ok(())
@ -1529,7 +1664,10 @@ 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.current_swf_version().unwrap())); self.push(Value::from_bool_v1(
result,
self.current_swf_version().unwrap(),
));
Ok(()) Ok(())
} }
@ -1566,7 +1704,10 @@ 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.current_swf_version().unwrap())); self.push(Value::from_bool_v1(
result,
self.current_swf_version().unwrap(),
));
Ok(()) Ok(())
} }
@ -1627,7 +1768,7 @@ impl<'gc> Avm1<'gc> {
_context: &mut ActionContext, _context: &mut ActionContext,
_frame: u16, _frame: u16,
num_actions_to_skip: u8, num_actions_to_skip: u8,
r: &mut Reader<'_> r: &mut Reader<'_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
// TODO(Herschel): Always true for now. // TODO(Herschel): Always true for now.
let loaded = true; let loaded = true;
@ -1643,7 +1784,7 @@ impl<'gc> Avm1<'gc> {
&mut self, &mut self,
_context: &mut ActionContext, _context: &mut ActionContext,
num_actions_to_skip: u8, num_actions_to_skip: u8,
r: &mut Reader<'_> r: &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;
@ -1656,11 +1797,27 @@ impl<'gc> Avm1<'gc> {
Ok(()) Ok(())
} }
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
let with_scope = Scope::new_with_scope(self.current_stack_frame().unwrap().scope_cell(), object, context.gc_context); .current_stack_frame()
let new_activation = self.current_stack_frame().unwrap().to_rescope(block, with_scope); .unwrap()
.data()
.to_subslice(actions)
.unwrap();
let with_scope = Scope::new_with_scope(
self.current_stack_frame().unwrap().scope_cell(),
object,
context.gc_context,
);
let new_activation = self
.current_stack_frame()
.unwrap()
.to_rescope(block, with_scope);
self.stack_frames.push(new_activation); self.stack_frames.push(new_activation);
Ok(()) Ok(())
} }

View File

@ -1,12 +1,12 @@
//! Activation records //! Activation records
use std::sync::Arc;
use std::cell::{Ref, RefMut};
use gc_arena::{GcCell, MutationContext};
use crate::tag_utils::SwfSlice;
use crate::avm1::scope::Scope;
use crate::avm1::object::Object; use crate::avm1::object::Object;
use crate::avm1::scope::Scope;
use crate::avm1::Value; use crate::avm1::Value;
use crate::tag_utils::SwfSlice;
use gc_arena::{GcCell, MutationContext};
use std::cell::{Ref, RefMut};
use std::sync::Arc;
/// Represents a single activation of a given AVM1 function or keyframe. /// Represents a single activation of a given AVM1 function or keyframe.
#[derive(Clone)] #[derive(Clone)]
@ -48,7 +48,7 @@ pub struct Activation<'gc> {
/// ///
/// Registers are stored in a `GcCell` so that rescopes (e.g. with) use the /// Registers are stored in a `GcCell` so that rescopes (e.g. with) use the
/// same register set. /// same register set.
local_registers: Option<GcCell<'gc, Vec<Value<'gc>>>> local_registers: Option<GcCell<'gc, Vec<Value<'gc>>>>,
} }
unsafe impl<'gc> gc_arena::Collect for Activation<'gc> { unsafe impl<'gc> gc_arena::Collect for Activation<'gc> {
@ -62,29 +62,41 @@ unsafe impl<'gc> gc_arena::Collect for Activation<'gc> {
} }
impl<'gc> Activation<'gc> { impl<'gc> Activation<'gc> {
pub fn from_action(swf_version: u8, code: SwfSlice, scope: GcCell<'gc, Scope<'gc>>, this: GcCell<'gc, Object<'gc>>, arguments: Option<GcCell<'gc, Object<'gc>>>) -> Activation<'gc> { pub fn from_action(
swf_version: u8,
code: SwfSlice,
scope: GcCell<'gc, Scope<'gc>>,
this: GcCell<'gc, Object<'gc>>,
arguments: Option<GcCell<'gc, Object<'gc>>>,
) -> Activation<'gc> {
Activation { Activation {
swf_version: swf_version, swf_version,
data: code, data: code,
pc: 0, pc: 0,
scope: scope, scope,
this: this, this,
arguments: arguments, arguments,
is_function: false, is_function: false,
local_registers: None local_registers: None,
} }
} }
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> { 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 { Activation {
swf_version: swf_version, swf_version,
data: code, data: code,
pc: 0, pc: 0,
scope: scope, scope,
this: this, this,
arguments: arguments, arguments,
is_function: true, is_function: true,
local_registers: None local_registers: None,
} }
} }
@ -94,23 +106,27 @@ impl<'gc> Activation<'gc> {
/// will prevent the AVM from panicking without a current activation. /// will prevent the AVM from panicking without a current activation.
/// We construct a single scope chain from a global object, and that's about /// We construct a single scope chain from a global object, and that's about
/// it. /// it.
pub fn from_nothing(swf_version: u8, globals: GcCell<'gc, Object<'gc>>, mc: MutationContext<'gc, '_>) -> Activation<'gc> { pub fn from_nothing(
swf_version: u8,
globals: GcCell<'gc, Object<'gc>>,
mc: MutationContext<'gc, '_>,
) -> Activation<'gc> {
let global_scope = GcCell::allocate(mc, Scope::from_global_object(globals)); let global_scope = GcCell::allocate(mc, Scope::from_global_object(globals));
let child_scope = GcCell::allocate(mc, Scope::new_local_scope(global_scope, mc)); let child_scope = GcCell::allocate(mc, Scope::new_local_scope(global_scope, mc));
Activation { Activation {
swf_version: swf_version, swf_version,
data: SwfSlice { data: SwfSlice {
data: Arc::new(Vec::new()), data: Arc::new(Vec::new()),
start: 0, start: 0,
end: 0 end: 0,
}, },
pc: 0, pc: 0,
scope: child_scope, scope: child_scope,
this: globals, this: globals,
arguments: None, arguments: None,
is_function: false, is_function: false,
local_registers: None local_registers: None,
} }
} }
@ -120,11 +136,11 @@ impl<'gc> Activation<'gc> {
swf_version: self.swf_version, swf_version: self.swf_version,
data: code, data: code,
pc: 0, pc: 0,
scope: scope, scope,
this: self.this, this: self.this,
arguments: self.arguments, arguments: self.arguments,
is_function: false, is_function: false,
local_registers: self.local_registers.clone() local_registers: self.local_registers,
} }
} }
@ -153,7 +169,6 @@ impl<'gc> Activation<'gc> {
pub fn pc(&self) -> usize { pub fn pc(&self) -> usize {
self.pc self.pc
} }
/// Change the current PC. /// Change the current PC.
pub fn set_pc(&mut self, new_pc: usize) { pub fn set_pc(&mut self, new_pc: usize) {
self.pc = new_pc; self.pc = new_pc;
@ -171,7 +186,7 @@ impl<'gc> Activation<'gc> {
/// Returns AVM local variable scope for reference. /// Returns AVM local variable scope for reference.
pub fn scope_cell(&self) -> GcCell<'gc, Scope<'gc>> { pub fn scope_cell(&self) -> GcCell<'gc, Scope<'gc>> {
self.scope.clone() self.scope
} }
/// Completely replace the current scope with a new one. /// Completely replace the current scope with a new one.
@ -220,7 +235,6 @@ impl<'gc> Activation<'gc> {
pub fn this_cell(&self) -> GcCell<'gc, Object<'gc>> { pub fn this_cell(&self) -> GcCell<'gc, Object<'gc>> {
self.this self.this
} }
/// Returns true if this function was called with a local register set. /// Returns true if this function was called with a local register set.
pub fn has_local_registers(&self) -> bool { pub fn has_local_registers(&self) -> bool {
self.local_registers.is_some() self.local_registers.is_some()
@ -233,7 +247,11 @@ impl<'gc> Activation<'gc> {
/// Retrieve a local register. /// Retrieve a local register.
pub fn local_register(&self, id: u8) -> Value<'gc> { pub fn local_register(&self, id: u8) -> Value<'gc> {
if let Some(local_registers) = self.local_registers { if let Some(local_registers) = self.local_registers {
local_registers.read().get(id as usize).map(|v| v.clone()).unwrap_or(Value::Undefined) local_registers
.read()
.get(id as usize)
.cloned()
.unwrap_or(Value::Undefined)
} else { } else {
Value::Undefined Value::Undefined
} }
@ -242,7 +260,9 @@ impl<'gc> Activation<'gc> {
/// Set a local register. /// Set a local register.
pub fn set_local_register(&mut self, id: u8, value: Value<'gc>, mc: MutationContext<'gc, '_>) { pub fn set_local_register(&mut self, id: u8, value: Value<'gc>, mc: MutationContext<'gc, '_>) {
if let Some(ref mut local_registers) = self.local_registers { if let Some(ref mut local_registers) = self.local_registers {
local_registers.write(mc).get_mut(id as usize).map(|r| *r = value); if let Some(r) = local_registers.write(mc).get_mut(id as usize) {
*r = value;
}
} }
} }
} }

View File

@ -1,13 +1,13 @@
//! Code relating to executable functions + calling conventions. //! Code relating to executable functions + calling conventions.
use crate::avm1::activation::Activation;
use crate::avm1::object::Object;
use crate::avm1::scope::Scope;
use crate::avm1::value::Value;
use crate::avm1::{ActionContext, Avm1};
use crate::tag_utils::SwfSlice;
use gc_arena::GcCell; use gc_arena::GcCell;
use swf::avm1::types::FunctionParam; use swf::avm1::types::FunctionParam;
use crate::tag_utils::SwfSlice;
use crate::avm1::{Avm1, ActionContext};
use crate::avm1::object::Object;
use crate::avm1::value::Value;
use crate::avm1::scope::Scope;
use crate::avm1::activation::Activation;
pub type NativeFunction<'gc> = fn( pub type NativeFunction<'gc> = fn(
&mut Avm1<'gc>, &mut Avm1<'gc>,
@ -24,7 +24,6 @@ pub struct Avm1Function<'gc> {
/// A reference to the underlying SWF data. /// A reference to the underlying SWF data.
data: SwfSlice, data: SwfSlice,
/// The name of the function, if not anonymous. /// The name of the function, if not anonymous.
name: Option<String>, name: Option<String>,
@ -32,22 +31,28 @@ pub struct Avm1Function<'gc> {
params: Vec<String>, params: Vec<String>,
/// The scope the function was born into. /// The scope the function was born into.
scope: GcCell<'gc, Scope<'gc>> scope: GcCell<'gc, Scope<'gc>>,
} }
impl<'gc> Avm1Function<'gc> { impl<'gc> Avm1Function<'gc> {
pub fn new(swf_version: u8, actions: SwfSlice, name: &str, params: &[&str], scope: GcCell<'gc, Scope<'gc>>) -> Self { pub fn new(
swf_version: u8,
actions: SwfSlice,
name: &str,
params: &[&str],
scope: GcCell<'gc, Scope<'gc>>,
) -> Self {
let name = match name { let name = match name {
"" => None, "" => None,
name => Some(name.to_string()) name => Some(name.to_string()),
}; };
Avm1Function { Avm1Function {
swf_version: swf_version, swf_version,
data: actions, data: actions,
name: name, name,
params: params.into_iter().map(|s| s.to_string()).collect(), params: params.iter().map(|s| s.to_string()).collect(),
scope: scope scope,
} }
} }
@ -60,7 +65,7 @@ impl<'gc> Avm1Function<'gc> {
} }
pub fn scope(&self) -> GcCell<'gc, Scope<'gc>> { pub fn scope(&self) -> GcCell<'gc, Scope<'gc>> {
self.scope.clone() self.scope
} }
} }
@ -79,7 +84,6 @@ pub struct Avm1Function2<'gc> {
/// A reference to the underlying SWF data. /// A reference to the underlying SWF data.
data: SwfSlice, data: SwfSlice,
/// The name of the function, if not anonymous. /// The name of the function, if not anonymous.
name: Option<String>, name: Option<String>,
@ -103,52 +107,46 @@ pub struct Avm1Function2<'gc> {
params: Vec<(Option<u8>, String)>, params: Vec<(Option<u8>, String)>,
/// The scope the function was born into. /// The scope the function was born into.
scope: GcCell<'gc, Scope<'gc>> scope: GcCell<'gc, Scope<'gc>>,
} }
impl<'gc> Avm1Function2<'gc> { impl<'gc> Avm1Function2<'gc> {
pub fn new(swf_version: u8, pub fn new(
swf_version: u8,
actions: SwfSlice, actions: SwfSlice,
name: &str, swf_function: &swf::avm1::types::Function,
register_count: u8, scope: GcCell<'gc, Scope<'gc>>,
preload_parent: bool, ) -> Self {
preload_root: bool, let name = match swf_function.name {
supress_super: bool,
preload_super: bool,
supress_arguments: bool,
preload_arguments: bool,
supress_this: bool,
preload_this: bool,
preload_global: bool,
params: &Vec<FunctionParam>,
scope: GcCell<'gc, Scope<'gc>>) -> Self {
let name = match name {
"" => None, "" => None,
name => Some(name.to_string()) name => Some(name.to_string()),
}; };
let mut owned_params = Vec::new(); let mut owned_params = Vec::new();
for FunctionParam{name: s, register_index: r} in params.into_iter() { for FunctionParam {
owned_params.push((r.clone(), s.to_string())) name: s,
register_index: r,
} in &swf_function.params
{
owned_params.push((*r, s.to_string()))
} }
Avm1Function2 { Avm1Function2 {
swf_version: swf_version, swf_version,
data: actions, data: actions,
name: name, name,
register_count: register_count, register_count: swf_function.params.capacity() as u8,
preload_parent: preload_parent, preload_parent: swf_function.preload_parent,
preload_root: preload_root, preload_root: swf_function.preload_root,
supress_super: supress_super, supress_super: swf_function.suppress_super,
preload_super: preload_super, preload_super: swf_function.preload_super,
supress_arguments: supress_arguments, supress_arguments: swf_function.suppress_super,
preload_arguments: preload_arguments, preload_arguments: swf_function.preload_arguments,
supress_this: supress_this, supress_this: swf_function.suppress_this,
preload_this: preload_this, preload_this: swf_function.preload_this,
preload_global: preload_global, preload_global: swf_function.preload_global,
params: owned_params, params: owned_params,
scope: scope scope,
} }
} }
@ -161,7 +159,7 @@ impl<'gc> Avm1Function2<'gc> {
} }
pub fn scope(&self) -> GcCell<'gc, Scope<'gc>> { pub fn scope(&self) -> GcCell<'gc, Scope<'gc>> {
self.scope.clone() self.scope
} }
pub fn register_count(&self) -> u8 { pub fn register_count(&self) -> u8 {
@ -180,7 +178,7 @@ pub enum Executable<'gc> {
Action(Avm1Function<'gc>), Action(Avm1Function<'gc>),
/// ActionScript data defined by a previous `DefineFunction2` action. /// ActionScript data defined by a previous `DefineFunction2` action.
Action2(Avm1Function2<'gc>) Action2(Avm1Function2<'gc>),
} }
impl<'gc> Executable<'gc> { impl<'gc> Executable<'gc> {
@ -190,7 +188,13 @@ impl<'gc> Executable<'gc> {
/// returns. If on-stack execution is possible, then this function returns /// returns. If on-stack execution is possible, then this function returns
/// a return value you must push onto the stack. Otherwise, you must /// a return value you must push onto the stack. Otherwise, you must
/// create a new stack frame and execute the action data yourself. /// create a new stack frame and execute the action data yourself.
pub fn exec(&self, avm: &mut Avm1<'gc>, ac: &mut ActionContext<'_, 'gc, '_>, this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>]) -> Option<Value<'gc>> { pub fn exec(
&self,
avm: &mut Avm1<'gc>,
ac: &mut ActionContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>],
) -> Option<Value<'gc>> {
match self { match self {
Executable::Native(nf) => Some(nf(avm, ac, this, args)), Executable::Native(nf) => Some(nf(avm, ac, this, args)),
Executable::Action(af) => { Executable::Action(af) => {
@ -201,23 +205,38 @@ impl<'gc> Executable<'gc> {
} }
arguments.force_set("length", Value::Number(args.len() as f64)); arguments.force_set("length", Value::Number(args.len() as f64));
let argcell = GcCell::allocate(ac.gc_context, arguments); let argcell = GcCell::allocate(ac.gc_context, arguments);
let child_scope = GcCell::allocate(ac.gc_context, Scope::new_local_scope(af.scope(), ac.gc_context)); let child_scope = GcCell::allocate(
ac.gc_context,
Scope::new_local_scope(af.scope(), ac.gc_context),
);
for i in 0..args.len() { for i in 0..args.len() {
if let Some(argname) = af.params.get(i) { if let Some(argname) = af.params.get(i) {
child_scope.write(ac.gc_context).define(argname, args.get(i).unwrap().clone(), ac.gc_context); child_scope.write(ac.gc_context).define(
argname,
args.get(i).unwrap().clone(),
ac.gc_context,
);
} }
} }
let frame = Activation::from_function(af.swf_version(), af.data(), child_scope, this, Some(argcell)); let frame = Activation::from_function(
af.swf_version(),
af.data(),
child_scope,
this,
Some(argcell),
);
avm.insert_stack_frame(frame); avm.insert_stack_frame(frame);
None None
}, }
Executable::Action2(af) => { Executable::Action2(af) => {
let child_scope = GcCell::allocate(ac.gc_context, Scope::new_local_scope(af.scope(), ac.gc_context)); let child_scope = GcCell::allocate(
ac.gc_context,
Scope::new_local_scope(af.scope(), ac.gc_context),
);
let mut arguments = Object::object(ac.gc_context); let mut arguments = Object::object(ac.gc_context);
if !af.supress_arguments { if !af.supress_arguments {
for i in 0..args.len() { for i in 0..args.len() {
@ -228,7 +247,13 @@ impl<'gc> Executable<'gc> {
} }
let argcell = GcCell::allocate(ac.gc_context, arguments); let argcell = GcCell::allocate(ac.gc_context, arguments);
let mut frame = Activation::from_function(af.swf_version(), af.data(), child_scope, this, Some(argcell)); let mut frame = Activation::from_function(
af.swf_version(),
af.data(),
child_scope,
this,
Some(argcell),
);
let mut preload_r = 1; let mut preload_r = 1;
if af.preload_this { if af.preload_this {
@ -272,12 +297,15 @@ impl<'gc> Executable<'gc> {
//preloaded registers? What gets done last? //preloaded registers? What gets done last?
for i in 0..args.len() { for i in 0..args.len() {
match (args.get(i), af.params.get(i)) { match (args.get(i), af.params.get(i)) {
(Some(arg), Some((Some(argreg), _argname))) => frame.set_local_register(*argreg, arg.clone(), ac.gc_context), (Some(arg), Some((Some(argreg), _argname))) => {
(Some(arg), Some((None, argname))) => frame.define(argname, arg.clone(), ac.gc_context), frame.set_local_register(*argreg, arg.clone(), ac.gc_context)
}
(Some(arg), Some((None, argname))) => {
frame.define(argname, arg.clone(), ac.gc_context)
}
_ => {} _ => {}
} }
} }
frame.allocate_local_registers(af.register_count(), ac.gc_context); frame.allocate_local_registers(af.register_count(), ac.gc_context);
avm.insert_stack_frame(frame); avm.insert_stack_frame(frame);

View File

@ -1,12 +1,11 @@
//! Represents AVM1 scope chain resolution. //! Represents AVM1 scope chain resolution.
use std::cell::{Ref, RefMut}; use crate::avm1::{ActionContext, Avm1, Object, Value};
use gc_arena::{GcCell, MutationContext}; use gc_arena::{GcCell, MutationContext};
use crate::avm1::{Avm1, ActionContext, Object, Value}; use std::cell::{Ref, RefMut};
/// Indicates what kind of scope a scope is. /// Indicates what kind of scope a scope is.
#[derive(Copy, Clone, #[derive(Copy, Clone, Debug, PartialEq)]
Debug, PartialEq)]
pub enum ScopeClass { pub enum ScopeClass {
/// Scope represents global scope. /// Scope represents global scope.
Global, Global,
@ -21,7 +20,7 @@ pub enum ScopeClass {
/// Scope represents an object added to the scope chain with `with`. /// Scope represents an object added to the scope chain with `with`.
/// It is not inherited when closures are defined. /// It is not inherited when closures are defined.
With With,
} }
/// Represents a scope chain for an AVM1 activation. /// Represents a scope chain for an AVM1 activation.
@ -29,7 +28,7 @@ pub enum ScopeClass {
pub struct Scope<'gc> { pub struct Scope<'gc> {
parent: Option<GcCell<'gc, Scope<'gc>>>, parent: Option<GcCell<'gc, Scope<'gc>>>,
class: ScopeClass, class: ScopeClass,
values: GcCell<'gc, Object<'gc>> values: GcCell<'gc, Object<'gc>>,
} }
unsafe impl<'gc> gc_arena::Collect for Scope<'gc> { unsafe impl<'gc> gc_arena::Collect for Scope<'gc> {
@ -46,30 +45,33 @@ impl<'gc> Scope<'gc> {
Scope { Scope {
parent: None, parent: None,
class: ScopeClass::Global, class: ScopeClass::Global,
values: globals values: globals,
} }
} }
/// Construct a child scope of another scope. /// Construct a child scope of another scope.
pub fn new_local_scope(parent: GcCell<'gc, Self>, mc: MutationContext<'gc, '_>) -> Scope<'gc> { pub fn new_local_scope(parent: GcCell<'gc, Self>, mc: MutationContext<'gc, '_>) -> Scope<'gc> {
Scope { Scope {
parent: Some(parent.clone()), parent: Some(parent),
class: ScopeClass::Local, class: ScopeClass::Local,
values: GcCell::allocate(mc, Object::bare_object()) values: GcCell::allocate(mc, Object::bare_object()),
} }
} }
/// Construct a closure scope to be used as the parent of all local scopes /// Construct a closure scope to be used as the parent of all local scopes
/// when invoking a function. /// when invoking a function.
pub fn new_closure_scope(mut parent: GcCell<'gc, Self>, mc: MutationContext<'gc, '_>) -> GcCell<'gc, Self> { pub fn new_closure_scope(
mut parent: GcCell<'gc, Self>,
mc: MutationContext<'gc, '_>,
) -> GcCell<'gc, Self> {
let mut closure_scope_list = Vec::new(); let mut closure_scope_list = Vec::new();
loop { loop {
if parent.read().class != ScopeClass::With { if parent.read().class != ScopeClass::With {
closure_scope_list.push(parent.clone()); closure_scope_list.push(parent);
} }
let grandparent = parent.read().parent.clone(); let grandparent = parent.read().parent;
if let Some(grandparent) = grandparent { if let Some(grandparent) = grandparent {
parent = grandparent; parent = grandparent;
} else { } else {
@ -79,42 +81,52 @@ impl<'gc> Scope<'gc> {
let mut parent_scope = None; let mut parent_scope = None;
for scope in closure_scope_list.iter().rev() { for scope in closure_scope_list.iter().rev() {
parent_scope = Some(GcCell::allocate(mc, Scope { parent_scope = Some(GcCell::allocate(
mc,
Scope {
parent: parent_scope, parent: parent_scope,
class: scope.read().class, class: scope.read().class,
values: scope.read().values.clone() values: scope.read().values,
})); },
));
} }
if let Some(parent_scope) = parent_scope { if let Some(parent_scope) = parent_scope {
parent_scope parent_scope
} else { } else {
GcCell::allocate(mc, Scope { GcCell::allocate(
mc,
Scope {
parent: None, parent: None,
class: ScopeClass::Global, class: ScopeClass::Global,
values: GcCell::allocate(mc, Object::bare_object()) values: GcCell::allocate(mc, Object::bare_object()),
}) },
)
} }
} }
/// Construct a scope for use with `tellTarget` code where the timeline /// Construct a scope for use with `tellTarget` code where the timeline
/// scope has been replaced with another given object. /// scope has been replaced with another given object.
pub fn new_target_scope(mut parent: GcCell<'gc, Self>, clip: GcCell<'gc, Object<'gc>>, mc: MutationContext<'gc, '_>) -> GcCell<'gc, Self> { pub fn new_target_scope(
mut parent: GcCell<'gc, Self>,
clip: GcCell<'gc, Object<'gc>>,
mc: MutationContext<'gc, '_>,
) -> GcCell<'gc, Self> {
let mut timeline_scope_list = Vec::new(); let mut timeline_scope_list = Vec::new();
loop { loop {
if parent.read().class != ScopeClass::Target { if parent.read().class != ScopeClass::Target {
timeline_scope_list.push(parent.clone()); timeline_scope_list.push(parent);
} else { } else {
let new_scope = Self { let new_scope = Self {
parent: None, parent: None,
class: ScopeClass::Target, class: ScopeClass::Target,
values: clip values: clip,
}; };
timeline_scope_list.push(GcCell::allocate(mc, new_scope)); timeline_scope_list.push(GcCell::allocate(mc, new_scope));
} }
let grandparent = parent.read().parent.clone(); let grandparent = parent.read().parent;
if let Some(grandparent) = grandparent { if let Some(grandparent) = grandparent {
parent = grandparent; parent = grandparent;
} else { } else {
@ -124,21 +136,27 @@ impl<'gc> Scope<'gc> {
let mut parent_scope = None; let mut parent_scope = None;
for scope in timeline_scope_list.iter().rev() { for scope in timeline_scope_list.iter().rev() {
parent_scope = Some(GcCell::allocate(mc, Scope { parent_scope = Some(GcCell::allocate(
mc,
Scope {
parent: parent_scope, parent: parent_scope,
class: scope.read().class, class: scope.read().class,
values: scope.read().values.clone() values: scope.read().values,
})); },
));
} }
if let Some(parent_scope) = parent_scope { if let Some(parent_scope) = parent_scope {
parent_scope parent_scope
} else { } else {
GcCell::allocate(mc, Scope { GcCell::allocate(
mc,
Scope {
parent: None, parent: None,
class: ScopeClass::Global, class: ScopeClass::Global,
values: GcCell::allocate(mc, Object::bare_object()) values: GcCell::allocate(mc, Object::bare_object()),
}) },
)
} }
} }
@ -147,30 +165,42 @@ impl<'gc> Scope<'gc> {
/// A with block inserts the values of a particular object into the scope /// A with block inserts the values of a particular object into the scope
/// of currently running code, while still maintaining the same local /// of currently running code, while still maintaining the same local
/// scope. This requires some scope chain juggling. /// scope. This requires some scope chain juggling.
pub fn new_with_scope(locals: GcCell<'gc, Self>, pub fn new_with_scope(
locals: GcCell<'gc, Self>,
with_object: GcCell<'gc, Object<'gc>>, with_object: GcCell<'gc, Object<'gc>>,
mc: MutationContext<'gc, '_>) -> GcCell<'gc, Self> { mc: MutationContext<'gc, '_>,
let parent_scope = locals.read().parent.clone(); ) -> GcCell<'gc, Self> {
let local_values = locals.read().values.clone(); let parent_scope = locals.read().parent;
let with_scope = GcCell::allocate(mc, Scope { let local_values = locals.read().values;
let with_scope = GcCell::allocate(
mc,
Scope {
parent: parent_scope, parent: parent_scope,
class: ScopeClass::With, class: ScopeClass::With,
values: with_object values: with_object,
}); },
);
GcCell::allocate(mc, Scope { GcCell::allocate(
mc,
Scope {
parent: Some(with_scope), parent: Some(with_scope),
class: ScopeClass::Local, class: ScopeClass::Local,
values: local_values 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 {
parent: Some(parent.clone()), parent: Some(parent),
class: class, class,
values: with_object values: with_object,
} }
} }
@ -188,7 +218,7 @@ impl<'gc> Scope<'gc> {
pub fn parent(&self) -> Option<Ref<Scope<'gc>>> { pub fn parent(&self) -> Option<Ref<Scope<'gc>>> {
match self.parent { match self.parent {
Some(ref p) => Some(p.read()), Some(ref p) => Some(p.read()),
None => None None => None,
} }
} }
@ -197,7 +227,6 @@ impl<'gc> Scope<'gc> {
if self.locals().has_property(name) { if self.locals().has_property(name) {
return self.locals().force_get(name); return self.locals().force_get(name);
} }
if let Some(scope) = self.parent() { if let Some(scope) = self.parent() {
return scope.resolve(name); return scope.resolve(name);
} }
@ -226,14 +255,17 @@ impl<'gc> Scope<'gc> {
/// until we find a defined value to overwrite. We do not define a property /// until we find a defined value to overwrite. We do not define a property
/// if it is not already defined somewhere in the scope chain, and instead /// if it is not already defined somewhere in the scope chain, and instead
/// return it so that the caller may manually define the property itself. /// return it so that the caller may manually define the property itself.
pub fn overwrite(&self, pub fn overwrite(
&self,
name: &str, name: &str,
value: Value<'gc>, value: Value<'gc>,
avm: &mut Avm1<'gc>, avm: &mut Avm1<'gc>,
context: &mut ActionContext<'_, 'gc, '_>, context: &mut ActionContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>) -> Option<Value<'gc>> { this: GcCell<'gc, Object<'gc>>,
) -> Option<Value<'gc>> {
if self.locals().has_property(name) { if self.locals().has_property(name) {
self.locals_mut(context.gc_context).set(name, value, avm, context, this); self.locals_mut(context.gc_context)
.set(name, value, avm, context, this);
return None; return None;
} }