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::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<Value<'gc>>,
|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<Value<'gc>>,
stack_continuation!(
target: GcCell<'gc, Object<'gc>>,
name: String,
object: Value<'gc>,
args: Vec<Value<'gc>>,
|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);
}

View File

@ -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<Value<'gc>> {
) -> 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)

View File

@ -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<Value<'gc>>;
) -> 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<Value<'gc>> {
) -> 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)
}
}
}

View File

@ -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<Value<'gc>> {
) -> 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<Value<'gc>> {
) -> 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<Value<'gc>> {
) -> 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<Value<'gc>> {
) -> 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<Value<'gc>> {
) -> 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(())

View File

@ -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<Value<'gc>> {
|_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<Value<'gc>> {
) -> 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<Value<'gc>> {
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)))
);
});
}

View File

@ -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<Value<'gc>> {
|_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<Value<'gc>> {
|_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<Value<'gc>> {
) -> 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<Value<'gc>> {
) -> 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())

View File

@ -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<Value<'gc>> {
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<Value<'gc>> {
) -> 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<Value<'gc>> {
) -> 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<Value<'gc>> {
) -> 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)

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.
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<Value<'gc>> {
) -> 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.

View File

@ -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<ReturnValue<'gc>, 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<ReturnValue<'gc>, 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),*}
}
};
}

View File

@ -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();

View File

@ -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);

View File

@ -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<Option<Value<'gc>>, Error> {
) -> Result<ReturnValue<'gc>, Error> {
if let Value::Object(object) = self {
Ok(object.read().call(avm, context, this, args))
} else {