Replace `Option<Value<'gc>>` 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.
This commit is contained in:
David Wendt 2019-10-21 18:37:04 -04:00
parent 2a3d324a33
commit a2ee7f9e3a
13 changed files with 363 additions and 262 deletions

View File

@ -1,6 +1,7 @@
use crate::avm1::function::Avm1Function; use crate::avm1::function::Avm1Function;
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::return_value::ReturnValue::*;
use crate::backend::navigator::NavigationMethod; use crate::backend::navigator::NavigationMethod;
use crate::context::UpdateContext; use crate::context::UpdateContext;
use crate::prelude::*; use crate::prelude::*;
@ -14,12 +15,15 @@ use swf::avm1::types::{Action, Function};
use crate::tag_utils::SwfSlice; use crate::tag_utils::SwfSlice;
#[macro_use]
mod stack_continuation;
mod activation; mod activation;
mod fscommand; mod fscommand;
mod function; mod function;
mod globals; mod globals;
pub mod movie_clip; pub mod movie_clip;
pub mod object; pub mod object;
mod return_value;
mod scope; mod scope;
mod value; mod value;
@ -29,9 +33,6 @@ mod test_utils;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
#[macro_use]
mod stack_continuation;
use activation::Activation; use activation::Activation;
use scope::Scope; use scope::Scope;
pub use value::Value; pub use value::Value;
@ -111,7 +112,9 @@ impl<'gc> Avm1<'gc> {
for k in keys { for k in keys {
let v = locals.read().get(&k, self, context, locals); 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()); 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. /// Add a stack frame for any arbitrary code.
pub fn insert_stack_frame( pub fn insert_stack_frame(&mut self, frame: GcCell<'gc, Activation<'gc>>) {
&mut self, self.stack_frames.push(frame);
frame: Activation<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) {
self.stack_frames
.push(GcCell::allocate(context.gc_context, frame));
} }
/// Retrieve the current AVM execution frame. /// Retrieve the current AVM execution frame.
@ -275,12 +273,14 @@ impl<'gc> Avm1<'gc> {
let can_return = !self.stack_frames.is_empty(); let can_return = !self.stack_frames.is_empty();
if let Some(func) = frame.write(context.gc_context).get_then_func() { if let Some(func) = frame.write(context.gc_context).get_then_func() {
let is_continuing = func.returned(self, context, return_value)?; match func.returned(self, context, return_value)? {
if is_continuing { Immediate(val) => self.stack.push(val),
if let Some(fr) = self.current_stack_frame() { ResultOf(new_frame) => {
fr.write(context.gc_context) new_frame
.write(context.gc_context)
.and_again(frame, context.gc_context); .and_again(frame, context.gc_context);
} }
NoResult => {}
} }
} else if can_return { } else if can_return {
self.stack.push(return_value); self.stack.push(return_value);
@ -694,29 +694,17 @@ impl<'gc> Avm1<'gc> {
); );
let this = context.active_clip.read().object().as_object()?.to_owned(); let this = context.active_clip.read().object().as_object()?.to_owned();
if let Some(target_fn) = target_fn { target_fn
let return_value = target_fn.call(self, context, this, &args)?; .and_then(
if let Some(instant_return) = return_value { self,
self.push(instant_return); context,
} stack_continuation!(
} else {
self.stack_frames
.last()
.unwrap()
.write(context.gc_context)
.and_then(stack_continuation!(
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
args: Vec<Value<'gc>>, args: Vec<Value<'gc>>,
|avm, context, target_fn| { |avm, context, target_fn| { target_fn.call(avm, context, *this, &args) }
let return_value = target_fn.call(avm, context, *this, &args)?; ),
if let Some(instant_return) = return_value { )?
avm.push(instant_return); .push(self);
}
Ok(())
}
))
}
Ok(()) Ok(())
} }
@ -736,47 +724,26 @@ impl<'gc> Avm1<'gc> {
match method_name { match method_name {
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)?; object.call(self, context, this, &args)?.push(self);
if let Some(instant_return) = return_value {
self.push(instant_return);
}
} }
Value::String(name) => { Value::String(name) => {
if name.is_empty() { if name.is_empty() {
let return_value = object
object.call(self, context, object.as_object()?.to_owned(), &args)?; .call(self, context, object.as_object()?.to_owned(), &args)?
if let Some(instant_return) = return_value { .push(self);
self.push(instant_return);
}
} else { } else {
let callable = object.as_object()?.read().get( let target = object.as_object()?.to_owned();
&name, object
self, .as_object()?
context, .read()
object.as_object()?.to_owned(), .get(&name, self, context, target)
); .and_then(
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(
self, self,
context, context,
object.as_object()?.to_owned(), stack_continuation!(
&args, target: GcCell<'gc, Object<'gc>>,
)?;
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<Value<'gc>>,
name: String, name: String,
object: Value<'gc>, args: Vec<Value<'gc>>,
|avm, context, callable| { |avm, context, callable| {
if let Value::Undefined = callable { if let Value::Undefined = callable {
return Err(format!( return Err(format!(
@ -786,19 +753,11 @@ impl<'gc> Avm1<'gc> {
.into()); .into());
} }
let return_value = callable.call( callable.call(avm, context, *target, &args)
avm,
context,
object.as_object()?.to_owned(),
&args,
)?;
if let Some(instant_return) = return_value {
avm.push(instant_return)
}
Ok(())
} }
)); ),
} )?
.push(self);
} }
} }
_ => { _ => {
@ -972,48 +931,30 @@ impl<'gc> Avm1<'gc> {
let name_value = self.pop()?; let name_value = self.pop()?;
let name = name_value.as_string()?; let name = name_value.as_string()?;
self.push(Value::Null); // Sentinel that indicates end of enumeration self.push(Value::Null); // Sentinel that indicates end of enumeration
match self self.current_stack_frame()
.current_stack_frame()
.unwrap() .unwrap()
.read() .read()
.resolve(name, self, context) .resolve(name, self, context)
{ .and_then(
Some(Value::Object(ob)) => { self,
for k in ob.read().get_keys() { context,
self.push(Value::String(k)); stack_continuation!(name_value: Value<'gc>, |avm, _context, object| {
} match object {
} Value::Object(ob) => {
Some(_) => { for k in ob.read().get_keys() {
log::error!("Cannot enumerate properties of {}", name); avm.push(Value::String(k));
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()?
);
} }
} }
_ => log::error!(
"Cannot enumerate properties of {}",
name_value.as_string()?
),
};
Ok(()) Ok(ReturnValue::NoResult)
} }),
)), )?
}; .ignore();
Ok(()) Ok(())
} }
@ -1070,10 +1011,7 @@ impl<'gc> Avm1<'gc> {
let name = name_val.into_string(); let name = name_val.into_string();
let object = self.pop()?.as_object()?; let object = self.pop()?.as_object()?;
let this = self.current_stack_frame().unwrap().read().this_cell(); let this = self.current_stack_frame().unwrap().read().this_cell();
let value = object.read().get(&name, self, context, this); object.read().get(&name, self, context, this).push(self);
if let Some(value) = value {
self.push(value);
}
Ok(()) Ok(())
} }
@ -1160,10 +1098,10 @@ impl<'gc> Avm1<'gc> {
if let Some(clip) = node.read().as_movie_clip() { if let Some(clip) = node.read().as_movie_clip() {
let object = clip.object().as_object()?; let object = clip.object().as_object()?;
if object.read().has_property(var_name) { if object.read().has_property(var_name) {
let result = object.read().get(var_name, self, context, object); object
if let Some(value) = result { .read()
self.push(value); .get(var_name, self, context, object)
} //NOTE: Natural return is sufficient for custom get .push(self);
} else { } else {
self.push(Value::Undefined); self.push(Value::Undefined);
} }
@ -1172,14 +1110,11 @@ impl<'gc> Avm1<'gc> {
} }
} }
} else if self.current_stack_frame().unwrap().read().is_defined(path) { } else if self.current_stack_frame().unwrap().read().is_defined(path) {
let result = self self.current_stack_frame()
.current_stack_frame()
.unwrap() .unwrap()
.read() .read()
.resolve(path, self, context); .resolve(path, self, context)
if let Some(value) = result { .push(self);
self.push(value);
} //NOTE: Natural return is sufficient for custom get
} else { } else {
self.push(Value::Undefined); self.push(Value::Undefined);
} }

