ruffle/core/src/avm2.rs

199 lines
5.5 KiB
Rust
Raw Normal View History

//! ActionScript Virtual Machine 2 (AS3) support
use crate::avm2::globals::SystemPrototypes;
2020-08-06 02:11:52 +00:00
use crate::avm2::object::ScriptObject;
use crate::avm2::scope::Scope;
use crate::avm2::script::Script;
use crate::avm2::script::TranslationUnit;
use crate::context::UpdateContext;
use crate::tag_utils::SwfSlice;
use gc_arena::{Collect, GcCell, MutationContext};
use std::rc::Rc;
use swf::avm2::read::Reader;
2020-02-20 19:41:15 +00:00
#[macro_export]
macro_rules! avm_debug {
($avm: expr, $($arg:tt)*) => (
if $avm.show_debug_output() {
log::debug!($($arg)*)
}
2020-02-20 19:41:15 +00:00
)
}
mod activation;
mod array;
mod class;
mod function;
mod globals;
mod method;
mod names;
mod object;
2020-02-15 01:30:19 +00:00
mod property;
mod property_map;
mod return_value;
2020-02-10 19:54:55 +00:00
mod scope;
mod script;
mod slot;
mod string;
mod traits;
mod value;
2020-08-06 02:11:52 +00:00
pub use crate::avm2::activation::Activation;
pub use crate::avm2::names::{Namespace, QName};
pub use crate::avm2::object::{Object, StageObject, TObject};
pub use crate::avm2::value::Value;
/// Boxed error alias.
///
/// As AVM2 is a far stricter VM than AVM1, this may eventually be replaced
/// with a proper Avm2Error enum.
pub type Error = Box<dyn std::error::Error>;
/// The state of an AVM2 interpreter.
#[derive(Collect)]
#[collect(no_drop)]
pub struct Avm2<'gc> {
/// Values currently present on the operand stack.
stack: Vec<Value<'gc>>,
/// Global scope object.
globals: Object<'gc>,
/// System prototypes.
system_prototypes: Option<SystemPrototypes<'gc>>,
#[cfg(feature = "avm_debug")]
pub debug_output: bool,
}
impl<'gc> Avm2<'gc> {
/// Construct a new AVM interpreter.
pub fn new(mc: MutationContext<'gc, '_>) -> Self {
let globals = ScriptObject::bare_object(mc);
Self {
stack: Vec::new(),
globals,
system_prototypes: None,
#[cfg(feature = "avm_debug")]
debug_output: false,
}
}
pub fn load_player_globals(context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let mut activation = Activation::from_nothing(context.reborrow());
globals::load_player_globals(&mut activation)
}
Completely overhaul the way traits are defined on objects. Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal. This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property. However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon. The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests. We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly. `super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
/// Return the current set of system prototypes.
///
/// This function panics if the interpreter has not yet been initialized.
Completely overhaul the way traits are defined on objects. Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal. This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property. However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon. The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests. We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly. `super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
pub fn prototypes(&self) -> &SystemPrototypes<'gc> {
self.system_prototypes.as_ref().unwrap()
Completely overhaul the way traits are defined on objects. Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal. This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property. However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon. The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests. We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly. `super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
}
/// Run a script's initializer method.
pub fn run_script_initializer(
script: GcCell<'gc, Script<'gc>>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<(), Error> {
2020-07-28 03:19:43 +00:00
let globals = context.avm2.globals;
let mut init_activation = Activation::from_script(context.reborrow(), script, globals)?;
2020-07-28 03:19:43 +00:00
init_activation.run_stack_frame_for_script(script)
}
/// Load an ABC file embedded in a `SwfSlice`.
///
/// The `SwfSlice` must resolve to the contents of an ABC file.
pub fn load_abc(
abc: SwfSlice,
_abc_name: &str,
_lazy_init: bool,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<(), Error> {
let mut read = Reader::new(abc.as_ref());
let abc_file = Rc::new(read.read()?);
let tunit = TranslationUnit::from_abc(abc_file.clone(), context.gc_context);
for i in (0..abc_file.scripts.len()).rev() {
let script = tunit.load_script(i as u32, context.avm2, context.gc_context)?;
2020-07-28 03:19:43 +00:00
let mut globals = context.avm2.globals();
let scope = Scope::push_scope(None, globals, context.gc_context);
2020-07-28 03:19:43 +00:00
let mut null_activation = Activation::from_nothing(context.reborrow());
// TODO: Lazyinit means we shouldn't do this until traits are
// actually mentioned...
for trait_entry in script.read().traits()?.iter() {
globals.install_foreign_trait(
&mut null_activation,
trait_entry.clone(),
Some(scope),
globals,
)?;
}
drop(null_activation);
2020-07-28 03:19:43 +00:00
Self::run_script_initializer(script, context)?;
}
Ok(())
}
pub fn globals(&self) -> Object<'gc> {
self.globals
}
/// Push a value onto the operand stack.
fn push(&mut self, value: impl Into<Value<'gc>>) {
let value = value.into();
avm_debug!(self, "Stack push {}: {:?}", self.stack.len(), value);
self.stack.push(value);
}
/// Retrieve the top-most value on the operand stack.
#[allow(clippy::let_and_return)]
fn pop(&mut self) -> Value<'gc> {
let value = self.stack.pop().unwrap_or_else(|| {
log::warn!("Avm1::pop: Stack underflow");
Value::Undefined
});
avm_debug!(self, "Stack pop {}: {:?}", self.stack.len(), value);
value
}
fn pop_args(&mut self, arg_count: u32) -> Vec<Value<'gc>> {
let mut args = Vec::with_capacity(arg_count as usize);
args.resize(arg_count as usize, Value::Undefined);
for arg in args.iter_mut().rev() {
*arg = self.pop();
}
args
}
#[cfg(feature = "avm_debug")]
#[inline]
pub fn show_debug_output(&self) -> bool {
self.debug_output
}
#[cfg(not(feature = "avm_debug"))]
pub const fn show_debug_output(&self) -> bool {
false
}
#[cfg(feature = "avm_debug")]
pub fn set_show_debug_output(&mut self, visible: bool) {
self.debug_output = visible;
}
#[cfg(not(feature = "avm_debug"))]
pub const fn set_show_debug_output(&self, _visible: bool) {}
}