From a2ee7f9e3a8beabad6e883bf9d35ed843f906ab5 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 21 Oct 2019 18:37:04 -0400 Subject: [PATCH] Replace `Option>` with a dedicated `ReturnValue<'gc>` type with associated methods. This type explicitly signals if an immediate value is to be returned, if a value is to be returned on the stack, or if no return value is to be generated. Holders of a `ReturnValue` can also use `and_then` to schedule a `StackContinuation` to be executed when and if that value is ready. `StackContinuations` now yield `ReturnValues` as well, so they have a moderate level of composability. For example, if you need to get a property from an object and push it on the stack, you can return the result of calling `get` directly and the machinery ensures it eventually gets there. --- core/src/avm1.rs | 199 ++++++++++------------------ core/src/avm1/activation.rs | 7 +- core/src/avm1/function.rs | 56 +++++--- core/src/avm1/globals.rs | 33 ++--- core/src/avm1/globals/math.rs | 35 ++--- core/src/avm1/movie_clip.rs | 36 +++-- core/src/avm1/object.rs | 64 +++++---- core/src/avm1/return_value.rs | 139 +++++++++++++++++++ core/src/avm1/scope.rs | 5 +- core/src/avm1/stack_continuation.rs | 39 ++---- core/src/avm1/test_utils.rs | 6 +- core/src/avm1/tests.rs | 3 +- core/src/avm1/value.rs | 3 +- 13 files changed, 363 insertions(+), 262 deletions(-) create mode 100644 core/src/avm1/return_value.rs diff --git a/core/src/avm1.rs b/core/src/avm1.rs index 120a898b0..371d9416b 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -1,6 +1,7 @@ use crate::avm1::function::Avm1Function; use crate::avm1::globals::create_globals; use crate::avm1::object::Object; +use crate::avm1::return_value::ReturnValue::*; use crate::backend::navigator::NavigationMethod; use crate::context::UpdateContext; use crate::prelude::*; @@ -14,12 +15,15 @@ use swf::avm1::types::{Action, Function}; use crate::tag_utils::SwfSlice; +#[macro_use] +mod stack_continuation; mod activation; mod fscommand; mod function; mod globals; pub mod movie_clip; pub mod object; +mod return_value; mod scope; mod value; @@ -29,9 +33,6 @@ mod test_utils; #[cfg(test)] mod tests; -#[macro_use] -mod stack_continuation; - use activation::Activation; use scope::Scope; pub use value::Value; @@ -111,7 +112,9 @@ impl<'gc> Avm1<'gc> { for k in keys { let v = locals.read().get(&k, self, context, locals); - if let Some(instant_value) = v { + + //TODO: Support on-stack properties + if let Immediate(instant_value) = v { form_values.insert(k, instant_value.clone().into_string()); } } @@ -177,13 +180,8 @@ impl<'gc> Avm1<'gc> { } /// Add a stack frame for any arbitrary code. - pub fn insert_stack_frame( - &mut self, - frame: Activation<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, - ) { - self.stack_frames - .push(GcCell::allocate(context.gc_context, frame)); + pub fn insert_stack_frame(&mut self, frame: GcCell<'gc, Activation<'gc>>) { + self.stack_frames.push(frame); } /// Retrieve the current AVM execution frame. @@ -275,12 +273,14 @@ impl<'gc> Avm1<'gc> { let can_return = !self.stack_frames.is_empty(); if let Some(func) = frame.write(context.gc_context).get_then_func() { - let is_continuing = func.returned(self, context, return_value)?; - if is_continuing { - if let Some(fr) = self.current_stack_frame() { - fr.write(context.gc_context) + match func.returned(self, context, return_value)? { + Immediate(val) => self.stack.push(val), + ResultOf(new_frame) => { + new_frame + .write(context.gc_context) .and_again(frame, context.gc_context); } + NoResult => {} } } else if can_return { self.stack.push(return_value); @@ -694,29 +694,17 @@ impl<'gc> Avm1<'gc> { ); let this = context.active_clip.read().object().as_object()?.to_owned(); - if let Some(target_fn) = target_fn { - let return_value = target_fn.call(self, context, this, &args)?; - if let Some(instant_return) = return_value { - self.push(instant_return); - } - } else { - self.stack_frames - .last() - .unwrap() - .write(context.gc_context) - .and_then(stack_continuation!( + target_fn + .and_then( + self, + context, + stack_continuation!( this: GcCell<'gc, Object<'gc>>, args: Vec>, - |avm, context, target_fn| { - let return_value = target_fn.call(avm, context, *this, &args)?; - if let Some(instant_return) = return_value { - avm.push(instant_return); - } - - Ok(()) - } - )) - } + |avm, context, target_fn| { target_fn.call(avm, context, *this, &args) } + ), + )? + .push(self); Ok(()) } @@ -736,47 +724,26 @@ impl<'gc> Avm1<'gc> { match method_name { Value::Undefined | Value::Null => { let this = context.active_clip.read().object().as_object()?.to_owned(); - let return_value = object.call(self, context, this, &args)?; - if let Some(instant_return) = return_value { - self.push(instant_return); - } + object.call(self, context, this, &args)?.push(self); } Value::String(name) => { if name.is_empty() { - let return_value = - object.call(self, context, object.as_object()?.to_owned(), &args)?; - if let Some(instant_return) = return_value { - self.push(instant_return); - } + object + .call(self, context, object.as_object()?.to_owned(), &args)? + .push(self); } else { - let callable = object.as_object()?.read().get( - &name, - self, - context, - object.as_object()?.to_owned(), - ); - - if let Some(Value::Undefined) = callable { - return Err(format!("Object method {} is not defined", name).into()); - } else if let Some(instant_fn) = callable { - let return_value = instant_fn.call( + let target = object.as_object()?.to_owned(); + object + .as_object()? + .read() + .get(&name, self, context, target) + .and_then( self, context, - object.as_object()?.to_owned(), - &args, - )?; - if let Some(instant_return) = return_value { - self.push(instant_return); - } - } else { - self.stack_frames - .last() - .unwrap() - .write(context.gc_context) - .and_then(stack_continuation!( - args: Vec>, + stack_continuation!( + target: GcCell<'gc, Object<'gc>>, name: String, - object: Value<'gc>, + args: Vec>, |avm, context, callable| { if let Value::Undefined = callable { return Err(format!( @@ -786,19 +753,11 @@ impl<'gc> Avm1<'gc> { .into()); } - let return_value = callable.call( - avm, - context, - object.as_object()?.to_owned(), - &args, - )?; - if let Some(instant_return) = return_value { - avm.push(instant_return) - } - Ok(()) + callable.call(avm, context, *target, &args) } - )); - } + ), + )? + .push(self); } } _ => { @@ -972,48 +931,30 @@ impl<'gc> Avm1<'gc> { let name_value = self.pop()?; let name = name_value.as_string()?; self.push(Value::Null); // Sentinel that indicates end of enumeration - match self - .current_stack_frame() + self.current_stack_frame() .unwrap() .read() .resolve(name, self, context) - { - Some(Value::Object(ob)) => { - for k in ob.read().get_keys() { - self.push(Value::String(k)); - } - } - Some(_) => { - log::error!("Cannot enumerate properties of {}", name); - - return Ok(()); //TODO: This is NOT OK(()). - } - None => self - .stack_frames - .last() - .unwrap() - .write(context.gc_context) - .and_then(stack_continuation!( - name_value: Value<'gc>, - |avm, _ctxt, return_value| { - match return_value { - Value::Object(ob) => { - for k in ob.read().get_keys() { - avm.push(Value::String(k)); - } - } - _ => { - log::error!( - "Cannot enumerate properties of {}", - name_value.as_string()? - ); + .and_then( + self, + context, + stack_continuation!(name_value: Value<'gc>, |avm, _context, object| { + match object { + Value::Object(ob) => { + for k in ob.read().get_keys() { + avm.push(Value::String(k)); } } + _ => log::error!( + "Cannot enumerate properties of {}", + name_value.as_string()? + ), + }; - Ok(()) - } - )), - }; + Ok(ReturnValue::NoResult) + }), + )? + .ignore(); Ok(()) } @@ -1070,10 +1011,7 @@ impl<'gc> Avm1<'gc> { let name = name_val.into_string(); let object = self.pop()?.as_object()?; let this = self.current_stack_frame().unwrap().read().this_cell(); - let value = object.read().get(&name, self, context, this); - if let Some(value) = value { - self.push(value); - } + object.read().get(&name, self, context, this).push(self); Ok(()) } @@ -1160,10 +1098,10 @@ impl<'gc> Avm1<'gc> { if let Some(clip) = node.read().as_movie_clip() { let object = clip.object().as_object()?; if object.read().has_property(var_name) { - let result = object.read().get(var_name, self, context, object); - if let Some(value) = result { - self.push(value); - } //NOTE: Natural return is sufficient for custom get + object + .read() + .get(var_name, self, context, object) + .push(self); } else { self.push(Value::Undefined); } @@ -1172,14 +1110,11 @@ impl<'gc> Avm1<'gc> { } } } else if self.current_stack_frame().unwrap().read().is_defined(path) { - let result = self - .current_stack_frame() + self.current_stack_frame() .unwrap() .read() - .resolve(path, self, context); - if let Some(value) = result { - self.push(value); - } //NOTE: Natural return is sufficient for custom get + .resolve(path, self, context) + .push(self); } else { self.push(Value::Undefined); } diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index dffa3aae5..49e845474 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -1,6 +1,7 @@ //! Activation records use crate::avm1::object::Object; +use crate::avm1::return_value::ReturnValue; use crate::avm1::scope::Scope; use crate::avm1::stack_continuation::StackContinuation; use crate::avm1::{Avm1, Value}; @@ -261,13 +262,13 @@ impl<'gc> Activation<'gc> { name: &str, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Option> { + ) -> ReturnValue<'gc> { if name == "this" { - return Some(Value::Object(self.this)); + return ReturnValue::Immediate(Value::Object(self.this)); } if name == "arguments" && self.arguments.is_some() { - return Some(Value::Object(self.arguments.unwrap())); + return ReturnValue::Immediate(Value::Object(self.arguments.unwrap())); } self.scope().resolve(name, avm, context, self.this) diff --git a/core/src/avm1/function.rs b/core/src/avm1/function.rs index d57a88f69..019eef954 100644 --- a/core/src/avm1/function.rs +++ b/core/src/avm1/function.rs @@ -2,6 +2,7 @@ use crate::avm1::activation::Activation; use crate::avm1::object::{Attribute::*, Object}; +use crate::avm1::return_value::ReturnValue; use crate::avm1::scope::Scope; use crate::avm1::value::Value; use crate::avm1::{Avm1, UpdateContext}; @@ -28,7 +29,7 @@ pub type NativeFunction<'gc> = fn( &mut UpdateContext<'_, 'gc, '_>, GcCell<'gc, Object<'gc>>, &[Value<'gc>], -) -> Option>; +) -> ReturnValue<'gc>; /// Represents a function defined in the AVM1 runtime, either through /// `DefineFunction` or `DefineFunction2`. @@ -183,7 +184,7 @@ impl<'gc> Executable<'gc> { ac: &mut UpdateContext<'_, 'gc, '_>, this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], - ) -> Option> { + ) -> ReturnValue<'gc> { match self { Executable::Native(nf) => nf(avm, ac, this, args), Executable::Action(af) => { @@ -218,13 +219,17 @@ impl<'gc> Executable<'gc> { .unwrap_or(ac.player_version) }; - let mut frame = Activation::from_function( - effective_ver, - af.data(), - child_scope, - this, - Some(argcell), + let frame_cell = GcCell::allocate( + ac.gc_context, + Activation::from_function( + effective_ver, + af.data(), + child_scope, + this, + Some(argcell), + ), ); + let mut frame = frame_cell.write(ac.gc_context); frame.allocate_local_registers(af.register_count(), ac.gc_context); @@ -258,12 +263,31 @@ impl<'gc> Executable<'gc> { } if af.preload_parent { - let parent = child_scope.read().resolve("_parent", avm, ac, this); - if let Some(instant_parent) = parent { - frame.set_local_register(preload_r, instant_parent, ac.gc_context); - } else { - log::error!("User-defined virtual _parent is NOT supported!"); - } + let parent_preload_r = preload_r; + + child_scope + .read() + .resolve("_parent", avm, ac, this) + .and_then( + avm, + ac, + stack_continuation!( + frame_cell: GcCell<'gc, Activation<'gc>>, + parent_preload_r: u8, + |_avm, ac, parent| { + frame_cell.write(ac.gc_context).set_local_register( + *parent_preload_r, + parent, + ac.gc_context, + ); + + Ok(ReturnValue::NoResult) + } + ), + ) + .unwrap() + .ignore(); + preload_r += 1; } @@ -284,9 +308,9 @@ impl<'gc> Executable<'gc> { _ => {} } } - avm.insert_stack_frame(frame, ac); + avm.insert_stack_frame(frame_cell); - None + ReturnValue::ResultOf(frame_cell) } } } diff --git a/core/src/avm1/globals.rs b/core/src/avm1/globals.rs index 4bb0ea0a7..d189c4cd8 100644 --- a/core/src/avm1/globals.rs +++ b/core/src/avm1/globals.rs @@ -1,4 +1,5 @@ use crate::avm1::fscommand; +use crate::avm1::return_value::ReturnValue; use crate::avm1::{Avm1, Object, UpdateContext, Value}; use crate::backend::navigator::NavigationMethod; use enumset::EnumSet; @@ -13,13 +14,13 @@ pub fn getURL<'a, 'gc>( context: &mut UpdateContext<'a, 'gc, '_>, _this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], -) -> Option> { +) -> ReturnValue<'gc> { //TODO: Error behavior if no arguments are present if let Some(url_val) = args.get(0) { let url = url_val.clone().into_string(); if let Some(fscommand) = fscommand::parse(&url) { fscommand::handle(fscommand, avm, context); - return Some(Value::Undefined); + return ReturnValue::Immediate(Value::Undefined); } let window = args.get(1).map(|v| v.clone().into_string()); @@ -33,7 +34,7 @@ pub fn getURL<'a, 'gc>( context.navigator.navigate_to_url(url, window, vars_method); } - Some(Value::Undefined) + ReturnValue::Immediate(Value::Undefined) } pub fn random<'gc>( @@ -41,12 +42,12 @@ pub fn random<'gc>( action_context: &mut UpdateContext<'_, 'gc, '_>, _this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], -) -> Option> { +) -> ReturnValue<'gc> { match args.get(0) { - Some(Value::Number(max)) => Some(Value::Number( + Some(Value::Number(max)) => ReturnValue::Immediate(Value::Number( action_context.rng.gen_range(0.0f64, max).floor(), )), - _ => Some(Value::Undefined), //TODO: Shouldn't this be an error condition? + _ => ReturnValue::Immediate(Value::Undefined), //TODO: Shouldn't this be an error condition? } } @@ -55,11 +56,11 @@ pub fn boolean<'gc>( _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], -) -> Option> { +) -> ReturnValue<'gc> { if let Some(val) = args.get(0) { - Some(Value::Bool(val.as_bool(avm.current_swf_version()))) + ReturnValue::Immediate(Value::Bool(val.as_bool(avm.current_swf_version()))) } else { - Some(Value::Bool(false)) + ReturnValue::Immediate(Value::Bool(false)) } } @@ -68,11 +69,11 @@ pub fn number<'gc>( _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], -) -> Option> { +) -> ReturnValue<'gc> { if let Some(val) = args.get(0) { - Some(Value::Number(val.as_number())) + ReturnValue::Immediate(Value::Number(val.as_number())) } else { - Some(Value::Number(0.0)) + ReturnValue::Immediate(Value::Number(0.0)) } } @@ -81,11 +82,11 @@ pub fn is_nan<'gc>( _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], -) -> Option> { +) -> ReturnValue<'gc> { if let Some(val) = args.get(0) { - Some(Value::Bool(val.as_number().is_nan())) + ReturnValue::Immediate(Value::Bool(val.as_number().is_nan())) } else { - Some(Value::Bool(true)) + ReturnValue::Immediate(Value::Bool(true)) } } @@ -128,7 +129,7 @@ mod tests { $( args.push($arg.into()); )* - assert_eq!($fun(avm, context, this, &args), Some($out.into())); + assert_eq!($fun(avm, context, this, &args), ReturnValue::Immediate($out.into())); )* Ok(()) diff --git a/core/src/avm1/globals/math.rs b/core/src/avm1/globals/math.rs index a4bf1e68b..b41788a9b 100644 --- a/core/src/avm1/globals/math.rs +++ b/core/src/avm1/globals/math.rs @@ -1,4 +1,5 @@ use crate::avm1::object::Attribute::*; +use crate::avm1::return_value::ReturnValue; use crate::avm1::{Avm1, Object, UpdateContext, Value}; use gc_arena::{GcCell, MutationContext}; use rand::Rng; @@ -9,11 +10,11 @@ macro_rules! wrap_std { $( $object.force_set_function( $name, - |_avm, _context, _this, args| -> Option> { + |_avm, _context, _this, args| -> ReturnValue<'gc> { if let Some(input) = args.get(0) { - Some(Value::Number($std(input.as_number()))) + ReturnValue::Immediate(Value::Number($std(input.as_number()))) } else { - Some(Value::Number(NAN)) + ReturnValue::Immediate(Value::Number(NAN)) } }, $gc_context, @@ -28,15 +29,15 @@ fn atan2<'gc>( _context: &mut UpdateContext<'_, 'gc, '_>, _this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], -) -> Option> { +) -> ReturnValue<'gc> { if let Some(y) = args.get(0) { if let Some(x) = args.get(1) { - return Some(Value::Number(y.as_number().atan2(x.as_number()))); + return ReturnValue::Immediate(Value::Number(y.as_number().atan2(x.as_number()))); } else { - return Some(Value::Number(y.as_number().atan2(0.0))); + return ReturnValue::Immediate(Value::Number(y.as_number().atan2(0.0))); } } - Some(Value::Number(NAN)) + ReturnValue::Immediate(Value::Number(NAN)) } pub fn random<'gc>( @@ -44,8 +45,8 @@ pub fn random<'gc>( action_context: &mut UpdateContext<'_, 'gc, '_>, _this: GcCell<'gc, Object<'gc>>, _args: &[Value<'gc>], -) -> Option> { - Some(Value::Number(action_context.rng.gen_range(0.0f64, 1.0f64))) +) -> ReturnValue<'gc> { + ReturnValue::Immediate(Value::Number(action_context.rng.gen_range(0.0f64, 1.0f64))) } pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> GcCell<'gc, Object<'gc>> { @@ -130,7 +131,7 @@ mod tests { fn $test() -> Result<(), Error> { with_avm(19, |avm, context, _root| { let math = create(context.gc_context); - let function = math.read().get($name, avm, context, math).unwrap(); + let function = math.read().get($name, avm, context, math).unwrap_immediate(); $( #[allow(unused_mut)] @@ -138,7 +139,7 @@ mod tests { $( args.push($arg.into()); )* - assert_eq!(function.call(avm, context, math, &args)?, Some($out.into())); + assert_eq!(function.call(avm, context, math, &args)?, ReturnValue::Immediate($out.into())); )* Ok(()) @@ -238,7 +239,7 @@ mod tests { let math = GcCell::allocate(context.gc_context, create(context.gc_context)); assert_eq!( atan2(avm, context, *math.read(), &[]), - Some(Value::Number(NAN)) + ReturnValue::Immediate(Value::Number(NAN)) ); assert_eq!( atan2( @@ -247,7 +248,7 @@ mod tests { *math.read(), &[Value::Number(1.0), Value::Null] ), - Some(Value::Number(NAN)) + ReturnValue::Immediate(Value::Number(NAN)) ); assert_eq!( atan2( @@ -256,7 +257,7 @@ mod tests { *math.read(), &[Value::Number(1.0), Value::Undefined] ), - Some(Value::Number(NAN)) + ReturnValue::Immediate(Value::Number(NAN)) ); assert_eq!( atan2( @@ -265,7 +266,7 @@ mod tests { *math.read(), &[Value::Undefined, Value::Number(1.0)] ), - Some(Value::Number(NAN)) + ReturnValue::Immediate(Value::Number(NAN)) ); }); } @@ -276,7 +277,7 @@ mod tests { let math = GcCell::allocate(context.gc_context, create(context.gc_context)); assert_eq!( atan2(avm, context, *math.read(), &[Value::Number(10.0)]), - Some(Value::Number(std::f64::consts::FRAC_PI_2)) + ReturnValue::Immediate(Value::Number(std::f64::consts::FRAC_PI_2)) ); assert_eq!( atan2( @@ -285,7 +286,7 @@ mod tests { *math.read(), &[Value::Number(1.0), Value::Number(2.0)] ), - Some(Value::Number(f64::atan2(1.0, 2.0))) + ReturnValue::Immediate(Value::Number(f64::atan2(1.0, 2.0))) ); }); } diff --git a/core/src/avm1/movie_clip.rs b/core/src/avm1/movie_clip.rs index 46afa681b..f7c61f0d4 100644 --- a/core/src/avm1/movie_clip.rs +++ b/core/src/avm1/movie_clip.rs @@ -1,7 +1,8 @@ use crate::avm1::function::Executable; use crate::avm1::object::{Attribute::*, Object}; +use crate::avm1::return_value::ReturnValue; use crate::avm1::{Avm1, UpdateContext, Value}; -use crate::display_object::{DisplayNode, MovieClip}; +use crate::display_object::{DisplayNode, DisplayObject, MovieClip}; use enumset::EnumSet; use gc_arena::{GcCell, MutationContext}; @@ -10,13 +11,13 @@ macro_rules! with_movie_clip { $( $object.force_set_function( $name, - |_avm, _context, this, args| -> Option> { + |_avm, _context, this, args| -> ReturnValue<'gc> { if let Some(display_object) = this.read().display_node() { if let Some(movie_clip) = display_object.read().as_movie_clip() { - return Some($fn(movie_clip, args)); + return ReturnValue::Immediate($fn(movie_clip, args)); } } - Some(Value::Undefined) + ReturnValue::Immediate(Value::Undefined) }, $gc_context, DontDelete | ReadOnly | DontEnum, @@ -30,13 +31,13 @@ macro_rules! with_movie_clip_mut { $( $object.force_set_function( $name, - |_avm, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> Option> { + |_avm, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> ReturnValue<'gc> { if let Some(display_object) = this.read().display_node() { if let Some(movie_clip) = display_object.write(context.gc_context).as_movie_clip_mut() { - return Some($fn(movie_clip, context, display_object, args)); + return ReturnValue::Immediate($fn(movie_clip, context, display_object, args)); } } - Some(Value::Undefined) + ReturnValue::Immediate(Value::Undefined) } as crate::avm1::function::NativeFunction<'gc>, $gc_context, DontDelete | ReadOnly | DontEnum, @@ -50,7 +51,7 @@ pub fn overwrite_root<'gc>( ac: &mut UpdateContext<'_, 'gc, '_>, this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], -) -> Option> { +) -> ReturnValue<'gc> { let new_val = args .get(0) .map(|v| v.to_owned()) @@ -58,7 +59,7 @@ pub fn overwrite_root<'gc>( this.write(ac.gc_context) .force_set("_root", new_val, EnumSet::new()); - Some(Value::Undefined) + ReturnValue::Immediate(Value::Undefined) } pub fn overwrite_global<'gc>( @@ -66,7 +67,7 @@ pub fn overwrite_global<'gc>( ac: &mut UpdateContext<'_, 'gc, '_>, this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], -) -> Option> { +) -> ReturnValue<'gc> { let new_val = args .get(0) .map(|v| v.to_owned()) @@ -74,7 +75,7 @@ pub fn overwrite_global<'gc>( this.write(ac.gc_context) .force_set("_global", new_val, EnumSet::new()); - Some(Value::Undefined) + ReturnValue::Immediate(Value::Undefined) } pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<'gc> { @@ -111,19 +112,26 @@ pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object< "getBytesTotal" => |_movie_clip: &MovieClip<'gc>, _args| { // TODO find a correct value Value::Number(1.0) + }, + "toString" => |movie_clip: &MovieClip, _args| { + Value::String(movie_clip.name().to_string()) } ); object.force_set_virtual( "_global", - Executable::Native(|avm, context, _this, _args| Some(avm.global_object(context))), + Executable::Native(|avm, context, _this, _args| { + ReturnValue::Immediate(avm.global_object(context)) + }), Some(Executable::Native(overwrite_global)), EnumSet::new(), ); object.force_set_virtual( "_root", - Executable::Native(|avm, context, _this, _args| Some(avm.root_object(context))), + Executable::Native(|avm, context, _this, _args| { + ReturnValue::Immediate(avm.root_object(context)) + }), Some(Executable::Native(overwrite_root)), EnumSet::new(), ); @@ -131,7 +139,7 @@ pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object< object.force_set_virtual( "_parent", Executable::Native(|_avm, _context, this, _args| { - Some( + ReturnValue::Immediate( this.read() .display_node() .and_then(|mc| mc.read().parent()) diff --git a/core/src/avm1/object.rs b/core/src/avm1/object.rs index c3d7f4177..ee181e438 100644 --- a/core/src/avm1/object.rs +++ b/core/src/avm1/object.rs @@ -1,5 +1,6 @@ use self::Attribute::*; use crate::avm1::function::{Avm1Function, Executable, NativeFunction}; +use crate::avm1::return_value::ReturnValue; use crate::avm1::{Avm1, UpdateContext, Value}; use crate::display_object::DisplayNode; use core::fmt; @@ -18,8 +19,8 @@ fn default_to_string<'gc>( _: &mut UpdateContext<'_, 'gc, '_>, _: GcCell<'gc, Object<'gc>>, _: &[Value<'gc>], -) -> Option> { - Some("[Object object]".into()) +) -> ReturnValue<'gc> { + ReturnValue::Immediate("[Object object]".into()) } #[derive(EnumSetType, Debug)] @@ -53,10 +54,10 @@ impl<'gc> Property<'gc> { avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: GcCell<'gc, Object<'gc>>, - ) -> Option> { + ) -> ReturnValue<'gc> { match self { Property::Virtual { get, .. } => get.exec(avm, context, this, &[]), - Property::Stored { value, .. } => Some(value.to_owned()), + Property::Stored { value, .. } => ReturnValue::Immediate(value.to_owned()), } } @@ -77,7 +78,7 @@ impl<'gc> Property<'gc> { Property::Virtual { set, .. } => { if let Some(function) = set { let return_value = function.exec(avm, context, this, &[new_value.into()]); - return_value.is_some() + return_value.is_immediate() } else { true } @@ -310,11 +311,12 @@ impl<'gc> Object<'gc> { avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: GcCell<'gc, Object<'gc>>, - ) -> Option> { + ) -> ReturnValue<'gc> { if let Some(value) = self.values.get(name) { return value.get(avm, context, this); } - Some(Value::Undefined) + + ReturnValue::Immediate(Value::Undefined) } /// Delete a given value off the object. @@ -356,11 +358,11 @@ impl<'gc> Object<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], - ) -> Option> { + ) -> ReturnValue<'gc> { if let Some(function) = &self.function { function.exec(avm, context, this, args) } else { - Some(Value::Undefined) + ReturnValue::Immediate(Value::Undefined) } } @@ -437,10 +439,10 @@ mod tests { let object = GcCell::allocate(gc_context, Object::object(gc_context)); let globals = avm.global_object_cell(); - avm.insert_stack_frame( + avm.insert_stack_frame(GcCell::allocate( + gc_context, Activation::from_nothing(swf_version, globals, gc_context), - &mut context, - ); + )); test(&mut avm, &mut context, object) }) @@ -451,7 +453,7 @@ mod tests { with_object(0, |avm, context, object| { assert_eq!( object.read().get("not_defined", avm, context, object), - Some(Value::Undefined) + ReturnValue::Immediate(Value::Undefined) ); }) } @@ -468,11 +470,11 @@ mod tests { assert_eq!( object.read().get("forced", avm, context, object), - Some("forced".into()) + ReturnValue::Immediate("forced".into()) ); assert_eq!( object.read().get("natural", avm, context, object), - Some("natural".into()) + ReturnValue::Immediate("natural".into()) ); }) } @@ -496,11 +498,11 @@ mod tests { assert_eq!( object.read().get("normal", avm, context, object), - Some("replaced".into()) + ReturnValue::Immediate("replaced".into()) ); assert_eq!( object.read().get("readonly", avm, context, object), - Some("initial".into()) + ReturnValue::Immediate("initial".into()) ); }) } @@ -515,7 +517,7 @@ mod tests { assert_eq!(object.write(context.gc_context).delete("test"), false); assert_eq!( object.read().get("test", avm, context, object), - Some("initial".into()) + ReturnValue::Immediate("initial".into()) ); object @@ -525,7 +527,7 @@ mod tests { assert_eq!(object.write(context.gc_context).delete("test"), false); assert_eq!( object.read().get("test", avm, context, object), - Some("replaced".into()) + ReturnValue::Immediate("replaced".into()) ); }) } @@ -533,7 +535,9 @@ mod tests { #[test] fn test_virtual_get() { with_object(0, |avm, context, object| { - let getter = Executable::Native(|_avm, _context, _this, _args| Some("Virtual!".into())); + let getter = Executable::Native(|_avm, _context, _this, _args| { + ReturnValue::Immediate("Virtual!".into()) + }); object.write(context.gc_context).force_set_virtual( "test", @@ -544,7 +548,7 @@ mod tests { assert_eq!( object.read().get("test", avm, context, object), - Some("Virtual!".into()) + ReturnValue::Immediate("Virtual!".into()) ); // This set should do nothing @@ -553,7 +557,7 @@ mod tests { .set("test", "Ignored!", avm, context, object); assert_eq!( object.read().get("test", avm, context, object), - Some("Virtual!".into()) + ReturnValue::Immediate("Virtual!".into()) ); }) } @@ -561,7 +565,9 @@ mod tests { #[test] fn test_delete() { with_object(0, |avm, context, object| { - let getter = Executable::Native(|_avm, _context, _this, _args| Some("Virtual!".into())); + let getter = Executable::Native(|_avm, _context, _this, _args| { + ReturnValue::Immediate("Virtual!".into()) + }); object.write(context.gc_context).force_set_virtual( "virtual", @@ -593,19 +599,19 @@ mod tests { assert_eq!( object.read().get("virtual", avm, context, object), - Some(Value::Undefined) + ReturnValue::Immediate(Value::Undefined) ); assert_eq!( object.read().get("virtual_un", avm, context, object), - Some("Virtual!".into()) + ReturnValue::Immediate("Virtual!".into()) ); assert_eq!( object.read().get("stored", avm, context, object), - Some(Value::Undefined) + ReturnValue::Immediate(Value::Undefined) ); assert_eq!( object.read().get("stored_un", avm, context, object), - Some("Stored!".into()) + ReturnValue::Immediate("Stored!".into()) ); }) } @@ -613,7 +619,9 @@ mod tests { #[test] fn test_iter_values() { with_object(0, |_avm, context, object| { - let getter = Executable::Native(|_avm, _context, _this, _args| Some(Value::Null)); + let getter = Executable::Native(|_avm, _context, _this, _args| { + ReturnValue::Immediate(Value::Null) + }); object .write(context.gc_context) diff --git a/core/src/avm1/return_value.rs b/core/src/avm1/return_value.rs new file mode 100644 index 000000000..717b9eefb --- /dev/null +++ b/core/src/avm1/return_value.rs @@ -0,0 +1,139 @@ +//! Return value enum + +use crate::avm1::activation::Activation; +use crate::avm1::stack_continuation::StackContinuation; +use crate::avm1::{Avm1, Error, Value}; +use crate::context::UpdateContext; +use gc_arena::{Collect, GcCell}; +use std::fmt; + +/// Represents a value which can be returned immediately or at a later time. +#[must_use = "Return values must be used"] +#[derive(Clone)] +pub enum ReturnValue<'gc> { + /// Indicates that the return value is available immediately. + Immediate(Value<'gc>), + + /// Indicates that the return value will be calculated on the stack. + ResultOf(GcCell<'gc, Activation<'gc>>), + + /// Indicates that there is no value to return. + /// + /// This is primarily intended to signal to the AVM that a given stack + /// frame should not cause a value to be pushed to the stack when it + /// returns. + NoResult, +} + +unsafe impl<'gc> Collect for ReturnValue<'gc> { + #[inline] + fn trace(&self, cc: gc_arena::CollectionContext) { + use ReturnValue::*; + + match self { + Immediate(value) => value.trace(cc), + ResultOf(frame) => frame.trace(cc), + NoResult => {} + } + } +} + +impl PartialEq for ReturnValue<'_> { + fn eq(&self, other: &Self) -> bool { + use ReturnValue::*; + + match (self, other) { + (Immediate(val1), Immediate(val2)) => val1 == val2, + (ResultOf(frame1), ResultOf(frame2)) => GcCell::ptr_eq(*frame1, *frame2), + (NoResult, NoResult) => true, + _ => false, + } + } +} + +impl fmt::Debug for ReturnValue<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use ReturnValue::*; + + match self { + Immediate(val) => write!(f, "Immediate({:?})", val), + ResultOf(_frame) => write!(f, "ResultOf()"), + NoResult => write!(f, "NoResult"), + } + } +} + +impl<'gc> ReturnValue<'gc> { + /// Run the return value through a stack continuation. + /// + /// If the return value is instant, we call the continuation immediately; + /// else we schedule it on the AVM stack. We return a new return value + /// representing the most up-to-date state of the computation in question. + /// This means it's possible to chain `and_then` functions across multiple + /// AVM stack frames. + pub fn and_then( + self, + avm: &mut Avm1<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + mut cont: F, + ) -> Result, Error> + where + F: StackContinuation<'gc>, + { + use ReturnValue::*; + + match self { + Immediate(val) => cont.returned(avm, context, val), + ResultOf(frame) => { + frame.write(context.gc_context).and_then(Box::new(cont)); + + //WARNING: This isn't exactly chainable, only one continuation + //can run at once. + Ok(ResultOf(frame)) + } + NoResult => Ok(NoResult), + } + } + + /// Mark a given return value as intended to be pushed onto the stack. + /// + /// The natural result of a stack frame retiring is to be pushed, so this + /// only ensures that Immediate values are pushed. + pub fn push(self, avm: &mut Avm1<'gc>) { + use ReturnValue::*; + + match self { + Immediate(val) => avm.push(val), + ResultOf(_frame) => {} + NoResult => {} + }; + } + + /// Consumes the given return value. + /// + /// This exists primarily so that users of return values can indicate that + /// they do not plan to use them. + pub fn ignore(self) {} + + pub fn is_immediate(&self) -> bool { + use ReturnValue::*; + + if let Immediate(_v) = self { + true + } else { + false + } + } + + /// Panic if a value is not immediate. + /// + /// This should only be used in test assertions. + pub fn unwrap_immediate(self) -> Value<'gc> { + use ReturnValue::*; + + match self { + Immediate(val) => val, + _ => panic!("Unwrapped a non-immediate return value"), + } + } +} diff --git a/core/src/avm1/scope.rs b/core/src/avm1/scope.rs index 43b5e88dc..13aa5e2b1 100644 --- a/core/src/avm1/scope.rs +++ b/core/src/avm1/scope.rs @@ -1,5 +1,6 @@ //! Represents AVM1 scope chain resolution. +use crate::avm1::return_value::ReturnValue; use crate::avm1::{Avm1, Object, UpdateContext, Value}; use enumset::EnumSet; use gc_arena::{GcCell, MutationContext}; @@ -246,7 +247,7 @@ impl<'gc> Scope<'gc> { avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: GcCell<'gc, Object<'gc>>, - ) -> Option> { + ) -> ReturnValue<'gc> { if self.locals().has_property(name) { return self.locals().get(name, avm, context, this); } @@ -254,7 +255,7 @@ impl<'gc> Scope<'gc> { return scope.resolve(name, avm, context, this); } - Some(Value::Undefined) + ReturnValue::Immediate(Value::Undefined) } /// Check if a particular property in the scope chain is defined. diff --git a/core/src/avm1/stack_continuation.rs b/core/src/avm1/stack_continuation.rs index 543f97e20..4fc8f764b 100644 --- a/core/src/avm1/stack_continuation.rs +++ b/core/src/avm1/stack_continuation.rs @@ -1,5 +1,6 @@ //! GC-compatible scope continuations +use crate::avm1::return_value::ReturnValue; use crate::avm1::{Avm1, Error, Value}; use crate::context::UpdateContext; use gc_arena::Collect; @@ -14,12 +15,16 @@ pub trait StackContinuation<'gc>: 'gc + Collect { /// You are free to use it as you please. In general, however, if you intend /// to return to the previous activation frame, then you should push this /// return value on the stack. + /// + /// This function returns another ReturnValue, which can be used to signal + /// that the given continuation should be run again, to replace the current + /// return value, or to suppress the return value. fn returned( &mut self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, return_value: Value<'gc>, - ) -> Result<(), Error>; + ) -> Result, Error>; } /// Generate a continuation from some set of garbage-collected values. @@ -31,7 +36,9 @@ pub trait StackContinuation<'gc>: 'gc + Collect { macro_rules! stack_continuation { ($( $name:ident: $type:ty ),*, | $avmname:ident, $ctxtname:ident, $retvalname:ident | $code:block) => { { + use crate::avm1::return_value::ReturnValue; use crate::avm1::stack_continuation::StackContinuation; + use crate::avm1::Error; struct MyCont<'gc> { $( @@ -50,7 +57,7 @@ macro_rules! stack_continuation { impl<'gc> StackContinuation<'gc> for MyCont<'gc> { #[allow(unused_parens)] - fn returned(&mut self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, return_value: Value<'gc>) -> Result<(), Error> { + fn returned(&mut self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, return_value: Value<'gc>) -> Result, Error> { $( let $name = &mut self.$name; )* @@ -62,33 +69,7 @@ macro_rules! stack_continuation { } } - let cont = MyCont{$($name),*}; - - Box::new(cont) - } - }; -} - -/// Wait for the result of a `get` to be ready, then call the continuation. -#[allow(unused_macros)] -macro_rules! and_then { - ( $value:expr, $avm:expr, $context:expr, $cont:expr) => { - #[allow(unused_imports)] - use crate::avm1::stack_continuation::StackContinuation; - - let value = $value; - let mut continuation = $cont; - let avm = $avm; - let context = $context; - - if let Some(instant_value) = value { - continuation.returned(avm, context, instant_value)?; - } else { - avm.stack_frames - .last() - .unwrap() - .write(context.gc_context) - .and_then(continuation); + MyCont{$($name),*} } }; } diff --git a/core/src/avm1/test_utils.rs b/core/src/avm1/test_utils.rs index a65d218b0..a702aee59 100644 --- a/core/src/avm1/test_utils.rs +++ b/core/src/avm1/test_utils.rs @@ -49,10 +49,10 @@ where }; let globals = avm.global_object_cell(); - avm.insert_stack_frame( + avm.insert_stack_frame(GcCell::allocate( + gc_context, Activation::from_nothing(swf_version, globals, gc_context), - &mut context, - ); + )); let this = root.read().object().as_object().unwrap().to_owned(); diff --git a/core/src/avm1/tests.rs b/core/src/avm1/tests.rs index 0dc86eed0..fd041f911 100644 --- a/core/src/avm1/tests.rs +++ b/core/src/avm1/tests.rs @@ -1,5 +1,6 @@ use crate::avm1::activation::Activation; use crate::avm1::test_utils::with_avm; +use gc_arena::GcCell; #[test] fn locals_into_form_values() { @@ -15,7 +16,7 @@ fn locals_into_form_values() { .write(context.gc_context) .set("value2", 2.0, avm, context, my_locals); - avm.insert_stack_frame(my_activation, context); + avm.insert_stack_frame(GcCell::allocate(context.gc_context, my_activation)); let my_local_values = avm.locals_into_form_values(context); diff --git a/core/src/avm1/value.rs b/core/src/avm1/value.rs index 38dd278ae..3a925f7f5 100644 --- a/core/src/avm1/value.rs +++ b/core/src/avm1/value.rs @@ -1,4 +1,5 @@ use crate::avm1::object::Object; +use crate::avm1::return_value::ReturnValue; use crate::avm1::{Avm1, Error, UpdateContext}; use gc_arena::GcCell; @@ -257,7 +258,7 @@ impl<'gc> Value<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, this: GcCell<'gc, Object<'gc>>, args: &[Value<'gc>], - ) -> Result>, Error> { + ) -> Result, Error> { if let Value::Object(object) = self { Ok(object.read().call(avm, context, this, args)) } else {