ruffle/core/src/avm2.rs

1533 lines
50 KiB
Rust
Raw Normal View History

//! ActionScript Virtual Machine 2 (AS3) support
use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::function::{Avm2MethodEntry, FunctionObject};
use crate::avm2::globals::SystemPrototypes;
2020-02-11 19:33:30 +00:00
use crate::avm2::names::{Multiname, Namespace, QName};
use crate::avm2::object::{Object, TObject};
use crate::avm2::return_value::ReturnValue;
use crate::avm2::scope::Scope;
use crate::avm2::script::{Script, TranslationUnit};
2020-02-22 23:44:51 +00:00
use crate::avm2::script_object::ScriptObject;
use crate::avm2::value::Value;
use crate::context::UpdateContext;
use crate::tag_utils::SwfSlice;
use gc_arena::{Collect, GcCell, MutationContext};
use std::io::Cursor;
use std::rc::Rc;
use swf::avm2::read::Reader;
use swf::avm2::types::{
AbcFile, Class as AbcClass, Index, Method as AbcMethod, MethodBody, Multiname as AbcMultiname,
Namespace as AbcNamespace, Op,
};
use swf::read::SwfRead;
2020-02-20 19:41:15 +00:00
#[macro_export]
macro_rules! avm_debug {
($($arg:tt)*) => (
#[cfg(feature = "avm_debug")]
log::debug!($($arg)*)
)
}
mod activation;
mod class;
mod function;
mod globals;
mod names;
mod object;
2020-02-15 01:30:19 +00:00
mod property;
mod return_value;
2020-02-10 19:54:55 +00:00
mod scope;
mod script;
mod script_object;
mod slot;
mod r#trait;
mod value;
/// Boxed error alias.
///
/// As AVM2 is a far stricter VM than AVM1, this may eventually be replaced
/// with a proper Avm2Error enum.
type Error = Box<dyn std::error::Error>;
/// The state of an AVM2 interpreter.
#[derive(Collect)]
#[collect(no_drop)]
pub struct Avm2<'gc> {
/// All activation records for the current interpreter.
stack_frames: Vec<GcCell<'gc, Activation<'gc>>>,
/// Values currently present on the operand stack.
stack: Vec<Value<'gc>>,
/// Global scope object.
globals: Object<'gc>,
/// System prototypes.
system_prototypes: SystemPrototypes<'gc>,
}
impl<'gc> Avm2<'gc> {
/// Construct a new AVM interpreter.
pub fn new(mc: MutationContext<'gc, '_>) -> Self {
let (globals, system_prototypes) = globals::construct_global_scope(mc);
Self {
stack_frames: Vec::new(),
stack: Vec::new(),
globals,
system_prototypes,
}
}
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.
pub fn prototypes(&self) -> &SystemPrototypes<'gc> {
&self.system_prototypes
}
/// Load an ABC file embedded in a `SwfSlice`.
///
/// The `SwfSlice` must resolve to the contents of an ABC file.
pub fn load_abc(
&mut self,
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, context.gc_context);
for i in (0..abc_file.scripts.len()).rev() {
let script = tunit.load_script(i as u32, context.gc_context)?;
let scope = Scope::push_scope(None, self.globals(), context.gc_context);
// TODO: Lazyinit means we shouldn't do this until traits are
// actually mentioned...
for trait_entry in script.read().traits()?.iter() {
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
self.globals().install_foreign_trait(
self,
context,
trait_entry.clone(),
Some(scope),
self.globals(),
)?;
}
self.insert_stack_frame_for_script(context, script)?;
}
Ok(())
}
pub fn globals(&self) -> Object<'gc> {
self.globals
}
/// Get the current stack frame (`Activation` object).
pub fn current_stack_frame(&self) -> Option<GcCell<'gc, Activation<'gc>>> {
self.stack_frames.last().copied()
}
/// Add a new stack frame to the stack, which can represent any particular
/// operation you like that needs to execute AVM2 code.
pub fn insert_stack_frame(&mut self, frame: GcCell<'gc, Activation<'gc>>) {
self.stack_frames.push(frame)
}
/// Add a new stack frame for executing an entrypoint script.
pub fn insert_stack_frame_for_script(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
script: GcCell<'gc, Script<'gc>>,
) -> Result<(), Error> {
self.stack_frames.push(GcCell::allocate(
context.gc_context,
Activation::from_script(context, script, self.globals)?,
));
Ok(())
}
/// Destroy the current stack frame (if there is one) with a return value.
///
/// The given return value will be pushed on the stack if there is a
/// function to return it to. Otherwise, it will be discarded.
///
/// NOTE: This means that if you are starting a brand new AVM stack just to
/// get it's return value, you won't get that value. Instead, retain a cell
/// referencing the oldest activation frame and use that to retrieve the
/// return value.
fn retire_stack_frame(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
return_value: Value<'gc>,
) -> Result<(), Error> {
if let Some(frame) = self.current_stack_frame() {
self.stack_frames.pop();
let can_return = !self.stack_frames.is_empty();
if can_return {
frame
.write(context.gc_context)
.set_return_value(return_value.clone());
self.push(return_value);
}
}
Ok(())
}
/// Destroy the current stack frame (if there is one) with an exception.
///
/// TODO: This function should allow exception recovery at some point in
/// the future.
///
/// NOTE: This means that if you are starting a brand new AVM stack just to
/// get it's return value, you won't get that value. Instead, retain a cell
/// referencing the oldest activation frame and use that to retrieve the
/// return value.
fn unwind_stack_frame(&mut self) {
if let Some(_frame) = self.current_stack_frame() {
self.stack_frames.pop();
}
}
/// Perform some action with the current stack frame's reader.
///
/// This function constructs a reader based off the current stack frame's
/// reader. You are permitted to mutate the stack frame as you wish. If the
/// stack frame we started with still exists in the same location on the
/// stack, it's PC will be updated to the Reader's current PC.
///
/// Stack frame identity (for the purpose of the above paragraph) is
/// determined by the data pointed to by the `SwfSlice` of a given frame.
///
/// # Warnings
///
/// It is incorrect to call this function multiple times in the same stack.
/// Doing so will result in any changes in duplicate readers being ignored.
/// Always pass the borrowed reader into functions that need it.
pub fn with_current_reader_mut<F, R>(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
func: F,
) -> Result<R, Error>
where
F: FnOnce(
&mut Self,
&mut Reader<Cursor<&[u8]>>,
&mut UpdateContext<'_, 'gc, '_>,
) -> Result<R, Error>,
{
2020-02-12 23:52:00 +00:00
let (abc, frame_cell, method_body_index, pc) = {
let frame = self
.current_stack_frame()
.ok_or("No stack frame to read!")?;
let mut frame_ref = frame.write(context.gc_context);
frame_ref.lock()?;
2020-02-12 23:52:00 +00:00
let method = frame_ref.method();
let abc = method.abc();
let _method_index = method.abc_method;
2020-02-12 23:52:00 +00:00
let method_body_index = method.abc_method_body as usize;
(abc, frame, method_body_index, frame_ref.pc())
};
let method_body: Result<&MethodBody, Error> =
abc.method_bodies.get(method_body_index).ok_or_else(|| {
"Attempting to execute a method that does not exist"
.to_string()
.into()
});
let cursor = Cursor::new(method_body?.code.as_ref());
let mut read = Reader::new(cursor);
read.get_inner().set_position(pc as u64);
let r = func(self, &mut read, context);
let mut frame_ref = frame_cell.write(context.gc_context);
frame_ref.unlock_execution();
frame_ref.set_pc(read.get_inner().position() as usize);
r
}
/// Execute the AVM stack until it is exhausted.
pub fn run_stack_till_empty(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<(), Error> {
while !self.stack_frames.is_empty() {
self.with_current_reader_mut(context, |this, r, context| {
this.do_next_opcode(context, r)
})?;
}
// Operand stack should be empty at this point.
// This is probably a bug on our part,
// although bytecode could in theory leave data on the stack.
if !self.stack.is_empty() {
log::warn!("Operand stack is not empty after execution");
self.stack.clear();
}
Ok(())
}
/// Execute the AVM stack until a given activation returns.
pub fn run_current_frame(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
stop_frame: GcCell<'gc, Activation<'gc>>,
) -> Result<(), Error> {
let mut stop_frame_id = None;
for (index, frame) in self.stack_frames.iter().enumerate() {
if GcCell::ptr_eq(stop_frame, *frame) {
stop_frame_id = Some(index);
}
}
if let Some(stop_frame_id) = stop_frame_id {
while self
.stack_frames
.get(stop_frame_id)
.map(|fr| GcCell::ptr_eq(stop_frame, *fr))
.unwrap_or(false)
{
self.with_current_reader_mut(context, |this, r, context| {
this.do_next_opcode(context, r)
})?;
}
Ok(())
} else {
Err("Attempted to run a frame not on the current interpreter stack".into())
}
}
/// Push a value onto the operand stack.
fn push(&mut self, value: impl Into<Value<'gc>>) {
let value = value.into();
avm_debug!("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!("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
}
fn register_value(&self, index: u32) -> Result<Value<'gc>, Error> {
self.current_stack_frame()
.and_then(|sf| sf.read().local_register(index))
.ok_or_else(|| format!("Out of bounds register read: {}", index).into())
}
fn set_register_value(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: u32,
value: impl Into<Value<'gc>>,
) -> Result<(), Error> {
match self.current_stack_frame().map(|sf| {
sf.write(context.gc_context)
.set_local_register(index, value, context.gc_context)
}) {
Some(true) => Ok(()),
_ => Err(format!("Out of bounds register write: {}", index).into()),
}
}
/// Retrieve the current constant pool for the currently executing function.
fn current_abc(&self) -> Option<Rc<AbcFile>> {
self.current_stack_frame()
.map(|sf| sf.read().method().abc())
}
fn current_translation_unit(&self) -> Option<TranslationUnit<'gc>> {
self.current_stack_frame()
.map(|sf| sf.read().method().translation_unit())
}
/// Retrieve a int from the current constant pool.
fn pool_int(&self, index: Index<i32>) -> Result<i32, Error> {
value::abc_int(&self.current_abc().unwrap(), index)
}
/// Retrieve a int from the current constant pool.
fn pool_uint(&self, index: Index<u32>) -> Result<u32, Error> {
value::abc_uint(&self.current_abc().unwrap(), index)
}
/// Retrieve a double from the current constant pool.
fn pool_double(&self, index: Index<f64>) -> Result<f64, Error> {
value::abc_double(&self.current_abc().unwrap(), index)
}
/// Retrieve a string from the current constant pool.
fn pool_string(&self, index: Index<String>) -> Result<String, Error> {
value::abc_string(&self.current_abc().unwrap(), index)
}
/// Retrieve a namespace from the current constant pool.
fn pool_namespace(&self, index: Index<AbcNamespace>) -> Result<Namespace, Error> {
Namespace::from_abc_namespace(&self.current_abc().unwrap(), index)
}
/// Retrieve a multiname from the current constant pool.
fn pool_multiname(&mut self, index: Index<AbcMultiname>) -> Result<Multiname, Error> {
Multiname::from_abc_multiname(&self.current_abc().unwrap(), index, self)
}
/// Retrieve a static, or non-runtime, multiname from the current constant
/// pool.
fn pool_multiname_static(&mut self, index: Index<AbcMultiname>) -> Result<Multiname, Error> {
Multiname::from_abc_multiname_static(&self.current_abc().unwrap(), index)
}
/// Retrieve a method entry from the current ABC file's method table.
fn table_method(&mut self, index: Index<AbcMethod>) -> Result<Avm2MethodEntry<'gc>, Error> {
Avm2MethodEntry::from_method_index(self.current_translation_unit().unwrap(), index.clone())
.ok_or_else(|| format!("Method index {} does not exist", index.0).into())
}
/// Retrieve a class entry from the current ABC file's method table.
fn table_class(
&mut self,
index: Index<AbcClass>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<GcCell<'gc, Class<'gc>>, Error> {
self.current_translation_unit()
.unwrap()
.load_class(index.0, context.gc_context)
}
/// Run a single action from a given action reader.
pub fn do_next_opcode(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
reader: &mut Reader<Cursor<&[u8]>>,
) -> Result<(), Error> {
let op = reader.read_op();
if let Ok(Some(op)) = op {
avm_debug!("Opcode: {:?}", op);
let result = match op {
Op::PushByte { value } => self.op_push_byte(value),
Op::PushDouble { value } => self.op_push_double(value),
Op::PushFalse => self.op_push_false(),
Op::PushInt { value } => self.op_push_int(value),
Op::PushNamespace { value } => self.op_push_namespace(value),
Op::PushNaN => self.op_push_nan(),
Op::PushNull => self.op_push_null(),
Op::PushShort { value } => self.op_push_short(value),
Op::PushString { value } => self.op_push_string(value),
Op::PushTrue => self.op_push_true(),
Op::PushUint { value } => self.op_push_uint(value),
Op::PushUndefined => self.op_push_undefined(),
Op::Pop => self.op_pop(),
2020-02-22 21:30:45 +00:00
Op::Dup => self.op_dup(),
Op::GetLocal { index } => self.op_get_local(index),
Op::SetLocal { index } => self.op_set_local(context, index),
Op::Kill { index } => self.op_kill(context, index),
Op::Call { num_args } => self.op_call(context, num_args),
Op::CallMethod { index, num_args } => self.op_call_method(context, index, num_args),
Op::CallProperty { index, num_args } => {
self.op_call_property(context, index, num_args)
}
Op::CallPropLex { index, num_args } => {
self.op_call_prop_lex(context, index, num_args)
}
Op::CallPropVoid { index, num_args } => {
self.op_call_prop_void(context, index, num_args)
}
Op::CallStatic { index, num_args } => self.op_call_static(context, index, num_args),
Op::CallSuper { index, num_args } => self.op_call_super(context, index, num_args),
Op::CallSuperVoid { index, num_args } => {
self.op_call_super_void(context, index, num_args)
}
Op::ReturnValue => self.op_return_value(context),
Op::ReturnVoid => self.op_return_void(context),
2020-02-11 19:33:30 +00:00
Op::GetProperty { index } => self.op_get_property(context, index),
Op::SetProperty { index } => self.op_set_property(context, index),
2020-02-22 21:21:28 +00:00
Op::InitProperty { index } => self.op_init_property(context, index),
2020-02-21 19:52:24 +00:00
Op::DeleteProperty { index } => self.op_delete_property(context, index),
Op::GetSuper { index } => self.op_get_super(context, index),
Op::SetSuper { index } => self.op_set_super(context, index),
Op::PushScope => self.op_push_scope(context),
Op::PushWith => self.op_push_with(context),
Op::PopScope => self.op_pop_scope(context),
2020-02-22 04:31:30 +00:00
Op::GetScopeObject { index } => self.op_get_scope_object(index),
2020-02-22 23:14:07 +00:00
Op::GetGlobalScope => self.op_get_global_scope(),
Op::FindProperty { index } => self.op_find_property(context, index),
Op::FindPropStrict { index } => self.op_find_prop_strict(context, index),
Op::GetLex { index } => self.op_get_lex(context, index),
2020-02-19 19:17:33 +00:00
Op::GetSlot { index } => self.op_get_slot(index),
Op::SetSlot { index } => self.op_set_slot(context, index),
Op::GetGlobalSlot { index } => self.op_get_global_slot(index),
Op::SetGlobalSlot { index } => self.op_set_global_slot(context, index),
2020-02-19 23:53:21 +00:00
Op::Construct { num_args } => self.op_construct(context, num_args),
Op::ConstructProp { index, num_args } => {
self.op_construct_prop(context, index, num_args)
}
Op::ConstructSuper { num_args } => self.op_construct_super(context, num_args),
2020-02-22 23:44:51 +00:00
Op::NewActivation => self.op_new_activation(context),
Op::NewObject { num_args } => self.op_new_object(context, num_args),
Op::NewFunction { index } => self.op_new_function(context, index),
Op::NewClass { index } => self.op_new_class(context, index),
Op::CoerceA => self.op_coerce_a(),
Op::Jump { offset } => self.op_jump(offset, reader),
Op::IfTrue { offset } => self.op_if_true(offset, reader),
Op::IfFalse { offset } => self.op_if_false(offset, reader),
Op::IfStrictEq { offset } => self.op_if_strict_eq(offset, reader),
Op::IfStrictNe { offset } => self.op_if_strict_ne(offset, reader),
2020-03-09 02:48:54 +00:00
Op::StrictEquals => self.op_strict_equals(),
Implement `hasnext`, `hasnext2`, `nextname`, `nextvalue`, and the underlying enumeration machinery that powers it. I have... significant reservations with the way object enumeration happens in AVM2. For comparison, AVM1 enumeration works like this: You enumerate the entire object at once, producing a list of property names, which are then pushed onto the stack after a sentinel value. This is a properly abstract way to handle property enumeration. In AVM2, they completely replaced this with index-based enumeration. What this means is that you hand the object an index and it gives you back a name or value. There's also an instruction that will give you the next index in the object. The only advantage I can think of is that it results in less stack manipulation if you want to bail out of iteration early. You just jump out of your loop and kill the registers you don't care about. The disadvantage is that it locks the object representation down pretty hard. They also screwed up the definition of `hasnext`, and thus the VM is stuck enumerating properties from 1. This is because `hasnext` and `hasnext2` increment the index value before checking the object. Code generated by Animate 2020 (which I suspect to be the final version of that software that generates AVM2 code) initializes the index at hero, and then does `hasnext2`, hence we have to start from one. I actually cheated a little and added a separate `Vec` for storing enumerant names. I strongly suspect that Adobe's implementation has objects be inherently slot-oriented, and named properties are just hashmap lookups to slots. This would allow enumerating the slots to get names out of the object.
2020-03-06 02:26:01 +00:00
Op::HasNext => self.op_has_next(),
Op::HasNext2 {
object_register,
index_register,
} => self.op_has_next_2(context, object_register, index_register),
Op::NextName => self.op_next_name(),
Op::NextValue => self.op_next_value(context),
Op::Label => Ok(()),
Op::Debug {
is_local_register,
register_name,
register,
} => self.op_debug(is_local_register, register_name, register),
Op::DebugFile { file_name } => self.op_debug_file(file_name),
Op::DebugLine { line_num } => self.op_debug_line(line_num),
_ => self.unknown_op(op),
};
if let Err(ref e) = result {
log::error!("AVM2 error: {}", e);
self.unwind_stack_frame();
return result;
}
} else if let Ok(None) = op {
log::error!("Unknown opcode!");
self.unwind_stack_frame();
return Err("Unknown opcode!".into());
} else if let Err(e) = op {
log::error!("Parse error: {:?}", e);
self.unwind_stack_frame();
return Err(e.into());
}
Ok(())
}
fn unknown_op(&mut self, op: swf::avm2::types::Op) -> Result<(), Error> {
log::error!("Unknown AVM2 opcode: {:?}", op);
Err("Unknown op".into())
}
fn op_push_byte(&mut self, value: u8) -> Result<(), Error> {
self.push(value);
Ok(())
}
fn op_push_double(&mut self, value: Index<f64>) -> Result<(), Error> {
self.push(self.pool_double(value)?);
Ok(())
}
fn op_push_false(&mut self) -> Result<(), Error> {
self.push(false);
Ok(())
}
fn op_push_int(&mut self, value: Index<i32>) -> Result<(), Error> {
self.push(self.pool_int(value)?);
Ok(())
}
fn op_push_namespace(&mut self, value: Index<AbcNamespace>) -> Result<(), Error> {
self.push(self.pool_namespace(value)?);
Ok(())
}
fn op_push_nan(&mut self) -> Result<(), Error> {
self.push(std::f64::NAN);
Ok(())
}
fn op_push_null(&mut self) -> Result<(), Error> {
self.push(Value::Null);
Ok(())
}
fn op_push_short(&mut self, value: u32) -> Result<(), Error> {
self.push(value);
Ok(())
}
fn op_push_string(&mut self, value: Index<String>) -> Result<(), Error> {
self.push(self.pool_string(value)?);
Ok(())
}
fn op_push_true(&mut self) -> Result<(), Error> {
self.push(true);
Ok(())
}
fn op_push_uint(&mut self, value: Index<u32>) -> Result<(), Error> {
self.push(self.pool_uint(value)?);
Ok(())
}
fn op_push_undefined(&mut self) -> Result<(), Error> {
self.push(Value::Undefined);
Ok(())
}
fn op_pop(&mut self) -> Result<(), Error> {
self.pop();
Ok(())
}
2020-02-22 21:30:45 +00:00
fn op_dup(&mut self) -> Result<(), Error> {
self.push(self.stack.last().cloned().unwrap_or(Value::Undefined));
Ok(())
}
fn op_get_local(&mut self, register_index: u32) -> Result<(), Error> {
self.push(self.register_value(register_index)?);
Ok(())
}
fn op_set_local(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
register_index: u32,
) -> Result<(), Error> {
let value = self.pop();
self.set_register_value(context, register_index, value)
}
fn op_kill(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
register_index: u32,
) -> Result<(), Error> {
self.set_register_value(context, register_index, Value::Undefined)
}
fn op_call(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
arg_count: u32,
) -> Result<(), Error> {
let args = self.pop_args(arg_count);
let receiver = self.pop().as_object().ok();
let function = self.pop().as_object()?;
let base_proto = receiver.and_then(|r| r.proto());
let value = function.call(receiver, &args, self, context, base_proto)?;
self.push(value);
Ok(())
}
fn op_call_method(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMethod>,
arg_count: u32,
) -> Result<(), Error> {
let args = self.pop_args(arg_count);
let receiver = self.pop().as_object()?;
let function: Result<Object<'gc>, Error> = receiver
.get_method(index.0)
.ok_or_else(|| format!("Object method {} does not exist", index.0).into());
let base_proto = receiver.proto();
let value = function?.call(Some(receiver), &args, self, context, base_proto)?;
self.push(value);
Ok(())
}
fn op_call_property(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
arg_count: u32,
) -> Result<(), Error> {
let args = self.pop_args(arg_count);
let multiname = self.pool_multiname(index)?;
let mut receiver = self.pop().as_object()?;
let name: Result<QName, Error> = receiver
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
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let name = name?;
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
let base_proto = receiver.get_base_proto(&name)?;
let function = receiver
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
.get_property(receiver, &name, self, context)?
.as_object()?;
let value = function.call(Some(receiver), &args, self, context, base_proto)?;
self.push(value);
Ok(())
}
fn op_call_prop_lex(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
arg_count: u32,
) -> Result<(), Error> {
let args = self.pop_args(arg_count);
let multiname = self.pool_multiname(index)?;
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
let mut receiver = self.pop().as_object()?;
let name: Result<QName, Error> = receiver
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
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let function = receiver
.get_property(receiver, &name?, self, context)?
.as_object()?;
let value = function.call(None, &args, self, context, None)?;
self.push(value);
Ok(())
}
fn op_call_prop_void(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
arg_count: u32,
) -> Result<(), Error> {
let args = self.pop_args(arg_count);
let multiname = self.pool_multiname(index)?;
let mut receiver = self.pop().as_object()?;
let name: Result<QName, Error> = receiver
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
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let name = name?;
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
let base_proto = receiver.get_base_proto(&name)?;
let function = receiver
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
.get_property(receiver, &name, self, context)?
.as_object()?;
function.call(Some(receiver), &args, self, context, base_proto)?;
Ok(())
}
fn op_call_static(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMethod>,
arg_count: u32,
) -> Result<(), Error> {
let args = self.pop_args(arg_count);
let receiver = self.pop().as_object()?;
let method = self.table_method(index)?;
let scope = self.current_stack_frame().unwrap().read().scope(); //TODO: Is this correct?
let function = FunctionObject::from_method(
context.gc_context,
method.into(),
scope,
self.system_prototypes.function,
None,
);
let value = function.call(Some(receiver), &args, self, context, receiver.proto())?;
self.push(value);
Ok(())
}
fn op_call_super(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
arg_count: u32,
) -> Result<(), Error> {
let args = self.pop_args(arg_count);
let multiname = self.pool_multiname(index)?;
let receiver = self.pop().as_object()?;
let name: Result<QName, Error> = receiver
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
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let base_proto: Result<Object<'gc>, Error> = self
.current_stack_frame()
.unwrap()
.read()
.base_proto()
.and_then(|bp| bp.proto())
.ok_or_else(|| {
"Attempted to call super method without a superclass."
.to_string()
.into()
});
let base_proto = base_proto?;
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
let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround
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
let function = base
.get_property(receiver, &name?, self, context)?
.as_object()?;
let value = function.call(Some(receiver), &args, self, context, Some(base_proto))?;
self.push(value);
Ok(())
}
fn op_call_super_void(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
arg_count: u32,
) -> Result<(), Error> {
let args = self.pop_args(arg_count);
let multiname = self.pool_multiname(index)?;
let receiver = self.pop().as_object()?;
let name: Result<QName, Error> = receiver
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
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let base_proto: Result<Object<'gc>, Error> = self
.current_stack_frame()
.unwrap()
.read()
.base_proto()
.and_then(|bp| bp.proto())
.ok_or_else(|| {
"Attempted to call super method without a superclass."
.to_string()
.into()
});
let base_proto = base_proto?;
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
let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround
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
let function = base
.get_property(receiver, &name?, self, context)?
.as_object()?;
function.call(Some(receiver), &args, self, context, Some(base_proto))?;
Ok(())
}
fn op_return_value(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let return_value = self.pop();
self.retire_stack_frame(context, return_value)
}
fn op_return_void(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
self.retire_stack_frame(context, Value::Undefined)
}
2020-02-11 19:33:30 +00:00
fn op_get_property(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
) -> Result<(), Error> {
let multiname = self.pool_multiname(index)?;
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
let mut object = self.pop().as_object()?;
2020-02-11 19:33:30 +00:00
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
let name: Result<QName, Error> = object.resolve_multiname(&multiname)?.ok_or_else(|| {
format!("Could not resolve property {:?}", multiname.local_name()).into()
});
2020-02-11 19:33:30 +00:00
let value = object.get_property(object, &name?, self, context)?;
2020-02-11 19:33:30 +00:00
self.push(value);
Ok(())
}
fn op_set_property(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
) -> Result<(), Error> {
let value = self.pop();
let multiname = self.pool_multiname(index)?;
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
let mut object = self.pop().as_object()?;
2020-02-11 19:33:30 +00:00
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
if let Some(name) = object.resolve_multiname(&multiname)? {
object.set_property(object, &name, value, self, context)
2020-02-11 19:33:30 +00:00
} else {
//TODO: Non-dynamic objects should fail
//TODO: This should only work if the public namespace is present
let local_name: Result<&str, Error> = multiname
.local_name()
.ok_or_else(|| "Cannot set property using any name".into());
let name = QName::dynamic_name(local_name?);
object.set_property(object, &name, value, self, context)
2020-02-11 19:33:30 +00:00
}
}
2020-02-22 21:21:28 +00:00
fn op_init_property(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
) -> Result<(), Error> {
let value = self.pop();
let multiname = self.pool_multiname(index)?;
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
let mut object = self.pop().as_object()?;
2020-02-22 21:21:28 +00:00
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
if let Some(name) = object.resolve_multiname(&multiname)? {
object.init_property(object, &name, value, self, context)
2020-02-22 21:21:28 +00:00
} else {
//TODO: Non-dynamic objects should fail
//TODO: This should only work if the public namespace is present
let local_name: Result<&str, Error> = multiname
.local_name()
.ok_or_else(|| "Cannot set property using any name".into());
let name = QName::dynamic_name(local_name?);
object.init_property(object, &name, value, self, context)
2020-02-22 21:21:28 +00:00
}
}
2020-02-21 19:52:24 +00:00
fn op_delete_property(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
) -> Result<(), Error> {
let multiname = self.pool_multiname(index)?;
let object = self.pop().as_object()?;
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
if let Some(name) = object.resolve_multiname(&multiname)? {
2020-02-21 19:52:24 +00:00
self.push(object.delete_property(context.gc_context, &name))
} else {
self.push(false)
}
Ok(())
}
fn op_get_super(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
) -> Result<(), Error> {
let multiname = self.pool_multiname(index)?;
let object = self.pop().as_object()?;
let base_proto: Result<Object<'gc>, Error> = self
.current_stack_frame()
.unwrap()
.read()
.base_proto()
.and_then(|p| p.proto())
.ok_or_else(|| "Attempted to get property on non-existent super object".into());
let base_proto = base_proto?;
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
let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround
let name: Result<QName, Error> = base.resolve_multiname(&multiname)?.ok_or_else(|| {
format!(
"Could not resolve {:?} as super property",
multiname.local_name()
)
.into()
});
let value = base.get_property(object, &name?, self, context)?;
self.push(value);
Ok(())
}
fn op_set_super(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
) -> Result<(), Error> {
let value = self.pop();
let multiname = self.pool_multiname(index)?;
let object = self.pop().as_object()?;
let base_proto: Result<Object<'gc>, Error> = self
.current_stack_frame()
.unwrap()
.read()
.base_proto()
.and_then(|p| p.proto())
.ok_or_else(|| "Attempted to get property on non-existent super object".into());
let base_proto = base_proto?;
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
let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround
let name: Result<QName, Error> = base.resolve_multiname(&multiname)?.ok_or_else(|| {
format!(
"Could not resolve {:?} as super property",
multiname.local_name()
)
.into()
});
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
base.set_property(object, &name?, value, self, context)?;
Ok(())
}
fn op_push_scope(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let object = self.pop().as_object()?;
let activation = self.current_stack_frame().unwrap();
let mut write = activation.write(context.gc_context);
let scope_stack = write.scope();
let new_scope = Scope::push_scope(scope_stack, object, context.gc_context);
write.set_scope(Some(new_scope));
Ok(())
}
fn op_push_with(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let object = self.pop().as_object()?;
let activation = self.current_stack_frame().unwrap();
let mut write = activation.write(context.gc_context);
let scope_stack = write.scope();
let new_scope = Scope::push_with(scope_stack, object, context.gc_context);
write.set_scope(Some(new_scope));
Ok(())
}
fn op_pop_scope(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let activation = self.current_stack_frame().unwrap();
let mut write = activation.write(context.gc_context);
let scope_stack = write.scope();
let new_scope = scope_stack.and_then(|s| s.read().pop_scope());
write.set_scope(new_scope);
Ok(())
}
2020-02-22 04:31:30 +00:00
fn op_get_scope_object(&mut self, mut index: u8) -> Result<(), Error> {
let mut scope = self.current_stack_frame().unwrap().read().scope();
while index > 0 {
if let Some(child_scope) = scope {
scope = child_scope.read().parent_cell();
}
index -= 1;
}
self.push(
scope
.map(|s| s.read().locals().clone().into())
.unwrap_or(Value::Undefined),
);
Ok(())
}
2020-02-22 23:14:07 +00:00
fn op_get_global_scope(&mut self) -> Result<(), Error> {
let mut scope = self.current_stack_frame().unwrap().read().scope();
while let Some(this_scope) = scope {
let parent = this_scope.read().parent_cell();
if parent.is_none() {
break;
}
scope = parent;
}
self.push(
scope
.map(|s| s.read().locals().clone().into())
.unwrap_or(Value::Undefined),
);
Ok(())
}
fn op_find_property(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
) -> Result<(), Error> {
let multiname = self.pool_multiname(index)?;
avm_debug!("Resolving {:?}", multiname);
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
let result = if let Some(scope) = self.current_stack_frame().unwrap().read().scope() {
scope.read().find(&multiname, self, context)?
} else {
None
};
self.push(result.map(|o| o.into()).unwrap_or(Value::Undefined));
Ok(())
}
fn op_find_prop_strict(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
) -> Result<(), Error> {
let multiname = self.pool_multiname(index)?;
avm_debug!("Resolving {:?}", multiname);
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
let found: Result<Object<'gc>, Error> =
if let Some(scope) = self.current_stack_frame().unwrap().read().scope() {
scope.read().find(&multiname, self, context)?
} else {
None
}
.ok_or_else(|| format!("Property does not exist: {:?}", multiname.local_name()).into());
let result: Value<'gc> = found?.into();
self.push(result);
Ok(())
}
fn op_get_lex(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
) -> Result<(), Error> {
let multiname = self.pool_multiname_static(index)?;
avm_debug!("Resolving {:?}", multiname);
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
let found: Result<ReturnValue<'gc>, Error> =
if let Some(scope) = self.current_stack_frame().unwrap().read().scope() {
scope
.write(context.gc_context)
.resolve(&multiname, self, context)?
} else {
None
}
.ok_or_else(|| format!("Property does not exist: {:?}", multiname.local_name()).into());
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
let result: Value<'gc> = found?.resolve(self, context)?;
self.push(result);
Ok(())
}
2020-02-19 19:17:33 +00:00
fn op_get_slot(&mut self, index: u32) -> Result<(), Error> {
let object = self.pop().as_object()?;
let value = object.get_slot(index)?;
self.push(value);
Ok(())
}
fn op_set_slot(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: u32,
) -> Result<(), Error> {
let object = self.pop().as_object()?;
let value = self.pop();
object.set_slot(index, value, context.gc_context)
}
fn op_get_global_slot(&mut self, index: u32) -> Result<(), Error> {
let value = self.globals.get_slot(index)?;
self.push(value);
Ok(())
}
fn op_set_global_slot(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: u32,
) -> Result<(), Error> {
let value = self.pop();
self.globals.set_slot(index, value, context.gc_context)
}
2020-02-19 23:53:21 +00:00
fn op_construct(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
arg_count: u32,
) -> Result<(), Error> {
let args = self.pop_args(arg_count);
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
let mut ctor = self.pop().as_object()?;
2020-02-19 23:53:21 +00:00
let proto = ctor
.get_property(
ctor,
2020-02-19 23:53:21 +00:00
&QName::new(Namespace::public_namespace(), "prototype"),
self,
context,
)?
.as_object()?;
let object = proto.construct(self, context, &args)?;
ctor.call(Some(object), &args, self, context, object.proto())?;
2020-02-19 23:53:21 +00:00
self.push(object);
2020-02-19 23:53:21 +00:00
Ok(())
}
fn op_construct_prop(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
arg_count: u32,
) -> Result<(), Error> {
let args = self.pop_args(arg_count);
2020-02-19 23:53:21 +00:00
let multiname = self.pool_multiname(index)?;
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
let mut source = self.pop().as_object()?;
let ctor_name: Result<QName, Error> =
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
source.resolve_multiname(&multiname)?.ok_or_else(|| {
format!("Could not resolve property {:?}", multiname.local_name()).into()
});
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
let mut ctor = source
.get_property(source, &ctor_name?, self, context)?
2020-02-19 23:53:21 +00:00
.as_object()?;
let proto = ctor
.get_property(
ctor,
2020-02-19 23:53:21 +00:00
&QName::new(Namespace::public_namespace(), "prototype"),
self,
context,
)?
.as_object()?;
let object = proto.construct(self, context, &args)?;
ctor.call(Some(object), &args, self, context, Some(proto))?;
2020-02-19 23:53:21 +00:00
self.push(object);
2020-02-22 23:44:51 +00:00
Ok(())
}
fn op_construct_super(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
arg_count: u32,
) -> Result<(), Error> {
let args = self.pop_args(arg_count);
let receiver = self.pop().as_object()?;
let name = QName::new(Namespace::public_namespace(), "constructor");
let base_proto: Result<Object<'gc>, Error> = self
.current_stack_frame()
.unwrap()
.read()
.base_proto()
.and_then(|p| p.proto())
.ok_or_else(|| {
"Attempted to call super constructor without a superclass."
.to_string()
.into()
});
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
let mut base_proto = base_proto?;
let function = base_proto
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
.get_property(receiver, &name, self, context)?
.as_object()?;
function.call(Some(receiver), &args, self, context, Some(base_proto))?;
Ok(())
}
2020-02-22 23:44:51 +00:00
fn op_new_activation(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
self.push(ScriptObject::bare_object(context.gc_context));
Ok(())
}
fn op_new_object(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
num_args: u32,
) -> Result<(), Error> {
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
let mut object = ScriptObject::object(context.gc_context, self.system_prototypes.object);
2020-02-22 23:44:51 +00:00
for _ in 0..num_args {
let value = self.pop();
let name = self.pop();
object.set_property(
object,
2020-02-22 23:44:51 +00:00
&QName::new(Namespace::public_namespace(), name.as_string()?),
value,
self,
context,
)?;
}
self.push(object);
2020-02-19 23:53:21 +00:00
Ok(())
}
fn op_new_function(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMethod>,
) -> Result<(), Error> {
let method_entry = self.table_method(index)?;
let scope = self.current_stack_frame().unwrap().read().scope();
let mut new_fn = FunctionObject::from_method(
context.gc_context,
method_entry.into(),
scope,
self.system_prototypes.function,
None,
);
let es3_proto = ScriptObject::object(context.gc_context, self.prototypes().object);
new_fn.install_slot(
context.gc_context,
QName::new(Namespace::public_namespace(), "prototype"),
0,
es3_proto.into(),
);
self.push(new_fn);
Ok(())
}
fn op_new_class(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcClass>,
) -> Result<(), Error> {
let base_class = self.pop().as_object()?;
let class_entry = self.table_class(index, context)?;
let scope = self.current_stack_frame().unwrap().read().scope();
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
let (new_class, class_init) =
FunctionObject::from_class(self, context, class_entry, base_class, scope)?;
class_init.call(Some(new_class), &[], self, context, None)?;
self.push(new_class);
Ok(())
}
fn op_coerce_a(&mut self) -> Result<(), Error> {
Ok(())
}
fn op_jump(&mut self, offset: i32, reader: &mut Reader<Cursor<&[u8]>>) -> Result<(), Error> {
reader.seek(offset as i64)?;
Ok(())
}
fn op_if_true(&mut self, offset: i32, reader: &mut Reader<Cursor<&[u8]>>) -> Result<(), Error> {
let value = self.pop().as_bool()?;
if value {
reader.seek(offset as i64)?;
}
Ok(())
}
fn op_if_false(
&mut self,
offset: i32,
reader: &mut Reader<Cursor<&[u8]>>,
) -> Result<(), Error> {
let value = self.pop().as_bool()?;
if !value {
reader.seek(offset as i64)?;
}
Ok(())
}
fn op_if_strict_eq(
&mut self,
offset: i32,
reader: &mut Reader<Cursor<&[u8]>>,
) -> Result<(), Error> {
let value2 = self.pop();
let value1 = self.pop();
if value1 == value2 {
reader.seek(offset as i64)?;
}
Ok(())
}
fn op_if_strict_ne(
&mut self,
offset: i32,
reader: &mut Reader<Cursor<&[u8]>>,
) -> Result<(), Error> {
let value2 = self.pop();
let value1 = self.pop();
if value1 != value2 {
reader.seek(offset as i64)?;
}
Ok(())
}
2020-03-09 02:48:54 +00:00
fn op_strict_equals(&mut self) -> Result<(), Error> {
let value2 = self.pop();
let value1 = self.pop();
self.push(value1 == value2);
Ok(())
}
Implement `hasnext`, `hasnext2`, `nextname`, `nextvalue`, and the underlying enumeration machinery that powers it. I have... significant reservations with the way object enumeration happens in AVM2. For comparison, AVM1 enumeration works like this: You enumerate the entire object at once, producing a list of property names, which are then pushed onto the stack after a sentinel value. This is a properly abstract way to handle property enumeration. In AVM2, they completely replaced this with index-based enumeration. What this means is that you hand the object an index and it gives you back a name or value. There's also an instruction that will give you the next index in the object. The only advantage I can think of is that it results in less stack manipulation if you want to bail out of iteration early. You just jump out of your loop and kill the registers you don't care about. The disadvantage is that it locks the object representation down pretty hard. They also screwed up the definition of `hasnext`, and thus the VM is stuck enumerating properties from 1. This is because `hasnext` and `hasnext2` increment the index value before checking the object. Code generated by Animate 2020 (which I suspect to be the final version of that software that generates AVM2 code) initializes the index at hero, and then does `hasnext2`, hence we have to start from one. I actually cheated a little and added a separate `Vec` for storing enumerant names. I strongly suspect that Adobe's implementation has objects be inherently slot-oriented, and named properties are just hashmap lookups to slots. This would allow enumerating the slots to get names out of the object.
2020-03-06 02:26:01 +00:00
fn op_has_next(&mut self) -> Result<(), Error> {
//TODO: After adding ints, change this to ints.
let cur_index = self.pop().as_number()?;
let object = self.pop().as_object()?;
let next_index = cur_index as u32 + 1;
if object.get_enumerant_name(next_index).is_some() {
self.push(next_index as f32);
} else {
self.push(0.0);
}
Ok(())
}
fn op_has_next_2(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
object_register: u32,
index_register: u32,
) -> Result<(), Error> {
//TODO: After adding ints, change this to ints.
let cur_index = self.register_value(index_register)?.as_number()?;
let mut object = Some(self.register_value(object_register)?.as_object()?);
let mut next_index = cur_index as u32 + 1;
while let Some(cur_object) = object {
if cur_object.get_enumerant_name(next_index).is_none() {
next_index = 1;
object = cur_object.proto();
} else {
break;
}
}
if object.is_none() {
next_index = 0;
}
self.push(next_index != 0);
self.set_register_value(context, index_register, next_index)?;
self.set_register_value(
context,
object_register,
object.map(|v| v.into()).unwrap_or(Value::Null),
)?;
Ok(())
}
fn op_next_name(&mut self) -> Result<(), Error> {
//TODO: After adding ints, change this to ints.
let cur_index = self.pop().as_number()?;
let object = self.pop().as_object()?;
let name = object
.get_enumerant_name(cur_index as u32)
.map(|n| n.local_name().into());
self.push(name.unwrap_or(Value::Undefined));
Ok(())
}
fn op_next_value(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
//TODO: After adding ints, change this to ints.
let cur_index = self.pop().as_number()?;
let mut object = self.pop().as_object()?;
let name = object.get_enumerant_name(cur_index as u32);
let value = if let Some(name) = name {
object.get_property(object, &name, self, context)?
Implement `hasnext`, `hasnext2`, `nextname`, `nextvalue`, and the underlying enumeration machinery that powers it. I have... significant reservations with the way object enumeration happens in AVM2. For comparison, AVM1 enumeration works like this: You enumerate the entire object at once, producing a list of property names, which are then pushed onto the stack after a sentinel value. This is a properly abstract way to handle property enumeration. In AVM2, they completely replaced this with index-based enumeration. What this means is that you hand the object an index and it gives you back a name or value. There's also an instruction that will give you the next index in the object. The only advantage I can think of is that it results in less stack manipulation if you want to bail out of iteration early. You just jump out of your loop and kill the registers you don't care about. The disadvantage is that it locks the object representation down pretty hard. They also screwed up the definition of `hasnext`, and thus the VM is stuck enumerating properties from 1. This is because `hasnext` and `hasnext2` increment the index value before checking the object. Code generated by Animate 2020 (which I suspect to be the final version of that software that generates AVM2 code) initializes the index at hero, and then does `hasnext2`, hence we have to start from one. I actually cheated a little and added a separate `Vec` for storing enumerant names. I strongly suspect that Adobe's implementation has objects be inherently slot-oriented, and named properties are just hashmap lookups to slots. This would allow enumerating the slots to get names out of the object.
2020-03-06 02:26:01 +00:00
} else {
Value::Undefined
};
self.push(value);
Ok(())
}
#[allow(unused_variables)]
fn op_debug(
&mut self,
is_local_register: bool,
register_name: Index<String>,
register: u8,
) -> Result<(), Error> {
if is_local_register {
let register_name = self.pool_string(register_name)?;
let value = self.register_value(register as u32)?;
avm_debug!("Debug: {} = {:?}", register_name, value);
} else {
avm_debug!("Unknown debugging mode!");
}
Ok(())
}
#[allow(unused_variables)]
fn op_debug_file(&mut self, file_name: Index<String>) -> Result<(), Error> {
let file_name = self.pool_string(file_name)?;
avm_debug!("File: {}", file_name);
Ok(())
}
#[allow(unused_variables)]
fn op_debug_line(&mut self, line_num: u32) -> Result<(), Error> {
avm_debug!("Line: {}", line_num);
Ok(())
}
}