View File

@ -1,6 +1,7 @@
//! Activation records //! Activation records
use crate::avm1::object::Object; use crate::avm1::object::Object;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::scope::Scope; use crate::avm1::scope::Scope;
use crate::avm1::stack_continuation::StackContinuation; use crate::avm1::stack_continuation::StackContinuation;
use crate::avm1::{Avm1, Value}; use crate::avm1::{Avm1, Value};
@ -261,13 +262,13 @@ impl<'gc> Activation<'gc> {
name: &str, name: &str,
avm: &mut Avm1<'gc>, avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
if name == "this" { if name == "this" {
return Some(Value::Object(self.this)); return ReturnValue::Immediate(Value::Object(self.this));
} }
if name == "arguments" && self.arguments.is_some() { 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) self.scope().resolve(name, avm, context, self.this)

View File

@ -2,6 +2,7 @@
use crate::avm1::activation::Activation; use crate::avm1::activation::Activation;
use crate::avm1::object::{Attribute::*, Object}; use crate::avm1::object::{Attribute::*, Object};
use crate::avm1::return_value::ReturnValue;
use crate::avm1::scope::Scope; use crate::avm1::scope::Scope;
use crate::avm1::value::Value; use crate::avm1::value::Value;
use crate::avm1::{Avm1, UpdateContext}; use crate::avm1::{Avm1, UpdateContext};
@ -28,7 +29,7 @@ pub type NativeFunction<'gc> = fn(
&mut UpdateContext<'_, 'gc, '_>, &mut UpdateContext<'_, 'gc, '_>,
GcCell<'gc, Object<'gc>>, GcCell<'gc, Object<'gc>>,
&[Value<'gc>], &[Value<'gc>],
) -> Option<Value<'gc>>; ) -> ReturnValue<'gc>;
/// Represents a function defined in the AVM1 runtime, either through /// Represents a function defined in the AVM1 runtime, either through
/// `DefineFunction` or `DefineFunction2`. /// `DefineFunction` or `DefineFunction2`.
@ -183,7 +184,7 @@ impl<'gc> Executable<'gc> {
ac: &mut UpdateContext<'_, 'gc, '_>, ac: &mut UpdateContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
match self { match self {
Executable::Native(nf) => nf(avm, ac, this, args), Executable::Native(nf) => nf(avm, ac, this, args),
Executable::Action(af) => { Executable::Action(af) => {
@ -218,13 +219,17 @@ impl<'gc> Executable<'gc> {
.unwrap_or(ac.player_version) .unwrap_or(ac.player_version)
}; };
let mut frame = Activation::from_function( let frame_cell = GcCell::allocate(
effective_ver, ac.gc_context,
af.data(), Activation::from_function(
child_scope, effective_ver,
this, af.data(),
Some(argcell), child_scope,
this,
Some(argcell),
),
); );
let mut frame = frame_cell.write(ac.gc_context);
frame.allocate_local_registers(af.register_count(), 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 { if af.preload_parent {
let parent = child_scope.read().resolve("_parent", avm, ac, this); let parent_preload_r = preload_r;
if let Some(instant_parent) = parent {
frame.set_local_register(preload_r, instant_parent, ac.gc_context); child_scope
} else { .read()
log::error!("User-defined virtual _parent is NOT supported!"); .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; 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)
} }
} }
} }

View File

@ -1,4 +1,5 @@
use crate::avm1::fscommand; use crate::avm1::fscommand;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Object, UpdateContext, Value}; use crate::avm1::{Avm1, Object, UpdateContext, Value};
use crate::backend::navigator::NavigationMethod; use crate::backend::navigator::NavigationMethod;
use enumset::EnumSet; use enumset::EnumSet;
@ -13,13 +14,13 @@ pub fn getURL<'a, 'gc>(
context: &mut UpdateContext<'a, 'gc, '_>, context: &mut UpdateContext<'a, 'gc, '_>,
_this: GcCell<'gc, Object<'gc>>, _this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
//TODO: Error behavior if no arguments are present //TODO: Error behavior if no arguments are present
if let Some(url_val) = args.get(0) { if let Some(url_val) = args.get(0) {
let url = url_val.clone().into_string(); let url = url_val.clone().into_string();
if let Some(fscommand) = fscommand::parse(&url) { if let Some(fscommand) = fscommand::parse(&url) {
fscommand::handle(fscommand, avm, context); 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()); 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); context.navigator.navigate_to_url(url, window, vars_method);
} }
Some(Value::Undefined) ReturnValue::Immediate(Value::Undefined)
} }
pub fn random<'gc>( pub fn random<'gc>(
@ -41,12 +42,12 @@ pub fn random<'gc>(
action_context: &mut UpdateContext<'_, 'gc, '_>, action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: GcCell<'gc, Object<'gc>>, _this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
match args.get(0) { 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(), 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, '_>, _action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: GcCell<'gc, Object<'gc>>, _this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
if let Some(val) = args.get(0) { 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 { } else {
Some(Value::Bool(false)) ReturnValue::Immediate(Value::Bool(false))
} }
} }
@ -68,11 +69,11 @@ pub fn number<'gc>(
_action_context: &mut UpdateContext<'_, 'gc, '_>, _action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: GcCell<'gc, Object<'gc>>, _this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
if let Some(val) = args.get(0) { if let Some(val) = args.get(0) {
Some(Value::Number(val.as_number())) ReturnValue::Immediate(Value::Number(val.as_number()))
} else { } 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, '_>, _action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: GcCell<'gc, Object<'gc>>, _this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
if let Some(val) = args.get(0) { 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 { } else {
Some(Value::Bool(true)) ReturnValue::Immediate(Value::Bool(true))
} }
} }
@ -128,7 +129,7 @@ mod tests {
$( $(
args.push($arg.into()); 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(()) Ok(())

View File

@ -1,4 +1,5 @@
use crate::avm1::object::Attribute::*; use crate::avm1::object::Attribute::*;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Object, UpdateContext, Value}; use crate::avm1::{Avm1, Object, UpdateContext, Value};
use gc_arena::{GcCell, MutationContext}; use gc_arena::{GcCell, MutationContext};
use rand::Rng; use rand::Rng;
@ -9,11 +10,11 @@ macro_rules! wrap_std {
$( $(
$object.force_set_function( $object.force_set_function(
$name, $name,
|_avm, _context, _this, args| -> Option<Value<'gc>> { |_avm, _context, _this, args| -> ReturnValue<'gc> {
if let Some(input) = args.get(0) { if let Some(input) = args.get(0) {
Some(Value::Number($std(input.as_number()))) ReturnValue::Immediate(Value::Number($std(input.as_number())))
} else { } else {
Some(Value::Number(NAN)) ReturnValue::Immediate(Value::Number(NAN))
} }
}, },
$gc_context, $gc_context,
@ -28,15 +29,15 @@ fn atan2<'gc>(
_context: &mut UpdateContext<'_, 'gc, '_>, _context: &mut UpdateContext<'_, 'gc, '_>,
_this: GcCell<'gc, Object<'gc>>, _this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
if let Some(y) = args.get(0) { if let Some(y) = args.get(0) {
if let Some(x) = args.get(1) { 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 { } 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>( pub fn random<'gc>(
@ -44,8 +45,8 @@ pub fn random<'gc>(
action_context: &mut UpdateContext<'_, 'gc, '_>, action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: GcCell<'gc, Object<'gc>>, _this: GcCell<'gc, Object<'gc>>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
Some(Value::Number(action_context.rng.gen_range(0.0f64, 1.0f64))) 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>> { pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> GcCell<'gc, Object<'gc>> {
@ -130,7 +131,7 @@ mod tests {
fn $test() -> Result<(), Error> { fn $test() -> Result<(), Error> {
with_avm(19, |avm, context, _root| { with_avm(19, |avm, context, _root| {
let math = create(context.gc_context); 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)] #[allow(unused_mut)]
@ -138,7 +139,7 @@ mod tests {
$( $(
args.push($arg.into()); 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(()) Ok(())
@ -238,7 +239,7 @@ mod tests {
let math = GcCell::allocate(context.gc_context, create(context.gc_context)); let math = GcCell::allocate(context.gc_context, create(context.gc_context));
assert_eq!( assert_eq!(
atan2(avm, context, *math.read(), &[]), atan2(avm, context, *math.read(), &[]),
Some(Value::Number(NAN)) ReturnValue::Immediate(Value::Number(NAN))
); );
assert_eq!( assert_eq!(
atan2( atan2(
@ -247,7 +248,7 @@ mod tests {
*math.read(), *math.read(),
&[Value::Number(1.0), Value::Null] &[Value::Number(1.0), Value::Null]
), ),
Some(Value::Number(NAN)) ReturnValue::Immediate(Value::Number(NAN))
); );
assert_eq!( assert_eq!(
atan2( atan2(
@ -256,7 +257,7 @@ mod tests {
*math.read(), *math.read(),
&[Value::Number(1.0), Value::Undefined] &[Value::Number(1.0), Value::Undefined]
), ),
Some(Value::Number(NAN)) ReturnValue::Immediate(Value::Number(NAN))
); );
assert_eq!( assert_eq!(
atan2( atan2(
@ -265,7 +266,7 @@ mod tests {
*math.read(), *math.read(),
&[Value::Undefined, Value::Number(1.0)] &[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)); let math = GcCell::allocate(context.gc_context, create(context.gc_context));
assert_eq!( assert_eq!(
atan2(avm, context, *math.read(), &[Value::Number(10.0)]), 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!( assert_eq!(
atan2( atan2(
@ -285,7 +286,7 @@ mod tests {
*math.read(), *math.read(),
&[Value::Number(1.0), Value::Number(2.0)] &[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)))
); );
}); });
} }

View File

@ -1,7 +1,8 @@
use crate::avm1::function::Executable; use crate::avm1::function::Executable;
use crate::avm1::object::{Attribute::*, Object}; use crate::avm1::object::{Attribute::*, Object};
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, UpdateContext, Value}; use crate::avm1::{Avm1, UpdateContext, Value};
use crate::display_object::{DisplayNode, MovieClip}; use crate::display_object::{DisplayNode, DisplayObject, MovieClip};
use enumset::EnumSet; use enumset::EnumSet;
use gc_arena::{GcCell, MutationContext}; use gc_arena::{GcCell, MutationContext};
@ -10,13 +11,13 @@ macro_rules! with_movie_clip {
$( $(
$object.force_set_function( $object.force_set_function(
$name, $name,
|_avm, _context, this, args| -> Option<Value<'gc>> { |_avm, _context, this, args| -> ReturnValue<'gc> {
if let Some(display_object) = this.read().display_node() { if let Some(display_object) = this.read().display_node() {
if let Some(movie_clip) = display_object.read().as_movie_clip() { 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, $gc_context,
DontDelete | ReadOnly | DontEnum, DontDelete | ReadOnly | DontEnum,
@ -30,13 +31,13 @@ macro_rules! with_movie_clip_mut {
$( $(
$object.force_set_function( $object.force_set_function(
$name, $name,
|_avm, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> Option<Value<'gc>> { |_avm, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> ReturnValue<'gc> {
if let Some(display_object) = this.read().display_node() { if let Some(display_object) = this.read().display_node() {
if let Some(movie_clip) = display_object.write(context.gc_context).as_movie_clip_mut() { 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>, } as crate::avm1::function::NativeFunction<'gc>,
$gc_context, $gc_context,
DontDelete | ReadOnly | DontEnum, DontDelete | ReadOnly | DontEnum,
@ -50,7 +51,7 @@ pub fn overwrite_root<'gc>(
ac: &mut UpdateContext<'_, 'gc, '_>, ac: &mut UpdateContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
let new_val = args let new_val = args
.get(0) .get(0)
.map(|v| v.to_owned()) .map(|v| v.to_owned())
@ -58,7 +59,7 @@ pub fn overwrite_root<'gc>(
this.write(ac.gc_context) this.write(ac.gc_context)
.force_set("_root", new_val, EnumSet::new()); .force_set("_root", new_val, EnumSet::new());
Some(Value::Undefined) ReturnValue::Immediate(Value::Undefined)
} }
pub fn overwrite_global<'gc>( pub fn overwrite_global<'gc>(
@ -66,7 +67,7 @@ pub fn overwrite_global<'gc>(
ac: &mut UpdateContext<'_, 'gc, '_>, ac: &mut UpdateContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
let new_val = args let new_val = args
.get(0) .get(0)
.map(|v| v.to_owned()) .map(|v| v.to_owned())
@ -74,7 +75,7 @@ pub fn overwrite_global<'gc>(
this.write(ac.gc_context) this.write(ac.gc_context)
.force_set("_global", new_val, EnumSet::new()); .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> { 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| { "getBytesTotal" => |_movie_clip: &MovieClip<'gc>, _args| {
// TODO find a correct value // TODO find a correct value
Value::Number(1.0) Value::Number(1.0)
},
"toString" => |movie_clip: &MovieClip, _args| {
Value::String(movie_clip.name().to_string())
} }
); );
object.force_set_virtual( object.force_set_virtual(
"_global", "_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)), Some(Executable::Native(overwrite_global)),
EnumSet::new(), EnumSet::new(),
); );
object.force_set_virtual( object.force_set_virtual(
"_root", "_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)), Some(Executable::Native(overwrite_root)),
EnumSet::new(), EnumSet::new(),
); );
@ -131,7 +139,7 @@ pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<
object.force_set_virtual( object.force_set_virtual(
"_parent", "_parent",
Executable::Native(|_avm, _context, this, _args| { Executable::Native(|_avm, _context, this, _args| {
Some( ReturnValue::Immediate(
this.read() this.read()
.display_node() .display_node()
.and_then(|mc| mc.read().parent()) .and_then(|mc| mc.read().parent())

View File

@ -1,5 +1,6 @@
use self::Attribute::*; use self::Attribute::*;
use crate::avm1::function::{Avm1Function, Executable, NativeFunction}; use crate::avm1::function::{Avm1Function, Executable, NativeFunction};
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, UpdateContext, Value}; use crate::avm1::{Avm1, UpdateContext, Value};
use crate::display_object::DisplayNode; use crate::display_object::DisplayNode;
use core::fmt; use core::fmt;
@ -18,8 +19,8 @@ fn default_to_string<'gc>(
_: &mut UpdateContext<'_, 'gc, '_>, _: &mut UpdateContext<'_, 'gc, '_>,
_: GcCell<'gc, Object<'gc>>, _: GcCell<'gc, Object<'gc>>,
_: &[Value<'gc>], _: &[Value<'gc>],
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
Some("[Object object]".into()) ReturnValue::Immediate("[Object object]".into())
} }
#[derive(EnumSetType, Debug)] #[derive(EnumSetType, Debug)]
@ -53,10 +54,10 @@ impl<'gc> Property<'gc> {
avm: &mut Avm1<'gc>, avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
match self { match self {
Property::Virtual { get, .. } => get.exec(avm, context, this, &[]), 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, .. } => { Property::Virtual { set, .. } => {
if let Some(function) = set { if let Some(function) = set {
let return_value = function.exec(avm, context, this, &[new_value.into()]); let return_value = function.exec(avm, context, this, &[new_value.into()]);
return_value.is_some() return_value.is_immediate()
} else { } else {
true true
} }
@ -310,11 +311,12 @@ impl<'gc> Object<'gc> {
avm: &mut Avm1<'gc>, avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
if let Some(value) = self.values.get(name) { if let Some(value) = self.values.get(name) {
return value.get(avm, context, this); return value.get(avm, context, this);
} }
Some(Value::Undefined)
ReturnValue::Immediate(Value::Undefined)
} }
/// Delete a given value off the object. /// Delete a given value off the object.
@ -356,11 +358,11 @@ impl<'gc> Object<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
if let Some(function) = &self.function { if let Some(function) = &self.function {
function.exec(avm, context, this, args) function.exec(avm, context, this, args)
} else { } 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 object = GcCell::allocate(gc_context, Object::object(gc_context));
let globals = avm.global_object_cell(); 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), Activation::from_nothing(swf_version, globals, gc_context),
&mut context, ));
);
test(&mut avm, &mut context, object) test(&mut avm, &mut context, object)
}) })
@ -451,7 +453,7 @@ mod tests {
with_object(0, |avm, context, object| { with_object(0, |avm, context, object| {
assert_eq!( assert_eq!(
object.read().get("not_defined", avm, context, object), object.read().get("not_defined", avm, context, object),
Some(Value::Undefined) ReturnValue::Immediate(Value::Undefined)
); );
}) })
} }
@ -468,11 +470,11 @@ mod tests {
assert_eq!( assert_eq!(
object.read().get("forced", avm, context, object), object.read().get("forced", avm, context, object),
Some("forced".into()) ReturnValue::Immediate("forced".into())
); );
assert_eq!( assert_eq!(
object.read().get("natural", avm, context, object), object.read().get("natural", avm, context, object),
Some("natural".into()) ReturnValue::Immediate("natural".into())
); );
}) })
} }
@ -496,11 +498,11 @@ mod tests {
assert_eq!( assert_eq!(
object.read().get("normal", avm, context, object), object.read().get("normal", avm, context, object),
Some("replaced".into()) ReturnValue::Immediate("replaced".into())
); );
assert_eq!( assert_eq!(
object.read().get("readonly", avm, context, object), 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.write(context.gc_context).delete("test"), false);
assert_eq!( assert_eq!(
object.read().get("test", avm, context, object), object.read().get("test", avm, context, object),
Some("initial".into()) ReturnValue::Immediate("initial".into())
); );
object object
@ -525,7 +527,7 @@ mod tests {
assert_eq!(object.write(context.gc_context).delete("test"), false); assert_eq!(object.write(context.gc_context).delete("test"), false);
assert_eq!( assert_eq!(
object.read().get("test", avm, context, object), object.read().get("test", avm, context, object),
Some("replaced".into()) ReturnValue::Immediate("replaced".into())
); );
}) })
} }
@ -533,7 +535,9 @@ mod tests {
#[test] #[test]
fn test_virtual_get() { fn test_virtual_get() {
with_object(0, |avm, context, object| { 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( object.write(context.gc_context).force_set_virtual(
"test", "test",
@ -544,7 +548,7 @@ mod tests {
assert_eq!( assert_eq!(
object.read().get("test", avm, context, object), object.read().get("test", avm, context, object),
Some("Virtual!".into()) ReturnValue::Immediate("Virtual!".into())
); );
// This set should do nothing // This set should do nothing
@ -553,7 +557,7 @@ mod tests {
.set("test", "Ignored!", avm, context, object); .set("test", "Ignored!", avm, context, object);
assert_eq!( assert_eq!(
object.read().get("test", avm, context, object), object.read().get("test", avm, context, object),
Some("Virtual!".into()) ReturnValue::Immediate("Virtual!".into())
); );
}) })
} }
@ -561,7 +565,9 @@ mod tests {
#[test] #[test]
fn test_delete() { fn test_delete() {
with_object(0, |avm, context, object| { 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( object.write(context.gc_context).force_set_virtual(
"virtual", "virtual",
@ -593,19 +599,19 @@ mod tests {
assert_eq!( assert_eq!(
object.read().get("virtual", avm, context, object), object.read().get("virtual", avm, context, object),
Some(Value::Undefined) ReturnValue::Immediate(Value::Undefined)
); );
assert_eq!( assert_eq!(
object.read().get("virtual_un", avm, context, object), object.read().get("virtual_un", avm, context, object),
Some("Virtual!".into()) ReturnValue::Immediate("Virtual!".into())
); );
assert_eq!( assert_eq!(
object.read().get("stored", avm, context, object), object.read().get("stored", avm, context, object),
Some(Value::Undefined) ReturnValue::Immediate(Value::Undefined)
); );
assert_eq!( assert_eq!(
object.read().get("stored_un", avm, context, object), object.read().get("stored_un", avm, context, object),
Some("Stored!".into()) ReturnValue::Immediate("Stored!".into())
); );
}) })
} }
@ -613,7 +619,9 @@ mod tests {
#[test] #[test]
fn test_iter_values() { fn test_iter_values() {
with_object(0, |_avm, context, object| { 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 object
.write(context.gc_context) .write(context.gc_context)

View File

@ -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(<activation frame>)"),
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<F>(
self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
mut cont: F,
) -> Result<ReturnValue<'gc>, 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"),
}
}
}

View File

@ -1,5 +1,6 @@
//! Represents AVM1 scope chain resolution. //! Represents AVM1 scope chain resolution.
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Object, UpdateContext, Value}; use crate::avm1::{Avm1, Object, UpdateContext, Value};
use enumset::EnumSet; use enumset::EnumSet;
use gc_arena::{GcCell, MutationContext}; use gc_arena::{GcCell, MutationContext};
@ -246,7 +247,7 @@ impl<'gc> Scope<'gc> {
avm: &mut Avm1<'gc>, avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
) -> Option<Value<'gc>> { ) -> ReturnValue<'gc> {
if self.locals().has_property(name) { if self.locals().has_property(name) {
return self.locals().get(name, avm, context, this); return self.locals().get(name, avm, context, this);
} }
@ -254,7 +255,7 @@ impl<'gc> Scope<'gc> {
return scope.resolve(name, avm, context, this); 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. /// Check if a particular property in the scope chain is defined.

View File

@ -1,5 +1,6 @@
//! GC-compatible scope continuations //! GC-compatible scope continuations
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, Value}; use crate::avm1::{Avm1, Error, Value};
use crate::context::UpdateContext; use crate::context::UpdateContext;
use gc_arena::Collect; 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 /// 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 /// to return to the previous activation frame, then you should push this
/// return value on the stack. /// 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( fn returned(
&mut self, &mut self,
avm: &mut Avm1<'gc>, avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
return_value: Value<'gc>, return_value: Value<'gc>,
) -> Result<(), Error>; ) -> Result<ReturnValue<'gc>, Error>;
} }
/// Generate a continuation from some set of garbage-collected values. /// Generate a continuation from some set of garbage-collected values.
@ -31,7 +36,9 @@ pub trait StackContinuation<'gc>: 'gc + Collect {
macro_rules! stack_continuation { macro_rules! stack_continuation {
($( $name:ident: $type:ty ),*, | $avmname:ident, $ctxtname:ident, $retvalname:ident | $code:block) => { ($( $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::stack_continuation::StackContinuation;
use crate::avm1::Error;
struct MyCont<'gc> { struct MyCont<'gc> {
$( $(
@ -50,7 +57,7 @@ macro_rules! stack_continuation {
impl<'gc> StackContinuation<'gc> for MyCont<'gc> { impl<'gc> StackContinuation<'gc> for MyCont<'gc> {
#[allow(unused_parens)] #[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<ReturnValue<'gc>, Error> {
$( $(
let $name = &mut self.$name; let $name = &mut self.$name;
)* )*
@ -62,33 +69,7 @@ macro_rules! stack_continuation {
} }
} }
let cont = MyCont{$($name),*}; 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);
} }
}; };
} }

View File

@ -49,10 +49,10 @@ where
}; };
let globals = avm.global_object_cell(); 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), Activation::from_nothing(swf_version, globals, gc_context),
&mut context, ));
);
let this = root.read().object().as_object().unwrap().to_owned(); let this = root.read().object().as_object().unwrap().to_owned();

View File

@ -1,5 +1,6 @@
use crate::avm1::activation::Activation; use crate::avm1::activation::Activation;
use crate::avm1::test_utils::with_avm; use crate::avm1::test_utils::with_avm;
use gc_arena::GcCell;
#[test] #[test]
fn locals_into_form_values() { fn locals_into_form_values() {
@ -15,7 +16,7 @@ fn locals_into_form_values() {
.write(context.gc_context) .write(context.gc_context)
.set("value2", 2.0, avm, context, my_locals); .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); let my_local_values = avm.locals_into_form_values(context);

View File

@ -1,4 +1,5 @@
use crate::avm1::object::Object; use crate::avm1::object::Object;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, UpdateContext}; use crate::avm1::{Avm1, Error, UpdateContext};
use gc_arena::GcCell; use gc_arena::GcCell;
@ -257,7 +258,7 @@ impl<'gc> Value<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
this: GcCell<'gc, Object<'gc>>, this: GcCell<'gc, Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Option<Value<'gc>>, Error> { ) -> Result<ReturnValue<'gc>, Error> {
if let Value::Object(object) = self { if let Value::Object(object) = self {
Ok(object.read().call(avm, context, this, args)) Ok(object.read().call(avm, context, this, args))
} else { } else {