2020-02-06 04:15:03 +00:00
|
|
|
//! Activation frames
|
|
|
|
|
2020-07-01 03:44:14 +00:00
|
|
|
use crate::avm2::function::Avm2MethodEntry;
|
2020-02-06 04:15:03 +00:00
|
|
|
use crate::avm2::object::Object;
|
2020-02-10 19:54:55 +00:00
|
|
|
use crate::avm2::scope::Scope;
|
2020-07-02 04:01:07 +00:00
|
|
|
use crate::avm2::script::Script;
|
2020-02-10 19:54:55 +00:00
|
|
|
use crate::avm2::script_object::ScriptObject;
|
2020-02-07 19:54:14 +00:00
|
|
|
use crate::avm2::value::Value;
|
2020-02-22 01:51:44 +00:00
|
|
|
use crate::avm2::Error;
|
2020-02-07 19:54:14 +00:00
|
|
|
use crate::context::UpdateContext;
|
2020-02-22 01:51:44 +00:00
|
|
|
use gc_arena::{Collect, GcCell, MutationContext};
|
2020-02-07 19:54:14 +00:00
|
|
|
use smallvec::SmallVec;
|
|
|
|
|
|
|
|
/// Represents a particular register set.
|
|
|
|
///
|
|
|
|
/// This type exists primarily because SmallVec isn't garbage-collectable.
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct RegisterSet<'gc>(SmallVec<[Value<'gc>; 8]>);
|
|
|
|
|
|
|
|
unsafe impl<'gc> gc_arena::Collect for RegisterSet<'gc> {
|
|
|
|
#[inline]
|
|
|
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
|
|
|
for register in &self.0 {
|
|
|
|
register.trace(cc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> RegisterSet<'gc> {
|
|
|
|
/// Create a new register set with a given number of specified registers.
|
|
|
|
///
|
|
|
|
/// The given registers will be set to `undefined`.
|
|
|
|
pub fn new(num: u32) -> Self {
|
|
|
|
Self(smallvec![Value::Undefined; num as usize])
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a reference to a given register, if it exists.
|
|
|
|
pub fn get(&self, num: u32) -> Option<&Value<'gc>> {
|
|
|
|
self.0.get(num as usize)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a mutable reference to a given register, if it exists.
|
|
|
|
pub fn get_mut(&mut self, num: u32) -> Option<&mut Value<'gc>> {
|
|
|
|
self.0.get_mut(num as usize)
|
|
|
|
}
|
|
|
|
}
|
2020-02-06 04:15:03 +00:00
|
|
|
|
|
|
|
/// Represents a single activation of a given AVM2 function or keyframe.
|
|
|
|
#[derive(Clone, Collect)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct Activation<'gc> {
|
2020-02-12 23:52:00 +00:00
|
|
|
/// The AVM method entry we're executing code out of.
|
2020-07-02 23:02:54 +00:00
|
|
|
method: Avm2MethodEntry<'gc>,
|
2020-02-06 04:15:03 +00:00
|
|
|
|
|
|
|
/// The current location of the instruction stream being executed.
|
|
|
|
pc: usize,
|
|
|
|
|
|
|
|
/// The immutable value of `this`.
|
2020-02-24 03:11:02 +00:00
|
|
|
this: Option<Object<'gc>>,
|
2020-02-06 04:15:03 +00:00
|
|
|
|
|
|
|
/// The arguments this function was called by.
|
|
|
|
arguments: Option<Object<'gc>>,
|
|
|
|
|
|
|
|
/// Flags that the current activation frame is being executed and has a
|
|
|
|
/// reader object copied from it. Taking out two readers on the same
|
|
|
|
/// activation frame is a programming error.
|
|
|
|
is_executing: bool,
|
2020-02-07 19:54:14 +00:00
|
|
|
|
|
|
|
/// Local registers.
|
|
|
|
///
|
|
|
|
/// All activations have local registers, but it is possible for multiple
|
|
|
|
/// activations (such as a rescope) to execute from the same register set.
|
|
|
|
local_registers: GcCell<'gc, RegisterSet<'gc>>,
|
2020-02-08 21:59:59 +00:00
|
|
|
|
|
|
|
/// What was returned from the function.
|
|
|
|
///
|
|
|
|
/// A return value of `None` indicates that the called function is still
|
|
|
|
/// executing. Functions that do not return instead return `Undefined`.
|
|
|
|
return_value: Option<Value<'gc>>,
|
2020-02-10 19:54:55 +00:00
|
|
|
|
|
|
|
/// The current local scope, implemented as a bare object.
|
|
|
|
local_scope: Object<'gc>,
|
|
|
|
|
|
|
|
/// The current scope stack.
|
|
|
|
///
|
|
|
|
/// A `scope` of `None` indicates that the scope stack is empty.
|
|
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
2020-02-25 23:07:53 +00:00
|
|
|
|
|
|
|
/// The base prototype of `this`.
|
|
|
|
///
|
|
|
|
/// This will not be available if this is not a method call.
|
|
|
|
base_proto: Option<Object<'gc>>,
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Activation<'gc> {
|
2020-02-18 22:54:39 +00:00
|
|
|
pub fn from_script(
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2020-07-02 04:01:07 +00:00
|
|
|
script: GcCell<'gc, Script<'gc>>,
|
2020-02-18 22:54:39 +00:00
|
|
|
global: Object<'gc>,
|
|
|
|
) -> Result<Self, Error> {
|
2020-07-03 01:11:10 +00:00
|
|
|
let method = script.read().init().into_entry()?;
|
2020-02-18 22:54:39 +00:00
|
|
|
let scope = Some(Scope::push_scope(None, global, context.gc_context));
|
|
|
|
let num_locals = method.body().num_locals;
|
2020-02-22 17:51:34 +00:00
|
|
|
let local_registers =
|
|
|
|
GcCell::allocate(context.gc_context, RegisterSet::new(num_locals + 1));
|
|
|
|
|
|
|
|
*local_registers
|
|
|
|
.write(context.gc_context)
|
|
|
|
.get_mut(0)
|
|
|
|
.unwrap() = global.into();
|
2020-02-18 22:54:39 +00:00
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
method,
|
|
|
|
pc: 0,
|
2020-02-24 03:11:02 +00:00
|
|
|
this: Some(global),
|
2020-02-18 22:54:39 +00:00
|
|
|
arguments: None,
|
|
|
|
is_executing: false,
|
2020-02-22 17:51:34 +00:00
|
|
|
local_registers,
|
2020-02-18 22:54:39 +00:00
|
|
|
return_value: None,
|
|
|
|
local_scope: ScriptObject::bare_object(context.gc_context),
|
|
|
|
scope,
|
2020-02-25 23:07:53 +00:00
|
|
|
base_proto: None,
|
2020-02-18 22:54:39 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
pub fn from_action(
|
2020-02-07 19:54:14 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2020-07-02 23:02:54 +00:00
|
|
|
method: Avm2MethodEntry<'gc>,
|
2020-07-01 03:44:14 +00:00
|
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
2020-02-24 03:11:02 +00:00
|
|
|
this: Option<Object<'gc>>,
|
2020-02-22 20:02:51 +00:00
|
|
|
arguments: &[Value<'gc>],
|
2020-02-25 23:07:53 +00:00
|
|
|
base_proto: Option<Object<'gc>>,
|
2020-02-07 19:54:14 +00:00
|
|
|
) -> Result<Self, Error> {
|
2020-02-12 23:52:00 +00:00
|
|
|
let num_locals = method.body().num_locals;
|
2020-02-22 20:02:51 +00:00
|
|
|
let num_declared_arguments = method.method().params.len() as u32;
|
|
|
|
let local_registers = GcCell::allocate(
|
|
|
|
context.gc_context,
|
|
|
|
RegisterSet::new(num_locals + num_declared_arguments + 1),
|
|
|
|
);
|
|
|
|
|
|
|
|
{
|
|
|
|
let mut write = local_registers.write(context.gc_context);
|
2020-02-24 03:11:02 +00:00
|
|
|
*write.get_mut(0).unwrap() = this.map(|t| t.into()).unwrap_or(Value::Null);
|
2020-02-22 20:02:51 +00:00
|
|
|
|
|
|
|
for i in 0..num_declared_arguments {
|
|
|
|
*write.get_mut(1 + i).unwrap() = arguments
|
|
|
|
.get(i as usize)
|
|
|
|
.cloned()
|
|
|
|
.unwrap_or(Value::Undefined);
|
|
|
|
}
|
|
|
|
}
|
2020-02-07 19:54:14 +00:00
|
|
|
|
|
|
|
Ok(Self {
|
2020-02-12 23:52:00 +00:00
|
|
|
method,
|
2020-02-06 04:15:03 +00:00
|
|
|
pc: 0,
|
|
|
|
this,
|
2020-02-22 20:02:51 +00:00
|
|
|
arguments: None,
|
2020-02-06 04:15:03 +00:00
|
|
|
is_executing: false,
|
2020-02-22 20:02:51 +00:00
|
|
|
local_registers,
|
2020-02-08 21:59:59 +00:00
|
|
|
return_value: None,
|
2020-02-10 19:54:55 +00:00
|
|
|
local_scope: ScriptObject::bare_object(context.gc_context),
|
2020-02-14 04:33:24 +00:00
|
|
|
scope,
|
2020-02-25 23:07:53 +00:00
|
|
|
base_proto,
|
2020-02-07 19:54:14 +00:00
|
|
|
})
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempts to lock the activation frame for execution.
|
|
|
|
///
|
|
|
|
/// If this frame is already executing, that is an error condition.
|
|
|
|
pub fn lock(&mut self) -> Result<(), Error> {
|
|
|
|
if self.is_executing {
|
|
|
|
return Err("Attempted to execute the same frame twice".into());
|
|
|
|
}
|
|
|
|
|
|
|
|
self.is_executing = true;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Unlock the activation object. This allows future execution to run on it
|
|
|
|
/// again.
|
|
|
|
pub fn unlock_execution(&mut self) {
|
|
|
|
self.is_executing = false;
|
|
|
|
}
|
|
|
|
|
2020-02-12 23:52:00 +00:00
|
|
|
/// Obtain a reference to the method being executed.
|
2020-07-02 23:02:54 +00:00
|
|
|
pub fn method(&self) -> &Avm2MethodEntry<'gc> {
|
2020-02-12 23:52:00 +00:00
|
|
|
&self.method
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the PC value.
|
|
|
|
pub fn pc(&self) -> usize {
|
|
|
|
self.pc
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set the PC value.
|
|
|
|
pub fn set_pc(&mut self, new_pc: usize) {
|
|
|
|
self.pc = new_pc;
|
|
|
|
}
|
2020-02-07 19:54:14 +00:00
|
|
|
|
|
|
|
/// Retrieve a local register.
|
|
|
|
pub fn local_register(&self, id: u32) -> Option<Value<'gc>> {
|
|
|
|
self.local_registers.read().get(id).cloned()
|
|
|
|
}
|
|
|
|
|
2020-02-11 19:46:47 +00:00
|
|
|
/// Get the current scope stack.
|
2020-02-11 04:58:15 +00:00
|
|
|
pub fn scope(&self) -> Option<GcCell<'gc, Scope<'gc>>> {
|
|
|
|
self.scope
|
|
|
|
}
|
|
|
|
|
2020-02-11 19:46:47 +00:00
|
|
|
/// Set a new scope stack.
|
|
|
|
pub fn set_scope(&mut self, new_scope: Option<GcCell<'gc, Scope<'gc>>>) {
|
|
|
|
self.scope = new_scope;
|
|
|
|
}
|
|
|
|
|
2020-02-07 19:54:14 +00:00
|
|
|
/// Set a local register.
|
|
|
|
///
|
|
|
|
/// Returns `true` if the set was successful; `false` otherwise
|
|
|
|
pub fn set_local_register(
|
|
|
|
&mut self,
|
|
|
|
id: u32,
|
|
|
|
value: impl Into<Value<'gc>>,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> bool {
|
|
|
|
if let Some(r) = self.local_registers.write(mc).get_mut(id) {
|
|
|
|
*r = value.into();
|
|
|
|
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2020-02-08 21:59:59 +00:00
|
|
|
|
|
|
|
/// Set the return value.
|
|
|
|
pub fn set_return_value(&mut self, value: Value<'gc>) {
|
|
|
|
self.return_value = Some(value);
|
|
|
|
}
|
2020-02-25 23:07:53 +00:00
|
|
|
|
|
|
|
/// Get the base prototype of the object that the currently executing
|
|
|
|
/// method was retrieved from, if one exists.
|
|
|
|
pub fn base_proto(&self) -> Option<Object<'gc>> {
|
|
|
|
self.base_proto
|
|
|
|
}
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|