From a852a6939a0481a9bda59890aaa600d0bf926d7d Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 3 Feb 2020 21:22:44 -0500 Subject: [PATCH 001/189] Add an extremely trivial implementation for the AVM2 interpreter state. --- core/src/avm2.rs | 21 +++++++++++++++++++++ core/src/avm2/value.rs | 17 +++++++++++++++++ core/src/lib.rs | 1 + core/src/player.rs | 22 ++++++++++++++++------ 4 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 core/src/avm2.rs create mode 100644 core/src/avm2/value.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs new file mode 100644 index 000000000000..f206d1bc84c8 --- /dev/null +++ b/core/src/avm2.rs @@ -0,0 +1,21 @@ +//! ActionScript Virtual Machine 2 (AS3) support + +use crate::avm2::value::Value; +use gc_arena::Collect; + +mod value; + +/// The state of an AVM2 interpreter. +#[derive(Collect)] +#[collect(no_drop)] +pub struct Avm2<'gc> { + /// Values currently present on the operand stack. + stack: Vec>, +} + +impl<'gc> Avm2<'gc> { + /// Construct a new AVM interpreter. + pub fn new() -> Self { + Self { stack: Vec::new() } + } +} diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs new file mode 100644 index 000000000000..69ea07fd63e0 --- /dev/null +++ b/core/src/avm2/value.rs @@ -0,0 +1,17 @@ +//! AVM2 values + +use gc_arena::{Collect, Gc}; + +/// An AVM2 value. +/// +/// TODO: AVM2 also needs Object, Scope, Namespace, and XML values. +#[derive(Collect)] +#[collect(no_drop)] +pub enum Value<'gc> { + Undefined, + Null, + Boolean(bool), + Number(f64), + String(String), + Object(Gc<'gc, Value<'gc>>), +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 96f7cabdd390..dec8e212c48f 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -10,6 +10,7 @@ extern crate smallvec; extern crate downcast_rs; mod avm1; +mod avm2; mod bounding_box; mod character; pub mod color_transform; diff --git a/core/src/player.rs b/core/src/player.rs index b0aaa5901f53..e5c2322cd79a 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -4,6 +4,7 @@ use crate::avm1::globals::system::SystemProperties; use crate::avm1::listeners::SystemListener; use crate::avm1::object::Object; use crate::avm1::{Avm1, AvmString, TObject, Timers, Value}; +use crate::avm2::Avm2; use crate::backend::input::{InputBackend, MouseCursor}; use crate::backend::storage::StorageBackend; use crate::backend::{ @@ -53,7 +54,12 @@ struct GcRootData<'gc> { /// The object being dragged via a `startDrag` action. drag_object: Option>, - avm: Avm1<'gc>, + /// Interpreter state for AVM1 code. + avm1: Avm1<'gc>, + + /// Interpreter state for AVM2 code. + avm2: Avm2<'gc>, + action_queue: ActionQueue<'gc>, /// Object which manages asynchronous processes that need to interact with @@ -80,6 +86,7 @@ impl<'gc> GcRootData<'gc> { &mut Library<'gc>, &mut ActionQueue<'gc>, &mut Avm1<'gc>, + &mut Avm2<'gc>, &mut Option>, &mut LoadManager<'gc>, &mut HashMap>, @@ -90,7 +97,8 @@ impl<'gc> GcRootData<'gc> { &mut self.levels, &mut self.library, &mut self.action_queue, - &mut self.avm, + &mut self.avm1, + &mut self.avm2, &mut self.drag_object, &mut self.load_manager, &mut self.shared_objects, @@ -238,7 +246,8 @@ impl Player { levels: BTreeMap::new(), mouse_hovered_object: None, drag_object: None, - avm: Avm1::new(gc_context, NEWEST_PLAYER_VERSION), + avm1: Avm1::new(gc_context, NEWEST_PLAYER_VERSION), + avm2: Avm2::new(), action_queue: ActionQueue::new(), load_manager: LoadManager::new(), shared_objects: HashMap::new(), @@ -945,7 +954,8 @@ impl Player { levels, library, action_queue, - avm, + avm1, + avm2, drag_object, load_manager, shared_objects, @@ -970,7 +980,7 @@ impl Player { mouse_position, drag_object, stage_size: (stage_width, stage_height), - system_prototypes: avm.prototypes().clone(), + system_prototypes: avm1.prototypes().clone(), player, load_manager, system: system_properties, @@ -982,7 +992,7 @@ impl Player { needs_render, }; - let ret = f(avm, &mut update_context); + let ret = f(avm1, &mut update_context); // Hovered object may have been updated; copy it back to the GC root. root_data.mouse_hovered_object = update_context.mouse_hovered_object; From b7f257e7c883202678e34c8b6ad36e5f3bab4ec3 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 4 Feb 2020 13:51:18 -0500 Subject: [PATCH 002/189] Add a path to get from the movie clip to the Avm2. --- core/src/avm2.rs | 30 +++++++ core/src/context.rs | 23 ++++++ core/src/display_object/movie_clip.rs | 50 +++++++++++- core/src/loader.rs | 42 +++++----- core/src/player.rs | 113 ++++++++++++++++---------- 5 files changed, 190 insertions(+), 68 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index f206d1bc84c8..8b5d3e5f9e6c 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,10 +1,19 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::value::Value; +use crate::context::UpdateContext; +use crate::tag_utils::SwfSlice; use gc_arena::Collect; +use swf::avm2::read::Reader; 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; + /// The state of an AVM2 interpreter. #[derive(Collect)] #[collect(no_drop)] @@ -18,4 +27,25 @@ impl<'gc> Avm2<'gc> { pub fn new() -> Self { Self { stack: Vec::new() } } + + /// Load an ABC file embedded in a `SwfSlice`. + /// + /// The `SwfSlice` must resolve to the contents of an ABC file. + /// + /// The `preload` flag indicates if the file is being encountered as part + /// of a preloading operation. If false, then this file has actually been + /// encountered as part of normal movie playback and it's final script + /// should be executed. + pub fn load_abc( + &mut self, + abc: SwfSlice, + context: &mut UpdateContext<'_, 'gc, '_>, + preload: bool, + ) -> Result<(), Error> { + let mut read = Reader::new(abc.as_ref()); + + let _abc_file = read.read()?; + + Ok(()) + } } diff --git a/core/src/context.rs b/core/src/context.rs index 6619d534cb42..547f6e2ed4bf 100644 --- a/core/src/context.rs +++ b/core/src/context.rs @@ -246,6 +246,17 @@ pub enum ActionType<'gc> { method: &'static str, args: Vec>, }, + + /// AVM2 ABC files. + DoABC { + name: String, + is_lazy_initialize: bool, + abc: SwfSlice, + + /// Whether or not this ABC file was encountered during the preloading + /// step. + preload: bool, + }, } impl fmt::Debug for ActionType<'_> { @@ -279,6 +290,18 @@ impl fmt::Debug for ActionType<'_> { .field("method", method) .field("args", args) .finish(), + ActionType::DoABC { + name, + is_lazy_initialize, + abc, + preload, + } => f + .debug_struct("ActionType::DoABC") + .field("name", name) + .field("is_lazy_initialize", is_lazy_initialize) + .field("bytecode", abc) + .field("preload", preload) + .finish(), } } } diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index b7b50482746b..ced79a65cf36 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -134,7 +134,7 @@ impl<'gc> MovieClip<'gc> { pub fn preload( self, - avm: &mut Avm1<'gc>, + avm1: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, morph_shapes: &mut fnv::FnvHashMap, ) { @@ -250,7 +250,7 @@ impl<'gc> MovieClip<'gc> { .write(context.gc_context) .define_sound(context, reader), TagCode::DefineSprite => self.0.write(context.gc_context).define_sprite( - avm, + avm1, context, reader, tag_len, @@ -264,7 +264,8 @@ impl<'gc> MovieClip<'gc> { .0 .write(context.gc_context) .define_text(context, reader, 2), - TagCode::DoInitAction => self.do_init_action(avm, context, reader, tag_len), + TagCode::DoInitAction => self.do_init_action(avm1, context, reader, tag_len), + TagCode::DoAbc => self.do_abc(context, reader, tag_len), TagCode::ExportAssets => self .0 .write(context.gc_context) @@ -326,7 +327,7 @@ impl<'gc> MovieClip<'gc> { &mut cur_frame, ), TagCode::ScriptLimits => { - self.0.write(context.gc_context).script_limits(reader, avm) + self.0.write(context.gc_context).script_limits(reader, avm1) } TagCode::SoundStreamHead => self .0 @@ -396,6 +397,47 @@ impl<'gc> MovieClip<'gc> { Ok(()) } + #[inline] + fn do_abc( + self, + context: &mut UpdateContext<'_, 'gc, '_>, + reader: &mut SwfStream<&[u8]>, + tag_len: usize, + ) -> DecodeResult { + // Queue the actions. + // TODO: The tag reader parses the entire ABC file, instead of just + // giving us a `SwfSlice` for later parsing, so we have to replcate the + // *entire* parsing code here. This sucks. + let flags = reader.read_u32()?; + let name = reader.read_c_string()?; + let is_lazy_initialize = flags & 1 != 0; + + // The rest of the tag is an ABC file so we can take our SwfSlice now. + let slice = self + .0 + .read() + .static_data + .swf + .resize_to_reader(reader, tag_len) + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::Other, + "Invalid source or tag length when running init action", + ) + })?; + context.action_queue.queue_actions( + self.into(), + ActionType::DoABC { + name, + is_lazy_initialize, + abc: slice, + preload: true, + }, + false, + ); + Ok(()) + } + #[allow(dead_code)] pub fn playing(self) -> bool { self.0.read().playing() diff --git a/core/src/loader.rs b/core/src/loader.rs index 4fde1490f457..d0aed7607f60 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -298,7 +298,7 @@ impl<'gc> Loader<'gc> { Box::pin(async move { player.lock().expect("Could not lock player!!").update( - |avm, uc| -> Result<(), Error> { + |avm1, _avm2, uc| -> Result<(), Error> { let (clip, broadcaster) = match uc.load_manager.get_loader(handle) { Some(Loader::Movie { target_clip, @@ -316,7 +316,7 @@ impl<'gc> Loader<'gc> { .replace_with_movie(uc.gc_context, None); if let Some(broadcaster) = broadcaster { - avm.run_stack_frame_for_method( + avm1.run_stack_frame_for_method( clip, broadcaster, NEWEST_PLAYER_VERSION, @@ -337,7 +337,7 @@ impl<'gc> Loader<'gc> { player .lock() .expect("Could not lock player!!") - .update(|avm, uc| { + .update(|avm1, _avm2, uc| { let (clip, broadcaster) = match uc.load_manager.get_loader(handle) { Some(Loader::Movie { target_clip, @@ -349,7 +349,7 @@ impl<'gc> Loader<'gc> { }; if let Some(broadcaster) = broadcaster { - avm.run_stack_frame_for_method( + avm1.run_stack_frame_for_method( clip, broadcaster, NEWEST_PLAYER_VERSION, @@ -369,10 +369,10 @@ impl<'gc> Loader<'gc> { .expect("Attempted to load movie into not movie clip"); mc.replace_with_movie(uc.gc_context, Some(movie.clone())); - mc.post_instantiation(avm, uc, clip, None, false); + mc.post_instantiation(avm1, uc, clip, None, false); let mut morph_shapes = fnv::FnvHashMap::default(); - mc.preload(avm, uc, &mut morph_shapes); + mc.preload(avm1, uc, &mut morph_shapes); // Finalize morph shapes. for (id, static_data) in morph_shapes { @@ -386,7 +386,7 @@ impl<'gc> Loader<'gc> { } if let Some(broadcaster) = broadcaster { - avm.run_stack_frame_for_method( + avm1.run_stack_frame_for_method( clip, broadcaster, NEWEST_PLAYER_VERSION, @@ -411,7 +411,7 @@ impl<'gc> Loader<'gc> { //This also can get errors from decoding an invalid SWF file, //too. We should distinguish those to player code. player.lock().expect("Could not lock player!!").update( - |avm, uc| -> Result<(), Error> { + |avm1, _avm2, uc| -> Result<(), Error> { let (clip, broadcaster) = match uc.load_manager.get_loader(handle) { Some(Loader::Movie { target_clip, @@ -423,7 +423,7 @@ impl<'gc> Loader<'gc> { }; if let Some(broadcaster) = broadcaster { - avm.run_stack_frame_for_method( + avm1.run_stack_frame_for_method( clip, broadcaster, NEWEST_PLAYER_VERSION, @@ -467,7 +467,7 @@ impl<'gc> Loader<'gc> { Box::pin(async move { let data = fetch.await?; - player.lock().unwrap().update(|avm, uc| { + player.lock().unwrap().update(|avm1, _avm2, uc| { let loader = uc.load_manager.get_loader(handle); let that = match loader { Some(Loader::Form { target_object, .. }) => *target_object, @@ -476,13 +476,14 @@ impl<'gc> Loader<'gc> { }; let mut activation = Activation::from_nothing( - avm, + avm1, ActivationIdentifier::root("[Form Loader]"), uc.swf.version(), - avm.global_object_cell(), + avm1.global_object_cell(), uc.gc_context, *uc.levels.get(&0).unwrap(), ); + for (k, v) in form_urlencoded::parse(&data) { that.set( &k, @@ -561,7 +562,7 @@ impl<'gc> Loader<'gc> { let xmlstring = String::from_utf8(data)?; player.lock().expect("Could not lock player!!").update( - |avm, uc| -> Result<(), Error> { + |avm1, _avm2, uc| -> Result<(), Error> { let (mut node, active_clip) = match uc.load_manager.get_loader(handle) { Some(Loader::XML { target_node, @@ -573,8 +574,8 @@ impl<'gc> Loader<'gc> { }; let object = - node.script_object(uc.gc_context, Some(avm.prototypes().xml_node)); - avm.run_stack_frame_for_method( + node.script_object(uc.gc_context, Some(avm1.prototypes().xml_node)); + avm1.run_stack_frame_for_method( active_clip, object, NEWEST_PLAYER_VERSION, @@ -583,7 +584,7 @@ impl<'gc> Loader<'gc> { &[200.into()], ); - avm.run_stack_frame_for_method( + avm1.run_stack_frame_for_method( active_clip, object, NEWEST_PLAYER_VERSION, @@ -597,7 +598,7 @@ impl<'gc> Loader<'gc> { )?; } else { player.lock().expect("Could not lock player!!").update( - |avm, uc| -> Result<(), Error> { + |avm1, _avm2, uc| -> Result<(), Error> { let (mut node, active_clip) = match uc.load_manager.get_loader(handle) { Some(Loader::XML { target_node, @@ -609,8 +610,9 @@ impl<'gc> Loader<'gc> { }; let object = - node.script_object(uc.gc_context, Some(avm.prototypes().xml_node)); - avm.run_stack_frame_for_method( + node.script_object(uc.gc_context, Some(avm1.prototypes().xml_node)); + + avm1.run_stack_frame_for_method( active_clip, object, NEWEST_PLAYER_VERSION, @@ -619,7 +621,7 @@ impl<'gc> Loader<'gc> { &[404.into()], ); - avm.run_stack_frame_for_method( + avm1.run_stack_frame_for_method( active_clip, object, NEWEST_PLAYER_VERSION, diff --git a/core/src/player.rs b/core/src/player.rs index e5c2322cd79a..c92f137c1799 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -281,19 +281,19 @@ impl Player { storage, }; - player.mutate_with_update_context(|avm, context| { + player.mutate_with_update_context(|avm1, avm2, context| { let mut root: DisplayObject = MovieClip::from_movie(context.gc_context, movie.clone()).into(); root.set_depth(context.gc_context, 0); - root.post_instantiation(avm, context, root, None, false); + root.post_instantiation(avm1, context, root, None, false); root.set_name(context.gc_context, ""); context.levels.insert(0, root); let mut activation = Activation::from_nothing( - avm, + avm1, ActivationIdentifier::root("[Version Setter]"), context.swf.version(), - avm.global_object_cell(), + avm1.global_object_cell(), context.gc_context, *context.levels.get(&0).unwrap(), ); @@ -414,14 +414,14 @@ impl Player { } = event { if self.input.is_key_down(KeyCode::Control) && self.input.is_key_down(KeyCode::Alt) { - self.mutate_with_update_context(|avm, context| { + self.mutate_with_update_context(|avm1, _avm2, context| { let mut dumper = VariableDumper::new(" "); let mut activation = Activation::from_nothing( - avm, + avm1, ActivationIdentifier::root("[Variable Dumper]"), context.swf.version(), - avm.global_object_cell(), + avm1.global_object_cell(), context.gc_context, *context.levels.get(&0).unwrap(), ); @@ -486,11 +486,11 @@ impl Player { }; if button_event.is_some() { - self.mutate_with_update_context(|avm, context| { + self.mutate_with_update_context(|avm1, _avm2, context| { let levels: Vec> = context.levels.values().copied().collect(); for level in levels { if let Some(button_event) = button_event { - let state = level.handle_clip_event(avm, context, button_event); + let state = level.handle_clip_event(avm1, context, button_event); if state == ClipEventResult::Handled { return; } @@ -510,12 +510,12 @@ impl Player { }; if clip_event.is_some() || mouse_event_name.is_some() { - self.mutate_with_update_context(|avm, context| { + self.mutate_with_update_context(|avm1, _avm2, context| { let levels: Vec> = context.levels.values().copied().collect(); for level in levels { if let Some(clip_event) = clip_event { - level.handle_clip_event(avm, context, clip_event); + level.handle_clip_event(avm1, context, clip_event); } } @@ -534,7 +534,7 @@ impl Player { } let mut is_mouse_down = self.is_mouse_down; - self.mutate_with_update_context(|avm, context| { + self.mutate_with_update_context(|avm1, avm2, context| { if let Some(node) = context.mouse_hovered_object { if node.removed() { context.mouse_hovered_object = None; @@ -546,7 +546,7 @@ impl Player { is_mouse_down = true; needs_render = true; if let Some(node) = context.mouse_hovered_object { - node.handle_clip_event(avm, context, ClipEvent::Press); + node.handle_clip_event(avm1, context, ClipEvent::Press); } } @@ -554,14 +554,14 @@ impl Player { is_mouse_down = false; needs_render = true; if let Some(node) = context.mouse_hovered_object { - node.handle_clip_event(avm, context, ClipEvent::Release); + node.handle_clip_event(avm1, context, ClipEvent::Release); } } _ => (), } - Self::run_actions(avm, context); + Self::run_actions(avm1, avm2, context); }); self.is_mouse_down = is_mouse_down; if needs_render { @@ -572,7 +572,7 @@ impl Player { /// Update dragged object, if any. fn update_drag(&mut self) { let mouse_pos = self.mouse_pos; - self.mutate_with_update_context(|_activation, context| { + self.mutate_with_update_context(|_avm1, _avm2, context| { if let Some(drag_object) = &mut context.drag_object { if drag_object.display_object.removed() { // Be sure to clear the drag if the object was removed. @@ -607,13 +607,13 @@ impl Player { let mouse_pos = self.mouse_pos; let mut new_cursor = self.mouse_cursor; - let hover_changed = self.mutate_with_update_context(|avm, context| { + let hover_changed = self.mutate_with_update_context(|avm1, avm2, context| { // Check hovered object. let mut new_hovered = None; for (_depth, level) in context.levels.clone().iter().rev() { if new_hovered.is_none() { new_hovered = - level.mouse_pick(avm, context, *level, (mouse_pos.0, mouse_pos.1)); + level.mouse_pick(avm1, context, *level, (mouse_pos.0, mouse_pos.1)); } else { break; } @@ -625,20 +625,20 @@ impl Player { // RollOut of previous node. if let Some(node) = cur_hovered { if !node.removed() { - node.handle_clip_event(avm, context, ClipEvent::RollOut); + node.handle_clip_event(avm1, context, ClipEvent::RollOut); } } - // RollOver on new node. + // RollOver on new node.I stil new_cursor = MouseCursor::Arrow; if let Some(node) = new_hovered { new_cursor = MouseCursor::Hand; - node.handle_clip_event(avm, context, ClipEvent::RollOver); + node.handle_clip_event(avm1, context, ClipEvent::RollOver); } context.mouse_hovered_object = new_hovered; - Self::run_actions(avm, context); + Self::run_actions(avm1, avm2, context); true } else { false @@ -659,12 +659,12 @@ impl Player { /// This should only be called once. Further movie loads should preload the /// specific `MovieClip` referenced. fn preload(&mut self) { - self.mutate_with_update_context(|activation, context| { + self.mutate_with_update_context(|avm1, _avm2, context| { let mut morph_shapes = fnv::FnvHashMap::default(); let root = *context.levels.get(&0).expect("root level"); root.as_movie_clip() .unwrap() - .preload(activation, context, &mut morph_shapes); + .preload(avm1, context, &mut morph_shapes); // Finalize morph shapes. for (id, static_data) in morph_shapes { @@ -678,7 +678,7 @@ impl Player { } pub fn run_frame(&mut self) { - self.update(|avm, update_context| { + self.update(|avm1, _avm2, update_context| { // TODO: In what order are levels run? // NOTE: We have to copy all the layer pointers into a separate list // because level updates can create more levels, which we don't @@ -686,7 +686,7 @@ impl Player { let levels: Vec<_> = update_context.levels.values().copied().collect(); for mut level in levels { - level.run_frame(avm, update_context); + level.run_frame(avm1, update_context); } }); self.needs_render = true; @@ -759,7 +759,11 @@ impl Player { self.input.deref_mut() } - fn run_actions<'gc>(avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) { + fn run_actions<'gc>( + avm1: &mut Avm1<'gc>, + avm2: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) { // Note that actions can queue further actions, so a while loop is necessary here. while let Some(actions) = context.action_queue.pop_action() { // We don't run frame actions if the clip was removed after it queued the action. @@ -770,7 +774,7 @@ impl Player { match actions.action_type { // DoAction/clip event code ActionType::Normal { bytecode } => { - avm.run_stack_frame_for_action( + avm1.run_stack_frame_for_action( actions.clip, "[Frame]", context.swf.header().version, @@ -784,10 +788,10 @@ impl Player { events, } => { let mut activation = Activation::from_nothing( - avm, + avm1, ActivationIdentifier::root("[Construct]"), context.swf.version(), - avm.global_object_cell(), + avm1.global_object_cell(), context.gc_context, *context.levels.get(&0).unwrap(), ); @@ -824,7 +828,7 @@ impl Player { events, } => { for event in events { - avm.run_stack_frame_for_action( + avm1.run_stack_frame_for_action( actions.clip, "[Construct]", context.swf.header().version, @@ -835,7 +839,7 @@ impl Player { } // Event handler method call (e.g. onEnterFrame) ActionType::Method { object, name, args } => { - avm.run_stack_frame_for_method( + avm1.run_stack_frame_for_method( actions.clip, object, context.swf.header().version, @@ -853,7 +857,7 @@ impl Player { } => { // A native function ends up resolving immediately, // so this doesn't require any further execution. - avm.notify_system_listeners( + avm1.notify_system_listeners( actions.clip, context.swf.version(), context, @@ -862,6 +866,18 @@ impl Player { &args, ); } + + // DoABC code + ActionType::DoABC { + name, + is_lazy_initialize, + abc, + preload, + } => { + if let Err(e) = avm2.load_abc(abc, context, preload) { + log::warn!("Error loading ABC file: {}", e); + } + } } } } @@ -907,7 +923,11 @@ impl Player { /// This takes cares of populating the `UpdateContext` struct, avoiding borrow issues. fn mutate_with_update_context(&mut self, f: F) -> R where - F: for<'a, 'gc> FnOnce(&mut Avm1<'gc>, &mut UpdateContext<'a, 'gc, '_>) -> R, + F: for<'a, 'gc> FnOnce( + &mut Avm1<'gc>, + &mut Avm2<'gc>, + &mut UpdateContext<'a, 'gc, '_>, + ) -> R, { // We have to do this piecewise borrowing of fields before the closure to avoid // completely borrowing `self`. @@ -992,7 +1012,7 @@ impl Player { needs_render, }; - let ret = f(avm1, &mut update_context); + let ret = f(avm1, avm2, &mut update_context); // Hovered object may have been updated; copy it back to the GC root. root_data.mouse_hovered_object = update_context.mouse_hovered_object; @@ -1027,12 +1047,16 @@ impl Player { /// hover state up to date, and running garbage collection. pub fn update(&mut self, func: F) -> R where - F: for<'a, 'gc> FnOnce(&mut Avm1<'gc>, &mut UpdateContext<'a, 'gc, '_>) -> R, + F: for<'a, 'gc> FnOnce( + &mut Avm1<'gc>, + &mut Avm2<'gc>, + &mut UpdateContext<'a, 'gc, '_>, + ) -> R, { - let rval = self.mutate_with_update_context(|avm, context| { - let rval = func(avm, context); + let rval = self.mutate_with_update_context(|avm1, avm2, context| { + let rval = func(avm1, avm2, context); - Self::run_actions(avm, context); + Self::run_actions(avm1, avm2, context); rval }); @@ -1048,12 +1072,12 @@ impl Player { } pub fn flush_shared_objects(&mut self) { - self.update(|avm, context| { + self.update(|avm1, _avm2, context| { let mut activation = Activation::from_nothing( - avm, + avm1, ActivationIdentifier::root("[Flush]"), context.swf.version(), - avm.global_object_cell(), + avm1.global_object_cell(), context.gc_context, *context.levels.get(&0).unwrap(), ); @@ -1068,8 +1092,9 @@ impl Player { /// Update all AVM-based timers (such as created via setInterval). /// Returns the approximate amount of time until the next timer tick. pub fn update_timers(&mut self, dt: f64) { - self.time_til_next_timer = - self.mutate_with_update_context(|avm, context| Timers::update_timers(avm, context, dt)); + self.time_til_next_timer = self.mutate_with_update_context(|avm1, _avm2, context| { + Timers::update_timers(avm1, context, dt) + }); } } From e80c887261d2f46cbe1c348aa13d1d20e9e844ab Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 5 Feb 2020 13:52:11 -0500 Subject: [PATCH 003/189] Add a very basic object model to the AVM2 interpreter. --- core/src/avm2.rs | 3 ++ core/src/avm2/names.rs | 61 ++++++++++++++++++++++++++++++++++ core/src/avm2/object.rs | 28 ++++++++++++++++ core/src/avm2/script_object.rs | 22 ++++++++++++ core/src/avm2/value.rs | 9 ++--- 5 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 core/src/avm2/names.rs create mode 100644 core/src/avm2/object.rs create mode 100644 core/src/avm2/script_object.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 8b5d3e5f9e6c..b8ec1654c20a 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -6,6 +6,9 @@ use crate::tag_utils::SwfSlice; use gc_arena::Collect; use swf::avm2::read::Reader; +mod names; +mod object; +mod script_object; mod value; /// Boxed error alias. diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs new file mode 100644 index 000000000000..1c78dceab557 --- /dev/null +++ b/core/src/avm2/names.rs @@ -0,0 +1,61 @@ +//! AVM2 names & namespacing + +use gc_arena::Collect; +use swf::avm2::types::{AbcFile, Index, Namespace as AbcNamespace}; + +/// Represents the name of a namespace. +#[derive(Clone, Collect, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[collect(no_drop)] +pub enum Namespace { + Namespace(String), + Package(String), + PackageInternal(String), + Protected(String), + Explicit(String), + StaticProtected(String), + Private(String), +} + +impl Namespace { + pub fn from_abc_namespace(name: &AbcNamespace, file: &AbcFile) -> Option { + Some(match name { + AbcNamespace::Namespace(Index(idx, ..)) => { + Self::Namespace(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + AbcNamespace::Package(Index(idx, ..)) => { + Self::Package(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + AbcNamespace::PackageInternal(Index(idx, ..)) => { + Self::PackageInternal(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + AbcNamespace::Protected(Index(idx, ..)) => { + Self::Protected(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + AbcNamespace::Explicit(Index(idx, ..)) => { + Self::Explicit(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + AbcNamespace::StaticProtected(Index(idx, ..)) => { + Self::StaticProtected(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + AbcNamespace::Private(Index(idx, ..)) => { + Self::Private(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + }) + } +} + +/// A `QName`, likely "qualified name", consists of a namespace and name string. +/// +/// This is technically interchangeable with `xml::XMLName`, as they both +/// implement `QName`; however, AVM2 and XML have separate representations. +/// +/// A property cannot be retrieved or set without first being resolved into a +/// `QName`. All other forms of names and multinames are either versions of +/// `QName` with unspecified parameters, or multiple names to be checked in +/// order. +#[derive(Clone, Collect, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[collect(no_drop)] +pub struct QName { + ns: Namespace, + name: String, +} diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs new file mode 100644 index 000000000000..3efc08b6c4f1 --- /dev/null +++ b/core/src/avm2/object.rs @@ -0,0 +1,28 @@ +//! AVM2 objects. + +use crate::avm2::names::QName; +use crate::avm2::script_object::ScriptObject; +use crate::avm2::value::Value; +use gc_arena::Collect; +use ruffle_macros::enum_trait_object; +use std::fmt::Debug; + +/// Represents an object that can be directly interacted with by the AVM2 +/// runtime. +#[enum_trait_object( + #[derive(Clone, Collect, Debug, Copy)] + #[collect(no_drop)] + pub enum Object<'gc> { + ScriptObject(ScriptObject<'gc>) + } +)] + +pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy { + /// Retrieve a property by it's QName. + fn get_property(self, _name: &QName) -> Value<'gc> { + Value::Undefined + } + + /// Set a property by it's QName. + fn set_property(self, _name: &QName, _value: Value<'gc>) {} +} diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs new file mode 100644 index 000000000000..331aa1b717dd --- /dev/null +++ b/core/src/avm2/script_object.rs @@ -0,0 +1,22 @@ +//! Default AVM2 object impl + +use crate::avm2::names::QName; +use crate::avm2::object::TObject; +use crate::avm2::value::Value; +use gc_arena::{Collect, GcCell}; +use std::collections::HashMap; +use std::fmt::Debug; + +/// Default implementation of `avm2::Object`. +#[derive(Clone, Collect, Debug, Copy)] +#[collect(no_drop)] +pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>); + +#[derive(Clone, Collect, Debug)] +#[collect(no_drop)] +pub struct ScriptObjectData<'gc> { + /// Properties stored on this object. + values: HashMap>, +} + +impl<'gc> TObject<'gc> for ScriptObject<'gc> {} diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 69ea07fd63e0..84084ff37d9d 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -1,11 +1,12 @@ //! AVM2 values -use gc_arena::{Collect, Gc}; +use crate::avm2::object::Object; +use gc_arena::Collect; /// An AVM2 value. /// -/// TODO: AVM2 also needs Object, Scope, Namespace, and XML values. -#[derive(Collect)] +/// TODO: AVM2 also needs Scope, Namespace, and XML values. +#[derive(Clone, Collect, Debug)] #[collect(no_drop)] pub enum Value<'gc> { Undefined, @@ -13,5 +14,5 @@ pub enum Value<'gc> { Boolean(bool), Number(f64), String(String), - Object(Gc<'gc, Value<'gc>>), + Object(Object<'gc>), } From 7f60fab1e5f9ea7e1f591a1d8f5d1861df22767a Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 5 Feb 2020 23:15:03 -0500 Subject: [PATCH 004/189] Add the bare minimum necessary to get opcodes out of an ABC and into an interpreter loop. Surprisingly enough, the "bare minimum" includes a stack, object model, and values already. --- core/src/avm2.rs | 117 ++++++++++++++++++++- core/src/avm2/activation.rs | 78 ++++++++++++++ core/src/avm2/function.rs | 96 +++++++++++++++++ core/src/avm2/object.rs | 38 ++++++- core/src/avm2/return_value.rs | 182 +++++++++++++++++++++++++++++++++ core/src/avm2/script_object.rs | 8 +- core/src/avm2/value.rs | 35 ++++++- swf/src/avm2/read.rs | 2 +- 8 files changed, 545 insertions(+), 11 deletions(-) create mode 100644 core/src/avm2/activation.rs create mode 100644 core/src/avm2/function.rs create mode 100644 core/src/avm2/return_value.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index b8ec1654c20a..e69fe799a218 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,16 +1,30 @@ //! ActionScript Virtual Machine 2 (AS3) support +use crate::avm2::activation::Activation; use crate::avm2::value::Value; use crate::context::UpdateContext; use crate::tag_utils::SwfSlice; -use gc_arena::Collect; +use gc_arena::{Collect, GcCell}; +use std::io::Cursor; use swf::avm2::read::Reader; +use swf::avm2::types::MethodBody; +use swf::read::SwfRead; +mod activation; +mod function; mod names; mod object; +mod return_value; mod script_object; mod value; +macro_rules! avm_debug { + ($($arg:tt)*) => ( + #[cfg(feature = "avm_debug")] + log::debug!($($arg)*) + ) +} + /// Boxed error alias. /// /// As AVM2 is a far stricter VM than AVM1, this may eventually be replaced @@ -21,6 +35,9 @@ type Error = Box; #[derive(Collect)] #[collect(no_drop)] pub struct Avm2<'gc> { + /// All activation records for the current interpreter. + stack_frames: Vec>>, + /// Values currently present on the operand stack. stack: Vec>, } @@ -28,7 +45,10 @@ pub struct Avm2<'gc> { impl<'gc> Avm2<'gc> { /// Construct a new AVM interpreter. pub fn new() -> Self { - Self { stack: Vec::new() } + Self { + stack_frames: Vec::new(), + stack: Vec::new(), + } } /// Load an ABC file embedded in a `SwfSlice`. @@ -51,4 +71,97 @@ impl<'gc> Avm2<'gc> { Ok(()) } + + pub fn current_stack_frame(&self) -> Option>> { + self.stack_frames.last().copied() + } + + /// 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( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + func: F, + ) -> Result + where + F: FnOnce( + &mut Self, + &mut Reader>, + &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, + { + let (frame_cell, action, pc) = { + let frame = self.stack_frames.last().ok_or("No stack frame to read!")?; + let mut frame_ref = frame.write(context.gc_context); + frame_ref.lock()?; + + (*frame, frame_ref.action(), frame_ref.pc()) + }; + + let abc = action.abc.as_ref(); + let method_index = action.abc_method; + let method_body_index = action.abc_method_body as usize; + let method_body: Result<&MethodBody, Error> = + abc.method_bodies.get(method_body_index).ok_or( + "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 + } + + /// Run a single action from a given action reader. + pub fn do_next_opcode( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + reader: &mut Reader>, + ) -> Result<(), Error> { + if let Some(op) = reader.read_op()? { + avm_debug!("Opcode: {:?}", op); + + let result = match op { + _ => self.unknown_op(context, op), + }; + + if let Err(ref e) = result { + log::error!("AVM2 error: {}", e); + return result; + } + } + + Ok(()) + } + + fn unknown_op( + &mut self, + _context: &mut UpdateContext, + op: swf::avm2::types::Op, + ) -> Result<(), Error> { + log::error!("Unknown AVM2 opcode: {:?}", op); + Err("Unknown op".into()) + } } diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs new file mode 100644 index 000000000000..5fd0ea31dab8 --- /dev/null +++ b/core/src/avm2/activation.rs @@ -0,0 +1,78 @@ +//! Activation frames + +use crate::avm2::function::Avm2Function; +use crate::avm2::object::Object; +use crate::avm2::Error; +use gc_arena::{Collect, Gc}; + +/// Represents a single activation of a given AVM2 function or keyframe. +#[derive(Clone, Collect)] +#[collect(no_drop)] +pub struct Activation<'gc> { + /// The function being executed. + action: Gc<'gc, Avm2Function>, + + /// The current location of the instruction stream being executed. + pc: usize, + + /// The immutable value of `this`. + this: Object<'gc>, + + /// The arguments this function was called by. + arguments: Option>, + + /// Flags that the current activation frame is being executed and has a + /// reader object copied from it. Taking out two readers on the same + /// activation frame is a programming error. + is_executing: bool, +} + +impl<'gc> Activation<'gc> { + pub fn from_action( + action: Gc<'gc, Avm2Function>, + this: Object<'gc>, + arguments: Option>, + ) -> Self { + Self { + action, + pc: 0, + this, + arguments, + is_executing: false, + } + } + + /// Attempts to lock the activation frame for execution. + /// + /// If this frame is already executing, that is an error condition. + pub fn lock(&mut self) -> Result<(), Error> { + if self.is_executing { + return Err("Attempted to execute the same frame twice".into()); + } + + self.is_executing = true; + + Ok(()) + } + + /// Unlock the activation object. This allows future execution to run on it + /// again. + pub fn unlock_execution(&mut self) { + self.is_executing = false; + } + + /// Obtain a reference to the function being executed. + pub fn action(&self) -> Gc<'gc, Avm2Function> { + self.action + } + + /// Get the PC value. + pub fn pc(&self) -> usize { + self.pc + } + + /// Set the PC value. + pub fn set_pc(&mut self, new_pc: usize) { + self.pc = new_pc; + } +} diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs new file mode 100644 index 000000000000..278b86b5c104 --- /dev/null +++ b/core/src/avm2/function.rs @@ -0,0 +1,96 @@ +//! AVM2 executables. + +use crate::avm2::object::{Object, ObjectPtr, TObject}; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::script_object::ScriptObjectData; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::{Collect, CollectionContext, GcCell}; +use std::fmt; +use std::rc::Rc; +use swf::avm2::types::AbcFile; + +/// Represents a function defined in Ruffle's code. +/// +/// Parameters are as follows: +/// +/// * The AVM2 runtime +/// * The action context +/// * The current `this` object +/// * The arguments this function was called with +/// +/// Native functions are allowed to return a value or `None`. `None` indicates +/// that the given value will not be returned on the stack and instead will +/// resolve on the AVM stack, as if you had called a non-native function. If +/// your function yields `None`, you must ensure that the top-most activation +/// in the AVM1 runtime will return with the value of this function. +pub type NativeFunction<'gc> = fn( + &mut Avm2<'gc>, + &mut UpdateContext<'_, 'gc, '_>, + Object<'gc>, + &[Value<'gc>], +) -> Result, Error>; + +/// Represents an AVM2 function. +#[derive(Collect, Clone, Debug)] +#[collect(require_static)] +pub struct Avm2Function { + /// The ABC file this function was defined in. + pub abc: Rc, + + /// The ABC method this function uses. + pub abc_method: u32, + + /// The ABC method body this function uses. + pub abc_method_body: u32, +} + +/// Represents code that can be executed by some means. +#[derive(Clone)] +pub enum Executable<'gc> { + Native(NativeFunction<'gc>), + Action(Avm2Function), +} + +unsafe impl<'gc> Collect for Executable<'gc> { + fn trace(&self, cc: CollectionContext) { + match self { + Self::Action(a2f) => a2f.trace(cc), + Self::Native(_nf) => {} + } + } +} + +impl<'gc> fmt::Debug for Executable<'gc> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Action(a2f) => fmt.debug_tuple("Executable::Action").field(a2f).finish(), + Self::Native(nf) => fmt + .debug_tuple("Executable::Native") + .field(&format!("{:p}", nf)) + .finish(), + } + } +} + +/// An Object which can be called to execute it's function code. +#[derive(Collect, Debug, Clone, Copy)] +#[collect(no_drop)] +pub struct FunctionObject<'gc>(GcCell<'gc, FunctionObjectData<'gc>>); + +#[derive(Collect, Debug, Clone)] +#[collect(no_drop)] +pub struct FunctionObjectData<'gc> { + /// Base script object + base: ScriptObjectData<'gc>, + + /// Executable code + exec: Executable<'gc>, +} + +impl<'gc> TObject<'gc> for FunctionObject<'gc> { + fn as_ptr(&self) -> *const ObjectPtr { + self.0.as_ptr() as *const ObjectPtr + } +} diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 3efc08b6c4f1..b49d7bec9180 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1,8 +1,12 @@ //! AVM2 objects. +use crate::avm2::function::FunctionObject; use crate::avm2::names::QName; +use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; use gc_arena::Collect; use ruffle_macros::enum_trait_object; use std::fmt::Debug; @@ -13,16 +17,40 @@ use std::fmt::Debug; #[derive(Clone, Collect, Debug, Copy)] #[collect(no_drop)] pub enum Object<'gc> { - ScriptObject(ScriptObject<'gc>) + ScriptObject(ScriptObject<'gc>), + FunctionObject(FunctionObject<'gc>) } )] - pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy { /// Retrieve a property by it's QName. - fn get_property(self, _name: &QName) -> Value<'gc> { - Value::Undefined + fn get_property( + self, + _name: &QName, + _avm: &mut Avm2<'gc>, + _context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + Ok(Value::Undefined.into()) } /// Set a property by it's QName. - fn set_property(self, _name: &QName, _value: Value<'gc>) {} + fn set_property( + self, + _name: &QName, + _value: Value<'gc>, + _avm: &mut Avm2<'gc>, + _context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result<(), Error> { + Ok(()) + } + + /// Get a raw pointer value for this object. + fn as_ptr(&self) -> *const ObjectPtr; +} + +pub enum ObjectPtr {} + +impl<'gc> Object<'gc> { + pub fn ptr_eq(a: Object<'gc>, b: Object<'gc>) -> bool { + a.as_ptr() == b.as_ptr() + } } diff --git a/core/src/avm2/return_value.rs b/core/src/avm2/return_value.rs new file mode 100644 index 000000000000..58e13887862f --- /dev/null +++ b/core/src/avm2/return_value.rs @@ -0,0 +1,182 @@ +//! Return value enum + +use crate::avm2::activation::Activation; +use crate::avm2::object::Object; +use crate::avm2::{Avm2, Error, Value}; +use crate::context::UpdateContext; +use gc_arena::{Collect, GcCell}; +use std::fmt; + +/// Represents the return value of a function call. +/// +/// Since function calls can result in invoking native code or adding a new +/// activation onto the AVM stack, you need another type to represent how the +/// return value will be delivered to you. +/// +/// This function contains a handful of utility methods for deciding what to do +/// with a given value regardless of how it is delivered to the calling +/// function. +/// +/// It is `must_use` - failing to use a return value is a compiler warning. We +/// provide a helper function specifically to indicate that you aren't +/// interested in the result of a call. +#[must_use = "Return values must be used"] +#[derive(Clone, Collect)] +#[collect(no_drop)] +pub enum ReturnValue<'gc> { + /// Indicates that the return value is available immediately. + Immediate(Value<'gc>), + + /// Indicates that the return value is the result of a given user-defined + /// function call. The activation record returned is the frame that needs + /// to return to get your value. + ResultOf(GcCell<'gc, Activation<'gc>>), +} + +impl PartialEq for ReturnValue<'_> { + fn eq(&self, other: &Self) -> bool { + use ReturnValue::*; + + match (self, other) { + (Immediate(val1), Immediate(val2)) => val1 == val2, + (ResultOf(frame1), ResultOf(frame2)) => GcCell::ptr_eq(*frame1, *frame2), + _ => false, + } + } +} + +impl fmt::Debug for ReturnValue<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use ReturnValue::*; + + match self { + Immediate(val) => write!(f, "Immediate({:?})", val), + ResultOf(_frame) => write!(f, "ResultOf()"), + } + } +} + +impl<'gc> ReturnValue<'gc> { + /// Mark a given return value as intended to be pushed onto the stack. + /// + /// The natural result of a stack frame retiring is to be pushed, so this + /// only ensures that Immediate values are pushed. + pub fn push(self, avm: &mut Avm2<'gc>) { + use ReturnValue::*; + + match self { + Immediate(val) => {} //TODO: avm.push(val) + ResultOf(_frame) => {} + }; + } + + /// Force a return value to resolve on the Rust stack by recursing back + /// into the AVM. + pub fn resolve( + self, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + use ReturnValue::*; + + match self { + Immediate(val) => Ok(val), + ResultOf(frame) => { + //TODO: Execute AVM frame, pop return value + Ok(Value::Undefined) + } + } + } + + pub fn is_immediate(&self) -> bool { + use ReturnValue::*; + + if let Immediate(_v) = self { + true + } else { + false + } + } + + /// Panic if a value is not immediate. + /// + /// This should only be used in test assertions. + #[cfg(test)] + pub fn unwrap_immediate(self) -> Value<'gc> { + use ReturnValue::*; + + match self { + Immediate(val) => val, + _ => panic!("Unwrapped a non-immediate return value"), + } + } +} + +impl<'gc> From> for ReturnValue<'gc> { + fn from(val: Value<'gc>) -> Self { + ReturnValue::Immediate(val) + } +} + +impl<'gc> From for ReturnValue<'gc> { + fn from(string: String) -> Self { + ReturnValue::Immediate(Value::String(string)) + } +} + +impl<'gc> From<&str> for ReturnValue<'gc> { + fn from(string: &str) -> Self { + ReturnValue::Immediate(Value::String(string.to_owned())) + } +} + +impl<'gc> From for ReturnValue<'gc> { + fn from(value: bool) -> Self { + ReturnValue::Immediate(Value::Bool(value)) + } +} + +impl<'gc, T> From for ReturnValue<'gc> +where + Object<'gc>: From, +{ + fn from(value: T) -> Self { + ReturnValue::Immediate(Value::Object(Object::from(value))) + } +} + +impl<'gc> From for ReturnValue<'gc> { + fn from(value: f64) -> Self { + ReturnValue::Immediate(Value::Number(value)) + } +} + +impl<'gc> From for ReturnValue<'gc> { + fn from(value: f32) -> Self { + ReturnValue::Immediate(Value::Number(f64::from(value))) + } +} + +impl<'gc> From for ReturnValue<'gc> { + fn from(value: u8) -> Self { + ReturnValue::Immediate(Value::Number(f64::from(value))) + } +} + +impl<'gc> From for ReturnValue<'gc> { + fn from(value: i32) -> Self { + ReturnValue::Immediate(Value::Number(f64::from(value))) + } +} + +impl<'gc> From for ReturnValue<'gc> { + fn from(value: u32) -> Self { + ReturnValue::Immediate(Value::Number(f64::from(value))) + } +} + +impl<'gc> From>> for ReturnValue<'gc> { + fn from(frame: GcCell<'gc, Activation<'gc>>) -> Self { + ReturnValue::ResultOf(frame) + } +} diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 331aa1b717dd..0a45da222a59 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -1,7 +1,7 @@ //! Default AVM2 object impl use crate::avm2::names::QName; -use crate::avm2::object::TObject; +use crate::avm2::object::{ObjectPtr, TObject}; use crate::avm2::value::Value; use gc_arena::{Collect, GcCell}; use std::collections::HashMap; @@ -19,4 +19,8 @@ pub struct ScriptObjectData<'gc> { values: HashMap>, } -impl<'gc> TObject<'gc> for ScriptObject<'gc> {} +impl<'gc> TObject<'gc> for ScriptObject<'gc> { + fn as_ptr(&self) -> *const ObjectPtr { + self.0.as_ptr() as *const ObjectPtr + } +} diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 84084ff37d9d..1e11256b4bb9 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -11,8 +11,41 @@ use gc_arena::Collect; pub enum Value<'gc> { Undefined, Null, - Boolean(bool), + Bool(bool), Number(f64), String(String), Object(Object<'gc>), } + +impl PartialEq for Value<'_> { + fn eq(&self, other: &Self) -> bool { + match self { + Value::Undefined => match other { + Value::Undefined => true, + _ => false, + }, + Value::Null => match other { + Value::Null => true, + _ => false, + }, + Value::Bool(value) => match other { + Value::Bool(other_value) => value == other_value, + _ => false, + }, + Value::Number(value) => match other { + Value::Number(other_value) => { + (value == other_value) || (value.is_nan() && other_value.is_nan()) + } + _ => false, + }, + Value::String(value) => match other { + Value::String(other_value) => value == other_value, + _ => false, + }, + Value::Object(value) => match other { + Value::Object(other_value) => Object::ptr_eq(*value, *other_value), + _ => false, + }, + } + } +} diff --git a/swf/src/avm2/read.rs b/swf/src/avm2/read.rs index b6ee9bc3f557..84dbc5b80b54 100644 --- a/swf/src/avm2/read.rs +++ b/swf/src/avm2/read.rs @@ -518,7 +518,7 @@ impl Reader { } #[allow(dead_code)] - fn read_op(&mut self) -> Result> { + pub fn read_op(&mut self) -> Result> { use crate::avm2::opcode::OpCode; use num_traits::FromPrimitive; From 5600ac477ceca91a8cdcf5587e83ae0b6ca1626b Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 6 Feb 2020 14:12:01 -0500 Subject: [PATCH 005/189] Always execute any AVM2 code that may have been queued as a result of loading ABC files. --- core/src/avm2.rs | 34 +++++++++++++++++++++++++++++----- core/src/player.rs | 2 ++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index e69fe799a218..b899aa3d1f5e 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -104,22 +104,24 @@ impl<'gc> Avm2<'gc> { ) -> Result, { let (frame_cell, action, pc) = { - let frame = self.stack_frames.last().ok_or("No stack frame to read!")?; + 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()?; - (*frame, frame_ref.action(), frame_ref.pc()) + (frame, frame_ref.action(), frame_ref.pc()) }; let abc = action.abc.as_ref(); let method_index = action.abc_method; let method_body_index = action.abc_method_body as usize; let method_body: Result<&MethodBody, Error> = - abc.method_bodies.get(method_body_index).ok_or( + abc.method_bodies.get(method_body_index).ok_or_else(|| { "Attempting to execute a method that does not exist" .to_string() - .into(), - ); + .into() + }); let cursor = Cursor::new(method_body?.code.as_ref()); let mut read = Reader::new(cursor); @@ -134,6 +136,28 @@ impl<'gc> Avm2<'gc> { 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(()) + } + /// Run a single action from a given action reader. pub fn do_next_opcode( &mut self, diff --git a/core/src/player.rs b/core/src/player.rs index c92f137c1799..475a21f3381f 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -879,6 +879,8 @@ impl Player { } } } + // Execute the stack frame (if any). + let _ = avm2.run_stack_till_empty(context); } } From 4d000e1ce04ac6a5dff8aecc2762257fe7e32851 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 6 Feb 2020 19:28:54 -0500 Subject: [PATCH 006/189] Implement `pushxyz` opcodes for all value types that we currently support. --- core/src/avm2.rs | 131 ++++++++++++++++++++++++++++++++++++++--- core/src/avm2/value.rs | 75 +++++++++++++++++++++++ 2 files changed, 199 insertions(+), 7 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index b899aa3d1f5e..28bc2c85d1eb 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -6,8 +6,9 @@ use crate::context::UpdateContext; use crate::tag_utils::SwfSlice; use gc_arena::{Collect, GcCell}; use std::io::Cursor; +use std::rc::Rc; use swf::avm2::read::Reader; -use swf::avm2::types::MethodBody; +use swf::avm2::types::{AbcFile, Index, MethodBody, Op}; use swf::read::SwfRead; mod activation; @@ -158,6 +159,60 @@ impl<'gc> Avm2<'gc> { Ok(()) } + /// Push a value onto the operand stack. + fn push(&mut self, value: impl Into>) { + 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 + } + + /// Retrieve the current constant pool for the currently executing function. + fn current_abc(&self) -> Option> { + self.current_stack_frame() + .map(|sf| sf.read().action().abc.clone()) + } + + /// Retrieve a int from the current constant pool. + fn pool_int(&self, index: Index) -> Result { + self.current_abc() + .and_then(|abc| abc.constant_pool.ints.get(index.0 as usize).copied()) + .ok_or_else(|| format!("Unknown int constant {}", index.0).into()) + } + + /// Retrieve a int from the current constant pool. + fn pool_uint(&self, index: Index) -> Result { + self.current_abc() + .and_then(|abc| abc.constant_pool.uints.get(index.0 as usize).copied()) + .ok_or_else(|| format!("Unknown uint constant {}", index.0).into()) + } + + /// Retrieve a double from the current constant pool. + fn pool_double(&self, index: Index) -> Result { + self.current_abc() + .and_then(|abc| abc.constant_pool.doubles.get(index.0 as usize).copied()) + .ok_or_else(|| format!("Unknown double constant {}", index.0).into()) + } + + /// Retrieve a string from the current constant pool. + fn pool_string(&self, index: Index) -> Result { + self.current_abc() + .and_then(|abc| abc.constant_pool.strings.get(index.0 as usize).cloned()) + .ok_or_else(|| format!("Unknown string constant {}", index.0).into()) + } + /// Run a single action from a given action reader. pub fn do_next_opcode( &mut self, @@ -168,7 +223,18 @@ impl<'gc> Avm2<'gc> { avm_debug!("Opcode: {:?}", op); let result = match op { - _ => self.unknown_op(context, 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::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(), + _ => self.unknown_op(op), }; if let Err(ref e) = result { @@ -180,12 +246,63 @@ impl<'gc> Avm2<'gc> { Ok(()) } - fn unknown_op( - &mut self, - _context: &mut UpdateContext, - op: swf::avm2::types::Op, - ) -> Result<(), Error> { + 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) -> 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) -> Result<(), Error> { + self.push(self.pool_int(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) -> 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) -> Result<(), Error> { + self.push(self.pool_uint(value)?); + Ok(()) + } + + fn op_push_undefined(&mut self) -> Result<(), Error> { + self.push(Value::Undefined); + Ok(()) + } } diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 1e11256b4bb9..6451b7c58d00 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -17,6 +17,81 @@ pub enum Value<'gc> { Object(Object<'gc>), } +impl<'gc> From for Value<'gc> { + fn from(string: String) -> Self { + Value::String(string) + } +} + +impl<'gc> From<&str> for Value<'gc> { + fn from(string: &str) -> Self { + Value::String(string.to_owned()) + } +} + +impl<'gc> From for Value<'gc> { + fn from(value: bool) -> Self { + Value::Bool(value) + } +} + +impl<'gc, T> From for Value<'gc> +where + Object<'gc>: From, +{ + fn from(value: T) -> Self { + Value::Object(Object::from(value)) + } +} + +impl<'gc> From for Value<'gc> { + fn from(value: f64) -> Self { + Value::Number(value) + } +} + +impl<'gc> From for Value<'gc> { + fn from(value: f32) -> Self { + Value::Number(f64::from(value)) + } +} + +impl<'gc> From for Value<'gc> { + fn from(value: u8) -> Self { + Value::Number(f64::from(value)) + } +} + +impl<'gc> From for Value<'gc> { + fn from(value: i16) -> Self { + Value::Number(f64::from(value)) + } +} + +impl<'gc> From for Value<'gc> { + fn from(value: u16) -> Self { + Value::Number(f64::from(value)) + } +} + +impl<'gc> From for Value<'gc> { + fn from(value: i32) -> Self { + Value::Number(f64::from(value)) + } +} + +impl<'gc> From for Value<'gc> { + fn from(value: u32) -> Self { + Value::Number(f64::from(value)) + } +} + +impl<'gc> From for Value<'gc> { + fn from(value: usize) -> Self { + Value::Number(value as f64) + } +} + impl PartialEq for Value<'_> { fn eq(&self, other: &Self) -> bool { match self { From d1aeae8e024acbbafabbc9862289653e1353fd11 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 7 Feb 2020 14:54:14 -0500 Subject: [PATCH 007/189] Add support for local registers in the interpreter. --- core/src/avm2.rs | 37 +++++++++++++++ core/src/avm2/activation.rs | 89 +++++++++++++++++++++++++++++++++++-- 2 files changed, 122 insertions(+), 4 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 28bc2c85d1eb..975521c553c4 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -179,6 +179,27 @@ impl<'gc> Avm2<'gc> { value } + fn register_value(&self, index: u32) -> Result, 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>, + ) -> 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> { self.current_stack_frame() @@ -234,6 +255,8 @@ impl<'gc> Avm2<'gc> { Op::PushTrue => self.op_push_true(), Op::PushUint { value } => self.op_push_uint(value), Op::PushUndefined => self.op_push_undefined(), + Op::GetLocal { index } => self.op_get_local(index), + Op::SetLocal { index } => self.op_set_local(context, index), _ => self.unknown_op(op), }; @@ -305,4 +328,18 @@ impl<'gc> Avm2<'gc> { self.push(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) + } } diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 5fd0ea31dab8..031b35022281 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -2,8 +2,49 @@ use crate::avm2::function::Avm2Function; use crate::avm2::object::Object; +use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::{Collect, Gc}; +use crate::context::UpdateContext; +use gc_arena::{Collect, Gc, GcCell, MutationContext}; +use smallvec::SmallVec; + +/// Represents a particular register set. +/// +/// This type exists primarily because SmallVec isn't garbage-collectable. +#[derive(Clone)] +pub struct RegisterSet<'gc>(SmallVec<[Value<'gc>; 8]>); + +unsafe impl<'gc> gc_arena::Collect for RegisterSet<'gc> { + #[inline] + fn trace(&self, cc: gc_arena::CollectionContext) { + for register in &self.0 { + register.trace(cc); + } + } +} + +impl<'gc> RegisterSet<'gc> { + /// Create a new register set with a given number of specified registers. + /// + /// The given registers will be set to `undefined`. + pub fn new(num: u32) -> Self { + Self(smallvec![Value::Undefined; num as usize]) + } + + /// Return a reference to a given register, if it exists. + pub fn get(&self, num: u32) -> Option<&Value<'gc>> { + self.0.get(num as usize) + } + + /// Return a mutable reference to a given register, if it exists. + pub fn get_mut(&mut self, num: u32) -> Option<&mut Value<'gc>> { + self.0.get_mut(num as usize) + } + + pub fn len(&self) -> u32 { + self.0.len() as u32 + } +} /// Represents a single activation of a given AVM2 function or keyframe. #[derive(Clone, Collect)] @@ -25,21 +66,38 @@ pub struct Activation<'gc> { /// reader object copied from it. Taking out two readers on the same /// activation frame is a programming error. is_executing: bool, + + /// Local registers. + /// + /// All activations have local registers, but it is possible for multiple + /// activations (such as a rescope) to execute from the same register set. + local_registers: GcCell<'gc, RegisterSet<'gc>>, } impl<'gc> Activation<'gc> { pub fn from_action( + context: &mut UpdateContext<'_, 'gc, '_>, action: Gc<'gc, Avm2Function>, this: Object<'gc>, arguments: Option>, - ) -> Self { - Self { + ) -> Result { + let abc = action.abc.clone(); + let method_body = abc + .method_bodies + .get(action.abc_method_body as usize) + .ok_or_else(|| format!("Method body {} does not exist", action.abc_method_body))?; + + Ok(Self { action, pc: 0, this, arguments, is_executing: false, - } + local_registers: GcCell::allocate( + context.gc_context, + RegisterSet::new(method_body.num_locals), + ), + }) } /// Attempts to lock the activation frame for execution. @@ -75,4 +133,27 @@ impl<'gc> Activation<'gc> { pub fn set_pc(&mut self, new_pc: usize) { self.pc = new_pc; } + + /// Retrieve a local register. + pub fn local_register(&self, id: u32) -> Option> { + self.local_registers.read().get(id).cloned() + } + + /// Set a local register. + /// + /// Returns `true` if the set was successful; `false` otherwise + pub fn set_local_register( + &mut self, + id: u32, + value: impl Into>, + mc: MutationContext<'gc, '_>, + ) -> bool { + if let Some(r) = self.local_registers.write(mc).get_mut(id) { + *r = value.into(); + + true + } else { + false + } + } } From 115f0393aa5c42da8a59c9dce9960cc2acdc1fae Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 7 Feb 2020 22:42:04 -0500 Subject: [PATCH 008/189] Add `call` method to the object trait. Only functions are callable in AVM2, all others error out. --- core/src/avm2.rs | 7 +++++++ core/src/avm2/function.rs | 28 ++++++++++++++++++++++++++-- core/src/avm2/object.rs | 13 ++++++++++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 975521c553c4..1f3b284f9b8c 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -73,10 +73,17 @@ impl<'gc> Avm2<'gc> { Ok(()) } + /// Get the current stack frame (`Activation` object). pub fn current_stack_frame(&self) -> Option>> { 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) + } + /// Perform some action with the current stack frame's reader. /// /// This function constructs a reader based off the current stack frame's diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 278b86b5c104..ef84e3eb7174 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -1,12 +1,13 @@ //! AVM2 executables. +use crate::avm2::activation::Activation; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObjectData; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use gc_arena::{Collect, CollectionContext, GcCell}; +use gc_arena::{Collect, CollectionContext, Gc, GcCell}; use std::fmt; use std::rc::Rc; use swf::avm2::types::AbcFile; @@ -50,7 +51,7 @@ pub struct Avm2Function { #[derive(Clone)] pub enum Executable<'gc> { Native(NativeFunction<'gc>), - Action(Avm2Function), + Action(Gc<'gc, Avm2Function>), } unsafe impl<'gc> Collect for Executable<'gc> { @@ -93,4 +94,27 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } + + fn call( + self, + reciever: Object<'gc>, + arguments: &[Value<'gc>], + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + let exec = self.0.read().exec.clone(); + + match exec { + Executable::Native(nf) => nf(avm, context, reciever, arguments), + Executable::Action(a2f) => { + let activation = GcCell::allocate( + context.gc_context, + Activation::from_action(context, a2f, reciever, None)?, + ); + + avm.insert_stack_frame(activation); + Ok(activation.into()) + } + } + } } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index b49d7bec9180..6ac5dad700b6 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -34,7 +34,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Set a property by it's QName. fn set_property( - self, + &mut self, _name: &QName, _value: Value<'gc>, _avm: &mut Avm2<'gc>, @@ -43,6 +43,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy Ok(()) } + /// Call the object. + fn call( + self, + _reciever: Object<'gc>, + _arguments: &[Value<'gc>], + _avm: &mut Avm2<'gc>, + _context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + Err("Object is not callable".into()) + } + /// Get a raw pointer value for this object. fn as_ptr(&self) -> *const ObjectPtr; } From 52ac7a6583c7cb9eb1106b4c6c281868aaa47540 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 8 Feb 2020 16:59:59 -0500 Subject: [PATCH 009/189] Implement call/return for bare functions --- core/src/avm2.rs | 92 +++++++++++++++++++++++++++++++++++ core/src/avm2/activation.rs | 18 +++++++ core/src/avm2/return_value.rs | 7 +-- core/src/avm2/value.rs | 11 +++++ 4 files changed, 125 insertions(+), 3 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 1f3b284f9b8c..7871c68ca5de 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,6 +1,7 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::Activation; +use crate::avm2::object::TObject; use crate::avm2::value::Value; use crate::context::UpdateContext; use crate::tag_utils::SwfSlice; @@ -84,6 +85,36 @@ impl<'gc> Avm2<'gc> { self.stack_frames.push(frame) } + /// Destroy the current stack frame (if there is one). + /// + /// 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(()) + } + /// Perform some action with the current stack frame's reader. /// /// This function constructs a reader based off the current stack frame's @@ -166,6 +197,37 @@ impl<'gc> Avm2<'gc> { 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>) { let value = value.into(); @@ -264,6 +326,9 @@ impl<'gc> Avm2<'gc> { Op::PushUndefined => self.op_push_undefined(), Op::GetLocal { index } => self.op_get_local(index), Op::SetLocal { index } => self.op_set_local(context, index), + Op::Call { num_args } => self.op_call(context, num_args), + Op::ReturnValue => self.op_return_value(context), + Op::ReturnVoid => self.op_return_void(context), _ => self.unknown_op(op), }; @@ -349,4 +414,31 @@ impl<'gc> Avm2<'gc> { let value = self.pop(); self.set_register_value(context, register_index, value) } + + fn op_call( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + arg_count: u32, + ) -> Result<(), Error> { + let function = self.pop().as_object()?; + let receiver = self.pop().as_object()?; + let mut args = Vec::new(); + for _ in 0..arg_count { + args.push(self.pop()); + } + + function.call(receiver, &args, self, context)?.push(self); + + 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) + } } diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 031b35022281..be8f83214d79 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -72,6 +72,12 @@ pub struct Activation<'gc> { /// All activations have local registers, but it is possible for multiple /// activations (such as a rescope) to execute from the same register set. local_registers: GcCell<'gc, RegisterSet<'gc>>, + + /// What was returned from the function. + /// + /// A return value of `None` indicates that the called function is still + /// executing. Functions that do not return instead return `Undefined`. + return_value: Option>, } impl<'gc> Activation<'gc> { @@ -97,6 +103,7 @@ impl<'gc> Activation<'gc> { context.gc_context, RegisterSet::new(method_body.num_locals), ), + return_value: None, }) } @@ -156,4 +163,15 @@ impl<'gc> Activation<'gc> { false } } + + /// Retrieve the return value from a completed activation, if the function + /// has already returned. + pub fn return_value(&self) -> Option> { + self.return_value.clone() + } + + /// Set the return value. + pub fn set_return_value(&mut self, value: Value<'gc>) { + self.return_value = Some(value); + } } diff --git a/core/src/avm2/return_value.rs b/core/src/avm2/return_value.rs index 58e13887862f..9246eabe4ad9 100644 --- a/core/src/avm2/return_value.rs +++ b/core/src/avm2/return_value.rs @@ -65,7 +65,7 @@ impl<'gc> ReturnValue<'gc> { use ReturnValue::*; match self { - Immediate(val) => {} //TODO: avm.push(val) + Immediate(val) => avm.push(val), ResultOf(_frame) => {} }; } @@ -82,8 +82,9 @@ impl<'gc> ReturnValue<'gc> { match self { Immediate(val) => Ok(val), ResultOf(frame) => { - //TODO: Execute AVM frame, pop return value - Ok(Value::Undefined) + avm.run_current_frame(context, frame)?; + + Ok(avm.pop()) } } } diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 6451b7c58d00..bc1d1020f12b 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -1,6 +1,7 @@ //! AVM2 values use crate::avm2::object::Object; +use crate::avm2::Error; use gc_arena::Collect; /// An AVM2 value. @@ -124,3 +125,13 @@ impl PartialEq for Value<'_> { } } } + +impl<'gc> Value<'gc> { + pub fn as_object(&self) -> Result, Error> { + if let Value::Object(object) = self { + Ok(*object) + } else { + Err(format!("Expected Object, found {:?}", self).into()) + } + } +} From 43f1080fabb1f66cf650a0ca5675ad6cae3f2ea0 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 8 Feb 2020 18:07:50 -0500 Subject: [PATCH 010/189] Implement namespaces as a value type --- core/src/avm2/return_value.rs | 7 +++++++ core/src/avm2/value.rs | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/core/src/avm2/return_value.rs b/core/src/avm2/return_value.rs index 9246eabe4ad9..a9d692350fb6 100644 --- a/core/src/avm2/return_value.rs +++ b/core/src/avm2/return_value.rs @@ -1,6 +1,7 @@ //! Return value enum use crate::avm2::activation::Activation; +use crate::avm2::names::Namespace; use crate::avm2::object::Object; use crate::avm2::{Avm2, Error, Value}; use crate::context::UpdateContext; @@ -176,6 +177,12 @@ impl<'gc> From for ReturnValue<'gc> { } } +impl<'gc> From for ReturnValue<'gc> { + fn from(value: Namespace) -> Self { + ReturnValue::Immediate(Value::Namespace(value)) + } +} + impl<'gc> From>> for ReturnValue<'gc> { fn from(frame: GcCell<'gc, Activation<'gc>>) -> Self { ReturnValue::ResultOf(frame) diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index bc1d1020f12b..e786d72b1a3a 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -1,5 +1,6 @@ //! AVM2 values +use crate::avm2::names::Namespace; use crate::avm2::object::Object; use crate::avm2::Error; use gc_arena::Collect; @@ -15,6 +16,7 @@ pub enum Value<'gc> { Bool(bool), Number(f64), String(String), + Namespace(Namespace), Object(Object<'gc>), } @@ -93,6 +95,12 @@ impl<'gc> From for Value<'gc> { } } +impl<'gc> From for Value<'gc> { + fn from(value: Namespace) -> Self { + Value::Namespace(value) + } +} + impl PartialEq for Value<'_> { fn eq(&self, other: &Self) -> bool { match self { @@ -122,6 +130,10 @@ impl PartialEq for Value<'_> { Value::Object(other_value) => Object::ptr_eq(*value, *other_value), _ => false, }, + Value::Namespace(ns) => match other { + Value::Namespace(other_ns) => ns == other_ns, + _ => false, + }, } } } From 6bd94d6bc9c0a81be513403d1dfde97167022bda Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 8 Feb 2020 18:08:30 -0500 Subject: [PATCH 011/189] `from_abc_namespace` should accept a namespace index and retrieve it from the file's constant pool itself. --- core/src/avm2/names.rs | 61 +++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 1c78dceab557..117652c2fd87 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -1,7 +1,7 @@ //! AVM2 names & namespacing use gc_arena::Collect; -use swf::avm2::types::{AbcFile, Index, Namespace as AbcNamespace}; +use swf::avm2::types::{AbcFile, Index, Multiname as AbcMultiname, Namespace as AbcNamespace}; /// Represents the name of a namespace. #[derive(Clone, Collect, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -17,30 +17,41 @@ pub enum Namespace { } impl Namespace { - pub fn from_abc_namespace(name: &AbcNamespace, file: &AbcFile) -> Option { - Some(match name { - AbcNamespace::Namespace(Index(idx, ..)) => { - Self::Namespace(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - AbcNamespace::Package(Index(idx, ..)) => { - Self::Package(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - AbcNamespace::PackageInternal(Index(idx, ..)) => { - Self::PackageInternal(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - AbcNamespace::Protected(Index(idx, ..)) => { - Self::Protected(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - AbcNamespace::Explicit(Index(idx, ..)) => { - Self::Explicit(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - AbcNamespace::StaticProtected(Index(idx, ..)) => { - Self::StaticProtected(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - AbcNamespace::Private(Index(idx, ..)) => { - Self::Private(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - }) + /// Read a namespace declaration from the ABC constant pool and copy it to + /// a namespace value. + pub fn from_abc_namespace( + file: &AbcFile, + namespace_index: Index, + ) -> Option { + Some( + match file + .constant_pool + .namespaces + .get(namespace_index.0 as usize)? + { + AbcNamespace::Namespace(Index(idx, ..)) => { + Self::Namespace(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + AbcNamespace::Package(Index(idx, ..)) => { + Self::Package(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + AbcNamespace::PackageInternal(Index(idx, ..)) => { + Self::PackageInternal(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + AbcNamespace::Protected(Index(idx, ..)) => { + Self::Protected(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + AbcNamespace::Explicit(Index(idx, ..)) => { + Self::Explicit(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + AbcNamespace::StaticProtected(Index(idx, ..)) => { + Self::StaticProtected(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + AbcNamespace::Private(Index(idx, ..)) => { + Self::Private(file.constant_pool.strings.get(*idx as usize)?.clone()) + } + }, + ) } } From c65d93d0636b2f04459ac644993729ada4bf8430 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 8 Feb 2020 18:09:03 -0500 Subject: [PATCH 012/189] Implement multinames, sans runtime namespace support. --- core/src/avm2/names.rs | 57 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 117652c2fd87..b1a2cc10c5ae 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -1,5 +1,6 @@ //! AVM2 names & namespacing +use crate::avm2::Avm2; use gc_arena::Collect; use swf::avm2::types::{AbcFile, Index, Multiname as AbcMultiname, Namespace as AbcNamespace}; @@ -70,3 +71,59 @@ pub struct QName { ns: Namespace, name: String, } + +/// A `Multiname` consists of a name which could be resolved in one or more +/// potential namespaces. +/// +/// All unresolved names are of the form `Multiname`, and the name resolution +/// process consists of searching each name space for a given name. +pub struct Multiname { + ns: Vec, + name: String, +} + +impl Multiname { + /// Read a multiname from the ABC constant pool, copying it into the most + /// general form of multiname. + /// + /// This does not yet support late-bound or runtime multinames. + pub fn from_abc_multiname( + file: &AbcFile, + multiname_index: Index, + avm: &mut Avm2<'_>, + ) -> Option { + Some( + match file + .constant_pool + .multinames + .get(multiname_index.0 as usize)? + { + AbcMultiname::QName { namespace, name } + | AbcMultiname::QNameA { namespace, name } => Self { + ns: vec![Namespace::from_abc_namespace(file, namespace.clone())?], + name: file.constant_pool.strings.get(name.0 as usize)?.clone(), + }, + AbcMultiname::Multiname { + namespace_set, + name, + } + | AbcMultiname::MultinameA { + namespace_set, + name, + } => Self { + ns: file + .constant_pool + .namespace_sets + .get(namespace_set.0 as usize)? + .iter() + .map(|ns| Namespace::from_abc_namespace(file, ns.clone())) + .filter(|ns| ns.is_some()) + .map(|ns| ns.unwrap()) + .collect(), + name: file.constant_pool.strings.get(name.0 as usize)?.clone(), + }, + _ => return None, + }, + ) + } +} From 6d8dc6e63d2c488d48968b81b1ea2481613bd9ba Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 9 Feb 2020 14:45:51 -0700 Subject: [PATCH 013/189] Pull ABC constant pool methods out of Avm2 and into Value. The old methods still exist and do the same thing, but the Value methods accept arbitrary AbcFile references. --- core/src/avm2.rs | 16 ++++------------ core/src/avm2/value.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 7871c68ca5de..1843ede79ddb 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -277,30 +277,22 @@ impl<'gc> Avm2<'gc> { /// Retrieve a int from the current constant pool. fn pool_int(&self, index: Index) -> Result { - self.current_abc() - .and_then(|abc| abc.constant_pool.ints.get(index.0 as usize).copied()) - .ok_or_else(|| format!("Unknown int constant {}", index.0).into()) + value::abc_int(&self.current_abc().unwrap(), index) } /// Retrieve a int from the current constant pool. fn pool_uint(&self, index: Index) -> Result { - self.current_abc() - .and_then(|abc| abc.constant_pool.uints.get(index.0 as usize).copied()) - .ok_or_else(|| format!("Unknown uint constant {}", index.0).into()) + value::abc_uint(&self.current_abc().unwrap(), index) } /// Retrieve a double from the current constant pool. fn pool_double(&self, index: Index) -> Result { - self.current_abc() - .and_then(|abc| abc.constant_pool.doubles.get(index.0 as usize).copied()) - .ok_or_else(|| format!("Unknown double constant {}", index.0).into()) + value::abc_double(&self.current_abc().unwrap(), index) } /// Retrieve a string from the current constant pool. fn pool_string(&self, index: Index) -> Result { - self.current_abc() - .and_then(|abc| abc.constant_pool.strings.get(index.0 as usize).cloned()) - .ok_or_else(|| format!("Unknown string constant {}", index.0).into()) + value::abc_string(&self.current_abc().unwrap(), index) } /// Run a single action from a given action reader. diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index e786d72b1a3a..dced306276ae 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -4,6 +4,7 @@ use crate::avm2::names::Namespace; use crate::avm2::object::Object; use crate::avm2::Error; use gc_arena::Collect; +use swf::avm2::types::{AbcFile, Index}; /// An AVM2 value. /// @@ -138,6 +139,38 @@ impl PartialEq for Value<'_> { } } +pub fn abc_int(file: &AbcFile, index: Index) -> Result { + file.constant_pool + .ints + .get(index.0 as usize) + .cloned() + .ok_or_else(|| format!("Unknown int constant {}", index.0).into()) +} + +pub fn abc_uint(file: &AbcFile, index: Index) -> Result { + file.constant_pool + .uints + .get(index.0 as usize) + .cloned() + .ok_or_else(|| format!("Unknown uint constant {}", index.0).into()) +} + +pub fn abc_double(file: &AbcFile, index: Index) -> Result { + file.constant_pool + .doubles + .get(index.0 as usize) + .cloned() + .ok_or_else(|| format!("Unknown double constant {}", index.0).into()) +} + +pub fn abc_string(file: &AbcFile, index: Index) -> Result { + file.constant_pool + .strings + .get(index.0 as usize) + .cloned() + .ok_or_else(|| format!("Unknown string constant {}", index.0).into()) +} + impl<'gc> Value<'gc> { pub fn as_object(&self) -> Result, Error> { if let Value::Object(object) = self { From 2f3a3aff6f7969fa48f77c6a8853bb4c15673ca6 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 9 Feb 2020 14:46:11 -0700 Subject: [PATCH 014/189] Add exact type assertion methods for strings and namespace values. --- core/src/avm2/value.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index dced306276ae..167a08483134 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -179,4 +179,18 @@ impl<'gc> Value<'gc> { Err(format!("Expected Object, found {:?}", self).into()) } } + + pub fn as_string(&self) -> Result<&String, Error> { + match self { + Value::String(s) => Ok(s), + _ => Err(format!("Expected String, found {:?}", self).into()), + } + } + + pub fn as_namespace(&self) -> Result<&Namespace, Error> { + match self { + Value::Namespace(ns) => Ok(ns), + _ => Err(format!("Expected Namespace, found {:?}", self).into()), + } + } } From 1a6acb94407e95f67f394a7f497e9985f2085b25 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 9 Feb 2020 14:46:29 -0700 Subject: [PATCH 015/189] Implement late binding and runtime qualifications for names. --- core/src/avm2/names.rs | 159 ++++++++++++++++++++++++----------------- 1 file changed, 94 insertions(+), 65 deletions(-) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index b1a2cc10c5ae..2640501c2679 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -1,8 +1,12 @@ //! AVM2 names & namespacing -use crate::avm2::Avm2; +use crate::avm2::value::abc_string; +use crate::avm2::{Avm2, Error}; use gc_arena::Collect; -use swf::avm2::types::{AbcFile, Index, Multiname as AbcMultiname, Namespace as AbcNamespace}; +use swf::avm2::types::{ + AbcFile, Index, Multiname as AbcMultiname, Namespace as AbcNamespace, + NamespaceSet as AbcNamespaceSet, +}; /// Represents the name of a namespace. #[derive(Clone, Collect, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -23,36 +27,26 @@ impl Namespace { pub fn from_abc_namespace( file: &AbcFile, namespace_index: Index, - ) -> Option { - Some( - match file - .constant_pool - .namespaces - .get(namespace_index.0 as usize)? - { - AbcNamespace::Namespace(Index(idx, ..)) => { - Self::Namespace(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - AbcNamespace::Package(Index(idx, ..)) => { - Self::Package(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - AbcNamespace::PackageInternal(Index(idx, ..)) => { - Self::PackageInternal(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - AbcNamespace::Protected(Index(idx, ..)) => { - Self::Protected(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - AbcNamespace::Explicit(Index(idx, ..)) => { - Self::Explicit(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - AbcNamespace::StaticProtected(Index(idx, ..)) => { - Self::StaticProtected(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - AbcNamespace::Private(Index(idx, ..)) => { - Self::Private(file.constant_pool.strings.get(*idx as usize)?.clone()) - } - }, - ) + ) -> Result { + let abc_namespace: Result<&AbcNamespace, Error> = file + .constant_pool + .namespaces + .get(namespace_index.0 as usize) + .ok_or_else(|| format!("Unknown namespace constant {}", namespace_index.0).into()); + + Ok(match abc_namespace? { + AbcNamespace::Namespace(idx) => Self::Namespace(abc_string(file, idx.clone())?), + AbcNamespace::Package(idx) => Self::Package(abc_string(file, idx.clone())?), + AbcNamespace::PackageInternal(idx) => { + Self::PackageInternal(abc_string(file, idx.clone())?) + } + AbcNamespace::Protected(idx) => Self::Protected(abc_string(file, idx.clone())?), + AbcNamespace::Explicit(idx) => Self::Explicit(abc_string(file, idx.clone())?), + AbcNamespace::StaticProtected(idx) => { + Self::StaticProtected(abc_string(file, idx.clone())?) + } + AbcNamespace::Private(idx) => Self::Private(abc_string(file, idx.clone())?), + }) } } @@ -83,47 +77,82 @@ pub struct Multiname { } impl Multiname { + /// Read a namespace set from the ABC constant pool, and return a list of + /// copied namespaces. + fn abc_namespace_set( + file: &AbcFile, + namespace_set_index: Index, + ) -> Result, Error> { + let ns_set: Result<&AbcNamespaceSet, Error> = file + .constant_pool + .namespace_sets + .get(namespace_set_index.0 as usize) + .ok_or_else(|| { + format!("Unknown namespace set constant {}", namespace_set_index.0).into() + }); + let mut result = vec![]; + + for ns in ns_set? { + result.push(Namespace::from_abc_namespace(file, ns.clone())?) + } + + Ok(result) + } + /// Read a multiname from the ABC constant pool, copying it into the most /// general form of multiname. - /// - /// This does not yet support late-bound or runtime multinames. pub fn from_abc_multiname( file: &AbcFile, multiname_index: Index, avm: &mut Avm2<'_>, - ) -> Option { - Some( - match file - .constant_pool - .multinames - .get(multiname_index.0 as usize)? - { - AbcMultiname::QName { namespace, name } - | AbcMultiname::QNameA { namespace, name } => Self { + ) -> Result { + let abc_multiname: Result<&AbcMultiname, Error> = file + .constant_pool + .multinames + .get(multiname_index.0 as usize) + .ok_or_else(|| format!("Unknown multiname constant {}", multiname_index.0).into()); + + Ok(match abc_multiname? { + AbcMultiname::QName { namespace, name } | AbcMultiname::QNameA { namespace, name } => { + Self { ns: vec![Namespace::from_abc_namespace(file, namespace.clone())?], - name: file.constant_pool.strings.get(name.0 as usize)?.clone(), - }, - AbcMultiname::Multiname { - namespace_set, - name, + name: abc_string(file, name.clone())?, } - | AbcMultiname::MultinameA { - namespace_set, - name, - } => Self { - ns: file - .constant_pool - .namespace_sets - .get(namespace_set.0 as usize)? - .iter() - .map(|ns| Namespace::from_abc_namespace(file, ns.clone())) - .filter(|ns| ns.is_some()) - .map(|ns| ns.unwrap()) - .collect(), - name: file.constant_pool.strings.get(name.0 as usize)?.clone(), - }, - _ => return None, + } + AbcMultiname::RTQName { name } | AbcMultiname::RTQNameA { name } => { + let ns = avm.pop().as_namespace()?.clone(); + Self { + ns: vec![ns], + name: abc_string(file, name.clone())?, + } + } + AbcMultiname::RTQNameL | AbcMultiname::RTQNameLA => { + let ns = avm.pop().as_namespace()?.clone(); + let name = avm.pop().as_string()?.clone(); + Self { + ns: vec![ns], + name: name, + } + } + AbcMultiname::Multiname { + namespace_set, + name, + } + | AbcMultiname::MultinameA { + namespace_set, + name, + } => Self { + ns: Self::abc_namespace_set(file, namespace_set.clone())?, + name: abc_string(file, name.clone())?, }, - ) + AbcMultiname::MultinameL { namespace_set } + | AbcMultiname::MultinameLA { namespace_set } => { + let name = avm.pop().as_string()?.clone(); + Self { + ns: Self::abc_namespace_set(file, namespace_set.clone())?, + name, + } + } + }) } } From 3b476cba9e409c41b754b1916fe1b31acb445975 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 9 Feb 2020 21:25:42 -0500 Subject: [PATCH 016/189] Implement `pushnamespace` since that's a value type now --- core/src/avm2.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 1843ede79ddb..aec310167df5 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,6 +1,7 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::Activation; +use crate::avm2::names::Namespace; use crate::avm2::object::TObject; use crate::avm2::value::Value; use crate::context::UpdateContext; @@ -9,7 +10,7 @@ use gc_arena::{Collect, GcCell}; use std::io::Cursor; use std::rc::Rc; use swf::avm2::read::Reader; -use swf::avm2::types::{AbcFile, Index, MethodBody, Op}; +use swf::avm2::types::{AbcFile, Index, MethodBody, Namespace as AbcNamespace, Op}; use swf::read::SwfRead; mod activation; @@ -295,6 +296,11 @@ impl<'gc> Avm2<'gc> { value::abc_string(&self.current_abc().unwrap(), index) } + /// Retrieve a namespace from the current constant pool. + fn pool_namespace(&self, index: Index) -> Result { + Namespace::from_abc_namespace(&self.current_abc().unwrap(), index) + } + /// Run a single action from a given action reader. pub fn do_next_opcode( &mut self, @@ -309,6 +315,7 @@ impl<'gc> Avm2<'gc> { 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), @@ -358,6 +365,11 @@ impl<'gc> Avm2<'gc> { Ok(()) } + fn op_push_namespace(&mut self, value: Index) -> Result<(), Error> { + self.push(self.pool_namespace(value)?); + Ok(()) + } + fn op_push_nan(&mut self) -> Result<(), Error> { self.push(std::f64::NAN); Ok(()) From 376d1a8ca679688724650c55f8b02b7d6f44ebc1 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 10 Feb 2020 14:54:55 -0500 Subject: [PATCH 017/189] Add scope support --- core/src/avm2.rs | 1 + core/src/avm2/activation.rs | 12 ++ core/src/avm2/object.rs | 67 ++++++++- core/src/avm2/scope.rs | 240 +++++++++++++++++++++++++++++++++ core/src/avm2/script_object.rs | 16 ++- 5 files changed, 332 insertions(+), 4 deletions(-) create mode 100644 core/src/avm2/scope.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index aec310167df5..c1afecdba6ed 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -18,6 +18,7 @@ mod function; mod names; mod object; mod return_value; +mod scope; mod script_object; mod value; diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index be8f83214d79..7caa1c1ca8ab 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -2,6 +2,8 @@ use crate::avm2::function::Avm2Function; use crate::avm2::object::Object; +use crate::avm2::scope::Scope; +use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; use crate::avm2::Error; use crate::context::UpdateContext; @@ -78,6 +80,14 @@ pub struct Activation<'gc> { /// A return value of `None` indicates that the called function is still /// executing. Functions that do not return instead return `Undefined`. return_value: Option>, + + /// The current local scope, implemented as a bare object. + local_scope: Object<'gc>, + + /// The current scope stack. + /// + /// A `scope` of `None` indicates that the scope stack is empty. + scope: Option>>, } impl<'gc> Activation<'gc> { @@ -104,6 +114,8 @@ impl<'gc> Activation<'gc> { RegisterSet::new(method_body.num_locals), ), return_value: None, + local_scope: ScriptObject::bare_object(context.gc_context), + scope: None, }) } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 6ac5dad700b6..ea2ca29dca6c 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -7,10 +7,21 @@ use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use gc_arena::Collect; +use enumset::{EnumSet, EnumSetType}; +use gc_arena::{Collect, MutationContext}; use ruffle_macros::enum_trait_object; use std::fmt::Debug; +/// Attributes of properties in the AVM runtime. +/// +/// TODO: Replace with AVM2 properties for traits +#[derive(EnumSetType, Debug)] +pub enum Attribute { + DontEnum, + DontDelete, + ReadOnly, +} + /// Represents an object that can be directly interacted with by the AVM2 /// runtime. #[enum_trait_object( @@ -34,7 +45,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Set a property by it's QName. fn set_property( - &mut self, + self, _name: &QName, _value: Value<'gc>, _avm: &mut Avm2<'gc>, @@ -43,6 +54,58 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy Ok(()) } + /// Indicates whether or not a property exists on an object. + fn has_property(self, _name: &QName) -> bool { + false + } + + /// Indicates whether or not a property exists on an object and is not part + /// of the prototype chain. + fn has_own_property(self, _name: &QName) -> bool { + false + } + + /// Indicates whether or not a property is overwritable. + fn is_property_overwritable(self, _name: &QName) -> bool { + false + } + + /// Delete a named property from the object. + /// + /// Returns false if the property cannot be deleted. + fn delete(&self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool { + false + } + + /// Retrieve the `__proto__` of a given object. + /// + /// The proto is another object used to resolve methods across a class of + /// multiple objects. It should also be accessible as `__proto__` from + /// `get`. + fn proto(&self) -> Option> { + None + } + + /// Define a value on an object. + /// + /// Unlike setting a value, this function is intended to replace any + /// existing virtual or built-in properties already installed on a given + /// object. As such, this should not run any setters; the resulting name + /// slot should either be completely replaced with the value or completely + /// untouched. + /// + /// It is not guaranteed that all objects accept value definitions, + /// especially if a property name conflicts with a built-in property, such + /// as `__proto__`. + fn define_value( + &self, + gc_context: MutationContext<'gc, '_>, + name: &QName, + value: Value<'gc>, + attributes: EnumSet, + ) { + } + /// Call the object. fn call( self, diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs new file mode 100644 index 000000000000..e8cbf6619a43 --- /dev/null +++ b/core/src/avm2/scope.rs @@ -0,0 +1,240 @@ +//! Represents AVM2 scope chain resolution. + +use crate::avm2::names::QName; +use crate::avm2::object::{Object, TObject}; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::script_object::ScriptObject; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use enumset::EnumSet; +use gc_arena::{Collect, GcCell, MutationContext}; +use std::cell::Ref; + +/// Indicates what kind of scope a scope is. +#[derive(Copy, Clone, Debug, PartialEq, Collect)] +#[collect(no_drop)] +pub enum ScopeClass { + /// Scope represents global or closure scope. + GlobalOrClosure, + + /// Scope represents an object added to the scope chain with `with`. + /// It is not inherited when closures are defined. Furthermore, a `with` + /// scope gains the ability to be searched for dynamic properties. + With, +} + +/// Represents a scope chain for an AVM2 activation. +#[derive(Debug, Collect)] +#[collect(no_drop)] +pub struct Scope<'gc> { + parent: Option>>, + class: ScopeClass, + values: Object<'gc>, +} + +impl<'gc> Scope<'gc> { + /// Push a scope onto the stack, producing a new scope chain that's one + /// item longer. + pub fn push_scope( + scope_stack: Option>>, + object: Object<'gc>, + mc: MutationContext<'gc, '_>, + ) -> GcCell<'gc, Self> { + GcCell::allocate( + mc, + Self { + parent: scope_stack, + class: ScopeClass::GlobalOrClosure, + values: object, + }, + ) + } + + /// Construct a with scope to be used as the scope during a with block. + /// + /// A with block adds an object to the top of the scope chain, so unqualified + /// references will try to resolve on that object first. + pub fn push_with( + scope_stack: Option>>, + with_object: Object<'gc>, + mc: MutationContext<'gc, '_>, + ) -> GcCell<'gc, Self> { + GcCell::allocate( + mc, + Scope { + parent: scope_stack, + class: ScopeClass::With, + values: with_object, + }, + ) + } + + /// Construct a closure scope to be used as the scope stack when invoking a + /// function. + /// + /// This function filters With scopes from the scope chain. If all scopes + /// are filtered, this function returns None, representing an empty scope + /// stack. + pub fn new_closure_scope( + mut parent: GcCell<'gc, Self>, + mc: MutationContext<'gc, '_>, + ) -> Option> { + let mut bottom_scope = None; + let mut top_scope: Option> = None; + + loop { + if parent.read().class != ScopeClass::With { + let next_scope = GcCell::allocate( + mc, + Self { + parent: None, + class: parent.read().class, + values: parent.read().values, + }, + ); + + if bottom_scope.is_none() { + bottom_scope = Some(next_scope); + } + + if let Some(ref scope) = top_scope { + scope.write(mc).parent = Some(next_scope); + } + + top_scope = Some(next_scope); + } + + let grandparent = parent.read().parent; + if let Some(grandparent) = grandparent { + parent = grandparent; + } else { + break; + } + } + + bottom_scope + } + + /// Construct an arbitrary scope + pub fn new( + parent: GcCell<'gc, Self>, + class: ScopeClass, + with_object: Object<'gc>, + ) -> Scope<'gc> { + Scope { + parent: Some(parent), + class, + values: with_object, + } + } + + /// Returns a reference to the current local scope object. + pub fn locals(&self) -> &Object<'gc> { + &self.values + } + + /// Returns a reference to the current local scope object for mutation. + #[allow(dead_code)] + pub fn locals_mut(&mut self) -> &mut Object<'gc> { + &mut self.values + } + + /// Returns a reference to the parent scope object. + pub fn parent(&self) -> Option>> { + match self.parent { + Some(ref p) => Some(p.read()), + None => None, + } + } + + /// Resolve a particular value in the scope chain. + /// + /// Because scopes are object chains, the same rules for `Object::get` + /// still apply here. This function is allowed to yield `None` to indicate + /// that the result will be calculated on the AVM stack. + pub fn resolve( + &self, + name: &QName, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + ) -> Result, Error> { + if self.locals().has_property(name) { + return self.locals().get_property(name, avm, context); + } + if let Some(scope) = self.parent() { + return scope.resolve(name, avm, context, this); + } + + //TODO: Should undefined variables halt execution? + Ok(Value::Undefined.into()) + } + + /// Check if a particular property in the scope chain is defined. + pub fn is_defined(&self, name: &QName) -> bool { + if self.locals().has_property(name) { + return true; + } + + if let Some(scope) = self.parent() { + return scope.is_defined(name); + } + + false + } + + /// Update a particular value in the scope chain. + /// + /// Traverses the scope chain in search of a value. If it's found, it's overwritten. + /// The traversal stops at Target scopes, which represents the movie clip timeline + /// the code is executing in. + /// If the value is not found, it is defined on this Target scope. + pub fn set( + &self, + name: &QName, + value: Value<'gc>, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + ) -> Result<(), Error> { + if self.locals().has_property(name) && self.locals().is_property_overwritable(name) { + // Value found on this object, so overwrite it. + // Or we've hit the executing movie clip, so create it here. + self.locals().set_property(name, value, avm, context) + } else if let Some(scope) = self.parent() { + // Traverse the scope chain in search of the value. + scope.set(name, value, avm, context, this) + } else { + // This probably shouldn't happen -- all AVM2 code runs in reference to some movieclip, + // so we should always have a movieclip scope. + // Define on the top-level scope. + debug_assert!(false, "Scope::set: No top-level movie clip scope"); + self.locals().set_property(name, value, avm, context) + } + } + + /// Set a particular value in the locals for this scope. + /// + /// By convention, the locals for a given function are always defined as + /// stored (e.g. not virtual) properties on the lowest object in the scope + /// chain. As a result, this function always force sets a property on the + /// local object and does not traverse the scope chain. + pub fn define(&self, name: &QName, value: impl Into>, mc: MutationContext<'gc, '_>) { + self.locals() + .define_value(mc, name, value.into(), EnumSet::empty()); + } + + /// Delete a value from scope + pub fn delete(&self, name: &QName, mc: MutationContext<'gc, '_>) -> bool { + if self.locals().has_property(name) { + return self.locals().delete(mc, name); + } + + if let Some(scope) = self.parent() { + return scope.delete(name, mc); + } + + false + } +} diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 0a45da222a59..6a452571125b 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -1,9 +1,9 @@ //! Default AVM2 object impl use crate::avm2::names::QName; -use crate::avm2::object::{ObjectPtr, TObject}; +use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::value::Value; -use gc_arena::{Collect, GcCell}; +use gc_arena::{Collect, GcCell, MutationContext}; use std::collections::HashMap; use std::fmt::Debug; @@ -24,3 +24,15 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.as_ptr() as *const ObjectPtr } } + +impl<'gc> ScriptObject<'gc> { + pub fn bare_object(mc: MutationContext<'gc, '_>) -> Object<'gc> { + ScriptObject(GcCell::allocate( + mc, + ScriptObjectData { + values: HashMap::new(), + }, + )) + .into() + } +} From 0ff1ba7120299d780c80919127bdc2980853f6d3 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 10 Feb 2020 23:28:05 -0500 Subject: [PATCH 018/189] Multiname resolution is another object method. --- core/src/avm2/object.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index ea2ca29dca6c..e89d3a9da767 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1,7 +1,7 @@ //! AVM2 objects. use crate::avm2::function::FunctionObject; -use crate::avm2::names::QName; +use crate::avm2::names::{Multiname, QName}; use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; @@ -54,6 +54,12 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy Ok(()) } + /// Resolve a multiname into a single QName, if any of the namespaces + /// match. + fn resolve_multiname(self, _multiname: &Multiname) -> Option { + None + } + /// Indicates whether or not a property exists on an object. fn has_property(self, _name: &QName) -> bool { false @@ -73,7 +79,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Delete a named property from the object. /// /// Returns false if the property cannot be deleted. - fn delete(&self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool { + fn delete(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool { false } From 12223d524a5710be4fcf80f2d8924c5e0547c0d9 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 10 Feb 2020 23:28:46 -0500 Subject: [PATCH 019/189] Add support methods in `Scope` to support opcodes that traverse the scope chain. --- core/src/avm2/scope.rs | 46 +++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index e8cbf6619a43..a68dd96a9eb2 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -1,9 +1,8 @@ //! Represents AVM2 scope chain resolution. -use crate::avm2::names::QName; +use crate::avm2::names::{Multiname, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::return_value::ReturnValue; -use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; @@ -148,21 +147,42 @@ impl<'gc> Scope<'gc> { } } + /// Find an object that contains a given property in the scope stack. + pub fn find( + &self, + name: &Multiname, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + ) -> Result>, Error> { + if let Some(qname) = self.locals().resolve_multiname(name) { + if self.locals().has_property(&qname) { + return Ok(Some(*self.locals())); + } + } + + if let Some(scope) = self.parent() { + return scope.find(name, avm, context, this); + } + + //TODO: This should actually be an error. + Ok(None) + } + /// Resolve a particular value in the scope chain. - /// - /// Because scopes are object chains, the same rules for `Object::get` - /// still apply here. This function is allowed to yield `None` to indicate - /// that the result will be calculated on the AVM stack. pub fn resolve( &self, - name: &QName, + name: &Multiname, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result, Error> { - if self.locals().has_property(name) { - return self.locals().get_property(name, avm, context); + if let Some(qname) = self.locals().resolve_multiname(name) { + if self.locals().has_property(&qname) { + return self.locals().get_property(&qname, avm, context); + } } + if let Some(scope) = self.parent() { return scope.resolve(name, avm, context, this); } @@ -226,9 +246,11 @@ impl<'gc> Scope<'gc> { } /// Delete a value from scope - pub fn delete(&self, name: &QName, mc: MutationContext<'gc, '_>) -> bool { - if self.locals().has_property(name) { - return self.locals().delete(mc, name); + pub fn delete(&self, name: &Multiname, mc: MutationContext<'gc, '_>) -> bool { + if let Some(qname) = self.locals().resolve_multiname(name) { + if self.locals().has_property(&qname) { + return self.locals().delete(mc, &qname); + } } if let Some(scope) = self.parent() { From 3c8035f871cc995d4ef453c0c1cab862b9eef5bc Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 10 Feb 2020 23:29:00 -0500 Subject: [PATCH 020/189] clippy pls --- core/src/avm2/names.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 2640501c2679..3261bfa2b829 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -129,10 +129,7 @@ impl Multiname { AbcMultiname::RTQNameL | AbcMultiname::RTQNameLA => { let ns = avm.pop().as_namespace()?.clone(); let name = avm.pop().as_string()?.clone(); - Self { - ns: vec![ns], - name: name, - } + Self { ns: vec![ns], name } } AbcMultiname::Multiname { namespace_set, From 60c16b0a60dbd00d3080fb3b9376f21ea2308239 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 10 Feb 2020 23:58:15 -0500 Subject: [PATCH 021/189] Implement `findproperty`, `findpropstrict`, and `getlex`; which are necessary for interacting with global scope. --- core/src/avm2.rs | 74 +++++++++++++++++++++++++++++++++++-- core/src/avm2/activation.rs | 4 ++ core/src/avm2/names.rs | 6 ++- core/src/avm2/scope.rs | 6 +-- 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index c1afecdba6ed..2aed9904a870 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,8 +1,9 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::Activation; -use crate::avm2::names::Namespace; -use crate::avm2::object::TObject; +use crate::avm2::names::{Multiname, Namespace}; +use crate::avm2::object::{Object, TObject}; +use crate::avm2::return_value::ReturnValue; use crate::avm2::value::Value; use crate::context::UpdateContext; use crate::tag_utils::SwfSlice; @@ -10,7 +11,9 @@ use gc_arena::{Collect, GcCell}; use std::io::Cursor; use std::rc::Rc; use swf::avm2::read::Reader; -use swf::avm2::types::{AbcFile, Index, MethodBody, Namespace as AbcNamespace, Op}; +use swf::avm2::types::{ + AbcFile, Index, MethodBody, Multiname as AbcMultiname, Namespace as AbcNamespace, Op, +}; use swf::read::SwfRead; mod activation; @@ -302,6 +305,11 @@ impl<'gc> Avm2<'gc> { Namespace::from_abc_namespace(&self.current_abc().unwrap(), index) } + /// Retrieve a namespace from the current constant pool. + fn pool_multiname(&mut self, index: Index) -> Result { + Multiname::from_abc_multiname(&self.current_abc().unwrap(), index, self) + } + /// Run a single action from a given action reader. pub fn do_next_opcode( &mut self, @@ -329,6 +337,9 @@ impl<'gc> Avm2<'gc> { Op::Call { num_args } => self.op_call(context, num_args), Op::ReturnValue => self.op_return_value(context), Op::ReturnVoid => self.op_return_void(context), + 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), _ => self.unknown_op(op), }; @@ -446,4 +457,61 @@ impl<'gc> Avm2<'gc> { fn op_return_void(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { self.retire_stack_frame(context, Value::Undefined) } + + fn op_find_property( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result<(), Error> { + let multiname = self.pool_multiname(index)?; + 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, + ) -> Result<(), Error> { + let multiname = self.pool_multiname(index)?; + let found: Result, 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, + ) -> Result<(), Error> { + //TODO: getlex does not allow runtime multinames according to spec. + let multiname = self.pool_multiname(index)?; + let found: Result, Error> = + if let Some(scope) = self.current_stack_frame().unwrap().read().scope() { + Ok(scope.read().resolve(&multiname, self, context)?) + } else { + Err("No objects exist on scope".into()) + }; + let result: Value<'gc> = found?.resolve(self, context)?; + + self.push(result); + + Ok(()) + } } diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 7caa1c1ca8ab..620ab677ecc6 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -158,6 +158,10 @@ impl<'gc> Activation<'gc> { self.local_registers.read().get(id).cloned() } + pub fn scope(&self) -> Option>> { + self.scope + } + /// Set a local register. /// /// Returns `true` if the set was successful; `false` otherwise diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 3261bfa2b829..2f823eaea991 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -103,7 +103,7 @@ impl Multiname { /// general form of multiname. pub fn from_abc_multiname( file: &AbcFile, - multiname_index: Index, + multiname_index: Index, avm: &mut Avm2<'_>, ) -> Result { let abc_multiname: Result<&AbcMultiname, Error> = file @@ -152,4 +152,8 @@ impl Multiname { } }) } + + pub fn local_name(&self) -> &str { + &self.name + } } diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index a68dd96a9eb2..e304de3ae599 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -153,7 +153,6 @@ impl<'gc> Scope<'gc> { name: &Multiname, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, ) -> Result>, Error> { if let Some(qname) = self.locals().resolve_multiname(name) { if self.locals().has_property(&qname) { @@ -162,7 +161,7 @@ impl<'gc> Scope<'gc> { } if let Some(scope) = self.parent() { - return scope.find(name, avm, context, this); + return scope.find(name, avm, context); } //TODO: This should actually be an error. @@ -175,7 +174,6 @@ impl<'gc> Scope<'gc> { name: &Multiname, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, ) -> Result, Error> { if let Some(qname) = self.locals().resolve_multiname(name) { if self.locals().has_property(&qname) { @@ -184,7 +182,7 @@ impl<'gc> Scope<'gc> { } if let Some(scope) = self.parent() { - return scope.resolve(name, avm, context, this); + return scope.resolve(name, avm, context); } //TODO: Should undefined variables halt execution? From 5e6fc79f42f8624ef9faa82144f6048878f16280 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 11 Feb 2020 14:33:30 -0500 Subject: [PATCH 022/189] Implement `getproperty`, `setproperty` --- core/src/avm2.rs | 43 +++++++++++++++++++++++++++++++++++++++++- core/src/avm2/names.rs | 13 +++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 2aed9904a870..fd5f01b10d78 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,7 +1,7 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::Activation; -use crate::avm2::names::{Multiname, Namespace}; +use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::return_value::ReturnValue; use crate::avm2::value::Value; @@ -337,6 +337,8 @@ impl<'gc> Avm2<'gc> { Op::Call { num_args } => self.op_call(context, num_args), Op::ReturnValue => self.op_return_value(context), Op::ReturnVoid => self.op_return_void(context), + Op::GetProperty { index } => self.op_get_property(context, index), + Op::SetProperty { index } => self.op_set_property(context, index), 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), @@ -458,6 +460,45 @@ impl<'gc> Avm2<'gc> { self.retire_stack_frame(context, Value::Undefined) } + fn op_get_property( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result<(), Error> { + let multiname = self.pool_multiname(index)?; + let object = self.pop().as_object()?; + + let name: Result = object + .resolve_multiname(&multiname) + .ok_or_else(|| format!("Could not resolve property {}", multiname.local_name()).into()); + + let value = object + .get_property(&name?, self, context)? + .resolve(self, context)?; + self.push(value); + + Ok(()) + } + + fn op_set_property( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result<(), Error> { + let value = self.pop(); + let multiname = self.pool_multiname(index)?; + let object = self.pop().as_object()?; + + if let Some(name) = object.resolve_multiname(&multiname) { + object.set_property(&name, value, self, context) + } else { + //TODO: Non-dynamic objects should fail + //TODO: This should only work if the public namespace is present + let name = QName::dynamic_name(multiname.local_name()); + object.set_property(&name, value, self, context) + } + } + fn op_find_property( &mut self, context: &mut UpdateContext<'_, 'gc, '_>, diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 2f823eaea991..6380493a718a 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -48,6 +48,10 @@ impl Namespace { AbcNamespace::Private(idx) => Self::Private(abc_string(file, idx.clone())?), }) } + + pub fn public_namespace() -> Self { + Namespace::Namespace("".to_string()) + } } /// A `QName`, likely "qualified name", consists of a namespace and name string. @@ -66,6 +70,15 @@ pub struct QName { name: String, } +impl QName { + pub fn dynamic_name(local_part: &str) -> Self { + Self { + ns: Namespace::public_namespace(), + name: local_part.to_string(), + } + } +} + /// A `Multiname` consists of a name which could be resolved in one or more /// potential namespaces. /// From 78a1c9a7e3ab807023267afa02401d5fa2c2a6da Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 11 Feb 2020 14:46:47 -0500 Subject: [PATCH 023/189] Implement `pushscope`, `popscope`, and `pushwith`. --- core/src/avm2.rs | 39 +++++++++++++++++++++++++++++++++++++ core/src/avm2/activation.rs | 6 ++++++ core/src/avm2/scope.rs | 4 ++++ 3 files changed, 49 insertions(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index fd5f01b10d78..0dee2ee44302 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -4,6 +4,7 @@ use crate::avm2::activation::Activation; 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::value::Value; use crate::context::UpdateContext; use crate::tag_utils::SwfSlice; @@ -339,6 +340,9 @@ impl<'gc> Avm2<'gc> { Op::ReturnVoid => self.op_return_void(context), Op::GetProperty { index } => self.op_get_property(context, index), Op::SetProperty { index } => self.op_set_property(context, index), + Op::PushScope => self.op_push_scope(context), + Op::PushWith => self.op_push_with(context), + Op::PopScope => self.op_pop_scope(context), 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), @@ -499,6 +503,41 @@ impl<'gc> Avm2<'gc> { } } + 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(()) + } + fn op_find_property( &mut self, context: &mut UpdateContext<'_, 'gc, '_>, diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 620ab677ecc6..965f0d4ea45a 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -158,10 +158,16 @@ impl<'gc> Activation<'gc> { self.local_registers.read().get(id).cloned() } + /// Get the current scope stack. pub fn scope(&self) -> Option>> { self.scope } + /// Set a new scope stack. + pub fn set_scope(&mut self, new_scope: Option>>) { + self.scope = new_scope; + } + /// Set a local register. /// /// Returns `true` if the set was successful; `false` otherwise diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index e304de3ae599..ad9cf07e51ce 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -69,6 +69,10 @@ impl<'gc> Scope<'gc> { ) } + pub fn pop_scope(&self) -> Option>> { + self.parent + } + /// Construct a closure scope to be used as the scope stack when invoking a /// function. /// From d56db0644759ba0d616584cdf35a73a8337543a4 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 11 Feb 2020 18:58:25 -0500 Subject: [PATCH 024/189] Implement `resolve_multiname`, sort of. --- core/src/avm2/names.rs | 11 +++++++++++ core/src/avm2/object.rs | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 6380493a718a..09c5fb0edb39 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -71,6 +71,13 @@ pub struct QName { } impl QName { + pub fn qualified(ns: &Namespace, name: &str) -> Self { + Self { + ns: ns.clone(), + name: name.to_string(), + } + } + pub fn dynamic_name(local_part: &str) -> Self { Self { ns: Namespace::public_namespace(), @@ -166,6 +173,10 @@ impl Multiname { }) } + pub fn namespace_set(&self) -> impl Iterator { + self.ns.iter() + } + pub fn local_name(&self) -> &str { &self.name } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index e89d3a9da767..58ce707374a4 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -56,7 +56,15 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Resolve a multiname into a single QName, if any of the namespaces /// match. - fn resolve_multiname(self, _multiname: &Multiname) -> Option { + fn resolve_multiname(self, multiname: &Multiname) -> Option { + for ns in multiname.namespace_set() { + let qname = QName::qualified(ns, multiname.local_name()); + + if self.has_property(&qname) { + return Some(qname); + } + } + None } From e5142e85e9db3f00fffb6b369961d135d3f40c9a Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 11 Feb 2020 19:28:42 -0500 Subject: [PATCH 025/189] Replace `get_property` and `set_property` with slightly-less-stub impls. --- core/src/avm2/function.rs | 23 ++++++++++++++ core/src/avm2/object.rs | 22 ++++++------- core/src/avm2/script_object.rs | 56 ++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 13 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index ef84e3eb7174..293271a5eb89 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -1,6 +1,7 @@ //! AVM2 executables. use crate::avm2::activation::Activation; +use crate::avm2::names::QName; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObjectData; @@ -91,6 +92,28 @@ pub struct FunctionObjectData<'gc> { } impl<'gc> TObject<'gc> for FunctionObject<'gc> { + fn get_property( + self, + name: &QName, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + self.0.read().base.get_property(name, avm, context) + } + + fn set_property( + self, + name: &QName, + value: Value<'gc>, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result<(), Error> { + self.0 + .write(context.gc_context) + .base + .set_property(name, value, avm, context) + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 58ce707374a4..6ab7c6d2e8c9 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -36,23 +36,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Retrieve a property by it's QName. fn get_property( self, - _name: &QName, - _avm: &mut Avm2<'gc>, - _context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error> { - Ok(Value::Undefined.into()) - } + name: &QName, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error>; /// Set a property by it's QName. fn set_property( self, - _name: &QName, - _value: Value<'gc>, - _avm: &mut Avm2<'gc>, - _context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error> { - Ok(()) - } + name: &QName, + value: Value<'gc>, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result<(), Error>; /// Resolve a multiname into a single QName, if any of the namespaces /// match. diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 6a452571125b..8ebf24d8897a 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -2,7 +2,10 @@ use crate::avm2::names::QName; use crate::avm2::object::{Object, ObjectPtr, TObject}; +use crate::avm2::return_value::ReturnValue; use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; use gc_arena::{Collect, GcCell, MutationContext}; use std::collections::HashMap; use std::fmt::Debug; @@ -20,6 +23,27 @@ pub struct ScriptObjectData<'gc> { } impl<'gc> TObject<'gc> for ScriptObject<'gc> { + fn get_property( + self, + name: &QName, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + self.0.read().get_property(name, avm, context) + } + + fn set_property( + self, + name: &QName, + value: Value<'gc>, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result<(), Error> { + self.0 + .write(context.gc_context) + .set_property(name, value, avm, context) + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } @@ -36,3 +60,35 @@ impl<'gc> ScriptObject<'gc> { .into() } } + +impl<'gc> ScriptObjectData<'gc> { + pub fn get_property( + &self, + name: &QName, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + Ok(self + .values + .get(name) + .cloned() + .unwrap_or(Value::Undefined) + .into()) + } + + pub fn set_property( + &mut self, + name: &QName, + value: Value<'gc>, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result<(), Error> { + if let Some(slot) = self.values.get_mut(name) { + *slot = value; + } else { + self.values.insert(name.clone(), value); + } + + Ok(()) + } +} From 984e701142d090199687535fce58a22b40813610 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 11 Feb 2020 19:42:47 -0500 Subject: [PATCH 026/189] Swap out `has_property`'s stub impl. --- core/src/avm2/function.rs | 4 ++++ core/src/avm2/object.rs | 4 +--- core/src/avm2/script_object.rs | 8 ++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 293271a5eb89..8746a0198c9c 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -114,6 +114,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { .set_property(name, value, avm, context) } + fn has_property(self, name: &QName) -> bool { + self.0.read().base.has_property(name) + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 6ab7c6d2e8c9..4227ff999aa3 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -65,9 +65,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } /// Indicates whether or not a property exists on an object. - fn has_property(self, _name: &QName) -> bool { - false - } + fn has_property(self, _name: &QName) -> bool; /// Indicates whether or not a property exists on an object and is not part /// of the prototype chain. diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 8ebf24d8897a..f6f4845a1b87 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -44,6 +44,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { .set_property(name, value, avm, context) } + fn has_property(self, name: &QName) -> bool { + self.0.read().has_property(name) + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } @@ -91,4 +95,8 @@ impl<'gc> ScriptObjectData<'gc> { Ok(()) } + + pub fn has_property(&self, name: &QName) -> bool { + self.values.get(name).is_some() + } } From 7d75255a1a13358267a6f69d94a67a2001fc52f3 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 12 Feb 2020 14:58:33 -0500 Subject: [PATCH 027/189] Add global scope which is automatically included on all new activations. --- core/src/avm2.rs | 13 +++++++++++-- core/src/avm2/activation.rs | 5 +++-- core/src/avm2/function.rs | 2 +- core/src/avm2/globals.rs | 11 +++++++++++ core/src/player.rs | 2 +- 5 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 core/src/avm2/globals.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 0dee2ee44302..a72f5280ce6e 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -8,7 +8,7 @@ use crate::avm2::scope::Scope; use crate::avm2::value::Value; use crate::context::UpdateContext; use crate::tag_utils::SwfSlice; -use gc_arena::{Collect, GcCell}; +use gc_arena::{Collect, GcCell, MutationContext}; use std::io::Cursor; use std::rc::Rc; use swf::avm2::read::Reader; @@ -19,6 +19,7 @@ use swf::read::SwfRead; mod activation; mod function; +mod globals; mod names; mod object; mod return_value; @@ -48,14 +49,18 @@ pub struct Avm2<'gc> { /// Values currently present on the operand stack. stack: Vec>, + + /// Global scope object. + globals: Object<'gc>, } impl<'gc> Avm2<'gc> { /// Construct a new AVM interpreter. - pub fn new() -> Self { + pub fn new(mc: MutationContext<'gc, '_>) -> Self { Self { stack_frames: Vec::new(), stack: Vec::new(), + globals: globals::construct_global_scope(mc), } } @@ -80,6 +85,10 @@ impl<'gc> Avm2<'gc> { Ok(()) } + pub fn globals(&self) -> Object<'gc> { + self.globals + } + /// Get the current stack frame (`Activation` object). pub fn current_stack_frame(&self) -> Option>> { self.stack_frames.last().copied() diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 965f0d4ea45a..ada84e518c70 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -5,7 +5,7 @@ use crate::avm2::object::Object; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::Error; +use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use gc_arena::{Collect, Gc, GcCell, MutationContext}; use smallvec::SmallVec; @@ -96,6 +96,7 @@ impl<'gc> Activation<'gc> { action: Gc<'gc, Avm2Function>, this: Object<'gc>, arguments: Option>, + avm2: &mut Avm2<'gc>, ) -> Result { let abc = action.abc.clone(); let method_body = abc @@ -115,7 +116,7 @@ impl<'gc> Activation<'gc> { ), return_value: None, local_scope: ScriptObject::bare_object(context.gc_context), - scope: None, + scope: Some(Scope::push_scope(None, avm2.globals(), context.gc_context)), }) } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 8746a0198c9c..c7af70de55ae 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -136,7 +136,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { Executable::Action(a2f) => { let activation = GcCell::allocate( context.gc_context, - Activation::from_action(context, a2f, reciever, None)?, + Activation::from_action(context, a2f, reciever, None, avm)?, ); avm.insert_stack_frame(activation); diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs new file mode 100644 index 000000000000..24119d5c824b --- /dev/null +++ b/core/src/avm2/globals.rs @@ -0,0 +1,11 @@ +//! Global scope built-ins + +use crate::avm2::object::{Object, TObject}; +use crate::avm2::script_object::ScriptObject; +use gc_arena::MutationContext; + +pub fn construct_global_scope<'gc>(mc: MutationContext<'gc, '_>) -> Object<'gc> { + let global_scope = ScriptObject::bare_object(mc); + + global_scope +} diff --git a/core/src/player.rs b/core/src/player.rs index 475a21f3381f..1753b4a1e591 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -247,7 +247,7 @@ impl Player { mouse_hovered_object: None, drag_object: None, avm1: Avm1::new(gc_context, NEWEST_PLAYER_VERSION), - avm2: Avm2::new(), + avm2: Avm2::new(gc_context), action_queue: ActionQueue::new(), load_manager: LoadManager::new(), shared_objects: HashMap::new(), From b12c6e0ff1bd0d13b11496c8b4e296934515f64c Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 12 Feb 2020 18:52:00 -0500 Subject: [PATCH 028/189] Implement closure scope stacks. --- core/src/avm2.rs | 14 ++++++++------ core/src/avm2/activation.rs | 34 +++++++++++++++------------------- core/src/avm2/function.rs | 37 ++++++++++++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 30 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index a72f5280ce6e..6d45250891a8 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -157,19 +157,21 @@ impl<'gc> Avm2<'gc> { &mut UpdateContext<'_, 'gc, '_>, ) -> Result, { - let (frame_cell, action, pc) = { + 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()?; - (frame, frame_ref.action(), frame_ref.pc()) + let method = frame_ref.method(); + let abc = method.abc.as_ref().clone(); + let method_index = method.abc_method; + let method_body_index = method.abc_method_body as usize; + + (abc, frame, method_body_index, frame_ref.pc()) }; - let abc = action.abc.as_ref(); - let method_index = action.abc_method; - let method_body_index = action.abc_method_body as usize; 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" @@ -287,7 +289,7 @@ impl<'gc> Avm2<'gc> { /// Retrieve the current constant pool for the currently executing function. fn current_abc(&self) -> Option> { self.current_stack_frame() - .map(|sf| sf.read().action().abc.clone()) + .map(|sf| sf.read().method().abc.clone()) } /// Retrieve a int from the current constant pool. diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index ada84e518c70..93e67c50dc53 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -1,6 +1,6 @@ //! Activation frames -use crate::avm2::function::Avm2Function; +use crate::avm2::function::{Avm2Function, Avm2MethodEntry}; use crate::avm2::object::Object; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; @@ -9,6 +9,8 @@ use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use gc_arena::{Collect, Gc, GcCell, MutationContext}; use smallvec::SmallVec; +use std::rc::Rc; +use swf::avm2::types::AbcFile; /// Represents a particular register set. /// @@ -52,8 +54,8 @@ impl<'gc> RegisterSet<'gc> { #[derive(Clone, Collect)] #[collect(no_drop)] pub struct Activation<'gc> { - /// The function being executed. - action: Gc<'gc, Avm2Function>, + /// The AVM method entry we're executing code out of. + method: Avm2MethodEntry, /// The current location of the instruction stream being executed. pc: usize, @@ -93,30 +95,24 @@ pub struct Activation<'gc> { impl<'gc> Activation<'gc> { pub fn from_action( context: &mut UpdateContext<'_, 'gc, '_>, - action: Gc<'gc, Avm2Function>, + action: &Avm2Function<'gc>, this: Object<'gc>, arguments: Option>, - avm2: &mut Avm2<'gc>, ) -> Result { - let abc = action.abc.clone(); - let method_body = abc - .method_bodies - .get(action.abc_method_body as usize) - .ok_or_else(|| format!("Method body {} does not exist", action.abc_method_body))?; + let method = action.method.clone(); + let scope = action.scope; + let num_locals = method.body().num_locals; Ok(Self { - action, + method, pc: 0, this, arguments, is_executing: false, - local_registers: GcCell::allocate( - context.gc_context, - RegisterSet::new(method_body.num_locals), - ), + local_registers: GcCell::allocate(context.gc_context, RegisterSet::new(num_locals)), return_value: None, local_scope: ScriptObject::bare_object(context.gc_context), - scope: Some(Scope::push_scope(None, avm2.globals(), context.gc_context)), + scope: scope, }) } @@ -139,9 +135,9 @@ impl<'gc> Activation<'gc> { self.is_executing = false; } - /// Obtain a reference to the function being executed. - pub fn action(&self) -> Gc<'gc, Avm2Function> { - self.action + /// Obtain a reference to the method being executed. + pub fn method(&self) -> &Avm2MethodEntry { + &self.method } /// Get the PC value. diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index c7af70de55ae..78cd47948128 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -4,6 +4,7 @@ use crate::avm2::activation::Activation; use crate::avm2::names::QName; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::return_value::ReturnValue; +use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObjectData; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; @@ -11,7 +12,7 @@ use crate::context::UpdateContext; use gc_arena::{Collect, CollectionContext, Gc, GcCell}; use std::fmt; use std::rc::Rc; -use swf::avm2::types::AbcFile; +use swf::avm2::types::{AbcFile, Method as AbcMethod, MethodBody as AbcMethodBody}; /// Represents a function defined in Ruffle's code. /// @@ -34,10 +35,10 @@ pub type NativeFunction<'gc> = fn( &[Value<'gc>], ) -> Result, Error>; -/// Represents an AVM2 function. +/// Represents a reference to an AVM2 method and body. #[derive(Collect, Clone, Debug)] #[collect(require_static)] -pub struct Avm2Function { +pub struct Avm2MethodEntry { /// The ABC file this function was defined in. pub abc: Rc, @@ -48,11 +49,37 @@ pub struct Avm2Function { pub abc_method_body: u32, } +impl Avm2MethodEntry { + /// Get a reference to the ABC method entry this refers to. + pub fn method(&self) -> &AbcMethod { + self.abc.methods.get(self.abc_method as usize).unwrap() + } + + /// Get a reference to the ABC method body entry this refers to. + pub fn body(&self) -> &AbcMethodBody { + self.abc + .method_bodies + .get(self.abc_method_body as usize) + .unwrap() + } +} + +/// Represents an AVM2 function. +#[derive(Collect, Clone, Debug)] +#[collect(no_drop)] +pub struct Avm2Function<'gc> { + /// The AVM method entry used to create this function. + pub method: Avm2MethodEntry, + + /// Closure scope stack at time of creation + pub scope: Option>>, +} + /// Represents code that can be executed by some means. #[derive(Clone)] pub enum Executable<'gc> { Native(NativeFunction<'gc>), - Action(Gc<'gc, Avm2Function>), + Action(Avm2Function<'gc>), } unsafe impl<'gc> Collect for Executable<'gc> { @@ -136,7 +163,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { Executable::Action(a2f) => { let activation = GcCell::allocate( context.gc_context, - Activation::from_action(context, a2f, reciever, None, avm)?, + Activation::from_action(context, &a2f, reciever, None)?, ); avm.insert_stack_frame(activation); From cf490bedfbcc22f6e8bde80680bb9aabd248af64 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 12 Feb 2020 22:47:56 -0500 Subject: [PATCH 029/189] Unstub `proto`. --- core/src/avm2/function.rs | 4 ++++ core/src/avm2/object.rs | 4 +--- core/src/avm2/script_object.rs | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 78cd47948128..d0fa0fafd374 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -145,6 +145,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.has_property(name) } + fn proto(&self) -> Option> { + self.0.read().base.proto() + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 4227ff999aa3..bbeea6cbdafe 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -90,9 +90,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// The proto is another object used to resolve methods across a class of /// multiple objects. It should also be accessible as `__proto__` from /// `get`. - fn proto(&self) -> Option> { - None - } + fn proto(&self) -> Option>; /// Define a value on an object. /// diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index f6f4845a1b87..e625632217fe 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -20,6 +20,9 @@ pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>); pub struct ScriptObjectData<'gc> { /// Properties stored on this object. values: HashMap>, + + /// Implicit prototype (or declared base class) of this script object. + proto: Option>, } impl<'gc> TObject<'gc> for ScriptObject<'gc> { @@ -48,17 +51,26 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().has_property(name) } + fn proto(&self) -> Option> { + self.0.read().proto + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } } impl<'gc> ScriptObject<'gc> { + /// Construct a bare object with no base class. + /// + /// This is *not* the same thing as an object literal, which actually does + /// have a base class: `Object`. pub fn bare_object(mc: MutationContext<'gc, '_>) -> Object<'gc> { ScriptObject(GcCell::allocate( mc, ScriptObjectData { values: HashMap::new(), + proto: None, }, )) .into() @@ -99,4 +111,8 @@ impl<'gc> ScriptObjectData<'gc> { pub fn has_property(&self, name: &QName) -> bool { self.values.get(name).is_some() } + + pub fn proto(&self) -> Option> { + self.proto + } } From eb0c9dcaec3b3b68ecc65bf06f7b4b2d7acf5d6a Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 13 Feb 2020 23:33:24 -0500 Subject: [PATCH 030/189] Allow constructing a function around a particular class definition. I'm writing all this code assuming that classes and traits are syntactic sugar around ES3-style prototype chains on function objects. Hence, `FunctionObject` is still our workhorse object type for implementing typing. --- core/src/avm2/activation.rs | 2 +- core/src/avm2/function.rs | 114 ++++++++++++++++++++++++++++++++- core/src/avm2/script_object.rs | 16 ++--- 3 files changed, 121 insertions(+), 11 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 93e67c50dc53..3f3d9af9d9bb 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -112,7 +112,7 @@ impl<'gc> Activation<'gc> { local_registers: GcCell::allocate(context.gc_context, RegisterSet::new(num_locals)), return_value: None, local_scope: ScriptObject::bare_object(context.gc_context), - scope: scope, + scope, }) } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index d0fa0fafd374..d49840dc557d 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -9,10 +9,13 @@ use crate::avm2::script_object::ScriptObjectData; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use gc_arena::{Collect, CollectionContext, Gc, GcCell}; +use gc_arena::{Collect, CollectionContext, GcCell, MutationContext}; use std::fmt; use std::rc::Rc; -use swf::avm2::types::{AbcFile, Method as AbcMethod, MethodBody as AbcMethodBody}; +use swf::avm2::types::{ + AbcFile, Class as AbcClass, Index, Instance as AbcInstance, Method as AbcMethod, + MethodBody as AbcMethodBody, +}; /// Represents a function defined in Ruffle's code. /// @@ -50,6 +53,31 @@ pub struct Avm2MethodEntry { } impl Avm2MethodEntry { + /// Construct an `Avm2MethodEntry` from an `AbcFile` and method index. + /// + /// The method body index will be determined by searching through the ABC + /// for a matching method. If none exists, this function returns `None`. + pub fn from_method_index(abc: Rc, abc_method: Index) -> Option { + if abc.methods.get(abc_method.0 as usize).is_some() { + for (index, method_body) in abc.method_bodies.iter().enumerate() { + if method_body.method.0 == abc_method.0 { + return Some(Self { + abc, + abc_method: abc_method.0, + abc_method_body: index as u32, + }); + } + } + } + + None + } + + /// Get the underlying ABC file. + pub fn abc(&self) -> Rc { + self.abc.clone() + } + /// Get a reference to the ABC method entry this refers to. pub fn method(&self) -> &AbcMethod { self.abc.methods.get(self.abc_method as usize).unwrap() @@ -75,6 +103,12 @@ pub struct Avm2Function<'gc> { pub scope: Option>>, } +impl<'gc> Avm2Function<'gc> { + pub fn from_method(method: Avm2MethodEntry, scope: Option>>) -> Self { + Self { method, scope } + } +} + /// Represents code that can be executed by some means. #[derive(Clone)] pub enum Executable<'gc> { @@ -103,6 +137,54 @@ impl<'gc> fmt::Debug for Executable<'gc> { } } +impl<'gc> From> for Executable<'gc> { + fn from(nf: NativeFunction<'gc>) -> Self { + Self::Native(nf) + } +} + +impl<'gc> From> for Executable<'gc> { + fn from(a2f: Avm2Function<'gc>) -> Self { + Self::Action(a2f) + } +} + +/// Represents a reference to an AVM2 class. +/// +/// For some reason, this comes in two parts, one for static properties (called +/// the "class") and one for dynamic properties (called the "instance", even +/// though it really defines what ES3/AS2 would call a prototype) +#[derive(Collect, Clone, Debug)] +#[collect(require_static)] +pub struct Avm2ClassEntry { + /// The ABC file this function was defined in. + pub abc: Rc, + + /// The ABC class (used to define static properties) + pub abc_class: u32, + + /// The ABC instance (used to define both instance properties as well as + /// "prototype" methods) + pub abc_instance: u32, +} + +impl Avm2ClassEntry { + /// Get the underlying ABC file. + pub fn abc(&self) -> Rc { + self.abc.clone() + } + + /// Get a reference to the ABC class entry this refers to. + pub fn class(&self) -> &AbcClass { + self.abc.classes.get(self.abc_class as usize).unwrap() + } + + /// Get a reference to the ABC class instance entry this refers to. + pub fn instance(&self) -> &AbcInstance { + self.abc.instances.get(self.abc_instance as usize).unwrap() + } +} + /// An Object which can be called to execute it's function code. #[derive(Collect, Debug, Clone, Copy)] #[collect(no_drop)] @@ -116,6 +198,34 @@ pub struct FunctionObjectData<'gc> { /// Executable code exec: Executable<'gc>, + + /// The class that defined this function object, if any. + class: Option, +} + +impl<'gc> FunctionObject<'gc> { + /// Construct a class from an ABC class/instance pair. + /// + /// If the initializer method cannot be found, this function returns None. + pub fn from_abc_class( + mc: MutationContext<'gc, '_>, + class: Avm2ClassEntry, + ) -> Option> { + let initializer_index = class.class().init_method.clone(); + let initializer = Avm2MethodEntry::from_method_index(class.abc(), initializer_index)?; + + Some( + FunctionObject(GcCell::allocate( + mc, + FunctionObjectData { + base: ScriptObjectData::base_new(None), + exec: Avm2Function::from_method(initializer, None).into(), + class: Some(class), + }, + )) + .into(), + ) + } } impl<'gc> TObject<'gc> for FunctionObject<'gc> { diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index e625632217fe..97f0772eb02b 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -66,18 +66,18 @@ impl<'gc> ScriptObject<'gc> { /// This is *not* the same thing as an object literal, which actually does /// have a base class: `Object`. pub fn bare_object(mc: MutationContext<'gc, '_>) -> Object<'gc> { - ScriptObject(GcCell::allocate( - mc, - ScriptObjectData { - values: HashMap::new(), - proto: None, - }, - )) - .into() + ScriptObject(GcCell::allocate(mc, ScriptObjectData::base_new(None))).into() } } impl<'gc> ScriptObjectData<'gc> { + pub fn base_new(proto: Option>) -> Self { + ScriptObjectData { + values: HashMap::new(), + proto, + } + } + pub fn get_property( &self, name: &QName, From 12e9fbbffb4ef12835d5c6b3d0cce368498c49f6 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 14 Feb 2020 20:30:19 -0500 Subject: [PATCH 031/189] Impl virtual property slots --- core/src/avm2.rs | 1 + core/src/avm2/function.rs | 45 ++++++---- core/src/avm2/object.rs | 11 +-- core/src/avm2/property.rs | 151 +++++++++++++++++++++++++++++++++ core/src/avm2/script_object.rs | 30 ++++--- 5 files changed, 200 insertions(+), 38 deletions(-) create mode 100644 core/src/avm2/property.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 6d45250891a8..0b25143cac5d 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -22,6 +22,7 @@ mod function; mod globals; mod names; mod object; +mod property; mod return_value; mod scope; mod script_object; diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index d49840dc557d..a8df87c044de 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -125,6 +125,29 @@ unsafe impl<'gc> Collect for Executable<'gc> { } } +impl<'gc> Executable<'gc> { + pub fn exec( + &self, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + reciever: Object<'gc>, + arguments: &[Value<'gc>], + ) -> Result, Error> { + match self { + Executable::Native(nf) => nf(avm, context, reciever, arguments), + Executable::Action(a2f) => { + let activation = GcCell::allocate( + context.gc_context, + Activation::from_action(context, &a2f, reciever, None)?, + ); + + avm.insert_stack_frame(activation); + Ok(activation.into()) + } + } + } +} + impl<'gc> fmt::Debug for Executable<'gc> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -235,7 +258,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { - self.0.read().base.get_property(name, avm, context) + self.0 + .read() + .base + .get_property(name, avm, context, self.into()) } fn set_property( @@ -248,7 +274,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0 .write(context.gc_context) .base - .set_property(name, value, avm, context) + .set_property(name, value, avm, context, self.into()) } fn has_property(self, name: &QName) -> bool { @@ -270,19 +296,6 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { - let exec = self.0.read().exec.clone(); - - match exec { - Executable::Native(nf) => nf(avm, context, reciever, arguments), - Executable::Action(a2f) => { - let activation = GcCell::allocate( - context.gc_context, - Activation::from_action(context, &a2f, reciever, None)?, - ); - - avm.insert_stack_frame(activation); - Ok(activation.into()) - } - } + self.0.read().exec.exec(avm, context, reciever, arguments) } } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index bbeea6cbdafe..bb0ed268b091 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -2,6 +2,7 @@ use crate::avm2::function::FunctionObject; use crate::avm2::names::{Multiname, QName}; +use crate::avm2::property::Attribute; use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; @@ -12,16 +13,6 @@ use gc_arena::{Collect, MutationContext}; use ruffle_macros::enum_trait_object; use std::fmt::Debug; -/// Attributes of properties in the AVM runtime. -/// -/// TODO: Replace with AVM2 properties for traits -#[derive(EnumSetType, Debug)] -pub enum Attribute { - DontEnum, - DontDelete, - ReadOnly, -} - /// Represents an object that can be directly interacted with by the AVM2 /// runtime. #[enum_trait_object( diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs new file mode 100644 index 000000000000..3df8622181b7 --- /dev/null +++ b/core/src/avm2/property.rs @@ -0,0 +1,151 @@ +//! Property data structures + +use self::Attribute::*; +use crate::avm2::function::Executable; +use crate::avm2::object::Object; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use enumset::{EnumSet, EnumSetType}; +use gc_arena::{Collect, CollectionContext}; +use std::mem::replace; + +/// Attributes of properties in the AVM runtime. +/// +/// TODO: Replace with AVM2 properties for traits +#[derive(EnumSetType, Debug)] +pub enum Attribute { + DontEnum, + DontDelete, + ReadOnly, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug)] +pub enum Property<'gc> { + Virtual { + get: Executable<'gc>, + set: Option>, + attributes: EnumSet, + }, + Stored { + value: Value<'gc>, + attributes: EnumSet, + }, +} + +unsafe impl<'gc> Collect for Property<'gc> { + fn trace(&self, cc: CollectionContext) { + match self { + Property::Virtual { get, set, .. } => { + get.trace(cc); + set.trace(cc); + } + Property::Stored { value, .. } => value.trace(cc), + } + } +} + +impl<'gc> Property<'gc> { + /// Convert a value into a dynamic property. + pub fn new_dynamic_property(value: impl Into>) -> Self { + Property::Stored { + value: value.into(), + attributes: EnumSet::empty(), + } + } + + /// Get the value of a property slot. + /// + /// This function yields `ReturnValue` because some properties may be + /// user-defined. + pub fn get( + &self, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + ) -> Result, Error> { + match self { + Property::Virtual { get, .. } => get.exec(avm, context, this, &[]), + Property::Stored { value, .. } => Ok(value.to_owned().into()), + } + } + + /// Set a property slot. + /// + /// This function returns `true` if the set has completed, or `false` if + /// it has not yet occured. If `false`, and you need to run code after the + /// set has occured, you must recursively execute the top-most frame via + /// `run_current_frame`. + pub fn set( + &mut self, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + new_value: impl Into>, + ) -> Result { + match self { + Property::Virtual { set, .. } => { + if let Some(function) = set { + let return_value = function.exec(avm, context, this, &[new_value.into()])?; + Ok(return_value.is_immediate()) + } else { + Ok(true) + } + } + Property::Stored { + value, attributes, .. + } => { + if !attributes.contains(ReadOnly) { + replace::>(value, new_value.into()); + } + + Ok(true) + } + } + } + + /// List this property's attributes. + pub fn attributes(&self) -> EnumSet { + match self { + Property::Virtual { attributes, .. } => *attributes, + Property::Stored { attributes, .. } => *attributes, + } + } + + /// Re-define this property's attributes. + pub fn set_attributes(&mut self, new_attributes: EnumSet) { + match self { + Property::Virtual { + ref mut attributes, .. + } => *attributes = new_attributes, + Property::Stored { + ref mut attributes, .. + } => *attributes = new_attributes, + } + } + + pub fn can_delete(&self) -> bool { + match self { + Property::Virtual { attributes, .. } => !attributes.contains(DontDelete), + Property::Stored { attributes, .. } => !attributes.contains(DontDelete), + } + } + + pub fn is_enumerable(&self) -> bool { + match self { + Property::Virtual { attributes, .. } => !attributes.contains(DontEnum), + Property::Stored { attributes, .. } => !attributes.contains(DontEnum), + } + } + + pub fn is_overwritable(&self) -> bool { + match self { + Property::Virtual { + attributes, set, .. + } => !attributes.contains(ReadOnly) && !set.is_none(), + Property::Stored { attributes, .. } => !attributes.contains(ReadOnly), + } + } +} diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 97f0772eb02b..326885e023c4 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -2,6 +2,7 @@ use crate::avm2::names::QName; use crate::avm2::object::{Object, ObjectPtr, TObject}; +use crate::avm2::property::Property; use crate::avm2::return_value::ReturnValue; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; @@ -19,7 +20,7 @@ pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>); #[collect(no_drop)] pub struct ScriptObjectData<'gc> { /// Properties stored on this object. - values: HashMap>, + values: HashMap>, /// Implicit prototype (or declared base class) of this script object. proto: Option>, @@ -32,7 +33,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { - self.0.read().get_property(name, avm, context) + self.0.read().get_property(name, avm, context, self.into()) } fn set_property( @@ -44,7 +45,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { ) -> Result<(), Error> { self.0 .write(context.gc_context) - .set_property(name, value, avm, context) + .set_property(name, value, avm, context, self.into()) } fn has_property(self, name: &QName) -> bool { @@ -83,13 +84,15 @@ impl<'gc> ScriptObjectData<'gc> { name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, ) -> Result, Error> { - Ok(self - .values - .get(name) - .cloned() - .unwrap_or(Value::Undefined) - .into()) + let prop = self.values.get(name); + + if let Some(prop) = prop { + prop.get(avm, context, this) + } else { + Ok(Value::Undefined.into()) + } } pub fn set_property( @@ -98,11 +101,14 @@ impl<'gc> ScriptObjectData<'gc> { value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, ) -> Result<(), Error> { - if let Some(slot) = self.values.get_mut(name) { - *slot = value; + if let Some(prop) = self.values.get_mut(name) { + prop.set(avm, context, this, value)?; } else { - self.values.insert(name.clone(), value); + //TODO: Not all classes are dynamic like this + self.values + .insert(name.clone(), Property::new_dynamic_property(value)); } Ok(()) From 502936f0fedc1e3f08ab5ffa759c8dc30d990cd6 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 17 Feb 2020 19:43:23 -0500 Subject: [PATCH 032/189] Implement non-slot trait properties (Method, Getter, and Setter) --- core/src/avm2/function.rs | 20 ++++++++ core/src/avm2/names.rs | 23 ++++++++++ core/src/avm2/property.rs | 50 +++++++++++++++++++- core/src/avm2/script_object.rs | 84 ++++++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 2 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index a8df87c044de..e57bb2514e50 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -249,6 +249,26 @@ impl<'gc> FunctionObject<'gc> { .into(), ) } + + /// Construct a function from an ABC method and the current closure scope. + pub fn from_abc_method( + mc: MutationContext<'gc, '_>, + method: Avm2MethodEntry, + scope: Option>>, + fn_proto: Object<'gc>, + ) -> Object<'gc> { + let exec = Avm2Function::from_method(method, scope).into(); + + FunctionObject(GcCell::allocate( + mc, + FunctionObjectData { + base: ScriptObjectData::base_new(Some(fn_proto)), + exec, + class: None, + }, + )) + .into() + } } impl<'gc> TObject<'gc> for FunctionObject<'gc> { diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 09c5fb0edb39..ec145f3bfbfd 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -84,6 +84,29 @@ impl QName { name: local_part.to_string(), } } + + /// Pull a `QName` from the multiname pool. + /// + /// This function returns an Err if the multiname does not exist or is not + /// a `QName`. + pub fn from_abc_multiname( + file: &AbcFile, + multiname_index: Index, + ) -> Result { + let abc_multiname: Result<&AbcMultiname, Error> = file + .constant_pool + .multinames + .get(multiname_index.0 as usize) + .ok_or_else(|| format!("Unknown multiname constant {}", multiname_index.0).into()); + + Ok(match abc_multiname? { + AbcMultiname::QName { namespace, name } => Self { + ns: Namespace::from_abc_namespace(file, namespace.clone())?, + name: abc_string(file, name.clone())?, + }, + _ => return Err("Attempted to pull QName from non-QName multiname".into()), + }) + } } /// A `Multiname` consists of a name which could be resolved in one or more diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 3df8622181b7..b2bb25db9410 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -25,7 +25,7 @@ pub enum Attribute { #[derive(Clone, Debug)] pub enum Property<'gc> { Virtual { - get: Executable<'gc>, + get: Option>, set: Option>, attributes: EnumSet, }, @@ -56,6 +56,51 @@ impl<'gc> Property<'gc> { } } + /// Convert a function into a method. + /// + /// This applies ReadOnly/DontDelete to the property. + pub fn new_method(fn_obj: Object<'gc>) -> Self { + Property::Stored { + value: fn_obj.into(), + attributes: Attribute::ReadOnly | Attribute::DontDelete, + } + } + + /// Create a new, unconfigured virtual property item. + pub fn new_virtual() -> Self { + Property::Virtual { + get: None, + set: None, + attributes: Attribute::ReadOnly | Attribute::DontDelete, + } + } + + /// Install a getter into this property. + /// + /// This function errors if attempting to install executables into a + /// non-virtual property. + pub fn install_virtual_getter(&mut self, getter_impl: Executable<'gc>) -> Result<(), Error> { + match self { + Property::Virtual { get, .. } => *get = Some(getter_impl), + Property::Stored { .. } => return Err("Not a virtual property".into()), + }; + + Ok(()) + } + + /// Install a setter into this property. + /// + /// This function errors if attempting to install executables into a + /// non-virtual property. + pub fn install_virtual_setter(&mut self, setter_impl: Executable<'gc>) -> Result<(), Error> { + match self { + Property::Virtual { set, .. } => *set = Some(setter_impl), + Property::Stored { .. } => return Err("Not a virtual property".into()), + }; + + Ok(()) + } + /// Get the value of a property slot. /// /// This function yields `ReturnValue` because some properties may be @@ -67,7 +112,8 @@ impl<'gc> Property<'gc> { this: Object<'gc>, ) -> Result, Error> { match self { - Property::Virtual { get, .. } => get.exec(avm, context, this, &[]), + Property::Virtual { get: Some(get), .. } => get.exec(avm, context, this, &[]), + Property::Virtual { get: None, .. } => Ok(Value::Undefined.into()), Property::Stored { value, .. } => Ok(value.to_owned().into()), } } diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 326885e023c4..a4b6f0144e48 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -1,15 +1,20 @@ //! Default AVM2 object impl +use crate::avm2::function::{ + Avm2ClassEntry, Avm2Function, Avm2MethodEntry, Executable, FunctionObject, +}; use crate::avm2::names::QName; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::property::Property; use crate::avm2::return_value::ReturnValue; +use crate::avm2::scope::Scope; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use gc_arena::{Collect, GcCell, MutationContext}; use std::collections::HashMap; use std::fmt::Debug; +use swf::avm2::types::TraitKind as AbcTraitKind; /// Default implementation of `avm2::Object`. #[derive(Clone, Collect, Debug, Copy)] @@ -69,6 +74,48 @@ impl<'gc> ScriptObject<'gc> { pub fn bare_object(mc: MutationContext<'gc, '_>) -> Object<'gc> { ScriptObject(GcCell::allocate(mc, ScriptObjectData::base_new(None))).into() } + + /// Construct the instance prototype half of a class. + pub fn instance_prototype( + mc: MutationContext<'gc, '_>, + type_entry: Avm2ClassEntry, + base_class: Object<'gc>, + scope: Option>>, + fn_proto: Object<'gc>, + ) -> Result, Error> { + let mut data = ScriptObjectData::base_new(Some(base_class)); + + for trait_entry in type_entry.instance().traits.iter() { + let trait_name = + QName::from_abc_multiname(&type_entry.abc(), trait_entry.name.clone())?; + match &trait_entry.kind { + AbcTraitKind::Method { method, .. } => { + let method = + Avm2MethodEntry::from_method_index(type_entry.abc(), method.clone()) + .unwrap(); + let function = FunctionObject::from_abc_method(mc, method, scope, fn_proto); + data.install_method(trait_name, function); + } + AbcTraitKind::Getter { method, .. } => { + let method = + Avm2MethodEntry::from_method_index(type_entry.abc(), method.clone()) + .unwrap(); + let exec = Avm2Function::from_method(method, scope).into(); + data.install_getter(trait_name, exec)?; + } + AbcTraitKind::Setter { method, .. } => { + let method = + Avm2MethodEntry::from_method_index(type_entry.abc(), method.clone()) + .unwrap(); + let exec = Avm2Function::from_method(method, scope).into(); + data.install_setter(trait_name, exec)?; + } + _ => return Err("".into()), + } + } + + Ok(ScriptObject(GcCell::allocate(mc, data)).into()) + } } impl<'gc> ScriptObjectData<'gc> { @@ -121,4 +168,41 @@ impl<'gc> ScriptObjectData<'gc> { pub fn proto(&self) -> Option> { self.proto } + + /// Install a method into the object. + fn install_method(&mut self, name: QName, function: Object<'gc>) { + self.values.insert(name, Property::new_method(function)); + } + + /// Install a getter into the object. + /// + /// This is a little more complicated than methods, since virtual property + /// slots can be installed in two parts. Thus, we need to support + /// installing them in either order. + fn install_getter(&mut self, name: QName, function: Executable<'gc>) -> Result<(), Error> { + if !self.values.contains_key(&name) { + self.values.insert(name.clone(), Property::new_virtual()); + } + + self.values + .get_mut(&name) + .unwrap() + .install_virtual_getter(function) + } + + /// Install a setter into the object. + /// + /// This is a little more complicated than methods, since virtual property + /// slots can be installed in two parts. Thus, we need to support + /// installing them in either order. + fn install_setter(&mut self, name: QName, function: Executable<'gc>) -> Result<(), Error> { + if !self.values.contains_key(&name) { + self.values.insert(name.clone(), Property::new_virtual()); + } + + self.values + .get_mut(&name) + .unwrap() + .install_virtual_setter(function) + } } From 560900e7089ae0dc5c79b1c76a2a0fc917a6d667 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 17 Feb 2020 22:35:42 -0700 Subject: [PATCH 033/189] ABC files are always pre-loaded. Frame actions are handled as syntactic sugar on top of a `MovieClip` subclass and event handlers. ABC scripts do not live on the normal timeline. --- core/src/avm2.rs | 6 ------ core/src/context.rs | 6 ------ core/src/display_object/movie_clip.rs | 1 - core/src/player.rs | 3 +-- 4 files changed, 1 insertion(+), 15 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 0b25143cac5d..99e5f477508c 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -68,16 +68,10 @@ impl<'gc> Avm2<'gc> { /// Load an ABC file embedded in a `SwfSlice`. /// /// The `SwfSlice` must resolve to the contents of an ABC file. - /// - /// The `preload` flag indicates if the file is being encountered as part - /// of a preloading operation. If false, then this file has actually been - /// encountered as part of normal movie playback and it's final script - /// should be executed. pub fn load_abc( &mut self, abc: SwfSlice, context: &mut UpdateContext<'_, 'gc, '_>, - preload: bool, ) -> Result<(), Error> { let mut read = Reader::new(abc.as_ref()); diff --git a/core/src/context.rs b/core/src/context.rs index 547f6e2ed4bf..f06f41cdf581 100644 --- a/core/src/context.rs +++ b/core/src/context.rs @@ -252,10 +252,6 @@ pub enum ActionType<'gc> { name: String, is_lazy_initialize: bool, abc: SwfSlice, - - /// Whether or not this ABC file was encountered during the preloading - /// step. - preload: bool, }, } @@ -294,13 +290,11 @@ impl fmt::Debug for ActionType<'_> { name, is_lazy_initialize, abc, - preload, } => f .debug_struct("ActionType::DoABC") .field("name", name) .field("is_lazy_initialize", is_lazy_initialize) .field("bytecode", abc) - .field("preload", preload) .finish(), } } diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index ced79a65cf36..7dfa462de012 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -431,7 +431,6 @@ impl<'gc> MovieClip<'gc> { name, is_lazy_initialize, abc: slice, - preload: true, }, false, ); diff --git a/core/src/player.rs b/core/src/player.rs index 1753b4a1e591..4e5406092393 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -872,9 +872,8 @@ impl Player { name, is_lazy_initialize, abc, - preload, } => { - if let Err(e) = avm2.load_abc(abc, context, preload) { + if let Err(e) = avm2.load_abc(abc, context) { log::warn!("Error loading ABC file: {}", e); } } From 35c36a807b460850e3321f894e19bc5afbb5d0b1 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 18 Feb 2020 17:54:39 -0500 Subject: [PATCH 034/189] Always execute the last script when loading an ABC file --- core/src/avm2.rs | 27 +++++++++++++-- core/src/avm2/activation.rs | 67 ++++++++++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 99e5f477508c..72f42f4d6a19 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,6 +1,6 @@ //! ActionScript Virtual Machine 2 (AS3) support -use crate::avm2::activation::Activation; +use crate::avm2::activation::{Activation, Avm2ScriptEntry}; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::return_value::ReturnValue; @@ -14,6 +14,7 @@ use std::rc::Rc; use swf::avm2::read::Reader; use swf::avm2::types::{ AbcFile, Index, MethodBody, Multiname as AbcMultiname, Namespace as AbcNamespace, Op, + Script as AbcScript, }; use swf::read::SwfRead; @@ -75,7 +76,15 @@ impl<'gc> Avm2<'gc> { ) -> Result<(), Error> { let mut read = Reader::new(abc.as_ref()); - let _abc_file = read.read()?; + let abc_file = Rc::new(read.read()?); + + if abc_file.scripts.len() > 0 { + let entrypoint_script: Index = Index::new(abc_file.scripts.len() as u32); + let entrypoint = + Avm2ScriptEntry::from_script_index(abc_file, entrypoint_script).unwrap(); + + self.insert_stack_frame_for_script(context, entrypoint)?; + } Ok(()) } @@ -95,6 +104,20 @@ impl<'gc> Avm2<'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: Avm2ScriptEntry, + ) -> 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). /// /// The given return value will be pushed on the stack if there is a diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 3f3d9af9d9bb..3095a0c62d2a 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -10,7 +10,45 @@ use crate::context::UpdateContext; use gc_arena::{Collect, Gc, GcCell, MutationContext}; use smallvec::SmallVec; use std::rc::Rc; -use swf::avm2::types::AbcFile; +use swf::avm2::types::{AbcFile, Index, Script as AbcScript}; + +/// Represents a reference to an AVM2 script. +#[derive(Collect, Clone, Debug)] +#[collect(require_static)] +pub struct Avm2ScriptEntry { + /// The ABC file this function was defined in. + pub abc: Rc, + + /// The ABC method this function uses. + pub abc_script: u32, +} + +impl Avm2ScriptEntry { + /// Take a script index and ABC file, and produce an `Avm2ScriptEntry`. + /// + /// This function returns `None` if the given script does not exist within + /// the given ABC file. + pub fn from_script_index(abc: Rc, script_index: Index) -> Option { + if abc.scripts.get(script_index.0 as usize).is_some() { + return Some(Self { + abc, + abc_script: script_index.0, + }); + } + + None + } + + /// Get the underlying ABC file. + pub fn abc(&self) -> Rc { + self.abc.clone() + } + + /// Get a reference to the ABC script entry this refers to. + pub fn script(&self) -> &AbcScript { + self.abc.scripts.get(self.abc_script as usize).unwrap() + } +} /// Represents a particular register set. /// @@ -93,6 +131,33 @@ pub struct Activation<'gc> { } impl<'gc> Activation<'gc> { + pub fn from_script( + context: &mut UpdateContext<'_, 'gc, '_>, + script: Avm2ScriptEntry, + global: Object<'gc>, + ) -> Result { + let method: Result = + Avm2MethodEntry::from_method_index(script.abc(), script.script().init_method.clone()) + .ok_or_else(|| { + format!("Script index {} is not a valid script", script.abc_script).into() + }); + let method = method?; + let scope = Some(Scope::push_scope(None, global, context.gc_context)); + let num_locals = method.body().num_locals; + + Ok(Self { + method, + pc: 0, + this: global, + arguments: None, + is_executing: false, + local_registers: GcCell::allocate(context.gc_context, RegisterSet::new(num_locals)), + return_value: None, + local_scope: ScriptObject::bare_object(context.gc_context), + scope, + }) + } + pub fn from_action( context: &mut UpdateContext<'_, 'gc, '_>, action: &Avm2Function<'gc>, From 1945f36dc0ab1d480bed8ba879306cae949615b1 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 18 Feb 2020 20:08:13 -0500 Subject: [PATCH 035/189] When running the initial script, also install it's traits onto the global scope. --- core/src/avm2.rs | 14 ++++++- core/src/avm2/function.rs | 16 +++++++- core/src/avm2/object.rs | 15 ++++++- core/src/avm2/script_object.rs | 74 +++++++++++++++++++++------------- 4 files changed, 89 insertions(+), 30 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 72f42f4d6a19..5c7a7688e3ee 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -78,10 +78,22 @@ impl<'gc> Avm2<'gc> { let abc_file = Rc::new(read.read()?); - if abc_file.scripts.len() > 0 { + if !abc_file.scripts.is_empty() { let entrypoint_script: Index = Index::new(abc_file.scripts.len() as u32); let entrypoint = Avm2ScriptEntry::from_script_index(abc_file, entrypoint_script).unwrap(); + let scope = Scope::push_scope(None, self.globals(), context.gc_context); + + for trait_entry in entrypoint.script().traits.iter() { + //TODO: Actually stick the Function proto here + self.globals.install_trait( + context.gc_context, + entrypoint.abc(), + trait_entry, + Some(scope), + self.globals, + )?; + } self.insert_stack_frame_for_script(context, entrypoint)?; } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index e57bb2514e50..3929a0a24d4e 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -14,7 +14,7 @@ use std::fmt; use std::rc::Rc; use swf::avm2::types::{ AbcFile, Class as AbcClass, Index, Instance as AbcInstance, Method as AbcMethod, - MethodBody as AbcMethodBody, + MethodBody as AbcMethodBody, Trait as AbcTrait, }; /// Represents a function defined in Ruffle's code. @@ -318,4 +318,18 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { ) -> Result, Error> { self.0.read().exec.exec(avm, context, reciever, arguments) } + + fn install_trait( + &mut self, + mc: MutationContext<'gc, '_>, + abc: Rc, + trait_entry: &AbcTrait, + scope: Option>>, + fn_proto: Object<'gc>, + ) -> Result<(), Error> { + self.0 + .write(mc) + .base + .install_trait(mc, abc, trait_entry, scope, fn_proto) + } } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index bb0ed268b091..08355f86bbe7 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -4,14 +4,17 @@ use crate::avm2::function::FunctionObject; use crate::avm2::names::{Multiname, QName}; use crate::avm2::property::Attribute; use crate::avm2::return_value::ReturnValue; +use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use enumset::{EnumSet, EnumSetType}; -use gc_arena::{Collect, MutationContext}; +use gc_arena::{Collect, GcCell, MutationContext}; use ruffle_macros::enum_trait_object; use std::fmt::Debug; +use std::rc::Rc; +use swf::avm2::types::{AbcFile, Trait as AbcTrait}; /// Represents an object that can be directly interacted with by the AVM2 /// runtime. @@ -103,6 +106,16 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy ) { } + /// Install a trait from an ABC file on an object. + fn install_trait( + &mut self, + mc: MutationContext<'gc, '_>, + abc: Rc, + trait_entry: &AbcTrait, + scope: Option>>, + fn_proto: Object<'gc>, + ) -> Result<(), Error>; + /// Call the object. fn call( self, diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index a4b6f0144e48..2d3fe11c8135 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -14,7 +14,8 @@ use crate::context::UpdateContext; use gc_arena::{Collect, GcCell, MutationContext}; use std::collections::HashMap; use std::fmt::Debug; -use swf::avm2::types::TraitKind as AbcTraitKind; +use std::rc::Rc; +use swf::avm2::types::{AbcFile, Trait as AbcTrait, TraitKind as AbcTraitKind}; /// Default implementation of `avm2::Object`. #[derive(Clone, Collect, Debug, Copy)] @@ -64,6 +65,19 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } + + fn install_trait( + &mut self, + mc: MutationContext<'gc, '_>, + abc: Rc, + trait_entry: &AbcTrait, + scope: Option>>, + fn_proto: Object<'gc>, + ) -> Result<(), Error> { + self.0 + .write(mc) + .install_trait(mc, abc, trait_entry, scope, fn_proto) + } } impl<'gc> ScriptObject<'gc> { @@ -86,32 +100,7 @@ impl<'gc> ScriptObject<'gc> { let mut data = ScriptObjectData::base_new(Some(base_class)); for trait_entry in type_entry.instance().traits.iter() { - let trait_name = - QName::from_abc_multiname(&type_entry.abc(), trait_entry.name.clone())?; - match &trait_entry.kind { - AbcTraitKind::Method { method, .. } => { - let method = - Avm2MethodEntry::from_method_index(type_entry.abc(), method.clone()) - .unwrap(); - let function = FunctionObject::from_abc_method(mc, method, scope, fn_proto); - data.install_method(trait_name, function); - } - AbcTraitKind::Getter { method, .. } => { - let method = - Avm2MethodEntry::from_method_index(type_entry.abc(), method.clone()) - .unwrap(); - let exec = Avm2Function::from_method(method, scope).into(); - data.install_getter(trait_name, exec)?; - } - AbcTraitKind::Setter { method, .. } => { - let method = - Avm2MethodEntry::from_method_index(type_entry.abc(), method.clone()) - .unwrap(); - let exec = Avm2Function::from_method(method, scope).into(); - data.install_setter(trait_name, exec)?; - } - _ => return Err("".into()), - } + data.install_trait(mc, type_entry.abc(), trait_entry, scope, fn_proto)?; } Ok(ScriptObject(GcCell::allocate(mc, data)).into()) @@ -169,6 +158,37 @@ impl<'gc> ScriptObjectData<'gc> { self.proto } + pub fn install_trait( + &mut self, + mc: MutationContext<'gc, '_>, + abc: Rc, + trait_entry: &AbcTrait, + scope: Option>>, + fn_proto: Object<'gc>, + ) -> Result<(), Error> { + let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?; + match &trait_entry.kind { + AbcTraitKind::Method { method, .. } => { + let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); + let function = FunctionObject::from_abc_method(mc, method, scope, fn_proto); + self.install_method(trait_name, function); + } + AbcTraitKind::Getter { method, .. } => { + let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); + let exec = Avm2Function::from_method(method, scope).into(); + self.install_getter(trait_name, exec)?; + } + AbcTraitKind::Setter { method, .. } => { + let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); + let exec = Avm2Function::from_method(method, scope).into(); + self.install_setter(trait_name, exec)?; + } + _ => return Err("".into()), + } + + Ok(()) + } + /// Install a method into the object. fn install_method(&mut self, name: QName, function: Object<'gc>) { self.values.insert(name, Property::new_method(function)); From 88957b2b3d387fedf4e9bb50b9d9ed166e6b978b Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 18 Feb 2020 22:26:08 -0500 Subject: [PATCH 036/189] Add stub builtins for Object and Function. These are more-or-less identical to the way we did it in AVM1 (e.g. no fancy player globals file) --- core/src/avm2.rs | 12 ++++-- core/src/avm2/function.rs | 57 ++++++++++++++++++++++++++- core/src/avm2/globals.rs | 64 +++++++++++++++++++++++++++++-- core/src/avm2/globals/function.rs | 50 ++++++++++++++++++++++++ core/src/avm2/globals/object.rs | 34 ++++++++++++++++ core/src/avm2/names.rs | 7 ++++ core/src/avm2/object.rs | 13 ++++++- core/src/avm2/script_object.rs | 35 ++++++++++++++++- 8 files changed, 263 insertions(+), 9 deletions(-) create mode 100644 core/src/avm2/globals/function.rs create mode 100644 core/src/avm2/globals/object.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 5c7a7688e3ee..90f3cdfda1ed 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,6 +1,7 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::{Activation, Avm2ScriptEntry}; +use crate::avm2::globals::SystemPrototypes; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::return_value::ReturnValue; @@ -54,15 +55,21 @@ pub struct Avm2<'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: globals::construct_global_scope(mc), + globals, + system_prototypes, } } @@ -85,13 +92,12 @@ impl<'gc> Avm2<'gc> { let scope = Scope::push_scope(None, self.globals(), context.gc_context); for trait_entry in entrypoint.script().traits.iter() { - //TODO: Actually stick the Function proto here self.globals.install_trait( context.gc_context, entrypoint.abc(), trait_entry, Some(scope), - self.globals, + self.system_prototypes.function, )?; } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 3929a0a24d4e..bf8078248b77 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -1,7 +1,7 @@ //! AVM2 executables. use crate::avm2::activation::Activation; -use crate::avm2::names::QName; +use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; @@ -269,6 +269,48 @@ impl<'gc> FunctionObject<'gc> { )) .into() } + + /// Construct a builtin function object from a Rust function. + pub fn from_builtin( + mc: MutationContext<'gc, '_>, + nf: NativeFunction<'gc>, + fn_proto: Object<'gc>, + ) -> Object<'gc> { + FunctionObject(GcCell::allocate( + mc, + FunctionObjectData { + base: ScriptObjectData::base_new(Some(fn_proto)), + exec: nf.into(), + class: None, + }, + )) + .into() + } + + /// Construct a builtin type from a Rust constructor and prototype. + pub fn from_builtin_constr( + mc: MutationContext<'gc, '_>, + constr: NativeFunction<'gc>, + prototype: Object<'gc>, + fn_proto: Object<'gc>, + ) -> Result, Error> { + let mut base = ScriptObjectData::base_new(Some(fn_proto)); + + base.install_dynamic_property( + QName::new(Namespace::public_namespace(), "prototype"), + prototype.into(), + )?; + + Ok(FunctionObject(GcCell::allocate( + mc, + FunctionObjectData { + base, + exec: constr.into(), + class: None, + }, + )) + .into()) + } } impl<'gc> TObject<'gc> for FunctionObject<'gc> { @@ -332,4 +374,17 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { .base .install_trait(mc, abc, trait_entry, scope, fn_proto) } + + fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) { + self.0.write(mc).base.install_method(name, function) + } + + fn install_dynamic_property( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + value: Value<'gc>, + ) -> Result<(), Error> { + self.0.write(mc).base.install_dynamic_property(name, value) + } } diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 24119d5c824b..52e99f8a0c9e 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -1,11 +1,69 @@ //! Global scope built-ins +use crate::avm2::function::FunctionObject; +use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::script_object::ScriptObject; -use gc_arena::MutationContext; +use gc_arena::{Collect, MutationContext}; -pub fn construct_global_scope<'gc>(mc: MutationContext<'gc, '_>) -> Object<'gc> { - let global_scope = ScriptObject::bare_object(mc); +mod function; +mod object; + +/// This structure represents all system builtins' prototypes. +#[derive(Clone, Collect)] +#[collect(no_drop)] +pub struct SystemPrototypes<'gc> { + pub object: Object<'gc>, + pub function: Object<'gc>, +} + +/// Construct a new global scope. +/// +/// This function returns both the global scope object, as well as all builtin +/// prototypes that other parts of the VM will need to use. +pub fn construct_global_scope<'gc>( + mc: MutationContext<'gc, '_>, +) -> (Object<'gc>, SystemPrototypes<'gc>) { + let mut global_scope = ScriptObject::bare_object(mc); + + let object_proto = ScriptObject::bare_object(mc); + let function_proto = function::create_proto(mc, object_proto); + + object::fill_proto(mc, object_proto, function_proto); + + let system_prototypes = SystemPrototypes { + object: object_proto, + function: function_proto, + }; global_scope + .install_dynamic_property( + mc, + QName::new(Namespace::public_namespace(), "Object"), + FunctionObject::from_builtin_constr( + mc, + object::constructor, + object_proto, + function_proto, + ) + .unwrap() + .into(), + ) + .unwrap(); + global_scope + .install_dynamic_property( + mc, + QName::new(Namespace::public_namespace(), "Function"), + FunctionObject::from_builtin_constr( + mc, + function::constructor, + function_proto, + function_proto, + ) + .unwrap() + .into(), + ) + .unwrap(); + + (global_scope, system_prototypes) } diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs new file mode 100644 index 000000000000..9eb58c8a23d4 --- /dev/null +++ b/core/src/avm2/globals/function.rs @@ -0,0 +1,50 @@ +//! Function builtin and prototype + +use crate::avm2::function::FunctionObject; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::{Object, TObject}; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::script_object::ScriptObject; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::MutationContext; + +/// Implements `Function` +pub fn constructor<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined.into()) +} + +/// Implements `Function.prototype.toString` +fn to_string<'gc>( + _: &mut Avm2<'gc>, + _: &mut UpdateContext<'_, 'gc, '_>, + _: Object<'gc>, + _: &[Value<'gc>], +) -> Result, Error> { + Ok(ReturnValue::Immediate("[type Function]".into())) +} + +/// Partially construct `Function.prototype`. +/// +/// `__proto__` and other cross-linked properties of this object will *not* +/// be defined here. The caller of this function is responsible for linking +/// them in order to obtain a valid ECMAScript `Function` prototype. The +/// returned object is also a bare object, which will need to be linked into +/// the prototype of `Object`. +pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> { + let mut function_proto = ScriptObject::object(gc_context, proto); + + function_proto.install_method( + gc_context, + QName::new(Namespace::public_namespace(), "toString"), + FunctionObject::from_builtin(gc_context, to_string, function_proto), + ); + + function_proto +} diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs new file mode 100644 index 000000000000..9982feaa5912 --- /dev/null +++ b/core/src/avm2/globals/object.rs @@ -0,0 +1,34 @@ +//! Object builtin and prototype + +use crate::avm2::object::Object; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::MutationContext; + +/// Implements `Object` +pub fn constructor<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined.into()) +} + +/// Partially construct `Object.prototype`. +/// +/// `__proto__` and other cross-linked properties of this object will *not* +/// be defined here. The caller of this function is responsible for linking +/// them in order to obtain a valid ECMAScript `Object` prototype. +/// +/// Since Object and Function are so heavily intertwined, this function does +/// not allocate an object to store either proto. Instead, you must allocate +/// bare objects for both and let this function fill Object for you. +pub fn fill_proto<'gc>( + _gc_context: MutationContext<'gc, '_>, + _object_proto: Object<'gc>, + _fn_proto: Object<'gc>, +) { +} diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index ec145f3bfbfd..29b02f047c87 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -71,6 +71,13 @@ pub struct QName { } impl QName { + pub fn new(ns: Namespace, name: &str) -> Self { + Self { + ns, + name: name.to_string(), + } + } + pub fn qualified(ns: &Namespace, name: &str) -> Self { Self { ns: ns.clone(), diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 08355f86bbe7..44512e66246f 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -9,7 +9,7 @@ use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use enumset::{EnumSet, EnumSetType}; +use enumset::EnumSet; use gc_arena::{Collect, GcCell, MutationContext}; use ruffle_macros::enum_trait_object; use std::fmt::Debug; @@ -116,6 +116,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn_proto: Object<'gc>, ) -> Result<(), Error>; + /// Install a method (not necessarily from an ABC file) on an object. + fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>); + + /// Install a dynamic or built-in value property on an object. + fn install_dynamic_property( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + value: Value<'gc>, + ) -> Result<(), Error>; + /// Call the object. fn call( self, diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 2d3fe11c8135..dc895e606890 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -78,6 +78,19 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { .write(mc) .install_trait(mc, abc, trait_entry, scope, fn_proto) } + + fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) { + self.0.write(mc).install_method(name, function) + } + + fn install_dynamic_property( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + value: Value<'gc>, + ) -> Result<(), Error> { + self.0.write(mc).install_dynamic_property(name, value) + } } impl<'gc> ScriptObject<'gc> { @@ -89,6 +102,15 @@ impl<'gc> ScriptObject<'gc> { ScriptObject(GcCell::allocate(mc, ScriptObjectData::base_new(None))).into() } + /// Construct an object with a base class. + pub fn object(mc: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> { + ScriptObject(GcCell::allocate( + mc, + ScriptObjectData::base_new(Some(proto)), + )) + .into() + } + /// Construct the instance prototype half of a class. pub fn instance_prototype( mc: MutationContext<'gc, '_>, @@ -190,7 +212,7 @@ impl<'gc> ScriptObjectData<'gc> { } /// Install a method into the object. - fn install_method(&mut self, name: QName, function: Object<'gc>) { + pub fn install_method(&mut self, name: QName, function: Object<'gc>) { self.values.insert(name, Property::new_method(function)); } @@ -225,4 +247,15 @@ impl<'gc> ScriptObjectData<'gc> { .unwrap() .install_virtual_setter(function) } + + pub fn install_dynamic_property( + &mut self, + name: QName, + value: Value<'gc>, + ) -> Result<(), Error> { + self.values + .insert(name, Property::new_dynamic_property(value)); + + Ok(()) + } } From 1ab4091050f59cd98f7d8a16657ce17933809ba7 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 19 Feb 2020 14:17:33 -0500 Subject: [PATCH 037/189] Implement slots and related opcodes. --- core/src/avm2.rs | 42 ++++++++++++++++++++++++++++++++++ core/src/avm2/function.rs | 13 +++++++++++ core/src/avm2/object.rs | 11 +++++++++ core/src/avm2/property.rs | 20 +++++++++++++++- core/src/avm2/script_object.rs | 40 ++++++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 1 deletion(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 90f3cdfda1ed..e0036695a2bf 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -393,6 +393,10 @@ impl<'gc> Avm2<'gc> { 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), + 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), _ => self.unknown_op(op), }; @@ -641,4 +645,42 @@ impl<'gc> Avm2<'gc> { Ok(()) } + + 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) + } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index bf8078248b77..25ebbf946ec7 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -339,6 +339,19 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { .set_property(name, value, avm, context, self.into()) } + fn get_slot(self, id: u32) -> Result, Error> { + self.0.read().base.get_slot(id) + } + + fn set_slot( + self, + id: u32, + value: Value<'gc>, + mc: MutationContext<'gc, '_>, + ) -> Result<(), Error> { + self.0.write(mc).base.set_slot(id, value, mc) + } + fn has_property(self, name: &QName) -> bool { self.0.read().base.has_property(name) } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 44512e66246f..560337f80f6e 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -44,6 +44,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error>; + /// Retrieve a slot by it's index. + fn get_slot(self, id: u32) -> Result, Error>; + + /// Set a slot by it's index. + fn set_slot( + self, + id: u32, + value: Value<'gc>, + mc: MutationContext<'gc, '_>, + ) -> Result<(), Error>; + /// Resolve a multiname into a single QName, if any of the namespaces /// match. fn resolve_multiname(self, multiname: &Multiname) -> Option { diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index b2bb25db9410..7366f2bc30bb 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -2,7 +2,7 @@ use self::Attribute::*; use crate::avm2::function::Executable; -use crate::avm2::object::Object; +use crate::avm2::object::{Object, TObject}; use crate::avm2::return_value::ReturnValue; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; @@ -33,6 +33,10 @@ pub enum Property<'gc> { value: Value<'gc>, attributes: EnumSet, }, + Slot { + slot_id: u32, + attributes: EnumSet, + }, } unsafe impl<'gc> Collect for Property<'gc> { @@ -43,6 +47,7 @@ unsafe impl<'gc> Collect for Property<'gc> { set.trace(cc); } Property::Stored { value, .. } => value.trace(cc), + Property::Slot { .. } => {} } } } @@ -83,6 +88,7 @@ impl<'gc> Property<'gc> { match self { Property::Virtual { get, .. } => *get = Some(getter_impl), Property::Stored { .. } => return Err("Not a virtual property".into()), + Property::Slot { .. } => return Err("Not a virtual property".into()), }; Ok(()) @@ -96,6 +102,7 @@ impl<'gc> Property<'gc> { match self { Property::Virtual { set, .. } => *set = Some(setter_impl), Property::Stored { .. } => return Err("Not a virtual property".into()), + Property::Slot { .. } => return Err("Not a virtual property".into()), }; Ok(()) @@ -115,6 +122,7 @@ impl<'gc> Property<'gc> { Property::Virtual { get: Some(get), .. } => get.exec(avm, context, this, &[]), Property::Virtual { get: None, .. } => Ok(Value::Undefined.into()), Property::Stored { value, .. } => Ok(value.to_owned().into()), + Property::Slot { slot_id, .. } => this.get_slot(*slot_id).map(|v| v.into()), } } @@ -149,6 +157,9 @@ impl<'gc> Property<'gc> { Ok(true) } + Property::Slot { slot_id, .. } => this + .set_slot(*slot_id, new_value.into(), context.gc_context) + .map(|v| true), } } @@ -157,6 +168,7 @@ impl<'gc> Property<'gc> { match self { Property::Virtual { attributes, .. } => *attributes, Property::Stored { attributes, .. } => *attributes, + Property::Slot { attributes, .. } => *attributes, } } @@ -169,6 +181,9 @@ impl<'gc> Property<'gc> { Property::Stored { ref mut attributes, .. } => *attributes = new_attributes, + Property::Slot { + ref mut attributes, .. + } => *attributes = new_attributes, } } @@ -176,6 +191,7 @@ impl<'gc> Property<'gc> { match self { Property::Virtual { attributes, .. } => !attributes.contains(DontDelete), Property::Stored { attributes, .. } => !attributes.contains(DontDelete), + Property::Slot { attributes, .. } => !attributes.contains(DontDelete), } } @@ -183,6 +199,7 @@ impl<'gc> Property<'gc> { match self { Property::Virtual { attributes, .. } => !attributes.contains(DontEnum), Property::Stored { attributes, .. } => !attributes.contains(DontEnum), + Property::Slot { attributes, .. } => !attributes.contains(DontEnum), } } @@ -192,6 +209,7 @@ impl<'gc> Property<'gc> { attributes, set, .. } => !attributes.contains(ReadOnly) && !set.is_none(), Property::Stored { attributes, .. } => !attributes.contains(ReadOnly), + Property::Slot { attributes, .. } => !attributes.contains(ReadOnly), } } } diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index dc895e606890..0c3ab57635da 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -28,6 +28,9 @@ pub struct ScriptObjectData<'gc> { /// Properties stored on this object. values: HashMap>, + /// Slots stored on this object. + slots: Vec>, + /// Implicit prototype (or declared base class) of this script object. proto: Option>, } @@ -54,6 +57,19 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { .set_property(name, value, avm, context, self.into()) } + fn get_slot(self, id: u32) -> Result, Error> { + self.0.read().get_slot(id) + } + + fn set_slot( + self, + id: u32, + value: Value<'gc>, + mc: MutationContext<'gc, '_>, + ) -> Result<(), Error> { + self.0.write(mc).set_slot(id, value, mc) + } + fn has_property(self, name: &QName) -> bool { self.0.read().has_property(name) } @@ -133,6 +149,7 @@ impl<'gc> ScriptObjectData<'gc> { pub fn base_new(proto: Option>) -> Self { ScriptObjectData { values: HashMap::new(), + slots: Vec::new(), proto, } } @@ -172,6 +189,29 @@ impl<'gc> ScriptObjectData<'gc> { Ok(()) } + pub fn get_slot(&self, id: u32) -> Result, Error> { + self.slots + .get(id as usize) + .cloned() + .ok_or_else(|| format!("Slot index {} out of bounds!", id).into()) + } + + /// Set a slot by it's index. + pub fn set_slot( + &mut self, + id: u32, + value: Value<'gc>, + mc: MutationContext<'gc, '_>, + ) -> Result<(), Error> { + if let Some(slot) = self.slots.get_mut(id as usize) { + *slot = value; + + Ok(()) + } else { + Err(format!("Slot index {} out of bounds!", id).into()) + } + } + pub fn has_property(&self, name: &QName) -> bool { self.values.get(name).is_some() } From ecfd5abb41b9eafd2c296db58aa04d3d73b20cd4 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 19 Feb 2020 18:53:21 -0500 Subject: [PATCH 038/189] Impl `construct` and `constructprop`. --- core/src/avm2.rs | 68 ++++++++++++++++++++++++++++++++++ core/src/avm2/function.rs | 36 +++++++++++++++--- core/src/avm2/object.rs | 21 +++++++++++ core/src/avm2/script_object.rs | 26 +++++++++++++ 4 files changed, 145 insertions(+), 6 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index e0036695a2bf..e1d81b21c9a8 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -397,6 +397,10 @@ impl<'gc> Avm2<'gc> { 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), + Op::Construct { num_args } => self.op_construct(context, num_args), + Op::ConstructProp { index, num_args } => { + self.op_construct_prop(context, index, num_args) + } _ => self.unknown_op(op), }; @@ -683,4 +687,68 @@ impl<'gc> Avm2<'gc> { self.globals.set_slot(index, value, context.gc_context) } + + fn op_construct( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + arg_count: u32, + ) -> Result<(), Error> { + let mut args = Vec::new(); + for _ in 0..arg_count { + args.push(self.pop()); + } + + let ctor = self.pop().as_object()?; + let proto = ctor + .get_property( + &QName::new(Namespace::public_namespace(), "prototype"), + self, + context, + )? + .resolve(self, context)? + .as_object()?; + + let object = proto.construct(self, context, &args)?; + ctor.call(object, &args, self, context)? + .resolve(self, context)?; + + Ok(()) + } + + fn op_construct_prop( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result<(), Error> { + let mut args = Vec::new(); + for _ in 0..arg_count { + args.push(self.pop()); + } + + let multiname = self.pool_multiname(index)?; + + let source = self.pop().as_object()?; + let ctor_name: Result = source + .resolve_multiname(&multiname) + .ok_or_else(|| format!("Could not resolve property {}", multiname.local_name()).into()); + let ctor = source + .get_property(&ctor_name?, self, context)? + .resolve(self, context)? + .as_object()?; + let proto = ctor + .get_property( + &QName::new(Namespace::public_namespace(), "prototype"), + self, + context, + )? + .resolve(self, context)? + .as_object()?; + + let object = proto.construct(self, context, &args)?; + ctor.call(object, &args, self, context)? + .resolve(self, context)?; + + Ok(()) + } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 25ebbf946ec7..4cb5d19ced35 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -220,7 +220,7 @@ pub struct FunctionObjectData<'gc> { base: ScriptObjectData<'gc>, /// Executable code - exec: Executable<'gc>, + exec: Option>, /// The class that defined this function object, if any. class: Option, @@ -242,7 +242,7 @@ impl<'gc> FunctionObject<'gc> { mc, FunctionObjectData { base: ScriptObjectData::base_new(None), - exec: Avm2Function::from_method(initializer, None).into(), + exec: Some(Avm2Function::from_method(initializer, None).into()), class: Some(class), }, )) @@ -257,7 +257,7 @@ impl<'gc> FunctionObject<'gc> { scope: Option>>, fn_proto: Object<'gc>, ) -> Object<'gc> { - let exec = Avm2Function::from_method(method, scope).into(); + let exec = Some(Avm2Function::from_method(method, scope).into()); FunctionObject(GcCell::allocate( mc, @@ -280,7 +280,7 @@ impl<'gc> FunctionObject<'gc> { mc, FunctionObjectData { base: ScriptObjectData::base_new(Some(fn_proto)), - exec: nf.into(), + exec: Some(nf.into()), class: None, }, )) @@ -305,7 +305,7 @@ impl<'gc> FunctionObject<'gc> { mc, FunctionObjectData { base, - exec: constr.into(), + exec: Some(constr.into()), class: None, }, )) @@ -371,7 +371,31 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { - self.0.read().exec.exec(avm, context, reciever, arguments) + if let Some(exec) = &self.0.read().exec { + exec.exec(avm, context, reciever, arguments) + } else { + Err("Not a callable function!".into()) + } + } + + fn construct( + &self, + _avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + _args: &[Value<'gc>], + ) -> Result, Error> { + let this: Object<'gc> = Object::FunctionObject(*self); + let base = ScriptObjectData::base_new(Some(this)); + + Ok(FunctionObject(GcCell::allocate( + context.gc_context, + FunctionObjectData { + base, + exec: None, + class: None, + }, + )) + .into()) } fn install_trait( diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 560337f80f6e..73893c735733 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -149,6 +149,27 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy Err("Object is not callable".into()) } + /// Construct a host object of some kind and return it's cell. + /// + /// As the first step in object construction, the `construct` method is + /// called on the prototype to create a new object. The prototype may + /// construct any object implementation it wants, however, it's expected + /// to produce a like `TObject` implementor with itself as the new object's + /// proto. + /// + /// After construction, the constructor function is `call`ed with the new + /// object as `this` to initialize the object. + /// + /// The arguments passed to the constructor are provided here; however, all + /// object construction should happen in `call`, not `new`. `new` exists + /// purely so that host objects can be constructed by the VM. + fn construct( + &self, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + args: &[Value<'gc>], + ) -> Result, Error>; + /// Get a raw pointer value for this object. fn as_ptr(&self) -> *const ObjectPtr; } diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 0c3ab57635da..9184a686ed9b 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -82,6 +82,16 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.as_ptr() as *const ObjectPtr } + fn construct( + &self, + _avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + _args: &[Value<'gc>], + ) -> Result, Error> { + let this: Object<'gc> = Object::ScriptObject(*self); + Ok(ScriptObject::object(context.gc_context, this)) + } + fn install_trait( &mut self, mc: MutationContext<'gc, '_>, @@ -288,6 +298,22 @@ impl<'gc> ScriptObjectData<'gc> { .install_virtual_setter(function) } + /// Install a class into the object. + /// + /// Classes are fairly complicated. We desugar them into an ES3-style + /// prototype chain, which means we need to build a function, prototype, + /// and so on. In concert with the `new` Rust trait function we also ensure + /// that subclasses of an object impl use the same impl (e.g. subclasses of + /// `MovieClip` remain movie clips). + fn install_class( + &mut self, + name: QName, + type_entry: Avm2ClassEntry, + slot: u32, + ) -> Result<(), Error> { + Err("unimplemented".into()) + } + pub fn install_dynamic_property( &mut self, name: QName, From 04879fc4195d6232db589b76e5ab5c828d85cfeb Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 19 Feb 2020 23:10:21 -0500 Subject: [PATCH 039/189] Implement class traits. This allows the AVM to declare classes, which necessitated some refactoring to avoid double-borrows or having to do something "magic" that would dodge virtual properties. --- core/src/avm2.rs | 7 ++- core/src/avm2/function.rs | 109 +++++++++++++++++++++++---------- core/src/avm2/object.rs | 109 +++++++++++++++++++++++++++++---- core/src/avm2/script_object.rs | 102 ++++++------------------------ 4 files changed, 198 insertions(+), 129 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index e1d81b21c9a8..25e73574b150 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -92,8 +92,9 @@ impl<'gc> Avm2<'gc> { let scope = Scope::push_scope(None, self.globals(), context.gc_context); for trait_entry in entrypoint.script().traits.iter() { - self.globals.install_trait( - context.gc_context, + self.globals().install_trait( + self, + context, entrypoint.abc(), trait_entry, Some(scope), @@ -202,7 +203,7 @@ impl<'gc> Avm2<'gc> { let method = frame_ref.method(); let abc = method.abc.as_ref().clone(); - let method_index = method.abc_method; + let _method_index = method.abc_method; let method_body_index = method.abc_method_body as usize; (abc, frame, method_body_index, frame_ref.pc()) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 4cb5d19ced35..8a430c34cd6a 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -14,7 +14,7 @@ use std::fmt; use std::rc::Rc; use swf::avm2::types::{ AbcFile, Class as AbcClass, Index, Instance as AbcInstance, Method as AbcMethod, - MethodBody as AbcMethodBody, Trait as AbcTrait, + MethodBody as AbcMethodBody, }; /// Represents a function defined in Ruffle's code. @@ -74,11 +74,13 @@ impl Avm2MethodEntry { } /// Get the underlying ABC file. + #[allow(dead_code)] pub fn abc(&self) -> Rc { self.abc.clone() } /// Get a reference to the ABC method entry this refers to. + #[allow(dead_code)] pub fn method(&self) -> &AbcMethod { self.abc.methods.get(self.abc_method as usize).unwrap() } @@ -183,15 +185,32 @@ pub struct Avm2ClassEntry { /// The ABC file this function was defined in. pub abc: Rc, - /// The ABC class (used to define static properties) + /// The ABC class (used to define static properties). + /// + /// This is also the index of the ABC instance, which holds instance + /// properties. pub abc_class: u32, - - /// The ABC instance (used to define both instance properties as well as - /// "prototype" methods) - pub abc_instance: u32, } impl Avm2ClassEntry { + /// Construct an `Avm2MethodEntry` from an `AbcFile` and method index. + /// + /// This function returns `None` if the given class index does not resolve + /// to a valid ABC class, or a valid ABC instance. As mentioned in the type + /// documentation, ABC classes and instances are intended to be paired. + pub fn from_class_index(abc: Rc, abc_class: Index) -> Option { + if abc.classes.get(abc_class.0 as usize).is_some() + && abc.instances.get(abc_class.0 as usize).is_some() + { + return Some(Self { + abc, + abc_class: abc_class.0, + }); + } + + None + } + /// Get the underlying ABC file. pub fn abc(&self) -> Rc { self.abc.clone() @@ -204,7 +223,7 @@ impl Avm2ClassEntry { /// Get a reference to the ABC class instance entry this refers to. pub fn instance(&self) -> &AbcInstance { - self.abc.instances.get(self.abc_instance as usize).unwrap() + self.abc.instances.get(self.abc_class as usize).unwrap() } } @@ -231,23 +250,45 @@ impl<'gc> FunctionObject<'gc> { /// /// If the initializer method cannot be found, this function returns None. pub fn from_abc_class( - mc: MutationContext<'gc, '_>, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, class: Avm2ClassEntry, - ) -> Option> { + proto: Object<'gc>, + scope: Option>>, + fn_proto: Object<'gc>, + ) -> Result, Error> { let initializer_index = class.class().init_method.clone(); - let initializer = Avm2MethodEntry::from_method_index(class.abc(), initializer_index)?; - - Some( - FunctionObject(GcCell::allocate( - mc, - FunctionObjectData { - base: ScriptObjectData::base_new(None), - exec: Some(Avm2Function::from_method(initializer, None).into()), - class: Some(class), + let initializer: Result = + Avm2MethodEntry::from_method_index(class.abc(), initializer_index.clone()).ok_or_else( + || { + format!( + "Class initializer method index {} does not exist", + initializer_index.0 + ) + .into() }, - )) - .into(), - ) + ); + let mut constr: Object<'gc> = FunctionObject(GcCell::allocate( + context.gc_context, + FunctionObjectData { + base: ScriptObjectData::base_new(None), + exec: Some(Avm2Function::from_method(initializer?, None).into()), + class: Some(class.clone()), + }, + )) + .into(); + + for trait_entry in class.class().traits.iter() { + constr.install_trait(avm, context, class.abc(), trait_entry, scope, fn_proto)?; + } + + constr.install_method( + context.gc_context, + QName::new(Namespace::public_namespace(), "prototype"), + proto, + ); + + Ok(constr) } /// Construct a function from an ABC method and the current closure scope. @@ -398,22 +439,26 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { .into()) } - fn install_trait( + fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) { + self.0.write(mc).base.install_method(name, function) + } + + fn install_getter( &mut self, mc: MutationContext<'gc, '_>, - abc: Rc, - trait_entry: &AbcTrait, - scope: Option>>, - fn_proto: Object<'gc>, + name: QName, + function: Executable<'gc>, ) -> Result<(), Error> { - self.0 - .write(mc) - .base - .install_trait(mc, abc, trait_entry, scope, fn_proto) + self.0.write(mc).base.install_getter(name, function) } - fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) { - self.0.write(mc).base.install_method(name, function) + fn install_setter( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + function: Executable<'gc>, + ) -> Result<(), Error> { + self.0.write(mc).base.install_setter(name, function) } fn install_dynamic_property( diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 73893c735733..2c244cab6de1 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1,7 +1,9 @@ //! AVM2 objects. -use crate::avm2::function::FunctionObject; -use crate::avm2::names::{Multiname, QName}; +use crate::avm2::function::{ + Avm2ClassEntry, Avm2Function, Avm2MethodEntry, Executable, FunctionObject, +}; +use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::property::Attribute; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; @@ -14,7 +16,7 @@ use gc_arena::{Collect, GcCell, MutationContext}; use ruffle_macros::enum_trait_object; use std::fmt::Debug; use std::rc::Rc; -use swf::avm2::types::{AbcFile, Trait as AbcTrait}; +use swf::avm2::types::{AbcFile, Trait as AbcTrait, TraitKind as AbcTraitKind}; /// Represents an object that can be directly interacted with by the AVM2 /// runtime. @@ -117,18 +119,24 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy ) { } - /// Install a trait from an ABC file on an object. - fn install_trait( + /// Install a method (or any other non-slot value) on an object. + fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>); + + /// Install a getter method on an object property. + fn install_getter( &mut self, mc: MutationContext<'gc, '_>, - abc: Rc, - trait_entry: &AbcTrait, - scope: Option>>, - fn_proto: Object<'gc>, + name: QName, + function: Executable<'gc>, ) -> Result<(), Error>; - /// Install a method (not necessarily from an ABC file) on an object. - fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>); + /// Install a setter method on an object property. + fn install_setter( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + function: Executable<'gc>, + ) -> Result<(), Error>; /// Install a dynamic or built-in value property on an object. fn install_dynamic_property( @@ -138,6 +146,85 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy value: Value<'gc>, ) -> Result<(), Error>; + /// Install a trait from an ABC file on an object. + fn install_trait( + &mut self, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + abc: Rc, + trait_entry: &AbcTrait, + scope: Option>>, + fn_proto: Object<'gc>, + ) -> Result<(), Error> { + let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?; + match &trait_entry.kind { + AbcTraitKind::Method { method, .. } => { + let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); + let function = + FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto); + self.install_method(context.gc_context, trait_name, function); + } + AbcTraitKind::Getter { method, .. } => { + let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); + let exec = Avm2Function::from_method(method, scope).into(); + self.install_getter(context.gc_context, trait_name, exec)?; + } + AbcTraitKind::Setter { method, .. } => { + let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); + let exec = Avm2Function::from_method(method, scope).into(); + self.install_setter(context.gc_context, trait_name, exec)?; + } + AbcTraitKind::Class { class, .. } => { + let type_entry = Avm2ClassEntry::from_class_index(abc, class.clone()).unwrap(); + let super_name = QName::from_abc_multiname( + &type_entry.abc(), + type_entry.instance().super_name.clone(), + )?; + let super_class = self + .get_property(&super_name, avm, context)? + .resolve(avm, context)? + .as_object()?; + let super_proto = super_class + .get_property( + &QName::new(Namespace::public_namespace(), "prototype"), + avm, + context, + )? + .resolve(avm, context)? + .as_object()?; + let mut class_proto = super_proto.construct(avm, context, &[])?; + + for trait_entry in type_entry.instance().traits.iter() { + class_proto.install_trait( + avm, + context, + type_entry.abc(), + trait_entry, + scope, + fn_proto, + )?; + } + + let class = FunctionObject::from_abc_class( + avm, + context, + type_entry.clone(), + class_proto, + scope, + fn_proto, + )?; + let class_name = QName::from_abc_multiname( + &type_entry.abc(), + type_entry.instance().name.clone(), + )?; + self.install_method(context.gc_context, class_name, class); + } + _ => return Err("".into()), + } + + Ok(()) + } + /// Call the object. fn call( self, diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 9184a686ed9b..8cad11cba091 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -1,21 +1,16 @@ //! Default AVM2 object impl -use crate::avm2::function::{ - Avm2ClassEntry, Avm2Function, Avm2MethodEntry, Executable, FunctionObject, -}; +use crate::avm2::function::Executable; use crate::avm2::names::QName; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::property::Property; use crate::avm2::return_value::ReturnValue; -use crate::avm2::scope::Scope; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use gc_arena::{Collect, GcCell, MutationContext}; use std::collections::HashMap; use std::fmt::Debug; -use std::rc::Rc; -use swf::avm2::types::{AbcFile, Trait as AbcTrait, TraitKind as AbcTraitKind}; /// Default implementation of `avm2::Object`. #[derive(Clone, Collect, Debug, Copy)] @@ -92,21 +87,26 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { Ok(ScriptObject::object(context.gc_context, this)) } - fn install_trait( + fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) { + self.0.write(mc).install_method(name, function) + } + + fn install_getter( &mut self, mc: MutationContext<'gc, '_>, - abc: Rc, - trait_entry: &AbcTrait, - scope: Option>>, - fn_proto: Object<'gc>, + name: QName, + function: Executable<'gc>, ) -> Result<(), Error> { - self.0 - .write(mc) - .install_trait(mc, abc, trait_entry, scope, fn_proto) + self.0.write(mc).install_getter(name, function) } - fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) { - self.0.write(mc).install_method(name, function) + fn install_setter( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + function: Executable<'gc>, + ) -> Result<(), Error> { + self.0.write(mc).install_setter(name, function) } fn install_dynamic_property( @@ -136,23 +136,6 @@ impl<'gc> ScriptObject<'gc> { )) .into() } - - /// Construct the instance prototype half of a class. - pub fn instance_prototype( - mc: MutationContext<'gc, '_>, - type_entry: Avm2ClassEntry, - base_class: Object<'gc>, - scope: Option>>, - fn_proto: Object<'gc>, - ) -> Result, Error> { - let mut data = ScriptObjectData::base_new(Some(base_class)); - - for trait_entry in type_entry.instance().traits.iter() { - data.install_trait(mc, type_entry.abc(), trait_entry, scope, fn_proto)?; - } - - Ok(ScriptObject(GcCell::allocate(mc, data)).into()) - } } impl<'gc> ScriptObjectData<'gc> { @@ -211,7 +194,7 @@ impl<'gc> ScriptObjectData<'gc> { &mut self, id: u32, value: Value<'gc>, - mc: MutationContext<'gc, '_>, + _mc: MutationContext<'gc, '_>, ) -> Result<(), Error> { if let Some(slot) = self.slots.get_mut(id as usize) { *slot = value; @@ -230,37 +213,6 @@ impl<'gc> ScriptObjectData<'gc> { self.proto } - pub fn install_trait( - &mut self, - mc: MutationContext<'gc, '_>, - abc: Rc, - trait_entry: &AbcTrait, - scope: Option>>, - fn_proto: Object<'gc>, - ) -> Result<(), Error> { - let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?; - match &trait_entry.kind { - AbcTraitKind::Method { method, .. } => { - let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); - let function = FunctionObject::from_abc_method(mc, method, scope, fn_proto); - self.install_method(trait_name, function); - } - AbcTraitKind::Getter { method, .. } => { - let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); - let exec = Avm2Function::from_method(method, scope).into(); - self.install_getter(trait_name, exec)?; - } - AbcTraitKind::Setter { method, .. } => { - let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); - let exec = Avm2Function::from_method(method, scope).into(); - self.install_setter(trait_name, exec)?; - } - _ => return Err("".into()), - } - - Ok(()) - } - /// Install a method into the object. pub fn install_method(&mut self, name: QName, function: Object<'gc>) { self.values.insert(name, Property::new_method(function)); @@ -271,7 +223,7 @@ impl<'gc> ScriptObjectData<'gc> { /// This is a little more complicated than methods, since virtual property /// slots can be installed in two parts. Thus, we need to support /// installing them in either order. - fn install_getter(&mut self, name: QName, function: Executable<'gc>) -> Result<(), Error> { + pub fn install_getter(&mut self, name: QName, function: Executable<'gc>) -> Result<(), Error> { if !self.values.contains_key(&name) { self.values.insert(name.clone(), Property::new_virtual()); } @@ -287,7 +239,7 @@ impl<'gc> ScriptObjectData<'gc> { /// This is a little more complicated than methods, since virtual property /// slots can be installed in two parts. Thus, we need to support /// installing them in either order. - fn install_setter(&mut self, name: QName, function: Executable<'gc>) -> Result<(), Error> { + pub fn install_setter(&mut self, name: QName, function: Executable<'gc>) -> Result<(), Error> { if !self.values.contains_key(&name) { self.values.insert(name.clone(), Property::new_virtual()); } @@ -298,22 +250,6 @@ impl<'gc> ScriptObjectData<'gc> { .install_virtual_setter(function) } - /// Install a class into the object. - /// - /// Classes are fairly complicated. We desugar them into an ES3-style - /// prototype chain, which means we need to build a function, prototype, - /// and so on. In concert with the `new` Rust trait function we also ensure - /// that subclasses of an object impl use the same impl (e.g. subclasses of - /// `MovieClip` remain movie clips). - fn install_class( - &mut self, - name: QName, - type_entry: Avm2ClassEntry, - slot: u32, - ) -> Result<(), Error> { - Err("unimplemented".into()) - } - pub fn install_dynamic_property( &mut self, name: QName, From bc0bdf849669dcc28c017b02d950911439079dae Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 20 Feb 2020 14:13:26 -0500 Subject: [PATCH 040/189] The public namespace appears to just be an unnamed package namespace, not a regular namespace. --- core/src/avm2/names.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 29b02f047c87..8551af6e0525 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -50,7 +50,7 @@ impl Namespace { } pub fn public_namespace() -> Self { - Namespace::Namespace("".to_string()) + Namespace::Package("".to_string()) } } From cfe0e333befe3b10a3f41d9819af98cdd6a8981a Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 20 Feb 2020 14:31:08 -0500 Subject: [PATCH 041/189] Fix invalid script index when loading an ABC file. --- core/src/avm2.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 25e73574b150..91fa53c00940 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -86,9 +86,12 @@ impl<'gc> Avm2<'gc> { let abc_file = Rc::new(read.read()?); if !abc_file.scripts.is_empty() { - let entrypoint_script: Index = Index::new(abc_file.scripts.len() as u32); - let entrypoint = - Avm2ScriptEntry::from_script_index(abc_file, entrypoint_script).unwrap(); + let entrypoint_script: Index = Index::new(abc_file.scripts.len() as u32 - 1); + let entrypoint: Result = + Avm2ScriptEntry::from_script_index(abc_file, entrypoint_script.clone()).ok_or_else( + || format!("Script method {} does not exist", entrypoint_script.0).into(), + ); + let entrypoint = entrypoint?; let scope = Scope::push_scope(None, self.globals(), context.gc_context); for trait_entry in entrypoint.script().traits.iter() { From e1916519dd239625cbb22b24820f0564708ca1ec Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 20 Feb 2020 14:41:15 -0500 Subject: [PATCH 042/189] Add debug for trait installs --- core/src/avm2.rs | 15 ++++++++------- core/src/avm2/object.rs | 6 ++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 91fa53c00940..21404895ee26 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -19,6 +19,14 @@ use swf::avm2::types::{ }; use swf::read::SwfRead; +#[macro_export] +macro_rules! avm_debug { + ($($arg:tt)*) => ( + #[cfg(feature = "avm_debug")] + log::debug!($($arg)*) + ) +} + mod activation; mod function; mod globals; @@ -30,13 +38,6 @@ mod scope; mod script_object; mod value; -macro_rules! avm_debug { - ($($arg:tt)*) => ( - #[cfg(feature = "avm_debug")] - log::debug!($($arg)*) - ) -} - /// Boxed error alias. /// /// As AVM2 is a far stricter VM than AVM1, this may eventually be replaced diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 2c244cab6de1..d9e8b83d5f88 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -157,6 +157,12 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn_proto: Object<'gc>, ) -> Result<(), Error> { let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?; + avm_debug!( + "Installing trait {:?} of kind {:?}", + trait_name, + trait_entry.kind + ); + match &trait_entry.kind { AbcTraitKind::Method { method, .. } => { let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); From 362294181f25e0a738fc0de74d38d88083185673 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 20 Feb 2020 19:05:33 -0500 Subject: [PATCH 043/189] Implement constant pool default values (index 0). All constant pools in an ABC file are actually numbered starting from one; there's an implicit 0 entry not stored in the file that the runtime is expected to retrieve when pulling constants from the pool. The AVM2/ABC spec only mentions this in passing. --- core/src/avm2.rs | 20 +++++++++------- core/src/avm2/names.rs | 52 +++++++++++++++++++++++++---------------- core/src/avm2/object.rs | 2 +- core/src/avm2/value.rs | 47 +++++++++++++++++++++++++++++++++---- 4 files changed, 88 insertions(+), 33 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 21404895ee26..7916dac52ea6 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -532,9 +532,9 @@ impl<'gc> Avm2<'gc> { let multiname = self.pool_multiname(index)?; let object = self.pop().as_object()?; - let name: Result = object - .resolve_multiname(&multiname) - .ok_or_else(|| format!("Could not resolve property {}", multiname.local_name()).into()); + let name: Result = object.resolve_multiname(&multiname).ok_or_else(|| { + format!("Could not resolve property {:?}", multiname.local_name()).into() + }); let value = object .get_property(&name?, self, context)? @@ -558,7 +558,10 @@ impl<'gc> Avm2<'gc> { } else { //TODO: Non-dynamic objects should fail //TODO: This should only work if the public namespace is present - let name = QName::dynamic_name(multiname.local_name()); + 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(&name, value, self, context) } } @@ -627,7 +630,7 @@ impl<'gc> Avm2<'gc> { } else { None } - .ok_or_else(|| format!("Property does not exist: {}", multiname.local_name()).into()); + .ok_or_else(|| format!("Property does not exist: {:?}", multiname.local_name()).into()); let result: Value<'gc> = found?.into(); self.push(result); @@ -734,9 +737,10 @@ impl<'gc> Avm2<'gc> { let multiname = self.pool_multiname(index)?; let source = self.pop().as_object()?; - let ctor_name: Result = source - .resolve_multiname(&multiname) - .ok_or_else(|| format!("Could not resolve property {}", multiname.local_name()).into()); + let ctor_name: Result = + source.resolve_multiname(&multiname).ok_or_else(|| { + format!("Could not resolve property {:?}", multiname.local_name()).into() + }); let ctor = source .get_property(&ctor_name?, self, context)? .resolve(self, context)? diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 8551af6e0525..213f331319b7 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -1,6 +1,6 @@ //! AVM2 names & namespacing -use crate::avm2::value::abc_string; +use crate::avm2::value::{abc_string, abc_string_option}; use crate::avm2::{Avm2, Error}; use gc_arena::Collect; use swf::avm2::types::{ @@ -19,6 +19,7 @@ pub enum Namespace { Explicit(String), StaticProtected(String), Private(String), + Any, } impl Namespace { @@ -28,10 +29,15 @@ impl Namespace { file: &AbcFile, namespace_index: Index, ) -> Result { + if namespace_index.0 == 0 { + return Ok(Self::Any); + } + + let actual_index = namespace_index.0 as usize - 1; let abc_namespace: Result<&AbcNamespace, Error> = file .constant_pool .namespaces - .get(namespace_index.0 as usize) + .get(actual_index) .ok_or_else(|| format!("Unknown namespace constant {}", namespace_index.0).into()); Ok(match abc_namespace? { @@ -78,13 +84,6 @@ impl QName { } } - pub fn qualified(ns: &Namespace, name: &str) -> Self { - Self { - ns: ns.clone(), - name: name.to_string(), - } - } - pub fn dynamic_name(local_part: &str) -> Self { Self { ns: Namespace::public_namespace(), @@ -100,10 +99,11 @@ impl QName { file: &AbcFile, multiname_index: Index, ) -> Result { + let actual_index = multiname_index.0 as usize - 1; let abc_multiname: Result<&AbcMultiname, Error> = file .constant_pool .multinames - .get(multiname_index.0 as usize) + .get(actual_index) .ok_or_else(|| format!("Unknown multiname constant {}", multiname_index.0).into()); Ok(match abc_multiname? { @@ -121,9 +121,11 @@ impl QName { /// /// All unresolved names are of the form `Multiname`, and the name resolution /// process consists of searching each name space for a given name. +/// +/// The existence of a `name` of `None` indicates the `Any` name. pub struct Multiname { ns: Vec, - name: String, + name: Option, } impl Multiname { @@ -133,10 +135,16 @@ impl Multiname { file: &AbcFile, namespace_set_index: Index, ) -> Result, Error> { + if namespace_set_index.0 == 0 { + //TODO: What is namespace set zero? + return Ok(vec![]); + } + + let actual_index = namespace_set_index.0 as usize - 1; let ns_set: Result<&AbcNamespaceSet, Error> = file .constant_pool .namespace_sets - .get(namespace_set_index.0 as usize) + .get(actual_index) .ok_or_else(|| { format!("Unknown namespace set constant {}", namespace_set_index.0).into() }); @@ -156,30 +164,34 @@ impl Multiname { multiname_index: Index, avm: &mut Avm2<'_>, ) -> Result { + let actual_index = multiname_index.0 as usize - 1; let abc_multiname: Result<&AbcMultiname, Error> = file .constant_pool .multinames - .get(multiname_index.0 as usize) + .get(actual_index) .ok_or_else(|| format!("Unknown multiname constant {}", multiname_index.0).into()); Ok(match abc_multiname? { AbcMultiname::QName { namespace, name } | AbcMultiname::QNameA { namespace, name } => { Self { ns: vec![Namespace::from_abc_namespace(file, namespace.clone())?], - name: abc_string(file, name.clone())?, + name: abc_string_option(file, name.clone())?, } } AbcMultiname::RTQName { name } | AbcMultiname::RTQNameA { name } => { let ns = avm.pop().as_namespace()?.clone(); Self { ns: vec![ns], - name: abc_string(file, name.clone())?, + name: abc_string_option(file, name.clone())?, } } AbcMultiname::RTQNameL | AbcMultiname::RTQNameLA => { let ns = avm.pop().as_namespace()?.clone(); let name = avm.pop().as_string()?.clone(); - Self { ns: vec![ns], name } + Self { + ns: vec![ns], + name: Some(name), + } } AbcMultiname::Multiname { namespace_set, @@ -190,14 +202,14 @@ impl Multiname { name, } => Self { ns: Self::abc_namespace_set(file, namespace_set.clone())?, - name: abc_string(file, name.clone())?, + name: abc_string_option(file, name.clone())?, }, AbcMultiname::MultinameL { namespace_set } | AbcMultiname::MultinameLA { namespace_set } => { let name = avm.pop().as_string()?.clone(); Self { ns: Self::abc_namespace_set(file, namespace_set.clone())?, - name, + name: Some(name), } } }) @@ -207,7 +219,7 @@ impl Multiname { self.ns.iter() } - pub fn local_name(&self) -> &str { - &self.name + pub fn local_name(&self) -> Option<&str> { + self.name.as_deref() } } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index d9e8b83d5f88..742047e46f02 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -61,7 +61,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// match. fn resolve_multiname(self, multiname: &Multiname) -> Option { for ns in multiname.namespace_set() { - let qname = QName::qualified(ns, multiname.local_name()); + let qname = QName::new(ns.clone(), multiname.local_name()?); if self.has_property(&qname) { return Some(qname); diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 167a08483134..e7bf8dc76c10 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -4,6 +4,7 @@ use crate::avm2::names::Namespace; use crate::avm2::object::Object; use crate::avm2::Error; use gc_arena::Collect; +use std::f64::NAN; use swf::avm2::types::{AbcFile, Index}; /// An AVM2 value. @@ -140,34 +141,72 @@ impl PartialEq for Value<'_> { } pub fn abc_int(file: &AbcFile, index: Index) -> Result { + if index.0 == 0 { + return Ok(0); + } + file.constant_pool .ints - .get(index.0 as usize) + .get(index.0 as usize - 1) .cloned() .ok_or_else(|| format!("Unknown int constant {}", index.0).into()) } pub fn abc_uint(file: &AbcFile, index: Index) -> Result { + if index.0 == 0 { + return Ok(0); + } + file.constant_pool .uints - .get(index.0 as usize) + .get(index.0 as usize - 1) .cloned() .ok_or_else(|| format!("Unknown uint constant {}", index.0).into()) } pub fn abc_double(file: &AbcFile, index: Index) -> Result { + if index.0 == 0 { + return Ok(NAN); + } + file.constant_pool .doubles - .get(index.0 as usize) + .get(index.0 as usize - 1) .cloned() .ok_or_else(|| format!("Unknown double constant {}", index.0).into()) } +/// Retrieve a string from an ABC constant pool, yielding `""` if the string +/// is the zero string. pub fn abc_string(file: &AbcFile, index: Index) -> Result { + if index.0 == 0 { + return Ok("".to_string()); + } + + file.constant_pool + .strings + .get(index.0 as usize - 1) + .cloned() + .ok_or_else(|| format!("Unknown string constant {}", index.0).into()) +} + +/// Retrieve a string from an ABC constant pool, yielding `None` if the string +/// is the zero string. +/// +/// This function still yields `Err` for out-of-bounds string constants, which +/// should cause the runtime to halt. `None` indicates that the zero string is +/// in use, which callers are free to interpret as necessary (although this +/// usually means "any name"). +pub fn abc_string_option(file: &AbcFile, index: Index) -> Result, Error> { + if index.0 == 0 { + return Ok(None); + } + file.constant_pool .strings - .get(index.0 as usize) + .get(index.0 as usize - 1) .cloned() + .map(Some) .ok_or_else(|| format!("Unknown string constant {}", index.0).into()) } From ebcfee467613f027885fb990b80776f65685a45a Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 20 Feb 2020 19:34:48 -0500 Subject: [PATCH 044/189] Add specific error messages for unresolvable super classes --- core/src/avm2/names.rs | 4 ++++ core/src/avm2/object.rs | 20 +++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 213f331319b7..05e4e429b353 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -114,6 +114,10 @@ impl QName { _ => return Err("Attempted to pull QName from non-QName multiname".into()), }) } + + pub fn local_name(&self) -> &str { + &self.name + } } /// A `Multiname` consists of a name which could be resolved in one or more diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 742047e46f02..fb20c0d120dd 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -186,19 +186,29 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &type_entry.abc(), type_entry.instance().super_name.clone(), )?; - let super_class = self + let super_class: Result, Error> = self .get_property(&super_name, avm, context)? .resolve(avm, context)? - .as_object()?; - let super_proto = super_class + .as_object() + .map_err(|_e| { + format!("Could not resolve superclass {:?}", super_name.local_name()).into() + }); + let super_proto: Result, Error> = super_class? .get_property( &QName::new(Namespace::public_namespace(), "prototype"), avm, context, )? .resolve(avm, context)? - .as_object()?; - let mut class_proto = super_proto.construct(avm, context, &[])?; + .as_object() + .map_err(|_e| { + format!( + "Could not resolve superclass prototype {:?}", + super_name.local_name() + ) + .into() + }); + let mut class_proto = super_proto?.construct(avm, context, &[])?; for trait_entry in type_entry.instance().traits.iter() { class_proto.install_trait( From d42b16f02153a5dcb2eae1938cd62bbc93a92c1f Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 20 Feb 2020 20:29:51 -0500 Subject: [PATCH 045/189] Add stub impl of `flash.display.MovieClip`. --- core/src/avm2/globals.rs | 16 +++++++++++++++ core/src/avm2/globals/movieclip.rs | 31 ++++++++++++++++++++++++++++++ core/src/avm2/names.rs | 4 ++++ 3 files changed, 51 insertions(+) create mode 100644 core/src/avm2/globals/movieclip.rs diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 52e99f8a0c9e..ebff8da8f76d 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -7,6 +7,7 @@ use crate::avm2::script_object::ScriptObject; use gc_arena::{Collect, MutationContext}; mod function; +mod movieclip; mod object; /// This structure represents all system builtins' prototypes. @@ -28,6 +29,7 @@ pub fn construct_global_scope<'gc>( let object_proto = ScriptObject::bare_object(mc); let function_proto = function::create_proto(mc, object_proto); + let movieclip_proto = movieclip::create_proto(mc, object_proto, function_proto); object::fill_proto(mc, object_proto, function_proto); @@ -64,6 +66,20 @@ pub fn construct_global_scope<'gc>( .into(), ) .unwrap(); + global_scope + .install_dynamic_property( + mc, + QName::new(Namespace::package("flash.display"), "MovieClip"), + FunctionObject::from_builtin_constr( + mc, + movieclip::constructor, + movieclip_proto, + function_proto, + ) + .unwrap() + .into(), + ) + .unwrap(); (global_scope, system_prototypes) } diff --git a/core/src/avm2/globals/movieclip.rs b/core/src/avm2/globals/movieclip.rs new file mode 100644 index 000000000000..b1e9a2900067 --- /dev/null +++ b/core/src/avm2/globals/movieclip.rs @@ -0,0 +1,31 @@ +//! `flash.display.MovieClip` builtin/prototype + +use crate::avm2::object::Object; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::script_object::ScriptObject; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::MutationContext; + +/// Implements `flash.display.MovieClip`'s constructor. +pub fn constructor<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined.into()) +} + +/// Construct `MovieClip.prototype`. +pub fn create_proto<'gc>( + mc: MutationContext<'gc, '_>, + super_proto: Object<'gc>, + _fn_proto: Object<'gc>, +) -> Object<'gc> { + // TODO: Use `StageObject` here. + let movieclip_proto = ScriptObject::object(mc, super_proto); + + movieclip_proto +} diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 05e4e429b353..d1b4e0bd2639 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -58,6 +58,10 @@ impl Namespace { pub fn public_namespace() -> Self { Namespace::Package("".to_string()) } + + pub fn package(package_name: &str) -> Self { + Namespace::Package(package_name.to_string()) + } } /// A `QName`, likely "qualified name", consists of a namespace and name string. From 200c10b4a2d3c53c87dab221f9ac335277588b09 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 20 Feb 2020 21:02:49 -0500 Subject: [PATCH 046/189] Classes can fit in slots, so let's stick them in there. --- core/src/avm2/function.rs | 10 ++++++++++ core/src/avm2/object.rs | 13 +++++++++++-- core/src/avm2/property.rs | 16 ++++++++++++++++ core/src/avm2/script_object.rs | 26 ++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 8a430c34cd6a..f9684f339da7 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -469,4 +469,14 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { ) -> Result<(), Error> { self.0.write(mc).base.install_dynamic_property(name, value) } + + fn install_slot( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + id: u32, + value: Value<'gc>, + ) { + self.0.write(mc).base.install_slot(name, id, value) + } } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index fb20c0d120dd..844b5362851d 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -146,6 +146,15 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy value: Value<'gc>, ) -> Result<(), Error>; + /// Install a slot on an object property. + fn install_slot( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + id: u32, + value: Value<'gc>, + ); + /// Install a trait from an ABC file on an object. fn install_trait( &mut self, @@ -180,7 +189,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy let exec = Avm2Function::from_method(method, scope).into(); self.install_setter(context.gc_context, trait_name, exec)?; } - AbcTraitKind::Class { class, .. } => { + AbcTraitKind::Class { slot_id, class } => { let type_entry = Avm2ClassEntry::from_class_index(abc, class.clone()).unwrap(); let super_name = QName::from_abc_multiname( &type_entry.abc(), @@ -233,7 +242,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &type_entry.abc(), type_entry.instance().name.clone(), )?; - self.install_method(context.gc_context, class_name, class); + self.install_slot(context.gc_context, class_name, *slot_id, class.into()); } _ => return Err("".into()), } diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 7366f2bc30bb..246f683c7c84 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -53,6 +53,14 @@ unsafe impl<'gc> Collect for Property<'gc> { } impl<'gc> Property<'gc> { + /// Create a new stored property. + pub fn new_stored(value: impl Into>) -> Self { + Property::Stored { + value: value.into(), + attributes: EnumSet::from(Attribute::DontDelete), + } + } + /// Convert a value into a dynamic property. pub fn new_dynamic_property(value: impl Into>) -> Self { Property::Stored { @@ -80,6 +88,14 @@ impl<'gc> Property<'gc> { } } + /// Create a new slot property. + pub fn new_slot(slot_id: u32) -> Self { + Property::Slot { + slot_id, + attributes: EnumSet::from(Attribute::DontDelete), + } + } + /// Install a getter into this property. /// /// This function errors if attempting to install executables into a diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 8cad11cba091..9f674bdfe08f 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -117,6 +117,16 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { ) -> Result<(), Error> { self.0.write(mc).install_dynamic_property(name, value) } + + fn install_slot( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + id: u32, + value: Value<'gc>, + ) { + self.0.write(mc).install_slot(name, id, value) + } } impl<'gc> ScriptObject<'gc> { @@ -260,4 +270,20 @@ impl<'gc> ScriptObjectData<'gc> { Ok(()) } + + /// Install a slot onto the object. + /// + /// Slot number zero indicates a slot ID that is unknown and should be + /// allocated by the VM - as far as I know, there is no way to discover + /// slot IDs, so we don't allocate a slot for them at all. + pub fn install_slot(&mut self, name: QName, id: u32, value: Value<'gc>) { + if id == 0 { + self.values.insert(name, Property::new_stored(value)); + } else { + self.values.insert(name, Property::new_slot(id)); + if self.slots.len() < id as usize { + self.slots.resize(id as usize, Value::Undefined); + } + } + } } From af70024f624eb23db457e16ceec76c852bb7b703 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 20 Feb 2020 22:04:31 -0500 Subject: [PATCH 047/189] Implement slot traits. --- core/src/avm2/object.rs | 10 +++++++++- core/src/avm2/value.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 844b5362851d..f0ca786c6053 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -8,7 +8,7 @@ use crate::avm2::property::Attribute; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; -use crate::avm2::value::Value; +use crate::avm2::value::{abc_default_value, Value}; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use enumset::EnumSet; @@ -173,6 +173,14 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy ); match &trait_entry.kind { + AbcTraitKind::Slot { slot_id, value, .. } => { + let value = if let Some(value) = value { + abc_default_value(&abc, value)? + } else { + Value::Undefined + }; + self.install_slot(context.gc_context, trait_name, *slot_id, value); + } AbcTraitKind::Method { method, .. } => { let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); let function = diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index e7bf8dc76c10..477ae9eaab5f 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -5,7 +5,7 @@ use crate::avm2::object::Object; use crate::avm2::Error; use gc_arena::Collect; use std::f64::NAN; -use swf::avm2::types::{AbcFile, Index}; +use swf::avm2::types::{AbcFile, DefaultValue as AbcDefaultValue, Index}; /// An AVM2 value. /// @@ -210,6 +210,32 @@ pub fn abc_string_option(file: &AbcFile, index: Index) -> Result( + file: &AbcFile, + default: &AbcDefaultValue, +) -> Result, Error> { + match default { + AbcDefaultValue::Int(i) => abc_int(file, *i).map(|v| v.into()), + AbcDefaultValue::Uint(u) => abc_uint(file, *u).map(|v| v.into()), + AbcDefaultValue::Double(d) => abc_double(file, *d).map(|v| v.into()), + AbcDefaultValue::String(s) => abc_string(file, s.clone()).map(|v| v.into()), + AbcDefaultValue::True => Ok(true.into()), + AbcDefaultValue::False => Ok(false.into()), + AbcDefaultValue::Null => Ok(Value::Null), + AbcDefaultValue::Undefined => Ok(Value::Undefined), + AbcDefaultValue::Namespace(ns) + | AbcDefaultValue::Package(ns) + | AbcDefaultValue::PackageInternal(ns) + | AbcDefaultValue::Protected(ns) + | AbcDefaultValue::Explicit(ns) + | AbcDefaultValue::StaticProtected(ns) + | AbcDefaultValue::Private(ns) => { + Namespace::from_abc_namespace(file, ns.clone()).map(|v| v.into()) + } + } +} + impl<'gc> Value<'gc> { pub fn as_object(&self) -> Result, Error> { if let Value::Object(object) = self { From 412c3d84891f0665d467dbe62d685f6a6a07421a Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 20 Feb 2020 22:13:24 -0500 Subject: [PATCH 048/189] Implement `Function` traits. --- core/src/avm2/object.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index f0ca786c6053..39e61a4334f5 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -252,6 +252,14 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy )?; self.install_slot(context.gc_context, class_name, *slot_id, class.into()); } + AbcTraitKind::Function { + slot_id, function, .. + } => { + let method = Avm2MethodEntry::from_method_index(abc, function.clone()).unwrap(); + let function = + FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto); + self.install_slot(context.gc_context, trait_name, *slot_id, function.into()); + } _ => return Err("".into()), } From fd275bdcf3910d2d52b89b79cad345bdeef45dcc Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 20 Feb 2020 23:20:46 -0500 Subject: [PATCH 049/189] Implement constant slots and traits. Class and Function traits now generate const slots, too. --- core/src/avm2.rs | 1 + core/src/avm2/function.rs | 10 +++++ core/src/avm2/object.rs | 22 +++++++-- core/src/avm2/property.rs | 8 ++++ core/src/avm2/script_object.rs | 47 ++++++++++++++++--- core/src/avm2/slot.rs | 82 ++++++++++++++++++++++++++++++++++ 6 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 core/src/avm2/slot.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 7916dac52ea6..5b54696c9893 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -36,6 +36,7 @@ mod property; mod return_value; mod scope; mod script_object; +mod slot; mod value; /// Boxed error alias. diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index f9684f339da7..756d286458e9 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -479,4 +479,14 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { ) { self.0.write(mc).base.install_slot(name, id, value) } + + fn install_const( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + id: u32, + value: Value<'gc>, + ) { + self.0.write(mc).base.install_const(name, id, value) + } } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 39e61a4334f5..d3d1683b5a39 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -155,6 +155,15 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy value: Value<'gc>, ); + /// Install a const on an object property. + fn install_const( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + id: u32, + value: Value<'gc>, + ); + /// Install a trait from an ABC file on an object. fn install_trait( &mut self, @@ -250,7 +259,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &type_entry.abc(), type_entry.instance().name.clone(), )?; - self.install_slot(context.gc_context, class_name, *slot_id, class.into()); + self.install_const(context.gc_context, class_name, *slot_id, class.into()); } AbcTraitKind::Function { slot_id, function, .. @@ -258,9 +267,16 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy let method = Avm2MethodEntry::from_method_index(abc, function.clone()).unwrap(); let function = FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto); - self.install_slot(context.gc_context, trait_name, *slot_id, function.into()); + self.install_const(context.gc_context, trait_name, *slot_id, function.into()); + } + AbcTraitKind::Const { slot_id, value, .. } => { + let value = if let Some(value) = value { + abc_default_value(&abc, value)? + } else { + Value::Undefined + }; + self.install_const(context.gc_context, trait_name, *slot_id, value); } - _ => return Err("".into()), } Ok(()) diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 246f683c7c84..eb6298b6ba35 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -61,6 +61,14 @@ impl<'gc> Property<'gc> { } } + /// Create a new stored property. + pub fn new_const(value: impl Into>) -> Self { + Property::Stored { + value: value.into(), + attributes: Attribute::ReadOnly | Attribute::DontDelete, + } + } + /// Convert a value into a dynamic property. pub fn new_dynamic_property(value: impl Into>) -> Self { Property::Stored { diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 9f674bdfe08f..fb5434811922 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -5,6 +5,7 @@ use crate::avm2::names::QName; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::property::Property; use crate::avm2::return_value::ReturnValue; +use crate::avm2::slot::Slot; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; @@ -24,7 +25,7 @@ pub struct ScriptObjectData<'gc> { values: HashMap>, /// Slots stored on this object. - slots: Vec>, + slots: Vec>, /// Implicit prototype (or declared base class) of this script object. proto: Option>, @@ -127,6 +128,16 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { ) { self.0.write(mc).install_slot(name, id, value) } + + fn install_const( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + id: u32, + value: Value<'gc>, + ) { + self.0.write(mc).install_const(name, id, value) + } } impl<'gc> ScriptObject<'gc> { @@ -193,10 +204,12 @@ impl<'gc> ScriptObjectData<'gc> { } pub fn get_slot(&self, id: u32) -> Result, Error> { + //TODO: slot inheritance, I think? self.slots .get(id as usize) .cloned() .ok_or_else(|| format!("Slot index {} out of bounds!", id).into()) + .map(|slot| slot.get().unwrap_or(Value::Undefined)) } /// Set a slot by it's index. @@ -207,9 +220,7 @@ impl<'gc> ScriptObjectData<'gc> { _mc: MutationContext<'gc, '_>, ) -> Result<(), Error> { if let Some(slot) = self.slots.get_mut(id as usize) { - *slot = value; - - Ok(()) + slot.set(value) } else { Err(format!("Slot index {} out of bounds!", id).into()) } @@ -281,8 +292,32 @@ impl<'gc> ScriptObjectData<'gc> { self.values.insert(name, Property::new_stored(value)); } else { self.values.insert(name, Property::new_slot(id)); - if self.slots.len() < id as usize { - self.slots.resize(id as usize, Value::Undefined); + if self.slots.len() < id as usize + 1 { + self.slots.resize_with(id as usize + 1, Default::default); + } + + if let Some(slot) = self.slots.get_mut(id as usize) { + *slot = Slot::new(value); + } + } + } + + /// Install a const onto the object. + /// + /// Slot number zero indicates a slot ID that is unknown and should be + /// allocated by the VM - as far as I know, there is no way to discover + /// slot IDs, so we don't allocate a slot for them at all. + pub fn install_const(&mut self, name: QName, id: u32, value: Value<'gc>) { + if id == 0 { + self.values.insert(name, Property::new_const(value)); + } else { + self.values.insert(name, Property::new_slot(id)); + if self.slots.len() < id as usize + 1 { + self.slots.resize_with(id as usize + 1, Default::default); + } + + if let Some(slot) = self.slots.get_mut(id as usize) { + *slot = Slot::new_const(value); } } } diff --git a/core/src/avm2/slot.rs b/core/src/avm2/slot.rs new file mode 100644 index 000000000000..105589357116 --- /dev/null +++ b/core/src/avm2/slot.rs @@ -0,0 +1,82 @@ +//! Slot contents type + +use crate::avm2::property::Attribute; +use crate::avm2::value::Value; +use crate::avm2::Error; +use enumset::EnumSet; +use gc_arena::{Collect, CollectionContext}; + +/// Represents a single slot on an object. +#[derive(Clone, Debug)] +pub enum Slot<'gc> { + /// An unoccupied slot. + /// + /// Attempts to read an unoccupied slot proceed up the prototype chain. + /// Writing an unoccupied slot will always fail. + Unoccupied, + + /// An occupied slot. + Occupied { + value: Value<'gc>, + attributes: EnumSet, + }, +} + +unsafe impl<'gc> Collect for Slot<'gc> { + fn trace(&self, cc: CollectionContext) { + match self { + Self::Unoccupied => {} + Self::Occupied { value, .. } => value.trace(cc), + } + } +} + +impl<'gc> Default for Slot<'gc> { + fn default() -> Self { + Self::Unoccupied + } +} + +impl<'gc> Slot<'gc> { + /// Create a normal slot with a given value. + pub fn new(value: impl Into>) -> Self { + Self::Occupied { + value: value.into(), + attributes: EnumSet::empty(), + } + } + + /// Create a `const` slot that cannot be overwritten. + pub fn new_const(value: impl Into>) -> Self { + Self::Occupied { + value: value.into(), + attributes: EnumSet::from(Attribute::ReadOnly), + } + } + + /// Retrieve the value of this slot. + pub fn get(&self) -> Option> { + match self { + Self::Unoccupied => None, + Self::Occupied { value, .. } => Some(value.clone()), + } + } + + /// Write the value of this slot. + pub fn set(&mut self, new_value: impl Into>) -> Result<(), Error> { + match self { + Self::Unoccupied => Err("Cannot overwrite unoccupied slot".into()), + Self::Occupied { value, attributes } => { + if attributes.contains(Attribute::ReadOnly) { + return Err("Cannot overwrite const slot".into()); + } + + //TODO: Type assert + + *value = new_value.into(); + + Ok(()) + } + } + } +} From 8b56973d29b3d82c1bdbed145f2d19411ff4392f Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 21 Feb 2020 14:27:50 -0500 Subject: [PATCH 050/189] Remove scope methods that aren't necessary. In AVM1, these are necessary because `ActionGetVariable` et. all directly interface with the scope chain. In AVM2, you `findpropstrict` up the scope chain, which gives you a normal object that you can interact with as you like. Ergo, the scope chain doesn't need set/get property methods. --- core/src/avm2/scope.rs | 85 +----------------------------------------- 1 file changed, 1 insertion(+), 84 deletions(-) diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index ad9cf07e51ce..34922c39384b 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -1,12 +1,11 @@ //! Represents AVM2 scope chain resolution. -use crate::avm2::names::{Multiname, QName}; +use crate::avm2::names::Multiname; use crate::avm2::object::{Object, TObject}; use crate::avm2::return_value::ReturnValue; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use enumset::EnumSet; use gc_arena::{Collect, GcCell, MutationContext}; use std::cell::Ref; @@ -119,19 +118,6 @@ impl<'gc> Scope<'gc> { bottom_scope } - /// Construct an arbitrary scope - pub fn new( - parent: GcCell<'gc, Self>, - class: ScopeClass, - with_object: Object<'gc>, - ) -> Scope<'gc> { - Scope { - parent: Some(parent), - class, - values: with_object, - } - } - /// Returns a reference to the current local scope object. pub fn locals(&self) -> &Object<'gc> { &self.values @@ -192,73 +178,4 @@ impl<'gc> Scope<'gc> { //TODO: Should undefined variables halt execution? Ok(Value::Undefined.into()) } - - /// Check if a particular property in the scope chain is defined. - pub fn is_defined(&self, name: &QName) -> bool { - if self.locals().has_property(name) { - return true; - } - - if let Some(scope) = self.parent() { - return scope.is_defined(name); - } - - false - } - - /// Update a particular value in the scope chain. - /// - /// Traverses the scope chain in search of a value. If it's found, it's overwritten. - /// The traversal stops at Target scopes, which represents the movie clip timeline - /// the code is executing in. - /// If the value is not found, it is defined on this Target scope. - pub fn set( - &self, - name: &QName, - value: Value<'gc>, - avm: &mut Avm2<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - ) -> Result<(), Error> { - if self.locals().has_property(name) && self.locals().is_property_overwritable(name) { - // Value found on this object, so overwrite it. - // Or we've hit the executing movie clip, so create it here. - self.locals().set_property(name, value, avm, context) - } else if let Some(scope) = self.parent() { - // Traverse the scope chain in search of the value. - scope.set(name, value, avm, context, this) - } else { - // This probably shouldn't happen -- all AVM2 code runs in reference to some movieclip, - // so we should always have a movieclip scope. - // Define on the top-level scope. - debug_assert!(false, "Scope::set: No top-level movie clip scope"); - self.locals().set_property(name, value, avm, context) - } - } - - /// Set a particular value in the locals for this scope. - /// - /// By convention, the locals for a given function are always defined as - /// stored (e.g. not virtual) properties on the lowest object in the scope - /// chain. As a result, this function always force sets a property on the - /// local object and does not traverse the scope chain. - pub fn define(&self, name: &QName, value: impl Into>, mc: MutationContext<'gc, '_>) { - self.locals() - .define_value(mc, name, value.into(), EnumSet::empty()); - } - - /// Delete a value from scope - pub fn delete(&self, name: &Multiname, mc: MutationContext<'gc, '_>) -> bool { - if let Some(qname) = self.locals().resolve_multiname(name) { - if self.locals().has_property(&qname) { - return self.locals().delete(mc, &qname); - } - } - - if let Some(scope) = self.parent() { - return scope.delete(name, mc); - } - - false - } } From d19d9ef90e5929223295f701df1d3ecbce680a53 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 21 Feb 2020 14:30:14 -0500 Subject: [PATCH 051/189] Clean up unused variables --- core/src/avm2/property.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index eb6298b6ba35..73f6ed867f2c 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -183,7 +183,7 @@ impl<'gc> Property<'gc> { } Property::Slot { slot_id, .. } => this .set_slot(*slot_id, new_value.into(), context.gc_context) - .map(|v| true), + .map(|_v| true), } } From cbce8660bc268ad545fbef28fd9f0f1897a28dea Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 21 Feb 2020 14:52:24 -0500 Subject: [PATCH 052/189] Implement `deleteproperty`. --- core/src/avm2.rs | 18 ++++++++++++++++++ core/src/avm2/function.rs | 4 ++++ core/src/avm2/object.rs | 4 +--- core/src/avm2/script_object.rs | 18 ++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 5b54696c9893..d01dd34b5e30 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -393,6 +393,7 @@ impl<'gc> Avm2<'gc> { Op::ReturnVoid => self.op_return_void(context), Op::GetProperty { index } => self.op_get_property(context, index), Op::SetProperty { index } => self.op_set_property(context, index), + Op::DeleteProperty { index } => self.op_delete_property(context, index), Op::PushScope => self.op_push_scope(context), Op::PushWith => self.op_push_with(context), Op::PopScope => self.op_pop_scope(context), @@ -567,6 +568,23 @@ impl<'gc> Avm2<'gc> { } } + fn op_delete_property( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result<(), Error> { + let multiname = self.pool_multiname(index)?; + let object = self.pop().as_object()?; + + if let Some(name) = object.resolve_multiname(&multiname) { + self.push(object.delete_property(context.gc_context, &name)) + } else { + self.push(false) + } + + 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(); diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 756d286458e9..8b90526e8a1c 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -380,6 +380,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { .set_property(name, value, avm, context, self.into()) } + fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool { + self.0.write(gc_context).base.delete_property(multiname) + } + fn get_slot(self, id: u32) -> Result, Error> { self.0.read().base.get_slot(id) } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index d3d1683b5a39..7f97ddabafc8 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -88,9 +88,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Delete a named property from the object. /// /// Returns false if the property cannot be deleted. - fn delete(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool { - false - } + fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool; /// Retrieve the `__proto__` of a given object. /// diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index fb5434811922..ef4738da5ee2 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -53,6 +53,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { .set_property(name, value, avm, context, self.into()) } + fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool { + self.0.write(gc_context).delete_property(multiname) + } + fn get_slot(self, id: u32) -> Result, Error> { self.0.read().get_slot(id) } @@ -203,6 +207,20 @@ impl<'gc> ScriptObjectData<'gc> { Ok(()) } + pub fn delete_property(&mut self, name: &QName) -> bool { + let can_delete = if let Some(prop) = self.values.get(name) { + prop.can_delete() + } else { + false + }; + + if can_delete { + self.values.remove(name); + } + + can_delete + } + pub fn get_slot(&self, id: u32) -> Result, Error> { //TODO: slot inheritance, I think? self.slots From 279d90ec220aa7a63c438242f62135808c154abd Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 21 Feb 2020 14:55:14 -0500 Subject: [PATCH 053/189] Remove `define_value` from AVM2 objects. We already have a menagerie of `install_*` functions for adding static properties to a an object; and we don't have to support any kind of asinine nonsense liks `ASSetPropFlags` here. Ergo, we don't need this. --- core/src/avm2/object.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 7f97ddabafc8..09119aaf8ed8 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -4,14 +4,12 @@ use crate::avm2::function::{ Avm2ClassEntry, Avm2Function, Avm2MethodEntry, Executable, FunctionObject, }; use crate::avm2::names::{Multiname, Namespace, QName}; -use crate::avm2::property::Attribute; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::{abc_default_value, Value}; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use enumset::EnumSet; use gc_arena::{Collect, GcCell, MutationContext}; use ruffle_macros::enum_trait_object; use std::fmt::Debug; @@ -97,26 +95,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// `get`. fn proto(&self) -> Option>; - /// Define a value on an object. - /// - /// Unlike setting a value, this function is intended to replace any - /// existing virtual or built-in properties already installed on a given - /// object. As such, this should not run any setters; the resulting name - /// slot should either be completely replaced with the value or completely - /// untouched. - /// - /// It is not guaranteed that all objects accept value definitions, - /// especially if a property name conflicts with a built-in property, such - /// as `__proto__`. - fn define_value( - &self, - gc_context: MutationContext<'gc, '_>, - name: &QName, - value: Value<'gc>, - attributes: EnumSet, - ) { - } - /// Install a method (or any other non-slot value) on an object. fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>); From 5f98a198cbcb560d4958f1706412b5df68a0c56f Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 21 Feb 2020 20:51:44 -0500 Subject: [PATCH 054/189] Remove dead code in Activation --- core/src/avm2/activation.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 3095a0c62d2a..369c6bc55409 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -5,9 +5,9 @@ use crate::avm2::object::Object; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; -use gc_arena::{Collect, Gc, GcCell, MutationContext}; +use gc_arena::{Collect, GcCell, MutationContext}; use smallvec::SmallVec; use std::rc::Rc; use swf::avm2::types::{AbcFile, Index, Script as AbcScript}; @@ -82,10 +82,6 @@ impl<'gc> RegisterSet<'gc> { pub fn get_mut(&mut self, num: u32) -> Option<&mut Value<'gc>> { self.0.get_mut(num as usize) } - - pub fn len(&self) -> u32 { - self.0.len() as u32 - } } /// Represents a single activation of a given AVM2 function or keyframe. @@ -248,12 +244,6 @@ impl<'gc> Activation<'gc> { } } - /// Retrieve the return value from a completed activation, if the function - /// has already returned. - pub fn return_value(&self) -> Option> { - self.return_value.clone() - } - /// Set the return value. pub fn set_return_value(&mut self, value: Value<'gc>) { self.return_value = Some(value); From 4ab9a465150a45f39344f1dd3f3ed7f892a42b3b Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 21 Feb 2020 23:31:30 -0500 Subject: [PATCH 055/189] Impl `getscopeobject` --- core/src/avm2.rs | 21 +++++++++++++++++++++ core/src/avm2/scope.rs | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index d01dd34b5e30..4ddd516ffe8a 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -397,6 +397,7 @@ impl<'gc> Avm2<'gc> { Op::PushScope => self.op_push_scope(context), Op::PushWith => self.op_push_with(context), Op::PopScope => self.op_pop_scope(context), + Op::GetScopeObject { index } => self.op_get_scope_object(index), 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), @@ -620,6 +621,26 @@ impl<'gc> Avm2<'gc> { Ok(()) } + 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(()) + } + fn op_find_property( &mut self, context: &mut UpdateContext<'_, 'gc, '_>, diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index 34922c39384b..2c2991d97e46 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -137,6 +137,10 @@ impl<'gc> Scope<'gc> { } } + pub fn parent_cell(&self) -> Option>> { + self.parent + } + /// Find an object that contains a given property in the scope stack. pub fn find( &self, From b2f530721332fda8590b39c298fd9bb8c5889f03 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 12:47:03 -0500 Subject: [PATCH 056/189] Add `flash.display.Sprite` because Flash Builder tests demand it. --- core/src/avm2/globals.rs | 18 +++++++++++++++++- core/src/avm2/globals/sprite.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 core/src/avm2/globals/sprite.rs diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index ebff8da8f76d..ea8ed62d8206 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -9,6 +9,7 @@ use gc_arena::{Collect, MutationContext}; mod function; mod movieclip; mod object; +mod sprite; /// This structure represents all system builtins' prototypes. #[derive(Clone, Collect)] @@ -29,7 +30,8 @@ pub fn construct_global_scope<'gc>( let object_proto = ScriptObject::bare_object(mc); let function_proto = function::create_proto(mc, object_proto); - let movieclip_proto = movieclip::create_proto(mc, object_proto, function_proto); + let sprite_proto = sprite::create_proto(mc, object_proto, function_proto); + let movieclip_proto = movieclip::create_proto(mc, sprite_proto, function_proto); object::fill_proto(mc, object_proto, function_proto); @@ -66,6 +68,20 @@ pub fn construct_global_scope<'gc>( .into(), ) .unwrap(); + global_scope + .install_dynamic_property( + mc, + QName::new(Namespace::package("flash.display"), "Sprite"), + FunctionObject::from_builtin_constr( + mc, + sprite::constructor, + sprite_proto, + function_proto, + ) + .unwrap() + .into(), + ) + .unwrap(); global_scope .install_dynamic_property( mc, diff --git a/core/src/avm2/globals/sprite.rs b/core/src/avm2/globals/sprite.rs new file mode 100644 index 000000000000..29e522f111cc --- /dev/null +++ b/core/src/avm2/globals/sprite.rs @@ -0,0 +1,31 @@ +//! `flash.display.Sprite` builtin/prototype + +use crate::avm2::object::Object; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::script_object::ScriptObject; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::MutationContext; + +/// Implements `flash.display.Sprite`'s constructor. +pub fn constructor<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined.into()) +} + +/// Construct `Sprite.prototype`. +pub fn create_proto<'gc>( + mc: MutationContext<'gc, '_>, + super_proto: Object<'gc>, + _fn_proto: Object<'gc>, +) -> Object<'gc> { + // TODO: Use `StageObject` here. + let sprite_proto = ScriptObject::object(mc, super_proto); + + sprite_proto +} From 5b00c1fd9651a68e2d4ee5cb9a0f4f180ece0ffa Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 12:51:34 -0500 Subject: [PATCH 057/189] Ensure that `this` is properly populated into local registers. --- core/src/avm2/activation.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 369c6bc55409..c505902330aa 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -140,6 +140,13 @@ impl<'gc> Activation<'gc> { let method = method?; let scope = Some(Scope::push_scope(None, global, context.gc_context)); let num_locals = method.body().num_locals; + let local_registers = + GcCell::allocate(context.gc_context, RegisterSet::new(num_locals + 1)); + + *local_registers + .write(context.gc_context) + .get_mut(0) + .unwrap() = global.into(); Ok(Self { method, @@ -147,7 +154,7 @@ impl<'gc> Activation<'gc> { this: global, arguments: None, is_executing: false, - local_registers: GcCell::allocate(context.gc_context, RegisterSet::new(num_locals)), + local_registers, return_value: None, local_scope: ScriptObject::bare_object(context.gc_context), scope, From 9e120c216b0b3e4c9c9d5b7a02081b9cb648751c Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 15:02:51 -0500 Subject: [PATCH 058/189] Propagate arguments into local registers when calling AVM functions. --- core/src/avm2/activation.rs | 23 ++++++++++++++++++++--- core/src/avm2/function.rs | 3 +-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index c505902330aa..d70bd5213c6d 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -165,19 +165,36 @@ impl<'gc> Activation<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, action: &Avm2Function<'gc>, this: Object<'gc>, - arguments: Option>, + arguments: &[Value<'gc>], ) -> Result { let method = action.method.clone(); let scope = action.scope; let num_locals = method.body().num_locals; + let num_declared_arguments = method.method().params.len() as u32; + let local_registers = GcCell::allocate( + context.gc_context, + RegisterSet::new(num_locals + num_declared_arguments + 1), + ); + + { + let mut write = local_registers.write(context.gc_context); + *write.get_mut(0).unwrap() = this.into(); + + for i in 0..num_declared_arguments { + *write.get_mut(1 + i).unwrap() = arguments + .get(i as usize) + .cloned() + .unwrap_or(Value::Undefined); + } + } Ok(Self { method, pc: 0, this, - arguments, + arguments: None, is_executing: false, - local_registers: GcCell::allocate(context.gc_context, RegisterSet::new(num_locals)), + local_registers, return_value: None, local_scope: ScriptObject::bare_object(context.gc_context), scope, diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 8b90526e8a1c..eb18829e51c0 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -80,7 +80,6 @@ impl Avm2MethodEntry { } /// Get a reference to the ABC method entry this refers to. - #[allow(dead_code)] pub fn method(&self) -> &AbcMethod { self.abc.methods.get(self.abc_method as usize).unwrap() } @@ -140,7 +139,7 @@ impl<'gc> Executable<'gc> { Executable::Action(a2f) => { let activation = GcCell::allocate( context.gc_context, - Activation::from_action(context, &a2f, reciever, None)?, + Activation::from_action(context, &a2f, reciever, arguments)?, ); avm.insert_stack_frame(activation); From 5c0e095ab5587d42fce452022b5663e16a5ef71d Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 15:17:33 -0500 Subject: [PATCH 059/189] `getlex` does not support runtime multinames according to spec. --- core/src/avm2.rs | 11 ++++++++--- core/src/avm2/names.rs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 4ddd516ffe8a..f16fe937198a 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -359,11 +359,17 @@ impl<'gc> Avm2<'gc> { Namespace::from_abc_namespace(&self.current_abc().unwrap(), index) } - /// Retrieve a namespace from the current constant pool. + /// Retrieve a multiname from the current constant pool. fn pool_multiname(&mut self, index: Index) -> Result { 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) -> Result { + Multiname::from_abc_multiname_static(&self.current_abc().unwrap(), index) + } + /// Run a single action from a given action reader. pub fn do_next_opcode( &mut self, @@ -683,8 +689,7 @@ impl<'gc> Avm2<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result<(), Error> { - //TODO: getlex does not allow runtime multinames according to spec. - let multiname = self.pool_multiname(index)?; + let multiname = self.pool_multiname_static(index)?; let found: Result, Error> = if let Some(scope) = self.current_stack_frame().unwrap().read().scope() { Ok(scope.read().resolve(&multiname, self, context)?) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index d1b4e0bd2639..57b72e1b0731 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -223,6 +223,43 @@ impl Multiname { }) } + /// Read a static multiname from the ABC constant pool + /// + /// This function prohibits the use of runtime-qualified and late-bound + /// names. Runtime multinames will instead result in an error. + pub fn from_abc_multiname_static( + file: &AbcFile, + multiname_index: Index, + ) -> Result { + let actual_index = multiname_index.0 as usize - 1; + let abc_multiname: Result<&AbcMultiname, Error> = file + .constant_pool + .multinames + .get(actual_index) + .ok_or_else(|| format!("Unknown multiname constant {}", multiname_index.0).into()); + + Ok(match abc_multiname? { + AbcMultiname::QName { namespace, name } | AbcMultiname::QNameA { namespace, name } => { + Self { + ns: vec![Namespace::from_abc_namespace(file, namespace.clone())?], + name: abc_string_option(file, name.clone())?, + } + } + AbcMultiname::Multiname { + namespace_set, + name, + } + | AbcMultiname::MultinameA { + namespace_set, + name, + } => Self { + ns: Self::abc_namespace_set(file, namespace_set.clone())?, + name: abc_string_option(file, name.clone())?, + }, + _ => return Err(format!("Multiname {} is not static", multiname_index.0).into()), + }) + } + pub fn namespace_set(&self) -> impl Iterator { self.ns.iter() } From 5042fc1bc7c0f1e1feca2df73e952fe4fe23437d Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 15:28:00 -0500 Subject: [PATCH 060/189] Debug all multiname resolutions --- core/src/avm2/names.rs | 1 + core/src/avm2/object.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 57b72e1b0731..a8b50abc54f6 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -131,6 +131,7 @@ impl QName { /// process consists of searching each name space for a given name. /// /// The existence of a `name` of `None` indicates the `Any` name. +#[derive(Debug)] pub struct Multiname { ns: Vec, name: Option, diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 09119aaf8ed8..18e8e4161156 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -58,6 +58,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Resolve a multiname into a single QName, if any of the namespaces /// match. fn resolve_multiname(self, multiname: &Multiname) -> Option { + avm_debug!("Resolving {:?}", multiname); for ns in multiname.namespace_set() { let qname = QName::new(ns.clone(), multiname.local_name()?); From 0ff1c046972ab62963c1b15617cd6f84de53fde4 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 16:21:28 -0500 Subject: [PATCH 061/189] Impl `initproperty` --- core/src/avm2.rs | 23 +++++++++++++++ core/src/avm2/function.rs | 22 ++++++++++++++ core/src/avm2/object.rs | 17 +++++++++++ core/src/avm2/property.rs | 41 ++++++++++++++++++++++++-- core/src/avm2/script_object.rs | 54 ++++++++++++++++++++++++++++++++++ core/src/avm2/slot.rs | 14 +++++++++ 6 files changed, 169 insertions(+), 2 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index f16fe937198a..737e203295b1 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -399,6 +399,7 @@ impl<'gc> Avm2<'gc> { Op::ReturnVoid => self.op_return_void(context), Op::GetProperty { index } => self.op_get_property(context, index), Op::SetProperty { index } => self.op_set_property(context, index), + Op::InitProperty { index } => self.op_init_property(context, index), Op::DeleteProperty { index } => self.op_delete_property(context, index), Op::PushScope => self.op_push_scope(context), Op::PushWith => self.op_push_with(context), @@ -575,6 +576,28 @@ impl<'gc> Avm2<'gc> { } } + fn op_init_property( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result<(), Error> { + let value = self.pop(); + let multiname = self.pool_multiname(index)?; + let object = self.pop().as_object()?; + + if let Some(name) = object.resolve_multiname(&multiname) { + object.init_property(&name, value, self, context) + } 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(&name, value, self, context) + } + } + fn op_delete_property( &mut self, context: &mut UpdateContext<'_, 'gc, '_>, diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index eb18829e51c0..10a2128c9d85 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -379,6 +379,19 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { .set_property(name, value, avm, context, self.into()) } + fn init_property( + self, + name: &QName, + value: Value<'gc>, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result<(), Error> { + self.0 + .write(context.gc_context) + .base + .init_property(name, value, avm, context, self.into()) + } + fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool { self.0.write(gc_context).base.delete_property(multiname) } @@ -396,6 +409,15 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.write(mc).base.set_slot(id, value, mc) } + fn init_slot( + self, + id: u32, + value: Value<'gc>, + mc: MutationContext<'gc, '_>, + ) -> Result<(), Error> { + self.0.write(mc).base.init_slot(id, value, mc) + } + fn has_property(self, name: &QName) -> bool { self.0.read().base.has_property(name) } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 18e8e4161156..65bd213483d4 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -44,6 +44,15 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error>; + /// Init a property by it's QName. + fn init_property( + self, + name: &QName, + value: Value<'gc>, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result<(), Error>; + /// Retrieve a slot by it's index. fn get_slot(self, id: u32) -> Result, Error>; @@ -55,6 +64,14 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy mc: MutationContext<'gc, '_>, ) -> Result<(), Error>; + /// Initialize a slot by it's index. + fn init_slot( + self, + id: u32, + value: Value<'gc>, + mc: MutationContext<'gc, '_>, + ) -> Result<(), Error>; + /// Resolve a multiname into a single QName, if any of the namespaces /// match. fn resolve_multiname(self, multiname: &Multiname) -> Option { diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 73f6ed867f2c..9fb6d22a0df9 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -9,7 +9,6 @@ use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use enumset::{EnumSet, EnumSetType}; use gc_arena::{Collect, CollectionContext}; -use std::mem::replace; /// Attributes of properties in the AVM runtime. /// @@ -176,7 +175,7 @@ impl<'gc> Property<'gc> { value, attributes, .. } => { if !attributes.contains(ReadOnly) { - replace::>(value, new_value.into()); + *value = new_value.into(); } Ok(true) @@ -187,6 +186,44 @@ impl<'gc> Property<'gc> { } } + /// Init a property slot. + /// + /// The difference between `set` and `init` is that this function does not + /// respect `ReadOnly` and will allow initializing nominally `const` + /// properties, at least once. Virtual properties with no setter cannot be + /// initialized. + /// + /// This function returns `true` if the set has completed, or `false` if + /// it has not yet occured. If `false`, and you need to run code after the + /// set has occured, you must recursively execute the top-most frame via + /// `run_current_frame`. + pub fn init( + &mut self, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + new_value: impl Into>, + ) -> Result { + match self { + Property::Virtual { set, .. } => { + if let Some(function) = set { + let return_value = function.exec(avm, context, this, &[new_value.into()])?; + Ok(return_value.is_immediate()) + } else { + Ok(true) + } + } + Property::Stored { value, .. } => { + *value = new_value.into(); + + Ok(true) + } + Property::Slot { slot_id, .. } => this + .init_slot(*slot_id, new_value.into(), context.gc_context) + .map(|_v| true), + } + } + /// List this property's attributes. pub fn attributes(&self) -> EnumSet { match self { diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index ef4738da5ee2..389e500719f5 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -53,6 +53,18 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { .set_property(name, value, avm, context, self.into()) } + fn init_property( + self, + name: &QName, + value: Value<'gc>, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result<(), Error> { + self.0 + .write(context.gc_context) + .init_property(name, value, avm, context, self.into()) + } + fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool { self.0.write(gc_context).delete_property(multiname) } @@ -70,6 +82,15 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.write(mc).set_slot(id, value, mc) } + fn init_slot( + self, + id: u32, + value: Value<'gc>, + mc: MutationContext<'gc, '_>, + ) -> Result<(), Error> { + self.0.write(mc).init_slot(id, value, mc) + } + fn has_property(self, name: &QName) -> bool { self.0.read().has_property(name) } @@ -207,6 +228,25 @@ impl<'gc> ScriptObjectData<'gc> { Ok(()) } + pub fn init_property( + &mut self, + name: &QName, + value: Value<'gc>, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Object<'gc>, + ) -> Result<(), Error> { + if let Some(prop) = self.values.get_mut(name) { + prop.init(avm, context, this, value)?; + } else { + //TODO: Not all classes are dynamic like this + self.values + .insert(name.clone(), Property::new_dynamic_property(value)); + } + + Ok(()) + } + pub fn delete_property(&mut self, name: &QName) -> bool { let can_delete = if let Some(prop) = self.values.get(name) { prop.can_delete() @@ -244,6 +284,20 @@ impl<'gc> ScriptObjectData<'gc> { } } + /// Set a slot by it's index. + pub fn init_slot( + &mut self, + id: u32, + value: Value<'gc>, + _mc: MutationContext<'gc, '_>, + ) -> Result<(), Error> { + if let Some(slot) = self.slots.get_mut(id as usize) { + slot.init(value) + } else { + Err(format!("Slot index {} out of bounds!", id).into()) + } + } + pub fn has_property(&self, name: &QName) -> bool { self.values.get(name).is_some() } diff --git a/core/src/avm2/slot.rs b/core/src/avm2/slot.rs index 105589357116..1ddbdc269452 100644 --- a/core/src/avm2/slot.rs +++ b/core/src/avm2/slot.rs @@ -79,4 +79,18 @@ impl<'gc> Slot<'gc> { } } } + + /// Initialize a slot to a particular value. + pub fn init(&mut self, new_value: impl Into>) -> Result<(), Error> { + match self { + Self::Unoccupied => Err("Cannot initialize unoccupied slot".into()), + Self::Occupied { value, .. } => { + //TODO: Type assert + + *value = new_value.into(); + + Ok(()) + } + } + } } From 1fe73b332918f10fb79e0c7537f47ec43db10122 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 16:30:45 -0500 Subject: [PATCH 062/189] Impl `dup` --- core/src/avm2.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 737e203295b1..6fb46c86511b 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -392,6 +392,7 @@ impl<'gc> Avm2<'gc> { Op::PushTrue => self.op_push_true(), Op::PushUint { value } => self.op_push_uint(value), Op::PushUndefined => self.op_push_undefined(), + Op::Dup => self.op_dup(), Op::GetLocal { index } => self.op_get_local(index), Op::SetLocal { index } => self.op_set_local(context, index), Op::Call { num_args } => self.op_call(context, num_args), @@ -493,6 +494,12 @@ impl<'gc> Avm2<'gc> { Ok(()) } + 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(()) From 074ba94c17e4608faddfc3ca0524af2e78a53575 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 17:54:38 -0500 Subject: [PATCH 063/189] Impl `newfunction` and `newclass`. Notably, this also removes `new_closure_scope` as it is not needed. AVM1 does not capture `with` scopes in closures, but AVM2 (as well as modern ECMAScript) does. --- core/src/avm2.rs | 62 +++++++++++++++++++++++++++++++++++++-- core/src/avm2/function.rs | 39 ++++++++++++++++++++++-- core/src/avm2/object.rs | 31 ++------------------ core/src/avm2/scope.rs | 46 ----------------------------- 4 files changed, 99 insertions(+), 79 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 6fb46c86511b..6c725793c4c8 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,6 +1,7 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::{Activation, Avm2ScriptEntry}; +use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, FunctionObject}; use crate::avm2::globals::SystemPrototypes; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::object::{Object, TObject}; @@ -14,8 +15,8 @@ use std::io::Cursor; use std::rc::Rc; use swf::avm2::read::Reader; use swf::avm2::types::{ - AbcFile, Index, MethodBody, Multiname as AbcMultiname, Namespace as AbcNamespace, Op, - Script as AbcScript, + AbcFile, Class as AbcClass, Index, Method as AbcMethod, MethodBody, Multiname as AbcMultiname, + Namespace as AbcNamespace, Op, Script as AbcScript, }; use swf::read::SwfRead; @@ -370,6 +371,18 @@ impl<'gc> Avm2<'gc> { 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) -> Result { + Avm2MethodEntry::from_method_index(self.current_abc().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) -> Result { + Avm2ClassEntry::from_class_index(self.current_abc().unwrap(), index.clone()) + .ok_or_else(|| format!("Class index {} does not exist", index.0).into()) + } + /// Run a single action from a given action reader. pub fn do_next_opcode( &mut self, @@ -417,6 +430,8 @@ impl<'gc> Avm2<'gc> { Op::ConstructProp { index, num_args } => { self.op_construct_prop(context, index, num_args) } + Op::NewFunction { index } => self.op_new_function(context, index), + Op::NewClass { index } => self.op_new_class(context, index), _ => self.unknown_op(op), }; @@ -835,4 +850,47 @@ impl<'gc> Avm2<'gc> { Ok(()) } + + fn op_new_function( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result<(), Error> { + let method_entry = self.table_method(index)?; + let scope = self.current_stack_frame().unwrap().read().scope(); + + let new_fn = FunctionObject::from_abc_method( + context.gc_context, + method_entry, + scope, + self.system_prototypes.function, + ); + + self.push(new_fn); + + Ok(()) + } + + fn op_new_class( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result<(), Error> { + let base_class = self.pop().as_object()?; + let class_entry = self.table_class(index)?; + let scope = self.current_stack_frame().unwrap().read().scope(); + + let new_class = FunctionObject::from_abc_class( + self, + context, + class_entry, + base_class, + scope, + self.system_prototypes.function, + )?; + + self.push(new_class); + + Ok(()) + } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 10a2128c9d85..6938cd430ce3 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -252,10 +252,44 @@ impl<'gc> FunctionObject<'gc> { avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, class: Avm2ClassEntry, - proto: Object<'gc>, + base_class: Object<'gc>, scope: Option>>, fn_proto: Object<'gc>, ) -> Result, Error> { + let super_proto: Result, Error> = base_class + .get_property( + &QName::new(Namespace::public_namespace(), "prototype"), + avm, + context, + )? + .resolve(avm, context)? + .as_object() + .map_err(|_| { + let super_name = QName::from_abc_multiname( + &class.abc(), + class.instance().super_name.clone(), + ); + + if let Ok(super_name) = super_name { + format!( + "Could not resolve superclass prototype {:?}", + super_name.local_name() + ) + .into() + } else { + format!( + "Could not resolve superclass prototype, and got this error when getting it's name: {:?}", + super_name.unwrap_err() + ) + .into() + } + }); + let mut class_proto = super_proto?.construct(avm, context, &[])?; + + for trait_entry in class.instance().traits.iter() { + class_proto.install_trait(avm, context, class.abc(), trait_entry, scope, fn_proto)?; + } + let initializer_index = class.class().init_method.clone(); let initializer: Result = Avm2MethodEntry::from_method_index(class.abc(), initializer_index.clone()).ok_or_else( @@ -267,6 +301,7 @@ impl<'gc> FunctionObject<'gc> { .into() }, ); + let mut constr: Object<'gc> = FunctionObject(GcCell::allocate( context.gc_context, FunctionObjectData { @@ -284,7 +319,7 @@ impl<'gc> FunctionObject<'gc> { constr.install_method( context.gc_context, QName::new(Namespace::public_namespace(), "prototype"), - proto, + class_proto, ); Ok(constr) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 65bd213483d4..dccc5ae8878d 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -3,7 +3,7 @@ use crate::avm2::function::{ Avm2ClassEntry, Avm2Function, Avm2MethodEntry, Executable, FunctionObject, }; -use crate::avm2::names::{Multiname, Namespace, QName}; +use crate::avm2::names::{Multiname, QName}; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; @@ -213,39 +213,12 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy .map_err(|_e| { format!("Could not resolve superclass {:?}", super_name.local_name()).into() }); - let super_proto: Result, Error> = super_class? - .get_property( - &QName::new(Namespace::public_namespace(), "prototype"), - avm, - context, - )? - .resolve(avm, context)? - .as_object() - .map_err(|_e| { - format!( - "Could not resolve superclass prototype {:?}", - super_name.local_name() - ) - .into() - }); - let mut class_proto = super_proto?.construct(avm, context, &[])?; - - for trait_entry in type_entry.instance().traits.iter() { - class_proto.install_trait( - avm, - context, - type_entry.abc(), - trait_entry, - scope, - fn_proto, - )?; - } let class = FunctionObject::from_abc_class( avm, context, type_entry.clone(), - class_proto, + super_class?, scope, fn_proto, )?; diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index 2c2991d97e46..585dfcad208d 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -72,52 +72,6 @@ impl<'gc> Scope<'gc> { self.parent } - /// Construct a closure scope to be used as the scope stack when invoking a - /// function. - /// - /// This function filters With scopes from the scope chain. If all scopes - /// are filtered, this function returns None, representing an empty scope - /// stack. - pub fn new_closure_scope( - mut parent: GcCell<'gc, Self>, - mc: MutationContext<'gc, '_>, - ) -> Option> { - let mut bottom_scope = None; - let mut top_scope: Option> = None; - - loop { - if parent.read().class != ScopeClass::With { - let next_scope = GcCell::allocate( - mc, - Self { - parent: None, - class: parent.read().class, - values: parent.read().values, - }, - ); - - if bottom_scope.is_none() { - bottom_scope = Some(next_scope); - } - - if let Some(ref scope) = top_scope { - scope.write(mc).parent = Some(next_scope); - } - - top_scope = Some(next_scope); - } - - let grandparent = parent.read().parent; - if let Some(grandparent) = grandparent { - parent = grandparent; - } else { - break; - } - } - - bottom_scope - } - /// Returns a reference to the current local scope object. pub fn locals(&self) -> &Object<'gc> { &self.values From 1d1bad1ab48fb515f94a5a72f7cbe59509959368 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 18:14:07 -0500 Subject: [PATCH 064/189] Impl `getglobalscope` --- core/src/avm2.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 6c725793c4c8..aa84df7f9ab6 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -419,6 +419,7 @@ impl<'gc> Avm2<'gc> { Op::PushWith => self.op_push_with(context), Op::PopScope => self.op_pop_scope(context), Op::GetScopeObject { index } => self.op_get_scope_object(index), + 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), @@ -692,6 +693,27 @@ impl<'gc> Avm2<'gc> { Ok(()) } + 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, '_>, From 7201f6c4feb52790aa286bda61c02558ba7b878c Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 18:24:10 -0500 Subject: [PATCH 065/189] Impl `debug`, `debugfile` and `debugline`. --- core/src/avm2.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index aa84df7f9ab6..f14868a0c761 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -433,6 +433,13 @@ impl<'gc> Avm2<'gc> { } Op::NewFunction { index } => self.op_new_function(context, index), Op::NewClass { index } => self.op_new_class(context, index), + 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), }; @@ -915,4 +922,39 @@ impl<'gc> Avm2<'gc> { Ok(()) } + + #[allow(unused_variables)] + fn op_debug( + &mut self, + is_local_register: bool, + register_name: Index, + 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) -> 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(()) + } } From 843de29460a1154e685bd577611b35e340068ef0 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 18:44:51 -0500 Subject: [PATCH 066/189] Impl `newobject` --- core/src/avm2.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index f14868a0c761..3613aa666c71 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -7,6 +7,7 @@ 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_object::ScriptObject; use crate::avm2::value::Value; use crate::context::UpdateContext; use crate::tag_utils::SwfSlice; @@ -431,6 +432,8 @@ impl<'gc> Avm2<'gc> { Op::ConstructProp { index, num_args } => { self.op_construct_prop(context, index, num_args) } + 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::Debug { @@ -880,6 +883,36 @@ impl<'gc> Avm2<'gc> { Ok(()) } + 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> { + let object = ScriptObject::object(context.gc_context, self.system_prototypes.object); + + for _ in 0..num_args { + let value = self.pop(); + let name = self.pop(); + + object.set_property( + &QName::new(Namespace::public_namespace(), name.as_string()?), + value, + self, + context, + )?; + } + + self.push(object); + + Ok(()) + } + fn op_new_function( &mut self, context: &mut UpdateContext<'_, 'gc, '_>, From 38b1524a49902dca599c4d02c64f44f4830b92c1 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 19:02:53 -0500 Subject: [PATCH 067/189] Fix the error messages for `findpropstrict` and `getlex` to be more informative. --- core/src/avm2.rs | 38 ++++++++++++++++++++------------------ core/src/avm2/scope.rs | 19 +++++++++++-------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 3613aa666c71..82e924f5270b 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -730,11 +730,12 @@ impl<'gc> Avm2<'gc> { index: Index, ) -> Result<(), Error> { let multiname = self.pool_multiname(index)?; - let result = if let Some(scope) = self.current_stack_frame().unwrap().read().scope() { - scope.read().find(&multiname, self, context)? - } else { - None - }; + let result = self + .current_stack_frame() + .unwrap() + .read() + .scope() + .and_then(|scope| scope.read().find(&multiname, self, context)); self.push(result.map(|o| o.into()).unwrap_or(Value::Undefined)); @@ -747,12 +748,12 @@ impl<'gc> Avm2<'gc> { index: Index, ) -> Result<(), Error> { let multiname = self.pool_multiname(index)?; - let found: Result, Error> = - if let Some(scope) = self.current_stack_frame().unwrap().read().scope() { - scope.read().find(&multiname, self, context)? - } else { - None - } + let found: Result, Error> = self + .current_stack_frame() + .unwrap() + .read() + .scope() + .and_then(|scope| scope.read().find(&multiname, self, context)) .ok_or_else(|| format!("Property does not exist: {:?}", multiname.local_name()).into()); let result: Value<'gc> = found?.into(); @@ -767,13 +768,14 @@ impl<'gc> Avm2<'gc> { index: Index, ) -> Result<(), Error> { let multiname = self.pool_multiname_static(index)?; - let found: Result, Error> = - if let Some(scope) = self.current_stack_frame().unwrap().read().scope() { - Ok(scope.read().resolve(&multiname, self, context)?) - } else { - Err("No objects exist on scope".into()) - }; - let result: Value<'gc> = found?.resolve(self, context)?; + let found: Result, Error>, Error> = self + .current_stack_frame() + .unwrap() + .read() + .scope() + .and_then(|scope| scope.read().resolve(&multiname, self, context)) + .ok_or_else(|| format!("Property does not exist: {:?}", multiname.local_name()).into()); + let result: Value<'gc> = found??.resolve(self, context)?; self.push(result); diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index 585dfcad208d..3fb8a7db6e6d 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -3,7 +3,6 @@ use crate::avm2::names::Multiname; use crate::avm2::object::{Object, TObject}; use crate::avm2::return_value::ReturnValue; -use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use gc_arena::{Collect, GcCell, MutationContext}; @@ -96,15 +95,17 @@ impl<'gc> Scope<'gc> { } /// Find an object that contains a given property in the scope stack. + /// + /// This function yields `None` if no such scope exists. pub fn find( &self, name: &Multiname, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result>, Error> { + ) -> Option> { if let Some(qname) = self.locals().resolve_multiname(name) { if self.locals().has_property(&qname) { - return Ok(Some(*self.locals())); + return Some(*self.locals()); } } @@ -112,20 +113,22 @@ impl<'gc> Scope<'gc> { return scope.find(name, avm, context); } - //TODO: This should actually be an error. - Ok(None) + None } /// Resolve a particular value in the scope chain. + /// + /// This function yields `None` if no such scope exists to provide the + /// property's value. pub fn resolve( &self, name: &Multiname, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error> { + ) -> Option, Error>> { if let Some(qname) = self.locals().resolve_multiname(name) { if self.locals().has_property(&qname) { - return self.locals().get_property(&qname, avm, context); + return Some(self.locals().get_property(&qname, avm, context)); } } @@ -134,6 +137,6 @@ impl<'gc> Scope<'gc> { } //TODO: Should undefined variables halt execution? - Ok(Value::Undefined.into()) + None } } From f6e2ca1fe591bc837445313c99fdca0a015db1b8 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 19:12:33 -0500 Subject: [PATCH 068/189] Clean up the current set of builtins to accurately reflect the namespace hierarchy. --- core/src/avm2/globals.rs | 11 +++++------ core/src/avm2/globals/flash.rs | 3 +++ core/src/avm2/globals/flash/display.rs | 4 ++++ .../src/avm2/globals/{ => flash/display}/movieclip.rs | 4 +--- core/src/avm2/globals/{ => flash/display}/sprite.rs | 4 +--- 5 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 core/src/avm2/globals/flash.rs create mode 100644 core/src/avm2/globals/flash/display.rs rename core/src/avm2/globals/{ => flash/display}/movieclip.rs (90%) rename core/src/avm2/globals/{ => flash/display}/sprite.rs (90%) diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index ea8ed62d8206..bcd4d492265a 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -6,10 +6,9 @@ use crate::avm2::object::{Object, TObject}; use crate::avm2::script_object::ScriptObject; use gc_arena::{Collect, MutationContext}; +mod flash; mod function; -mod movieclip; mod object; -mod sprite; /// This structure represents all system builtins' prototypes. #[derive(Clone, Collect)] @@ -30,8 +29,8 @@ pub fn construct_global_scope<'gc>( let object_proto = ScriptObject::bare_object(mc); let function_proto = function::create_proto(mc, object_proto); - let sprite_proto = sprite::create_proto(mc, object_proto, function_proto); - let movieclip_proto = movieclip::create_proto(mc, sprite_proto, function_proto); + let sprite_proto = flash::display::sprite::create_proto(mc, object_proto, function_proto); + let movieclip_proto = flash::display::movieclip::create_proto(mc, sprite_proto, function_proto); object::fill_proto(mc, object_proto, function_proto); @@ -74,7 +73,7 @@ pub fn construct_global_scope<'gc>( QName::new(Namespace::package("flash.display"), "Sprite"), FunctionObject::from_builtin_constr( mc, - sprite::constructor, + flash::display::sprite::constructor, sprite_proto, function_proto, ) @@ -88,7 +87,7 @@ pub fn construct_global_scope<'gc>( QName::new(Namespace::package("flash.display"), "MovieClip"), FunctionObject::from_builtin_constr( mc, - movieclip::constructor, + flash::display::movieclip::constructor, movieclip_proto, function_proto, ) diff --git a/core/src/avm2/globals/flash.rs b/core/src/avm2/globals/flash.rs new file mode 100644 index 000000000000..ba16b1402e40 --- /dev/null +++ b/core/src/avm2/globals/flash.rs @@ -0,0 +1,3 @@ +//! `flash` namespace + +pub mod display; diff --git a/core/src/avm2/globals/flash/display.rs b/core/src/avm2/globals/flash/display.rs new file mode 100644 index 000000000000..40c0fbc01661 --- /dev/null +++ b/core/src/avm2/globals/flash/display.rs @@ -0,0 +1,4 @@ +//! `flash.display` namespace + +pub mod movieclip; +pub mod sprite; diff --git a/core/src/avm2/globals/movieclip.rs b/core/src/avm2/globals/flash/display/movieclip.rs similarity index 90% rename from core/src/avm2/globals/movieclip.rs rename to core/src/avm2/globals/flash/display/movieclip.rs index b1e9a2900067..f718717e5654 100644 --- a/core/src/avm2/globals/movieclip.rs +++ b/core/src/avm2/globals/flash/display/movieclip.rs @@ -25,7 +25,5 @@ pub fn create_proto<'gc>( _fn_proto: Object<'gc>, ) -> Object<'gc> { // TODO: Use `StageObject` here. - let movieclip_proto = ScriptObject::object(mc, super_proto); - - movieclip_proto + ScriptObject::object(mc, super_proto) } diff --git a/core/src/avm2/globals/sprite.rs b/core/src/avm2/globals/flash/display/sprite.rs similarity index 90% rename from core/src/avm2/globals/sprite.rs rename to core/src/avm2/globals/flash/display/sprite.rs index 29e522f111cc..a94194ec0304 100644 --- a/core/src/avm2/globals/sprite.rs +++ b/core/src/avm2/globals/flash/display/sprite.rs @@ -25,7 +25,5 @@ pub fn create_proto<'gc>( _fn_proto: Object<'gc>, ) -> Object<'gc> { // TODO: Use `StageObject` here. - let sprite_proto = ScriptObject::object(mc, super_proto); - - sprite_proto + ScriptObject::object(mc, super_proto) } From a557867c7167c729502c89bbf3f75f9d3ae5ee2c Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 19:39:12 -0500 Subject: [PATCH 069/189] Add the entire class hierarchy of `MovieClip` and `Sprite`. --- core/src/avm2/globals.rs | 182 +++++++++++------- core/src/avm2/globals/flash.rs | 1 + core/src/avm2/globals/flash/display.rs | 3 + .../globals/flash/display/displayobject.rs | 29 +++ .../flash/display/displayobjectcontainer.rs | 29 +++ .../flash/display/interactiveobject.rs | 29 +++ core/src/avm2/globals/flash/events.rs | 3 + .../globals/flash/events/eventdispatcher.rs | 29 +++ 8 files changed, 240 insertions(+), 65 deletions(-) create mode 100644 core/src/avm2/globals/flash/display/displayobject.rs create mode 100644 core/src/avm2/globals/flash/display/displayobjectcontainer.rs create mode 100644 core/src/avm2/globals/flash/display/interactiveobject.rs create mode 100644 core/src/avm2/globals/flash/events.rs create mode 100644 core/src/avm2/globals/flash/events/eventdispatcher.rs diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index bcd4d492265a..f0d783e594dc 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -1,6 +1,6 @@ //! Global scope built-ins -use crate::avm2::function::FunctionObject; +use crate::avm2::function::{FunctionObject, NativeFunction}; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::script_object::ScriptObject; @@ -18,6 +18,26 @@ pub struct SystemPrototypes<'gc> { pub function: Object<'gc>, } +fn class<'gc>( + mc: MutationContext<'gc, '_>, + mut global_scope: Object<'gc>, + package: &str, + name: &str, + constr: NativeFunction<'gc>, + proto: Object<'gc>, + fn_proto: Object<'gc>, +) { + global_scope + .install_dynamic_property( + mc, + QName::new(Namespace::package(package), name), + FunctionObject::from_builtin_constr(mc, constr, proto, fn_proto) + .unwrap() + .into(), + ) + .unwrap(); +} + /// Construct a new global scope. /// /// This function returns both the global scope object, as well as all builtin @@ -25,76 +45,108 @@ pub struct SystemPrototypes<'gc> { pub fn construct_global_scope<'gc>( mc: MutationContext<'gc, '_>, ) -> (Object<'gc>, SystemPrototypes<'gc>) { - let mut global_scope = ScriptObject::bare_object(mc); + let gs = ScriptObject::bare_object(mc); + // public / root package let object_proto = ScriptObject::bare_object(mc); - let function_proto = function::create_proto(mc, object_proto); - let sprite_proto = flash::display::sprite::create_proto(mc, object_proto, function_proto); - let movieclip_proto = flash::display::movieclip::create_proto(mc, sprite_proto, function_proto); + let fn_proto = function::create_proto(mc, object_proto); + + object::fill_proto(mc, object_proto, fn_proto); - object::fill_proto(mc, object_proto, function_proto); + class( + mc, + gs, + "", + "Object", + object::constructor, + object_proto, + fn_proto, + ); + class( + mc, + gs, + "", + "Function", + function::constructor, + fn_proto, + fn_proto, + ); + + // package `flash.events` + let eventdispatcher_proto = + flash::events::eventdispatcher::create_proto(mc, object_proto, fn_proto); + + class( + mc, + gs, + "flash.events", + "EventDispatcher", + flash::events::eventdispatcher::constructor, + eventdispatcher_proto, + fn_proto, + ); + + // package `flash.display` + let displayobject_proto = + flash::display::displayobject::create_proto(mc, eventdispatcher_proto, fn_proto); + let interactiveobject_proto = + flash::display::interactiveobject::create_proto(mc, displayobject_proto, fn_proto); + let displayobjectcontainer_proto = + flash::display::displayobjectcontainer::create_proto(mc, interactiveobject_proto, fn_proto); + let sprite_proto = + flash::display::sprite::create_proto(mc, displayobjectcontainer_proto, fn_proto); + let movieclip_proto = flash::display::movieclip::create_proto(mc, sprite_proto, fn_proto); + + class( + mc, + gs, + "flash.display", + "DisplayObject", + flash::display::displayobject::constructor, + displayobject_proto, + fn_proto, + ); + class( + mc, + gs, + "flash.display", + "InteractiveObject", + flash::display::interactiveobject::constructor, + interactiveobject_proto, + fn_proto, + ); + class( + mc, + gs, + "flash.display", + "DisplayObjectContainer", + flash::display::displayobjectcontainer::constructor, + sprite_proto, + fn_proto, + ); + class( + mc, + gs, + "flash.display", + "Sprite", + flash::display::sprite::constructor, + sprite_proto, + fn_proto, + ); + class( + mc, + gs, + "flash.display", + "MovieClip", + flash::display::movieclip::constructor, + movieclip_proto, + fn_proto, + ); let system_prototypes = SystemPrototypes { object: object_proto, - function: function_proto, + function: fn_proto, }; - global_scope - .install_dynamic_property( - mc, - QName::new(Namespace::public_namespace(), "Object"), - FunctionObject::from_builtin_constr( - mc, - object::constructor, - object_proto, - function_proto, - ) - .unwrap() - .into(), - ) - .unwrap(); - global_scope - .install_dynamic_property( - mc, - QName::new(Namespace::public_namespace(), "Function"), - FunctionObject::from_builtin_constr( - mc, - function::constructor, - function_proto, - function_proto, - ) - .unwrap() - .into(), - ) - .unwrap(); - global_scope - .install_dynamic_property( - mc, - QName::new(Namespace::package("flash.display"), "Sprite"), - FunctionObject::from_builtin_constr( - mc, - flash::display::sprite::constructor, - sprite_proto, - function_proto, - ) - .unwrap() - .into(), - ) - .unwrap(); - global_scope - .install_dynamic_property( - mc, - QName::new(Namespace::package("flash.display"), "MovieClip"), - FunctionObject::from_builtin_constr( - mc, - flash::display::movieclip::constructor, - movieclip_proto, - function_proto, - ) - .unwrap() - .into(), - ) - .unwrap(); - - (global_scope, system_prototypes) + (gs, system_prototypes) } diff --git a/core/src/avm2/globals/flash.rs b/core/src/avm2/globals/flash.rs index ba16b1402e40..18e6d2b4e14a 100644 --- a/core/src/avm2/globals/flash.rs +++ b/core/src/avm2/globals/flash.rs @@ -1,3 +1,4 @@ //! `flash` namespace pub mod display; +pub mod events; diff --git a/core/src/avm2/globals/flash/display.rs b/core/src/avm2/globals/flash/display.rs index 40c0fbc01661..c0dc34135098 100644 --- a/core/src/avm2/globals/flash/display.rs +++ b/core/src/avm2/globals/flash/display.rs @@ -1,4 +1,7 @@ //! `flash.display` namespace +pub mod displayobject; +pub mod displayobjectcontainer; +pub mod interactiveobject; pub mod movieclip; pub mod sprite; diff --git a/core/src/avm2/globals/flash/display/displayobject.rs b/core/src/avm2/globals/flash/display/displayobject.rs new file mode 100644 index 000000000000..cf8192ef0f7a --- /dev/null +++ b/core/src/avm2/globals/flash/display/displayobject.rs @@ -0,0 +1,29 @@ +//! `flash.display.DisplayObject` builtin/prototype + +use crate::avm2::object::Object; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::script_object::ScriptObject; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::MutationContext; + +/// Implements `flash.display.DisplayObject`'s constructor. +pub fn constructor<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined.into()) +} + +/// Construct `DisplayObject.prototype`. +pub fn create_proto<'gc>( + mc: MutationContext<'gc, '_>, + super_proto: Object<'gc>, + _fn_proto: Object<'gc>, +) -> Object<'gc> { + // TODO: Use `StageObject` here. + ScriptObject::object(mc, super_proto) +} diff --git a/core/src/avm2/globals/flash/display/displayobjectcontainer.rs b/core/src/avm2/globals/flash/display/displayobjectcontainer.rs new file mode 100644 index 000000000000..bd63e1f94435 --- /dev/null +++ b/core/src/avm2/globals/flash/display/displayobjectcontainer.rs @@ -0,0 +1,29 @@ +//! `flash.display.DisplayObjectContainer` builtin/prototype + +use crate::avm2::object::Object; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::script_object::ScriptObject; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::MutationContext; + +/// Implements `flash.display.DisplayObjectContainer`'s constructor. +pub fn constructor<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined.into()) +} + +/// Construct `DisplayObjectContainer.prototype`. +pub fn create_proto<'gc>( + mc: MutationContext<'gc, '_>, + super_proto: Object<'gc>, + _fn_proto: Object<'gc>, +) -> Object<'gc> { + // TODO: Use `StageObject` here. + ScriptObject::object(mc, super_proto) +} diff --git a/core/src/avm2/globals/flash/display/interactiveobject.rs b/core/src/avm2/globals/flash/display/interactiveobject.rs new file mode 100644 index 000000000000..4500d5e3a4e2 --- /dev/null +++ b/core/src/avm2/globals/flash/display/interactiveobject.rs @@ -0,0 +1,29 @@ +//! `flash.display.InteractiveObject` builtin/prototype + +use crate::avm2::object::Object; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::script_object::ScriptObject; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::MutationContext; + +/// Implements `flash.display.InteractiveObject`'s constructor. +pub fn constructor<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined.into()) +} + +/// Construct `InteractiveObject.prototype`. +pub fn create_proto<'gc>( + mc: MutationContext<'gc, '_>, + super_proto: Object<'gc>, + _fn_proto: Object<'gc>, +) -> Object<'gc> { + // TODO: Use `StageObject` here. + ScriptObject::object(mc, super_proto) +} diff --git a/core/src/avm2/globals/flash/events.rs b/core/src/avm2/globals/flash/events.rs new file mode 100644 index 000000000000..afd4e106bae3 --- /dev/null +++ b/core/src/avm2/globals/flash/events.rs @@ -0,0 +1,3 @@ +//! `flash.events` namespace + +pub mod eventdispatcher; diff --git a/core/src/avm2/globals/flash/events/eventdispatcher.rs b/core/src/avm2/globals/flash/events/eventdispatcher.rs new file mode 100644 index 000000000000..10ec93ddd097 --- /dev/null +++ b/core/src/avm2/globals/flash/events/eventdispatcher.rs @@ -0,0 +1,29 @@ +//! `flash.events.EventDispatcher` builtin/prototype + +use crate::avm2::object::Object; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::script_object::ScriptObject; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::MutationContext; + +/// Implements `flash.events.EventDispatcher`'s constructor. +pub fn constructor<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined.into()) +} + +/// Construct `EventDispatcher.prototype`. +pub fn create_proto<'gc>( + mc: MutationContext<'gc, '_>, + super_proto: Object<'gc>, + _fn_proto: Object<'gc>, +) -> Object<'gc> { + // TODO: Use `StageObject` here. + ScriptObject::object(mc, super_proto) +} From 7792fd558193f24e897db6805e50c768d19a56f6 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 21:27:03 -0500 Subject: [PATCH 070/189] Impl `trace`, which is a free function rather than an opcode this time. --- core/src/avm2/globals.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index f0d783e594dc..e0585ee0876f 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -3,13 +3,30 @@ use crate::avm2::function::{FunctionObject, NativeFunction}; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, TObject}; +use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; use gc_arena::{Collect, MutationContext}; mod flash; mod function; mod object; +fn trace<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(s) = args.get(0) { + log::info!(target: "avm_trace", "{}", s.as_string()?); + } + + Ok(Value::Undefined.into()) +} + /// This structure represents all system builtins' prototypes. #[derive(Clone, Collect)] #[collect(no_drop)] @@ -18,6 +35,25 @@ pub struct SystemPrototypes<'gc> { pub function: Object<'gc>, } +/// Add a free-function builtin to the global scope. +fn function<'gc>( + mc: MutationContext<'gc, '_>, + mut global_scope: Object<'gc>, + package: &str, + name: &str, + nf: NativeFunction<'gc>, + fn_proto: Object<'gc>, +) { + global_scope + .install_dynamic_property( + mc, + QName::new(Namespace::package(package), name), + FunctionObject::from_builtin(mc, nf, fn_proto).into(), + ) + .unwrap() +} + +/// Add a class builtin to the global scope. fn class<'gc>( mc: MutationContext<'gc, '_>, mut global_scope: Object<'gc>, @@ -71,6 +107,7 @@ pub fn construct_global_scope<'gc>( fn_proto, fn_proto, ); + function(mc, gs, "", "trace", trace, fn_proto); // package `flash.events` let eventdispatcher_proto = From bf45f7f16182796edf98be3d16f7028b31f257c6 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 21:28:02 -0500 Subject: [PATCH 071/189] Fix crash when reading or writing a property that redirects to a slot. --- core/src/avm2/property.rs | 24 ++++++++++++++++++------ core/src/avm2/script_object.rs | 12 ++++++++++-- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 9fb6d22a0df9..9e9ed66dc7c7 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -155,6 +155,9 @@ impl<'gc> Property<'gc> { /// it has not yet occured. If `false`, and you need to run code after the /// set has occured, you must recursively execute the top-most frame via /// `run_current_frame`. + /// + /// This function cannot set slot properties and will panic if one + /// is encountered. pub fn set( &mut self, avm: &mut Avm2<'gc>, @@ -180,9 +183,7 @@ impl<'gc> Property<'gc> { Ok(true) } - Property::Slot { slot_id, .. } => this - .set_slot(*slot_id, new_value.into(), context.gc_context) - .map(|_v| true), + Property::Slot { slot_id, .. } => panic!("Cannot recursively set slots"), } } @@ -197,6 +198,9 @@ impl<'gc> Property<'gc> { /// it has not yet occured. If `false`, and you need to run code after the /// set has occured, you must recursively execute the top-most frame via /// `run_current_frame`. + /// + /// This function cannot initialize slot properties and will panic if one + /// is encountered. pub fn init( &mut self, avm: &mut Avm2<'gc>, @@ -218,9 +222,17 @@ impl<'gc> Property<'gc> { Ok(true) } - Property::Slot { slot_id, .. } => this - .init_slot(*slot_id, new_value.into(), context.gc_context) - .map(|_v| true), + Property::Slot { slot_id, .. } => panic!("Cannot recursively init slots"), + } + } + + /// Retrieve the slot ID of a property. + /// + /// This function yields `None` if this property is not a slot. + pub fn slot_id(&self) -> Option { + match self { + Property::Slot { slot_id, .. } => Some(*slot_id), + _ => None, } } diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 389e500719f5..6793e2ae9ad2 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -218,7 +218,11 @@ impl<'gc> ScriptObjectData<'gc> { this: Object<'gc>, ) -> Result<(), Error> { if let Some(prop) = self.values.get_mut(name) { - prop.set(avm, context, this, value)?; + if let Some(slot_id) = prop.slot_id() { + self.set_slot(slot_id, value, context.gc_context)?; + } else { + prop.set(avm, context, this, value)?; + } } else { //TODO: Not all classes are dynamic like this self.values @@ -237,7 +241,11 @@ impl<'gc> ScriptObjectData<'gc> { this: Object<'gc>, ) -> Result<(), Error> { if let Some(prop) = self.values.get_mut(name) { - prop.init(avm, context, this, value)?; + if let Some(slot_id) = prop.slot_id() { + self.init_slot(slot_id, value, context.gc_context)?; + } else { + prop.init(avm, context, this, value)?; + } } else { //TODO: Not all classes are dynamic like this self.values From dd6b0a8728712d7990a5dcc69da0e4c7553b34c4 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 22 Feb 2020 22:13:15 -0500 Subject: [PATCH 072/189] Remove unused reference to slot property fields --- core/src/avm2/property.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 9e9ed66dc7c7..986f66402035 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -183,7 +183,7 @@ impl<'gc> Property<'gc> { Ok(true) } - Property::Slot { slot_id, .. } => panic!("Cannot recursively set slots"), + Property::Slot { .. } => panic!("Cannot recursively set slots"), } } @@ -222,7 +222,7 @@ impl<'gc> Property<'gc> { Ok(true) } - Property::Slot { slot_id, .. } => panic!("Cannot recursively init slots"), + Property::Slot { .. } => panic!("Cannot recursively init slots"), } } From a7ff2de476f44a158b2248d91469cb6c05e621bd Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 23 Feb 2020 16:32:16 -0500 Subject: [PATCH 073/189] Don't spam the test log with `Resolving Multiname` messages for each scope that gets checked --- core/src/avm2.rs | 14 +++++++++----- core/src/avm2/object.rs | 1 - 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 82e924f5270b..cc4d379e9e33 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -89,12 +89,13 @@ impl<'gc> Avm2<'gc> { let abc_file = Rc::new(read.read()?); - if !abc_file.scripts.is_empty() { - let entrypoint_script: Index = Index::new(abc_file.scripts.len() as u32 - 1); + for i in (0..abc_file.scripts.len()).rev() { + let entrypoint_script: Index = Index::new(i as u32); let entrypoint: Result = - Avm2ScriptEntry::from_script_index(abc_file, entrypoint_script.clone()).ok_or_else( - || format!("Script method {} does not exist", entrypoint_script.0).into(), - ); + Avm2ScriptEntry::from_script_index(abc_file.clone(), entrypoint_script.clone()) + .ok_or_else(|| { + format!("Script method {} does not exist", entrypoint_script.0).into() + }); let entrypoint = entrypoint?; let scope = Scope::push_scope(None, self.globals(), context.gc_context); @@ -730,6 +731,7 @@ impl<'gc> Avm2<'gc> { index: Index, ) -> Result<(), Error> { let multiname = self.pool_multiname(index)?; + avm_debug!("Resolving {:?}", multiname); let result = self .current_stack_frame() .unwrap() @@ -748,6 +750,7 @@ impl<'gc> Avm2<'gc> { index: Index, ) -> Result<(), Error> { let multiname = self.pool_multiname(index)?; + avm_debug!("Resolving {:?}", multiname); let found: Result, Error> = self .current_stack_frame() .unwrap() @@ -768,6 +771,7 @@ impl<'gc> Avm2<'gc> { index: Index, ) -> Result<(), Error> { let multiname = self.pool_multiname_static(index)?; + avm_debug!("Resolving {:?}", multiname); let found: Result, Error>, Error> = self .current_stack_frame() .unwrap() diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index dccc5ae8878d..0056e863ce4f 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -75,7 +75,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Resolve a multiname into a single QName, if any of the namespaces /// match. fn resolve_multiname(self, multiname: &Multiname) -> Option { - avm_debug!("Resolving {:?}", multiname); for ns in multiname.namespace_set() { let qname = QName::new(ns.clone(), multiname.local_name()?); From 68cf9e8869da7a7333537cf6df6bc04b3dea9525 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 23 Feb 2020 17:28:28 -0500 Subject: [PATCH 074/189] Upon encountering an `Err`, dispose of the current AVM2 stack. In the future, the `unwrap_stack_frame` mechanism should be expanded upon to allow running exception handlers and recovering from a Rust error - but not today. --- core/src/avm2.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index cc4d379e9e33..3b558497ff5f 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -145,7 +145,7 @@ impl<'gc> Avm2<'gc> { Ok(()) } - /// Destroy the current stack frame (if there is one). + /// 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. @@ -175,6 +175,21 @@ impl<'gc> Avm2<'gc> { 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 @@ -449,6 +464,7 @@ impl<'gc> Avm2<'gc> { if let Err(ref e) = result { log::error!("AVM2 error: {}", e); + self.unwind_stack_frame(); return result; } } From a0ab978bed8f0b2c1281a837d701308266b0fa21 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 23 Feb 2020 22:11:02 -0500 Subject: [PATCH 075/189] Impl `callmethod`, `callproperty`, `callproplex`, `callpropvoid`, and `callstatic`. Also, implement a method table that method traits can optionally add themselves to. Also also, add the ability to invoke a method without a `this` object. This required a non-trivial refactoring of the activation machinery, and changes to the signature of `NativeFunction`, and all native AVM2 functions. --- core/src/avm2.rs | 145 +++++++++++++++++- core/src/avm2/activation.rs | 8 +- core/src/avm2/function.rs | 49 ++++-- core/src/avm2/globals.rs | 2 +- .../globals/flash/display/displayobject.rs | 2 +- .../flash/display/displayobjectcontainer.rs | 2 +- .../flash/display/interactiveobject.rs | 2 +- .../avm2/globals/flash/display/movieclip.rs | 2 +- core/src/avm2/globals/flash/display/sprite.rs | 2 +- .../globals/flash/events/eventdispatcher.rs | 2 +- core/src/avm2/globals/function.rs | 5 +- core/src/avm2/globals/object.rs | 2 +- core/src/avm2/object.rs | 52 +++++-- core/src/avm2/property.rs | 8 +- core/src/avm2/script_object.rs | 90 +++++++++-- 15 files changed, 315 insertions(+), 58 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 3b558497ff5f..36a2688fac4c 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -426,6 +426,17 @@ impl<'gc> Avm2<'gc> { Op::GetLocal { index } => self.op_get_local(index), Op::SetLocal { index } => self.op_set_local(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::ReturnValue => self.op_return_value(context), Op::ReturnVoid => self.op_return_void(context), Op::GetProperty { index } => self.op_get_property(context, index), @@ -562,14 +573,142 @@ impl<'gc> Avm2<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, arg_count: u32, ) -> Result<(), Error> { + let mut args = Vec::new(); + for _ in 0..arg_count { + args.push(self.pop()); + } + let receiver = self.pop().as_object().ok(); let function = self.pop().as_object()?; + + function.call(receiver, &args, self, context)?.push(self); + + Ok(()) + } + + fn op_call_method( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result<(), Error> { + let mut args = Vec::new(); + for _ in 0..arg_count { + args.push(self.pop()); + } let receiver = self.pop().as_object()?; + let function: Result, Error> = receiver + .get_method(index.0) + .ok_or_else(|| format!("Object method {} does not exist", index.0).into()); + + function? + .call(Some(receiver), &args, self, context)? + .push(self); + + Ok(()) + } + + fn op_call_property( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result<(), Error> { let mut args = Vec::new(); for _ in 0..arg_count { args.push(self.pop()); } + let multiname = self.pool_multiname(index)?; + let receiver = self.pop().as_object()?; + let name: Result = receiver + .resolve_multiname(&multiname) + .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); + let function = receiver + .get_property(&name?, self, context)? + .resolve(self, context)? + .as_object()?; - function.call(receiver, &args, self, context)?.push(self); + function + .call(Some(receiver), &args, self, context)? + .push(self); + + Ok(()) + } + + fn op_call_prop_lex( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result<(), Error> { + let mut args = Vec::new(); + for _ in 0..arg_count { + args.push(self.pop()); + } + let multiname = self.pool_multiname(index)?; + let receiver = self.pop().as_object()?; + let name: Result = receiver + .resolve_multiname(&multiname) + .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); + let function = receiver + .get_property(&name?, self, context)? + .resolve(self, context)? + .as_object()?; + + function.call(None, &args, self, context)?.push(self); + + Ok(()) + } + + fn op_call_prop_void( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result<(), Error> { + let mut args = Vec::new(); + for _ in 0..arg_count { + args.push(self.pop()); + } + let multiname = self.pool_multiname(index)?; + let receiver = self.pop().as_object()?; + let name: Result = receiver + .resolve_multiname(&multiname) + .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); + let function = receiver + .get_property(&name?, self, context)? + .resolve(self, context)? + .as_object()?; + + function + .call(Some(receiver), &args, self, context)? + .resolve(self, context)?; + + Ok(()) + } + + fn op_call_static( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result<(), Error> { + let mut args = Vec::new(); + for _ in 0..arg_count { + args.push(self.pop()); + } + 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_abc_method( + context.gc_context, + method, + scope, + self.system_prototypes.function, + ); + + function + .call(Some(receiver), &args, self, context)? + .push(self); Ok(()) } @@ -861,7 +1000,7 @@ impl<'gc> Avm2<'gc> { .as_object()?; let object = proto.construct(self, context, &args)?; - ctor.call(object, &args, self, context)? + ctor.call(Some(object), &args, self, context)? .resolve(self, context)?; Ok(()) @@ -899,7 +1038,7 @@ impl<'gc> Avm2<'gc> { .as_object()?; let object = proto.construct(self, context, &args)?; - ctor.call(object, &args, self, context)? + ctor.call(Some(object), &args, self, context)? .resolve(self, context)?; Ok(()) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index d70bd5213c6d..2595e88ca2f3 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -95,7 +95,7 @@ pub struct Activation<'gc> { pc: usize, /// The immutable value of `this`. - this: Object<'gc>, + this: Option>, /// The arguments this function was called by. arguments: Option>, @@ -151,7 +151,7 @@ impl<'gc> Activation<'gc> { Ok(Self { method, pc: 0, - this: global, + this: Some(global), arguments: None, is_executing: false, local_registers, @@ -164,7 +164,7 @@ impl<'gc> Activation<'gc> { pub fn from_action( context: &mut UpdateContext<'_, 'gc, '_>, action: &Avm2Function<'gc>, - this: Object<'gc>, + this: Option>, arguments: &[Value<'gc>], ) -> Result { let method = action.method.clone(); @@ -178,7 +178,7 @@ impl<'gc> Activation<'gc> { { let mut write = local_registers.write(context.gc_context); - *write.get_mut(0).unwrap() = this.into(); + *write.get_mut(0).unwrap() = this.map(|t| t.into()).unwrap_or(Value::Null); for i in 0..num_declared_arguments { *write.get_mut(1 + i).unwrap() = arguments diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 6938cd430ce3..c36c5a8e7bf2 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -34,7 +34,7 @@ use swf::avm2::types::{ pub type NativeFunction<'gc> = fn( &mut Avm2<'gc>, &mut UpdateContext<'_, 'gc, '_>, - Object<'gc>, + Option>, &[Value<'gc>], ) -> Result, Error>; @@ -131,7 +131,7 @@ impl<'gc> Executable<'gc> { &self, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - reciever: Object<'gc>, + reciever: Option>, arguments: &[Value<'gc>], ) -> Result, Error> { match self { @@ -316,11 +316,11 @@ impl<'gc> FunctionObject<'gc> { constr.install_trait(avm, context, class.abc(), trait_entry, scope, fn_proto)?; } - constr.install_method( + constr.install_dynamic_property( context.gc_context, QName::new(Namespace::public_namespace(), "prototype"), - class_proto, - ); + class_proto.into(), + )?; Ok(constr) } @@ -453,6 +453,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.write(mc).base.init_slot(id, value, mc) } + fn get_method(self, id: u32) -> Option> { + self.0.read().base.get_method(id) + } + fn has_property(self, name: &QName) -> bool { self.0.read().base.has_property(name) } @@ -465,9 +469,13 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.as_ptr() as *const ObjectPtr } + fn as_executable(&self) -> Option> { + self.0.read().exec.clone() + } + fn call( self, - reciever: Object<'gc>, + reciever: Option>, arguments: &[Value<'gc>], avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -499,26 +507,43 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { .into()) } - fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) { - self.0.write(mc).base.install_method(name, function) + fn install_method( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + disp_id: u32, + function: Object<'gc>, + ) { + self.0 + .write(mc) + .base + .install_method(name, disp_id, function) } fn install_getter( &mut self, mc: MutationContext<'gc, '_>, name: QName, - function: Executable<'gc>, + disp_id: u32, + function: Object<'gc>, ) -> Result<(), Error> { - self.0.write(mc).base.install_getter(name, function) + self.0 + .write(mc) + .base + .install_getter(name, disp_id, function) } fn install_setter( &mut self, mc: MutationContext<'gc, '_>, name: QName, - function: Executable<'gc>, + disp_id: u32, + function: Object<'gc>, ) -> Result<(), Error> { - self.0.write(mc).base.install_setter(name, function) + self.0 + .write(mc) + .base + .install_setter(name, disp_id, function) } fn install_dynamic_property( diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index e0585ee0876f..7dcf3b68b9d2 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -17,7 +17,7 @@ mod object; fn trace<'gc>( _avm: &mut Avm2<'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, - _this: Object<'gc>, + _this: Option>, args: &[Value<'gc>], ) -> Result, Error> { if let Some(s) = args.get(0) { diff --git a/core/src/avm2/globals/flash/display/displayobject.rs b/core/src/avm2/globals/flash/display/displayobject.rs index cf8192ef0f7a..9542041a28ed 100644 --- a/core/src/avm2/globals/flash/display/displayobject.rs +++ b/core/src/avm2/globals/flash/display/displayobject.rs @@ -12,7 +12,7 @@ use gc_arena::MutationContext; pub fn constructor<'gc>( _avm: &mut Avm2<'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, - _this: Object<'gc>, + _this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { Ok(Value::Undefined.into()) diff --git a/core/src/avm2/globals/flash/display/displayobjectcontainer.rs b/core/src/avm2/globals/flash/display/displayobjectcontainer.rs index bd63e1f94435..74a5d248ea9a 100644 --- a/core/src/avm2/globals/flash/display/displayobjectcontainer.rs +++ b/core/src/avm2/globals/flash/display/displayobjectcontainer.rs @@ -12,7 +12,7 @@ use gc_arena::MutationContext; pub fn constructor<'gc>( _avm: &mut Avm2<'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, - _this: Object<'gc>, + _this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { Ok(Value::Undefined.into()) diff --git a/core/src/avm2/globals/flash/display/interactiveobject.rs b/core/src/avm2/globals/flash/display/interactiveobject.rs index 4500d5e3a4e2..fe736a3a8e20 100644 --- a/core/src/avm2/globals/flash/display/interactiveobject.rs +++ b/core/src/avm2/globals/flash/display/interactiveobject.rs @@ -12,7 +12,7 @@ use gc_arena::MutationContext; pub fn constructor<'gc>( _avm: &mut Avm2<'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, - _this: Object<'gc>, + _this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { Ok(Value::Undefined.into()) diff --git a/core/src/avm2/globals/flash/display/movieclip.rs b/core/src/avm2/globals/flash/display/movieclip.rs index f718717e5654..6e39e7c94783 100644 --- a/core/src/avm2/globals/flash/display/movieclip.rs +++ b/core/src/avm2/globals/flash/display/movieclip.rs @@ -12,7 +12,7 @@ use gc_arena::MutationContext; pub fn constructor<'gc>( _avm: &mut Avm2<'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, - _this: Object<'gc>, + _this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { Ok(Value::Undefined.into()) diff --git a/core/src/avm2/globals/flash/display/sprite.rs b/core/src/avm2/globals/flash/display/sprite.rs index a94194ec0304..110afca1526e 100644 --- a/core/src/avm2/globals/flash/display/sprite.rs +++ b/core/src/avm2/globals/flash/display/sprite.rs @@ -12,7 +12,7 @@ use gc_arena::MutationContext; pub fn constructor<'gc>( _avm: &mut Avm2<'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, - _this: Object<'gc>, + _this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { Ok(Value::Undefined.into()) diff --git a/core/src/avm2/globals/flash/events/eventdispatcher.rs b/core/src/avm2/globals/flash/events/eventdispatcher.rs index 10ec93ddd097..ea16792cf768 100644 --- a/core/src/avm2/globals/flash/events/eventdispatcher.rs +++ b/core/src/avm2/globals/flash/events/eventdispatcher.rs @@ -12,7 +12,7 @@ use gc_arena::MutationContext; pub fn constructor<'gc>( _avm: &mut Avm2<'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, - _this: Object<'gc>, + _this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { Ok(Value::Undefined.into()) diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs index 9eb58c8a23d4..878e42ebd13e 100644 --- a/core/src/avm2/globals/function.rs +++ b/core/src/avm2/globals/function.rs @@ -14,7 +14,7 @@ use gc_arena::MutationContext; pub fn constructor<'gc>( _avm: &mut Avm2<'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, - _this: Object<'gc>, + _this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { Ok(Value::Undefined.into()) @@ -24,7 +24,7 @@ pub fn constructor<'gc>( fn to_string<'gc>( _: &mut Avm2<'gc>, _: &mut UpdateContext<'_, 'gc, '_>, - _: Object<'gc>, + _: Option>, _: &[Value<'gc>], ) -> Result, Error> { Ok(ReturnValue::Immediate("[type Function]".into())) @@ -43,6 +43,7 @@ pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc function_proto.install_method( gc_context, QName::new(Namespace::public_namespace(), "toString"), + 0, FunctionObject::from_builtin(gc_context, to_string, function_proto), ); diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 9982feaa5912..98c1b6b9839c 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -11,7 +11,7 @@ use gc_arena::MutationContext; pub fn constructor<'gc>( _avm: &mut Avm2<'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, - _this: Object<'gc>, + _this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { Ok(Value::Undefined.into()) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 0056e863ce4f..7765ac8a11c4 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1,8 +1,6 @@ //! AVM2 objects. -use crate::avm2::function::{ - Avm2ClassEntry, Avm2Function, Avm2MethodEntry, Executable, FunctionObject, -}; +use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, Executable, FunctionObject}; use crate::avm2::names::{Multiname, QName}; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; @@ -72,6 +70,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy mc: MutationContext<'gc, '_>, ) -> Result<(), Error>; + /// Retrieve a method by it's index. + fn get_method(self, id: u32) -> Option>; + /// Resolve a multiname into a single QName, if any of the namespaces /// match. fn resolve_multiname(self, multiname: &Multiname) -> Option { @@ -113,14 +114,21 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn proto(&self) -> Option>; /// Install a method (or any other non-slot value) on an object. - fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>); + fn install_method( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + disp_id: u32, + function: Object<'gc>, + ); /// Install a getter method on an object property. fn install_getter( &mut self, mc: MutationContext<'gc, '_>, name: QName, - function: Executable<'gc>, + disp_id: u32, + function: Object<'gc>, ) -> Result<(), Error>; /// Install a setter method on an object property. @@ -128,7 +136,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &mut self, mc: MutationContext<'gc, '_>, name: QName, - function: Executable<'gc>, + disp_id: u32, + function: Object<'gc>, ) -> Result<(), Error>; /// Install a dynamic or built-in value property on an object. @@ -183,21 +192,29 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy }; self.install_slot(context.gc_context, trait_name, *slot_id, value); } - AbcTraitKind::Method { method, .. } => { + AbcTraitKind::Method { + disp_id, method, .. + } => { let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); let function = FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto); - self.install_method(context.gc_context, trait_name, function); + self.install_method(context.gc_context, trait_name, *disp_id, function); } - AbcTraitKind::Getter { method, .. } => { + AbcTraitKind::Getter { + disp_id, method, .. + } => { let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); - let exec = Avm2Function::from_method(method, scope).into(); - self.install_getter(context.gc_context, trait_name, exec)?; + let function = + FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto); + self.install_getter(context.gc_context, trait_name, *disp_id, function)?; } - AbcTraitKind::Setter { method, .. } => { + AbcTraitKind::Setter { + disp_id, method, .. + } => { let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); - let exec = Avm2Function::from_method(method, scope).into(); - self.install_setter(context.gc_context, trait_name, exec)?; + let function = + FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto); + self.install_setter(context.gc_context, trait_name, *disp_id, function)?; } AbcTraitKind::Class { slot_id, class } => { let type_entry = Avm2ClassEntry::from_class_index(abc, class.clone()).unwrap(); @@ -251,7 +268,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Call the object. fn call( self, - _reciever: Object<'gc>, + _reciever: Option>, _arguments: &[Value<'gc>], _avm: &mut Avm2<'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, @@ -282,6 +299,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Get a raw pointer value for this object. fn as_ptr(&self) -> *const ObjectPtr; + + /// Get this object's `Executable`, if it has one. + fn as_executable(&self) -> Option> { + None + } } pub enum ObjectPtr {} diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 986f66402035..2c31439419e0 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -142,7 +142,7 @@ impl<'gc> Property<'gc> { this: Object<'gc>, ) -> Result, Error> { match self { - Property::Virtual { get: Some(get), .. } => get.exec(avm, context, this, &[]), + Property::Virtual { get: Some(get), .. } => get.exec(avm, context, Some(this), &[]), Property::Virtual { get: None, .. } => Ok(Value::Undefined.into()), Property::Stored { value, .. } => Ok(value.to_owned().into()), Property::Slot { slot_id, .. } => this.get_slot(*slot_id).map(|v| v.into()), @@ -168,7 +168,8 @@ impl<'gc> Property<'gc> { match self { Property::Virtual { set, .. } => { if let Some(function) = set { - let return_value = function.exec(avm, context, this, &[new_value.into()])?; + let return_value = + function.exec(avm, context, Some(this), &[new_value.into()])?; Ok(return_value.is_immediate()) } else { Ok(true) @@ -211,7 +212,8 @@ impl<'gc> Property<'gc> { match self { Property::Virtual { set, .. } => { if let Some(function) = set { - let return_value = function.exec(avm, context, this, &[new_value.into()])?; + let return_value = + function.exec(avm, context, Some(this), &[new_value.into()])?; Ok(return_value.is_immediate()) } else { Ok(true) diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 6793e2ae9ad2..53f7d676de23 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -27,6 +27,9 @@ pub struct ScriptObjectData<'gc> { /// Slots stored on this object. slots: Vec>, + /// Methods stored on this object. + methods: Vec>>, + /// Implicit prototype (or declared base class) of this script object. proto: Option>, } @@ -91,6 +94,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.write(mc).init_slot(id, value, mc) } + fn get_method(self, id: u32) -> Option> { + self.0.read().get_method(id) + } + fn has_property(self, name: &QName) -> bool { self.0.read().has_property(name) } @@ -113,26 +120,34 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { Ok(ScriptObject::object(context.gc_context, this)) } - fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) { - self.0.write(mc).install_method(name, function) + fn install_method( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + disp_id: u32, + function: Object<'gc>, + ) { + self.0.write(mc).install_method(name, disp_id, function) } fn install_getter( &mut self, mc: MutationContext<'gc, '_>, name: QName, - function: Executable<'gc>, + disp_id: u32, + function: Object<'gc>, ) -> Result<(), Error> { - self.0.write(mc).install_getter(name, function) + self.0.write(mc).install_getter(name, disp_id, function) } fn install_setter( &mut self, mc: MutationContext<'gc, '_>, name: QName, - function: Executable<'gc>, + disp_id: u32, + function: Object<'gc>, ) -> Result<(), Error> { - self.0.write(mc).install_setter(name, function) + self.0.write(mc).install_setter(name, disp_id, function) } fn install_dynamic_property( @@ -189,6 +204,7 @@ impl<'gc> ScriptObjectData<'gc> { ScriptObjectData { values: HashMap::new(), slots: Vec::new(), + methods: Vec::new(), proto, } } @@ -306,6 +322,11 @@ impl<'gc> ScriptObjectData<'gc> { } } + /// Retrieve a method from the method table. + pub fn get_method(&self, id: u32) -> Option> { + self.methods.get(id as usize).and_then(|v| *v) + } + pub fn has_property(&self, name: &QName) -> bool { self.values.get(name).is_some() } @@ -315,7 +336,16 @@ impl<'gc> ScriptObjectData<'gc> { } /// Install a method into the object. - pub fn install_method(&mut self, name: QName, function: Object<'gc>) { + pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) { + if disp_id > 0 { + if self.methods.len() <= disp_id as usize { + self.methods + .resize_with(disp_id as usize + 1, Default::default); + } + + *self.methods.get_mut(disp_id as usize).unwrap() = Some(function); + } + self.values.insert(name, Property::new_method(function)); } @@ -324,7 +354,26 @@ impl<'gc> ScriptObjectData<'gc> { /// This is a little more complicated than methods, since virtual property /// slots can be installed in two parts. Thus, we need to support /// installing them in either order. - pub fn install_getter(&mut self, name: QName, function: Executable<'gc>) -> Result<(), Error> { + pub fn install_getter( + &mut self, + name: QName, + disp_id: u32, + function: Object<'gc>, + ) -> Result<(), Error> { + let executable: Result, Error> = function + .as_executable() + .ok_or_else(|| "Attempted to install getter without a valid method".into()); + let executable = executable?; + + if disp_id > 0 { + if self.methods.len() <= disp_id as usize { + self.methods + .resize_with(disp_id as usize + 1, Default::default); + } + + *self.methods.get_mut(disp_id as usize).unwrap() = Some(function); + } + if !self.values.contains_key(&name) { self.values.insert(name.clone(), Property::new_virtual()); } @@ -332,7 +381,7 @@ impl<'gc> ScriptObjectData<'gc> { self.values .get_mut(&name) .unwrap() - .install_virtual_getter(function) + .install_virtual_getter(executable) } /// Install a setter into the object. @@ -340,7 +389,26 @@ impl<'gc> ScriptObjectData<'gc> { /// This is a little more complicated than methods, since virtual property /// slots can be installed in two parts. Thus, we need to support /// installing them in either order. - pub fn install_setter(&mut self, name: QName, function: Executable<'gc>) -> Result<(), Error> { + pub fn install_setter( + &mut self, + name: QName, + disp_id: u32, + function: Object<'gc>, + ) -> Result<(), Error> { + let executable: Result, Error> = function + .as_executable() + .ok_or_else(|| "Attempted to install setter without a valid method".into()); + let executable = executable?; + + if disp_id > 0 { + if self.methods.len() <= disp_id as usize { + self.methods + .resize_with(disp_id as usize + 1, Default::default); + } + + *self.methods.get_mut(disp_id as usize).unwrap() = Some(function); + } + if !self.values.contains_key(&name) { self.values.insert(name.clone(), Property::new_virtual()); } @@ -348,7 +416,7 @@ impl<'gc> ScriptObjectData<'gc> { self.values .get_mut(&name) .unwrap() - .install_virtual_setter(function) + .install_virtual_setter(executable) } pub fn install_dynamic_property( From 7d576203c9b722a935b465882c6a6cd856b3d2ba Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 23 Feb 2020 22:24:23 -0500 Subject: [PATCH 076/189] Impl `coerce_a`. This currently treats `coerce_a` as a no-op. Strictly speaking, this is for type verification purposes, but we currently don't type-verify ABC code. Ergo, this requires no VM support at this time. --- core/src/avm2.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 36a2688fac4c..d82a513a5c70 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -463,6 +463,7 @@ impl<'gc> Avm2<'gc> { 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::Debug { is_local_register, register_name, @@ -1117,6 +1118,10 @@ impl<'gc> Avm2<'gc> { Ok(()) } + fn op_coerce_a(&mut self) -> Result<(), Error> { + Ok(()) + } + #[allow(unused_variables)] fn op_debug( &mut self, From a2dfffc56e6db976c2d897ce360f96ee885932b9 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 23 Feb 2020 22:28:37 -0500 Subject: [PATCH 077/189] Add our first AVM2 regression test: hello world! --- core/tests/regression_tests.rs | 1 + core/tests/swfs/avm2/hello_world/Test.as | 5 +++++ core/tests/swfs/avm2/hello_world/output.txt | 1 + core/tests/swfs/avm2/hello_world/test.fla | Bin 0 -> 3985 bytes core/tests/swfs/avm2/hello_world/test.swf | Bin 0 -> 580 bytes 5 files changed, 7 insertions(+) create mode 100644 core/tests/swfs/avm2/hello_world/Test.as create mode 100644 core/tests/swfs/avm2/hello_world/output.txt create mode 100644 core/tests/swfs/avm2/hello_world/test.fla create mode 100644 core/tests/swfs/avm2/hello_world/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index bb809260779b..5fb7f61b7087 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -227,6 +227,7 @@ swf_tests! { (set_interval, "avm1/set_interval", 20), (context_menu, "avm1/context_menu", 1), (context_menu_item, "avm1/context_menu_item", 1), + (as3_hello_world, "avm2/hello_world", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/hello_world/Test.as b/core/tests/swfs/avm2/hello_world/Test.as new file mode 100644 index 000000000000..6ee9ef5d251c --- /dev/null +++ b/core/tests/swfs/avm2/hello_world/Test.as @@ -0,0 +1,5 @@ +package { + public class Test {} +} + +trace("Hello world!"); \ No newline at end of file diff --git a/core/tests/swfs/avm2/hello_world/output.txt b/core/tests/swfs/avm2/hello_world/output.txt new file mode 100644 index 000000000000..cd0875583aab --- /dev/null +++ b/core/tests/swfs/avm2/hello_world/output.txt @@ -0,0 +1 @@ +Hello world! diff --git a/core/tests/swfs/avm2/hello_world/test.fla b/core/tests/swfs/avm2/hello_world/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..e8422682499736c841d221c3be55563405f47944 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/hello_world/test.swf b/core/tests/swfs/avm2/hello_world/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..777bb8c2dff1641131b1af46e02f894ec9ffa1cc GIT binary patch literal 580 zcmV-K0=xY~S5qsR0{{ScoRw0|PTN2bo?ZW}ogV^)n1B=&)bvo%1gEH%aA?(}lpvv~ zN~-3N2%C7LxH5KRJ3y~}lD-MA;2!!O;R(`N3{@&oRbAOTv)?!K&5XU*pqv2|z5|p9 zwAM5LXwt=E@op2~>FR8&Qm>e`O84_sc+hnE@zHtjQ|HyJ{&8{=)ZnI=#dreU5%>8{ z9pGkTTM_`SRj3KG4SJ79@xIue$GDx1J-cygyME8T;GXOA{om*~v@dvl?;lais4QgF z&iNNUinWJ`+;T)7foM{5+-U0AU-i@A(&bIhofaR}Q_~h+w;O|27);ND>p?Sc_#m8( z#~!bWoM>Q22h|Dpyr6m&gr0MFD4M9*krIdYi06(4wR1=9mLICrma8=GCta}}=SXRm&M-BnD})Jfjj;_T7ny7@d6UT{ zmR=AFsTD8Y8IV(-5~@>lBw15HAqo+M1QZLPqY_CbgaJV-CFTcAL0risX3~b+{9HwZ zVyv{An0TZ^)uN_F`%s<`JS#)?vT(%6x1T?bQI!xqld7O-CR@Oq)wwhzCj?&1WrP(8 zVdGAGsNFu9lL{nbrK8a~MOMY0^u5I7Ad2Xwl`*ZXY2{2SZ(1@weNs->M7rIYmoVkc S`u6?EkN#_Ti_9;kU-lwO<041^ literal 0 HcmV?d00001 From 38868fbdfea246d802dc60512ced28140ec65cd2 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 24 Feb 2020 14:12:36 -0500 Subject: [PATCH 078/189] Args are pushed onto the stack in normal order, so we need to pop them off in reverse order. --- core/src/avm2.rs | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index d82a513a5c70..53db65716a34 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -325,6 +325,16 @@ impl<'gc> Avm2<'gc> { value } + fn pop_args(&mut self, arg_count: u32) -> Vec> { + 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, Error> { self.current_stack_frame() .and_then(|sf| sf.read().local_register(index)) @@ -574,10 +584,7 @@ impl<'gc> Avm2<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, arg_count: u32, ) -> Result<(), Error> { - let mut args = Vec::new(); - for _ in 0..arg_count { - args.push(self.pop()); - } + let args = self.pop_args(arg_count); let receiver = self.pop().as_object().ok(); let function = self.pop().as_object()?; @@ -592,10 +599,7 @@ impl<'gc> Avm2<'gc> { index: Index, arg_count: u32, ) -> Result<(), Error> { - let mut args = Vec::new(); - for _ in 0..arg_count { - args.push(self.pop()); - } + let args = self.pop_args(arg_count); let receiver = self.pop().as_object()?; let function: Result, Error> = receiver .get_method(index.0) @@ -614,10 +618,7 @@ impl<'gc> Avm2<'gc> { index: Index, arg_count: u32, ) -> Result<(), Error> { - let mut args = Vec::new(); - for _ in 0..arg_count { - args.push(self.pop()); - } + let args = self.pop_args(arg_count); let multiname = self.pool_multiname(index)?; let receiver = self.pop().as_object()?; let name: Result = receiver @@ -641,10 +642,7 @@ impl<'gc> Avm2<'gc> { index: Index, arg_count: u32, ) -> Result<(), Error> { - let mut args = Vec::new(); - for _ in 0..arg_count { - args.push(self.pop()); - } + let args = self.pop_args(arg_count); let multiname = self.pool_multiname(index)?; let receiver = self.pop().as_object()?; let name: Result = receiver @@ -666,10 +664,7 @@ impl<'gc> Avm2<'gc> { index: Index, arg_count: u32, ) -> Result<(), Error> { - let mut args = Vec::new(); - for _ in 0..arg_count { - args.push(self.pop()); - } + let args = self.pop_args(arg_count); let multiname = self.pool_multiname(index)?; let receiver = self.pop().as_object()?; let name: Result = receiver @@ -693,10 +688,7 @@ impl<'gc> Avm2<'gc> { index: Index, arg_count: u32, ) -> Result<(), Error> { - let mut args = Vec::new(); - for _ in 0..arg_count { - args.push(self.pop()); - } + 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? From bedf5cb459c42427399f6015fd194298e51cb1e9 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 24 Feb 2020 14:12:51 -0500 Subject: [PATCH 079/189] Add a basic test for function calls. --- core/tests/regression_tests.rs | 1 + core/tests/swfs/avm2/function_call/Test.as | 11 +++++++++++ core/tests/swfs/avm2/function_call/output.txt | 3 +++ core/tests/swfs/avm2/function_call/test.fla | Bin 0 -> 3985 bytes core/tests/swfs/avm2/function_call/test.swf | Bin 0 -> 685 bytes 5 files changed, 15 insertions(+) create mode 100644 core/tests/swfs/avm2/function_call/Test.as create mode 100644 core/tests/swfs/avm2/function_call/output.txt create mode 100644 core/tests/swfs/avm2/function_call/test.fla create mode 100644 core/tests/swfs/avm2/function_call/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 5fb7f61b7087..bce5b648109f 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -228,6 +228,7 @@ swf_tests! { (context_menu, "avm1/context_menu", 1), (context_menu_item, "avm1/context_menu_item", 1), (as3_hello_world, "avm2/hello_world", 1), + (as3_function_call, "avm2/function_call", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/function_call/Test.as b/core/tests/swfs/avm2/function_call/Test.as new file mode 100644 index 000000000000..612b75f96f15 --- /dev/null +++ b/core/tests/swfs/avm2/function_call/Test.as @@ -0,0 +1,11 @@ +package { + public class Test {} +} + +function testfunc(v1, v2, v3) { + trace(v1); + trace(v2); + trace(v3); +} + +testfunc("arg1", "arg2", "arg3"); \ No newline at end of file diff --git a/core/tests/swfs/avm2/function_call/output.txt b/core/tests/swfs/avm2/function_call/output.txt new file mode 100644 index 000000000000..6a8e7e73b1ea --- /dev/null +++ b/core/tests/swfs/avm2/function_call/output.txt @@ -0,0 +1,3 @@ +arg1 +arg2 +arg3 diff --git a/core/tests/swfs/avm2/function_call/test.fla b/core/tests/swfs/avm2/function_call/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..e8422682499736c841d221c3be55563405f47944 GIT binary patch literal 3985 zcmbtXc{r5&7oWzGeQE4Qc7`mClqomag)G@8Tb5yD#yYmK#)LtL?6O4JvJ6p{vhN|% zRU}J8=vuRfe&f2;t>@nR&+oj?dEWQ?eb487&-*^-JkRH&uT4hI0sv3}0KUb>`aJaZ zWYhov;Lu5101UztBjxLW(l^jEgG;Iyp4I;;oD}ih4#Tf#DjO&nz@`5^>M*g6nvs&E zrmp&bgfWxC+`Md2PM$~%0_}#fArT2hO zM!1kFMx&}t##}NKnF8bP2EQ3f*=%;GP1Oe8;Ywj!)48<`npHH8-6t-aAjEFGaF3O_5`RhznC38{ z-+fxRES~{w2o@HOf=P-BJt!j@t2PP3;+{f`?3wkP(&~qNuCQVHgxp4&GS=ak2sXa= zOH4UBWa40DrgFC$KyfigGWpsZyX3O7N)dH1d9J?pm*&S_@aDjZRc%lS2QO{j1g=tHtwyeyg}vkIb9gj6hp zq8Sz1LK8-m8?_p`rSFhY>{Zc2mgOC*iKjh~xg6%q++Y9cb5n-@2uvE;-3*2XVFNG$ zsxRvfR;ND9_w}U-Ha-j$Y_v)e#9Ay;iF|GJSu689F3QgfR?{&s&uP{0h`4j3sXxzO zzE^MWo%iN}e5MngT4RRJJ+^yIDaWjx)8R=r6vXZH<>}~Sybi2KDMPL`7Cl|jJA6^XPQ(V3AcGNfsN_emN=#5~NC}C6wWlUSkNO1Bwp`s#(6%QY86SQ2` zK*iMIjgcEhdvpZbMpgoi{|R%;wq$hN@RJn+pUD?1rm74x{Vj~z$0W8kt$~x=_oB|a zuVSs3@~S%15vij#wi-c|8w-7PU*dc>*^kBz_}2`RX5qWBlAgYg8ybPJvA4n49FE5~ z$k8kXKC;$2I1T_5f&l;!38H^AdWHxL#>vIe^AOMucXgeHfr5jxfw`~pV3b^wHcL; zxlAD#W6-?0;tXe1=F4@v*YZWCr^K#J#l7aw%JXU|NkaB<= zrGRZYGUlp1p2`X@u_Ml$K4YjvE5qJ0of}flX+@=~P(5oTKw1O{N=8NiA=juCf78;X zd(zK(aF<{?lF1nn?#-hhJIFa2-kdh6tmXzjfBbFui#wjV0`W`AuP)j$nQFXHipr6< zWiS|xVz#eogZ1xdK2Q6!2w53_R#7m?j#}=_O=EKR7wM7lSIJBhSl^_(St#kQ%D-D9 z1n|%UpJxsSyj><<}S9i>^Ao zZa$vQyBl_`>J@UNe{930bPWAuAfnX<#*t|{1C`LbSh%zoFnK$DqiPiHJY0B|`y_Jh zQ{N~AhbTg(;?%AA$;5(2QHr8|oir?V=js-b&8vy_s_ zlH{&v$Z*j#GslR%Zkc{roC*{RkAf-W+nOqhxf@4=_L;==O^fz__hIcR>T1HS@z;Bx zv(FWsm8$gk8j?UmylRxMRa$VJ)H)IlzSiCM0>2yWTj;psi1y+hD!b7b(VV2QJ zcj0ymX{x^D{SZNlg7D=P8j-@%#C^LJn@!<}&oa@1o2m{r$@I(`RopZOeJ!a9)9}<2 zklRm(3gs5gWGV!t_4Da`v?n-R9Mk6i@V#ET_I&M;hw(E zNbVw_fd~7*eJ0lQ9+YO&ke%QK;0mzqa!di{UPn(bH(}KJ<$Clk`b9&RN8%N}!DhYc zt%OZ((VTwHk!rPYZ!Tzpdme-Aa@fYXyg6#gmGHY@YWTLP}zr0c(c&xIT7s`e*@tWiF2`S;HPO7D=@)kU3k3h3AE~tDzj>CZiV< zWe^qd;1hE`Ijon(?6;Mhy4{b9wOu*?N`}H&_d%+RvFnm~$GVk!)58Fsi}X-%Z<8}# z;g1jz`up}g9~wJBNkEDM`E$0blzX&!RH9~Q!8RV!R*!h1+5{NWtt{K&1d(j}>^lB9 zI>HEdLeyuDir<)Pt6eYqM9lZ$rf~N;s-UylT*tY?Cyg-!O-nKJ`;hGoIb_*F!+{Iy z{itArOSdS91zOw3y2EKdEcA^U=P#??NV=Z@A+J!isiteAf4Q2PihYo5iY#KD!b&UdD z?!qiV99p00xYW3wMeVRzdylWZ99=7&a6PDa*!Wqcx(p|At>sv`2_*Yd$YNY}&FA8e z3wk@R6}!5w@oQ|+4s2xj8V3sRsmu6xc!y0)PiH7>aKGnaQ}`1Wj68&r3u{akQ{Am+=3^G~sPD!ZKz zZ|GlI4T6!y24=rL*9j@U@?vZIt8;p9HZZ8G`-AoE>KXrp18vD#8un zVlQQ9V~0dIlD3J*fdEPX8-N8s@n>4n7T|d)iH-cEIqDOp!Z}HA4*gJbpq)s(#?K95 zx9pb#&^#DXYKmq+1Eef6-ar z&i*&{`2*ZW_P3IM=Au8J|0^5)kz$LK;vcx_f7Ni)+4|6=#K=`qy*nOR$rTf^6(7}>53;QS$_J%)&BsahCnd@ literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/function_call/test.swf b/core/tests/swfs/avm2/function_call/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..96dae9e090dc5b2d14b432688aa0bdd57e302f12 GIT binary patch literal 685 zcmV;e0#f}$S5qrK1ONbdoRw4EP7^^CK4KVJY?Ne&jXg>3aS>zC}~+UDTN{@%-1wb#3+`Fd}&W5D@*l=Be|Tg+vv z1%UIpFOmTGmxR1fYqLkp5Av3uEj=v<+_VR73v>Z)2=Pc@CwA8=A6Slif zhj!D4R^=jadM+6j$goI;rE;xoR(fq_dIQ_;GPBk5m?`MK>A$mm(;A)?O?%>X(z3eU zt*uF5CGe~klP&K}L5QLdrRAHF^3x@iCu5nh+U=^a4qKkn57z!do<7CG-r113fuGnF zYKPCk3R)fJ#dpGygu_eK+<@_#0%yoVB>l&IrRN4bGV|2KzUKt2_}{&odYWX^w3d!! zVhfrwlfEY*B27_LDN-uwRE|kiFL?2YdL)dgAV#7I9$|4THEGhsB7MpNeN7?;DZBV9clXSmC= T*(C`7;ai^*Z1bBRjl&^XcSKKg literal 0 HcmV?d00001 From 9431e02802c57afd18d7d8b872c99a54a477e1cc Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 24 Feb 2020 14:41:09 -0500 Subject: [PATCH 080/189] The class function should use the *instance* initializer as it's callable, not the class initializer. --- core/src/avm2/function.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index c36c5a8e7bf2..962a9f2b22a9 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -290,12 +290,13 @@ impl<'gc> FunctionObject<'gc> { class_proto.install_trait(avm, context, class.abc(), trait_entry, scope, fn_proto)?; } - let initializer_index = class.class().init_method.clone(); + // TODO: Get the class initializer, and store it somewhere. + let initializer_index = class.instance().init_method.clone(); let initializer: Result = Avm2MethodEntry::from_method_index(class.abc(), initializer_index.clone()).ok_or_else( || { format!( - "Class initializer method index {} does not exist", + "Instance initializer method index {} does not exist", initializer_index.0 ) .into() From 0fc9b9a2872571426713a1a7a42302ce160b92c3 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 24 Feb 2020 14:43:39 -0500 Subject: [PATCH 081/189] `construct` and `constructprop` should take their args in reverse-order like the call functions do. --- core/src/avm2.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 53db65716a34..03b39d3f12be 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -977,12 +977,9 @@ impl<'gc> Avm2<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, arg_count: u32, ) -> Result<(), Error> { - let mut args = Vec::new(); - for _ in 0..arg_count { - args.push(self.pop()); - } - + let args = self.pop_args(arg_count); let ctor = self.pop().as_object()?; + let proto = ctor .get_property( &QName::new(Namespace::public_namespace(), "prototype"), @@ -1005,14 +1002,10 @@ impl<'gc> Avm2<'gc> { index: Index, arg_count: u32, ) -> Result<(), Error> { - let mut args = Vec::new(); - for _ in 0..arg_count { - args.push(self.pop()); - } - + let args = self.pop_args(arg_count); let multiname = self.pool_multiname(index)?; - let source = self.pop().as_object()?; + let ctor_name: Result = source.resolve_multiname(&multiname).ok_or_else(|| { format!("Could not resolve property {:?}", multiname.local_name()).into() From a77f676279b62a013554eab11a7d0c7579017b3b Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 24 Feb 2020 14:43:58 -0500 Subject: [PATCH 082/189] `construct` and `constructprop` should push the object that was just constructed. --- core/src/avm2.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 03b39d3f12be..c9a83a9eb8dc 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -993,6 +993,8 @@ impl<'gc> Avm2<'gc> { ctor.call(Some(object), &args, self, context)? .resolve(self, context)?; + self.push(object); + Ok(()) } @@ -1027,6 +1029,8 @@ impl<'gc> Avm2<'gc> { ctor.call(Some(object), &args, self, context)? .resolve(self, context)?; + self.push(object); + Ok(()) } From f3dee5c310f0c17d486c4ff26b4984ad88f54d71 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 24 Feb 2020 18:01:57 -0500 Subject: [PATCH 083/189] Add (currently failing) test for constructors. --- core/tests/regression_tests.rs | 1 + core/tests/swfs/avm2/constructor_call/Test.as | 14 ++++++++++++++ core/tests/swfs/avm2/constructor_call/output.txt | 3 +++ core/tests/swfs/avm2/constructor_call/test.fla | Bin 0 -> 3985 bytes core/tests/swfs/avm2/constructor_call/test.swf | Bin 0 -> 705 bytes 5 files changed, 18 insertions(+) create mode 100644 core/tests/swfs/avm2/constructor_call/Test.as create mode 100644 core/tests/swfs/avm2/constructor_call/output.txt create mode 100644 core/tests/swfs/avm2/constructor_call/test.fla create mode 100644 core/tests/swfs/avm2/constructor_call/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index bce5b648109f..e9137bf9fb2f 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -229,6 +229,7 @@ swf_tests! { (context_menu_item, "avm1/context_menu_item", 1), (as3_hello_world, "avm2/hello_world", 1), (as3_function_call, "avm2/function_call", 1), + (as3_constructor_call, "avm2/constructor_call", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/constructor_call/Test.as b/core/tests/swfs/avm2/constructor_call/Test.as new file mode 100644 index 000000000000..ab4401bb16e5 --- /dev/null +++ b/core/tests/swfs/avm2/constructor_call/Test.as @@ -0,0 +1,14 @@ +package { + public class Test { + } +} + +class Test2 { + function Test2(v1, v2, v3) { + trace(v1); + trace(v2); + trace(v3); + } +} + +new Test2("arg1", "arg2", "arg3"); \ No newline at end of file diff --git a/core/tests/swfs/avm2/constructor_call/output.txt b/core/tests/swfs/avm2/constructor_call/output.txt new file mode 100644 index 000000000000..6a8e7e73b1ea --- /dev/null +++ b/core/tests/swfs/avm2/constructor_call/output.txt @@ -0,0 +1,3 @@ +arg1 +arg2 +arg3 diff --git a/core/tests/swfs/avm2/constructor_call/test.fla b/core/tests/swfs/avm2/constructor_call/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..84376c02ef3abb67f80e2788b6e658e5112c93b8 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/constructor_call/test.swf b/core/tests/swfs/avm2/constructor_call/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..6104d91193c5c1cf7756101a328298ed24d28e13 GIT binary patch literal 705 zcmV;y0zUmiS5qr|1ONbdoSjl_Pt#BozAtTW+ih$JjDb!-<_7~A?T|!9jK<;1Fa!-m z`LKqS?#|WhEp0dToAHP)rjs|((NqlLG1_%_ zo6nmF9ZmkE0)Uf3d7&1Ow#WToMTi9<^0uE_eNYtF!pc?PZUFM2-n5I`w%guwTfFJE z`O0s!?%6GFF8w2F6qQ6>W#{lccLM#AP9D;wuJWrFHR7W+5&-y(e9iT{P5YC!-RZl$ z)O5R3*D@+mlMlC!&uX! zCKQbISQ6^Ygw(=Y7~KmcfEfiiJ#5_4Pj7}ehX!?JM<*cxuL@IWO9O)=K@>Bpaif|r ns$)hqX;fwS_DME64e9hwn1Pf>6SL=Ud+pkx$Kd$|gOo3zB-&l4 literal 0 HcmV?d00001 From 1b67bb94c85b27afc52fac1b5e2fa9afc2e6a2a5 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 25 Feb 2020 18:07:53 -0500 Subject: [PATCH 084/189] Impl `callsuper`, `callsupervoid`, and `constructsuper`. This works primarily by retaining the current superclass prototype in the activation object and then using it to retrieve the super method. For constructors, we implement the `constructor` property, which is probably not the correct way to do this. --- core/src/avm2.rs | 102 +++++++++++++++++++++++++++++++++++- core/src/avm2/activation.rs | 14 +++++ core/src/avm2/function.rs | 75 +++++++++++++++++++++----- 3 files changed, 178 insertions(+), 13 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index c9a83a9eb8dc..2b2b56a1c2da 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,7 +1,7 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::{Activation, Avm2ScriptEntry}; -use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, FunctionObject}; +use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, Executable, FunctionObject}; use crate::avm2::globals::SystemPrototypes; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::object::{Object, TObject}; @@ -447,6 +447,10 @@ impl<'gc> Avm2<'gc> { 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), Op::GetProperty { index } => self.op_get_property(context, index), @@ -469,6 +473,7 @@ impl<'gc> Avm2<'gc> { Op::ConstructProp { index, num_args } => { self.op_construct_prop(context, index, num_args) } + Op::ConstructSuper { num_args } => self.op_construct_super(context, num_args), 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), @@ -706,6 +711,72 @@ impl<'gc> Avm2<'gc> { Ok(()) } + fn op_call_super( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + 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 = receiver + .resolve_multiname(&multiname) + .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); + let function = self + .current_stack_frame() + .unwrap() + .read() + .base_proto() + .unwrap_or(receiver) + .get_property(&name?, self, context)? + .resolve(self, context)? + .as_object()?; + + let exec: Result, Error> = function.as_executable().ok_or_else(|| { + format!("Super method {:?} is not callable", multiname.local_name()).into() + }); + + exec? + .exec_super(self, context, Some(receiver), &args)? + .push(self); + + Ok(()) + } + + fn op_call_super_void( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + 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 = receiver + .resolve_multiname(&multiname) + .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); + let function = self + .current_stack_frame() + .unwrap() + .read() + .base_proto() + .unwrap_or(receiver) + .get_property(&name?, self, context)? + .resolve(self, context)? + .as_object()?; + + let exec: Result, Error> = function.as_executable().ok_or_else(|| { + format!("Super method {:?} is not callable", multiname.local_name()).into() + }); + + exec? + .exec_super(self, context, Some(receiver), &args)? + .resolve(self, context)?; + + Ok(()) + } + fn op_return_value(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { let return_value = self.pop(); @@ -1034,6 +1105,35 @@ impl<'gc> Avm2<'gc> { 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 function = self + .current_stack_frame() + .unwrap() + .read() + .base_proto() + .unwrap_or(receiver) + .get_property(&name, self, context)? + .resolve(self, context)? + .as_object()?; + + let exec: Result, Error> = function + .as_executable() + .ok_or_else(|| "Super constructor is not callable".to_string().into()); + + exec? + .exec_super(self, context, Some(receiver), &args)? + .resolve(self, context)?; + + Ok(()) + } + fn op_new_activation(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { self.push(ScriptObject::bare_object(context.gc_context)); diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 2595e88ca2f3..a1cca6c79440 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -124,6 +124,11 @@ pub struct Activation<'gc> { /// /// A `scope` of `None` indicates that the scope stack is empty. scope: Option>>, + + /// The base prototype of `this`. + /// + /// This will not be available if this is not a method call. + base_proto: Option>, } impl<'gc> Activation<'gc> { @@ -158,6 +163,7 @@ impl<'gc> Activation<'gc> { return_value: None, local_scope: ScriptObject::bare_object(context.gc_context), scope, + base_proto: None, }) } @@ -166,6 +172,7 @@ impl<'gc> Activation<'gc> { action: &Avm2Function<'gc>, this: Option>, arguments: &[Value<'gc>], + base_proto: Option>, ) -> Result { let method = action.method.clone(); let scope = action.scope; @@ -198,6 +205,7 @@ impl<'gc> Activation<'gc> { return_value: None, local_scope: ScriptObject::bare_object(context.gc_context), scope, + base_proto, }) } @@ -272,4 +280,10 @@ impl<'gc> Activation<'gc> { pub fn set_return_value(&mut self, value: Value<'gc>) { self.return_value = Some(value); } + + /// Get the base prototype of the object that the currently executing + /// method was retrieved from, if one exists. + pub fn base_proto(&self) -> Option> { + self.base_proto + } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 962a9f2b22a9..228b20e5214f 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -127,6 +127,13 @@ unsafe impl<'gc> Collect for Executable<'gc> { } impl<'gc> Executable<'gc> { + /// Execute a method. + /// + /// The function will either be called directly if it is a Rust builtin, or + /// placed on the stack of the passed-in AVM2 otherwise. As a result, we + /// return a `ReturnValue` which can be used to force execution of the + /// given stack frame and obtain it's return value or to push said value + /// onto the AVM operand stack. pub fn exec( &self, avm: &mut Avm2<'gc>, @@ -137,9 +144,42 @@ impl<'gc> Executable<'gc> { match self { Executable::Native(nf) => nf(avm, context, reciever, arguments), Executable::Action(a2f) => { + let base_proto = reciever.and_then(|o| o.proto()); + let activation = GcCell::allocate( + context.gc_context, + Activation::from_action(context, &a2f, reciever, arguments, base_proto)?, + ); + + avm.insert_stack_frame(activation); + Ok(activation.into()) + } + } + } + + /// Execute a method that is the `super` of an existing method. + /// + /// The primary difference between `exec` and `exec_super` is that the + /// former always resets the `base_proto` to the current `reciever` while + /// the latter sets it to the next object up the prototype chain. The + /// latter behavior is necessary to ensure that chains of `callsuper` and + /// `constructsuper` operate correctly. + pub fn exec_super( + &self, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + reciever: Option>, + arguments: &[Value<'gc>], + ) -> Result, Error> { + match self { + Executable::Native(nf) => nf(avm, context, reciever, arguments), + Executable::Action(a2f) => { + let base_proto = avm + .current_stack_frame() + .and_then(|sf| sf.read().base_proto()) + .and_then(|o| o.proto()); let activation = GcCell::allocate( context.gc_context, - Activation::from_action(context, &a2f, reciever, arguments)?, + Activation::from_action(context, &a2f, reciever, arguments, base_proto)?, ); avm.insert_stack_frame(activation); @@ -322,6 +362,11 @@ impl<'gc> FunctionObject<'gc> { QName::new(Namespace::public_namespace(), "prototype"), class_proto.into(), )?; + class_proto.install_dynamic_property( + context.gc_context, + QName::new(Namespace::public_namespace(), "constructor"), + constr.into(), + )?; Ok(constr) } @@ -367,25 +412,31 @@ impl<'gc> FunctionObject<'gc> { pub fn from_builtin_constr( mc: MutationContext<'gc, '_>, constr: NativeFunction<'gc>, - prototype: Object<'gc>, + mut prototype: Object<'gc>, fn_proto: Object<'gc>, ) -> Result, Error> { - let mut base = ScriptObjectData::base_new(Some(fn_proto)); - - base.install_dynamic_property( - QName::new(Namespace::public_namespace(), "prototype"), - prototype.into(), - )?; - - Ok(FunctionObject(GcCell::allocate( + let mut base: Object<'gc> = FunctionObject(GcCell::allocate( mc, FunctionObjectData { - base, + base: ScriptObjectData::base_new(Some(fn_proto)), exec: Some(constr.into()), class: None, }, )) - .into()) + .into(); + + base.install_dynamic_property( + mc, + QName::new(Namespace::public_namespace(), "prototype"), + prototype.into(), + )?; + prototype.install_dynamic_property( + mc, + QName::new(Namespace::public_namespace(), "constructor"), + base.into(), + )?; + + Ok(base) } } From 73966f1b31f61973ee2c6cd83346f5a7c94c9a17 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 25 Feb 2020 19:53:36 -0500 Subject: [PATCH 085/189] Make sure that we actually call the super constructor, not our own constructor. --- core/src/avm2.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 2b2b56a1c2da..6b2d9da05268 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1113,12 +1113,15 @@ impl<'gc> Avm2<'gc> { let args = self.pop_args(arg_count); let receiver = self.pop().as_object()?; let name = QName::new(Namespace::public_namespace(), "constructor"); - let function = self + let base_proto: Result, Error> = self .current_stack_frame() .unwrap() .read() .base_proto() - .unwrap_or(receiver) + .and_then(|p| p.proto()) + .ok_or_else(|| "No base prototype!".to_string().into()); + + let function = base_proto? .get_property(&name, self, context)? .resolve(self, context)? .as_object()?; From 687a82f6438f28ad7c7f59ce3e007e9ff066b3c4 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 25 Feb 2020 19:56:19 -0500 Subject: [PATCH 086/189] Constructors should also inherit closure scope. --- core/src/avm2/function.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 228b20e5214f..76fd9d3a4a9b 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -347,7 +347,7 @@ impl<'gc> FunctionObject<'gc> { context.gc_context, FunctionObjectData { base: ScriptObjectData::base_new(None), - exec: Some(Avm2Function::from_method(initializer?, None).into()), + exec: Some(Avm2Function::from_method(initializer?, scope).into()), class: Some(class.clone()), }, )) From fa4369da72e53b330b91c9e9c35868ee7b1b8fb5 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 25 Feb 2020 20:49:37 -0500 Subject: [PATCH 087/189] Execute static class initializers. This also fixes the lack of function prototype on classes. --- core/src/avm2.rs | 6 +++++- core/src/avm2/function.rs | 31 ++++++++++++++++++++++++++----- core/src/avm2/object.rs | 2 +- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 6b2d9da05268..9e5a09c8d2eb 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1196,7 +1196,7 @@ impl<'gc> Avm2<'gc> { let class_entry = self.table_class(index)?; let scope = self.current_stack_frame().unwrap().read().scope(); - let new_class = FunctionObject::from_abc_class( + let (new_class, class_init) = FunctionObject::from_abc_class( self, context, class_entry, @@ -1205,6 +1205,10 @@ impl<'gc> Avm2<'gc> { self.system_prototypes.function, )?; + class_init + .call(Some(new_class), &[], self, context)? + .resolve(self, context)?; + self.push(new_class); Ok(()) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 76fd9d3a4a9b..fe797f45cd8e 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -287,7 +287,9 @@ pub struct FunctionObjectData<'gc> { impl<'gc> FunctionObject<'gc> { /// Construct a class from an ABC class/instance pair. /// - /// If the initializer method cannot be found, this function returns None. + /// This function returns both the class itself, and the static class + /// initializer method that you should call before interacting with the + /// class. The latter should be called using the former as a reciever. pub fn from_abc_class( avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -295,7 +297,7 @@ impl<'gc> FunctionObject<'gc> { base_class: Object<'gc>, scope: Option>>, fn_proto: Object<'gc>, - ) -> Result, Error> { + ) -> Result<(Object<'gc>, Object<'gc>), Error> { let super_proto: Result, Error> = base_class .get_property( &QName::new(Namespace::public_namespace(), "prototype"), @@ -330,7 +332,6 @@ impl<'gc> FunctionObject<'gc> { class_proto.install_trait(avm, context, class.abc(), trait_entry, scope, fn_proto)?; } - // TODO: Get the class initializer, and store it somewhere. let initializer_index = class.instance().init_method.clone(); let initializer: Result = Avm2MethodEntry::from_method_index(class.abc(), initializer_index.clone()).ok_or_else( @@ -346,7 +347,7 @@ impl<'gc> FunctionObject<'gc> { let mut constr: Object<'gc> = FunctionObject(GcCell::allocate( context.gc_context, FunctionObjectData { - base: ScriptObjectData::base_new(None), + base: ScriptObjectData::base_new(Some(fn_proto)), exec: Some(Avm2Function::from_method(initializer?, scope).into()), class: Some(class.clone()), }, @@ -368,7 +369,27 @@ impl<'gc> FunctionObject<'gc> { constr.into(), )?; - Ok(constr) + let class_initializer_index = class.class().init_method.clone(); + let class_initializer: Result = + Avm2MethodEntry::from_method_index(class.abc(), class_initializer_index.clone()) + .ok_or_else(|| { + format!( + "Class initializer method index {} does not exist", + class_initializer_index.0 + ) + .into() + }); + let class_constr = FunctionObject(GcCell::allocate( + context.gc_context, + FunctionObjectData { + base: ScriptObjectData::base_new(Some(fn_proto)), + exec: Some(Avm2Function::from_method(class_initializer?, scope).into()), + class: Some(class.clone()), + }, + )) + .into(); + + Ok((constr, class_constr)) } /// Construct a function from an ABC method and the current closure scope. diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 7765ac8a11c4..c2b48ec4f0dc 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -230,7 +230,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy format!("Could not resolve superclass {:?}", super_name.local_name()).into() }); - let class = FunctionObject::from_abc_class( + let (class, _cinit) = FunctionObject::from_abc_class( avm, context, type_entry.clone(), From 1c3b9c50fe714c1ec36675531976bddcbf39ecfd Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 25 Feb 2020 21:37:47 -0500 Subject: [PATCH 088/189] Implement prototype awareness for `get_property`, `has_property`, and `resolve_multiname`. Furthermore, implement `has_own_property`. --- core/src/avm2/function.rs | 8 +++---- core/src/avm2/object.rs | 39 +++++++++++++++++++++++++++++----- core/src/avm2/script_object.rs | 14 ++++++------ 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index fe797f45cd8e..1511bff3794e 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -462,7 +462,7 @@ impl<'gc> FunctionObject<'gc> { } impl<'gc> TObject<'gc> for FunctionObject<'gc> { - fn get_property( + fn get_property_local( self, name: &QName, avm: &mut Avm2<'gc>, @@ -471,7 +471,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0 .read() .base - .get_property(name, avm, context, self.into()) + .get_property_local(name, avm, context, self.into()) } fn set_property( @@ -530,8 +530,8 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_method(id) } - fn has_property(self, name: &QName) -> bool { - self.0.read().base.has_property(name) + fn has_own_property(self, name: &QName) -> bool { + self.0.read().base.has_own_property(name) } fn proto(&self) -> Option> { diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index c2b48ec4f0dc..2c31bd88dca6 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -25,13 +25,32 @@ use swf::avm2::types::{AbcFile, Trait as AbcTrait, TraitKind as AbcTraitKind}; } )] pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy { + /// Retrieve a property by it's QName, without taking prototype lookups + /// into account. + fn get_property_local( + self, + name: &QName, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error>; + /// Retrieve a property by it's QName. fn get_property( self, name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error>; + ) -> Result, Error> { + if self.has_own_property(name) { + return self.get_property_local(name, avm, context); + } + + if let Some(proto) = self.proto() { + return proto.get_property(name, avm, context); + } + + Ok(Value::Undefined.into()) + } /// Set a property by it's QName. fn set_property( @@ -84,17 +103,27 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } } + if let Some(proto) = self.proto() { + proto.resolve_multiname(multiname); + } + None } /// Indicates whether or not a property exists on an object. - fn has_property(self, _name: &QName) -> bool; + fn has_property(self, name: &QName) -> bool { + if self.has_own_property(name) { + true + } else if let Some(proto) = self.proto() { + proto.has_own_property(name) + } else { + false + } + } /// Indicates whether or not a property exists on an object and is not part /// of the prototype chain. - fn has_own_property(self, _name: &QName) -> bool { - false - } + fn has_own_property(self, name: &QName) -> bool; /// Indicates whether or not a property is overwritable. fn is_property_overwritable(self, _name: &QName) -> bool { diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 53f7d676de23..406d9e2b4203 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -35,13 +35,15 @@ pub struct ScriptObjectData<'gc> { } impl<'gc> TObject<'gc> for ScriptObject<'gc> { - fn get_property( + fn get_property_local( self, name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { - self.0.read().get_property(name, avm, context, self.into()) + self.0 + .read() + .get_property_local(name, avm, context, self.into()) } fn set_property( @@ -98,8 +100,8 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_method(id) } - fn has_property(self, name: &QName) -> bool { - self.0.read().has_property(name) + fn has_own_property(self, name: &QName) -> bool { + self.0.read().has_own_property(name) } fn proto(&self) -> Option> { @@ -209,7 +211,7 @@ impl<'gc> ScriptObjectData<'gc> { } } - pub fn get_property( + pub fn get_property_local( &self, name: &QName, avm: &mut Avm2<'gc>, @@ -327,7 +329,7 @@ impl<'gc> ScriptObjectData<'gc> { self.methods.get(id as usize).and_then(|v| *v) } - pub fn has_property(&self, name: &QName) -> bool { + pub fn has_own_property(&self, name: &QName) -> bool { self.values.get(name).is_some() } From ab5a95c05b1a5676d97001325cc3b86cb0c19da0 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 25 Feb 2020 21:39:37 -0500 Subject: [PATCH 089/189] Add a test for various types of class methods. --- core/tests/regression_tests.rs | 1 + core/tests/swfs/avm2/class_methods/Test.as | 27 ++++++++++++++++++ core/tests/swfs/avm2/class_methods/output.txt | 5 ++++ core/tests/swfs/avm2/class_methods/test.fla | Bin 0 -> 3985 bytes core/tests/swfs/avm2/class_methods/test.swf | Bin 0 -> 839 bytes 5 files changed, 33 insertions(+) create mode 100644 core/tests/swfs/avm2/class_methods/Test.as create mode 100644 core/tests/swfs/avm2/class_methods/output.txt create mode 100644 core/tests/swfs/avm2/class_methods/test.fla create mode 100644 core/tests/swfs/avm2/class_methods/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index e9137bf9fb2f..6699fecac447 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -230,6 +230,7 @@ swf_tests! { (as3_hello_world, "avm2/hello_world", 1), (as3_function_call, "avm2/function_call", 1), (as3_constructor_call, "avm2/constructor_call", 1), + (as3_class_methods, "avm2/class_methods", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/class_methods/Test.as b/core/tests/swfs/avm2/class_methods/Test.as new file mode 100644 index 000000000000..34e857907cc8 --- /dev/null +++ b/core/tests/swfs/avm2/class_methods/Test.as @@ -0,0 +1,27 @@ +package { + public class Test { + } +} + +class Test2 { + { + trace("Class constructor"); + } + + function Test2() { + trace("Instance constructor"); + } + + static function classMethod() { + trace("Class method"); + } + + function method() { + trace("Instance method"); + } +} + +trace("Script initializer"); +Test2.classMethod(); +var x = new Test2(); +x.method(); \ No newline at end of file diff --git a/core/tests/swfs/avm2/class_methods/output.txt b/core/tests/swfs/avm2/class_methods/output.txt new file mode 100644 index 000000000000..188d37d197e8 --- /dev/null +++ b/core/tests/swfs/avm2/class_methods/output.txt @@ -0,0 +1,5 @@ +Class constructor +Script initializer +Class method +Instance constructor +Instance method diff --git a/core/tests/swfs/avm2/class_methods/test.fla b/core/tests/swfs/avm2/class_methods/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..84376c02ef3abb67f80e2788b6e658e5112c93b8 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/class_methods/test.swf b/core/tests/swfs/avm2/class_methods/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..9fce5d135154064f060c6913b07aba3ee9ce6adf GIT binary patch literal 839 zcmV-N1GxM{S5qsE1pokeoRw4Ca??f_-h-so!H(_3cAS7q0;aS>$5I0AFy^8yP6NqI z#xqSi!-YMOEU#-ZvgDCAA?319(l_B1%mweEJVE-e6~Hh+r#sri|9$^?wHHV|MX2-- zLJf@eDmp@_ORlc2o~$Eut9-W6+G#!AZ}q%KFCYGqT>KgMKmI;^F+2&MJ(=3*b54|)!;$jkyw!fyg z0B(OxW4ds1R{(N44!zEi7ept)l!rmY?f2+7@uqy|{(opKql%27^@cCFFU$||)I|d2 zPczc;H1v0_3d^pF0Hl-lC`f0a_m^=Lp9j1f2D92{18aG%)>4b`MdzT?@5UqEPtL~U zkoWyK;eBbI_S2W+wC|lycl!&A(>#;(@#E_pRgri;uXH83<;PJfk~3e#Ng+pa_38oO zUgYy-jnaECS6V&xlVB#cf+!Gy7Y46*vON)E*0F7G6c2gZkEiyp#}DjX$N9+~o&})@ zqUO6I+sj3Eu5V<;8;$nmDR+J63(Ct{Opdb7T*V2mln)TlA6 zGi)$wF6sUDW$A(3W8ZnBL;akkS&9pmAYccIE)4sLxw?$CCQCB{Qig~|F}R@$!iIdZCRPhQR2w1NJV*z7< zkE{UnrlgBNtCE(6Yd}|lxgnVvFq({VICp>}1Is0jOP-_V1cR&%3b~A%6AE$z>~2cC z4VU(Glx+@*1A0i2XJj{AV}Awx{r1f>=nTe&nb)LaY+8`(Y#MFmEjhek6EuoG6Jkgz`^~w!;y?$cHD~Nt~&0T<5nG4gYP+M=3}B>-pd+b^7Go} R_0@d#Pe#80^Dn?ce(i3#qrLzD literal 0 HcmV?d00001 From 43da7ac9522d85a8a9d5e35463051ab07e7ea6d4 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 26 Feb 2020 21:54:29 -0500 Subject: [PATCH 090/189] `resolve_multiname` should actually return it's prototype's return value. --- core/src/avm2/object.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 2c31bd88dca6..b47ea7a469e4 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -97,14 +97,13 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn resolve_multiname(self, multiname: &Multiname) -> Option { for ns in multiname.namespace_set() { let qname = QName::new(ns.clone(), multiname.local_name()?); - if self.has_property(&qname) { return Some(qname); } } if let Some(proto) = self.proto() { - proto.resolve_multiname(multiname); + return proto.resolve_multiname(multiname); } None From e8fbac6cf223ede639825454b72acd94b9e6f986 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 27 Feb 2020 21:58:59 -0500 Subject: [PATCH 091/189] Refactor the base_proto system to more accurately record what prototype methods come from. The previous system primarily relied on `Executable` to automatically start and continue a super chain. This works, but only for class hierarchies without *override gaps* - methods that override another method not defined by the direct superclass of the method. In that case, the override method would be called twice as the `base_class` was moved up one prototype at a time, which is wrong. The new system relies on the call site to accurately report the prototype from which the current method was retrieved from. Super calls then start the resolution process *from the superclass of this prototype*, to ensure that the already-called method is skipped. It should be noted that the proper `base_class` for things like `callmethod`, `callstatic`, `call`, `get`/`set` methods, and other call opcodes that don't use property look-up are best-effort guesses that may need to be amended later with better tests. To facilitate `base_proto` resolution, a new `Object` method has been added. It's similar to `get_property`, but instead returns the closest prototype that can resolve the given `QName`, rather than the actual property's `ReturnValue`. Call operations use this to resolve the `base_proto`, and then resolve the method being called in `base_proto`. The existing `exec_super` method was removed and a `base_proto` method added to `exec` and `call`. --- core/src/avm2.rs | 97 +++++++++++++++++++++++---------------- core/src/avm2/function.rs | 37 ++------------- core/src/avm2/object.rs | 14 ++++++ core/src/avm2/property.rs | 22 +++++++-- 4 files changed, 92 insertions(+), 78 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 9e5a09c8d2eb..318fdd852e2f 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,7 +1,7 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::{Activation, Avm2ScriptEntry}; -use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, Executable, FunctionObject}; +use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, FunctionObject}; use crate::avm2::globals::SystemPrototypes; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::object::{Object, TObject}; @@ -592,8 +592,11 @@ impl<'gc> Avm2<'gc> { 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()); - function.call(receiver, &args, self, context)?.push(self); + function + .call(receiver, &args, self, context, base_proto)? + .push(self); Ok(()) } @@ -609,9 +612,10 @@ impl<'gc> Avm2<'gc> { let function: Result, Error> = receiver .get_method(index.0) .ok_or_else(|| format!("Object method {} does not exist", index.0).into()); + let base_proto = receiver.proto(); function? - .call(Some(receiver), &args, self, context)? + .call(Some(receiver), &args, self, context, base_proto)? .push(self); Ok(()) @@ -629,13 +633,16 @@ impl<'gc> Avm2<'gc> { let name: Result = receiver .resolve_multiname(&multiname) .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); - let function = receiver - .get_property(&name?, self, context)? + let name = name?; + let base_proto = receiver.get_base_proto(&name); + let function = base_proto + .unwrap_or(receiver) + .get_property_local(&name, self, context)? .resolve(self, context)? .as_object()?; function - .call(Some(receiver), &args, self, context)? + .call(Some(receiver), &args, self, context, base_proto)? .push(self); Ok(()) @@ -658,7 +665,7 @@ impl<'gc> Avm2<'gc> { .resolve(self, context)? .as_object()?; - function.call(None, &args, self, context)?.push(self); + function.call(None, &args, self, context, None)?.push(self); Ok(()) } @@ -675,13 +682,16 @@ impl<'gc> Avm2<'gc> { let name: Result = receiver .resolve_multiname(&multiname) .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); - let function = receiver - .get_property(&name?, self, context)? + let name = name?; + let base_proto = receiver.get_base_proto(&name); + let function = base_proto + .unwrap_or(receiver) + .get_property_local(&name, self, context)? .resolve(self, context)? .as_object()?; function - .call(Some(receiver), &args, self, context)? + .call(Some(receiver), &args, self, context, base_proto)? .resolve(self, context)?; Ok(()) @@ -705,7 +715,7 @@ impl<'gc> Avm2<'gc> { ); function - .call(Some(receiver), &args, self, context)? + .call(Some(receiver), &args, self, context, receiver.proto())? .push(self); Ok(()) @@ -723,22 +733,26 @@ impl<'gc> Avm2<'gc> { let name: Result = receiver .resolve_multiname(&multiname) .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); - let function = self + let base_proto: Result, Error> = self .current_stack_frame() .unwrap() .read() .base_proto() - .unwrap_or(receiver) + .and_then(|bp| bp.proto()) + .ok_or_else(|| { + "Attempted to call super method without a superclass." + .to_string() + .into() + }); + let base_proto = base_proto?; + + let function = base_proto .get_property(&name?, self, context)? .resolve(self, context)? .as_object()?; - let exec: Result, Error> = function.as_executable().ok_or_else(|| { - format!("Super method {:?} is not callable", multiname.local_name()).into() - }); - - exec? - .exec_super(self, context, Some(receiver), &args)? + function + .call(Some(receiver), &args, self, context, Some(base_proto))? .push(self); Ok(()) @@ -756,22 +770,26 @@ impl<'gc> Avm2<'gc> { let name: Result = receiver .resolve_multiname(&multiname) .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); - let function = self + let base_proto: Result, Error> = self .current_stack_frame() .unwrap() .read() .base_proto() - .unwrap_or(receiver) + .and_then(|bp| bp.proto()) + .ok_or_else(|| { + "Attempted to call super method without a superclass." + .to_string() + .into() + }); + let base_proto = base_proto?; + + let function = base_proto .get_property(&name?, self, context)? .resolve(self, context)? .as_object()?; - let exec: Result, Error> = function.as_executable().ok_or_else(|| { - format!("Super method {:?} is not callable", multiname.local_name()).into() - }); - - exec? - .exec_super(self, context, Some(receiver), &args)? + function + .call(Some(receiver), &args, self, context, Some(base_proto))? .resolve(self, context)?; Ok(()) @@ -1061,7 +1079,7 @@ impl<'gc> Avm2<'gc> { .as_object()?; let object = proto.construct(self, context, &args)?; - ctor.call(Some(object), &args, self, context)? + ctor.call(Some(object), &args, self, context, object.proto())? .resolve(self, context)?; self.push(object); @@ -1097,7 +1115,7 @@ impl<'gc> Avm2<'gc> { .as_object()?; let object = proto.construct(self, context, &args)?; - ctor.call(Some(object), &args, self, context)? + ctor.call(Some(object), &args, self, context, Some(proto))? .resolve(self, context)?; self.push(object); @@ -1119,19 +1137,20 @@ impl<'gc> Avm2<'gc> { .read() .base_proto() .and_then(|p| p.proto()) - .ok_or_else(|| "No base prototype!".to_string().into()); + .ok_or_else(|| { + "Attempted to call super constructor without a superclass." + .to_string() + .into() + }); + let base_proto = base_proto?; - let function = base_proto? - .get_property(&name, self, context)? + let function = base_proto + .get_property_local(&name, self, context)? .resolve(self, context)? .as_object()?; - let exec: Result, Error> = function - .as_executable() - .ok_or_else(|| "Super constructor is not callable".to_string().into()); - - exec? - .exec_super(self, context, Some(receiver), &args)? + function + .call(Some(receiver), &args, self, context, Some(base_proto))? .resolve(self, context)?; Ok(()) @@ -1206,7 +1225,7 @@ impl<'gc> Avm2<'gc> { )?; class_init - .call(Some(new_class), &[], self, context)? + .call(Some(new_class), &[], self, context, None)? .resolve(self, context)?; self.push(new_class); diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 1511bff3794e..efd380e3dba0 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -136,47 +136,15 @@ impl<'gc> Executable<'gc> { /// onto the AVM operand stack. pub fn exec( &self, - avm: &mut Avm2<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, reciever: Option>, arguments: &[Value<'gc>], - ) -> Result, Error> { - match self { - Executable::Native(nf) => nf(avm, context, reciever, arguments), - Executable::Action(a2f) => { - let base_proto = reciever.and_then(|o| o.proto()); - let activation = GcCell::allocate( - context.gc_context, - Activation::from_action(context, &a2f, reciever, arguments, base_proto)?, - ); - - avm.insert_stack_frame(activation); - Ok(activation.into()) - } - } - } - - /// Execute a method that is the `super` of an existing method. - /// - /// The primary difference between `exec` and `exec_super` is that the - /// former always resets the `base_proto` to the current `reciever` while - /// the latter sets it to the next object up the prototype chain. The - /// latter behavior is necessary to ensure that chains of `callsuper` and - /// `constructsuper` operate correctly. - pub fn exec_super( - &self, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - reciever: Option>, - arguments: &[Value<'gc>], + base_proto: Option>, ) -> Result, Error> { match self { Executable::Native(nf) => nf(avm, context, reciever, arguments), Executable::Action(a2f) => { - let base_proto = avm - .current_stack_frame() - .and_then(|sf| sf.read().base_proto()) - .and_then(|o| o.proto()); let activation = GcCell::allocate( context.gc_context, Activation::from_action(context, &a2f, reciever, arguments, base_proto)?, @@ -552,9 +520,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { arguments: &[Value<'gc>], avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, + base_proto: Option>, ) -> Result, Error> { if let Some(exec) = &self.0.read().exec { - exec.exec(avm, context, reciever, arguments) + exec.exec(reciever, arguments, avm, context, base_proto) } else { Err("Not a callable function!".into()) } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index b47ea7a469e4..acfc134b5ef7 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -52,6 +52,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy Ok(Value::Undefined.into()) } + /// Retrieve the base prototype that a particular QName is defined in. + fn get_base_proto(self, name: &QName) -> Option> { + if self.has_own_property(name) { + return Some(self.into()); + } + + if let Some(proto) = self.proto() { + return proto.get_base_proto(name); + } + + None + } + /// Set a property by it's QName. fn set_property( self, @@ -300,6 +313,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy _arguments: &[Value<'gc>], _avm: &mut Avm2<'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, + _base_proto: Option>, ) -> Result, Error> { Err("Object is not callable".into()) } diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 2c31439419e0..361ebd8c88b7 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -142,7 +142,9 @@ impl<'gc> Property<'gc> { this: Object<'gc>, ) -> Result, Error> { match self { - Property::Virtual { get: Some(get), .. } => get.exec(avm, context, Some(this), &[]), + Property::Virtual { get: Some(get), .. } => { + get.exec(Some(this), &[], avm, context, this.proto()) + } Property::Virtual { get: None, .. } => Ok(Value::Undefined.into()), Property::Stored { value, .. } => Ok(value.to_owned().into()), Property::Slot { slot_id, .. } => this.get_slot(*slot_id).map(|v| v.into()), @@ -168,8 +170,13 @@ impl<'gc> Property<'gc> { match self { Property::Virtual { set, .. } => { if let Some(function) = set { - let return_value = - function.exec(avm, context, Some(this), &[new_value.into()])?; + let return_value = function.exec( + Some(this), + &[new_value.into()], + avm, + context, + this.proto(), + )?; Ok(return_value.is_immediate()) } else { Ok(true) @@ -212,8 +219,13 @@ impl<'gc> Property<'gc> { match self { Property::Virtual { set, .. } => { if let Some(function) = set { - let return_value = - function.exec(avm, context, Some(this), &[new_value.into()])?; + let return_value = function.exec( + Some(this), + &[new_value.into()], + avm, + context, + this.proto(), + )?; Ok(return_value.is_immediate()) } else { Ok(true) From 785832b7f33b47f45aca089528e265202d83707a Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 27 Feb 2020 22:00:02 -0500 Subject: [PATCH 092/189] Add `as3_inheritance` test, which is primarily designed to test method calls, constructor execution, and usage of `super`. --- core/tests/regression_tests.rs | 1 + core/tests/swfs/avm2/inheritance/Test.as | 81 ++++++++++++++++++++ core/tests/swfs/avm2/inheritance/output.txt | 30 ++++++++ core/tests/swfs/avm2/inheritance/test.fla | Bin 0 -> 3985 bytes core/tests/swfs/avm2/inheritance/test.swf | Bin 0 -> 1255 bytes 5 files changed, 112 insertions(+) create mode 100644 core/tests/swfs/avm2/inheritance/Test.as create mode 100644 core/tests/swfs/avm2/inheritance/output.txt create mode 100644 core/tests/swfs/avm2/inheritance/test.fla create mode 100644 core/tests/swfs/avm2/inheritance/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 6699fecac447..5b7e7966fd5b 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -231,6 +231,7 @@ swf_tests! { (as3_function_call, "avm2/function_call", 1), (as3_constructor_call, "avm2/constructor_call", 1), (as3_class_methods, "avm2/class_methods", 1), + (as3_inheritance, "avm2/inheritance", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/inheritance/Test.as b/core/tests/swfs/avm2/inheritance/Test.as new file mode 100644 index 000000000000..ab3c3d3d6cea --- /dev/null +++ b/core/tests/swfs/avm2/inheritance/Test.as @@ -0,0 +1,81 @@ +package { + public class Test { + } +} + +class Test2 { + { + trace("Class constructor"); + } + + function Test2() { + trace("Instance constructor"); + } + + function method() { + trace("Instance method"); + } + + function method2() { + trace("Instance method 2"); + } +} + +class Test3 extends Test2 { + { + trace("Child class constructor"); + } + + function Test3() { + trace("Child instance constructor pre-super"); + super(); + trace("Child instance constructor post-super"); + } + + override function method() { + trace("Child instance method pre-super"); + super.method(); + trace("Child instance method post-super"); + } + + function method3() { + trace("Child instance method3 pre-super"); + super.method(); + trace("Child instance method3 post-super"); + } +} + +class Test4 extends Test3 { + { + trace("Grandchild class constructor"); + } + + function Test4() { + trace("Grandchild instance constructor pre-super"); + super(); + trace("Grandchild instance constructor post-super"); + } + + override function method2() { + trace("Grandchild instance method2 pre-super"); + super.method2(); + trace("Grandchild instance method2 post-super"); + } + + override function method3() { + trace("Grandchild instance method3 pre-super"); + super.method3(); + trace("Grandchild instance method3 post-super"); + } +} + +trace("Script initializer"); +var x = new Test3(); +x.method(); +x.method2(); +x.method3(); + +var y = new Test4(); +y.method(); +y.method2(); +y.method3(); \ No newline at end of file diff --git a/core/tests/swfs/avm2/inheritance/output.txt b/core/tests/swfs/avm2/inheritance/output.txt new file mode 100644 index 000000000000..3b93ab9bbf0d --- /dev/null +++ b/core/tests/swfs/avm2/inheritance/output.txt @@ -0,0 +1,30 @@ +Class constructor +Child class constructor +Grandchild class constructor +Script initializer +Child instance constructor pre-super +Instance constructor +Child instance constructor post-super +Child instance method pre-super +Instance method +Child instance method post-super +Instance method 2 +Child instance method3 pre-super +Instance method +Child instance method3 post-super +Grandchild instance constructor pre-super +Child instance constructor pre-super +Instance constructor +Child instance constructor post-super +Grandchild instance constructor post-super +Child instance method pre-super +Instance method +Child instance method post-super +Grandchild instance method2 pre-super +Instance method 2 +Grandchild instance method2 post-super +Grandchild instance method3 pre-super +Child instance method3 pre-super +Instance method +Child instance method3 post-super +Grandchild instance method3 post-super diff --git a/core/tests/swfs/avm2/inheritance/test.fla b/core/tests/swfs/avm2/inheritance/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..84376c02ef3abb67f80e2788b6e658e5112c93b8 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/inheritance/test.swf b/core/tests/swfs/avm2/inheritance/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..5234147940b546e5764a1159dba7cb2a93b69b27 GIT binary patch literal 1255 zcmV~>1E%eGkuf1f_u^1K7)9I^j~C%t1*yzw35Dq{_od%7xMwhZxM?B ziBJ}!&7_JDs*um0KR?SLG?VCGDJ~bCcCp_s{Zih0zxMN5_ox1H<QXb zG1{|uo9CAi`ZW2ECJ6W>(2}@3L)shOb4%h>5T_-xS2R}F#qCNdNZh!{amd|fr!jlE zWhEG71cQuVka2m?7>f}MDhGqg!7Al3?S6&Fm~(-8d(bl1Uz>LOjeW>lcAJ;Z*aOEr zCLCN&Y9WySA<4O>2O89|pZl#LU)9F)uIX(N%*MxsFU znoAA4*KL^}wbz{^n^#(PcWP|givOng1OB+WwO*}s8ocWC4-Q(qYIPi56%X~Qy~mRx-cSM{KB^`b)F^8jk69{;Fp5)7rMTtnDJG`M(1K;&vNXp3l`h8&6fQe zOkmS>yX&Qr+339EOIGKw^mNZCt?2r_(!0Lha_x5RbU`nTF6i~)-i?`!#tU)hm!3Vx zJc=Uz&f!PA?e^l&#bpzAk?C5n=A)bbhVpkftXh}Ds(0-p?lbBC=#@^}h05Gf_PVfL zxc)!)RyCT&*_fQjPGr;BE9vRXVfJ zi$joqr7$EI@(?R97z}lY;RgzZV1$QQj0lXVe=lRA5xAj5li(y_B*_@b3e{x9h*Ur- z!$?yVO+jHI7f^&1P*e-48cO0YhMzrXM1g=fA~6L+BUD0Ikq}X#oX9x|LK9e%u#`eD zHxeD_FCiOaNe&J4!4^8c;r$2Y1)dRYauEYKSGBElykHo*4)log>o9PG%*dj!xB@G%&pr^csXd|r{sRwm@RC}*PV_q2N4X{jtke=d#l0h{|Z9xiIA;Ys-|o!$c%W~^~mld^1g-36!N}(`RgZvaVQL5{PC(sYj_)@1y2GxNr6tEEUQ}f zc-_NAG&l(FW{F2Yogh$PaiDV~n!Tder}g?(y?#xv=k$6Aj(sWTf129yQ!fjTJWfpy Rcg@(pQ0NIL{{nbe_SxrLa@qg@ literal 0 HcmV?d00001 From 665d7a4342319902cee53eaf3bd6a87e6d4189fc Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 27 Feb 2020 23:38:56 -0500 Subject: [PATCH 093/189] Implement `getsuper` and `setsuper`. This required the reintroduction of dedicated reciever parameters to `Object.get_property_local` and `Object.set_property`, which I had removed from the AVM1 code I copied it from. It turns out being able to change the reciever was actually necessary in order to make super set/get work. --- core/src/avm2.rs | 91 ++++++++++++++++++++++++++++++---- core/src/avm2/function.rs | 7 ++- core/src/avm2/object.rs | 10 ++-- core/src/avm2/scope.rs | 5 +- core/src/avm2/script_object.rs | 14 +++--- 5 files changed, 105 insertions(+), 22 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 318fdd852e2f..50e03a873bbf 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -457,6 +457,8 @@ impl<'gc> Avm2<'gc> { Op::SetProperty { index } => self.op_set_property(context, index), Op::InitProperty { index } => self.op_init_property(context, index), 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), @@ -637,7 +639,7 @@ impl<'gc> Avm2<'gc> { let base_proto = receiver.get_base_proto(&name); let function = base_proto .unwrap_or(receiver) - .get_property_local(&name, self, context)? + .get_property_local(receiver, &name, self, context)? .resolve(self, context)? .as_object()?; @@ -661,7 +663,7 @@ impl<'gc> Avm2<'gc> { .resolve_multiname(&multiname) .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); let function = receiver - .get_property(&name?, self, context)? + .get_property(receiver, &name?, self, context)? .resolve(self, context)? .as_object()?; @@ -686,7 +688,7 @@ impl<'gc> Avm2<'gc> { let base_proto = receiver.get_base_proto(&name); let function = base_proto .unwrap_or(receiver) - .get_property_local(&name, self, context)? + .get_property_local(receiver, &name, self, context)? .resolve(self, context)? .as_object()?; @@ -747,7 +749,7 @@ impl<'gc> Avm2<'gc> { let base_proto = base_proto?; let function = base_proto - .get_property(&name?, self, context)? + .get_property(receiver, &name?, self, context)? .resolve(self, context)? .as_object()?; @@ -784,7 +786,7 @@ impl<'gc> Avm2<'gc> { let base_proto = base_proto?; let function = base_proto - .get_property(&name?, self, context)? + .get_property(receiver, &name?, self, context)? .resolve(self, context)? .as_object()?; @@ -818,7 +820,7 @@ impl<'gc> Avm2<'gc> { }); let value = object - .get_property(&name?, self, context)? + .get_property(object, &name?, self, context)? .resolve(self, context)?; self.push(value); @@ -835,7 +837,7 @@ impl<'gc> Avm2<'gc> { let object = self.pop().as_object()?; if let Some(name) = object.resolve_multiname(&multiname) { - object.set_property(&name, value, self, context) + object.set_property(object, &name, value, self, context) } else { //TODO: Non-dynamic objects should fail //TODO: This should only work if the public namespace is present @@ -843,7 +845,7 @@ impl<'gc> Avm2<'gc> { .local_name() .ok_or_else(|| "Cannot set property using any name".into()); let name = QName::dynamic_name(local_name?); - object.set_property(&name, value, self, context) + object.set_property(object, &name, value, self, context) } } @@ -886,6 +888,72 @@ impl<'gc> Avm2<'gc> { Ok(()) } + fn op_get_super( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result<(), Error> { + let multiname = self.pool_multiname(index)?; + let object = self.pop().as_object()?; + let base_proto: Result, 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?; + + let name: Result = + base_proto.resolve_multiname(&multiname).ok_or_else(|| { + format!( + "Could not resolve {:?} as super property", + multiname.local_name() + ) + .into() + }); + + let value = base_proto + .get_property(object, &name?, self, context)? + .resolve(self, context)?; + + self.push(value); + + Ok(()) + } + + fn op_set_super( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result<(), Error> { + let multiname = self.pool_multiname(index)?; + let object = self.pop().as_object()?; + let base_proto: Result, 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?; + + let name: Result = + base_proto.resolve_multiname(&multiname).ok_or_else(|| { + format!( + "Could not resolve {:?} as super property", + multiname.local_name() + ) + .into() + }); + + let value = self.pop(); + + base_proto.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(); @@ -1071,6 +1139,7 @@ impl<'gc> Avm2<'gc> { let proto = ctor .get_property( + ctor, &QName::new(Namespace::public_namespace(), "prototype"), self, context, @@ -1102,11 +1171,12 @@ impl<'gc> Avm2<'gc> { format!("Could not resolve property {:?}", multiname.local_name()).into() }); let ctor = source - .get_property(&ctor_name?, self, context)? + .get_property(source, &ctor_name?, self, context)? .resolve(self, context)? .as_object()?; let proto = ctor .get_property( + ctor, &QName::new(Namespace::public_namespace(), "prototype"), self, context, @@ -1145,7 +1215,7 @@ impl<'gc> Avm2<'gc> { let base_proto = base_proto?; let function = base_proto - .get_property_local(&name, self, context)? + .get_property_local(receiver, &name, self, context)? .resolve(self, context)? .as_object()?; @@ -1174,6 +1244,7 @@ impl<'gc> Avm2<'gc> { let name = self.pop(); object.set_property( + object, &QName::new(Namespace::public_namespace(), name.as_string()?), value, self, diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index efd380e3dba0..e75738817c79 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -268,6 +268,7 @@ impl<'gc> FunctionObject<'gc> { ) -> Result<(Object<'gc>, Object<'gc>), Error> { let super_proto: Result, Error> = base_class .get_property( + base_class, &QName::new(Namespace::public_namespace(), "prototype"), avm, context, @@ -432,6 +433,7 @@ impl<'gc> FunctionObject<'gc> { impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn get_property_local( self, + reciever: Object<'gc>, name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -439,11 +441,12 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0 .read() .base - .get_property_local(name, avm, context, self.into()) + .get_property_local(reciever, name, avm, context) } fn set_property( self, + reciever: Object<'gc>, name: &QName, value: Value<'gc>, avm: &mut Avm2<'gc>, @@ -452,7 +455,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0 .write(context.gc_context) .base - .set_property(name, value, avm, context, self.into()) + .set_property(reciever, name, value, avm, context) } fn init_property( diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index acfc134b5ef7..c9a3e9c44300 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -29,6 +29,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// into account. fn get_property_local( self, + reciever: Object<'gc>, name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -37,16 +38,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Retrieve a property by it's QName. fn get_property( self, + reciever: Object<'gc>, name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { if self.has_own_property(name) { - return self.get_property_local(name, avm, context); + return self.get_property_local(reciever, name, avm, context); } if let Some(proto) = self.proto() { - return proto.get_property(name, avm, context); + return proto.get_property(reciever, name, avm, context); } Ok(Value::Undefined.into()) @@ -68,6 +70,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Set a property by it's QName. fn set_property( self, + reciever: Object<'gc>, name: &QName, value: Value<'gc>, avm: &mut Avm2<'gc>, @@ -263,8 +266,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &type_entry.abc(), type_entry.instance().super_name.clone(), )?; + let reciever: Object<'gc> = (*self).into(); let super_class: Result, Error> = self - .get_property(&super_name, avm, context)? + .get_property(reciever, &super_name, avm, context)? .resolve(avm, context)? .as_object() .map_err(|_e| { diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index 3fb8a7db6e6d..e4ee311473e5 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -128,7 +128,10 @@ impl<'gc> Scope<'gc> { ) -> Option, Error>> { if let Some(qname) = self.locals().resolve_multiname(name) { if self.locals().has_property(&qname) { - return Some(self.locals().get_property(&qname, avm, context)); + return Some( + self.locals() + .get_property(self.values, &qname, avm, context), + ); } } diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 406d9e2b4203..043d832c052c 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -37,17 +37,19 @@ pub struct ScriptObjectData<'gc> { impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn get_property_local( self, + reciever: Object<'gc>, name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { self.0 .read() - .get_property_local(name, avm, context, self.into()) + .get_property_local(reciever, name, avm, context) } fn set_property( self, + reciever: Object<'gc>, name: &QName, value: Value<'gc>, avm: &mut Avm2<'gc>, @@ -55,7 +57,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { ) -> Result<(), Error> { self.0 .write(context.gc_context) - .set_property(name, value, avm, context, self.into()) + .set_property(reciever, name, value, avm, context) } fn init_property( @@ -213,15 +215,15 @@ impl<'gc> ScriptObjectData<'gc> { pub fn get_property_local( &self, + reciever: Object<'gc>, name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, ) -> Result, Error> { let prop = self.values.get(name); if let Some(prop) = prop { - prop.get(avm, context, this) + prop.get(avm, context, reciever) } else { Ok(Value::Undefined.into()) } @@ -229,17 +231,17 @@ impl<'gc> ScriptObjectData<'gc> { pub fn set_property( &mut self, + reciever: Object<'gc>, name: &QName, value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, ) -> Result<(), Error> { if let Some(prop) = self.values.get_mut(name) { if let Some(slot_id) = prop.slot_id() { self.set_slot(slot_id, value, context.gc_context)?; } else { - prop.set(avm, context, this, value)?; + prop.set(avm, context, reciever, value)?; } } else { //TODO: Not all classes are dynamic like this From b8106d24d2dbde88a971a9b85dc180e33f81d494 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 28 Feb 2020 20:00:18 -0500 Subject: [PATCH 094/189] Ensure virtual setters are run when defined on a prototype. Normally, `set_property` only affects the object it was called on, which makes sense: otherwise, we couldn't override values that originate from a class prototype without accidentally monkey-patching the prototype. However, virtual setters only exist in prototypes and need to be accessible from child objects. The solution to this is to have a specific method to check if a virtual setter exists. Virtual setters are then resolved through the prototype chain. If no virtual setter exists, then the reciever object is handed the value. Note that we always use the `reciever` object rather than `self` so that `setsuper` can work correctly. In `setsuper`, we resolve the base class, and then set properties on it with the actual object in question as it's reciever. If a virtual setter is called, it will get the actual object it should be manipulating; and otherwise, prototypes will not be modified or consulted. --- core/src/avm2.rs | 7 ++-- core/src/avm2/function.rs | 13 ++++--- core/src/avm2/object.rs | 63 +++++++++++++++++++++++++++++++++- core/src/avm2/script_object.rs | 28 ++++++++++----- 4 files changed, 94 insertions(+), 17 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 50e03a873bbf..b3e40443a451 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -859,7 +859,7 @@ impl<'gc> Avm2<'gc> { let object = self.pop().as_object()?; if let Some(name) = object.resolve_multiname(&multiname) { - object.init_property(&name, value, self, context) + object.init_property(object, &name, value, self, context) } else { //TODO: Non-dynamic objects should fail //TODO: This should only work if the public namespace is present @@ -867,7 +867,7 @@ impl<'gc> Avm2<'gc> { .local_name() .ok_or_else(|| "Cannot set property using any name".into()); let name = QName::dynamic_name(local_name?); - object.init_property(&name, value, self, context) + object.init_property(object, &name, value, self, context) } } @@ -927,6 +927,7 @@ impl<'gc> Avm2<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result<(), Error> { + let value = self.pop(); let multiname = self.pool_multiname(index)?; let object = self.pop().as_object()?; let base_proto: Result, Error> = self @@ -947,8 +948,6 @@ impl<'gc> Avm2<'gc> { .into() }); - let value = self.pop(); - base_proto.set_property(object, &name?, value, self, context)?; Ok(()) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index e75738817c79..e21184efa92e 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -444,7 +444,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { .get_property_local(reciever, name, avm, context) } - fn set_property( + fn set_property_local( self, reciever: Object<'gc>, name: &QName, @@ -455,11 +455,12 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0 .write(context.gc_context) .base - .set_property(reciever, name, value, avm, context) + .set_property_local(reciever, name, value, avm, context) } - fn init_property( + fn init_property_local( self, + reciever: Object<'gc>, name: &QName, value: Value<'gc>, avm: &mut Avm2<'gc>, @@ -468,7 +469,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0 .write(context.gc_context) .base - .init_property(name, value, avm, context, self.into()) + .init_property_local(reciever, name, value, avm, context) } fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool { @@ -505,6 +506,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.has_own_property(name) } + fn has_own_virtual_setter(self, name: &QName) -> bool { + self.0.read().base.has_own_virtual_setter(name) + } + fn proto(&self) -> Option> { self.0.read().base.proto() } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index c9a3e9c44300..7082c7a59ecb 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -67,6 +67,16 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy None } + /// Set a property on this specific object. + fn set_property_local( + self, + reciever: Object<'gc>, + name: &QName, + value: Value<'gc>, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result<(), Error>; + /// Set a property by it's QName. fn set_property( self, @@ -75,16 +85,63 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result<(), Error> { + if self.has_own_virtual_setter(name) { + return self.set_property_local(reciever, name, value, avm, context); + } + + let mut proto = self.proto(); + while let Some(my_proto) = proto { + //NOTE: This only works because we validate ahead-of-time that + //we're calling a virtual setter. If you call `set_property` on + //a non-virtual you will actually alter the prototype. + if my_proto.has_own_virtual_setter(name) { + return my_proto.set_property(reciever, name, value, avm, context); + } + + proto = my_proto.proto(); + } + + reciever.set_property_local(reciever, name, value, avm, context) + } + + /// Init a property on this specific object. + fn init_property_local( + self, + reciever: Object<'gc>, + name: &QName, + value: Value<'gc>, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error>; /// Init a property by it's QName. fn init_property( self, + reciever: Object<'gc>, name: &QName, value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error>; + ) -> Result<(), Error> { + if self.has_own_virtual_setter(name) { + return self.init_property_local(reciever, name, value, avm, context); + } + + let mut proto = self.proto(); + while let Some(my_proto) = proto { + //NOTE: This only works because we validate ahead-of-time that + //we're calling a virtual setter. If you call `set_property` on + //a non-virtual you will actually alter the prototype. + if my_proto.has_own_virtual_setter(name) { + return my_proto.init_property(reciever, name, value, avm, context); + } + + proto = my_proto.proto(); + } + + reciever.init_property_local(reciever, name, value, avm, context) + } /// Retrieve a slot by it's index. fn get_slot(self, id: u32) -> Result, Error>; @@ -140,6 +197,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// of the prototype chain. fn has_own_property(self, name: &QName) -> bool; + /// Check if a particular object contains a virtual setter by the given + /// name. + fn has_own_virtual_setter(self, name: &QName) -> bool; + /// Indicates whether or not a property is overwritable. fn is_property_overwritable(self, _name: &QName) -> bool { false diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 043d832c052c..d6ed1208ee4d 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -47,7 +47,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { .get_property_local(reciever, name, avm, context) } - fn set_property( + fn set_property_local( self, reciever: Object<'gc>, name: &QName, @@ -57,11 +57,12 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { ) -> Result<(), Error> { self.0 .write(context.gc_context) - .set_property(reciever, name, value, avm, context) + .set_property_local(reciever, name, value, avm, context) } - fn init_property( + fn init_property_local( self, + reciever: Object<'gc>, name: &QName, value: Value<'gc>, avm: &mut Avm2<'gc>, @@ -69,7 +70,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { ) -> Result<(), Error> { self.0 .write(context.gc_context) - .init_property(name, value, avm, context, self.into()) + .init_property_local(reciever, name, value, avm, context) } fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool { @@ -106,6 +107,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().has_own_property(name) } + fn has_own_virtual_setter(self, name: &QName) -> bool { + self.0.read().has_own_virtual_setter(name) + } + fn proto(&self) -> Option> { self.0.read().proto } @@ -229,7 +234,7 @@ impl<'gc> ScriptObjectData<'gc> { } } - pub fn set_property( + pub fn set_property_local( &mut self, reciever: Object<'gc>, name: &QName, @@ -252,19 +257,19 @@ impl<'gc> ScriptObjectData<'gc> { Ok(()) } - pub fn init_property( + pub fn init_property_local( &mut self, + reciever: Object<'gc>, name: &QName, value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, ) -> Result<(), Error> { if let Some(prop) = self.values.get_mut(name) { if let Some(slot_id) = prop.slot_id() { self.init_slot(slot_id, value, context.gc_context)?; } else { - prop.init(avm, context, this, value)?; + prop.init(avm, context, reciever, value)?; } } else { //TODO: Not all classes are dynamic like this @@ -335,6 +340,13 @@ impl<'gc> ScriptObjectData<'gc> { self.values.get(name).is_some() } + pub fn has_own_virtual_setter(&self, name: &QName) -> bool { + match self.values.get(name) { + Some(Property::Virtual { .. }) => true, + _ => false, + } + } + pub fn proto(&self) -> Option> { self.proto } From 54b792ef3acf1d39e6b9ba08e92abbb646717aa1 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 28 Feb 2020 20:55:09 -0500 Subject: [PATCH 095/189] Ensure that called setters are properly resolved so that errors in setters propagate up the Rust stack correctly. The previous system for handling setters would execute the setter and then return a value to indicate whether or not the caller needed to resolve a stack frame. However, no caller of `Property.set` actually did this. Ergo, errors in setters and getters would not resolve up the stack at the correct time. This problem also exists in AVM1 but is far less noticable as AVM1 only has two very uncommon runtime errors and very few movies use `throw`. --- core/src/avm2/function.rs | 4 ++-- core/src/avm2/object.rs | 32 ++++++++++++++++++++++------ core/src/avm2/property.rs | 38 ++++++++++++++-------------------- core/src/avm2/script_object.rs | 22 +++++++++++--------- 4 files changed, 56 insertions(+), 40 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index e21184efa92e..2e324643491a 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -451,7 +451,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error> { + ) -> Result, Error> { self.0 .write(context.gc_context) .base @@ -465,7 +465,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error> { + ) -> Result, Error> { self.0 .write(context.gc_context) .base diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 7082c7a59ecb..2af100d12fe1 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -68,6 +68,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } /// Set a property on this specific object. + /// + /// This function returns a `ReturnValue` which should be resolved. The + /// resulting `Value` is unimportant and should be discarded. fn set_property_local( self, reciever: Object<'gc>, @@ -75,7 +78,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error>; + ) -> Result, Error>; /// Set a property by it's QName. fn set_property( @@ -87,7 +90,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error> { if self.has_own_virtual_setter(name) { - return self.set_property_local(reciever, name, value, avm, context); + self.set_property_local(reciever, name, value, avm, context)? + .resolve(avm, context)?; + + return Ok(()); } let mut proto = self.proto(); @@ -102,10 +108,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy proto = my_proto.proto(); } - reciever.set_property_local(reciever, name, value, avm, context) + reciever + .set_property_local(reciever, name, value, avm, context)? + .resolve(avm, context)?; + + Ok(()) } /// Init a property on this specific object. + /// + /// This function returns a `ReturnValue` which should be resolved. The + /// resulting `Value` is unimportant and should be discarded. fn init_property_local( self, reciever: Object<'gc>, @@ -113,7 +126,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error>; + ) -> Result, Error>; /// Init a property by it's QName. fn init_property( @@ -125,7 +138,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error> { if self.has_own_virtual_setter(name) { - return self.init_property_local(reciever, name, value, avm, context); + self.init_property_local(reciever, name, value, avm, context)? + .resolve(avm, context)?; + + return Ok(()); } let mut proto = self.proto(); @@ -140,7 +156,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy proto = my_proto.proto(); } - reciever.init_property_local(reciever, name, value, avm, context) + reciever + .init_property_local(reciever, name, value, avm, context)? + .resolve(avm, context)?; + + Ok(()) } /// Retrieve a slot by it's index. diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 361ebd8c88b7..16186fa90338 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -153,10 +153,8 @@ impl<'gc> Property<'gc> { /// Set a property slot. /// - /// This function returns `true` if the set has completed, or `false` if - /// it has not yet occured. If `false`, and you need to run code after the - /// set has occured, you must recursively execute the top-most frame via - /// `run_current_frame`. + /// This function returns a `ReturnValue` which should be resolved. The + /// resulting `Value` is unimportant and should be discarded. /// /// This function cannot set slot properties and will panic if one /// is encountered. @@ -166,21 +164,20 @@ impl<'gc> Property<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, new_value: impl Into>, - ) -> Result { + ) -> Result, Error> { match self { Property::Virtual { set, .. } => { if let Some(function) = set { - let return_value = function.exec( + return function.exec( Some(this), &[new_value.into()], avm, context, this.proto(), - )?; - Ok(return_value.is_immediate()) - } else { - Ok(true) + ); } + + Ok(Value::Undefined.into()) } Property::Stored { value, attributes, .. @@ -189,7 +186,7 @@ impl<'gc> Property<'gc> { *value = new_value.into(); } - Ok(true) + Ok(Value::Undefined.into()) } Property::Slot { .. } => panic!("Cannot recursively set slots"), } @@ -202,10 +199,8 @@ impl<'gc> Property<'gc> { /// properties, at least once. Virtual properties with no setter cannot be /// initialized. /// - /// This function returns `true` if the set has completed, or `false` if - /// it has not yet occured. If `false`, and you need to run code after the - /// set has occured, you must recursively execute the top-most frame via - /// `run_current_frame`. + /// This function returns a `ReturnValue` which should be resolved. The + /// resulting `Value` is unimportant and should be discarded. /// /// This function cannot initialize slot properties and will panic if one /// is encountered. @@ -215,26 +210,25 @@ impl<'gc> Property<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, new_value: impl Into>, - ) -> Result { + ) -> Result, Error> { match self { Property::Virtual { set, .. } => { if let Some(function) = set { - let return_value = function.exec( + return function.exec( Some(this), &[new_value.into()], avm, context, this.proto(), - )?; - Ok(return_value.is_immediate()) - } else { - Ok(true) + ); } + + Ok(Value::Undefined.into()) } Property::Stored { value, .. } => { *value = new_value.into(); - Ok(true) + Ok(Value::Undefined.into()) } Property::Slot { .. } => panic!("Cannot recursively init slots"), } diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index d6ed1208ee4d..e2ab31b78972 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -54,7 +54,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error> { + ) -> Result, Error> { self.0 .write(context.gc_context) .set_property_local(reciever, name, value, avm, context) @@ -67,7 +67,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error> { + ) -> Result, Error> { self.0 .write(context.gc_context) .init_property_local(reciever, name, value, avm, context) @@ -241,20 +241,21 @@ impl<'gc> ScriptObjectData<'gc> { value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error> { + ) -> Result, Error> { if let Some(prop) = self.values.get_mut(name) { if let Some(slot_id) = prop.slot_id() { self.set_slot(slot_id, value, context.gc_context)?; + Ok(Value::Undefined.into()) } else { - prop.set(avm, context, reciever, value)?; + prop.set(avm, context, reciever, value) } } else { //TODO: Not all classes are dynamic like this self.values .insert(name.clone(), Property::new_dynamic_property(value)); - } - Ok(()) + Ok(Value::Undefined.into()) + } } pub fn init_property_local( @@ -264,20 +265,21 @@ impl<'gc> ScriptObjectData<'gc> { value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error> { + ) -> Result, Error> { if let Some(prop) = self.values.get_mut(name) { if let Some(slot_id) = prop.slot_id() { self.init_slot(slot_id, value, context.gc_context)?; + Ok(Value::Undefined.into()) } else { - prop.init(avm, context, reciever, value)?; + prop.init(avm, context, reciever, value) } } else { //TODO: Not all classes are dynamic like this self.values .insert(name.clone(), Property::new_dynamic_property(value)); - } - Ok(()) + Ok(Value::Undefined.into()) + } } pub fn delete_property(&mut self, name: &QName) -> bool { From c5e3af2053d9fd70d9679e755bdef53ac827e770 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 28 Feb 2020 21:37:25 -0500 Subject: [PATCH 096/189] When resolving `get_property`, skip over virtual properties that do not have a defined getter. --- core/src/avm2/function.rs | 4 ++++ core/src/avm2/object.rs | 8 +++++++- core/src/avm2/script_object.rs | 13 ++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 2e324643491a..3076a77e9851 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -506,6 +506,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.has_own_property(name) } + fn has_own_virtual_getter(self, name: &QName) -> bool { + self.0.read().base.has_own_virtual_getter(name) + } + fn has_own_virtual_setter(self, name: &QName) -> bool { self.0.read().base.has_own_virtual_setter(name) } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 2af100d12fe1..06bdf2fb57f5 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -43,7 +43,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { - if self.has_own_property(name) { + let has_no_getter = self.has_own_virtual_setter(name) && !self.has_own_virtual_getter(name); + + if self.has_own_property(name) && !has_no_getter { return self.get_property_local(reciever, name, avm, context); } @@ -217,6 +219,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// of the prototype chain. fn has_own_property(self, name: &QName) -> bool; + /// Check if a particular object contains a virtual getter by the given + /// name. + fn has_own_virtual_getter(self, name: &QName) -> bool; + /// Check if a particular object contains a virtual setter by the given /// name. fn has_own_virtual_setter(self, name: &QName) -> bool; diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index e2ab31b78972..6cdffc665789 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -107,6 +107,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().has_own_property(name) } + fn has_own_virtual_getter(self, name: &QName) -> bool { + self.0.read().has_own_virtual_getter(name) + } + fn has_own_virtual_setter(self, name: &QName) -> bool { self.0.read().has_own_virtual_setter(name) } @@ -342,9 +346,16 @@ impl<'gc> ScriptObjectData<'gc> { self.values.get(name).is_some() } + pub fn has_own_virtual_getter(&self, name: &QName) -> bool { + match self.values.get(name) { + Some(Property::Virtual { get: Some(_), .. }) => true, + _ => false, + } + } + pub fn has_own_virtual_setter(&self, name: &QName) -> bool { match self.values.get(name) { - Some(Property::Virtual { .. }) => true, + Some(Property::Virtual { set: Some(_), .. }) => true, _ => false, } } From 5abc78d3bdbba476c036f5e499914176547d067f Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 28 Feb 2020 21:41:19 -0500 Subject: [PATCH 097/189] Add test of AVM2 virtual properties. This tests: * Getter invocation * Setter invocation * Properties with one or the other, but not both * Inheritance * Superproperty getters and setters * Getters with inherited setter * Setters with inherited getter --- core/tests/regression_tests.rs | 1 + .../swfs/avm2/virtual_properties/Test.as | 69 ++++++++++++++++++ .../swfs/avm2/virtual_properties/output.txt | 16 ++++ .../swfs/avm2/virtual_properties/test.fla | Bin 0 -> 3985 bytes .../swfs/avm2/virtual_properties/test.swf | Bin 0 -> 1298 bytes 5 files changed, 86 insertions(+) create mode 100644 core/tests/swfs/avm2/virtual_properties/Test.as create mode 100644 core/tests/swfs/avm2/virtual_properties/output.txt create mode 100644 core/tests/swfs/avm2/virtual_properties/test.fla create mode 100644 core/tests/swfs/avm2/virtual_properties/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 5b7e7966fd5b..bfe60e32566b 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -232,6 +232,7 @@ swf_tests! { (as3_constructor_call, "avm2/constructor_call", 1), (as3_class_methods, "avm2/class_methods", 1), (as3_inheritance, "avm2/inheritance", 1), + (as3_virtual_properties, "avm2/virtual_properties", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/virtual_properties/Test.as b/core/tests/swfs/avm2/virtual_properties/Test.as new file mode 100644 index 000000000000..c26496458ed9 --- /dev/null +++ b/core/tests/swfs/avm2/virtual_properties/Test.as @@ -0,0 +1,69 @@ +package { + public class Test { + } +} + +class Test2 { + function get prop() { + return "Test2 Prop"; + } + + function set prop(val:String) { + trace(val); + } + + function set prop2(val:String) { + trace("Test2 Set Prop2"); + } + + function get prop3() { + return "Test2 Prop3"; + } +} + +class Test3 extends Test2 { + function get prop2() { + return "Test3 Prop2"; + } + + function set prop3(val:String) { + trace(val); + } +} + +class Test4 extends Test3 { + override function get prop() { + trace("Child Prop2 getter"); + return super.prop; + } + + override function set prop(val:String) { + trace("Child Prop2 Setter"); + super.prop = val; + } +} + +var w = new Test2(); +trace(w.prop); +w.prop = "Setting Test2 Prop"; + +w.prop2 = "TEST FAIL - Test2::prop2 SETTER DOES NOT TRACE THIS VALUE"; + +trace(w.prop3); + +var x = new Test3(); +trace(x.prop); +x.prop = "Setting Test3 Prop"; + +trace(x.prop2); +x.prop3 = "Setting Test3 Prop3"; + +var y = new Test4(); +trace(y.prop); +y.prop = "Setting Test4 Prop"; + +trace(y.prop2); +y.prop2 = "TEST FAIL - Test4::prop2 SETTER DOES NOT TRACE THIS VALUE"; + +trace(y.prop3); +y.prop3 = "Setting Test4 Prop3"; \ No newline at end of file diff --git a/core/tests/swfs/avm2/virtual_properties/output.txt b/core/tests/swfs/avm2/virtual_properties/output.txt new file mode 100644 index 000000000000..5cc5898e5aec --- /dev/null +++ b/core/tests/swfs/avm2/virtual_properties/output.txt @@ -0,0 +1,16 @@ +Test2 Prop +Setting Test2 Prop +Test2 Set Prop2 +Test2 Prop3 +Test2 Prop +Setting Test3 Prop +Test3 Prop2 +Setting Test3 Prop3 +Child Prop2 getter +Test2 Prop +Child Prop2 Setter +Setting Test4 Prop +Test3 Prop2 +Test2 Set Prop2 +Test2 Prop3 +Setting Test4 Prop3 diff --git a/core/tests/swfs/avm2/virtual_properties/test.fla b/core/tests/swfs/avm2/virtual_properties/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..84376c02ef3abb67f80e2788b6e658e5112c93b8 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/virtual_properties/test.swf b/core/tests/swfs/avm2/virtual_properties/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..121fc7c63c9ca879955d3929be2335f61f0d1620 GIT binary patch literal 1298 zcmV+t1?~DnS5qs$2><|ioSl^Wa??f>hxa0BCEp!ilVDA%*G`yfq}A?se&?d2l|CTx4MO4H z5lUlJjmik24RUmJ^f-mkTx5Sbx18Il<$kWdUfC@5YF2MlZPZ>rx9nm5;6siNd3}^CtyK85SU3TO)?iq8XV_BRw#@#! z%e5oJUbswD{3cb2=S^OyDxP&Nolo)Ze2R0b5-;Rz3Qtu!`>5rM+H$$*f&`wZykNul zrqk0aFLbltF!zj}**6OR(vGF?8Ow{8s7_LW$Cb9<{$jLk-*HmWZCT`OCk0-g=r+q8 zORlbTR_EdJSzMOCmJm2uLJ+{Ho8L7@!=C<8c`-OJjg6i;Ok7y7meJ&Hqvq&+XVlaW_KM8|)3W#V-Wy)9VcDiJT3ww~=00&T`w}el zK<|mRrMHc+2kVCI8jAj7QOTW`JU+G2HG8|>JPR+@Hmp;q?xJg5+a6X51$}q$*2uR9 zdxiCSsZdnayM?#=X3sYJQ(OoiO7=L<{YI_c$Ua%$e4f4S;fl9Kv-Mh|QG1oGZrAGB zAGaIX#;f&>TDI|Qvz~pu{`{xfsbF5AvGb^ON>;u&XZc%m#?A*TWxl?={K=PBne5Pz zzPtN`&+2W<9NP2$AwIbZc)P?nF#7f=T;sPY>=@l{cMU68^)`>UkE5G?XuRIG%>%+48J%Y|$LIpXH;zXGy$}CaliISvBiYjTU z-<^wuDg-71RRqKYksvBkBuEsAj37xC6cPD}M3BNL@+q<+p)e7zqo6pCLO#E& zpcoEdG{p;)Azc)a2qOYQf`G6rAOc}&5KRhjGov{630O!V=%7Hy>Qg8LV?p$d)$t}e z{LXm-gd7_wH~N9IvAXoVBR_-Sga~31kbkWHjc*{o6VM`Gwj!(4##=LL`;};|(wF4d*5(@?3Fi{6!Rr?nVg3vlj`Or|RXyROA2OonrZ zW`HZe^IAd^wgjXHoq`r=1v^PziP{P-sd~~W!hKm!Ic3+B=BB&av?g!KNDn#pfQCGW zl~!n}2o4n&r8rszRMOMV18qiAwiKj?orhozdk$-@@KPBZ)?MT}JO*?Jl2u*gCaVFu zd+RSf{fA6``|}I5FN(3^^V&RxvCj|h|L~qeHL^v}O-BG6qkxlc@3h&&`wq^baVM}n zOB@2$IP5xQajY7Mx8sW1%BZcYYHLnyW!07+&K)7+9bMnyBPR_mYq6QhM|I&34m|?P IKTCC)N!Z_o@Bjb+ literal 0 HcmV?d00001 From 6cc3f7ecc38963af2a5927ba80417c3a0898b2f3 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 28 Feb 2020 22:31:47 -0500 Subject: [PATCH 098/189] Add a test for stored properties as well. This test passed with no errors. --- core/tests/regression_tests.rs | 1 + .../tests/swfs/avm2/stored_properties/Test.as | 47 ++++++++++++++++++ .../swfs/avm2/stored_properties/output.txt | 11 ++++ .../swfs/avm2/stored_properties/test.fla | Bin 0 -> 3985 bytes .../swfs/avm2/stored_properties/test.swf | Bin 0 -> 1068 bytes 5 files changed, 59 insertions(+) create mode 100644 core/tests/swfs/avm2/stored_properties/Test.as create mode 100644 core/tests/swfs/avm2/stored_properties/output.txt create mode 100644 core/tests/swfs/avm2/stored_properties/test.fla create mode 100644 core/tests/swfs/avm2/stored_properties/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index bfe60e32566b..436fe0c68b57 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -232,6 +232,7 @@ swf_tests! { (as3_constructor_call, "avm2/constructor_call", 1), (as3_class_methods, "avm2/class_methods", 1), (as3_inheritance, "avm2/inheritance", 1), + (as3_stored_properties, "avm2/stored_properties", 1), (as3_virtual_properties, "avm2/virtual_properties", 1), } diff --git a/core/tests/swfs/avm2/stored_properties/Test.as b/core/tests/swfs/avm2/stored_properties/Test.as new file mode 100644 index 000000000000..39fb61afdab3 --- /dev/null +++ b/core/tests/swfs/avm2/stored_properties/Test.as @@ -0,0 +1,47 @@ +package { + public class Test { + } +} + +class TestWithVars { + public var prop; + public var propDefault = "y.propDefault resolved!"; + public const propConst = "y.propConst resolved!"; +} + +class ExtendedTest extends TestWithVars { + public var prop2; + public var prop2Default = "z.prop2Default resolved!"; + public const prop2Const = "z.prop2Const resolved!"; +} + +var x = {}; + +x.prop = "x.prop resolved!"; +trace(x.prop); + +var y = new TestWithVars(); + +y.prop = "y.prop resolved!"; +trace(y.prop); + +trace(y.propDefault); +y.propDefault = "y.propDefault overwritten!"; +trace(y.propDefault); + +trace(y.propConst); + +var z = new ExtendedTest(); + +z.prop = "z.prop resolved!"; +trace(z.prop); + +z.prop2 = "z.prop2 resolved!"; +trace(z.prop2); + +trace(z.propDefault); +trace(z.prop2Default); +z.propDefault = "TEST FAIL: Default overrides should not affect other instances!"; +trace(y.propDefault); + +trace(z.propConst); \ No newline at end of file diff --git a/core/tests/swfs/avm2/stored_properties/output.txt b/core/tests/swfs/avm2/stored_properties/output.txt new file mode 100644 index 000000000000..d37d1a0a1e72 --- /dev/null +++ b/core/tests/swfs/avm2/stored_properties/output.txt @@ -0,0 +1,11 @@ +x.prop resolved! +y.prop resolved! +y.propDefault resolved! +y.propDefault overwritten! +y.propConst resolved! +z.prop resolved! +z.prop2 resolved! +y.propDefault resolved! +z.prop2Default! +y.propDefault overwritten! +y.propConst resolved! diff --git a/core/tests/swfs/avm2/stored_properties/test.fla b/core/tests/swfs/avm2/stored_properties/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..84376c02ef3abb67f80e2788b6e658e5112c93b8 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/stored_properties/test.swf b/core/tests/swfs/avm2/stored_properties/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..c745134008d2fca2b45639dd31c69d05db3742c0 GIT binary patch literal 1068 zcmV+{1k?LNS5qrc2LJ$goSjtbQsXuhmMq7Lau1LTmw*GIdx1?1beJ|f)9o&S7KSZ9 zpe@r$hL}W|xD9c}cCx_F_C@+8`wHogzK3}NdSqueX1h#hs0j?oYa9SAtG3X#d*b z{WfoV8G_Eb1I(F}$FA$6tL?h&0NTC~>)@7SaQo%U$=*`e-s+|b z6uXWys8%X^+q~rEmU&fqeY#WG*0kNq<iu+p66)hrPDiHyu^)bl`%hrqkt?YJjAAzs2oh z;e%4;piA ztWiuU4pS1LI7V@t;v^+$if1XFqc}^Ic}f>4D^Rjb$qFSZqnJe`mX_GE#A1Zv5?hg2 zoUkNes>Egqn*(rzM$42|DAg$4qI5gzKQ9vUdot2Zg;@ah3q9{HPa)Ma=Y_f2SZL5Aiu9CQYNj@yK$>-6 zQ+}E!06o8085%q3xNLf|u) Date: Sat, 29 Feb 2020 19:59:54 -0500 Subject: [PATCH 099/189] Pass the ABC name and lazy init flag to the AVM2. --- core/src/avm2.rs | 2 ++ core/src/player.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index b3e40443a451..a5b32cc35870 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -83,6 +83,8 @@ impl<'gc> Avm2<'gc> { 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()); diff --git a/core/src/player.rs b/core/src/player.rs index 4e5406092393..93b36bf11fb6 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -873,7 +873,7 @@ impl Player { is_lazy_initialize, abc, } => { - if let Err(e) = avm2.load_abc(abc, context) { + if let Err(e) = avm2.load_abc(abc, &name, is_lazy_initialize, context) { log::warn!("Error loading ABC file: {}", e); } } From f10920adc046c92df26282f79a301b264618eb05 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 29 Feb 2020 20:09:07 -0500 Subject: [PATCH 100/189] Implement `Object.prototype.hasOwnProperty` and resolution of `Namespace::Any`. --- core/src/avm2/function.rs | 4 ++++ core/src/avm2/globals/object.rs | 36 +++++++++++++++++++++++++++++---- core/src/avm2/names.rs | 11 ++++++++++ core/src/avm2/object.rs | 20 ++++++++++++++---- core/src/avm2/script_object.rs | 16 ++++++++++++++- 5 files changed, 78 insertions(+), 9 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 3076a77e9851..667c233075ff 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -502,6 +502,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_method(id) } + fn resolve_any(self, local_name: &str) -> Option { + self.0.read().base.resolve_any(local_name) + } + fn has_own_property(self, name: &QName) -> bool { self.0.read().base.has_own_property(name) } diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 98c1b6b9839c..733df22359c0 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -1,6 +1,8 @@ //! Object builtin and prototype -use crate::avm2::object::Object; +use crate::avm2::function::FunctionObject; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::{Object, TObject}; use crate::avm2::return_value::ReturnValue; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; @@ -17,6 +19,26 @@ pub fn constructor<'gc>( Ok(Value::Undefined.into()) } +/// `Object.prototype.hasOwnProperty` +pub fn has_own_property<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + let this: Result, Error> = this.ok_or_else(|| "No valid this parameter".into()); + let this = this?; + let name: Result<&Value<'gc>, Error> = args.get(0).ok_or_else(|| "No name specified".into()); + let name = name?.as_string()?; + + if let Some(ns) = this.resolve_any(&name) { + let qname = QName::new(ns, &name); + return Ok(this.has_own_property(&qname).into()); + } + + Ok(false.into()) +} + /// Partially construct `Object.prototype`. /// /// `__proto__` and other cross-linked properties of this object will *not* @@ -27,8 +49,14 @@ pub fn constructor<'gc>( /// not allocate an object to store either proto. Instead, you must allocate /// bare objects for both and let this function fill Object for you. pub fn fill_proto<'gc>( - _gc_context: MutationContext<'gc, '_>, - _object_proto: Object<'gc>, - _fn_proto: Object<'gc>, + gc_context: MutationContext<'gc, '_>, + mut object_proto: Object<'gc>, + fn_proto: Object<'gc>, ) { + object_proto.install_method( + gc_context, + QName::new(Namespace::public_namespace(), "hasOwnProperty"), + 0, + FunctionObject::from_builtin(gc_context, has_own_property, fn_proto), + ); } diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index a8b50abc54f6..1e2f7fab7026 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -62,6 +62,13 @@ impl Namespace { pub fn package(package_name: &str) -> Self { Namespace::Package(package_name.to_string()) } + + pub fn is_any(&self) -> bool { + match self { + Self::Any => true, + _ => false, + } + } } /// A `QName`, likely "qualified name", consists of a namespace and name string. @@ -122,6 +129,10 @@ impl QName { pub fn local_name(&self) -> &str { &self.name } + + pub fn namespace(&self) -> &Namespace { + &self.ns + } } /// A `Multiname` consists of a name which could be resolved in one or more diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 06bdf2fb57f5..fd1aca0f5e8c 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1,7 +1,7 @@ //! AVM2 objects. use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, Executable, FunctionObject}; -use crate::avm2::names::{Multiname, QName}; +use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; @@ -191,9 +191,15 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// match. fn resolve_multiname(self, multiname: &Multiname) -> Option { for ns in multiname.namespace_set() { - let qname = QName::new(ns.clone(), multiname.local_name()?); - if self.has_property(&qname) { - return Some(qname); + if ns.is_any() { + let name = multiname.local_name()?; + let ns = self.resolve_any(name); + return ns.map(|ns| QName::new(ns, name)); + } else { + let qname = QName::new(ns.clone(), multiname.local_name()?); + if self.has_property(&qname) { + return Some(qname); + } } } @@ -204,6 +210,12 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy None } + /// Given a local name, find the namespace it resides in, if any. + /// + /// The `Namespace` must not be `Namespace::Any`, as this function exists + /// specifically resolve names in that namespace. + fn resolve_any(self, local_name: &str) -> Option; + /// Indicates whether or not a property exists on an object. fn has_property(self, name: &QName) -> bool { if self.has_own_property(name) { diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 6cdffc665789..b23a1e6da7ed 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -1,7 +1,7 @@ //! Default AVM2 object impl use crate::avm2::function::Executable; -use crate::avm2::names::QName; +use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::property::Property; use crate::avm2::return_value::ReturnValue; @@ -103,6 +103,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_method(id) } + fn resolve_any(self, local_name: &str) -> Option { + self.0.read().resolve_any(local_name) + } + fn has_own_property(self, name: &QName) -> bool { self.0.read().has_own_property(name) } @@ -342,6 +346,16 @@ impl<'gc> ScriptObjectData<'gc> { self.methods.get(id as usize).and_then(|v| *v) } + pub fn resolve_any(&self, local_name: &str) -> Option { + for (key, _value) in self.values.iter() { + if key.local_name() == local_name { + return Some(key.namespace().clone()); + } + } + + None + } + pub fn has_own_property(&self, name: &QName) -> bool { self.values.get(name).is_some() } From 353017576afeff7b20aff34693a0dca3aff32741 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 2 Mar 2020 14:29:31 -0500 Subject: [PATCH 101/189] `ScriptObject` now holds a reference to a class and allows retrieving traits from it. --- core/src/avm2/function.rs | 47 ++++++++------------ core/src/avm2/object.rs | 7 +++ core/src/avm2/script_object.rs | 78 +++++++++++++++++++++++++++++++--- 3 files changed, 96 insertions(+), 36 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 667c233075ff..4059ffbb0c9e 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -14,7 +14,7 @@ use std::fmt; use std::rc::Rc; use swf::avm2::types::{ AbcFile, Class as AbcClass, Index, Instance as AbcInstance, Method as AbcMethod, - MethodBody as AbcMethodBody, + MethodBody as AbcMethodBody, Trait as AbcTrait, }; /// Represents a function defined in Ruffle's code. @@ -247,9 +247,6 @@ pub struct FunctionObjectData<'gc> { /// Executable code exec: Option>, - - /// The class that defined this function object, if any. - class: Option, } impl<'gc> FunctionObject<'gc> { @@ -297,10 +294,6 @@ impl<'gc> FunctionObject<'gc> { }); let mut class_proto = super_proto?.construct(avm, context, &[])?; - for trait_entry in class.instance().traits.iter() { - class_proto.install_trait(avm, context, class.abc(), trait_entry, scope, fn_proto)?; - } - let initializer_index = class.instance().init_method.clone(); let initializer: Result = Avm2MethodEntry::from_method_index(class.abc(), initializer_index.clone()).ok_or_else( @@ -316,9 +309,8 @@ impl<'gc> FunctionObject<'gc> { let mut constr: Object<'gc> = FunctionObject(GcCell::allocate( context.gc_context, FunctionObjectData { - base: ScriptObjectData::base_new(Some(fn_proto)), + base: ScriptObjectData::base_new(Some(fn_proto), Some(class.clone()), true), exec: Some(Avm2Function::from_method(initializer?, scope).into()), - class: Some(class.clone()), }, )) .into(); @@ -348,15 +340,12 @@ impl<'gc> FunctionObject<'gc> { ) .into() }); - let class_constr = FunctionObject(GcCell::allocate( + let class_constr = FunctionObject::from_abc_method( context.gc_context, - FunctionObjectData { - base: ScriptObjectData::base_new(Some(fn_proto)), - exec: Some(Avm2Function::from_method(class_initializer?, scope).into()), - class: Some(class.clone()), - }, - )) - .into(); + class_initializer?, + scope, + fn_proto, + ); Ok((constr, class_constr)) } @@ -373,9 +362,8 @@ impl<'gc> FunctionObject<'gc> { FunctionObject(GcCell::allocate( mc, FunctionObjectData { - base: ScriptObjectData::base_new(Some(fn_proto)), + base: ScriptObjectData::base_new(Some(fn_proto), None, true), exec, - class: None, }, )) .into() @@ -390,9 +378,8 @@ impl<'gc> FunctionObject<'gc> { FunctionObject(GcCell::allocate( mc, FunctionObjectData { - base: ScriptObjectData::base_new(Some(fn_proto)), + base: ScriptObjectData::base_new(Some(fn_proto), None, true), exec: Some(nf.into()), - class: None, }, )) .into() @@ -408,9 +395,8 @@ impl<'gc> FunctionObject<'gc> { let mut base: Object<'gc> = FunctionObject(GcCell::allocate( mc, FunctionObjectData { - base: ScriptObjectData::base_new(Some(fn_proto)), + base: ScriptObjectData::base_new(Some(fn_proto), None, true), exec: Some(constr.into()), - class: None, }, )) .into(); @@ -502,6 +488,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_method(id) } + fn get_trait(self, name: &QName) -> Result, Error> { + self.0.read().base.get_trait(name) + } + fn resolve_any(self, local_name: &str) -> Option { self.0.read().base.resolve_any(local_name) } @@ -551,16 +541,13 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], ) -> Result, Error> { + let class = self.0.read().base.class().cloned(); let this: Object<'gc> = Object::FunctionObject(*self); - let base = ScriptObjectData::base_new(Some(this)); + let base = ScriptObjectData::base_new(Some(this), class, false); Ok(FunctionObject(GcCell::allocate( context.gc_context, - FunctionObjectData { - base, - exec: None, - class: None, - }, + FunctionObjectData { base, exec: None }, )) .into()) } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index fd1aca0f5e8c..794789843b14 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -187,6 +187,13 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Retrieve a method by it's index. fn get_method(self, id: u32) -> Option>; + /// Retrieves a trait entry by name. + /// + /// This function returns `None` if no such trait exists, or the object + /// does not have traits. It returns `Err` if *any* trait in the object is + /// malformed in some way. + fn get_trait(self, name: &QName) -> Result, Error>; + /// Resolve a multiname into a single QName, if any of the namespaces /// match. fn resolve_multiname(self, multiname: &Multiname) -> Option { diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index b23a1e6da7ed..fa228d3cba9d 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -1,6 +1,6 @@ //! Default AVM2 object impl -use crate::avm2::function::Executable; +use crate::avm2::function::{Avm2ClassEntry, Executable}; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::property::Property; @@ -12,6 +12,7 @@ use crate::context::UpdateContext; use gc_arena::{Collect, GcCell, MutationContext}; use std::collections::HashMap; use std::fmt::Debug; +use swf::avm2::types::Trait as AbcTrait; /// Default implementation of `avm2::Object`. #[derive(Clone, Collect, Debug, Copy)] @@ -30,8 +31,16 @@ pub struct ScriptObjectData<'gc> { /// Methods stored on this object. methods: Vec>>, - /// Implicit prototype (or declared base class) of this script object. + /// Implicit prototype of this script object. proto: Option>, + + /// Declared base class of this script object. + class: Option, + + /// Whether or not to use declared instance or class properties. + /// + /// Only effective if `class` is not `None`. + is_instance: bool, } impl<'gc> TObject<'gc> for ScriptObject<'gc> { @@ -103,6 +112,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_method(id) } + fn get_trait(self, name: &QName) -> Result, Error> { + self.0.read().get_trait(name) + } + fn resolve_any(self, local_name: &str) -> Option { self.0.read().resolve_any(local_name) } @@ -203,27 +216,76 @@ impl<'gc> ScriptObject<'gc> { /// This is *not* the same thing as an object literal, which actually does /// have a base class: `Object`. pub fn bare_object(mc: MutationContext<'gc, '_>) -> Object<'gc> { - ScriptObject(GcCell::allocate(mc, ScriptObjectData::base_new(None))).into() + ScriptObject(GcCell::allocate( + mc, + ScriptObjectData::base_new(None, None, true), + )) + .into() } - /// Construct an object with a base class. + /// Construct an object with a prototype. pub fn object(mc: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> { ScriptObject(GcCell::allocate( mc, - ScriptObjectData::base_new(Some(proto)), + ScriptObjectData::base_new(Some(proto), None, true), + )) + .into() + } + + /// Construct a class instance. + pub fn instance( + mc: MutationContext<'gc, '_>, + proto: Object<'gc>, + class: Avm2ClassEntry, + ) -> Object<'gc> { + ScriptObject(GcCell::allocate( + mc, + ScriptObjectData::base_new(Some(proto), Some(class), true), )) .into() } } impl<'gc> ScriptObjectData<'gc> { - pub fn base_new(proto: Option>) -> Self { + pub fn base_new( + proto: Option>, + class: Option, + is_instance: bool, + ) -> Self { ScriptObjectData { values: HashMap::new(), slots: Vec::new(), methods: Vec::new(), proto, + class, + is_instance, + } + } + + pub fn get_trait(&self, name: &QName) -> Result, Error> { + if let Some(class) = &self.class { + if self.is_instance { + for trait_entry in class.instance().traits.iter() { + let trait_name = + QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; + + if name == &trait_name { + return Ok(Some(trait_entry.clone())); + } + } + } else { + for trait_entry in class.class().traits.iter() { + let trait_name = + QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; + + if name == &trait_name { + return Ok(Some(trait_entry.clone())); + } + } + } } + + Ok(None) } pub fn get_property_local( @@ -378,6 +440,10 @@ impl<'gc> ScriptObjectData<'gc> { self.proto } + pub fn class(&self) -> Option<&Avm2ClassEntry> { + self.class.as_ref() + } + /// Install a method into the object. pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) { if disp_id > 0 { From 2f95a7a81b2b6c76661437351ecccb63fc6017ad Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 3 Mar 2020 19:39:49 -0500 Subject: [PATCH 102/189] 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. --- core/src/avm2.rs | 153 ++++++++--------- core/src/avm2/function.rs | 70 ++++++-- core/src/avm2/globals/object.rs | 2 +- core/src/avm2/object.rs | 167 ++++++++++++++---- core/src/avm2/property.rs | 9 +- core/src/avm2/scope.rs | 35 ++-- core/src/avm2/script_object.rs | 290 ++++++++++++++++++++++++++------ 7 files changed, 523 insertions(+), 203 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index a5b32cc35870..08c94fa48a37 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -77,6 +77,11 @@ impl<'gc> Avm2<'gc> { } } + /// 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. @@ -102,13 +107,12 @@ impl<'gc> Avm2<'gc> { let scope = Scope::push_scope(None, self.globals(), context.gc_context); for trait_entry in entrypoint.script().traits.iter() { - self.globals().install_trait( + self.globals().install_foreign_trait( self, context, - entrypoint.abc(), + abc_file.clone(), trait_entry, Some(scope), - self.system_prototypes.function, )?; } @@ -635,13 +639,13 @@ impl<'gc> Avm2<'gc> { let multiname = self.pool_multiname(index)?; let receiver = self.pop().as_object()?; let name: Result = receiver - .resolve_multiname(&multiname) + .resolve_multiname(&multiname)? .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); let name = name?; - let base_proto = receiver.get_base_proto(&name); + let base_proto = receiver.get_base_proto(&name)?; let function = base_proto .unwrap_or(receiver) - .get_property_local(receiver, &name, self, context)? + .get_property(receiver, &name, self, context)? .resolve(self, context)? .as_object()?; @@ -660,9 +664,9 @@ impl<'gc> Avm2<'gc> { ) -> Result<(), Error> { let args = self.pop_args(arg_count); let multiname = self.pool_multiname(index)?; - let receiver = self.pop().as_object()?; + let mut receiver = self.pop().as_object()?; let name: Result = receiver - .resolve_multiname(&multiname) + .resolve_multiname(&multiname)? .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); let function = receiver .get_property(receiver, &name?, self, context)? @@ -684,13 +688,13 @@ impl<'gc> Avm2<'gc> { let multiname = self.pool_multiname(index)?; let receiver = self.pop().as_object()?; let name: Result = receiver - .resolve_multiname(&multiname) + .resolve_multiname(&multiname)? .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); let name = name?; - let base_proto = receiver.get_base_proto(&name); + let base_proto = receiver.get_base_proto(&name)?; let function = base_proto .unwrap_or(receiver) - .get_property_local(receiver, &name, self, context)? + .get_property(receiver, &name, self, context)? .resolve(self, context)? .as_object()?; @@ -735,7 +739,7 @@ impl<'gc> Avm2<'gc> { let multiname = self.pool_multiname(index)?; let receiver = self.pop().as_object()?; let name: Result = receiver - .resolve_multiname(&multiname) + .resolve_multiname(&multiname)? .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); let base_proto: Result, Error> = self .current_stack_frame() @@ -749,8 +753,9 @@ impl<'gc> Avm2<'gc> { .into() }); let base_proto = base_proto?; + let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround - let function = base_proto + let function = base .get_property(receiver, &name?, self, context)? .resolve(self, context)? .as_object()?; @@ -772,7 +777,7 @@ impl<'gc> Avm2<'gc> { let multiname = self.pool_multiname(index)?; let receiver = self.pop().as_object()?; let name: Result = receiver - .resolve_multiname(&multiname) + .resolve_multiname(&multiname)? .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); let base_proto: Result, Error> = self .current_stack_frame() @@ -786,8 +791,9 @@ impl<'gc> Avm2<'gc> { .into() }); let base_proto = base_proto?; + let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround - let function = base_proto + let function = base .get_property(receiver, &name?, self, context)? .resolve(self, context)? .as_object()?; @@ -815,9 +821,9 @@ impl<'gc> Avm2<'gc> { index: Index, ) -> Result<(), Error> { let multiname = self.pool_multiname(index)?; - let object = self.pop().as_object()?; + let mut object = self.pop().as_object()?; - let name: Result = object.resolve_multiname(&multiname).ok_or_else(|| { + let name: Result = object.resolve_multiname(&multiname)?.ok_or_else(|| { format!("Could not resolve property {:?}", multiname.local_name()).into() }); @@ -836,9 +842,9 @@ impl<'gc> Avm2<'gc> { ) -> Result<(), Error> { let value = self.pop(); let multiname = self.pool_multiname(index)?; - let object = self.pop().as_object()?; + let mut object = self.pop().as_object()?; - if let Some(name) = object.resolve_multiname(&multiname) { + if let Some(name) = object.resolve_multiname(&multiname)? { object.set_property(object, &name, value, self, context) } else { //TODO: Non-dynamic objects should fail @@ -858,9 +864,9 @@ impl<'gc> Avm2<'gc> { ) -> Result<(), Error> { let value = self.pop(); let multiname = self.pool_multiname(index)?; - let object = self.pop().as_object()?; + let mut object = self.pop().as_object()?; - if let Some(name) = object.resolve_multiname(&multiname) { + if let Some(name) = object.resolve_multiname(&multiname)? { object.init_property(object, &name, value, self, context) } else { //TODO: Non-dynamic objects should fail @@ -881,7 +887,7 @@ impl<'gc> Avm2<'gc> { let multiname = self.pool_multiname(index)?; let object = self.pop().as_object()?; - if let Some(name) = object.resolve_multiname(&multiname) { + if let Some(name) = object.resolve_multiname(&multiname)? { self.push(object.delete_property(context.gc_context, &name)) } else { self.push(false) @@ -905,17 +911,17 @@ impl<'gc> Avm2<'gc> { .and_then(|p| p.proto()) .ok_or_else(|| "Attempted to get property on non-existent super object".into()); let base_proto = base_proto?; + let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround + + let name: Result = base.resolve_multiname(&multiname)?.ok_or_else(|| { + format!( + "Could not resolve {:?} as super property", + multiname.local_name() + ) + .into() + }); - let name: Result = - base_proto.resolve_multiname(&multiname).ok_or_else(|| { - format!( - "Could not resolve {:?} as super property", - multiname.local_name() - ) - .into() - }); - - let value = base_proto + let value = base .get_property(object, &name?, self, context)? .resolve(self, context)?; @@ -940,17 +946,17 @@ impl<'gc> Avm2<'gc> { .and_then(|p| p.proto()) .ok_or_else(|| "Attempted to get property on non-existent super object".into()); let base_proto = base_proto?; + let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround + + let name: Result = base.resolve_multiname(&multiname)?.ok_or_else(|| { + format!( + "Could not resolve {:?} as super property", + multiname.local_name() + ) + .into() + }); - let name: Result = - base_proto.resolve_multiname(&multiname).ok_or_else(|| { - format!( - "Could not resolve {:?} as super property", - multiname.local_name() - ) - .into() - }); - - base_proto.set_property(object, &name?, value, self, context)?; + base.set_property(object, &name?, value, self, context)?; Ok(()) } @@ -1038,12 +1044,11 @@ impl<'gc> Avm2<'gc> { ) -> Result<(), Error> { let multiname = self.pool_multiname(index)?; avm_debug!("Resolving {:?}", multiname); - let result = self - .current_stack_frame() - .unwrap() - .read() - .scope() - .and_then(|scope| scope.read().find(&multiname, self, context)); + 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)); @@ -1057,12 +1062,12 @@ impl<'gc> Avm2<'gc> { ) -> Result<(), Error> { let multiname = self.pool_multiname(index)?; avm_debug!("Resolving {:?}", multiname); - let found: Result, Error> = self - .current_stack_frame() - .unwrap() - .read() - .scope() - .and_then(|scope| scope.read().find(&multiname, self, context)) + let found: Result, 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(); @@ -1078,14 +1083,16 @@ impl<'gc> Avm2<'gc> { ) -> Result<(), Error> { let multiname = self.pool_multiname_static(index)?; avm_debug!("Resolving {:?}", multiname); - let found: Result, Error>, Error> = self - .current_stack_frame() - .unwrap() - .read() - .scope() - .and_then(|scope| scope.read().resolve(&multiname, self, context)) + let found: Result, 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()); - let result: Value<'gc> = found??.resolve(self, context)?; + let result: Value<'gc> = found?.resolve(self, context)?; self.push(result); @@ -1136,7 +1143,7 @@ impl<'gc> Avm2<'gc> { arg_count: u32, ) -> Result<(), Error> { let args = self.pop_args(arg_count); - let ctor = self.pop().as_object()?; + let mut ctor = self.pop().as_object()?; let proto = ctor .get_property( @@ -1165,13 +1172,13 @@ impl<'gc> Avm2<'gc> { ) -> Result<(), Error> { let args = self.pop_args(arg_count); let multiname = self.pool_multiname(index)?; - let source = self.pop().as_object()?; + let mut source = self.pop().as_object()?; let ctor_name: Result = - source.resolve_multiname(&multiname).ok_or_else(|| { + source.resolve_multiname(&multiname)?.ok_or_else(|| { format!("Could not resolve property {:?}", multiname.local_name()).into() }); - let ctor = source + let mut ctor = source .get_property(source, &ctor_name?, self, context)? .resolve(self, context)? .as_object()?; @@ -1213,10 +1220,10 @@ impl<'gc> Avm2<'gc> { .to_string() .into() }); - let base_proto = base_proto?; + let mut base_proto = base_proto?; let function = base_proto - .get_property_local(receiver, &name, self, context)? + .get_property(receiver, &name, self, context)? .resolve(self, context)? .as_object()?; @@ -1238,7 +1245,7 @@ impl<'gc> Avm2<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, num_args: u32, ) -> Result<(), Error> { - let object = ScriptObject::object(context.gc_context, self.system_prototypes.object); + let mut object = ScriptObject::object(context.gc_context, self.system_prototypes.object); for _ in 0..num_args { let value = self.pop(); @@ -1287,14 +1294,8 @@ impl<'gc> Avm2<'gc> { let class_entry = self.table_class(index)?; let scope = self.current_stack_frame().unwrap().read().scope(); - let (new_class, class_init) = FunctionObject::from_abc_class( - self, - context, - class_entry, - base_class, - scope, - self.system_prototypes.function, - )?; + let (new_class, class_init) = + FunctionObject::from_abc_class(self, context, class_entry, base_class, scope)?; class_init .call(Some(new_class), &[], self, context, None)? diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 4059ffbb0c9e..6963a82c6364 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -5,7 +5,7 @@ use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; -use crate::avm2::script_object::ScriptObjectData; +use crate::avm2::script_object::{ScriptObjectClass, ScriptObjectData}; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; @@ -259,9 +259,8 @@ impl<'gc> FunctionObject<'gc> { avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, class: Avm2ClassEntry, - base_class: Object<'gc>, + mut base_class: Object<'gc>, scope: Option>>, - fn_proto: Object<'gc>, ) -> Result<(Object<'gc>, Object<'gc>), Error> { let super_proto: Result, Error> = base_class .get_property( @@ -292,7 +291,8 @@ impl<'gc> FunctionObject<'gc> { .into() } }); - let mut class_proto = super_proto?.construct(avm, context, &[])?; + let mut class_proto = super_proto?.derive(avm, context, class.clone(), scope)?; + let fn_proto = avm.prototypes().function; let initializer_index = class.instance().init_method.clone(); let initializer: Result = @@ -309,16 +309,15 @@ impl<'gc> FunctionObject<'gc> { let mut constr: Object<'gc> = FunctionObject(GcCell::allocate( context.gc_context, FunctionObjectData { - base: ScriptObjectData::base_new(Some(fn_proto), Some(class.clone()), true), + base: ScriptObjectData::base_new( + Some(fn_proto), + ScriptObjectClass::ClassConstructor(class.clone(), scope), + ), exec: Some(Avm2Function::from_method(initializer?, scope).into()), }, )) .into(); - for trait_entry in class.class().traits.iter() { - constr.install_trait(avm, context, class.abc(), trait_entry, scope, fn_proto)?; - } - constr.install_dynamic_property( context.gc_context, QName::new(Namespace::public_namespace(), "prototype"), @@ -362,7 +361,7 @@ impl<'gc> FunctionObject<'gc> { FunctionObject(GcCell::allocate( mc, FunctionObjectData { - base: ScriptObjectData::base_new(Some(fn_proto), None, true), + base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass), exec, }, )) @@ -378,7 +377,7 @@ impl<'gc> FunctionObject<'gc> { FunctionObject(GcCell::allocate( mc, FunctionObjectData { - base: ScriptObjectData::base_new(Some(fn_proto), None, true), + base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass), exec: Some(nf.into()), }, )) @@ -395,7 +394,7 @@ impl<'gc> FunctionObject<'gc> { let mut base: Object<'gc> = FunctionObject(GcCell::allocate( mc, FunctionObjectData { - base: ScriptObjectData::base_new(Some(fn_proto), None, true), + base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass), exec: Some(constr.into()), }, )) @@ -488,18 +487,38 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_method(id) } - fn get_trait(self, name: &QName) -> Result, Error> { + fn get_trait(self, name: &QName) -> Result, Error> { self.0.read().base.get_trait(name) } + fn get_scope(self) -> Option>> { + self.0.read().base.get_scope() + } + + fn get_abc(self) -> Option> { + self.0.read().base.get_abc() + } + fn resolve_any(self, local_name: &str) -> Option { self.0.read().base.resolve_any(local_name) } - fn has_own_property(self, name: &QName) -> bool { + fn has_own_property(self, name: &QName) -> Result { self.0.read().base.has_own_property(name) } + fn has_trait(self, name: &QName) -> Result { + self.0.read().base.has_trait(name) + } + + fn has_own_trait(self, name: &QName) -> Result { + self.0.read().base.has_own_trait(name) + } + + fn has_instantiated_property(self, name: &QName) -> bool { + self.0.read().base.has_instantiated_property(name) + } + fn has_own_virtual_getter(self, name: &QName) -> bool { self.0.read().base.has_own_virtual_getter(name) } @@ -541,9 +560,28 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], ) -> Result, Error> { - let class = self.0.read().base.class().cloned(); let this: Object<'gc> = Object::FunctionObject(*self); - let base = ScriptObjectData::base_new(Some(this), class, false); + let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass); + + Ok(FunctionObject(GcCell::allocate( + context.gc_context, + FunctionObjectData { base, exec: None }, + )) + .into()) + } + + fn derive( + &self, + _avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + class: Avm2ClassEntry, + scope: Option>>, + ) -> Result, Error> { + let this: Object<'gc> = Object::FunctionObject(*self); + let base = ScriptObjectData::base_new( + Some(this), + ScriptObjectClass::InstancePrototype(class, scope), + ); Ok(FunctionObject(GcCell::allocate( context.gc_context, diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 733df22359c0..cdf1e04da1c2 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -33,7 +33,7 @@ pub fn has_own_property<'gc>( if let Some(ns) = this.resolve_any(&name) { let qname = QName::new(ns, &name); - return Ok(this.has_own_property(&qname).into()); + return Ok(this.has_own_property(&qname)?.into()); } Ok(false.into()) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 794789843b14..b3b887c3fc79 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -37,36 +37,45 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Retrieve a property by it's QName. fn get_property( - self, + &mut self, reciever: Object<'gc>, name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { + if !self.has_instantiated_property(name) { + for abc_trait in self.get_trait(name)? { + self.install_trait(avm, context, &abc_trait)?; + } + } + let has_no_getter = self.has_own_virtual_setter(name) && !self.has_own_virtual_getter(name); - if self.has_own_property(name) && !has_no_getter { + if self.has_own_property(name)? && !has_no_getter { return self.get_property_local(reciever, name, avm, context); } - if let Some(proto) = self.proto() { + if let Some(mut proto) = self.proto() { return proto.get_property(reciever, name, avm, context); } Ok(Value::Undefined.into()) } - /// Retrieve the base prototype that a particular QName is defined in. - fn get_base_proto(self, name: &QName) -> Option> { - if self.has_own_property(name) { - return Some(self.into()); + /// Retrieve the base prototype that a particular QName trait is defined in. + /// + /// This function returns `None` for non-trait properties, such as actually + /// defined prototype methods for ES3-style classes. + fn get_base_proto(self, name: &QName) -> Result>, Error> { + if self.has_own_trait(name)? { + return Ok(Some(self.into())); } if let Some(proto) = self.proto() { return proto.get_base_proto(name); } - None + Ok(None) } /// Set a property on this specific object. @@ -84,13 +93,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Set a property by it's QName. fn set_property( - self, + &mut self, reciever: Object<'gc>, name: &QName, value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error> { + if !self.has_instantiated_property(name) { + for abc_trait in self.get_trait(name)? { + self.install_trait(avm, context, &abc_trait)?; + } + } + if self.has_own_virtual_setter(name) { self.set_property_local(reciever, name, value, avm, context)? .resolve(avm, context)?; @@ -99,7 +114,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } let mut proto = self.proto(); - while let Some(my_proto) = proto { + while let Some(mut my_proto) = proto { //NOTE: This only works because we validate ahead-of-time that //we're calling a virtual setter. If you call `set_property` on //a non-virtual you will actually alter the prototype. @@ -132,13 +147,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Init a property by it's QName. fn init_property( - self, + &mut self, reciever: Object<'gc>, name: &QName, value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error> { + if !self.has_instantiated_property(name) { + for abc_trait in self.get_trait(name)? { + self.install_trait(avm, context, &abc_trait)?; + } + } + if self.has_own_virtual_setter(name) { self.init_property_local(reciever, name, value, avm, context)? .resolve(avm, context)?; @@ -147,7 +168,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } let mut proto = self.proto(); - while let Some(my_proto) = proto { + while let Some(mut my_proto) = proto { //NOTE: This only works because we validate ahead-of-time that //we're calling a virtual setter. If you call `set_property` on //a non-virtual you will actually alter the prototype. @@ -192,29 +213,50 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// This function returns `None` if no such trait exists, or the object /// does not have traits. It returns `Err` if *any* trait in the object is /// malformed in some way. - fn get_trait(self, name: &QName) -> Result, Error>; + fn get_trait(self, name: &QName) -> Result, Error>; + + /// Retrieves the scope chain of the object at time of it's creation. + /// + /// The scope chain is used to determine the starting scope stack when an + /// object is called, as well as any class methods on the object. + /// Non-method functions and prototype functions (ES3 methods) do not use + /// this scope chain. + fn get_scope(self) -> Option>>; + + /// Retrieves the ABC file that this object, or it's class, was defined in. + /// + /// Objects that were not defined in an ABC file or created from a class + /// defined in an ABC file will return `None`. This can happen for things + /// such as object or array literals. If this object does not have an ABC + /// file, then it must also not have traits. + fn get_abc(self) -> Option>; /// Resolve a multiname into a single QName, if any of the namespaces /// match. - fn resolve_multiname(self, multiname: &Multiname) -> Option { + fn resolve_multiname(self, multiname: &Multiname) -> Result, Error> { for ns in multiname.namespace_set() { if ns.is_any() { - let name = multiname.local_name()?; - let ns = self.resolve_any(name); - return ns.map(|ns| QName::new(ns, name)); - } else { - let qname = QName::new(ns.clone(), multiname.local_name()?); - if self.has_property(&qname) { - return Some(qname); + if let Some(name) = multiname.local_name() { + let ns = self.resolve_any(name); + return Ok(ns.map(|ns| QName::new(ns, name))); + } else { + return Ok(None); } + } else if let Some(name) = multiname.local_name() { + let qname = QName::new(ns.clone(), name); + if self.has_property(&qname)? { + return Ok(Some(qname)); + } + } else { + return Ok(None); } } if let Some(proto) = self.proto() { - return proto.resolve_multiname(multiname); + return Ok(proto.resolve_multiname(multiname)?); } - None + Ok(None) } /// Given a local name, find the namespace it resides in, if any. @@ -224,19 +266,34 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn resolve_any(self, local_name: &str) -> Option; /// Indicates whether or not a property exists on an object. - fn has_property(self, name: &QName) -> bool { - if self.has_own_property(name) { - true + fn has_property(self, name: &QName) -> Result { + if self.has_own_property(name)? { + Ok(true) } else if let Some(proto) = self.proto() { - proto.has_own_property(name) + Ok(proto.has_own_property(name)?) } else { - false + Ok(false) } } - /// Indicates whether or not a property exists on an object and is not part - /// of the prototype chain. - fn has_own_property(self, name: &QName) -> bool; + /// Indicates whether or not a property or trait exists on an object and is + /// not part of the prototype chain. + fn has_own_property(self, name: &QName) -> Result; + + /// Returns true if an object has one or more traits of a given name. + fn has_trait(self, name: &QName) -> Result; + + /// Returns true if an object is part of a class that defines a trait of a + /// given name on itself (as opposed to merely inheriting a superclass + /// trait.) + fn has_own_trait(self, name: &QName) -> Result; + + /// Indicates whether or not a property or *instantiated* trait exists on + /// an object and is not part of the prototype chain. + /// + /// Unlike `has_own_property`, this will not yield `true` for traits this + /// object can have but has not yet instantiated. + fn has_instantiated_property(self, name: &QName) -> bool; /// Check if a particular object contains a virtual getter by the given /// name. @@ -316,16 +373,39 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy value: Value<'gc>, ); - /// Install a trait from an ABC file on an object. + /// Install a trait from the current object. + /// + /// This function should only be called once, as reinstalling a trait may + /// also unset already set properties. It may either be called immediately + /// when the object is instantiated or lazily; this behavior is ostensibly + /// controlled by the `lazy_init` flag provided to `load_abc`, but in + /// practice every version of Flash and Animate uses lazy trait + /// installation. fn install_trait( + &mut self, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + trait_entry: &AbcTrait, + ) -> Result<(), Error> { + let scope = self.get_scope(); + let abc: Result, Error> = self.get_abc().ok_or_else(|| { + "Object with traits must have an ABC file!" + .to_string() + .into() + }); + self.install_foreign_trait(avm, context, abc?, trait_entry, scope) + } + + /// Install a trait from anywyere. + fn install_foreign_trait( &mut self, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, abc: Rc, trait_entry: &AbcTrait, scope: Option>>, - fn_proto: Object<'gc>, ) -> Result<(), Error> { + let fn_proto = avm.prototypes().function; let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?; avm_debug!( "Installing trait {:?} of kind {:?}", @@ -387,7 +467,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy type_entry.clone(), super_class?, scope, - fn_proto, )?; let class_name = QName::from_abc_multiname( &type_entry.abc(), @@ -439,6 +518,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// After construction, the constructor function is `call`ed with the new /// object as `this` to initialize the object. /// + /// `construct`ed objects should instantiate instance traits of the class + /// that this prototype represents. + /// /// The arguments passed to the constructor are provided here; however, all /// object construction should happen in `call`, not `new`. `new` exists /// purely so that host objects can be constructed by the VM. @@ -449,6 +531,23 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy args: &[Value<'gc>], ) -> Result, Error>; + /// Construct a host object prototype of some kind and return it. + /// + /// This is called specifically to construct prototypes. The primary + /// difference is that a new class and scope closure are defined here. + /// Objects constructed from the new prototype should use that new class + /// and scope closure when instantiating non-prototype traits. + /// + /// Unlike `construct`, `derive`d objects should *not* instantiate instance + /// traits. + fn derive( + &self, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + class: Avm2ClassEntry, + scope: Option>>, + ) -> Result, Error>; + /// Get a raw pointer value for this object. fn as_ptr(&self) -> *const ObjectPtr; diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 16186fa90338..912b9830a55c 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -140,10 +140,11 @@ impl<'gc> Property<'gc> { avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, + base_proto: Option>, ) -> Result, Error> { match self { Property::Virtual { get: Some(get), .. } => { - get.exec(Some(this), &[], avm, context, this.proto()) + get.exec(Some(this), &[], avm, context, base_proto) } Property::Virtual { get: None, .. } => Ok(Value::Undefined.into()), Property::Stored { value, .. } => Ok(value.to_owned().into()), @@ -163,6 +164,7 @@ impl<'gc> Property<'gc> { avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, + base_proto: Option>, new_value: impl Into>, ) -> Result, Error> { match self { @@ -173,7 +175,7 @@ impl<'gc> Property<'gc> { &[new_value.into()], avm, context, - this.proto(), + base_proto, ); } @@ -209,6 +211,7 @@ impl<'gc> Property<'gc> { avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, + base_proto: Option>, new_value: impl Into>, ) -> Result, Error> { match self { @@ -219,7 +222,7 @@ impl<'gc> Property<'gc> { &[new_value.into()], avm, context, - this.proto(), + base_proto, ); } diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index e4ee311473e5..e9c751ee9c48 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -77,7 +77,6 @@ impl<'gc> Scope<'gc> { } /// Returns a reference to the current local scope object for mutation. - #[allow(dead_code)] pub fn locals_mut(&mut self) -> &mut Object<'gc> { &mut self.values } @@ -102,10 +101,10 @@ impl<'gc> Scope<'gc> { name: &Multiname, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Option> { - if let Some(qname) = self.locals().resolve_multiname(name) { - if self.locals().has_property(&qname) { - return Some(*self.locals()); + ) -> Result>, Error> { + if let Some(qname) = self.locals().resolve_multiname(name)? { + if self.locals().has_property(&qname)? { + return Ok(Some(*self.locals())); } } @@ -113,7 +112,7 @@ impl<'gc> Scope<'gc> { return scope.find(name, avm, context); } - None + Ok(None) } /// Resolve a particular value in the scope chain. @@ -121,25 +120,27 @@ impl<'gc> Scope<'gc> { /// This function yields `None` if no such scope exists to provide the /// property's value. pub fn resolve( - &self, + &mut self, name: &Multiname, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Option, Error>> { - if let Some(qname) = self.locals().resolve_multiname(name) { - if self.locals().has_property(&qname) { - return Some( - self.locals() - .get_property(self.values, &qname, avm, context), - ); + ) -> Result>, Error> { + if let Some(qname) = self.locals().resolve_multiname(name)? { + if self.locals().has_property(&qname)? { + return Ok(Some(self.values.get_property( + self.values, + &qname, + avm, + context, + )?)); } } - if let Some(scope) = self.parent() { - return scope.resolve(name, avm, context); + if let Some(parent) = self.parent { + return parent.write(context.gc_context).resolve(name, avm, context); } //TODO: Should undefined variables halt execution? - None + Ok(None) } } diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index fa228d3cba9d..9005b3c644b0 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -5,6 +5,7 @@ use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::property::Property; use crate::avm2::return_value::ReturnValue; +use crate::avm2::scope::Scope; use crate::avm2::slot::Slot; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; @@ -12,13 +13,40 @@ use crate::context::UpdateContext; use gc_arena::{Collect, GcCell, MutationContext}; use std::collections::HashMap; use std::fmt::Debug; -use swf::avm2::types::Trait as AbcTrait; +use std::rc::Rc; +use swf::avm2::types::{AbcFile, Trait as AbcTrait, TraitKind as AbcTraitKind}; /// Default implementation of `avm2::Object`. #[derive(Clone, Collect, Debug, Copy)] #[collect(no_drop)] pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>); +/// Information necessary for a script object to have a class attached to it. +/// +/// Classes can be attached to a `ScriptObject` such that the class's traits +/// are instantiated on-demand. Either class or instance traits can be +/// instantiated. +/// +/// Trait instantiation obeys prototyping rules: prototypes provide their +/// instances with classes to pull traits from. +#[derive(Clone, Collect, Debug)] +#[collect(no_drop)] +pub enum ScriptObjectClass<'gc> { + /// Instantiate instance traits, for prototypes. + InstancePrototype(Avm2ClassEntry, Option>>), + + /// Instantiate class traits, for class constructors. + ClassConstructor(Avm2ClassEntry, Option>>), + + /// Do not instantiate any class or instance traits. + NoClass, +} + +/// Base data common to all `TObject` implementations. +/// +/// Host implementations of `TObject` should embed `ScriptObjectData` and +/// forward any trait method implementations it does not overwrite to this +/// struct. #[derive(Clone, Collect, Debug)] #[collect(no_drop)] pub struct ScriptObjectData<'gc> { @@ -34,13 +62,8 @@ pub struct ScriptObjectData<'gc> { /// Implicit prototype of this script object. proto: Option>, - /// Declared base class of this script object. - class: Option, - - /// Whether or not to use declared instance or class properties. - /// - /// Only effective if `class` is not `None`. - is_instance: bool, + /// The class that this script object represents. + class: ScriptObjectClass<'gc>, } impl<'gc> TObject<'gc> for ScriptObject<'gc> { @@ -112,18 +135,38 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_method(id) } - fn get_trait(self, name: &QName) -> Result, Error> { + fn get_trait(self, name: &QName) -> Result, Error> { self.0.read().get_trait(name) } + fn get_scope(self) -> Option>> { + self.0.read().get_scope() + } + + fn get_abc(self) -> Option> { + self.0.read().get_abc() + } + fn resolve_any(self, local_name: &str) -> Option { self.0.read().resolve_any(local_name) } - fn has_own_property(self, name: &QName) -> bool { + fn has_own_property(self, name: &QName) -> Result { self.0.read().has_own_property(name) } + fn has_trait(self, name: &QName) -> Result { + self.0.read().has_trait(name) + } + + fn has_own_trait(self, name: &QName) -> Result { + self.0.read().has_own_trait(name) + } + + fn has_instantiated_property(self, name: &QName) -> bool { + self.0.read().has_instantiated_property(name) + } + fn has_own_virtual_getter(self, name: &QName) -> bool { self.0.read().has_own_virtual_getter(name) } @@ -150,6 +193,22 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { Ok(ScriptObject::object(context.gc_context, this)) } + fn derive( + &self, + _avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + class: Avm2ClassEntry, + scope: Option>>, + ) -> Result, Error> { + let this: Object<'gc> = Object::ScriptObject(*self); + Ok(ScriptObject::prototype( + context.gc_context, + this, + class, + scope, + )) + } + fn install_method( &mut self, mc: MutationContext<'gc, '_>, @@ -218,7 +277,7 @@ impl<'gc> ScriptObject<'gc> { pub fn bare_object(mc: MutationContext<'gc, '_>) -> Object<'gc> { ScriptObject(GcCell::allocate( mc, - ScriptObjectData::base_new(None, None, true), + ScriptObjectData::base_new(None, ScriptObjectClass::NoClass), )) .into() } @@ -227,67 +286,84 @@ impl<'gc> ScriptObject<'gc> { pub fn object(mc: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> { ScriptObject(GcCell::allocate( mc, - ScriptObjectData::base_new(Some(proto), None, true), + ScriptObjectData::base_new(Some(proto), ScriptObjectClass::NoClass), )) .into() } - /// Construct a class instance. - pub fn instance( + /// Construct a prototype for an ES4 class. + pub fn prototype( mc: MutationContext<'gc, '_>, proto: Object<'gc>, class: Avm2ClassEntry, + scope: Option>>, ) -> Object<'gc> { + let script_class = ScriptObjectClass::InstancePrototype(class, scope); + ScriptObject(GcCell::allocate( mc, - ScriptObjectData::base_new(Some(proto), Some(class), true), + ScriptObjectData::base_new(Some(proto), script_class), )) .into() } } +/// Given a list of traits from an ABC file, find the one that matches this +/// name. +/// +/// This function adds it's result onto the list of known traits, with the +/// caveat that duplicate entries will be replaced (if allowed). As such, this +/// function should be run on the class hierarchy from top to bottom. +/// +/// If a given trait has an invalid name, attempts to override a final trait, +/// or overlaps an existing trait without being an override, then this function +/// returns an error. +/// +/// TODO: This is an O(n^2) algorithm, it sucks. +fn do_trait_lookup( + name: &QName, + known_traits: &mut Vec, + abc: Rc, + traits: &[AbcTrait], +) -> Result<(), Error> { + for trait_entry in traits.iter() { + let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?; + + if name == &trait_name { + for known_trait in known_traits.iter() { + match (&trait_entry.kind, &known_trait.kind) { + (AbcTraitKind::Getter { .. }, AbcTraitKind::Setter { .. }) => continue, + (AbcTraitKind::Setter { .. }, AbcTraitKind::Getter { .. }) => continue, + _ => {} + }; + + if known_trait.is_final { + return Err("Attempting to override a final definition".into()); + } + + if !trait_entry.is_override { + return Err("Definition override is not marked as override".into()); + } + } + + known_traits.push(trait_entry.clone()); + } + } + + Ok(()) +} + impl<'gc> ScriptObjectData<'gc> { - pub fn base_new( - proto: Option>, - class: Option, - is_instance: bool, - ) -> Self { + pub fn base_new(proto: Option>, trait_source: ScriptObjectClass<'gc>) -> Self { ScriptObjectData { values: HashMap::new(), slots: Vec::new(), methods: Vec::new(), proto, - class, - is_instance, + class: trait_source, } } - pub fn get_trait(&self, name: &QName) -> Result, Error> { - if let Some(class) = &self.class { - if self.is_instance { - for trait_entry in class.instance().traits.iter() { - let trait_name = - QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; - - if name == &trait_name { - return Ok(Some(trait_entry.clone())); - } - } - } else { - for trait_entry in class.class().traits.iter() { - let trait_name = - QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; - - if name == &trait_name { - return Ok(Some(trait_entry.clone())); - } - } - } - } - - Ok(None) - } - pub fn get_property_local( &self, reciever: Object<'gc>, @@ -298,7 +374,14 @@ impl<'gc> ScriptObjectData<'gc> { let prop = self.values.get(name); if let Some(prop) = prop { - prop.get(avm, context, reciever) + prop.get( + avm, + context, + reciever, + avm.current_stack_frame() + .and_then(|sf| sf.read().base_proto()) + .or(self.proto), + ) } else { Ok(Value::Undefined.into()) } @@ -317,7 +400,16 @@ impl<'gc> ScriptObjectData<'gc> { self.set_slot(slot_id, value, context.gc_context)?; Ok(Value::Undefined.into()) } else { - prop.set(avm, context, reciever, value) + let proto = self.proto; + prop.set( + avm, + context, + reciever, + avm.current_stack_frame() + .and_then(|sf| sf.read().base_proto()) + .or(proto), + value, + ) } } else { //TODO: Not all classes are dynamic like this @@ -341,7 +433,16 @@ impl<'gc> ScriptObjectData<'gc> { self.init_slot(slot_id, value, context.gc_context)?; Ok(Value::Undefined.into()) } else { - prop.init(avm, context, reciever, value) + let proto = self.proto; + prop.init( + avm, + context, + reciever, + avm.current_stack_frame() + .and_then(|sf| sf.read().base_proto()) + .or(proto), + value, + ) } } else { //TODO: Not all classes are dynamic like this @@ -408,6 +509,83 @@ impl<'gc> ScriptObjectData<'gc> { self.methods.get(id as usize).and_then(|v| *v) } + pub fn get_trait(&self, name: &QName) -> Result, Error> { + let mut known_traits = if let Some(proto) = self.proto { + proto.get_trait(name)? + } else { + vec![] + }; + + match &self.class { + ScriptObjectClass::ClassConstructor(class, ..) => { + do_trait_lookup(name, &mut known_traits, class.abc(), &class.class().traits)? + } + ScriptObjectClass::InstancePrototype(class, ..) => do_trait_lookup( + name, + &mut known_traits, + class.abc(), + &class.instance().traits, + )?, + ScriptObjectClass::NoClass => {} + }; + + Ok(known_traits) + } + + pub fn has_trait(&self, name: &QName) -> Result { + if let Some(proto) = self.proto { + if proto.has_trait(name)? { + return Ok(true); + } + } + + self.has_own_trait(name) + } + + pub fn has_own_trait(&self, name: &QName) -> Result { + match &self.class { + ScriptObjectClass::ClassConstructor(class, ..) => { + for trait_entry in class.class().traits.iter() { + let trait_name = + QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; + + if name == &trait_name { + return Ok(true); + } + } + } + ScriptObjectClass::InstancePrototype(class, ..) => { + for trait_entry in class.instance().traits.iter() { + let trait_name = + QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; + + if name == &trait_name { + return Ok(true); + } + } + } + ScriptObjectClass::NoClass => {} + }; + + Ok(false) + } + + pub fn get_scope(&self) -> Option>> { + match &self.class { + ScriptObjectClass::ClassConstructor(_class, scope) => *scope, + ScriptObjectClass::InstancePrototype(_class, scope) => *scope, + ScriptObjectClass::NoClass => self.proto().and_then(|proto| proto.get_scope()), + } + } + + pub fn get_abc(&self) -> Option> { + match &self.class { + ScriptObjectClass::ClassConstructor(class, ..) => Some(class.abc()), + ScriptObjectClass::InstancePrototype(class, ..) => Some(class.abc()), + ScriptObjectClass::NoClass => self.proto().and_then(|proto| proto.get_abc()), + } + } + pub fn resolve_any(&self, local_name: &str) -> Option { for (key, _value) in self.values.iter() { if key.local_name() == local_name { @@ -418,7 +596,11 @@ impl<'gc> ScriptObjectData<'gc> { None } - pub fn has_own_property(&self, name: &QName) -> bool { + pub fn has_own_property(&self, name: &QName) -> Result { + Ok(self.values.get(name).is_some() || self.has_trait(name)?) + } + + pub fn has_instantiated_property(&self, name: &QName) -> bool { self.values.get(name).is_some() } @@ -440,10 +622,6 @@ impl<'gc> ScriptObjectData<'gc> { self.proto } - pub fn class(&self) -> Option<&Avm2ClassEntry> { - self.class.as_ref() - } - /// Install a method into the object. pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) { if disp_id > 0 { From f042e453a39a97e139f1aa530c30638fbbcfe0d1 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 3 Mar 2020 20:06:07 -0500 Subject: [PATCH 103/189] Add a test for interactions between prototype and class-trait properties. This is the test that broke the old object model's back, please see parent commit's description for more details. --- core/tests/regression_tests.rs | 1 + .../swfs/avm2/es4_oop_prototypes/Test.as | 53 ++++++++++++++++++ .../swfs/avm2/es4_oop_prototypes/output.txt | 8 +++ .../swfs/avm2/es4_oop_prototypes/test.fla | Bin 0 -> 3985 bytes .../swfs/avm2/es4_oop_prototypes/test.swf | Bin 0 -> 1123 bytes 5 files changed, 62 insertions(+) create mode 100644 core/tests/swfs/avm2/es4_oop_prototypes/Test.as create mode 100644 core/tests/swfs/avm2/es4_oop_prototypes/output.txt create mode 100644 core/tests/swfs/avm2/es4_oop_prototypes/test.fla create mode 100644 core/tests/swfs/avm2/es4_oop_prototypes/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 436fe0c68b57..778532b28826 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -234,6 +234,7 @@ swf_tests! { (as3_inheritance, "avm2/inheritance", 1), (as3_stored_properties, "avm2/stored_properties", 1), (as3_virtual_properties, "avm2/virtual_properties", 1), + (as3_es4_oop_prototypes, "avm2/es4_oop_prototypes", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/es4_oop_prototypes/Test.as b/core/tests/swfs/avm2/es4_oop_prototypes/Test.as new file mode 100644 index 000000000000..c90c7052e40a --- /dev/null +++ b/core/tests/swfs/avm2/es4_oop_prototypes/Test.as @@ -0,0 +1,53 @@ +package { + public class Test {} +} + +class ES4Class extends Object { + public function ES4Class() { + trace("ES4Class constructor"); + } + + public function test_method() { + trace("ES4Class test_method"); + } + + static public function test_static() { + trace("ES4Class test_static"); + } + + public var test_var = "ES4Class test_var"; + + public const test_const = "ES4Class test_const"; + + public function get test_get() { + return "ES4Class test_get"; + } +} + +ES4Class.prototype.test_proto_var = "ES4Class test_proto_var"; +ES4Class.prototype.test_var = "TEST FAIL: Class variables override prototype variables!"; +ES4Class.prototype.test_const = "TEST FAIL: Class constants override prototype variables!"; + +ES4Class.prototype.test_proto_method = function () { + trace("ES4Class test_proto_method"); +}; + +ES4Class.prototype.test_method = function () { + trace("TEST FAIL: Class methods override prototype functions!"); +}; + +ES4Class.prototype.test_static = function () { + trace("TEST FAIL: Class static methods override prototype functions!"); +}; + +ES4Class.prototype.test_get = "TEST FAIL: Class getters override prototype properties!"; + +var x = new ES4Class(); + +trace(x.test_var); +trace(x.test_const); +trace(x.test_proto_var); +x.test_method(); +ES4Class.test_static(); +x.test_proto_method(); +trace(x.test_get); \ No newline at end of file diff --git a/core/tests/swfs/avm2/es4_oop_prototypes/output.txt b/core/tests/swfs/avm2/es4_oop_prototypes/output.txt new file mode 100644 index 000000000000..2e4c7dbd7dd5 --- /dev/null +++ b/core/tests/swfs/avm2/es4_oop_prototypes/output.txt @@ -0,0 +1,8 @@ +ES4Class constructor +ES4Class test_var +ES4Class test_const +ES4Class test_proto_var +ES4Class test_method +ES4Class test_static +ES4Class test_proto_method +ES4Class test_get diff --git a/core/tests/swfs/avm2/es4_oop_prototypes/test.fla b/core/tests/swfs/avm2/es4_oop_prototypes/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..e8422682499736c841d221c3be55563405f47944 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/es4_oop_prototypes/test.swf b/core/tests/swfs/avm2/es4_oop_prototypes/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..1c4962132eeba625589691671ee9eca692cdcdd6 GIT binary patch literal 1123 zcmV-p1f2UrS5qr+2mkd*7cgcGOHhgtBgr(r?UVFP(np{deGmBw+#|`@IL4Dq8CLrC`_4Ig&e4%Rqu?n* zvA+_4W1JG(wA07qj_N{)p#4cC6o8pDy3-_S*fy=H=nvGE~+Q6M!Hn;WW z!X$kJ9(O($cE@Pt9@Rezy9jH=+qAp9{GRK*6a8Ft^`0o+vTc)}i_+RRtj5`Zd!$@B z`y`rn=jE~gZhzWV1Y4j{RabNT}WmK3lN-R8{SOf^GQ)uZ{ zzsoP#dt;!By{_J$nOKJGSFVmd3Hak;iXx0fF=9^Zxi zNfrYERm2Y5+dfP+x0@YdM)v&y_us(yaSt-&Fa!hPGpT#}p3$=*vM}Xp-_&if^ZzEi zrlxR)1=UO>l}x2FnRHTlmeK+kV?r=tixe}8LzILmj#3<>I8Jeb24|?8rpheEbCk?e zoMjBNutf0+drDZ0usC6BghdIPBP>DKJV*r^Dp7fZ%9~W)qVkKF|79It(#@T~3cw1& zlE-xT7$e2U1WQZ;BOsE4u*ZO9`pA&+aa)@ zZ;0JPDS|9>#nI@KX;Xo&vn)S+Dc7^Sqv^lXnF1 zuv4r@8sYY|o22H!gbPa^jJt575w6T2XR|>nDsr}9mqgABPfs64@Sp$w1BIvarNFBe!Xw?Y-9U(xcyl&TLuZj-NqhUK#nUx#~)F?bGwl>rRa%-N`8uMCX pL2G2S#uKf<;Qv9&`oE5Hwe4iU<#l52X5J>g(dZ2*{{iRTXVK(cFO2{I literal 0 HcmV?d00001 From 915b2da42bbe0b5a373cc5f87800891591d25613 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 3 Mar 2020 22:10:27 -0500 Subject: [PATCH 104/189] Allow binding a reciever to a function, and make all method traits bind themselves to the object they were constructed on. Our already odd `super` handling throws up another subtlety regarding bound recievers. Since we have to construct an instance of a parent class in order to get traits on it, we also have to make sure that we initialize traits with the correct reciever. I'll demonstrate here: ```let mut base = base_proto.construct(avm, context, &[])?; let name = base.resolve_multiname(&multiname).unwrap(); let value = base.get_property(object, &name, avm, context)?.resolve(avm, context)?``` In this case, if `name` is the name of a method, getter, or setter trait, then `get_property` will instantiate that trait on `base` but bound to `reciever`. This is correct behavior for this case, but more generally, trait instantiation is permenant and therefore there's potential for confusing shenanigans if you `get_property` with the wrong reciever. To be very clear, `reciever` should *always* be the same object that is getting `get_property` et. all called on it. In the event that you need to instantiate traits with a different `reciever`, you should construct a one-off object and retrieve prototypes from that. --- core/src/avm2.rs | 3 ++ core/src/avm2/function.rs | 32 +++++++++++++++---- core/src/avm2/globals/function.rs | 27 ++++++++++++++++ core/src/avm2/object.rs | 52 +++++++++++++++++++++++-------- 4 files changed, 95 insertions(+), 19 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 08c94fa48a37..f39212602fd9 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -113,6 +113,7 @@ impl<'gc> Avm2<'gc> { abc_file.clone(), trait_entry, Some(scope), + self.globals(), )?; } @@ -720,6 +721,7 @@ impl<'gc> Avm2<'gc> { method, scope, self.system_prototypes.function, + None, ); function @@ -1278,6 +1280,7 @@ impl<'gc> Avm2<'gc> { method_entry, scope, self.system_prototypes.function, + None, ); self.push(new_fn); diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 6963a82c6364..3612f9cf6e9d 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -102,11 +102,25 @@ pub struct Avm2Function<'gc> { /// Closure scope stack at time of creation pub scope: Option>>, + + /// The reciever this method is attached to. + /// + /// Objects without a reciever are free functions that can be invoked with + /// any desired parameter for `this`. + pub reciever: Option>, } impl<'gc> Avm2Function<'gc> { - pub fn from_method(method: Avm2MethodEntry, scope: Option>>) -> Self { - Self { method, scope } + pub fn from_method( + method: Avm2MethodEntry, + scope: Option>>, + reciever: Option>, + ) -> Self { + Self { + method, + scope, + reciever, + } } } @@ -136,15 +150,16 @@ impl<'gc> Executable<'gc> { /// onto the AVM operand stack. pub fn exec( &self, - reciever: Option>, + unbound_reciever: Option>, arguments: &[Value<'gc>], avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, base_proto: Option>, ) -> Result, Error> { match self { - Executable::Native(nf) => nf(avm, context, reciever, arguments), + Executable::Native(nf) => nf(avm, context, unbound_reciever, arguments), Executable::Action(a2f) => { + let reciever = a2f.reciever.or(unbound_reciever); let activation = GcCell::allocate( context.gc_context, Activation::from_action(context, &a2f, reciever, arguments, base_proto)?, @@ -313,7 +328,7 @@ impl<'gc> FunctionObject<'gc> { Some(fn_proto), ScriptObjectClass::ClassConstructor(class.clone(), scope), ), - exec: Some(Avm2Function::from_method(initializer?, scope).into()), + exec: Some(Avm2Function::from_method(initializer?, scope, None).into()), }, )) .into(); @@ -344,19 +359,24 @@ impl<'gc> FunctionObject<'gc> { class_initializer?, scope, fn_proto, + None, ); Ok((constr, class_constr)) } /// Construct a function from an ABC method and the current closure scope. + /// + /// The given `reciever`, if supplied, will override any user-specified + /// `this` parameter. pub fn from_abc_method( mc: MutationContext<'gc, '_>, method: Avm2MethodEntry, scope: Option>>, fn_proto: Object<'gc>, + reciever: Option>, ) -> Object<'gc> { - let exec = Some(Avm2Function::from_method(method, scope).into()); + let exec = Some(Avm2Function::from_method(method, scope, reciever).into()); FunctionObject(GcCell::allocate( mc, diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs index 878e42ebd13e..d8f82554e550 100644 --- a/core/src/avm2/globals/function.rs +++ b/core/src/avm2/globals/function.rs @@ -30,6 +30,27 @@ fn to_string<'gc>( Ok(ReturnValue::Immediate("[type Function]".into())) } +/// Implements `Function.prototype.call` +fn call<'gc>( + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + func: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + let this = args.get(0).and_then(|v| v.as_object().ok()); + let base_proto = this.and_then(|that| that.proto()); + + if let Some(func) = func { + if args.len() > 1 { + func.call(this, &args[1..], avm, context, base_proto) + } else { + func.call(this, &[], avm, context, base_proto) + } + } else { + Err("Not a callable function".into()) + } +} + /// Partially construct `Function.prototype`. /// /// `__proto__` and other cross-linked properties of this object will *not* @@ -46,6 +67,12 @@ pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc 0, FunctionObject::from_builtin(gc_context, to_string, function_proto), ); + function_proto.install_method( + gc_context, + QName::new(Namespace::public_namespace(), "call"), + 0, + FunctionObject::from_builtin(gc_context, call, function_proto), + ); function_proto } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index b3b887c3fc79..3f513105444b 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -45,7 +45,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy ) -> Result, Error> { if !self.has_instantiated_property(name) { for abc_trait in self.get_trait(name)? { - self.install_trait(avm, context, &abc_trait)?; + self.install_trait(avm, context, &abc_trait, reciever)?; } } @@ -102,7 +102,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy ) -> Result<(), Error> { if !self.has_instantiated_property(name) { for abc_trait in self.get_trait(name)? { - self.install_trait(avm, context, &abc_trait)?; + self.install_trait(avm, context, &abc_trait, reciever)?; } } @@ -156,7 +156,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy ) -> Result<(), Error> { if !self.has_instantiated_property(name) { for abc_trait in self.get_trait(name)? { - self.install_trait(avm, context, &abc_trait)?; + self.install_trait(avm, context, &abc_trait, reciever)?; } } @@ -381,11 +381,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// controlled by the `lazy_init` flag provided to `load_abc`, but in /// practice every version of Flash and Animate uses lazy trait /// installation. + /// + /// The `reciever` property allows specifying the object that methods are + /// bound to. It should always be `self` except when doing things with + /// `super`, which needs to create bound methods pointing to a different + /// object. fn install_trait( &mut self, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, trait_entry: &AbcTrait, + reciever: Object<'gc>, ) -> Result<(), Error> { let scope = self.get_scope(); let abc: Result, Error> = self.get_abc().ok_or_else(|| { @@ -393,7 +399,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy .to_string() .into() }); - self.install_foreign_trait(avm, context, abc?, trait_entry, scope) + self.install_foreign_trait(avm, context, abc?, trait_entry, scope, reciever) } /// Install a trait from anywyere. @@ -404,6 +410,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy abc: Rc, trait_entry: &AbcTrait, scope: Option>>, + reciever: Object<'gc>, ) -> Result<(), Error> { let fn_proto = avm.prototypes().function; let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?; @@ -426,24 +433,39 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy disp_id, method, .. } => { let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); - let function = - FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto); + let function = FunctionObject::from_abc_method( + context.gc_context, + method, + scope, + fn_proto, + Some(reciever), + ); self.install_method(context.gc_context, trait_name, *disp_id, function); } AbcTraitKind::Getter { disp_id, method, .. } => { let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); - let function = - FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto); + let function = FunctionObject::from_abc_method( + context.gc_context, + method, + scope, + fn_proto, + Some(reciever), + ); self.install_getter(context.gc_context, trait_name, *disp_id, function)?; } AbcTraitKind::Setter { disp_id, method, .. } => { let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); - let function = - FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto); + let function = FunctionObject::from_abc_method( + context.gc_context, + method, + scope, + fn_proto, + Some(reciever), + ); self.install_setter(context.gc_context, trait_name, *disp_id, function)?; } AbcTraitKind::Class { slot_id, class } => { @@ -452,7 +474,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &type_entry.abc(), type_entry.instance().super_name.clone(), )?; - let reciever: Object<'gc> = (*self).into(); let super_class: Result, Error> = self .get_property(reciever, &super_name, avm, context)? .resolve(avm, context)? @@ -478,8 +499,13 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy slot_id, function, .. } => { let method = Avm2MethodEntry::from_method_index(abc, function.clone()).unwrap(); - let function = - FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto); + let function = FunctionObject::from_abc_method( + context.gc_context, + method, + scope, + fn_proto, + None, + ); self.install_const(context.gc_context, trait_name, *slot_id, function.into()); } AbcTraitKind::Const { slot_id, value, .. } => { From ddc9aa4cca41a2867a7433419f2bea4983b52ef9 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 3 Mar 2020 22:10:46 -0500 Subject: [PATCH 105/189] Add a test for ES4 method binding of `this`. --- core/tests/regression_tests.rs | 1 + .../swfs/avm2/es4_method_binding/Test.as | 38 ++++++++++++++++++ .../swfs/avm2/es4_method_binding/output.txt | 6 +++ .../swfs/avm2/es4_method_binding/test.fla | Bin 0 -> 3985 bytes .../swfs/avm2/es4_method_binding/test.swf | Bin 0 -> 979 bytes 5 files changed, 45 insertions(+) create mode 100644 core/tests/swfs/avm2/es4_method_binding/Test.as create mode 100644 core/tests/swfs/avm2/es4_method_binding/output.txt create mode 100644 core/tests/swfs/avm2/es4_method_binding/test.fla create mode 100644 core/tests/swfs/avm2/es4_method_binding/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 778532b28826..d73d7341de77 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -235,6 +235,7 @@ swf_tests! { (as3_stored_properties, "avm2/stored_properties", 1), (as3_virtual_properties, "avm2/virtual_properties", 1), (as3_es4_oop_prototypes, "avm2/es4_oop_prototypes", 1), + (as3_es4_method_binding, "avm2/es4_method_binding", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/es4_method_binding/Test.as b/core/tests/swfs/avm2/es4_method_binding/Test.as new file mode 100644 index 000000000000..62a11ac0458a --- /dev/null +++ b/core/tests/swfs/avm2/es4_method_binding/Test.as @@ -0,0 +1,38 @@ +package { + public class Test {} +} + +class ES4Class extends Object { + public var value: String; + + public function ES4Class(value: String) { + this.value = value; + } + + public function test_method() { + trace(this.value); + } +} + +var x = new ES4Class("var x: ES4Class"); +var y = new ES4Class("var y: ES4Class"); + +trace("Using ES4 Class method..."); +x.test_method.call(y); +y.test_method.call(x); + +trace("Using prototype method..."); + +ES4Class.prototype.test_proto_method = function () { + trace(this.value); +} + +x.test_proto_method.call(y); +y.test_proto_method.call(x); + +(function () { + trace("Hi"); + trace(this); +}).call.call(); + +trace(Function.prototype.call.call()); \ No newline at end of file diff --git a/core/tests/swfs/avm2/es4_method_binding/output.txt b/core/tests/swfs/avm2/es4_method_binding/output.txt new file mode 100644 index 000000000000..a5463e96da97 --- /dev/null +++ b/core/tests/swfs/avm2/es4_method_binding/output.txt @@ -0,0 +1,6 @@ +Using ES4 Class method... +var x: ES4Class +var y: ES4Class +Using prototype method... +var y: ES4Class +var x: ES4Class diff --git a/core/tests/swfs/avm2/es4_method_binding/test.fla b/core/tests/swfs/avm2/es4_method_binding/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..e8422682499736c841d221c3be55563405f47944 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/es4_method_binding/test.swf b/core/tests/swfs/avm2/es4_method_binding/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..70ba78b3e767eb9190226930ad1cadc05ed3ac0e GIT binary patch literal 979 zcmV;^11$VQS5qrJ1^@tfoSjqKZrer>-Q|*8lAaez#WoStN<-Votef{W@yRW0q zzC z^@*`|pBMpmcEO^)-+P$awre$yEXO->E|}|hO#jN-fpx*QH~+(Hn&s2fj?X`_Zlv6; zE=^W1-II|pvfLrdP30v`Vyn}!^FW0CM$ZWcu5~3J`6Gwzy3U|{Zzf`vJif$NZx5U8 zUBAcL!O*r{*6#WNYbTS!cKC}Owyn{{^EL}#-mZRj?sz?D?VC5Z$qG?mbs6`;S?@>D zpsDLt&p&64u79DwYrW8)8^-tg`OtAA$19Aipmy1;O=?T=)g4~^F@(qotfjb?E!k)^ z_^#!;xAGwHBR{$tusd1)$`#XcQ#fhY!O-hQj_*|`ooGyVqItWIGghy6kXWs5;0&UT ze{r5hYSwZ#VqO#$_LE@`+HXbOJ`3`D*-m7;T|D$627QX05z83u8-3UJB8bcau{8*s zh`suMo6^o{xFQKkMb!#gu~J$nNK4uyo?s$ljL9m+gkp(enUWmEd5UMKFiXWbD$P?; zrbMH-BImIzadM8xWg=@tp26}ernn}rV=6<02Nc(-@|cQGsQ8qMVmW(RTda_I;+ z|0`I+lp?}QGn;Tk<`6+Vk$EI==~))xp^`+Af@O?A=8=Rr%5ywIJdd!F9P?cI!Ia0G zia01syIzPNbkwcIV-?369d(?H^&cQ$q83112GY(a^a{|x5}u3=FbT%Ipp1?2Ap*C@ z!0>mXz;V2=2)r~l{>FJEfpLgpdHkg>#0ofw9Lz3(gE}_ie6og+tT6);B^)My;IRQJ z*O@ViVUkl+?Q?*IP9ej%+UEfmJF^{rYxyIOEhV1o6alOxAQ#U=Rn;V9*;MQ~Gk09V zv1aDLR1&iS@>(J7axT zRQ%gtUp^otVJs_I;qb30xdP=qHZ^adu^0u^e`(NC9*w79r{{Zc^rnDG7 B>@)xX literal 0 HcmV?d00001 From b33c24671335452d89839b3abcd46d7f5b480ffa Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 3 Mar 2020 23:03:35 -0500 Subject: [PATCH 106/189] Implement `is_property_overwritable`. --- core/src/avm2/function.rs | 4 ++++ core/src/avm2/object.rs | 6 ++---- core/src/avm2/script_object.rs | 15 +++++++++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 3612f9cf6e9d..8019d0441daa 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -477,6 +477,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { .init_property_local(reciever, name, value, avm, context) } + fn is_property_overwritable(self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool { + self.0.write(gc_context).base.is_property_overwritable(name) + } + fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool { self.0.write(gc_context).base.delete_property(multiname) } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 3f513105444b..2ef2219729a7 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -304,14 +304,12 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn has_own_virtual_setter(self, name: &QName) -> bool; /// Indicates whether or not a property is overwritable. - fn is_property_overwritable(self, _name: &QName) -> bool { - false - } + fn is_property_overwritable(self, gc_context: MutationContext<'gc, '_>, _name: &QName) -> bool; /// Delete a named property from the object. /// /// Returns false if the property cannot be deleted. - fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool; + fn delete_property(&self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool; /// Retrieve the `__proto__` of a given object. /// diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 9005b3c644b0..ef87347c6402 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -105,8 +105,12 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { .init_property_local(reciever, name, value, avm, context) } - fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool { - self.0.write(gc_context).delete_property(multiname) + fn is_property_overwritable(self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool { + self.0.write(gc_context).is_property_overwritable(name) + } + + fn delete_property(&self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool { + self.0.write(gc_context).delete_property(name) } fn get_slot(self, id: u32) -> Result, Error> { @@ -453,6 +457,13 @@ impl<'gc> ScriptObjectData<'gc> { } } + pub fn is_property_overwritable(&self, name: &QName) -> bool { + self.values + .get(name) + .map(|p| p.is_overwritable()) + .unwrap_or(true) + } + pub fn delete_property(&mut self, name: &QName) -> bool { let can_delete = if let Some(prop) = self.values.get(name) { prop.can_delete() From 9c5ea1d30c27506f30acefc4ddbc181dd0abf943 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 4 Mar 2020 14:24:23 -0500 Subject: [PATCH 107/189] Implement `jump`, `iftrue`, `iffalse`, `ifstricteq`, and `ifstrictne`. --- core/src/avm2.rs | 65 ++++++++++++++++++++++++++++++++++++++++++ core/src/avm2/value.rs | 8 ++++++ swf/src/avm2/read.rs | 12 +++++++- 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index f39212602fd9..a778a601bca0 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -488,6 +488,11 @@ impl<'gc> Avm2<'gc> { 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), Op::Debug { is_local_register, register_name, @@ -1313,6 +1318,66 @@ impl<'gc> Avm2<'gc> { Ok(()) } + fn op_jump(&mut self, offset: i32, reader: &mut Reader>) -> Result<(), Error> { + reader.seek(offset as i64)?; + + Ok(()) + } + + fn op_if_true(&mut self, offset: i32, reader: &mut Reader>) -> 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>, + ) -> 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>, + ) -> Result<(), Error> { + let value2 = self.pop().as_bool()?; + let value1 = self.pop().as_bool()?; + + if value1 == value2 { + reader.seek(offset as i64)?; + } + + Ok(()) + } + + fn op_if_strict_ne( + &mut self, + offset: i32, + reader: &mut Reader>, + ) -> Result<(), Error> { + let value2 = self.pop().as_bool()?; + let value1 = self.pop().as_bool()?; + + if value1 != value2 { + reader.seek(offset as i64)?; + } + + Ok(()) + } + #[allow(unused_variables)] fn op_debug( &mut self, diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 477ae9eaab5f..8550cc0007b8 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -252,6 +252,14 @@ impl<'gc> Value<'gc> { } } + pub fn as_bool(&self) -> Result { + if let Value::Bool(b) = self { + Ok(*b) + } else { + Err(format!("Expected Boolean, found {:?}", self).into()) + } + } + pub fn as_namespace(&self) -> Result<&Namespace, Error> { match self { Value::Namespace(ns) => Ok(ns), diff --git a/swf/src/avm2/read.rs b/swf/src/avm2/read.rs index 84dbc5b80b54..9d0577e27e5a 100644 --- a/swf/src/avm2/read.rs +++ b/swf/src/avm2/read.rs @@ -1,7 +1,7 @@ use crate::avm2::types::*; use crate::error::{Error, Result}; use crate::read::SwfRead; -use std::io::Read; +use std::io::{Read, Seek, SeekFrom}; pub struct Reader { inner: R, @@ -13,6 +13,16 @@ impl SwfRead for Reader { } } +impl Reader +where + R: Read + Seek, +{ + #[inline] + pub fn seek(&mut self, relative_offset: i64) -> std::io::Result { + self.inner.seek(SeekFrom::Current(relative_offset as i64)) + } +} + impl Reader { pub fn new(inner: R) -> Reader { Reader { inner } From 7253c091a2769fc027ccd9051d02859a6d9496e8 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 4 Mar 2020 14:25:44 -0500 Subject: [PATCH 108/189] Add tests for control flow instructions that use booleans or strict equality. Other comparisons will have to wait until we have ECMA-compliant abstract comparison and coercion. --- core/tests/regression_tests.rs | 2 + .../tests/swfs/avm2/control_flow_bool/Test.as | 30 ++++++++++ .../swfs/avm2/control_flow_bool/output.txt | 4 ++ .../swfs/avm2/control_flow_bool/test.fla | Bin 0 -> 3985 bytes .../swfs/avm2/control_flow_bool/test.swf | Bin 0 -> 717 bytes .../swfs/avm2/control_flow_stricteq/Test.as | 54 ++++++++++++++++++ .../avm2/control_flow_stricteq/output.txt | 8 +++ .../swfs/avm2/control_flow_stricteq/test.fla | Bin 0 -> 3985 bytes .../swfs/avm2/control_flow_stricteq/test.swf | Bin 0 -> 827 bytes 9 files changed, 98 insertions(+) create mode 100644 core/tests/swfs/avm2/control_flow_bool/Test.as create mode 100644 core/tests/swfs/avm2/control_flow_bool/output.txt create mode 100644 core/tests/swfs/avm2/control_flow_bool/test.fla create mode 100644 core/tests/swfs/avm2/control_flow_bool/test.swf create mode 100644 core/tests/swfs/avm2/control_flow_stricteq/Test.as create mode 100644 core/tests/swfs/avm2/control_flow_stricteq/output.txt create mode 100644 core/tests/swfs/avm2/control_flow_stricteq/test.fla create mode 100644 core/tests/swfs/avm2/control_flow_stricteq/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index d73d7341de77..eff7b84da716 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -236,6 +236,8 @@ swf_tests! { (as3_virtual_properties, "avm2/virtual_properties", 1), (as3_es4_oop_prototypes, "avm2/es4_oop_prototypes", 1), (as3_es4_method_binding, "avm2/es4_method_binding", 1), + (as3_control_flow_bool, "avm2/control_flow_bool", 1), + (as3_control_flow_stricteq, "avm2/control_flow_stricteq", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/control_flow_bool/Test.as b/core/tests/swfs/avm2/control_flow_bool/Test.as new file mode 100644 index 000000000000..aeeb49045de3 --- /dev/null +++ b/core/tests/swfs/avm2/control_flow_bool/Test.as @@ -0,0 +1,30 @@ +package { + public class Test {} +} + +var t = true; +var f = false; + +if (t) { + trace("if (true)"); +} else { + trace("TEST FAIL: if (true)"); +} + +if (!f) { + trace("if (!false)"); +} else { + trace("TEST FAIL: if (!false)"); +} + +if (!t) { + trace("TEST FAIL: if (!true)"); +} else { + trace("if (!true)"); +} + +if (f) { + trace("TEST FAIL: if (false)"); +} else { + trace("if (false)"); +} \ No newline at end of file diff --git a/core/tests/swfs/avm2/control_flow_bool/output.txt b/core/tests/swfs/avm2/control_flow_bool/output.txt new file mode 100644 index 000000000000..c1e3ebcab604 --- /dev/null +++ b/core/tests/swfs/avm2/control_flow_bool/output.txt @@ -0,0 +1,4 @@ +if (true) +if (!false) +if (!true) +if (false) diff --git a/core/tests/swfs/avm2/control_flow_bool/test.fla b/core/tests/swfs/avm2/control_flow_bool/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..e8422682499736c841d221c3be55563405f47944 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/control_flow_bool/test.swf b/core/tests/swfs/avm2/control_flow_bool/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..e691fb87b5250ce0d931e9a668910735d33fbb5e GIT binary patch literal 717 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/control_flow_stricteq/test.swf b/core/tests/swfs/avm2/control_flow_stricteq/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..421426e41a0efbde9fd2de65786e04c1951de7bb GIT binary patch literal 827 zcmV-B1H}A8S5qr%1^@tfoSl^GPTMdP$8G20ruWiP3an5@xpc5LfEcImL&nUx0$pD;T8HG`x2gj9XDhYD4MjWiT(foe2%S?lYU3iAwsdA2xTzZ zNCXf<6>@WP^KJ;C@%Z_0ej$JIDc>%wy!ujURaeyQiuIv%vbntUd9{eH21BqOqrC=e zvD^YeSE=6;H{gn+A|K|Fvd?U%#2-@*JPwVfUf$8o*1mblnr4fY{itc|6w2Cp2JQ&Rgwp||FQ29aoSfJty>V4z{c_Y6CxJ7SHFi{9h1Ti2X2}_s*0TCi#n8^HZf%1op z1rZK_FpNS+eGkDHi&C(ocB<$)?yi7C2+&`oqk<;&fJRh}9H8tm1})WUk zZa|YYc_f06tJFjD1G^y{S3rb7Btf`Q;IzwQV5NMX05QmUN~5>&gJ7k7J_KTj^AX@) z{3uu%pJzb~bDrb-4|6^N)`-t1L5y-f&G+x}8L*!DyZ|E0`8?mh%U^;u=JOJWan2X{ z{$0KVR?g?kASOUSt>ASkL9dWd*I+0HReSCVb-ayH!4<)l1hCWH`qsnOc^BtV$B=fz z#3evwsI@ZJQ5oW8j;LBx)ncj^SG9zyiSRqXS?@X)uIJngguG6U+`j+5Uj=#t Date: Thu, 5 Mar 2020 19:42:44 -0500 Subject: [PATCH 109/189] Sign extend negative `s24` values correctly. --- swf/src/avm2/read.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/swf/src/avm2/read.rs b/swf/src/avm2/read.rs index 9d0577e27e5a..3c64459bc77c 100644 --- a/swf/src/avm2/read.rs +++ b/swf/src/avm2/read.rs @@ -102,9 +102,9 @@ impl Reader { #[allow(dead_code)] fn read_i24(&mut self) -> Result { - Ok(i32::from(self.read_u8()?) - | (i32::from(self.read_u8()?) << 8) - | (i32::from(self.read_u8()?) << 16)) + Ok(i32::from(self.read_u8()? as i8) + | (i32::from(self.read_u8()? as i8) << 8) + | (i32::from(self.read_u8()? as i8) << 16)) } fn read_i32(&mut self) -> Result { let mut n: i32 = 0; From da6a7c0723f4c187f661c15ea26a4201a3afc57d Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 5 Mar 2020 20:47:48 -0500 Subject: [PATCH 110/189] Implement `kill`, at least a little. I'm sure there's some other part of AVM2 that cares about killed registers, but I couldn't find it yet. --- core/src/avm2.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index a778a601bca0..aa344480a04f 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -442,6 +442,7 @@ impl<'gc> Avm2<'gc> { 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 } => { @@ -598,6 +599,14 @@ impl<'gc> Avm2<'gc> { 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, '_>, From 67b7fbb5934be51d21dcf68a48fc6472a485bc6d Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 5 Mar 2020 20:48:50 -0500 Subject: [PATCH 111/189] Implement `label`, which is a no-op designed specifically to silence verifier errors about unreachable code. --- core/src/avm2.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index aa344480a04f..c01d1733273c 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -494,6 +494,7 @@ impl<'gc> Avm2<'gc> { 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), + Op::Label => Ok(()), Op::Debug { is_local_register, register_name, From 9496fbde0a8c246a83fefef47f83555c61d5bf1d Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 5 Mar 2020 20:49:24 -0500 Subject: [PATCH 112/189] Remove `DontEnum`, `is_enumerable` and attribute mutation. They won't be needed. --- core/src/avm2/property.rs | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 912b9830a55c..b51794e9b7d9 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -15,7 +15,6 @@ use gc_arena::{Collect, CollectionContext}; /// TODO: Replace with AVM2 properties for traits #[derive(EnumSetType, Debug)] pub enum Attribute { - DontEnum, DontDelete, ReadOnly, } @@ -247,30 +246,6 @@ impl<'gc> Property<'gc> { } } - /// List this property's attributes. - pub fn attributes(&self) -> EnumSet { - match self { - Property::Virtual { attributes, .. } => *attributes, - Property::Stored { attributes, .. } => *attributes, - Property::Slot { attributes, .. } => *attributes, - } - } - - /// Re-define this property's attributes. - pub fn set_attributes(&mut self, new_attributes: EnumSet) { - match self { - Property::Virtual { - ref mut attributes, .. - } => *attributes = new_attributes, - Property::Stored { - ref mut attributes, .. - } => *attributes = new_attributes, - Property::Slot { - ref mut attributes, .. - } => *attributes = new_attributes, - } - } - pub fn can_delete(&self) -> bool { match self { Property::Virtual { attributes, .. } => !attributes.contains(DontDelete), @@ -279,14 +254,6 @@ impl<'gc> Property<'gc> { } } - pub fn is_enumerable(&self) -> bool { - match self { - Property::Virtual { attributes, .. } => !attributes.contains(DontEnum), - Property::Stored { attributes, .. } => !attributes.contains(DontEnum), - Property::Slot { attributes, .. } => !attributes.contains(DontEnum), - } - } - pub fn is_overwritable(&self) -> bool { match self { Property::Virtual { From 1cc8954747f95c53b0e9c19185a7f73414826548 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 5 Mar 2020 21:11:04 -0500 Subject: [PATCH 113/189] Impl `pop`, which is the opposite of `dup`; and also the opposite of all the `push` instructions. Confusingly, this one isn't documented in the AVM2 spec at all, but it's method of operation is fairly obvious. --- core/src/avm2.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index c01d1733273c..f715fe91e0b3 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -439,6 +439,7 @@ impl<'gc> Avm2<'gc> { 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(), Op::Dup => self.op_dup(), Op::GetLocal { index } => self.op_get_local(index), Op::SetLocal { index } => self.op_set_local(context, index), @@ -580,6 +581,12 @@ impl<'gc> Avm2<'gc> { Ok(()) } + fn op_pop(&mut self) -> Result<(), Error> { + self.pop(); + + Ok(()) + } + fn op_dup(&mut self) -> Result<(), Error> { self.push(self.stack.last().cloned().unwrap_or(Value::Undefined)); From 73189b6449c912326e886a821288021f2d2ac9dc Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 5 Mar 2020 21:12:54 -0500 Subject: [PATCH 114/189] Properly unwind errors thrown from the AVM2 reader. --- core/src/avm2.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index f715fe91e0b3..f1b990faea99 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -423,7 +423,8 @@ impl<'gc> Avm2<'gc> { context: &mut UpdateContext<'_, 'gc, '_>, reader: &mut Reader>, ) -> Result<(), Error> { - if let Some(op) = reader.read_op()? { + let op = reader.read_op(); + if let Ok(Some(op)) = op { avm_debug!("Opcode: {:?}", op); let result = match op { @@ -511,6 +512,14 @@ impl<'gc> Avm2<'gc> { 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(()) From c014b40109a48606a995dfe321b8531885aaae3d Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 5 Mar 2020 21:26:01 -0500 Subject: [PATCH 115/189] 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. --- core/src/avm2.rs | 92 ++++++++++++++++++++++++++++++++++ core/src/avm2/function.rs | 4 ++ core/src/avm2/object.rs | 11 ++++ core/src/avm2/script_object.rs | 21 ++++++++ core/src/avm2/value.rs | 7 +++ 5 files changed, 135 insertions(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index f1b990faea99..8f1d71f7e84a 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -496,6 +496,13 @@ impl<'gc> Avm2<'gc> { 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), + 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, @@ -1404,6 +1411,91 @@ impl<'gc> Avm2<'gc> { Ok(()) } + 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)? + .resolve(self, context)? + } else { + Value::Undefined + }; + + self.push(value); + + Ok(()) + } + #[allow(unused_variables)] fn op_debug( &mut self, diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 8019d0441daa..af6563b59cec 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -555,6 +555,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.proto() } + fn get_enumerant_name(&self, index: u32) -> Option { + self.0.read().base.get_enumerant_name(index) + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 2ef2219729a7..575a7ae44099 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -318,6 +318,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// `get`. fn proto(&self) -> Option>; + /// Retrieve a given enumerable name by index. + /// + /// Enumerants are listed by index, starting from zero. A value of `None` + /// indicates that no enumerant with that index, or any greater index, + /// exists. (In other words, it means stop.) + /// + /// Objects are responsible for maintaining a consistently ordered and + /// indexed list of enumerable names which can be queried by this + /// mechanism. + fn get_enumerant_name(&self, index: u32) -> Option; + /// Install a method (or any other non-slot value) on an object. fn install_method( &mut self, diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index ef87347c6402..211619231c7f 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -64,6 +64,9 @@ pub struct ScriptObjectData<'gc> { /// The class that this script object represents. class: ScriptObjectClass<'gc>, + + /// Enumeratable property names. + enumerants: Vec, } impl<'gc> TObject<'gc> for ScriptObject<'gc> { @@ -183,6 +186,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().proto } + fn get_enumerant_name(&self, index: u32) -> Option { + self.0.read().get_enumerant_name(index) + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } @@ -365,6 +372,7 @@ impl<'gc> ScriptObjectData<'gc> { methods: Vec::new(), proto, class: trait_source, + enumerants: Vec::new(), } } @@ -417,6 +425,7 @@ impl<'gc> ScriptObjectData<'gc> { } } else { //TODO: Not all classes are dynamic like this + self.enumerants.push(name.clone()); self.values .insert(name.clone(), Property::new_dynamic_property(value)); @@ -633,6 +642,18 @@ impl<'gc> ScriptObjectData<'gc> { self.proto } + pub fn get_enumerant_name(&self, index: u32) -> Option { + // NOTE: AVM2 object enumeration is one of the weakest parts of an + // otherwise well-designed VM. Notably, because of the way they + // implemented `hasnext` and `hasnext2`, all enumerants start from ONE. + // Hence why we have to `checked_sub` here in case some miscompiled + // code doesn't check for the zero index, which is actually a failure + // sentinel. + let true_index = (index as usize).checked_sub(1)?; + + self.enumerants.get(true_index).cloned() + } + /// Install a method into the object. pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) { if disp_id > 0 { diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 8550cc0007b8..19d29c186678 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -252,6 +252,13 @@ impl<'gc> Value<'gc> { } } + pub fn as_number(&self) -> Result { + match self { + Value::Number(f) => Ok(*f), + _ => Err(format!("Expected Number, found {:?}", self).into()), + } + } + pub fn as_bool(&self) -> Result { if let Value::Bool(b) = self { Ok(*b) From d29f3dc1d002e87ddf18083952e07f6dadefd8f1 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 5 Mar 2020 21:31:34 -0500 Subject: [PATCH 116/189] Add `as3_object_enumeration` and `as3_class_enumeration` tests. The former tests iterating normal objects and the latter tests iterating objects with prototypes. --- core/tests/regression_tests.rs | 2 ++ core/tests/swfs/avm2/class_enumeration/Test.as | 17 +++++++++++++++++ .../swfs/avm2/class_enumeration/output.txt | 4 ++++ .../tests/swfs/avm2/class_enumeration/test.fla | Bin 0 -> 3985 bytes .../tests/swfs/avm2/class_enumeration/test.swf | Bin 0 -> 906 bytes .../tests/swfs/avm2/object_enumeration/Test.as | 10 ++++++++++ .../swfs/avm2/object_enumeration/output.txt | 2 ++ .../swfs/avm2/object_enumeration/test.fla | Bin 0 -> 3985 bytes .../swfs/avm2/object_enumeration/test.swf | Bin 0 -> 705 bytes 9 files changed, 35 insertions(+) create mode 100644 core/tests/swfs/avm2/class_enumeration/Test.as create mode 100644 core/tests/swfs/avm2/class_enumeration/output.txt create mode 100644 core/tests/swfs/avm2/class_enumeration/test.fla create mode 100644 core/tests/swfs/avm2/class_enumeration/test.swf create mode 100644 core/tests/swfs/avm2/object_enumeration/Test.as create mode 100644 core/tests/swfs/avm2/object_enumeration/output.txt create mode 100644 core/tests/swfs/avm2/object_enumeration/test.fla create mode 100644 core/tests/swfs/avm2/object_enumeration/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index eff7b84da716..2c4f10c2cfe8 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -238,6 +238,8 @@ swf_tests! { (as3_es4_method_binding, "avm2/es4_method_binding", 1), (as3_control_flow_bool, "avm2/control_flow_bool", 1), (as3_control_flow_stricteq, "avm2/control_flow_stricteq", 1), + (as3_object_enumeration, "avm2/object_enumeration", 1), + (as3_class_enumeration, "avm2/class_enumeration", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/class_enumeration/Test.as b/core/tests/swfs/avm2/class_enumeration/Test.as new file mode 100644 index 000000000000..34b506010f9d --- /dev/null +++ b/core/tests/swfs/avm2/class_enumeration/Test.as @@ -0,0 +1,17 @@ +package { + public class Test {} +} + +dynamic class ES4Class { + public var variable = "TEST FAIL: Trait properties are NOT enumerable! (var)"; + public const constant = "TEST FAIL: Trait properties are NOT enumerable! (const)"; +} + +var x = new ES4Class(); +x.dynamic_variable = "variable value"; +ES4Class.prototype.dynamic_prototype_variable = "prototype value"; + +for (var name in x) { + trace(name); + trace(x[name]); +} \ No newline at end of file diff --git a/core/tests/swfs/avm2/class_enumeration/output.txt b/core/tests/swfs/avm2/class_enumeration/output.txt new file mode 100644 index 000000000000..ccd06b32894d --- /dev/null +++ b/core/tests/swfs/avm2/class_enumeration/output.txt @@ -0,0 +1,4 @@ +dynamic_variable +variable value +dynamic_prototype_variable +prototype value diff --git a/core/tests/swfs/avm2/class_enumeration/test.fla b/core/tests/swfs/avm2/class_enumeration/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..e8422682499736c841d221c3be55563405f47944 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/class_enumeration/test.swf b/core/tests/swfs/avm2/class_enumeration/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..984bd1d5634343753da12c7545632de1f6ffd6d2 GIT binary patch literal 906 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/object_enumeration/test.swf b/core/tests/swfs/avm2/object_enumeration/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..76e4f7a31dbe2fcf575617495dfb2ef997f305d4 GIT binary patch literal 705 zcmV;y0zUmiS5qrL1ONbdoSjnJZqq;z-C1AO&ON=PX-irV;i0sO-BtpWJ`|czl$H`f ziqr>3vFmIUgX09-3GFLi!k_dP0u}HZ?iYl0-J&3YkXWsEX3or>Js!;&5?TnQKO>X} zRLx2Vp$eW%CVLAAt!9Rc#m(ZYH^tG(hl8qp=u6DD*rTrDTYYC|=Z1-UPtS#sVGpU` zyM{^cb$x$ORurS%KcywJ-&3A7wv{baeWaWYE!(%8!mYs4w7~LBlTt>zy&rlF)3pZv z{l64XM~SEtbVMEBOFs`ym377N%`SD5)u=&HE3-AnXMbS&)`&((?zeoU@A!_B$_^qA{%~eAQ2z=7_f|)!|{@U1P~xha0p`#m?MVsO2R_~ zj_Du>nB~Um3w9ZR7h+>|TtioxAkGlXO1RcoWg%`#I#zXjf(l(=vCxz^vKupR!4Lyd zMBjAr)AvjfA-Ng*`eo(e4})Av+*%+t<2tFwA&{9P>ZFqh1G0r7dpVg%SOWG82O!5H z4NOujo@Ou32V4^l0IdZ)g9VPkxoc_71?wT;M`2J7 literal 0 HcmV?d00001 From 0e89cb217514d031776c571d44d01d8d63946c24 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 6 Mar 2020 15:02:19 -0500 Subject: [PATCH 117/189] Impl `Object.isPrototypeOf` w/ test --- core/src/avm2/globals/object.rs | 33 ++++++++++++++++-- core/tests/swfs/avm2/is_prototype_of/Test.as | 28 +++++++++++++++ .../swfs/avm2/is_prototype_of/output.txt | 13 +++++++ core/tests/swfs/avm2/is_prototype_of/test.fla | Bin 0 -> 3985 bytes core/tests/swfs/avm2/is_prototype_of/test.swf | Bin 0 -> 896 bytes 5 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 core/tests/swfs/avm2/is_prototype_of/Test.as create mode 100644 core/tests/swfs/avm2/is_prototype_of/output.txt create mode 100644 core/tests/swfs/avm2/is_prototype_of/test.fla create mode 100644 core/tests/swfs/avm2/is_prototype_of/test.swf diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index cdf1e04da1c2..16597ecf54ae 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -12,7 +12,7 @@ use gc_arena::MutationContext; /// Implements `Object` pub fn constructor<'gc>( _avm: &mut Avm2<'gc>, - _action_context: &mut UpdateContext<'_, 'gc, '_>, + _context: &mut UpdateContext<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { @@ -22,7 +22,7 @@ pub fn constructor<'gc>( /// `Object.prototype.hasOwnProperty` pub fn has_own_property<'gc>( _avm: &mut Avm2<'gc>, - _action_context: &mut UpdateContext<'_, 'gc, '_>, + _context: &mut UpdateContext<'_, 'gc, '_>, this: Option>, args: &[Value<'gc>], ) -> Result, Error> { @@ -39,6 +39,29 @@ pub fn has_own_property<'gc>( Ok(false.into()) } +/// `Object.prototype.isPrototypeOf` +pub fn is_prototype_of<'gc>( + _avm: &mut Avm2<'gc>, + _context: &mut UpdateContext<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + let search_proto: Result, Error> = + this.ok_or_else(|| "No valid this parameter".into()); + let search_proto = search_proto?; + let mut target_proto = args.get(0).cloned().unwrap_or(Value::Undefined); + + while let Value::Object(proto) = target_proto { + if Object::ptr_eq(search_proto, proto) { + return Ok(true.into()); + } + + target_proto = proto.proto().map(|o| o.into()).unwrap_or(Value::Undefined); + } + + Ok(false.into()) +} + /// Partially construct `Object.prototype`. /// /// `__proto__` and other cross-linked properties of this object will *not* @@ -59,4 +82,10 @@ pub fn fill_proto<'gc>( 0, FunctionObject::from_builtin(gc_context, has_own_property, fn_proto), ); + object_proto.install_method( + gc_context, + QName::new(Namespace::public_namespace(), "isPrototypeOf"), + 0, + FunctionObject::from_builtin(gc_context, is_prototype_of, fn_proto), + ); } diff --git a/core/tests/swfs/avm2/is_prototype_of/Test.as b/core/tests/swfs/avm2/is_prototype_of/Test.as new file mode 100644 index 000000000000..c23680bb7ce9 --- /dev/null +++ b/core/tests/swfs/avm2/is_prototype_of/Test.as @@ -0,0 +1,28 @@ +package { + public class Test {} +} + +class ES4Class extends Object { + +} + +function ES3Class() { + +} + +var es4inst = new ES4Class(); +var es3inst = new ES3Class(); + +trace("//ES4Class.prototype.isPrototypeOf(es4inst);"); +trace(ES4Class.prototype.isPrototypeOf(es4inst)); +trace("//Object.prototype.isPrototypeOf(es4inst);"); +trace(Object.prototype.isPrototypeOf(es4inst)); +trace("//ES3Class.prototype.isPrototypeOf(es4inst);"); +trace(ES3Class.prototype.isPrototypeOf(es4inst)); + +trace("//ES4Class.prototype.isPrototypeOf(es3inst);"); +trace(ES4Class.prototype.isPrototypeOf(es3inst)); +trace("//Object.prototype.isPrototypeOf(es3inst);"); +trace(Object.prototype.isPrototypeOf(es3inst)); +trace("//ES3Class.prototype.isPrototypeOf(es3inst);"); +trace(ES3Class.prototype.isPrototypeOf(es3inst)); \ No newline at end of file diff --git a/core/tests/swfs/avm2/is_prototype_of/output.txt b/core/tests/swfs/avm2/is_prototype_of/output.txt new file mode 100644 index 000000000000..3d3f2dc809d1 --- /dev/null +++ b/core/tests/swfs/avm2/is_prototype_of/output.txt @@ -0,0 +1,13 @@ + +//ES4Class.prototype.isPrototypeOf(es4inst); +true +//Object.prototype.isPrototypeOf(es4inst); +true +//ES3Class.prototype.isPrototypeOf(es4inst); +false +//ES4Class.prototype.isPrototypeOf(es3inst); +false +//Object.prototype.isPrototypeOf(es3inst); +true +//ES3Class.prototype.isPrototypeOf(es3inst); +true diff --git a/core/tests/swfs/avm2/is_prototype_of/test.fla b/core/tests/swfs/avm2/is_prototype_of/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..e8422682499736c841d221c3be55563405f47944 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/is_prototype_of/test.swf b/core/tests/swfs/avm2/is_prototype_of/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..ddbacb6f4813bfacce70ff0385877a91894143af GIT binary patch literal 896 zcmV-`1AqKOS5qsO1^@tfoSjtNa@s}|-XBS;6=1;p+A+-^Y2!G9@;P?|vPx7EC_2>P^~{}ylmH-Ln{aX6-He=Hm=Y+m$W_y z4?<2J+e54I%5vN{&WH^im+1?u?OP+Zz4ZmJWve98DXo*=STEH6ORdf`=B_D6okvP8 zM@n%+FsSvNU_7+W=qqpHu;$Pi7p~2#M5U_p4ygO5ZZC8^m#|>Raf6V{x{yB{HabnO z&pQ68Z4X(e=lQH7LI$1Sy&ZI{$>>SP3696U7kc5@m>qle?rvPQ9Qsy|J=PZutS#JX zPVmbdx9o>;*3DhI@JLORh85kc{CebklOpP0Q$*}snV({9`4k)R#I0EU{=Pu%p6`sq`=4=M){@bxM`%cJq{>R945=B%d zGA$>H$>K^mQ%b70id6|hh%v-;jX*-6O0Y)c6j9Pd%@VvyV2>Qj z5m+K{jjm$??-A_*QNAV0cSLzelua%A8z8=LETveACG(|37(+x62N+R_`$)1%fC?1| z2;^H8fMgl+tylt(C_n{N{9QEhpiq(^5}6>B&x^Cf!6n2Jo*En?2qjsY8q-6-$;yF9 zwIHQWNEsuD|AAA3>&Q%Wkn8D`wgx zM+8{fEMsOKms=S)%bJ-kwzFNjzY2xE%83mDa-2X*M4l5B0ScTTOGJqi-w05SLuoOw z#);bktjD3|#4S$T5nv+@C5ZhD{NwMxe&$_={0O5S5D2wIn!oVf88+b&1e-I7n^nxs z#mm;1x%2xoSOrsCY30xiahCG7(ygh%faplx=oXA_(dd?pZrSL{{C Date: Fri, 6 Mar 2020 15:26:50 -0500 Subject: [PATCH 118/189] Free functions always have a `prototype`, this is a holdover from ES3. --- core/src/avm2.rs | 10 +++++++++- core/src/avm2/object.rs | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 8f1d71f7e84a..874aa8b9ab0b 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1313,13 +1313,21 @@ impl<'gc> Avm2<'gc> { let method_entry = self.table_method(index)?; let scope = self.current_stack_frame().unwrap().read().scope(); - let new_fn = FunctionObject::from_abc_method( + let mut new_fn = FunctionObject::from_abc_method( context.gc_context, method_entry, 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); diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 575a7ae44099..33e4d5213b55 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -508,13 +508,21 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy slot_id, function, .. } => { let method = Avm2MethodEntry::from_method_index(abc, function.clone()).unwrap(); - let function = FunctionObject::from_abc_method( + let mut function = FunctionObject::from_abc_method( context.gc_context, method, scope, fn_proto, None, ); + let es3_proto = ScriptObject::object(context.gc_context, avm.prototypes().object); + + function.install_slot( + context.gc_context, + QName::new(Namespace::public_namespace(), "prototype"), + 0, + es3_proto.into(), + ); self.install_const(context.gc_context, trait_name, *slot_id, function.into()); } AbcTraitKind::Const { slot_id, value, .. } => { From c6265bb50ccad0a03a9a2264683ae74364d28954 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 6 Mar 2020 15:32:47 -0500 Subject: [PATCH 119/189] Allow tracing booleans. This requires implementing *some level* of coercions, even though this isn't the way to do it. --- core/src/avm2/globals.rs | 2 +- core/src/avm2/value.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 7dcf3b68b9d2..01a49346cde5 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -21,7 +21,7 @@ fn trace<'gc>( args: &[Value<'gc>], ) -> Result, Error> { if let Some(s) = args.get(0) { - log::info!(target: "avm_trace", "{}", s.as_string()?); + log::info!(target: "avm_trace", "{}", s.clone().coerce_string()); } Ok(Value::Undefined.into()) diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 19d29c186678..7754aa8e6f63 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -245,6 +245,9 @@ impl<'gc> Value<'gc> { } } + /// Demand a string value, erroring out if one is not found. + /// + /// TODO: This should be replaced with `coerce_string` where possible. pub fn as_string(&self) -> Result<&String, Error> { match self { Value::String(s) => Ok(s), @@ -252,6 +255,16 @@ impl<'gc> Value<'gc> { } } + /// Coerce a value into a string. + pub fn coerce_string(self) -> String { + match self { + Value::String(s) => s, + Value::Bool(true) => "true".to_string(), + Value::Bool(false) => "false".to_string(), + _ => "".to_string(), + } + } + pub fn as_number(&self) -> Result { match self { Value::Number(f) => Ok(*f), From 8677804ea0426ef99a2c7e0818041c40e763d5f4 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 6 Mar 2020 15:33:04 -0500 Subject: [PATCH 120/189] Actually enable the `isPrototypeOf` test. --- core/tests/regression_tests.rs | 1 + core/tests/swfs/avm2/is_prototype_of/output.txt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 2c4f10c2cfe8..3a58d058919a 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -240,6 +240,7 @@ swf_tests! { (as3_control_flow_stricteq, "avm2/control_flow_stricteq", 1), (as3_object_enumeration, "avm2/object_enumeration", 1), (as3_class_enumeration, "avm2/class_enumeration", 1), + (as3_is_prototype_of, "avm2/is_prototype_of", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/is_prototype_of/output.txt b/core/tests/swfs/avm2/is_prototype_of/output.txt index 3d3f2dc809d1..6c82d1994b56 100644 --- a/core/tests/swfs/avm2/is_prototype_of/output.txt +++ b/core/tests/swfs/avm2/is_prototype_of/output.txt @@ -1,4 +1,3 @@ - //ES4Class.prototype.isPrototypeOf(es4inst); true //Object.prototype.isPrototypeOf(es4inst); From 6e2508a79d2a624999f750573f9e305ff93a4e47 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 6 Mar 2020 23:15:36 -0500 Subject: [PATCH 121/189] Fix `any` name resolution, at least enough for the `has_own_property` test to work. Private names now return `false`, and we run any names through trait lookups. This also means any namespace resolution can fail now, in case we need to throw a `VerifyError`. --- core/src/avm2/function.rs | 6 +++- core/src/avm2/globals/object.rs | 8 ++++-- core/src/avm2/names.rs | 7 +++++ core/src/avm2/object.rs | 15 ++++++++-- core/src/avm2/script_object.rs | 51 ++++++++++++++++++++++++++++++--- 5 files changed, 77 insertions(+), 10 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index af6563b59cec..44f8651e41b0 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -523,10 +523,14 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_abc() } - fn resolve_any(self, local_name: &str) -> Option { + fn resolve_any(self, local_name: &str) -> Result, Error> { self.0.read().base.resolve_any(local_name) } + fn resolve_any_trait(self, local_name: &str) -> Result, Error> { + self.0.read().base.resolve_any_trait(local_name) + } + fn has_own_property(self, name: &QName) -> Result { self.0.read().base.has_own_property(name) } diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 16597ecf54ae..7d0c944a3d48 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -31,9 +31,11 @@ pub fn has_own_property<'gc>( let name: Result<&Value<'gc>, Error> = args.get(0).ok_or_else(|| "No name specified".into()); let name = name?.as_string()?; - if let Some(ns) = this.resolve_any(&name) { - let qname = QName::new(ns, &name); - return Ok(this.has_own_property(&qname)?.into()); + if let Some(ns) = this.resolve_any(&name)? { + if !ns.is_private() { + let qname = QName::new(ns, &name); + return Ok(this.has_own_property(&qname)?.into()); + } } Ok(false.into()) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 1e2f7fab7026..c6756ec5b348 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -69,6 +69,13 @@ impl Namespace { _ => false, } } + + pub fn is_private(&self) -> bool { + match self { + Self::Private(_) => true, + _ => false, + } + } } /// A `QName`, likely "qualified name", consists of a namespace and name string. diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 33e4d5213b55..1b2743495fc1 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -237,7 +237,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy for ns in multiname.namespace_set() { if ns.is_any() { if let Some(name) = multiname.local_name() { - let ns = self.resolve_any(name); + let ns = self.resolve_any(name)?; return Ok(ns.map(|ns| QName::new(ns, name))); } else { return Ok(None); @@ -263,7 +263,18 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// /// The `Namespace` must not be `Namespace::Any`, as this function exists /// specifically resolve names in that namespace. - fn resolve_any(self, local_name: &str) -> Option; + /// + /// Trait names will be resolve on class constructors and object instances, + /// but not prototypes. If you want to search a prototype's provided traits + /// you must walk the prototype chain using `resolve_any_trait`. + fn resolve_any(self, local_name: &str) -> Result, Error>; + + /// Given a local name of a trait, find the namespace it resides in, if any. + /// + /// This function only works for names which are trait properties, not + /// dynamic or prototype properties. Furthermore, instance prototypes *will* + /// resolve trait names here, contrary to their behavior in `resolve_any.` + fn resolve_any_trait(self, local_name: &str) -> Result, Error>; /// Indicates whether or not a property exists on an object. fn has_property(self, name: &QName) -> Result { diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 211619231c7f..8281269af35e 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -154,10 +154,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_abc() } - fn resolve_any(self, local_name: &str) -> Option { + fn resolve_any(self, local_name: &str) -> Result, Error> { self.0.read().resolve_any(local_name) } + fn resolve_any_trait(self, local_name: &str) -> Result, Error> { + self.0.read().resolve_any_trait(local_name) + } + fn has_own_property(self, name: &QName) -> Result { self.0.read().has_own_property(name) } @@ -606,14 +610,53 @@ impl<'gc> ScriptObjectData<'gc> { } } - pub fn resolve_any(&self, local_name: &str) -> Option { + pub fn resolve_any(&self, local_name: &str) -> Result, Error> { for (key, _value) in self.values.iter() { if key.local_name() == local_name { - return Some(key.namespace().clone()); + return Ok(Some(key.namespace().clone())); } } - None + match self.class { + ScriptObjectClass::ClassConstructor(..) => self.resolve_any_trait(local_name), + ScriptObjectClass::NoClass => self.resolve_any_trait(local_name), + _ => Ok(None), + } + } + + pub fn resolve_any_trait(&self, local_name: &str) -> Result, Error> { + if let Some(proto) = self.proto { + let proto_trait_name = proto.resolve_any_trait(local_name)?; + if let Some(ns) = proto_trait_name { + return Ok(Some(ns)); + } + } + + match &self.class { + ScriptObjectClass::ClassConstructor(class, ..) => { + for trait_entry in class.class().traits.iter() { + let trait_name = + QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; + + if local_name == trait_name.local_name() { + return Ok(Some(trait_name.namespace().clone())); + } + } + } + ScriptObjectClass::InstancePrototype(class, ..) => { + for trait_entry in class.instance().traits.iter() { + let trait_name = + QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; + + if local_name == trait_name.local_name() { + return Ok(Some(trait_name.namespace().clone())); + } + } + } + ScriptObjectClass::NoClass => {} + }; + + Ok(None) } pub fn has_own_property(&self, name: &QName) -> Result { From 42cb8f57c887dcfd10b30cacd3ccc6dd423f0b29 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 6 Mar 2020 23:16:10 -0500 Subject: [PATCH 122/189] Add a test for `has_own_property` in various class instance scenarios. --- core/tests/regression_tests.rs | 1 + core/tests/swfs/avm2/has_own_property/Test.as | 177 ++++++++++++++++++ .../swfs/avm2/has_own_property/output.txt | 102 ++++++++++ .../tests/swfs/avm2/has_own_property/test.fla | Bin 0 -> 3983 bytes .../tests/swfs/avm2/has_own_property/test.swf | Bin 0 -> 2422 bytes 5 files changed, 280 insertions(+) create mode 100644 core/tests/swfs/avm2/has_own_property/Test.as create mode 100644 core/tests/swfs/avm2/has_own_property/output.txt create mode 100644 core/tests/swfs/avm2/has_own_property/test.fla create mode 100644 core/tests/swfs/avm2/has_own_property/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 3a58d058919a..eb2f5131182f 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -241,6 +241,7 @@ swf_tests! { (as3_object_enumeration, "avm2/object_enumeration", 1), (as3_class_enumeration, "avm2/class_enumeration", 1), (as3_is_prototype_of, "avm2/is_prototype_of", 1), + (as3_has_own_property, "avm2/has_own_property", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/has_own_property/Test.as b/core/tests/swfs/avm2/has_own_property/Test.as new file mode 100644 index 000000000000..c7878fe5a415 --- /dev/null +++ b/core/tests/swfs/avm2/has_own_property/Test.as @@ -0,0 +1,177 @@ +package { + public class Test {} +} + +class ES4Class extends Object { + public var test_var = "var"; + public const test_const = "const"; + + public function test_function() { + trace("test_function"); + } + + public function get test_virt() { + return "test_virt"; + } + + public function set test_virt(val) { + trace("test_virt"); + } + + public static var test_static_var = "var"; + public static const test_static_const = "const"; + + public static function test_static_function() { + trace("test_static_function"); + } + + public static function get test_static_virt() { + return "test_static_virt"; + } + + public static function set test_static_virt(val) { + trace("test_static_virt"); + } + + private var test_private_var = "private_var"; + private const test_private_const = "private_const"; + + private function test_private_function() { + trace("test_private_function"); + } + + private function get test_private_virt() { + return "test_private_virt"; + } + + private function set test_private_virt(val) { + trace("test_private_virt"); + } +} + +function ES3Class() { + this.test_var = "var"; +} + +ES3Class.test_static_var = "var"; + +ES3Class.test_static_function = function () { + trace("test_static_function"); +} + +ES3Class.prototype.test_function = function() { + trace("test_function"); +} + +ES3Class.prototype.test_proto = "proto_var"; + +var es4inst = new ES4Class(); +var es3inst = new ES3Class(); + +trace("//es4inst.hasOwnProperty('test_var')"); +trace(es4inst.hasOwnProperty('test_var')); +trace("//es4inst.hasOwnProperty('test_const')"); +trace(es4inst.hasOwnProperty('test_const')); +trace("//es4inst.hasOwnProperty('test_function')"); +trace(es4inst.hasOwnProperty('test_function')); +trace("//es4inst.hasOwnProperty('test_virt')"); +trace(es4inst.hasOwnProperty('test_virt')); +trace("//es4inst.hasOwnProperty('test_static_var')"); +trace(es4inst.hasOwnProperty('test_static_var')); +trace("//es4inst.hasOwnProperty('test_static_const')"); +trace(es4inst.hasOwnProperty('test_static_const')); +trace("//es4inst.hasOwnProperty('test_static_function')"); +trace(es4inst.hasOwnProperty('test_static_function')); +trace("//es4inst.hasOwnProperty('test_static_virt')"); +trace(es4inst.hasOwnProperty('test_static_virt')); +trace("//es4inst.hasOwnProperty('test_private_var')"); +trace(es4inst.hasOwnProperty('test_private_var')); +trace("//es4inst.hasOwnProperty('test_private_const')"); +trace(es4inst.hasOwnProperty('test_private_const')); +trace("//es4inst.hasOwnProperty('test_private_function')"); +trace(es4inst.hasOwnProperty('test_private_function')); +trace("//es4inst.hasOwnProperty('test_private_virt')"); +trace(es4inst.hasOwnProperty('test_private_virt')); + +trace("//ES4Class.hasOwnProperty('test_var')"); +trace(ES4Class.hasOwnProperty('test_var')); +trace("//ES4Class.hasOwnProperty('test_const')"); +trace(ES4Class.hasOwnProperty('test_const')); +trace("//ES4Class.hasOwnProperty('test_function')"); +trace(ES4Class.hasOwnProperty('test_function')); +trace("//ES4Class.hasOwnProperty('test_virt')"); +trace(ES4Class.hasOwnProperty('test_virt')); +trace("//ES4Class.hasOwnProperty('test_static_var')"); +trace(ES4Class.hasOwnProperty('test_static_var')); +trace("//ES4Class.hasOwnProperty('test_static_const')"); +trace(ES4Class.hasOwnProperty('test_static_const')); +trace("//ES4Class.hasOwnProperty('test_static_function')"); +trace(ES4Class.hasOwnProperty('test_static_function')); +trace("//ES4Class.hasOwnProperty('test_static_virt')"); +trace(ES4Class.hasOwnProperty('test_static_virt')); +trace("//ES4Class.hasOwnProperty('test_private_var')"); +trace(ES4Class.hasOwnProperty('test_private_var')); +trace("//ES4Class.hasOwnProperty('test_private_const')"); +trace(ES4Class.hasOwnProperty('test_private_const')); +trace("//ES4Class.hasOwnProperty('test_private_function')"); +trace(ES4Class.hasOwnProperty('test_private_function')); +trace("//ES4Class.hasOwnProperty('test_private_virt')"); +trace(ES4Class.hasOwnProperty('test_private_virt')); + +trace("//ES4Class.prototype.hasOwnProperty('test_var')"); +trace(ES4Class.prototype.hasOwnProperty('test_var')); +trace("//ES4Class.prototype.hasOwnProperty('test_const')"); +trace(ES4Class.prototype.hasOwnProperty('test_const')); +trace("//ES4Class.prototype.hasOwnProperty('test_function')"); +trace(ES4Class.prototype.hasOwnProperty('test_function')); +trace("//ES4Class.prototype.hasOwnProperty('test_virt')"); +trace(ES4Class.prototype.hasOwnProperty('test_virt')); +trace("//ES4Class.prototype.hasOwnProperty('test_static_var')"); +trace(ES4Class.prototype.hasOwnProperty('test_static_var')); +trace("//ES4Class.prototype.hasOwnProperty('test_static_const')"); +trace(ES4Class.prototype.hasOwnProperty('test_static_const')); +trace("//ES4Class.prototype.hasOwnProperty('test_static_function')"); +trace(ES4Class.prototype.hasOwnProperty('test_static_function')); +trace("//ES4Class.prototype.hasOwnProperty('test_static_virt')"); +trace(ES4Class.prototype.hasOwnProperty('test_static_virt')); +trace("//ES4Class.prototype.hasOwnProperty('test_private_var')"); +trace(ES4Class.prototype.hasOwnProperty('test_private_var')); +trace("//ES4Class.prototype.hasOwnProperty('test_private_const')"); +trace(ES4Class.prototype.hasOwnProperty('test_private_const')); +trace("//ES4Class.prototype.hasOwnProperty('test_private_function')"); +trace(ES4Class.prototype.hasOwnProperty('test_private_function')); +trace("//ES4Class.prototype.hasOwnProperty('test_private_virt')"); +trace(ES4Class.prototype.hasOwnProperty('test_private_virt')); + +trace("//es3inst.hasOwnProperty('test_var')"); +trace(es3inst.hasOwnProperty('test_var')); +trace("//es3inst.hasOwnProperty('test_function')"); +trace(es3inst.hasOwnProperty('test_function')); +trace("//es3inst.hasOwnProperty('test_proto')"); +trace(es3inst.hasOwnProperty('test_proto')); +trace("//es3inst.hasOwnProperty('test_static_var')"); +trace(es3inst.hasOwnProperty('test_static_var')); +trace("//es3inst.hasOwnProperty('test_static_function')"); +trace(es3inst.hasOwnProperty('test_static_function')); + +trace("//ES3Class.hasOwnProperty('test_var')"); +trace(ES3Class.hasOwnProperty('test_var')); +trace("//ES3Class.hasOwnProperty('test_function')"); +trace(ES3Class.hasOwnProperty('test_function')); +trace("//ES3Class.hasOwnProperty('test_proto')"); +trace(ES3Class.hasOwnProperty('test_proto')); +trace("//ES3Class.hasOwnProperty('test_static_var')"); +trace(ES3Class.hasOwnProperty('test_static_var')); +trace("//ES3Class.hasOwnProperty('test_static_function')"); +trace(ES3Class.hasOwnProperty('test_static_function')); + +trace("//ES3Class.prototype.hasOwnProperty('test_var')"); +trace(ES3Class.prototype.hasOwnProperty('test_var')); +trace("//ES3Class.prototype.hasOwnProperty('test_function')"); +trace(ES3Class.prototype.hasOwnProperty('test_function')); +trace("//ES3Class.prototype.hasOwnProperty('test_proto')"); +trace(ES3Class.prototype.hasOwnProperty('test_proto')); +trace("//ES3Class.prototype.hasOwnProperty('test_static_var')"); +trace(ES3Class.prototype.hasOwnProperty('test_static_var')); +trace("//ES3Class.prototype.hasOwnProperty('test_static_function')"); +trace(ES3Class.prototype.hasOwnProperty('test_static_function')); \ No newline at end of file diff --git a/core/tests/swfs/avm2/has_own_property/output.txt b/core/tests/swfs/avm2/has_own_property/output.txt new file mode 100644 index 000000000000..5123fe445f3f --- /dev/null +++ b/core/tests/swfs/avm2/has_own_property/output.txt @@ -0,0 +1,102 @@ +//es4inst.hasOwnProperty('test_var') +true +//es4inst.hasOwnProperty('test_const') +true +//es4inst.hasOwnProperty('test_function') +true +//es4inst.hasOwnProperty('test_virt') +true +//es4inst.hasOwnProperty('test_static_var') +false +//es4inst.hasOwnProperty('test_static_const') +false +//es4inst.hasOwnProperty('test_static_function') +false +//es4inst.hasOwnProperty('test_static_virt') +false +//es4inst.hasOwnProperty('test_private_var') +false +//es4inst.hasOwnProperty('test_private_const') +false +//es4inst.hasOwnProperty('test_private_function') +false +//es4inst.hasOwnProperty('test_private_virt') +false +//ES4Class.hasOwnProperty('test_var') +false +//ES4Class.hasOwnProperty('test_const') +false +//ES4Class.hasOwnProperty('test_function') +false +//ES4Class.hasOwnProperty('test_virt') +false +//ES4Class.hasOwnProperty('test_static_var') +true +//ES4Class.hasOwnProperty('test_static_const') +true +//ES4Class.hasOwnProperty('test_static_function') +true +//ES4Class.hasOwnProperty('test_static_virt') +true +//ES4Class.hasOwnProperty('test_private_var') +false +//ES4Class.hasOwnProperty('test_private_const') +false +//ES4Class.hasOwnProperty('test_private_function') +false +//ES4Class.hasOwnProperty('test_private_virt') +false +//ES4Class.prototype.hasOwnProperty('test_var') +false +//ES4Class.prototype.hasOwnProperty('test_const') +false +//ES4Class.prototype.hasOwnProperty('test_function') +false +//ES4Class.prototype.hasOwnProperty('test_virt') +false +//ES4Class.prototype.hasOwnProperty('test_static_var') +false +//ES4Class.prototype.hasOwnProperty('test_static_const') +false +//ES4Class.prototype.hasOwnProperty('test_static_function') +false +//ES4Class.prototype.hasOwnProperty('test_static_virt') +false +//ES4Class.prototype.hasOwnProperty('test_private_var') +false +//ES4Class.prototype.hasOwnProperty('test_private_const') +false +//ES4Class.prototype.hasOwnProperty('test_private_function') +false +//ES4Class.prototype.hasOwnProperty('test_private_virt') +false +//es3inst.hasOwnProperty('test_var') +true +//es3inst.hasOwnProperty('test_function') +false +//es3inst.hasOwnProperty('test_proto') +false +//es3inst.hasOwnProperty('test_static_var') +false +//es3inst.hasOwnProperty('test_static_function') +false +//ES3Class.hasOwnProperty('test_var') +false +//ES3Class.hasOwnProperty('test_function') +false +//ES3Class.hasOwnProperty('test_proto') +false +//ES3Class.hasOwnProperty('test_static_var') +true +//ES3Class.hasOwnProperty('test_static_function') +true +//ES3Class.prototype.hasOwnProperty('test_var') +false +//ES3Class.prototype.hasOwnProperty('test_function') +true +//ES3Class.prototype.hasOwnProperty('test_proto') +true +//ES3Class.prototype.hasOwnProperty('test_static_var') +false +//ES3Class.prototype.hasOwnProperty('test_static_function') +false diff --git a/core/tests/swfs/avm2/has_own_property/test.fla b/core/tests/swfs/avm2/has_own_property/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..9117e9f0beb567f07719301e8775ac7b9d90c7dc GIT binary patch literal 3983 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/has_own_property/test.swf b/core/tests/swfs/avm2/has_own_property/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..bc27d08317d047bb69db889bca12ee2b6b391043 GIT binary patch literal 2422 zcmV-+35oVYS5qrL8UO%zoSj%{TiaF^)|D*FvH{x|3+%8a24ir%1d^tZHY8+&HYF)Z zOHv#xjKE5~#S$c>Tbk~BpUyt>%uHu}X5aom{sQx1=5N&b1(|asT`vv6W_S>K@B5x} z@44^2mM*@_@GA`C{)SIhWef;?G>28J@@N5P`$^RVk-015c7WPqS;SF%fIV_c%QJ)L1)=kKg)I?v}|lm)k-;eTfA7@QkB`9TJrDeiQTl( z-LN&~vbVl%6xFrc$HDX69qpi>EkV&HRY-&T1m2&Un_8YNt|`mq&Gq%1vYaWFm1WG< zmbE+U+OoWrKeC*awUy$X0!*@4Qp)=F^t81iyk3?w%1~C|bGll9Maqegtrzv}l47Ej>cSb#P&{sx7K7my?i4OrgN=siQimfQjnYVy z9K)b+B+?{|x)zQMH!A|H3`fSBMAeon968)ttW{~Ewdh*G(H2WF>TVWX_gM%>Vl4(+ zl_y&aua(}+nfgzZu`=|dzsLRvH;b~W9cva>D|4jjz|`Bl!|FFhti|A(w=IP?c-vYv zM(s_b8#YF~)o82wBdx~Q%5UmVk0L{ z>G;0WtXWO(JFzyoXZByKF~@!6HSD@)_n&9Y^2Gi#Yt!6^w~c$!c%4l&d7VX?1T{Q` z_KY-Yv))X5gw=1GL~F4PFGqTc*kev(&7x`@hMR?2vx+v0tDjb~#o$^Q@w$vw9v_pf z$JYi)ww`3o`dI53_4A+JcUrB;K6(_5)gMKttRp=eO1R7;-lneRG@p5%l`|Pd)6`Wp zr|R4EjH_t3^kS*aY$@e(v8>U?mQv8Q4y$ojSIPxBXQi#fSy3r#PP4V46!X>?8@b}D zoKx?~Sf*iSyw`h$*_=HiWzudk4}2)7h~!y-cXnIOjap($ZKooaK#rhWwoRq zddjA#Z@ItIMSH_&-Y064cAt{noc_`O=YNvr!xHK7@SQzfe3vvN9qJkG9&#jtUEPEq z=5IP~NjXPeDo8~~NxChS9h#&|o6?r!j^nOl+i_2NU3x=$)A5${wsfC3-XX!^-~$qT zmjvGB@h~q& ziFgbE5j}SCBPepjF8Dqng77I^pTYGxTwf5s`IQV_taazCyA3WLk^Md(0WJr&L~J?X za$&0tTkTA(mkzkxCgun$vd|63iZ;gW#Qr40+wtopJ7GkDWt;-XxbS+`8$uT_dhAt)Iv9plw^>X&kP8)d6JG>0*;R$rk*%;!(@0Ggn zkKaI~%|=*1!xBO|9>pprQ&KdrAd$+Y#XpgccPNqT{(6hCqg(I zx03*9gQe$PJBgh+24en2fZ|zD13QUtNtp59a6^{+TQW+d8=h!S)&l`{4CsXLo}EM` zkm`ZKdPpS*ihv!vKc)y=*tO8 z02LP~PNOnV+M%ytr33nMv8;{9zPx5%-byEA1Tn)08Ev$$F!VJF$^a@6P}*r<JU`6HU6r$` zkyQVDKO_4q=V2+2;!<8nN$GIZe{PguYYymV5X>8Nw?)4I^l=0)8g#&-F9Q86f|m?B zXweHmPa(Ku&|!XDmkoN2=WF^Jt+V^hoETC)RPj>^9XJm zH1(tm^a}_ygQlM7K);A!)1av*TR>k#aL1sjC)+^3gy0_1u%)OguY<6F;0;TlzPt&- zB?NC-0(ItX5H2IQPb`I+^9~Tvmk~Xn5;f>ulok=aMLg{nf#Li9#CD zH>N-b{}l={qFNd`wP*M>6nM$;P zzo4{^=&w|wCHxJg4McyZ5-s8%C}k1-lS){|If*>@&%fb;>?K5WnlGhJLYyu)JZRMl zml~TNV Date: Sat, 7 Mar 2020 15:06:31 -0500 Subject: [PATCH 123/189] `callproperty` and `callpropvoid` should *never* get callables from `base_proto`. --- core/src/avm2.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 874aa8b9ab0b..4b27b8fc4190 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -676,14 +676,13 @@ impl<'gc> Avm2<'gc> { ) -> Result<(), Error> { let args = self.pop_args(arg_count); let multiname = self.pool_multiname(index)?; - let receiver = self.pop().as_object()?; + let mut receiver = self.pop().as_object()?; let name: Result = receiver .resolve_multiname(&multiname)? .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); let name = name?; let base_proto = receiver.get_base_proto(&name)?; - let function = base_proto - .unwrap_or(receiver) + let function = receiver .get_property(receiver, &name, self, context)? .resolve(self, context)? .as_object()?; @@ -725,14 +724,13 @@ impl<'gc> Avm2<'gc> { ) -> Result<(), Error> { let args = self.pop_args(arg_count); let multiname = self.pool_multiname(index)?; - let receiver = self.pop().as_object()?; + let mut receiver = self.pop().as_object()?; let name: Result = receiver .resolve_multiname(&multiname)? .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); let name = name?; let base_proto = receiver.get_base_proto(&name)?; - let function = base_proto - .unwrap_or(receiver) + let function = receiver .get_property(receiver, &name, self, context)? .resolve(self, context)? .as_object()?; From a0ca5891e473ed0798e32b2013322b6764e69d80 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 7 Mar 2020 16:01:03 -0500 Subject: [PATCH 124/189] Prevent instance traits from being accessible directly from prototypes. --- core/src/avm2/function.rs | 12 +++- core/src/avm2/object.rs | 16 +++++- core/src/avm2/script_object.rs | 101 +++++++++++++++++++++++++-------- 3 files changed, 101 insertions(+), 28 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 44f8651e41b0..098cee53370f 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -515,6 +515,14 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_trait(name) } + fn get_provided_trait( + &self, + name: &QName, + known_traits: &mut Vec, + ) -> Result<(), Error> { + self.0.read().base.get_provided_trait(name, known_traits) + } + fn get_scope(self) -> Option>> { self.0.read().base.get_scope() } @@ -539,8 +547,8 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.has_trait(name) } - fn has_own_trait(self, name: &QName) -> Result { - self.0.read().base.has_own_trait(name) + fn provides_trait(self, name: &QName) -> Result { + self.0.read().base.provides_trait(name) } fn has_instantiated_property(self, name: &QName) -> bool { diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 1b2743495fc1..37cd6bf23f6e 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -67,7 +67,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// This function returns `None` for non-trait properties, such as actually /// defined prototype methods for ES3-style classes. fn get_base_proto(self, name: &QName) -> Result>, Error> { - if self.has_own_trait(name)? { + if self.provides_trait(name)? { return Ok(Some(self.into())); } @@ -215,6 +215,18 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// malformed in some way. fn get_trait(self, name: &QName) -> Result, Error>; + /// Populate a list of traits that this object provides. + /// + /// This function yields traits for class constructors and prototypes, but + /// not instances. For resolving traits for normal `TObject` methods, use + /// `get_trait` and `has_trait` as it will tell you if the current object + /// has a given trait. + fn get_provided_trait( + &self, + name: &QName, + known_traits: &mut Vec, + ) -> Result<(), Error>; + /// Retrieves the scope chain of the object at time of it's creation. /// /// The scope chain is used to determine the starting scope stack when an @@ -297,7 +309,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Returns true if an object is part of a class that defines a trait of a /// given name on itself (as opposed to merely inheriting a superclass /// trait.) - fn has_own_trait(self, name: &QName) -> Result; + fn provides_trait(self, name: &QName) -> Result; /// Indicates whether or not a property or *instantiated* trait exists on /// an object and is not part of the prototype chain. diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 8281269af35e..b0700fa5c142 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -146,6 +146,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_trait(name) } + fn get_provided_trait( + &self, + name: &QName, + known_traits: &mut Vec, + ) -> Result<(), Error> { + self.0.read().get_provided_trait(name, known_traits) + } + fn get_scope(self) -> Option>> { self.0.read().get_scope() } @@ -170,8 +178,8 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().has_trait(name) } - fn has_own_trait(self, name: &QName) -> Result { - self.0.read().has_own_trait(name) + fn provides_trait(self, name: &QName) -> Result { + self.0.read().provides_trait(name) } fn has_instantiated_property(self, name: &QName) -> bool { @@ -534,39 +542,84 @@ impl<'gc> ScriptObjectData<'gc> { } pub fn get_trait(&self, name: &QName) -> Result, Error> { - let mut known_traits = if let Some(proto) = self.proto { - proto.get_trait(name)? - } else { - vec![] - }; + match &self.class { + //Class constructors have local traits only. + ScriptObjectClass::ClassConstructor(..) => { + let mut known_traits = Vec::new(); + self.get_provided_trait(name, &mut known_traits)?; + + Ok(known_traits) + } + + //Prototypes do not have traits available locally, but they provide + //traits instead. + ScriptObjectClass::InstancePrototype(..) => Ok(Vec::new()), + + //Instances walk the prototype chain to build a list of known + //traits provided by the classes attached to those prototypes. + ScriptObjectClass::NoClass => { + let mut known_traits = Vec::new(); + let mut chain = Vec::new(); + let mut proto = self.proto(); + + while let Some(p) = proto { + chain.push(p); + proto = p.proto(); + } + + for proto in chain.iter().rev() { + proto.get_provided_trait(name, &mut known_traits)?; + } + + Ok(known_traits) + } + } + } + pub fn get_provided_trait( + &self, + name: &QName, + known_traits: &mut Vec, + ) -> Result<(), Error> { match &self.class { ScriptObjectClass::ClassConstructor(class, ..) => { - do_trait_lookup(name, &mut known_traits, class.abc(), &class.class().traits)? + do_trait_lookup(name, known_traits, class.abc(), &class.class().traits) } - ScriptObjectClass::InstancePrototype(class, ..) => do_trait_lookup( - name, - &mut known_traits, - class.abc(), - &class.instance().traits, - )?, - ScriptObjectClass::NoClass => {} - }; - - Ok(known_traits) + ScriptObjectClass::InstancePrototype(class, ..) => { + do_trait_lookup(name, known_traits, class.abc(), &class.instance().traits) + } + ScriptObjectClass::NoClass => Ok(()), + } } pub fn has_trait(&self, name: &QName) -> Result { - if let Some(proto) = self.proto { - if proto.has_trait(name)? { - return Ok(true); + match &self.class { + //Class constructors have local traits only. + ScriptObjectClass::ClassConstructor(..) => self.provides_trait(name), + + //Prototypes do not have traits available locally, but we walk + //through them to find traits (see `provides_trait`) + ScriptObjectClass::InstancePrototype(..) => Ok(false), + + //Instances walk the prototype chain to build a list of known + //traits provided by the classes attached to those prototypes. + ScriptObjectClass::NoClass => { + let mut proto = self.proto(); + + while let Some(p) = proto { + if p.provides_trait(name)? { + return Ok(true); + } + + proto = p.proto(); + } + + Ok(false) } } - - self.has_own_trait(name) } - pub fn has_own_trait(&self, name: &QName) -> Result { + pub fn provides_trait(&self, name: &QName) -> Result { match &self.class { ScriptObjectClass::ClassConstructor(class, ..) => { for trait_entry in class.class().traits.iter() { From 2afbcf450a2413a96809e86348f2020780119230 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 7 Mar 2020 20:16:48 -0500 Subject: [PATCH 125/189] Impl `propertyIsEnumerable` --- core/src/avm2/function.rs | 4 ++++ core/src/avm2/globals/object.rs | 28 ++++++++++++++++++++++++++++ core/src/avm2/object.rs | 5 +++++ core/src/avm2/script_object.rs | 8 ++++++++ 4 files changed, 45 insertions(+) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 098cee53370f..6e8b9f01b8a9 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -571,6 +571,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_enumerant_name(index) } + fn property_is_enumerable(&self, name: &QName) -> bool { + self.0.read().base.property_is_enumerable(name) + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 7d0c944a3d48..74677f7ebc4a 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -64,6 +64,28 @@ pub fn is_prototype_of<'gc>( Ok(false.into()) } +/// `Object.prototype.propertyIsEnumerable` +pub fn property_is_enumerable<'gc>( + _avm: &mut Avm2<'gc>, + _context: &mut UpdateContext<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + let this: Result, Error> = this.ok_or_else(|| "No valid this parameter".into()); + let this = this?; + let name: Result<&Value<'gc>, Error> = args.get(0).ok_or_else(|| "No name specified".into()); + let name = name?.as_string()?; + + if let Some(ns) = this.resolve_any(&name)? { + if !ns.is_private() { + let qname = QName::new(ns, &name); + return Ok(this.property_is_enumerable(&qname).into()); + } + } + + Ok(false.into()) +} + /// Partially construct `Object.prototype`. /// /// `__proto__` and other cross-linked properties of this object will *not* @@ -90,4 +112,10 @@ pub fn fill_proto<'gc>( 0, FunctionObject::from_builtin(gc_context, is_prototype_of, fn_proto), ); + object_proto.install_method( + gc_context, + QName::new(Namespace::public_namespace(), "propertyIsEnumerable"), + 0, + FunctionObject::from_builtin(gc_context, property_is_enumerable, fn_proto), + ); } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 37cd6bf23f6e..99a820a58472 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -352,6 +352,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// mechanism. fn get_enumerant_name(&self, index: u32) -> Option; + /// Determine if a property is currently enumerable. + /// + /// Properties that do not exist are also not enumerable. + fn property_is_enumerable(&self, name: &QName) -> bool; + /// Install a method (or any other non-slot value) on an object. fn install_method( &mut self, diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index b0700fa5c142..eb2e14580522 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -202,6 +202,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_enumerant_name(index) } + fn property_is_enumerable(&self, name: &QName) -> bool { + self.0.read().property_is_enumerable(name) + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } @@ -750,6 +754,10 @@ impl<'gc> ScriptObjectData<'gc> { self.enumerants.get(true_index).cloned() } + pub fn property_is_enumerable(&self, name: &QName) -> bool { + self.enumerants.contains(name) + } + /// Install a method into the object. pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) { if disp_id > 0 { From dc0cb00a036d83c29fc1c7dac6881681e199d098 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 7 Mar 2020 20:17:07 -0500 Subject: [PATCH 126/189] Add a test for `propertyIsEnumerable`. --- core/tests/regression_tests.rs | 1 + .../swfs/avm2/property_is_enumerable/Test.as | 177 ++++++++++++++++++ .../avm2/property_is_enumerable/output.txt | 102 ++++++++++ .../swfs/avm2/property_is_enumerable/test.fla | Bin 0 -> 3983 bytes .../swfs/avm2/property_is_enumerable/test.swf | Bin 0 -> 2431 bytes 5 files changed, 280 insertions(+) create mode 100644 core/tests/swfs/avm2/property_is_enumerable/Test.as create mode 100644 core/tests/swfs/avm2/property_is_enumerable/output.txt create mode 100644 core/tests/swfs/avm2/property_is_enumerable/test.fla create mode 100644 core/tests/swfs/avm2/property_is_enumerable/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index eb2f5131182f..191809e254f5 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -242,6 +242,7 @@ swf_tests! { (as3_class_enumeration, "avm2/class_enumeration", 1), (as3_is_prototype_of, "avm2/is_prototype_of", 1), (as3_has_own_property, "avm2/has_own_property", 1), + (as3_property_is_enumerable, "avm2/property_is_enumerable", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/property_is_enumerable/Test.as b/core/tests/swfs/avm2/property_is_enumerable/Test.as new file mode 100644 index 000000000000..505dc6be8709 --- /dev/null +++ b/core/tests/swfs/avm2/property_is_enumerable/Test.as @@ -0,0 +1,177 @@ +package { + public class Test {} +} + +class ES4Class extends Object { + public var test_var = "var"; + public const test_const = "const"; + + public function test_function() { + trace("test_function"); + } + + public function get test_virt() { + return "test_virt"; + } + + public function set test_virt(val) { + trace("test_virt"); + } + + public static var test_static_var = "var"; + public static const test_static_const = "const"; + + public static function test_static_function() { + trace("test_static_function"); + } + + public static function get test_static_virt() { + return "test_static_virt"; + } + + public static function set test_static_virt(val) { + trace("test_static_virt"); + } + + private var test_private_var = "private_var"; + private const test_private_const = "private_const"; + + private function test_private_function() { + trace("test_private_function"); + } + + private function get test_private_virt() { + return "test_private_virt"; + } + + private function set test_private_virt(val) { + trace("test_private_virt"); + } +} + +function ES3Class() { + this.test_var = "var"; +} + +ES3Class.test_static_var = "var"; + +ES3Class.test_static_function = function () { + trace("test_static_function"); +} + +ES3Class.prototype.test_function = function() { + trace("test_function"); +} + +ES3Class.prototype.test_proto = "proto_var"; + +var es4inst = new ES4Class(); +var es3inst = new ES3Class(); + +trace("//es4inst.propertyIsEnumerable('test_var')"); +trace(es4inst.propertyIsEnumerable('test_var')); +trace("//es4inst.propertyIsEnumerable('test_const')"); +trace(es4inst.propertyIsEnumerable('test_const')); +trace("//es4inst.propertyIsEnumerable('test_function')"); +trace(es4inst.propertyIsEnumerable('test_function')); +trace("//es4inst.propertyIsEnumerable('test_virt')"); +trace(es4inst.propertyIsEnumerable('test_virt')); +trace("//es4inst.propertyIsEnumerable('test_static_var')"); +trace(es4inst.propertyIsEnumerable('test_static_var')); +trace("//es4inst.propertyIsEnumerable('test_static_const')"); +trace(es4inst.propertyIsEnumerable('test_static_const')); +trace("//es4inst.propertyIsEnumerable('test_static_function')"); +trace(es4inst.propertyIsEnumerable('test_static_function')); +trace("//es4inst.propertyIsEnumerable('test_static_virt')"); +trace(es4inst.propertyIsEnumerable('test_static_virt')); +trace("//es4inst.propertyIsEnumerable('test_private_var')"); +trace(es4inst.propertyIsEnumerable('test_private_var')); +trace("//es4inst.propertyIsEnumerable('test_private_const')"); +trace(es4inst.propertyIsEnumerable('test_private_const')); +trace("//es4inst.propertyIsEnumerable('test_private_function')"); +trace(es4inst.propertyIsEnumerable('test_private_function')); +trace("//es4inst.propertyIsEnumerable('test_private_virt')"); +trace(es4inst.propertyIsEnumerable('test_private_virt')); + +trace("//ES4Class.propertyIsEnumerable('test_var')"); +trace(ES4Class.propertyIsEnumerable('test_var')); +trace("//ES4Class.propertyIsEnumerable('test_const')"); +trace(ES4Class.propertyIsEnumerable('test_const')); +trace("//ES4Class.propertyIsEnumerable('test_function')"); +trace(ES4Class.propertyIsEnumerable('test_function')); +trace("//ES4Class.propertyIsEnumerable('test_virt')"); +trace(ES4Class.propertyIsEnumerable('test_virt')); +trace("//ES4Class.propertyIsEnumerable('test_static_var')"); +trace(ES4Class.propertyIsEnumerable('test_static_var')); +trace("//ES4Class.propertyIsEnumerable('test_static_const')"); +trace(ES4Class.propertyIsEnumerable('test_static_const')); +trace("//ES4Class.propertyIsEnumerable('test_static_function')"); +trace(ES4Class.propertyIsEnumerable('test_static_function')); +trace("//ES4Class.propertyIsEnumerable('test_static_virt')"); +trace(ES4Class.propertyIsEnumerable('test_static_virt')); +trace("//ES4Class.propertyIsEnumerable('test_private_var')"); +trace(ES4Class.propertyIsEnumerable('test_private_var')); +trace("//ES4Class.propertyIsEnumerable('test_private_const')"); +trace(ES4Class.propertyIsEnumerable('test_private_const')); +trace("//ES4Class.propertyIsEnumerable('test_private_function')"); +trace(ES4Class.propertyIsEnumerable('test_private_function')); +trace("//ES4Class.propertyIsEnumerable('test_private_virt')"); +trace(ES4Class.propertyIsEnumerable('test_private_virt')); + +trace("//ES4Class.prototype.propertyIsEnumerable('test_var')"); +trace(ES4Class.prototype.propertyIsEnumerable('test_var')); +trace("//ES4Class.prototype.propertyIsEnumerable('test_const')"); +trace(ES4Class.prototype.propertyIsEnumerable('test_const')); +trace("//ES4Class.prototype.propertyIsEnumerable('test_function')"); +trace(ES4Class.prototype.propertyIsEnumerable('test_function')); +trace("//ES4Class.prototype.propertyIsEnumerable('test_virt')"); +trace(ES4Class.prototype.propertyIsEnumerable('test_virt')); +trace("//ES4Class.prototype.propertyIsEnumerable('test_static_var')"); +trace(ES4Class.prototype.propertyIsEnumerable('test_static_var')); +trace("//ES4Class.prototype.propertyIsEnumerable('test_static_const')"); +trace(ES4Class.prototype.propertyIsEnumerable('test_static_const')); +trace("//ES4Class.prototype.propertyIsEnumerable('test_static_function')"); +trace(ES4Class.prototype.propertyIsEnumerable('test_static_function')); +trace("//ES4Class.prototype.propertyIsEnumerable('test_static_virt')"); +trace(ES4Class.prototype.propertyIsEnumerable('test_static_virt')); +trace("//ES4Class.prototype.propertyIsEnumerable('test_private_var')"); +trace(ES4Class.prototype.propertyIsEnumerable('test_private_var')); +trace("//ES4Class.prototype.propertyIsEnumerable('test_private_const')"); +trace(ES4Class.prototype.propertyIsEnumerable('test_private_const')); +trace("//ES4Class.prototype.propertyIsEnumerable('test_private_function')"); +trace(ES4Class.prototype.propertyIsEnumerable('test_private_function')); +trace("//ES4Class.prototype.propertyIsEnumerable('test_private_virt')"); +trace(ES4Class.prototype.propertyIsEnumerable('test_private_virt')); + +trace("//es3inst.propertyIsEnumerable('test_var')"); +trace(es3inst.propertyIsEnumerable('test_var')); +trace("//es3inst.propertyIsEnumerable('test_function')"); +trace(es3inst.propertyIsEnumerable('test_function')); +trace("//es3inst.propertyIsEnumerable('test_proto')"); +trace(es3inst.propertyIsEnumerable('test_proto')); +trace("//es3inst.propertyIsEnumerable('test_static_var')"); +trace(es3inst.propertyIsEnumerable('test_static_var')); +trace("//es3inst.propertyIsEnumerable('test_static_function')"); +trace(es3inst.propertyIsEnumerable('test_static_function')); + +trace("//ES3Class.propertyIsEnumerable('test_var')"); +trace(ES3Class.propertyIsEnumerable('test_var')); +trace("//ES3Class.propertyIsEnumerable('test_function')"); +trace(ES3Class.propertyIsEnumerable('test_function')); +trace("//ES3Class.propertyIsEnumerable('test_proto')"); +trace(ES3Class.propertyIsEnumerable('test_proto')); +trace("//ES3Class.propertyIsEnumerable('test_static_var')"); +trace(ES3Class.propertyIsEnumerable('test_static_var')); +trace("//ES3Class.propertyIsEnumerable('test_static_function')"); +trace(ES3Class.propertyIsEnumerable('test_static_function')); + +trace("//ES3Class.prototype.propertyIsEnumerable('test_var')"); +trace(ES3Class.prototype.propertyIsEnumerable('test_var')); +trace("//ES3Class.prototype.propertyIsEnumerable('test_function')"); +trace(ES3Class.prototype.propertyIsEnumerable('test_function')); +trace("//ES3Class.prototype.propertyIsEnumerable('test_proto')"); +trace(ES3Class.prototype.propertyIsEnumerable('test_proto')); +trace("//ES3Class.prototype.propertyIsEnumerable('test_static_var')"); +trace(ES3Class.prototype.propertyIsEnumerable('test_static_var')); +trace("//ES3Class.prototype.propertyIsEnumerable('test_static_function')"); +trace(ES3Class.prototype.propertyIsEnumerable('test_static_function')); \ No newline at end of file diff --git a/core/tests/swfs/avm2/property_is_enumerable/output.txt b/core/tests/swfs/avm2/property_is_enumerable/output.txt new file mode 100644 index 000000000000..67ee3c59684d --- /dev/null +++ b/core/tests/swfs/avm2/property_is_enumerable/output.txt @@ -0,0 +1,102 @@ +//es4inst.propertyIsEnumerable('test_var') +false +//es4inst.propertyIsEnumerable('test_const') +false +//es4inst.propertyIsEnumerable('test_function') +false +//es4inst.propertyIsEnumerable('test_virt') +false +//es4inst.propertyIsEnumerable('test_static_var') +false +//es4inst.propertyIsEnumerable('test_static_const') +false +//es4inst.propertyIsEnumerable('test_static_function') +false +//es4inst.propertyIsEnumerable('test_static_virt') +false +//es4inst.propertyIsEnumerable('test_private_var') +false +//es4inst.propertyIsEnumerable('test_private_const') +false +//es4inst.propertyIsEnumerable('test_private_function') +false +//es4inst.propertyIsEnumerable('test_private_virt') +false +//ES4Class.propertyIsEnumerable('test_var') +false +//ES4Class.propertyIsEnumerable('test_const') +false +//ES4Class.propertyIsEnumerable('test_function') +false +//ES4Class.propertyIsEnumerable('test_virt') +false +//ES4Class.propertyIsEnumerable('test_static_var') +false +//ES4Class.propertyIsEnumerable('test_static_const') +false +//ES4Class.propertyIsEnumerable('test_static_function') +false +//ES4Class.propertyIsEnumerable('test_static_virt') +false +//ES4Class.propertyIsEnumerable('test_private_var') +false +//ES4Class.propertyIsEnumerable('test_private_const') +false +//ES4Class.propertyIsEnumerable('test_private_function') +false +//ES4Class.propertyIsEnumerable('test_private_virt') +false +//ES4Class.prototype.propertyIsEnumerable('test_var') +false +//ES4Class.prototype.propertyIsEnumerable('test_const') +false +//ES4Class.prototype.propertyIsEnumerable('test_function') +false +//ES4Class.prototype.propertyIsEnumerable('test_virt') +false +//ES4Class.prototype.propertyIsEnumerable('test_static_var') +false +//ES4Class.prototype.propertyIsEnumerable('test_static_const') +false +//ES4Class.prototype.propertyIsEnumerable('test_static_function') +false +//ES4Class.prototype.propertyIsEnumerable('test_static_virt') +false +//ES4Class.prototype.propertyIsEnumerable('test_private_var') +false +//ES4Class.prototype.propertyIsEnumerable('test_private_const') +false +//ES4Class.prototype.propertyIsEnumerable('test_private_function') +false +//ES4Class.prototype.propertyIsEnumerable('test_private_virt') +false +//es3inst.propertyIsEnumerable('test_var') +true +//es3inst.propertyIsEnumerable('test_function') +false +//es3inst.propertyIsEnumerable('test_proto') +false +//es3inst.propertyIsEnumerable('test_static_var') +false +//es3inst.propertyIsEnumerable('test_static_function') +false +//ES3Class.propertyIsEnumerable('test_var') +false +//ES3Class.propertyIsEnumerable('test_function') +false +//ES3Class.propertyIsEnumerable('test_proto') +false +//ES3Class.propertyIsEnumerable('test_static_var') +true +//ES3Class.propertyIsEnumerable('test_static_function') +true +//ES3Class.prototype.propertyIsEnumerable('test_var') +false +//ES3Class.prototype.propertyIsEnumerable('test_function') +true +//ES3Class.prototype.propertyIsEnumerable('test_proto') +true +//ES3Class.prototype.propertyIsEnumerable('test_static_var') +false +//ES3Class.prototype.propertyIsEnumerable('test_static_function') +false diff --git a/core/tests/swfs/avm2/property_is_enumerable/test.fla b/core/tests/swfs/avm2/property_is_enumerable/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..9117e9f0beb567f07719301e8775ac7b9d90c7dc GIT binary patch literal 3983 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/property_is_enumerable/test.swf b/core/tests/swfs/avm2/property_is_enumerable/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..60a6b4460e6d41d76661d13ddc47cce06a6e4a0b GIT binary patch literal 2431 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 literal 0 HcmV?d00001 From f13e2ea3c4ae9da61fbc69cbd3826105f2bd49dc Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 19:12:17 -0400 Subject: [PATCH 127/189] Implement `setPropertyIsEnumerable` --- core/src/avm2/function.rs | 12 ++++++++++ core/src/avm2/globals/object.rs | 33 ++++++++++++++++++++++++++++ core/src/avm2/object.rs | 8 +++++++ core/src/avm2/script_object.rs | 39 +++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 6e8b9f01b8a9..c2d228dd827e 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -575,6 +575,18 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.property_is_enumerable(name) } + fn set_local_property_is_enumerable( + &self, + mc: MutationContext<'gc, '_>, + name: &QName, + is_enumerable: bool, + ) -> Result<(), Error> { + self.0 + .write(mc) + .base + .set_local_property_is_enumerable(name, is_enumerable) + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 74677f7ebc4a..96eb41e3bc27 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -86,6 +86,33 @@ pub fn property_is_enumerable<'gc>( Ok(false.into()) } +/// `Object.prototype.setPropertyIsEnumerable` +pub fn set_property_is_enumerable<'gc>( + _avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + let this: Result, Error> = this.ok_or_else(|| "No valid this parameter".into()); + let this = this?; + let name: Result<&Value<'gc>, Error> = args.get(0).ok_or_else(|| "No name specified".into()); + let name = name?.as_string()?; + let is_enum = args + .get(1) + .cloned() + .unwrap_or(Value::Bool(true)) + .as_bool()?; + + if let Some(ns) = this.resolve_any(&name)? { + if !ns.is_private() { + let qname = QName::new(ns, &name); + this.set_local_property_is_enumerable(context.gc_context, &qname, is_enum)?; + } + } + + Ok(Value::Undefined.into()) +} + /// Partially construct `Object.prototype`. /// /// `__proto__` and other cross-linked properties of this object will *not* @@ -118,4 +145,10 @@ pub fn fill_proto<'gc>( 0, FunctionObject::from_builtin(gc_context, property_is_enumerable, fn_proto), ); + object_proto.install_method( + gc_context, + QName::new(Namespace::public_namespace(), "setPropertyIsEnumerable"), + 0, + FunctionObject::from_builtin(gc_context, set_property_is_enumerable, fn_proto), + ); } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 99a820a58472..db29dd982440 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -357,6 +357,14 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Properties that do not exist are also not enumerable. fn property_is_enumerable(&self, name: &QName) -> bool; + /// Mark a dynamic property on this object as enumerable. + fn set_local_property_is_enumerable( + &self, + mc: MutationContext<'gc, '_>, + name: &QName, + is_enumerable: bool, + ) -> Result<(), Error>; + /// Install a method (or any other non-slot value) on an object. fn install_method( &mut self, diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index eb2e14580522..68410bdbb5fa 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -206,6 +206,17 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().property_is_enumerable(name) } + fn set_local_property_is_enumerable( + &self, + mc: MutationContext<'gc, '_>, + name: &QName, + is_enumerable: bool, + ) -> Result<(), Error> { + self.0 + .write(mc) + .set_local_property_is_enumerable(name, is_enumerable) + } + fn as_ptr(&self) -> *const ObjectPtr { self.0.as_ptr() as *const ObjectPtr } @@ -758,6 +769,34 @@ impl<'gc> ScriptObjectData<'gc> { self.enumerants.contains(name) } + pub fn set_local_property_is_enumerable( + &mut self, + name: &QName, + is_enumerable: bool, + ) -> Result<(), Error> { + if is_enumerable && self.values.contains_key(name) && !self.enumerants.contains(name) { + // Traits are never enumerable + if self.has_trait(name)? { + return Ok(()); + } + + self.enumerants.push(name.clone()); + } else if !is_enumerable && self.enumerants.contains(name) { + let mut index = None; + for (i, other_name) in self.enumerants.iter().enumerate() { + if other_name == name { + index = Some(i); + } + } + + if let Some(index) = index { + self.enumerants.remove(index); + } + } + + Ok(()) + } + /// Install a method into the object. pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) { if disp_id > 0 { From ba2c1f575062be8b1bdfb0e79404290d3198074e Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 19:12:40 -0400 Subject: [PATCH 128/189] Add test for `setPropertyIsEnumerable` --- core/tests/regression_tests.rs | 1 + .../avm2/set_property_is_enumerable/Test.as | 188 ++++++++++++++++++ .../set_property_is_enumerable/output.txt | 85 ++++++++ .../avm2/set_property_is_enumerable/test.fla | Bin 0 -> 3983 bytes .../avm2/set_property_is_enumerable/test.swf | Bin 0 -> 2332 bytes 5 files changed, 274 insertions(+) create mode 100644 core/tests/swfs/avm2/set_property_is_enumerable/Test.as create mode 100644 core/tests/swfs/avm2/set_property_is_enumerable/output.txt create mode 100644 core/tests/swfs/avm2/set_property_is_enumerable/test.fla create mode 100644 core/tests/swfs/avm2/set_property_is_enumerable/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 191809e254f5..6d06fd6f38b3 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -243,6 +243,7 @@ swf_tests! { (as3_is_prototype_of, "avm2/is_prototype_of", 1), (as3_has_own_property, "avm2/has_own_property", 1), (as3_property_is_enumerable, "avm2/property_is_enumerable", 1), + (as3_set_property_is_enumerable, "avm2/set_property_is_enumerable", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/set_property_is_enumerable/Test.as b/core/tests/swfs/avm2/set_property_is_enumerable/Test.as new file mode 100644 index 000000000000..2e6c31cfbe4f --- /dev/null +++ b/core/tests/swfs/avm2/set_property_is_enumerable/Test.as @@ -0,0 +1,188 @@ +package { + public class Test {} +} + +dynamic class ES4Class extends Object { + public var test_var = "var"; + public const test_const = "const"; + + public function test_function() { + trace("test_function"); + } + + public function get test_virt() { + return "test_virt"; + } + + public function set test_virt(val) { + trace("test_virt"); + } + + public static var test_static_var = "var"; + public static const test_static_const = "const"; + + public static function test_static_function() { + trace("test_static_function"); + } + + public static function get test_static_virt() { + return "test_static_virt"; + } + + public static function set test_static_virt(val) { + trace("test_static_virt"); + } + + private var test_private_var = "private_var"; + private const test_private_const = "private_const"; + + private function test_private_function() { + trace("test_private_function"); + } + + private function get test_private_virt() { + return "test_private_virt"; + } + + private function set test_private_virt(val) { + trace("test_private_virt"); + } +} + +function ES3Class() { + this.test_var = "var"; +} + +ES3Class.test_static_var = "var"; + +ES3Class.test_static_function = function () { + trace("test_static_function"); +} + +ES3Class.prototype.test_function = function() { + trace("test_function"); +} + +ES3Class.prototype.test_proto = "proto_var"; + +var es4inst = new ES4Class(); +var es3inst = new ES3Class(); + +trace("//es4inst.propertyIsEnumerable('test_var')"); +trace(es4inst.propertyIsEnumerable('test_var')); +trace("//es4inst.setPropertyIsEnumerable('test_var', true)"); +es4inst.setPropertyIsEnumerable('test_var', true); +trace("//es4inst.propertyIsEnumerable('test_var')"); +trace(es4inst.propertyIsEnumerable('test_var')); + +trace("//es4inst.propertyIsEnumerable('test_const')"); +trace(es4inst.propertyIsEnumerable('test_const')); +trace("//es4inst.setPropertyIsEnumerable('test_const', true)"); +es4inst.setPropertyIsEnumerable('test_const', true); +trace("//es4inst.propertyIsEnumerable('test_const')"); +trace(es4inst.propertyIsEnumerable('test_const')); + +trace("//es4inst.propertyIsEnumerable('test_function')"); +trace(es4inst.propertyIsEnumerable('test_function')); +trace("//es4inst.setPropertyIsEnumerable('test_function', true)"); +es4inst.setPropertyIsEnumerable('test_function', true); +trace("//es4inst.propertyIsEnumerable('test_function')"); +trace(es4inst.propertyIsEnumerable('test_function')); + +trace("//es4inst.propertyIsEnumerable('test_virt')"); +trace(es4inst.propertyIsEnumerable('test_virt')); +trace("//es4inst.setPropertyIsEnumerable('test_virt', true)"); +es4inst.setPropertyIsEnumerable('test_virt', true); +trace("//es4inst.propertyIsEnumerable('test_virt')"); +trace(es4inst.propertyIsEnumerable('test_virt')); + +trace("//es4inst.propertyIsEnumerable('test_private_var')"); +trace(es4inst.propertyIsEnumerable('test_private_var')); +trace("//es4inst.setPropertyIsEnumerable('test_private_var', true)"); +es4inst.setPropertyIsEnumerable('test_private_var', true); +trace("//es4inst.propertyIsEnumerable('test_private_var')"); +trace(es4inst.propertyIsEnumerable('test_private_var')); + +trace("//es4inst.propertyIsEnumerable('test_private_const')"); +trace(es4inst.propertyIsEnumerable('test_private_const')); +trace("//es4inst.setPropertyIsEnumerable('test_private_const', true)"); +es4inst.setPropertyIsEnumerable('test_private_const', true); +trace("//es4inst.propertyIsEnumerable('test_private_const')"); +trace(es4inst.propertyIsEnumerable('test_private_const')); + +trace("//es4inst.propertyIsEnumerable('test_private_function')"); +trace(es4inst.propertyIsEnumerable('test_private_function')); +trace("//es4inst.setPropertyIsEnumerable('test_private_function', true)"); +es4inst.setPropertyIsEnumerable('test_private_function', true); +trace("//es4inst.propertyIsEnumerable('test_private_function')"); +trace(es4inst.propertyIsEnumerable('test_private_function')); + +trace("//es4inst.propertyIsEnumerable('test_private_virt')"); +trace(es4inst.propertyIsEnumerable('test_private_virt')); +trace("//es4inst.setPropertyIsEnumerable('test_private_virt', true)"); +es4inst.setPropertyIsEnumerable('test_private_virt', true); +trace("//es4inst.propertyIsEnumerable('test_private_virt')"); +trace(es4inst.propertyIsEnumerable('test_private_virt')); + +trace("//ES4Class.propertyIsEnumerable('test_static_var')"); +trace(ES4Class.propertyIsEnumerable('test_static_var')); +trace("//ES4Class.setPropertyIsEnumerable('test_static_var', true)"); +ES4Class.setPropertyIsEnumerable('test_static_var', true); +trace("//ES4Class.propertyIsEnumerable('test_static_var')"); +trace(ES4Class.propertyIsEnumerable('test_static_var')); + +trace("//ES4Class.propertyIsEnumerable('test_static_const')"); +trace(ES4Class.propertyIsEnumerable('test_static_const')); +trace("//ES4Class.setPropertyIsEnumerable('test_static_const', true)"); +ES4Class.setPropertyIsEnumerable('test_static_const', true); +trace("//ES4Class.propertyIsEnumerable('test_static_const')"); +trace(ES4Class.propertyIsEnumerable('test_static_const')); + +trace("//ES4Class.propertyIsEnumerable('test_static_function')"); +trace(ES4Class.propertyIsEnumerable('test_static_function')); +trace("//ES4Class.setPropertyIsEnumerable('test_static_function', true)"); +ES4Class.setPropertyIsEnumerable('test_static_function', true); +trace("//ES4Class.propertyIsEnumerable('test_static_function')"); +trace(ES4Class.propertyIsEnumerable('test_static_function')); + +trace("//ES4Class.propertyIsEnumerable('test_static_virt')"); +trace(ES4Class.propertyIsEnumerable('test_static_virt')); +trace("//ES4Class.setPropertyIsEnumerable('test_static_virt', true)"); +ES4Class.setPropertyIsEnumerable('test_static_virt', true); +trace("//ES4Class.propertyIsEnumerable('test_static_virt')"); +trace(ES4Class.propertyIsEnumerable('test_static_virt')); + +trace("//es3inst.propertyIsEnumerable('test_var')"); +trace(es3inst.propertyIsEnumerable('test_var')); +trace("//es3inst.setPropertyIsEnumerable('test_var', false)"); +es3inst.setPropertyIsEnumerable('test_var', false); +trace("//es3inst.propertyIsEnumerable('test_var')"); +trace(es3inst.propertyIsEnumerable('test_var')); + +trace("//ES3Class.propertyIsEnumerable('test_static_var')"); +trace(ES3Class.propertyIsEnumerable('test_static_var')); +trace("//ES3Class.setPropertyIsEnumerable('test_static_var', false)"); +ES3Class.setPropertyIsEnumerable('test_static_var', false); +trace("//ES3Class.propertyIsEnumerable('test_static_var')"); +trace(ES3Class.propertyIsEnumerable('test_static_var')); + +trace("//ES3Class.propertyIsEnumerable('test_static_function')"); +trace(ES3Class.propertyIsEnumerable('test_static_function')); +trace("//ES3Class.setPropertyIsEnumerable('test_static_function', false)"); +ES3Class.setPropertyIsEnumerable('test_static_function', false); +trace("//ES3Class.propertyIsEnumerable('test_static_function')"); +trace(ES3Class.propertyIsEnumerable('test_static_function')); + +trace("//ES3Class.prototype.propertyIsEnumerable('test_function')"); +trace(ES3Class.prototype.propertyIsEnumerable('test_function')); +trace("//ES3Class.prototype.setPropertyIsEnumerable('test_function', false)"); +ES3Class.prototype.setPropertyIsEnumerable('test_function', false); +trace("//ES3Class.prototype.propertyIsEnumerable('test_function')"); +trace(ES3Class.prototype.propertyIsEnumerable('test_function')); + +trace("//ES3Class.prototype.propertyIsEnumerable('test_proto')"); +trace(ES3Class.prototype.propertyIsEnumerable('test_proto')); +trace("//ES3Class.prototype.setPropertyIsEnumerable('test_proto', false)"); +ES3Class.prototype.setPropertyIsEnumerable('test_proto', false); +trace("//ES3Class.prototype.propertyIsEnumerable('test_proto')"); +trace(ES3Class.prototype.propertyIsEnumerable('test_proto')); \ No newline at end of file diff --git a/core/tests/swfs/avm2/set_property_is_enumerable/output.txt b/core/tests/swfs/avm2/set_property_is_enumerable/output.txt new file mode 100644 index 000000000000..81fbf1c4f104 --- /dev/null +++ b/core/tests/swfs/avm2/set_property_is_enumerable/output.txt @@ -0,0 +1,85 @@ +//es4inst.propertyIsEnumerable('test_var') +false +//es4inst.setPropertyIsEnumerable('test_var', true) +//es4inst.propertyIsEnumerable('test_var') +false +//es4inst.propertyIsEnumerable('test_const') +false +//es4inst.setPropertyIsEnumerable('test_const', true) +//es4inst.propertyIsEnumerable('test_const') +false +//es4inst.propertyIsEnumerable('test_function') +false +//es4inst.setPropertyIsEnumerable('test_function', true) +//es4inst.propertyIsEnumerable('test_function') +false +//es4inst.propertyIsEnumerable('test_virt') +false +//es4inst.setPropertyIsEnumerable('test_virt', true) +//es4inst.propertyIsEnumerable('test_virt') +false +//es4inst.propertyIsEnumerable('test_private_var') +false +//es4inst.setPropertyIsEnumerable('test_private_var', true) +//es4inst.propertyIsEnumerable('test_private_var') +false +//es4inst.propertyIsEnumerable('test_private_const') +false +//es4inst.setPropertyIsEnumerable('test_private_const', true) +//es4inst.propertyIsEnumerable('test_private_const') +false +//es4inst.propertyIsEnumerable('test_private_function') +false +//es4inst.setPropertyIsEnumerable('test_private_function', true) +//es4inst.propertyIsEnumerable('test_private_function') +false +//es4inst.propertyIsEnumerable('test_private_virt') +false +//es4inst.setPropertyIsEnumerable('test_private_virt', true) +//es4inst.propertyIsEnumerable('test_private_virt') +false +//ES4Class.propertyIsEnumerable('test_static_var') +false +//ES4Class.setPropertyIsEnumerable('test_static_var', true) +//ES4Class.propertyIsEnumerable('test_static_var') +false +//ES4Class.propertyIsEnumerable('test_static_const') +false +//ES4Class.setPropertyIsEnumerable('test_static_const', true) +//ES4Class.propertyIsEnumerable('test_static_const') +false +//ES4Class.propertyIsEnumerable('test_static_function') +false +//ES4Class.setPropertyIsEnumerable('test_static_function', true) +//ES4Class.propertyIsEnumerable('test_static_function') +false +//ES4Class.propertyIsEnumerable('test_static_virt') +false +//ES4Class.setPropertyIsEnumerable('test_static_virt', true) +//ES4Class.propertyIsEnumerable('test_static_virt') +false +//es3inst.propertyIsEnumerable('test_var') +true +//es3inst.setPropertyIsEnumerable('test_var', false) +//es3inst.propertyIsEnumerable('test_var') +false +//ES3Class.propertyIsEnumerable('test_static_var') +true +//ES3Class.setPropertyIsEnumerable('test_static_var', false) +//ES3Class.propertyIsEnumerable('test_static_var') +false +//ES3Class.propertyIsEnumerable('test_static_function') +true +//ES3Class.setPropertyIsEnumerable('test_static_function', false) +//ES3Class.propertyIsEnumerable('test_static_function') +false +//ES3Class.prototype.propertyIsEnumerable('test_function') +true +//ES3Class.prototype.setPropertyIsEnumerable('test_function', false) +//ES3Class.prototype.propertyIsEnumerable('test_function') +false +//ES3Class.prototype.propertyIsEnumerable('test_proto') +true +//ES3Class.prototype.setPropertyIsEnumerable('test_proto', false) +//ES3Class.prototype.propertyIsEnumerable('test_proto') +false diff --git a/core/tests/swfs/avm2/set_property_is_enumerable/test.fla b/core/tests/swfs/avm2/set_property_is_enumerable/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..9117e9f0beb567f07719301e8775ac7b9d90c7dc GIT binary patch literal 3983 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/set_property_is_enumerable/test.swf b/core/tests/swfs/avm2/set_property_is_enumerable/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..fd2b085b743b59ded62d2fa3c2259b80959837cc GIT binary patch literal 2332 zcmV+%3FG!dS5qqk7ytlxoTXT4bJJKB)srksvg6o}Z8O zojsLVT%BDgNtz}gm%?8Yj_8~bP3gd`Yr3Q>`Srq1C9f;h%5#OgimIo!_=1Ntr8>Qn{v- z<&sj7Q+2kWN@aOw>N%=r)`jKVD;sy@yzXg~=Ix517SrL)Wt3C~E{`$DS1X#%8ozyx zMhW#L4>%uJQoT*M#}PDr9HU6fh6JBjLf(GLuI8uo0ihMAHx+HWB<%}Vs=JE3P*S!d zE!V_tE~DS^YD#Lbd2CE7-0I^Hc>Im#?avk`YjX;u!F?9*OUs$Hh3clfrtTC9C3!7h zRpm9z*4DJQ3fh{qTRyf1uLL|`bz4^T{dGlKmn%DES(P?Q^6Ar7Z&+QG^72Sg*S9mN zl(bphkf-z2a_ZdbZ0cA#eIm87qm*=|0_`dZ;H6jf{cYJq>-82I!^u?2n&h-ybXi-h zmz;>(t&bG=SIl}RJF3^{+__GR zHg(RdoN-n*zr@iA`Q*>9Z-|^v-fnR-)vi2XpHilqPbnGev`3Goz2zh6$X0z=g%YxA zX*H?5-87ObT4S2pzNX7%YbH9(v%3MOVO@evL%FgAtHhhkj$+NvSR-qGd*xEpaWKr( z4~AhUzY0^<rpC0G9@~lS#@^x>!g$)U%03!rlLWmWDM3`cQDI&y)kRZY_BJ>eqjtEIs2od2Z zD4yBzl($9*zl{%0L8+PX8=-31qm$bBCW2N#bc z0Y@IVyg2H@k&kM)bi?I0F+*s91{;RvU6kL0ej;Jr_=g5Jp#+YmJRC!L@h=YjL;!w} zcnH#^DUyJjhV)>NargANV8K~FNYMe7fH~u$NC^Mp z5XN5-1CcHlp(7Mc25tPNa(HKu9C6$NS-9cQTF+6ZJ!V#Z;}5NN26UIKV| zAkv2sFGM`X%pl?euUM@cyu38+V$myTdIf7ekP*X-5M*@OUUBf6fXEO=5)kp(UQ^&T zTuXvix9ycOy;8L_WDH}*3}pD9T$ca?&;7u2kOqRz_T}hwE_Ra|C=wX6*+V$)zexs) z42NyDIG zE2|FWJ)jp5s0KY`(HhVf5a>u620ONL*P+}4dJ)0CK@VH>+dyAL@D7rO!MnEdJ%{pr zpqCKbC%}&-;}-uZ7$z?vdO(miJU$?{`qNJJXE3mg=(A>E!U}v21D6qf9&IKJn=jPW zUv#R!gn^e4ec22otiV?=@Cu@@qD{iE`C485b*K6p7`TGyn`U6r3VaI#uOj+3+DsZY z->Iv=>r{Ua11pHWkAcbhtN#NGTt)Olf^-~hepFX~=v4n01Fs?a2?hr1i}q6typHH+ z1Zl(O=XLcjoa$d=nQ@mAUvQw9*s6@>Lbcg|3H1^MI^5K4sj8X@Mqbo_eMng0VUrGY( z#WIDv=5#ujO6St)+)O%mES>Ygznk=c`BUtA_ Date: Sun, 8 Mar 2020 20:11:59 -0400 Subject: [PATCH 129/189] Make `toString` and `valueOf` methods of `TObject`, called `to_string` and `value_of` respectively. The reason for this is that, in AVM2, `toString` and `valueOf` are not defined on the classes or prototypes of `Function` or `Class`. Instead, they use the `Object.prototype` versions of those functions. Ergo, string and primitive coercion are inherent object methods (the ones that get `[[DoubleSquareBrackets]]` in the ECMA standards). In Ruffle, our equivalent to `[[DoubleSquareBrackets]]` methods are methods on the `TObject` trait, so we're adding them there. This mechanism will make implementing boxed value types (ala AVM1's `BoxedObject`) easier, too. We also add some reasonable defaults for `ScriptObject` and `FunctionObject` which will appear on objects, functions, and classes. --- core/src/avm2/function.rs | 13 +++++++++++ core/src/avm2/globals/function.rs | 16 ------------- core/src/avm2/globals/object.rs | 38 +++++++++++++++++++++++++++++++ core/src/avm2/object.rs | 17 ++++++++++++++ core/src/avm2/script_object.rs | 12 ++++++++++ 5 files changed, 80 insertions(+), 16 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index c2d228dd827e..1b063648707a 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -646,6 +646,19 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { .into()) } + fn to_string(&self) -> Result, Error> { + if let ScriptObjectClass::ClassConstructor(class, ..) = self.0.read().base.class() { + let name = QName::from_abc_multiname(&class.abc(), class.instance().name.clone())?; + Ok(format!("[class {}]", name.local_name()).into()) + } else { + Ok("function Function() {}".into()) + } + } + + fn value_of(&self) -> Result, Error> { + Ok(Value::Object(Object::from(*self))) + } + fn install_method( &mut self, mc: MutationContext<'gc, '_>, diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs index d8f82554e550..c3892a239029 100644 --- a/core/src/avm2/globals/function.rs +++ b/core/src/avm2/globals/function.rs @@ -20,16 +20,6 @@ pub fn constructor<'gc>( Ok(Value::Undefined.into()) } -/// Implements `Function.prototype.toString` -fn to_string<'gc>( - _: &mut Avm2<'gc>, - _: &mut UpdateContext<'_, 'gc, '_>, - _: Option>, - _: &[Value<'gc>], -) -> Result, Error> { - Ok(ReturnValue::Immediate("[type Function]".into())) -} - /// Implements `Function.prototype.call` fn call<'gc>( avm: &mut Avm2<'gc>, @@ -61,12 +51,6 @@ fn call<'gc>( pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> { let mut function_proto = ScriptObject::object(gc_context, proto); - function_proto.install_method( - gc_context, - QName::new(Namespace::public_namespace(), "toString"), - 0, - FunctionObject::from_builtin(gc_context, to_string, function_proto), - ); function_proto.install_method( gc_context, QName::new(Namespace::public_namespace(), "call"), diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 96eb41e3bc27..ef0e2937411e 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -19,6 +19,32 @@ pub fn constructor<'gc>( Ok(Value::Undefined.into()) } +/// Implements `Object.prototype.toString` +fn to_string<'gc>( + _: &mut Avm2<'gc>, + _: &mut UpdateContext<'_, 'gc, '_>, + this: Option>, + _: &[Value<'gc>], +) -> Result, Error> { + Ok(this + .map(|t| t.to_string()) + .unwrap_or(Ok(Value::Undefined))? + .into()) +} + +/// Implements `Object.prototype.valueOf` +fn value_of<'gc>( + _: &mut Avm2<'gc>, + _: &mut UpdateContext<'_, 'gc, '_>, + this: Option>, + _: &[Value<'gc>], +) -> Result, Error> { + Ok(this + .map(|t| t.value_of()) + .unwrap_or(Ok(Value::Undefined))? + .into()) +} + /// `Object.prototype.hasOwnProperty` pub fn has_own_property<'gc>( _avm: &mut Avm2<'gc>, @@ -127,6 +153,18 @@ pub fn fill_proto<'gc>( mut object_proto: Object<'gc>, fn_proto: Object<'gc>, ) { + object_proto.install_method( + gc_context, + QName::new(Namespace::public_namespace(), "toString"), + 0, + FunctionObject::from_builtin(gc_context, to_string, fn_proto), + ); + object_proto.install_method( + gc_context, + QName::new(Namespace::public_namespace(), "valueOf"), + 0, + FunctionObject::from_builtin(gc_context, value_of, fn_proto), + ); object_proto.install_method( gc_context, QName::new(Namespace::public_namespace(), "hasOwnProperty"), diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index db29dd982440..4f4ac9120e9b 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -627,6 +627,23 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy scope: Option>>, ) -> Result, Error>; + /// Implement the result of calling `Object.prototype.toString` on this + /// object class. + /// + /// `toString` is a method used to request an object be coerced to a string + /// value. The default implementation is stored here. User-specified string + /// coercions happen by defining `toString` in a downstream class or + /// prototype; this is then picked up by the VM runtime when doing + /// coercions. + fn to_string(&self) -> Result, Error>; + + /// Implement the result of calling `Object.prototype.valueOf` on this + /// object class. + /// + /// `valueOf` is a method used to request an object be coerced to a + /// primitive value. Typically, this would be a number of some kind. + fn value_of(&self) -> Result, Error>; + /// Get a raw pointer value for this object. fn as_ptr(&self) -> *const ObjectPtr; diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 68410bdbb5fa..3eef734635c3 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -247,6 +247,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { )) } + fn to_string(&self) -> Result, Error> { + Ok("[object Object]".into()) + } + + fn value_of(&self) -> Result, Error> { + Ok(Value::Object(Object::from(*self))) + } + fn install_method( &mut self, mc: MutationContext<'gc, '_>, @@ -797,6 +805,10 @@ impl<'gc> ScriptObjectData<'gc> { Ok(()) } + pub fn class(&self) -> &ScriptObjectClass<'gc> { + &self.class + } + /// Install a method into the object. pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) { if disp_id > 0 { From 37cdcb3bce10869ed37355c5fd9db23f41bdbc49 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 20:12:31 -0400 Subject: [PATCH 130/189] Add tests for `toString` on objects, functions, and classes. --- core/tests/regression_tests.rs | 3 +++ core/tests/swfs/avm2/class_to_string/Test.as | 10 ++++++++++ core/tests/swfs/avm2/class_to_string/output.txt | 2 ++ core/tests/swfs/avm2/class_to_string/test.fla | Bin 0 -> 4031 bytes core/tests/swfs/avm2/class_to_string/test.swf | Bin 0 -> 997 bytes core/tests/swfs/avm2/function_to_string/Test.as | 16 ++++++++++++++++ .../swfs/avm2/function_to_string/output.txt | 4 ++++ .../tests/swfs/avm2/function_to_string/test.fla | Bin 0 -> 4031 bytes .../tests/swfs/avm2/function_to_string/test.swf | Bin 0 -> 1031 bytes core/tests/swfs/avm2/object_to_string/Test.as | 6 ++++++ .../tests/swfs/avm2/object_to_string/output.txt | 2 ++ core/tests/swfs/avm2/object_to_string/test.fla | Bin 0 -> 4031 bytes core/tests/swfs/avm2/object_to_string/test.swf | Bin 0 -> 949 bytes 13 files changed, 43 insertions(+) create mode 100644 core/tests/swfs/avm2/class_to_string/Test.as create mode 100644 core/tests/swfs/avm2/class_to_string/output.txt create mode 100644 core/tests/swfs/avm2/class_to_string/test.fla create mode 100644 core/tests/swfs/avm2/class_to_string/test.swf create mode 100644 core/tests/swfs/avm2/function_to_string/Test.as create mode 100644 core/tests/swfs/avm2/function_to_string/output.txt create mode 100644 core/tests/swfs/avm2/function_to_string/test.fla create mode 100644 core/tests/swfs/avm2/function_to_string/test.swf create mode 100644 core/tests/swfs/avm2/object_to_string/Test.as create mode 100644 core/tests/swfs/avm2/object_to_string/output.txt create mode 100644 core/tests/swfs/avm2/object_to_string/test.fla create mode 100644 core/tests/swfs/avm2/object_to_string/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 6d06fd6f38b3..6563a714fe2c 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -244,6 +244,9 @@ swf_tests! { (as3_has_own_property, "avm2/has_own_property", 1), (as3_property_is_enumerable, "avm2/property_is_enumerable", 1), (as3_set_property_is_enumerable, "avm2/set_property_is_enumerable", 1), + (as3_object_to_string, "avm2/object_to_string", 1), + (as3_function_to_string, "avm2/function_to_string", 1), + (as3_class_to_string, "avm2/class_to_string", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/class_to_string/Test.as b/core/tests/swfs/avm2/class_to_string/Test.as new file mode 100644 index 000000000000..d9eccd9a9ac5 --- /dev/null +++ b/core/tests/swfs/avm2/class_to_string/Test.as @@ -0,0 +1,10 @@ +package { + public class Test {} +} + +class ES4Class { + +} + +trace("//ES4Class.toString()"); +trace(ES4Class.toString()); \ No newline at end of file diff --git a/core/tests/swfs/avm2/class_to_string/output.txt b/core/tests/swfs/avm2/class_to_string/output.txt new file mode 100644 index 000000000000..9c7c7c8649ae --- /dev/null +++ b/core/tests/swfs/avm2/class_to_string/output.txt @@ -0,0 +1,2 @@ +//ES4Class.toString() +[class ES4Class] diff --git a/core/tests/swfs/avm2/class_to_string/test.fla b/core/tests/swfs/avm2/class_to_string/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..d382fbe3e8ab454910c617c4d2ff9d879207277d GIT binary patch literal 4031 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 T)A?v{51wK04UhZ<=SJ~twp;hE literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/function_to_string/Test.as b/core/tests/swfs/avm2/function_to_string/Test.as new file mode 100644 index 000000000000..0765fb2b10c4 --- /dev/null +++ b/core/tests/swfs/avm2/function_to_string/Test.as @@ -0,0 +1,16 @@ +package { + public class Test {} +} + +var freeFunction = function() { +}; + +function namedFunction() { + +} + +trace("//freeFunction.toString()"); +trace(freeFunction.toString()); + +trace("//namedFunction.toString()"); +trace(namedFunction.toString()); \ No newline at end of file diff --git a/core/tests/swfs/avm2/function_to_string/output.txt b/core/tests/swfs/avm2/function_to_string/output.txt new file mode 100644 index 000000000000..823210c4aad6 --- /dev/null +++ b/core/tests/swfs/avm2/function_to_string/output.txt @@ -0,0 +1,4 @@ +//freeFunction.toString() +function Function() {} +//namedFunction.toString() +function Function() {} diff --git a/core/tests/swfs/avm2/function_to_string/test.fla b/core/tests/swfs/avm2/function_to_string/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..d382fbe3e8ab454910c617c4d2ff9d879207277d GIT binary patch literal 4031 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 B5vTwF literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/object_to_string/Test.as b/core/tests/swfs/avm2/object_to_string/Test.as new file mode 100644 index 000000000000..2a3e0b02fe76 --- /dev/null +++ b/core/tests/swfs/avm2/object_to_string/Test.as @@ -0,0 +1,6 @@ +package { + public class Test {} +} + +trace("//(new Object()).toString()"); +trace((new Object()).toString()); \ No newline at end of file diff --git a/core/tests/swfs/avm2/object_to_string/output.txt b/core/tests/swfs/avm2/object_to_string/output.txt new file mode 100644 index 000000000000..df1f145f12f5 --- /dev/null +++ b/core/tests/swfs/avm2/object_to_string/output.txt @@ -0,0 +1,2 @@ +//(new Object()).toString() +[object Object] diff --git a/core/tests/swfs/avm2/object_to_string/test.fla b/core/tests/swfs/avm2/object_to_string/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..d382fbe3e8ab454910c617c4d2ff9d879207277d GIT binary patch literal 4031 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 Date: Sun, 8 Mar 2020 20:22:26 -0400 Subject: [PATCH 131/189] Several built-in functions are not `public`, but instead live in the `AS3` namespace. This moves those functions there. In practice not many movies will care about this, because the `AS3` namespace is open by default. You could opt-out of that, and I suppose that was there for using existing ES3 code in AS3 projects. ES4 would have had a similar ES4 namespace, which "JavaScript 2.0" code would need to opt into. Of course, ES4/JS2 never happened, so we just have this weird historical quirk here. --- core/src/avm2/globals/function.rs | 2 +- core/src/avm2/globals/object.rs | 6 +++--- core/src/avm2/names.rs | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs index c3892a239029..7a07d31af830 100644 --- a/core/src/avm2/globals/function.rs +++ b/core/src/avm2/globals/function.rs @@ -53,7 +53,7 @@ pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc function_proto.install_method( gc_context, - QName::new(Namespace::public_namespace(), "call"), + QName::new(Namespace::as3_namespace(), "call"), 0, FunctionObject::from_builtin(gc_context, call, function_proto), ); diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index ef0e2937411e..8822bd6a00cd 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -167,19 +167,19 @@ pub fn fill_proto<'gc>( ); object_proto.install_method( gc_context, - QName::new(Namespace::public_namespace(), "hasOwnProperty"), + QName::new(Namespace::as3_namespace(), "hasOwnProperty"), 0, FunctionObject::from_builtin(gc_context, has_own_property, fn_proto), ); object_proto.install_method( gc_context, - QName::new(Namespace::public_namespace(), "isPrototypeOf"), + QName::new(Namespace::as3_namespace(), "isPrototypeOf"), 0, FunctionObject::from_builtin(gc_context, is_prototype_of, fn_proto), ); object_proto.install_method( gc_context, - QName::new(Namespace::public_namespace(), "propertyIsEnumerable"), + QName::new(Namespace::as3_namespace(), "propertyIsEnumerable"), 0, FunctionObject::from_builtin(gc_context, property_is_enumerable, fn_proto), ); diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index c6756ec5b348..5640f57ea672 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -59,6 +59,10 @@ impl Namespace { Namespace::Package("".to_string()) } + pub fn as3_namespace() -> Self { + Namespace::Namespace("http://adobe.com/AS3/2006/builtin".to_string()) + } + pub fn package(package_name: &str) -> Self { Namespace::Package(package_name.to_string()) } From 4b66af8dc3e0834ee3a5afc7b33132e61ed9595b Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 21:53:33 -0400 Subject: [PATCH 132/189] ES4 classes, while superficially similar to functions, are not functions and should not inherit from the `Function` prototype. We still reuse the `FunctionObject` machinery internally. If necessary, we may want to split this into a separate `ClassObject` if some internal `TObject` method needs replacing for classes. --- core/src/avm2/function.rs | 3 ++- core/src/avm2/globals.rs | 13 +++++++++++++ core/src/avm2/globals/class.rs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 core/src/avm2/globals/class.rs diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 1b063648707a..ee763390d0d2 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -308,6 +308,7 @@ impl<'gc> FunctionObject<'gc> { }); let mut class_proto = super_proto?.derive(avm, context, class.clone(), scope)?; let fn_proto = avm.prototypes().function; + let class_constr_proto = avm.prototypes().class; let initializer_index = class.instance().init_method.clone(); let initializer: Result = @@ -358,7 +359,7 @@ impl<'gc> FunctionObject<'gc> { context.gc_context, class_initializer?, scope, - fn_proto, + class_constr_proto, None, ); diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 01a49346cde5..a51716dfd43f 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -10,6 +10,7 @@ use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use gc_arena::{Collect, MutationContext}; +mod class; mod flash; mod function; mod object; @@ -33,6 +34,7 @@ fn trace<'gc>( pub struct SystemPrototypes<'gc> { pub object: Object<'gc>, pub function: Object<'gc>, + pub class: Object<'gc>, } /// Add a free-function builtin to the global scope. @@ -86,6 +88,7 @@ pub fn construct_global_scope<'gc>( // public / root package let object_proto = ScriptObject::bare_object(mc); let fn_proto = function::create_proto(mc, object_proto); + let class_proto = class::create_proto(mc, object_proto, fn_proto); object::fill_proto(mc, object_proto, fn_proto); @@ -107,6 +110,15 @@ pub fn construct_global_scope<'gc>( fn_proto, fn_proto, ); + class( + mc, + gs, + "", + "Class", + class::constructor, + class_proto, + fn_proto, + ); function(mc, gs, "", "trace", trace, fn_proto); // package `flash.events` @@ -183,6 +195,7 @@ pub fn construct_global_scope<'gc>( let system_prototypes = SystemPrototypes { object: object_proto, function: fn_proto, + class: class_proto, }; (gs, system_prototypes) diff --git a/core/src/avm2/globals/class.rs b/core/src/avm2/globals/class.rs new file mode 100644 index 000000000000..e7144bb79543 --- /dev/null +++ b/core/src/avm2/globals/class.rs @@ -0,0 +1,31 @@ +//! `Class` builtin/prototype + +use crate::avm2::object::Object; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::script_object::ScriptObject; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::MutationContext; + +/// Implements `Class` +/// +/// Notably, you cannot construct new classes this way, so this returns an +/// error. +pub fn constructor<'gc>( + _avm: &mut Avm2<'gc>, + _action_context: &mut UpdateContext<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Err("Classes cannot be constructed.".into()) +} + +/// Construct `Class.prototype`. +pub fn create_proto<'gc>( + mc: MutationContext<'gc, '_>, + super_proto: Object<'gc>, + _fn_proto: Object<'gc>, +) -> Object<'gc> { + ScriptObject::object(mc, super_proto) +} From 16774aa055284597c189c0544e9bac8fa00b8a01 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 22:21:45 -0400 Subject: [PATCH 133/189] Add a test for legacy / ES3 inheritance. This was originally something *way* more evil: mixed inheritance between ES3 and ES4 classes. It didn't pan out due to fundamental limitations of the two object models. How the hell did Brendan Eich/Adobe/TC-39 expect ES4 classes to be adopted in already-existing codebases?! --- core/tests/regression_tests.rs | 1 + core/tests/swfs/avm2/es3_inheritance/Test.as | 67 ++++++++++++++++++ .../swfs/avm2/es3_inheritance/output.txt | 31 ++++++++ core/tests/swfs/avm2/es3_inheritance/test.fla | Bin 0 -> 3983 bytes core/tests/swfs/avm2/es3_inheritance/test.swf | Bin 0 -> 1438 bytes 5 files changed, 99 insertions(+) create mode 100644 core/tests/swfs/avm2/es3_inheritance/Test.as create mode 100644 core/tests/swfs/avm2/es3_inheritance/output.txt create mode 100644 core/tests/swfs/avm2/es3_inheritance/test.fla create mode 100644 core/tests/swfs/avm2/es3_inheritance/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 6563a714fe2c..345741624255 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -247,6 +247,7 @@ swf_tests! { (as3_object_to_string, "avm2/object_to_string", 1), (as3_function_to_string, "avm2/function_to_string", 1), (as3_class_to_string, "avm2/class_to_string", 1), + (as3_es3_inheritance, "avm2/es3_inheritance", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/es3_inheritance/Test.as b/core/tests/swfs/avm2/es3_inheritance/Test.as new file mode 100644 index 000000000000..9d4d0536bc2a --- /dev/null +++ b/core/tests/swfs/avm2/es3_inheritance/Test.as @@ -0,0 +1,67 @@ +package { + public class Test { + } +} + +function Test2() { + trace("Instance constructor"); +} + +Test2.prototype.method = function() { + trace("Instance method"); +} + +Test2.prototype.method2 = function() { + trace("Instance method 2"); +} + +function Test3() { + trace("Child instance constructor pre-super"); + Test2.call(this); + trace("Child instance constructor post-super"); +} + +Test3.prototype = new Test2(); + +Test3.prototype.method = function() { + trace("Child instance method pre-super"); + Test2.prototype.method.call(this); + trace("Child instance method post-super"); +} + +Test3.prototype.method3 = function() { + trace("Child instance method3 pre-super"); + Test2.prototype.method.call(this); + trace("Child instance method3 post-super"); +} + +function Test4() { + trace("Grandchild instance constructor pre-super"); + Test3.call(this); + trace("Grandchild instance constructor post-super"); +} + +Test4.prototype = new Test3(); + +Test4.prototype.method2 = function () { + trace("Grandchild instance method2 pre-super"); + Test3.prototype.method2.call(this); + trace("Grandchild instance method2 post-super"); +} + +Test4.prototype.method3 = function () { + trace("Grandchild instance method3 pre-super"); + Test3.prototype.method3.call(this); + trace("Grandchild instance method3 post-super"); +} + +trace("Script initializer"); +var x = new Test3(); +x.method(); +x.method2(); +x.method3(); + +var y = new Test4(); +y.method(); +y.method2(); +y.method3(); \ No newline at end of file diff --git a/core/tests/swfs/avm2/es3_inheritance/output.txt b/core/tests/swfs/avm2/es3_inheritance/output.txt new file mode 100644 index 000000000000..3d03db4d47ee --- /dev/null +++ b/core/tests/swfs/avm2/es3_inheritance/output.txt @@ -0,0 +1,31 @@ +Instance constructor +Child instance constructor pre-super +Instance constructor +Child instance constructor post-super +Script initializer +Child instance constructor pre-super +Instance constructor +Child instance constructor post-super +Child instance method pre-super +Instance method +Child instance method post-super +Instance method 2 +Child instance method3 pre-super +Instance method +Child instance method3 post-super +Grandchild instance constructor pre-super +Child instance constructor pre-super +Instance constructor +Child instance constructor post-super +Grandchild instance constructor post-super +Child instance method pre-super +Instance method +Child instance method post-super +Grandchild instance method2 pre-super +Instance method 2 +Grandchild instance method2 post-super +Grandchild instance method3 pre-super +Child instance method3 pre-super +Instance method +Child instance method3 post-super +Grandchild instance method3 post-super diff --git a/core/tests/swfs/avm2/es3_inheritance/test.fla b/core/tests/swfs/avm2/es3_inheritance/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..6c88e118b22439d3ee8bf244d8878e635a3a79db GIT binary patch literal 3983 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/es3_inheritance/test.swf b/core/tests/swfs/avm2/es3_inheritance/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..a9d6767c51dba7396c4983f590b398d2d2814166 GIT binary patch literal 1438 zcmV;P1!4L_S5qqs3jhFkoSl|kTiQqz$7diR8QwvNh;OK9t8F2IeZzK3i`8oD+Fje{ z@dBPE!XVj&giWHWz1-XV68la172J!x-0wg?LHA4oiY~SLEDxDE=Qsa3bLKD^Jw^Nn zgd%?+l)`8uCL)9?o-bd%yckDlHhMgfUCnO2&2H%j_Q%~{wB~`m`TlkB{hPhb$7eG7 zlnj6#qy0M7X=W9nPl@0C48SLfa;$rgqVK3>=Ts|Od{koQ+I3L84hn>|dDbl2(0pjN z)zYr2>F=}-ZEHHsU(lwhcIfJzZ>WYT$HMyd4u7I`TO25Y!-^09tCeeLR=2I5kzM0N zqm{PSO^%G`b5rnN?typq;Iu`jUBjpN?8$biS}_{5Y92S6ZCb4xCatoFR@M5oX;sye zPN7Pz;s;G{QB$*3y-wHH`%!t@RO@uEW!v3SKCd>6Lz=4_o&59tV!j~D5Auh{THDt2 z#Vu3S8}*jfZe%pwa)M+!)NUD#Vy0`-d)9H6nxtzQwqc)jsn8$e>T0{abmL^hvfbG? ztWmgJqaV3&L(sxd&`jkUIs05P_ch%`q}h>*%Rr-78VV5GzBW{dTqDb)_Ea~(GLP%F zVO}n_@=fK%vf0Xih_`r?=ps~Nd&GErziw(>8|G@Zrna?@)SMn1=-lvuF7;n3PgV}N zC+*nQ+SZu+R;cwlwJhyWYist|;7y^{&$iJGx*j!6!?Xs@3Ds>Y+;>iGYU*mcKdlKz zmYSB|^^T~~>929rHV#>as;wD%z-1lU&{X2uI<-R=R<2=o`tiG_ro&5cbm1DX1U36w zOYL-rJgQrlXEaGa zT;=rgwjZfhsh6;9p|WNP@wD7|DI8&~Z*Ts2{} z(b#0axO+erzH%sry~wo?p)0G!{*{|FdP_cc`)mK>W~{DAI3-GcDVPe6`}wGt8pnK? z&x^9ODi)-ov?kt{9!L+xN77@dB&~~2#HZqS;xnl%J(pgH6=_3yDQ>32>8@C^yJj^%xq?Eo?pl=F5SAlyeME% zz(nu{1TG}_B7zVTgt#D#2|_{;l4Kl{I3{UKCNY`9WEPVcCi9qx9zwh%#*sKj#^8%d zf+Gn`k{n6GC{K2I65)mDp1{2oxE}=WM}gaqx_?_oJsxAA*8+DZ_5!%~KnXTO z&G7kN1os{SoM!OR7OXXp=7G*IDm&7sBV7fUVXy#F&p8k~l!qEeyV|mMn`cA6W64R2EtSSWC6>vacnANDcMUtng*8Bgr_nM`h{VRZ_L!PLN4*N(wM`X0AClawH|#!9BKv_1c81 zCA}v~dM}Q8PoayV-gnT2QLkK^tfh8RNcH!gD^qOz1#~c8QKmM-=(oTAe1%XDW8!y9 z5W?6Wh`@exJ#TG#XBsW{IKW{qV9BXWZRYd$J)A}7&Dp&fPtOCR5hyIVb1qZF3Y3wR sIa!&Pl?7SJ$_fYH13c}1THep^dMTLlY%JY>2}WLPs0<_j06RE1)Q3&dAOHXW literal 0 HcmV?d00001 From 3558c3afa02b69aa35dfc134014de0a6cd827710 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 22:29:15 -0400 Subject: [PATCH 134/189] Add test of `Function.prototype.call` --- core/tests/regression_tests.rs | 1 + .../swfs/avm2/function_call_via_call/Test.as | 11 +++++++++++ .../swfs/avm2/function_call_via_call/output.txt | 3 +++ .../swfs/avm2/function_call_via_call/test.fla | Bin 0 -> 3985 bytes .../swfs/avm2/function_call_via_call/test.swf | Bin 0 -> 730 bytes 5 files changed, 15 insertions(+) create mode 100644 core/tests/swfs/avm2/function_call_via_call/Test.as create mode 100644 core/tests/swfs/avm2/function_call_via_call/output.txt create mode 100644 core/tests/swfs/avm2/function_call_via_call/test.fla create mode 100644 core/tests/swfs/avm2/function_call_via_call/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 345741624255..0700024487bb 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -229,6 +229,7 @@ swf_tests! { (context_menu_item, "avm1/context_menu_item", 1), (as3_hello_world, "avm2/hello_world", 1), (as3_function_call, "avm2/function_call", 1), + (as3_function_call_via_call, "avm2/function_call_via_call", 1), (as3_constructor_call, "avm2/constructor_call", 1), (as3_class_methods, "avm2/class_methods", 1), (as3_inheritance, "avm2/inheritance", 1), diff --git a/core/tests/swfs/avm2/function_call_via_call/Test.as b/core/tests/swfs/avm2/function_call_via_call/Test.as new file mode 100644 index 000000000000..81c745852dd4 --- /dev/null +++ b/core/tests/swfs/avm2/function_call_via_call/Test.as @@ -0,0 +1,11 @@ +package { + public class Test {} +} + +function testfunc(v1, v2, v3) { + trace(v1); + trace(v2); + trace(v3); +} + +testfunc.call(null, "arg1", "arg2", "arg3"); \ No newline at end of file diff --git a/core/tests/swfs/avm2/function_call_via_call/output.txt b/core/tests/swfs/avm2/function_call_via_call/output.txt new file mode 100644 index 000000000000..6a8e7e73b1ea --- /dev/null +++ b/core/tests/swfs/avm2/function_call_via_call/output.txt @@ -0,0 +1,3 @@ +arg1 +arg2 +arg3 diff --git a/core/tests/swfs/avm2/function_call_via_call/test.fla b/core/tests/swfs/avm2/function_call_via_call/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..e8422682499736c841d221c3be55563405f47944 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/function_call_via_call/test.swf b/core/tests/swfs/avm2/function_call_via_call/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..2878e446fb165df28068d91ffc25307f528bbc05 GIT binary patch literal 730 zcmV<00ww)JS5qr}1ONbdoSjtbZW2KhK4N*_e}OZC`SPCF91n| zjY$mv%H;g~{Mj79-HHBuI-hPIra!)9o3HjCSK0;d!G5Jy?r(VZ>nt44M!7vg#bF*> z%mW;C4JR$df^V42tEQl3DK5nd$c%jTSSVj?YYC-ZOje8a>k@yC6+;{`l2^VLe%cLTQe|Fbe936fM3S|XB+&1uTGHlMg7AtIfk zs8Xa<(y1J!>KLVQiW3xPbrtnEq1pn)+f=_x0C$RUMp6h$#?atLQq?VDSmY2=P2^{#8d-;? z%C)GaE@l#$xlD2^Du4cd@|;(Xs7Jz}3SuN0=OJ#5q$<8ZxIdCPtxB9uT;!_8&Qc>x z!?2<3#mI>8Tgpq(SBC}zk#o$j#tkcOSQCadX;?B}hnNb#rgWAbCArI!=>-UX0XM!b MSm!rC0VFjxsS`b4dH?_b literal 0 HcmV?d00001 From cf6714d33cd4becd20c89e75e5dbb83ad1f53e0e Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 22:38:10 -0400 Subject: [PATCH 135/189] Implement and test `toLocaleString`. This function has vague documentation about enabling locale-specific formatting in subclasses. As far as I can tell, none of the objects I implemented so far do anything different than `toString`, so I just have it use the same `TObject` property I set up for `toString`. --- core/src/avm2/globals/object.rs | 19 ++++++++++++++++++ core/tests/regression_tests.rs | 3 +++ .../swfs/avm2/class_to_locale_string/Test.as | 10 +++++++++ .../avm2/class_to_locale_string/output.txt | 2 ++ .../swfs/avm2/class_to_locale_string/test.fla | Bin 0 -> 4031 bytes .../swfs/avm2/class_to_locale_string/test.swf | Bin 0 -> 1006 bytes .../avm2/function_to_locale_string/Test.as | 16 +++++++++++++++ .../avm2/function_to_locale_string/output.txt | 4 ++++ .../avm2/function_to_locale_string/test.fla | Bin 0 -> 4031 bytes .../avm2/function_to_locale_string/test.swf | Bin 0 -> 1041 bytes .../swfs/avm2/object_to_locale_string/Test.as | 6 ++++++ .../avm2/object_to_locale_string/output.txt | 2 ++ .../avm2/object_to_locale_string/test.fla | Bin 0 -> 4031 bytes .../avm2/object_to_locale_string/test.swf | Bin 0 -> 958 bytes 14 files changed, 62 insertions(+) create mode 100644 core/tests/swfs/avm2/class_to_locale_string/Test.as create mode 100644 core/tests/swfs/avm2/class_to_locale_string/output.txt create mode 100644 core/tests/swfs/avm2/class_to_locale_string/test.fla create mode 100644 core/tests/swfs/avm2/class_to_locale_string/test.swf create mode 100644 core/tests/swfs/avm2/function_to_locale_string/Test.as create mode 100644 core/tests/swfs/avm2/function_to_locale_string/output.txt create mode 100644 core/tests/swfs/avm2/function_to_locale_string/test.fla create mode 100644 core/tests/swfs/avm2/function_to_locale_string/test.swf create mode 100644 core/tests/swfs/avm2/object_to_locale_string/Test.as create mode 100644 core/tests/swfs/avm2/object_to_locale_string/output.txt create mode 100644 core/tests/swfs/avm2/object_to_locale_string/test.fla create mode 100644 core/tests/swfs/avm2/object_to_locale_string/test.swf diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 8822bd6a00cd..5bea902070b1 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -32,6 +32,19 @@ fn to_string<'gc>( .into()) } +/// Implements `Object.prototype.toLocaleString` +fn to_locale_string<'gc>( + _: &mut Avm2<'gc>, + _: &mut UpdateContext<'_, 'gc, '_>, + this: Option>, + _: &[Value<'gc>], +) -> Result, Error> { + Ok(this + .map(|t| t.to_string()) + .unwrap_or(Ok(Value::Undefined))? + .into()) +} + /// Implements `Object.prototype.valueOf` fn value_of<'gc>( _: &mut Avm2<'gc>, @@ -159,6 +172,12 @@ pub fn fill_proto<'gc>( 0, FunctionObject::from_builtin(gc_context, to_string, fn_proto), ); + object_proto.install_method( + gc_context, + QName::new(Namespace::public_namespace(), "toLocaleString"), + 0, + FunctionObject::from_builtin(gc_context, to_locale_string, fn_proto), + ); object_proto.install_method( gc_context, QName::new(Namespace::public_namespace(), "valueOf"), diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 0700024487bb..209fa937d5be 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -248,6 +248,9 @@ swf_tests! { (as3_object_to_string, "avm2/object_to_string", 1), (as3_function_to_string, "avm2/function_to_string", 1), (as3_class_to_string, "avm2/class_to_string", 1), + (as3_object_to_locale_string, "avm2/object_to_locale_string", 1), + (as3_function_to_locale_string, "avm2/function_to_locale_string", 1), + (as3_class_to_locale_string, "avm2/class_to_locale_string", 1), (as3_es3_inheritance, "avm2/es3_inheritance", 1), } diff --git a/core/tests/swfs/avm2/class_to_locale_string/Test.as b/core/tests/swfs/avm2/class_to_locale_string/Test.as new file mode 100644 index 000000000000..6598f397e1f6 --- /dev/null +++ b/core/tests/swfs/avm2/class_to_locale_string/Test.as @@ -0,0 +1,10 @@ +package { + public class Test {} +} + +class ES4Class { + +} + +trace("//ES4Class.toLocaleString()"); +trace(ES4Class.toLocaleString()); \ No newline at end of file diff --git a/core/tests/swfs/avm2/class_to_locale_string/output.txt b/core/tests/swfs/avm2/class_to_locale_string/output.txt new file mode 100644 index 000000000000..8a41ded47582 --- /dev/null +++ b/core/tests/swfs/avm2/class_to_locale_string/output.txt @@ -0,0 +1,2 @@ +//ES4Class.toLocaleString() +[class ES4Class] diff --git a/core/tests/swfs/avm2/class_to_locale_string/test.fla b/core/tests/swfs/avm2/class_to_locale_string/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..d382fbe3e8ab454910c617c4d2ff9d879207277d GIT binary patch literal 4031 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/function_to_locale_string/Test.as b/core/tests/swfs/avm2/function_to_locale_string/Test.as new file mode 100644 index 000000000000..5aff81c3d006 --- /dev/null +++ b/core/tests/swfs/avm2/function_to_locale_string/Test.as @@ -0,0 +1,16 @@ +package { + public class Test {} +} + +var freeFunction = function() { +}; + +function namedFunction() { + +} + +trace("//freeFunction.toLocaleString()"); +trace(freeFunction.toLocaleString()); + +trace("//namedFunction.toLocaleString()"); +trace(namedFunction.toLocaleString()); \ No newline at end of file diff --git a/core/tests/swfs/avm2/function_to_locale_string/output.txt b/core/tests/swfs/avm2/function_to_locale_string/output.txt new file mode 100644 index 000000000000..bd1c22247789 --- /dev/null +++ b/core/tests/swfs/avm2/function_to_locale_string/output.txt @@ -0,0 +1,4 @@ +//freeFunction.toLocaleString() +function Function() {} +//namedFunction.toLocaleString() +function Function() {} diff --git a/core/tests/swfs/avm2/function_to_locale_string/test.fla b/core/tests/swfs/avm2/function_to_locale_string/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..d382fbe3e8ab454910c617c4d2ff9d879207277d GIT binary patch literal 4031 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 Date: Sun, 8 Mar 2020 22:39:32 -0400 Subject: [PATCH 136/189] Since we have an `es3_inheritance` test now, rename the existing inheritance test to `es4_inheritance`. --- core/tests/regression_tests.rs | 4 ++-- .../avm2/{inheritance => es4_inheritance}/Test.as | 0 .../{inheritance => es4_inheritance}/output.txt | 0 .../avm2/{inheritance => es4_inheritance}/test.fla | Bin .../avm2/{inheritance => es4_inheritance}/test.swf | Bin 5 files changed, 2 insertions(+), 2 deletions(-) rename core/tests/swfs/avm2/{inheritance => es4_inheritance}/Test.as (100%) rename core/tests/swfs/avm2/{inheritance => es4_inheritance}/output.txt (100%) rename core/tests/swfs/avm2/{inheritance => es4_inheritance}/test.fla (100%) rename core/tests/swfs/avm2/{inheritance => es4_inheritance}/test.swf (100%) diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 209fa937d5be..722927ab5b6c 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -232,7 +232,8 @@ swf_tests! { (as3_function_call_via_call, "avm2/function_call_via_call", 1), (as3_constructor_call, "avm2/constructor_call", 1), (as3_class_methods, "avm2/class_methods", 1), - (as3_inheritance, "avm2/inheritance", 1), + (as3_es3_inheritance, "avm2/es3_inheritance", 1), + (as3_es4_inheritance, "avm2/es4_inheritance", 1), (as3_stored_properties, "avm2/stored_properties", 1), (as3_virtual_properties, "avm2/virtual_properties", 1), (as3_es4_oop_prototypes, "avm2/es4_oop_prototypes", 1), @@ -251,7 +252,6 @@ swf_tests! { (as3_object_to_locale_string, "avm2/object_to_locale_string", 1), (as3_function_to_locale_string, "avm2/function_to_locale_string", 1), (as3_class_to_locale_string, "avm2/class_to_locale_string", 1), - (as3_es3_inheritance, "avm2/es3_inheritance", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/inheritance/Test.as b/core/tests/swfs/avm2/es4_inheritance/Test.as similarity index 100% rename from core/tests/swfs/avm2/inheritance/Test.as rename to core/tests/swfs/avm2/es4_inheritance/Test.as diff --git a/core/tests/swfs/avm2/inheritance/output.txt b/core/tests/swfs/avm2/es4_inheritance/output.txt similarity index 100% rename from core/tests/swfs/avm2/inheritance/output.txt rename to core/tests/swfs/avm2/es4_inheritance/output.txt diff --git a/core/tests/swfs/avm2/inheritance/test.fla b/core/tests/swfs/avm2/es4_inheritance/test.fla similarity index 100% rename from core/tests/swfs/avm2/inheritance/test.fla rename to core/tests/swfs/avm2/es4_inheritance/test.fla diff --git a/core/tests/swfs/avm2/inheritance/test.swf b/core/tests/swfs/avm2/es4_inheritance/test.swf similarity index 100% rename from core/tests/swfs/avm2/inheritance/test.swf rename to core/tests/swfs/avm2/es4_inheritance/test.swf From b4d907bf2e5a4a19520b6f40e791fb90b3d19701 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 22:48:54 -0400 Subject: [PATCH 137/189] Implement `strictequals`. --- core/src/avm2.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 4b27b8fc4190..2de55d679582 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -496,6 +496,7 @@ impl<'gc> Avm2<'gc> { 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), + Op::StrictEquals => self.op_strict_equals(), Op::HasNext => self.op_has_next(), Op::HasNext2 { object_register, @@ -1417,6 +1418,15 @@ impl<'gc> Avm2<'gc> { Ok(()) } + fn op_strict_equals(&mut self) -> Result<(), Error> { + let value2 = self.pop(); + let value1 = self.pop(); + + self.push(value1 == value2); + + Ok(()) + } + fn op_has_next(&mut self) -> Result<(), Error> { //TODO: After adding ints, change this to ints. let cur_index = self.pop().as_number()?; From 03a240ebcd30e7f5fa3371c00b9052a82a503c7e Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 22:49:52 -0400 Subject: [PATCH 138/189] Add tests for `valueOf`. --- core/tests/regression_tests.rs | 3 +++ core/tests/swfs/avm2/class_value_of/Test.as | 10 ++++++++++ core/tests/swfs/avm2/class_value_of/output.txt | 2 ++ core/tests/swfs/avm2/class_value_of/test.fla | Bin 0 -> 4031 bytes core/tests/swfs/avm2/class_value_of/test.swf | Bin 0 -> 1007 bytes core/tests/swfs/avm2/function_value_of/Test.as | 16 ++++++++++++++++ .../swfs/avm2/function_value_of/output.txt | 4 ++++ core/tests/swfs/avm2/function_value_of/test.fla | Bin 0 -> 4031 bytes core/tests/swfs/avm2/function_value_of/test.swf | Bin 0 -> 1045 bytes core/tests/swfs/avm2/object_value_of/Test.as | 8 ++++++++ core/tests/swfs/avm2/object_value_of/output.txt | 2 ++ core/tests/swfs/avm2/object_value_of/test.fla | Bin 0 -> 4031 bytes core/tests/swfs/avm2/object_value_of/test.swf | Bin 0 -> 977 bytes 13 files changed, 45 insertions(+) create mode 100644 core/tests/swfs/avm2/class_value_of/Test.as create mode 100644 core/tests/swfs/avm2/class_value_of/output.txt create mode 100644 core/tests/swfs/avm2/class_value_of/test.fla create mode 100644 core/tests/swfs/avm2/class_value_of/test.swf create mode 100644 core/tests/swfs/avm2/function_value_of/Test.as create mode 100644 core/tests/swfs/avm2/function_value_of/output.txt create mode 100644 core/tests/swfs/avm2/function_value_of/test.fla create mode 100644 core/tests/swfs/avm2/function_value_of/test.swf create mode 100644 core/tests/swfs/avm2/object_value_of/Test.as create mode 100644 core/tests/swfs/avm2/object_value_of/output.txt create mode 100644 core/tests/swfs/avm2/object_value_of/test.fla create mode 100644 core/tests/swfs/avm2/object_value_of/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 722927ab5b6c..a691b002a48b 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -252,6 +252,9 @@ swf_tests! { (as3_object_to_locale_string, "avm2/object_to_locale_string", 1), (as3_function_to_locale_string, "avm2/function_to_locale_string", 1), (as3_class_to_locale_string, "avm2/class_to_locale_string", 1), + (as3_object_value_of, "avm2/object_value_of", 1), + (as3_function_value_of, "avm2/function_value_of", 1), + (as3_class_value_of, "avm2/class_value_of", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/class_value_of/Test.as b/core/tests/swfs/avm2/class_value_of/Test.as new file mode 100644 index 000000000000..7f8b320edcd1 --- /dev/null +++ b/core/tests/swfs/avm2/class_value_of/Test.as @@ -0,0 +1,10 @@ +package { + public class Test {} +} + +class ES4Class { + +} + +trace("//ES4Class.valueOf() === ES4Class"); +trace(ES4Class.valueOf() === ES4Class); \ No newline at end of file diff --git a/core/tests/swfs/avm2/class_value_of/output.txt b/core/tests/swfs/avm2/class_value_of/output.txt new file mode 100644 index 000000000000..199f9dd8a297 --- /dev/null +++ b/core/tests/swfs/avm2/class_value_of/output.txt @@ -0,0 +1,2 @@ +//ES4Class.valueOf() === ES4Class +true diff --git a/core/tests/swfs/avm2/class_value_of/test.fla b/core/tests/swfs/avm2/class_value_of/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..d382fbe3e8ab454910c617c4d2ff9d879207277d GIT binary patch literal 4031 zcmbtXc{r5&7oTj6ELlUAjD74B2HCePWwH%f24gH4>loSB$&w{y2~C#Bnk`Wm(M;JR zLW)G9g&1VXSo+P}?ycK>>i+XP^PK1XzVG*Z&i6d?p7TDR57d;Jh64Zq0stXZ)=+*X zH)?tS06=l_BR~)m9V8p#j)Gd4+uF(KSt>z)3MWT=H%hpfv95)Vg`M2*qbP|_8(8Vc z7@slxk1$qpm_No9<%RYPLi+flT*!s|6;1^N{#ojAc6)C=Y5-uF0RT8m4%at7t?!4x z_#l1B75ku?a_r3~kIC)y7>FurN<0%gL*mAie|L7NbtOo)ETu(|mCPt$n50>niE%NVW>0(A4|B?ME5wyPPul14{qKB9&?6?ndTq$0I($Ew#oFs5(Kt+AgOpsHY}AghZ@nFOVkgA15A z&Pvy>(LgOFVf(M@2Lf!=PS98FOy3f*?Q=`i(&g^;t}Yd05{!tmR4vm;J|>!XZD7ip zFydOJ3IK!Jj!Ia7qE|?`0XJlA8t#FeKtR5#7;SIoqVB?dF~W{N<|ygfK4)*B4bVUN z4~XVT$jPD*JB94U#5oaQ($fTD|O6B+k;H#R{G62D>l#JD-%Mc zlFlW=HMlX4FkiKu6@l0H1+8XW<1PxWzqiBfuORI$swgH?Mm2?miu0FRqaPUEmP^$@V-!<#3xX<#`Yh-%Zd79>S0+k zH7aPa;f?K6{HY79Ct9pg5JM?j^yyY%?xuMD*6WgtlRmV1{q<@#uWr6&Lmf3#sUIUR zQj10!X8D~5BIV>4nm;p^Bj1Cj0_MT%0%|$VqnP*?OU}v56_=jN$l%X)3)t6iE(X5E zyb`qWzKrR<1BXnV+;}NoO_KJFS~1^0@NGP){+fC6?0h#)^6L9|`XGZ`++2cODC6`u zDeJm9N zFZklK&9ff^O}E12G9*ENX(lE<#xW4auh*03|#`FVxi#|u_Ny6E>p%)<|?_$TbnnFZ{V8UG-#oD&7V0;06%f?4X zcgl|tlTy#RB?Fz3rMD$n^XDyf7!F$G4=b9mb_0-LLfIzrI>-^2LAgJhWN~8R` z{Z#%jQvA8b5zq0#$qnC{NuPU96Cb%ixN)#~1!;4QisjvinY8?k#tA#`(F!GA0nfF? zfe8szFx`?(WB4$t9p-3Mq0YV#0k2Y*s`SQ8m)9yR5O#w-<{m9%wd7cgZx^)ee~b;Y zukD0osC7%O!mFMfR!D54nGF-r6ut^HcKH(iC?{?)=nQqos*&UF#%_z)QI7;q9)b$i z11S6n!!)d*KgnWvdHe)+_$^OK!IsfD3%}l7rU3V(98iA33e&JWwmhkYr|MD0_!=j@ zq;|bHDwGNOjJ-<)l$uPtiEU5@u45aHq;6pqSPKev4Ekl%Dv|oIQuGl9)_)fS_6Fw zl`EBG?*x90$)6)wz`q8`-FAPKnJX7>!@wbBfrsTv=bZF>eqj9W>Luy9B|X-#8t|Jx z9IlISgy?yR#Yq<)$`5?}g{B70$M8|HnpUV3XN7fQK+f@ExkaD7JESYKz8s%o*=szP zM2s`}@>IzL7|oQwXQ@{e3yL!HT-SFy7BiEKTyhpm@-kCF9PMwq2B}G&vx{@;hB&5V zXW1bZ^7KP!1|@`P%M*wzhr}vsGWQTGE}No>pA}PtH}&0Ja+p|+8hH=x4-j%SXYF#? zB+~9bt58`uj?;|D8x%bK-mSs?)aad;V^d$;%h2%h*>wJ?t(+iq;*qVSTW=#kbwbJ( zvc17Cn`FCZYXfczO%J0^setEh7jtL|F$%3w-7n_Y=kdtut?)9zI~$IoVtFl&Wu#_u zZqpsL4FM(iXfxCRI}j3tV(A^l#tZW5;c6SlP8EVZxP1>+b!^W%WPQ2Q6H}|0fDZBY zhf;8%9hg9 zjd)yThul*MoYuY(pT&^>B-_Wp5~W&LoP;ST16DEbGh&Q3u#wcaUQHeMkn?SkXz%1B ztd^ehz<)N8OesQD`KUY#fg(@vgpm%H-kbapP&(UV`Mb}S^RmG4 z7w0mxJE&{4O+b&hsLrITp8~n}<>i2F(UP56m0{><#j>qS)XMXVAH0&@*wQ(fuDcvO z;l(1l$o*BLpn^jJGS$GEqA~;LHh|!(-T*VhS6!E&B1b}p))yoPFDY${?2Iynx+Oh$ zoneLRj+syHsfpBUQ8)7wV-)A;%uQ2(KZCrGr>VOcP8aN9Ml{0Lb?Bh2-)~%~X0hJQ zB!Q&`wX6w;PboWa!~CAt3Pp-rRC8qOgJMJvRU-k<1-D*Q^H>mZfipe2+D5W)gY1>y zsGDya2&e;cL}S&HH@%N3GLI`?sB~X#p?RNPL=Ep*jygEmM$nZiO=igJPkU$DgyL#^ zP5P9gzn#7c4{6m3?hGNOajna^fg-v{nrG|PRK>@28kaEVrsztAw&{Vw`HZ6h5U@AOArZ6Apf@;O28c#ROMN z&v+}8E=?%SsWT!)uGKfx(`WlRQ6<`TWO}>!98C9!%$pdPRMu0>!_98<$e4dbt(;`4H%MmI(~I&$Is7h+C+IrcYM z_@*holMC{KjlwVdT)j}pe{zMN<--0Tdo-C>04Po&7p`8ua+aY!`bdAIubV8w1>uSG zAU~cvNGAVek-|<+szP2-ti6WJMSfB!tykY9@Q`;XPEja6USw7i>W_5s_eXglT!Os( zeC2}4XT#v?kD>eUd&v-yyHy{^)y{C!niH29wZsskqxw-i+55PiZFk9X%ADiW$-W^b<_Qv;re5bpwF86l^^nJYoKFp{r zAA-6?T(Y3Ko^~9w z`qs4FW4lKj+a>xf+wsjF+1&V#tVvb~<)ovtpNU14J83jl%spTSjVmQ4Dq~_TDnFn>H$m;H4wNdlhq~Q-b9fve5&nJy=LC^@^cY=m_(c5ZRq3G#_ z=?uuJ*V)~TEiH8^?N@c(YYNdm@bX2Rwk`hW}bnIVEYJ094aXr$uO&rB8p~>5* z()N3C{l0IztPSV4&|?zBk8=aF*PlSd4eo3PR{AmQ+Hv7^(8w2iF?kvgd%2O z$NoO{DE4>RbMp62NA>n}OyB40%rYhA`{e6rj1Fndb18c{#9z3dbm+3)nHIG#NR&AD zFGc1jUq*F59Kv~%T3zC&%y#=AeB*V?xBGPY9}dMNN`F!!c~33-nK$j>ElJV#xBve~ z(b&*nK@w3x(Nry2Naxi&L{nl~%%g%fi;CKuHjhf$BW(dKYD?NG85$URiyzw*FL;p(I0D=CLd$WGN-143;34IgA-4;GBT5fM*0OiTFzqXGA&s zl`OrGrLSeFDoeXK`fubwH1hEn-U7SDDCQxDITr977FdoTA{j7Y2&8bS2snX*ES(ns zL{Jb|Hh_>9_@VKJt!MiZm7y^_1lQS-${6l$lK*T{;5o>@92yLAO}WJxEp!4(UC6N5 z#35ErV5!S9jGOAS=_3`6l&0EB93(+2@yCG*fBWmt@0oQ7u@Z@}&MIn}mDEOjGxb9X ztd9gn%RHmCkJ`=j_09;E!LXA#N^>KQ%_8O!Zww6rLd~qvnlf5Bqcv@`W{j4|ekWWh dy1K&kH=_bu@@=jd_wK<{3|_LCzX8XW^d?#Z^Kk$G literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/function_value_of/Test.as b/core/tests/swfs/avm2/function_value_of/Test.as new file mode 100644 index 000000000000..038d75e6c378 --- /dev/null +++ b/core/tests/swfs/avm2/function_value_of/Test.as @@ -0,0 +1,16 @@ +package { + public class Test {} +} + +var freeFunction = function() { +}; + +function namedFunction() { + +} + +trace("//freeFunction.valueOf() === freeFunction"); +trace(freeFunction.valueOf() === freeFunction); + +trace("//namedFunction.valueOf() === namedFunction"); +trace(namedFunction.valueOf() === namedFunction); \ No newline at end of file diff --git a/core/tests/swfs/avm2/function_value_of/output.txt b/core/tests/swfs/avm2/function_value_of/output.txt new file mode 100644 index 000000000000..5be28b9dbc08 --- /dev/null +++ b/core/tests/swfs/avm2/function_value_of/output.txt @@ -0,0 +1,4 @@ +//freeFunction.valueOf() === freeFunction +true +//namedFunction.valueOf() === namedFunction +true diff --git a/core/tests/swfs/avm2/function_value_of/test.fla b/core/tests/swfs/avm2/function_value_of/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..d382fbe3e8ab454910c617c4d2ff9d879207277d GIT binary patch literal 4031 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 Pe(;KeUGDh{JBAX|5RnG@ literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/object_value_of/Test.as b/core/tests/swfs/avm2/object_value_of/Test.as new file mode 100644 index 000000000000..38c66a8af8c7 --- /dev/null +++ b/core/tests/swfs/avm2/object_value_of/Test.as @@ -0,0 +1,8 @@ +package { + public class Test {} +} + +var obj = new Object(); + +trace("//obj.valueOf() === obj"); +trace(obj.valueOf() === obj); \ No newline at end of file diff --git a/core/tests/swfs/avm2/object_value_of/output.txt b/core/tests/swfs/avm2/object_value_of/output.txt new file mode 100644 index 000000000000..03efbeb6ffea --- /dev/null +++ b/core/tests/swfs/avm2/object_value_of/output.txt @@ -0,0 +1,2 @@ +//obj.valueOf() === obj +true diff --git a/core/tests/swfs/avm2/object_value_of/test.fla b/core/tests/swfs/avm2/object_value_of/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..d382fbe3e8ab454910c617c4d2ff9d879207277d GIT binary patch literal 4031 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 literal 0 HcmV?d00001 From ecfd16cec974174d83835f2f7205049cdb11dd29 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 23:09:30 -0400 Subject: [PATCH 139/189] Add global constants `undefined`, `null`, and `NaN`. --- core/src/avm2/globals.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index a51716dfd43f..842f9a03f421 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -9,6 +9,7 @@ use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use gc_arena::{Collect, MutationContext}; +use std::f64::NAN; mod class; mod flash; @@ -76,6 +77,17 @@ fn class<'gc>( .unwrap(); } +/// Add a builtin constant to the global scope. +fn constant<'gc>( + mc: MutationContext<'gc, '_>, + mut global_scope: Object<'gc>, + package: &str, + name: &str, + value: Value<'gc>, +) { + global_scope.install_const(mc, QName::new(Namespace::package(package), name), 0, value) +} + /// Construct a new global scope. /// /// This function returns both the global scope object, as well as all builtin @@ -120,6 +132,9 @@ pub fn construct_global_scope<'gc>( fn_proto, ); function(mc, gs, "", "trace", trace, fn_proto); + constant(mc, gs, "", "undefined", Value::Undefined); + constant(mc, gs, "", "null", Value::Null); + constant(mc, gs, "", "NaN", NAN.into()); // package `flash.events` let eventdispatcher_proto = From 34b3bbae6311f575fbf7d2d6f9ff159c3e062451 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 23:10:42 -0400 Subject: [PATCH 140/189] *Correctly* implement `ifstricteq` and `ifstrictne`. The previous implementation suffered from copypasta and was attempting to assert that both of it's values were `bool`. --- core/src/avm2.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 2de55d679582..9f634ee65c69 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1393,8 +1393,8 @@ impl<'gc> Avm2<'gc> { offset: i32, reader: &mut Reader>, ) -> Result<(), Error> { - let value2 = self.pop().as_bool()?; - let value1 = self.pop().as_bool()?; + let value2 = self.pop(); + let value1 = self.pop(); if value1 == value2 { reader.seek(offset as i64)?; @@ -1408,8 +1408,8 @@ impl<'gc> Avm2<'gc> { offset: i32, reader: &mut Reader>, ) -> Result<(), Error> { - let value2 = self.pop().as_bool()?; - let value1 = self.pop().as_bool()?; + let value2 = self.pop(); + let value1 = self.pop(); if value1 != value2 { reader.seek(offset as i64)?; From 34ab8c8ce641c05616bf6497372107e0b89ded4e Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 23:12:15 -0400 Subject: [PATCH 141/189] `NaN` is not special-cased in AS3. --- core/src/avm2/value.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 7754aa8e6f63..ef92a4808817 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -119,9 +119,7 @@ impl PartialEq for Value<'_> { _ => false, }, Value::Number(value) => match other { - Value::Number(other_value) => { - (value == other_value) || (value.is_nan() && other_value.is_nan()) - } + Value::Number(other_value) => value == other_value, _ => false, }, Value::String(value) => match other { From 6117288fe264409d62e67f028f1c4cc25e32ffd6 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sun, 8 Mar 2020 23:12:52 -0400 Subject: [PATCH 142/189] Add tests for `ifstricteq`, `ifstrictne`, and `strictequals`. --- core/tests/regression_tests.rs | 3 + core/tests/swfs/avm2/if_stricteq/Test.as | 72 ++++++++++++++++++ core/tests/swfs/avm2/if_stricteq/output.txt | 6 ++ core/tests/swfs/avm2/if_stricteq/test.fla | Bin 0 -> 3983 bytes core/tests/swfs/avm2/if_stricteq/test.swf | Bin 0 -> 1189 bytes core/tests/swfs/avm2/if_strictne/Test.as | 72 ++++++++++++++++++ core/tests/swfs/avm2/if_strictne/output.txt | 11 +++ core/tests/swfs/avm2/if_strictne/test.fla | Bin 0 -> 3983 bytes core/tests/swfs/avm2/if_strictne/test.swf | Bin 0 -> 1186 bytes core/tests/swfs/avm2/strict_equality/Test.as | 54 +++++++++++++ .../swfs/avm2/strict_equality/output.txt | 34 +++++++++ core/tests/swfs/avm2/strict_equality/test.fla | Bin 0 -> 3983 bytes core/tests/swfs/avm2/strict_equality/test.swf | Bin 0 -> 1208 bytes 13 files changed, 252 insertions(+) create mode 100644 core/tests/swfs/avm2/if_stricteq/Test.as create mode 100644 core/tests/swfs/avm2/if_stricteq/output.txt create mode 100644 core/tests/swfs/avm2/if_stricteq/test.fla create mode 100644 core/tests/swfs/avm2/if_stricteq/test.swf create mode 100644 core/tests/swfs/avm2/if_strictne/Test.as create mode 100644 core/tests/swfs/avm2/if_strictne/output.txt create mode 100644 core/tests/swfs/avm2/if_strictne/test.fla create mode 100644 core/tests/swfs/avm2/if_strictne/test.swf create mode 100644 core/tests/swfs/avm2/strict_equality/Test.as create mode 100644 core/tests/swfs/avm2/strict_equality/output.txt create mode 100644 core/tests/swfs/avm2/strict_equality/test.fla create mode 100644 core/tests/swfs/avm2/strict_equality/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index a691b002a48b..1054639474bd 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -255,6 +255,9 @@ swf_tests! { (as3_object_value_of, "avm2/object_value_of", 1), (as3_function_value_of, "avm2/function_value_of", 1), (as3_class_value_of, "avm2/class_value_of", 1), + (as3_if_stricteq, "avm2/if_stricteq", 1), + (as3_if_strictne, "avm2/if_strictne", 1), + (as3_strict_equality, "avm2/strict_equality", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/if_stricteq/Test.as b/core/tests/swfs/avm2/if_stricteq/Test.as new file mode 100644 index 000000000000..fdeb8740fa28 --- /dev/null +++ b/core/tests/swfs/avm2/if_stricteq/Test.as @@ -0,0 +1,72 @@ +package { + public class Test {} +} + +if(2 === "2") +{ + trace("ERROR: 2 === \"2\""); +} +if(2 === 2) +{ + trace("2 === 2"); +} +if(2 === 5) +{ + trace("ERROR: 2 === 5"); +} +if(true === true) +{ + trace("true === true"); +} +if(false === false) +{ + trace("false === false"); +} +if(true === false) +{ + trace("ERROR: true === false"); +} +if(1 === true) +{ + trace("ERROR: 1 === true"); +} +if(0 === false) +{ + trace("ERROR: 0 === false"); +} +if("abc" === "abc") +{ + trace("\"abc\" === \"abc\""); +} +if(0 === undefined) +{ + trace("ERROR: 0 === undefined"); +} +if(undefined === undefined) +{ + trace("undefined === undefined"); +} +if(NaN === NaN) +{ + trace("NaN === NaN"); +} +if(undefined === NaN) +{ + trace("ERROR: undefined === NaN"); +} +if(0 === null) +{ + trace("ERROR: 0 === null"); +} +if(null === null) +{ + trace("null === null"); +} +if(undefined === null) +{ + trace("ERROR: undefined === null"); +} +if(NaN === null) +{ + trace("ERROR: NaN === null"); +} diff --git a/core/tests/swfs/avm2/if_stricteq/output.txt b/core/tests/swfs/avm2/if_stricteq/output.txt new file mode 100644 index 000000000000..63634f96e651 --- /dev/null +++ b/core/tests/swfs/avm2/if_stricteq/output.txt @@ -0,0 +1,6 @@ +2 === 2 +true === true +false === false +"abc" === "abc" +undefined === undefined +null === null diff --git a/core/tests/swfs/avm2/if_stricteq/test.fla b/core/tests/swfs/avm2/if_stricteq/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..bbbf05379d9a0f5b17213c2debc83d0b5adf3174 GIT binary patch literal 3983 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/if_stricteq/test.swf b/core/tests/swfs/avm2/if_stricteq/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..c1b15ed67e85d11305a9515385a95538d917fcff GIT binary patch literal 1189 zcmV;W1X}w;S5qsO2mk*-iR1eUtp}FCN8tf^=3Z9J{6MIn}XtXTF))9nb9Q0}|gOl=&T@ zB1Zc;86mXCefjd`MFF9OY=2g(X%(V9Y@NQ_Cxh)H*ZpCzHE@4)j*m`j=+jIB{4qLd z5r>p(2z{C^$P~bnMHM<7A>|$MgDM?X0vO(RY_oA>TFyJGOKi&_)e-G@W|!19{=;jW z3Ut}n@%c}r70A~f$#IVq0ROVmw)~!LepHU!fkpOgt2cAwdNG#5lfy=H&ux>Y*Y9*} z(rmdNY0?0{>HpgCo93WfZ~B2}wSxELm%eFR!N;8)noGs>Rlzp)?aYkDNl9i)d{WHx&{6s}paYw=zpGEU^rzr1YIO=|EZ9>O{Z@7e>+~ z=iiub67Yr;=3*2+=?;e6#JW!3wo`N%X|p#5QcFIvT<=2J{B=(&<2`Le=Ze$x1ZzY; zuxvli4u;uk5#P7YE!xBD!$SO@1GkrC8u2{W^RJZwaRNUbDIWsjIi?+jb)cif^W#jr zAZ|C>P8I)1d>kGh2Z*Ak1;@UlzGF-i*cpov7UmT9?F2o9*^X+VPW? zXZ3>Re>fE5TIMfHq$YOD{Vg~3=D!qUfB*jf_)68n>A(DF_XvGkJ6BSjIy|WNS6y;V|Ly!kBL&412 zS_ldNR0?KS8JGi5pkQ9ZEM@^nk&;CXF`s21vp_aWo2B7Oh}HnjQKxkc8A^yY0L@c$ z&rl*%1$3LDnh`hRA=&~|rf3_y7MDu-kT$Py*HWd@!CSf=2)p)j%sM5E-u zh%@pE$O;fBDUY!dXZ42i#}gSS?&^>~OT12?^`QV*Q;8_trSUkR#N$mm8^H*&f$r5ghLPVqc@F8R;j4~r1;G+&Ai$BkDKJp;?1(92{4 D5|Bo- literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/if_strictne/Test.as b/core/tests/swfs/avm2/if_strictne/Test.as new file mode 100644 index 000000000000..27e3f306e233 --- /dev/null +++ b/core/tests/swfs/avm2/if_strictne/Test.as @@ -0,0 +1,72 @@ +package { + public class Test {} +} + +if(2 !== "2") +{ + trace("2 !== \"2\""); +} +if(2 !== 2) +{ + trace("ERROR: 2 !== 2"); +} +if(2 !== 5) +{ + trace("2 !== 5"); +} +if(true !== true) +{ + trace("ERROR: true !== true"); +} +if(false !== false) +{ + trace("ERROR: false !== false"); +} +if(true !== false) +{ + trace("true !== false"); +} +if(1 !== true) +{ + trace("1 !== true"); +} +if(0 !== false) +{ + trace("0 !== false"); +} +if("abc" !== "abc") +{ + trace("ERROR: \"abc\" !== \"abc\""); +} +if(0 !== undefined) +{ + trace("0 !== undefined"); +} +if(undefined !== undefined) +{ + trace("ERROR: undefined !== undefined"); +} +if(NaN !== NaN) +{ + trace("NaN !== NaN"); +} +if(undefined !== NaN) +{ + trace("undefined !== NaN"); +} +if(0 !== null) +{ + trace("0 !== null"); +} +if(null !== null) +{ + trace("ERROR: null !== null"); +} +if(undefined !== null) +{ + trace("undefined !== null"); +} +if(NaN !== null) +{ + trace("NaN !== null"); +} diff --git a/core/tests/swfs/avm2/if_strictne/output.txt b/core/tests/swfs/avm2/if_strictne/output.txt new file mode 100644 index 000000000000..1cb050425656 --- /dev/null +++ b/core/tests/swfs/avm2/if_strictne/output.txt @@ -0,0 +1,11 @@ +2 !== "2" +2 !== 5 +true !== false +1 !== true +0 !== false +0 !== undefined +NaN !== NaN +undefined !== NaN +0 !== null +undefined !== null +NaN !== null diff --git a/core/tests/swfs/avm2/if_strictne/test.fla b/core/tests/swfs/avm2/if_strictne/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..bbbf05379d9a0f5b17213c2debc83d0b5adf3174 GIT binary patch literal 3983 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/if_strictne/test.swf b/core/tests/swfs/avm2/if_strictne/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..69ee612d5ebe71649b38acfad54de7c4722019c5 GIT binary patch literal 1186 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/strict_equality/test.swf b/core/tests/swfs/avm2/strict_equality/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..ebb82eda0b9e52fa4e5cdad4d54ac75ea40f97f3 GIT binary patch literal 1208 zcmV;p1V{TrS5qsP2mk-RGpjMV#Nv0$p`y_pUyhC1re&}=5d4lw;WE(@%cBURn=YM{C_UzFft~YB(;fvI$<*`=#Yo^|N-l>iMo|`0wb?UEMPM_YWVkT1vVX?c3tU{kc=$ z?^IM(Z#oTHGMu)$bG)IJHEm06bWO`QZP9migT_>~T-e^;F3gwbaakUCmufSms=nK$ zfx|>bRa?5{g&kVT*T$3ptZka($*KX1l>_$@_swq_+ z>Z>u3`L+H!U|@N;#;?Z$iw|zkH09yVP!4C;Ai!({y#ya?3$$#**O(4(&FxZc1yqqgrQ`_8#fU0b*M(^}9i>Uz=8 zJE2ayzs8B>G@$<Db{rnx*w4wheL)U>NsN0YA^mb>& zqqcWhruW|SX}e#E4|OIlWeN5rGvZSF1a^spq-*x;3-+jA-+9~0UO6I_;Srg*%8#LB zpBAsez0igyaM!VYXbyE}uLd33uy?xQn`bl(oc@<2H%6OLdBzMI*L~xZx=Fp++-Ds> zHe9pgFZ{zxG3uqRrAS;^E%Ue9l&?QhBz*k4|KrP0T~TmOR3t^t#j=u^%wm!fW8xCI ztt^ujWmQ=tca)N%5=~iG%E|`0OE$?Cxu@J$9w-&^Pyf=(>G>r&KBn8#f9Rn#iiTJ;}RAn9Fce; z2~jB$mqjc`I62MBGrU~jWl@ly336PJ(g%|8ToMi?;e{l;l)~>hheFFP9xfr&9Oh&W z1TPQ~iCDyJfJYn$^&u3&ynwNUFj)RVJAf&`#ps20afm)AdiQ{EJcs!|xeE<^t`@0t zT3vdJW==8qVvVe2jtK5WYNW`mjeTUA032sRW^g57F<=u+#2G7!0Zam%WFl1~i;Dru z08*G3tC6Lp08IeOGBF8JA<6>EF_D98IGmXWHpRp=Fcikk0LwFR6U6k|bN~yWZ!j^( z0_$=B7XaR5qF5t!xepfs&N8tCFj|iWa2enn6DvVv2-g74GjWIIECfsiw!nl|BZUx^ z0Tr3pU~@yZ3G5aVTWpcS$9;l-{p0uN&?JoIXxJ3!Q8bo<4lMTgTI@v(E%yY#A`e&z zdR2>m-tXZex@aYi;#`k|(Fn9yUcJyLVl7K*^^{gmYxRs)AJggr{NLi4@OR38{ Date: Sun, 8 Mar 2020 23:16:50 -0400 Subject: [PATCH 143/189] Silence this warning about occupied slots being an unused variant. I don't know if I'm missing something, but I'm pretty sure this variant is reachable via `TObject::install_slot`. --- core/src/avm2/slot.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/avm2/slot.rs b/core/src/avm2/slot.rs index 1ddbdc269452..2456363ed6f5 100644 --- a/core/src/avm2/slot.rs +++ b/core/src/avm2/slot.rs @@ -8,6 +8,7 @@ use gc_arena::{Collect, CollectionContext}; /// Represents a single slot on an object. #[derive(Clone, Debug)] +#[allow(dead_code)] pub enum Slot<'gc> { /// An unoccupied slot. /// @@ -16,6 +17,8 @@ pub enum Slot<'gc> { Unoccupied, /// An occupied slot. + /// + /// TODO: For some reason, rustc believes this variant is unused. Occupied { value: Value<'gc>, attributes: EnumSet, From 3362ec09e801f52fd430645e9d6e2750f818b4d1 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 20 Apr 2020 21:52:43 -0400 Subject: [PATCH 144/189] chore: Clippy conformance --- core/src/player.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/player.rs b/core/src/player.rs index 93b36bf11fb6..4b8a61e17e5a 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -281,7 +281,7 @@ impl Player { storage, }; - player.mutate_with_update_context(|avm1, avm2, context| { + player.mutate_with_update_context(|avm1, _avm2, context| { let mut root: DisplayObject = MovieClip::from_movie(context.gc_context, movie.clone()).into(); root.set_depth(context.gc_context, 0); From 4cd30455debcf631468bc1d7c9a3acd5a9505979 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 22 Jun 2020 20:56:18 -0400 Subject: [PATCH 145/189] Excise `ReturnValue<'gc>` from all `TObject` methods. Inspired by Dinnerbone's PR doing the exact same thing to AVM1. On AVM2 we have a bit of a subtle issue: the base implementation of `set_property_local` and `init_property_local` *must* return `ReturnValue`s to avoid double-borrows. Each implementation of `TObject` must resolve them before returning. --- core/src/avm2.rs | 72 ++++++++++--------------------- core/src/avm2/function.rs | 30 ++++++++----- core/src/avm2/globals/function.rs | 6 ++- core/src/avm2/object.rs | 42 +++++------------- core/src/avm2/scope.rs | 11 +++-- core/src/avm2/script_object.rs | 31 ++++++++----- 6 files changed, 82 insertions(+), 110 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 9f634ee65c69..85e875d70441 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -641,10 +641,9 @@ impl<'gc> Avm2<'gc> { 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)?; - function - .call(receiver, &args, self, context, base_proto)? - .push(self); + self.push(value); Ok(()) } @@ -661,10 +660,9 @@ impl<'gc> Avm2<'gc> { .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)?; - function? - .call(Some(receiver), &args, self, context, base_proto)? - .push(self); + self.push(value); Ok(()) } @@ -685,12 +683,10 @@ impl<'gc> Avm2<'gc> { let base_proto = receiver.get_base_proto(&name)?; let function = receiver .get_property(receiver, &name, self, context)? - .resolve(self, context)? .as_object()?; + let value = function.call(Some(receiver), &args, self, context, base_proto)?; - function - .call(Some(receiver), &args, self, context, base_proto)? - .push(self); + self.push(value); Ok(()) } @@ -709,10 +705,10 @@ impl<'gc> Avm2<'gc> { .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); let function = receiver .get_property(receiver, &name?, self, context)? - .resolve(self, context)? .as_object()?; + let value = function.call(None, &args, self, context, None)?; - function.call(None, &args, self, context, None)?.push(self); + self.push(value); Ok(()) } @@ -733,12 +729,9 @@ impl<'gc> Avm2<'gc> { let base_proto = receiver.get_base_proto(&name)?; let function = receiver .get_property(receiver, &name, self, context)? - .resolve(self, context)? .as_object()?; - function - .call(Some(receiver), &args, self, context, base_proto)? - .resolve(self, context)?; + function.call(Some(receiver), &args, self, context, base_proto)?; Ok(()) } @@ -760,10 +753,9 @@ impl<'gc> Avm2<'gc> { self.system_prototypes.function, None, ); + let value = function.call(Some(receiver), &args, self, context, receiver.proto())?; - function - .call(Some(receiver), &args, self, context, receiver.proto())? - .push(self); + self.push(value); Ok(()) } @@ -796,12 +788,11 @@ impl<'gc> Avm2<'gc> { let function = base .get_property(receiver, &name?, self, context)? - .resolve(self, context)? .as_object()?; - function - .call(Some(receiver), &args, self, context, Some(base_proto))? - .push(self); + let value = function.call(Some(receiver), &args, self, context, Some(base_proto))?; + + self.push(value); Ok(()) } @@ -834,12 +825,9 @@ impl<'gc> Avm2<'gc> { let function = base .get_property(receiver, &name?, self, context)? - .resolve(self, context)? .as_object()?; - function - .call(Some(receiver), &args, self, context, Some(base_proto))? - .resolve(self, context)?; + function.call(Some(receiver), &args, self, context, Some(base_proto))?; Ok(()) } @@ -866,9 +854,7 @@ impl<'gc> Avm2<'gc> { format!("Could not resolve property {:?}", multiname.local_name()).into() }); - let value = object - .get_property(object, &name?, self, context)? - .resolve(self, context)?; + let value = object.get_property(object, &name?, self, context)?; self.push(value); Ok(()) @@ -960,9 +946,7 @@ impl<'gc> Avm2<'gc> { .into() }); - let value = base - .get_property(object, &name?, self, context)? - .resolve(self, context)?; + let value = base.get_property(object, &name?, self, context)?; self.push(value); @@ -1191,12 +1175,10 @@ impl<'gc> Avm2<'gc> { self, context, )? - .resolve(self, context)? .as_object()?; let object = proto.construct(self, context, &args)?; - ctor.call(Some(object), &args, self, context, object.proto())? - .resolve(self, context)?; + ctor.call(Some(object), &args, self, context, object.proto())?; self.push(object); @@ -1219,7 +1201,6 @@ impl<'gc> Avm2<'gc> { }); let mut ctor = source .get_property(source, &ctor_name?, self, context)? - .resolve(self, context)? .as_object()?; let proto = ctor .get_property( @@ -1228,12 +1209,10 @@ impl<'gc> Avm2<'gc> { self, context, )? - .resolve(self, context)? .as_object()?; let object = proto.construct(self, context, &args)?; - ctor.call(Some(object), &args, self, context, Some(proto))? - .resolve(self, context)?; + ctor.call(Some(object), &args, self, context, Some(proto))?; self.push(object); @@ -1263,12 +1242,9 @@ impl<'gc> Avm2<'gc> { let function = base_proto .get_property(receiver, &name, self, context)? - .resolve(self, context)? .as_object()?; - function - .call(Some(receiver), &args, self, context, Some(base_proto))? - .resolve(self, context)?; + function.call(Some(receiver), &args, self, context, Some(base_proto))?; Ok(()) } @@ -1345,9 +1321,7 @@ impl<'gc> Avm2<'gc> { let (new_class, class_init) = FunctionObject::from_abc_class(self, context, class_entry, base_class, scope)?; - class_init - .call(Some(new_class), &[], self, context, None)? - .resolve(self, context)?; + class_init.call(Some(new_class), &[], self, context, None)?; self.push(new_class); @@ -1500,9 +1474,7 @@ impl<'gc> Avm2<'gc> { let name = object.get_enumerant_name(cur_index as u32); let value = if let Some(name) = name { - object - .get_property(object, &name, self, context)? - .resolve(self, context)? + object.get_property(object, &name, self, context)? } else { Value::Undefined }; diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index ee763390d0d2..03b830dcd1fa 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -284,7 +284,6 @@ impl<'gc> FunctionObject<'gc> { avm, context, )? - .resolve(avm, context)? .as_object() .map_err(|_| { let super_name = QName::from_abc_multiname( @@ -443,7 +442,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error> { + ) -> Result, Error> { self.0 .read() .base @@ -457,11 +456,16 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error> { - self.0 + ) -> Result<(), Error> { + let rv = self + .0 .write(context.gc_context) .base - .set_property_local(reciever, name, value, avm, context) + .set_property_local(reciever, name, value, avm, context)?; + + rv.resolve(avm, context)?; + + Ok(()) } fn init_property_local( @@ -471,11 +475,16 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error> { - self.0 + ) -> Result<(), Error> { + let rv = self + .0 .write(context.gc_context) .base - .init_property_local(reciever, name, value, avm, context) + .init_property_local(reciever, name, value, avm, context)?; + + rv.resolve(avm, context)?; + + Ok(()) } fn is_property_overwritable(self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool { @@ -603,9 +612,10 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, base_proto: Option>, - ) -> Result, Error> { + ) -> Result, Error> { if let Some(exec) = &self.0.read().exec { - exec.exec(reciever, arguments, avm, context, base_proto) + exec.exec(reciever, arguments, avm, context, base_proto)? + .resolve(avm, context) } else { Err("Not a callable function!".into()) } diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs index 7a07d31af830..4f3fba60b1cb 100644 --- a/core/src/avm2/globals/function.rs +++ b/core/src/avm2/globals/function.rs @@ -32,9 +32,11 @@ fn call<'gc>( if let Some(func) = func { if args.len() > 1 { - func.call(this, &args[1..], avm, context, base_proto) + Ok(func + .call(this, &args[1..], avm, context, base_proto)? + .into()) } else { - func.call(this, &[], avm, context, base_proto) + Ok(func.call(this, &[], avm, context, base_proto)?.into()) } } else { Err("Not a callable function".into()) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 4f4ac9120e9b..908964ff06c3 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -2,7 +2,6 @@ use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, Executable, FunctionObject}; use crate::avm2::names::{Multiname, Namespace, QName}; -use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::{abc_default_value, Value}; @@ -33,7 +32,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error>; + ) -> Result, Error>; /// Retrieve a property by it's QName. fn get_property( @@ -42,7 +41,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error> { + ) -> Result, Error> { if !self.has_instantiated_property(name) { for abc_trait in self.get_trait(name)? { self.install_trait(avm, context, &abc_trait, reciever)?; @@ -59,7 +58,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy return proto.get_property(reciever, name, avm, context); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } /// Retrieve the base prototype that a particular QName trait is defined in. @@ -79,9 +78,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } /// Set a property on this specific object. - /// - /// This function returns a `ReturnValue` which should be resolved. The - /// resulting `Value` is unimportant and should be discarded. fn set_property_local( self, reciever: Object<'gc>, @@ -89,7 +85,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error>; + ) -> Result<(), Error>; /// Set a property by it's QName. fn set_property( @@ -107,10 +103,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } if self.has_own_virtual_setter(name) { - self.set_property_local(reciever, name, value, avm, context)? - .resolve(avm, context)?; - - return Ok(()); + return self.set_property_local(reciever, name, value, avm, context); } let mut proto = self.proto(); @@ -125,17 +118,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy proto = my_proto.proto(); } - reciever - .set_property_local(reciever, name, value, avm, context)? - .resolve(avm, context)?; - - Ok(()) + reciever.set_property_local(reciever, name, value, avm, context) } /// Init a property on this specific object. - /// - /// This function returns a `ReturnValue` which should be resolved. The - /// resulting `Value` is unimportant and should be discarded. fn init_property_local( self, reciever: Object<'gc>, @@ -143,7 +129,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error>; + ) -> Result<(), Error>; /// Init a property by it's QName. fn init_property( @@ -161,10 +147,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } if self.has_own_virtual_setter(name) { - self.init_property_local(reciever, name, value, avm, context)? - .resolve(avm, context)?; - - return Ok(()); + return self.init_property_local(reciever, name, value, avm, context); } let mut proto = self.proto(); @@ -179,11 +162,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy proto = my_proto.proto(); } - reciever - .init_property_local(reciever, name, value, avm, context)? - .resolve(avm, context)?; - - Ok(()) + reciever.init_property_local(reciever, name, value, avm, context) } /// Retrieve a slot by it's index. @@ -521,7 +500,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy )?; let super_class: Result, Error> = self .get_property(reciever, &super_name, avm, context)? - .resolve(avm, context)? .as_object() .map_err(|_e| { format!("Could not resolve superclass {:?}", super_name.local_name()).into() @@ -582,7 +560,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy _avm: &mut Avm2<'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _base_proto: Option>, - ) -> Result, Error> { + ) -> Result, Error> { Err("Object is not callable".into()) } diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index e9c751ee9c48..025946b17940 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -127,12 +127,11 @@ impl<'gc> Scope<'gc> { ) -> Result>, Error> { if let Some(qname) = self.locals().resolve_multiname(name)? { if self.locals().has_property(&qname)? { - return Ok(Some(self.values.get_property( - self.values, - &qname, - avm, - context, - )?)); + return Ok(Some( + self.values + .get_property(self.values, &qname, avm, context)? + .into(), + )); } } diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 3eef734635c3..127869b6ae8e 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -76,7 +76,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error> { + ) -> Result, Error> { self.0 .read() .get_property_local(reciever, name, avm, context) @@ -89,10 +89,15 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error> { - self.0 + ) -> Result<(), Error> { + let rv = self + .0 .write(context.gc_context) - .set_property_local(reciever, name, value, avm, context) + .set_property_local(reciever, name, value, avm, context)?; + + rv.resolve(avm, context)?; + + Ok(()) } fn init_property_local( @@ -102,10 +107,15 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { value: Value<'gc>, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error> { - self.0 + ) -> Result<(), Error> { + let rv = self + .0 .write(context.gc_context) - .init_property_local(reciever, name, value, avm, context) + .init_property_local(reciever, name, value, avm, context)?; + + rv.resolve(avm, context)?; + + Ok(()) } fn is_property_overwritable(self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool { @@ -417,7 +427,7 @@ impl<'gc> ScriptObjectData<'gc> { name: &QName, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error> { + ) -> Result, Error> { let prop = self.values.get(name); if let Some(prop) = prop { @@ -428,9 +438,10 @@ impl<'gc> ScriptObjectData<'gc> { avm.current_stack_frame() .and_then(|sf| sf.read().base_proto()) .or(self.proto), - ) + )? + .resolve(avm, context) } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } From 15a62d31cb97ad30291a53792710ddd2e4151fe9 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 29 Jun 2020 23:58:48 -0400 Subject: [PATCH 146/189] Add an internal representation of `Trait`, separate from `swf::avm2::types::Trait`, which is specific to the ABC file format. The intention is to completely replace all usage of `Avm2XYZEntry` with `Class`, `Trait`, and `Method`. This will allow runtime-provided global class traits to coexist with those provided by user code. --- core/src/avm2.rs | 5 +- core/src/avm2/class.rs | 109 +++++++++++++++++++++++++++++++++ core/src/avm2/function.rs | 89 ++++++++++----------------- core/src/avm2/names.rs | 3 +- core/src/avm2/object.rs | 3 +- core/src/avm2/script_object.rs | 3 +- core/src/avm2/trait.rs | 78 +++++++++++++++++++++++ 7 files changed, 231 insertions(+), 59 deletions(-) create mode 100644 core/src/avm2/class.rs create mode 100644 core/src/avm2/trait.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 85e875d70441..b2827b61f12e 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,7 +1,8 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::{Activation, Avm2ScriptEntry}; -use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, FunctionObject}; +use crate::avm2::class::Avm2ClassEntry; +use crate::avm2::function::{Avm2MethodEntry, FunctionObject}; use crate::avm2::globals::SystemPrototypes; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::object::{Object, TObject}; @@ -30,6 +31,7 @@ macro_rules! avm_debug { } mod activation; +mod class; mod function; mod globals; mod names; @@ -39,6 +41,7 @@ mod return_value; mod scope; mod script_object; mod slot; +mod r#trait; mod value; /// Boxed error alias. diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs new file mode 100644 index 000000000000..887c1738705f --- /dev/null +++ b/core/src/avm2/class.rs @@ -0,0 +1,109 @@ +//! AVM2 classes + +use crate::avm2::function::Executable; +use crate::avm2::names::{Multiname, Namespace, QName}; +use crate::avm2::r#trait::Trait; +use gc_arena::Collect; +use std::rc::Rc; +use swf::avm2::types::{AbcFile, Class as AbcClass, Index, Instance as AbcInstance}; + +/// Represents a reference to an AVM2 class. +/// +/// For some reason, this comes in two parts, one for static properties (called +/// the "class") and one for dynamic properties (called the "instance", even +/// though it really defines what ES3/AS2 would call a prototype) +#[derive(Collect, Clone, Debug)] +#[collect(require_static)] +pub struct Avm2ClassEntry { + /// The ABC file this function was defined in. + pub abc: Rc, + + /// The ABC class (used to define static properties). + /// + /// This is also the index of the ABC instance, which holds instance + /// properties. + pub abc_class: u32, +} + +impl Avm2ClassEntry { + /// Construct an `Avm2MethodEntry` from an `AbcFile` and method index. + /// + /// This function returns `None` if the given class index does not resolve + /// to a valid ABC class, or a valid ABC instance. As mentioned in the type + /// documentation, ABC classes and instances are intended to be paired. + pub fn from_class_index(abc: Rc, abc_class: Index) -> Option { + if abc.classes.get(abc_class.0 as usize).is_some() + && abc.instances.get(abc_class.0 as usize).is_some() + { + return Some(Self { + abc, + abc_class: abc_class.0, + }); + } + + None + } + + /// Get the underlying ABC file. + pub fn abc(&self) -> Rc { + self.abc.clone() + } + + /// Get a reference to the ABC class entry this refers to. + pub fn class(&self) -> &AbcClass { + self.abc.classes.get(self.abc_class as usize).unwrap() + } + + /// Get a reference to the ABC class instance entry this refers to. + pub fn instance(&self) -> &AbcInstance { + self.abc.instances.get(self.abc_class as usize).unwrap() + } +} + +/// A loaded ABC Class which can be used to construct objects with. +#[derive(Clone, Debug, Collect)] +#[collect(no_drop)] +pub struct Class<'gc> { + /// The name of the class. + name: QName, + + /// The name of this class's superclass. + super_class: Option, + + /// If this class is sealed (dynamic property writes should fail) + is_sealed: bool, + + /// If this class is final (subclassing should fail) + is_final: bool, + + /// If this class is an interface + is_interface: bool, + + /// The namespace that protected traits of this class are stored into. + protected_namespace: Option, + + /// The list of interfaces this class implements. + interfaces: Vec, + + /// The instance initializer for this class. + /// + /// Must be called each time a new class instance is constructed. + instance_initializer: Executable<'gc>, + + /// Instance traits for a given class. + /// + /// These are accessed as normal instance properties; they should not be + /// present on prototypes, but instead should shadow any prototype + /// properties that would match. + instance_traits: Vec>, + + /// The class initializer for this class. + /// + /// Must be called once prior to any use of this class. + class_init: Executable<'gc>, + + /// Static traits for a given class. + /// + /// These are accessed as constructor properties. + class_traits: Vec>, +} diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 03b830dcd1fa..1ee3733c93e1 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -1,6 +1,7 @@ //! AVM2 executables. use crate::avm2::activation::Activation; +use crate::avm2::class::Avm2ClassEntry; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::return_value::ReturnValue; @@ -13,8 +14,7 @@ use gc_arena::{Collect, CollectionContext, GcCell, MutationContext}; use std::fmt; use std::rc::Rc; use swf::avm2::types::{ - AbcFile, Class as AbcClass, Index, Instance as AbcInstance, Method as AbcMethod, - MethodBody as AbcMethodBody, Trait as AbcTrait, + AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody, Trait as AbcTrait, }; /// Represents a function defined in Ruffle's code. @@ -93,6 +93,38 @@ impl Avm2MethodEntry { } } +/// An uninstantiated method that can either be natively implemented or sourced +/// from an ABC file. +#[derive(Clone)] +pub enum Method<'gc> { + /// A native method. + Native(NativeFunction<'gc>), + + /// An ABC-provided method entry. + Entry(Avm2MethodEntry), +} + +unsafe impl<'gc> Collect for Method<'gc> { + fn trace(&self, cc: CollectionContext) { + match self { + Method::Native(_nf) => {} + Method::Entry(a2me) => a2me.trace(cc), + } + } +} + +impl<'gc> fmt::Debug for Method<'gc> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Method::Native(_nf) => f + .debug_tuple("Method::Native") + .field(&"".to_string()) + .finish(), + Method::Entry(a2me) => f.debug_tuple("Method::Entry").field(a2me).finish(), + } + } +} + /// Represents an AVM2 function. #[derive(Collect, Clone, Debug)] #[collect(no_drop)] @@ -196,59 +228,6 @@ impl<'gc> From> for Executable<'gc> { } } -/// Represents a reference to an AVM2 class. -/// -/// For some reason, this comes in two parts, one for static properties (called -/// the "class") and one for dynamic properties (called the "instance", even -/// though it really defines what ES3/AS2 would call a prototype) -#[derive(Collect, Clone, Debug)] -#[collect(require_static)] -pub struct Avm2ClassEntry { - /// The ABC file this function was defined in. - pub abc: Rc, - - /// The ABC class (used to define static properties). - /// - /// This is also the index of the ABC instance, which holds instance - /// properties. - pub abc_class: u32, -} - -impl Avm2ClassEntry { - /// Construct an `Avm2MethodEntry` from an `AbcFile` and method index. - /// - /// This function returns `None` if the given class index does not resolve - /// to a valid ABC class, or a valid ABC instance. As mentioned in the type - /// documentation, ABC classes and instances are intended to be paired. - pub fn from_class_index(abc: Rc, abc_class: Index) -> Option { - if abc.classes.get(abc_class.0 as usize).is_some() - && abc.instances.get(abc_class.0 as usize).is_some() - { - return Some(Self { - abc, - abc_class: abc_class.0, - }); - } - - None - } - - /// Get the underlying ABC file. - pub fn abc(&self) -> Rc { - self.abc.clone() - } - - /// Get a reference to the ABC class entry this refers to. - pub fn class(&self) -> &AbcClass { - self.abc.classes.get(self.abc_class as usize).unwrap() - } - - /// Get a reference to the ABC class instance entry this refers to. - pub fn instance(&self) -> &AbcInstance { - self.abc.instances.get(self.abc_class as usize).unwrap() - } -} - /// An Object which can be called to execute it's function code. #[derive(Collect, Debug, Clone, Copy)] #[collect(no_drop)] diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 5640f57ea672..153a72ba0623 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -153,7 +153,8 @@ impl QName { /// process consists of searching each name space for a given name. /// /// The existence of a `name` of `None` indicates the `Any` name. -#[derive(Debug)] +#[derive(Clone, Debug, Collect)] +#[collect(no_drop)] pub struct Multiname { ns: Vec, name: Option, diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 908964ff06c3..7fc082a923b2 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1,6 +1,7 @@ //! AVM2 objects. -use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, Executable, FunctionObject}; +use crate::avm2::class::Avm2ClassEntry; +use crate::avm2::function::{Avm2MethodEntry, Executable, FunctionObject}; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 127869b6ae8e..fc120d9fac44 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -1,6 +1,7 @@ //! Default AVM2 object impl -use crate::avm2::function::{Avm2ClassEntry, Executable}; +use crate::avm2::class::Avm2ClassEntry; +use crate::avm2::function::Executable; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::property::Property; diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs new file mode 100644 index 000000000000..2539cb793b38 --- /dev/null +++ b/core/src/avm2/trait.rs @@ -0,0 +1,78 @@ +//! Active trait definitions + +use crate::avm2::class::Class; +use crate::avm2::function::Method; +use crate::avm2::names::{Multiname, QName}; +use crate::avm2::value::Value; +use gc_arena::{Collect, Gc}; + +/// Represents a trait as loaded into the VM. +/// +/// A trait is an uninstantiated AVM2 property. Traits are used by objects to +/// track how to construct their properties when first accessed. +/// +/// This type exists primarily to support classes with native methods. Adobe's +/// implementation of AVM2 handles native classes by having a special ABC file +/// load before all other code. We instead generate an initial heap in the same +/// manner as we do in AVM1, which means that we need to have a way to +/// dynamically originate traits that do not come from any particular ABC file. +#[derive(Clone, Debug, Collect)] +#[collect(no_drop)] +pub struct Trait<'gc> { + /// The name of this trait. + name: QName, + + /// Whether or not traits in downstream classes are allowed to override + /// this trait. + is_final: bool, + + /// Whether or not this trait is intended to override an upstream class's + /// trait. + is_override: bool, + + /// The kind of trait in use. + kind: TraitKind<'gc>, +} + +/// The fields for a particular kind of trait. +/// +/// The kind of a trait also determines how it's instantiated on the object. +/// See each individual variant for more information. +#[derive(Clone, Debug, Collect)] +#[collect(no_drop)] +pub enum TraitKind<'gc> { + /// A data field on an object instance that can be read from and written + /// to. + Slot { + slot_id: u32, + type_name: Multiname, + default_value: Option>, + }, + + /// A method on an object that can be called. + Method { disp_id: u32, method: Method<'gc> }, + + /// A getter property on an object that can be read. + Getter { disp_id: u32, method: Method<'gc> }, + + /// A setter property on an object that can be written. + Setter { disp_id: u32, method: Method<'gc> }, + + /// A class property on an object that can be used to construct more + /// objects. + Class { + slot_id: u32, + class: Gc<'gc, Class<'gc>>, + }, + + /// A free function (not an instance method) that can be called. + Function { slot_id: u32, function: Method<'gc> }, + + /// A data field on an object that is always a particular value, and cannot + /// be overridden. + Const { + slot_id: u32, + type_name: Multiname, + default_value: Option>, + }, +} From 70e9030072958f5fa1ce552256e88ed655304716 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 30 Jun 2020 23:44:14 -0400 Subject: [PATCH 147/189] Decouple the entire trait machinery from ABC-provided traits. This commit breaks the build: we still need to tell `Avm2` how to turn ABC traits into our own internal `Trait<'gc>`, `Class<'gc>`, and `Method<'gc>` types. We also need something to track which traits have already been instantiated, because `callstatic` would otherwise reinstantiate the trait in a different scope. (In fact, I think it *does* do exactly that right now...) --- core/src/avm2/activation.rs | 7 +- core/src/avm2/class.rs | 159 ++++++++++++++++++++++-- core/src/avm2/function.rs | 217 ++++++++++++++++++--------------- core/src/avm2/object.rs | 153 +++++++++++------------ core/src/avm2/script_object.rs | 136 ++++----------------- core/src/avm2/trait.rs | 18 +++ 6 files changed, 381 insertions(+), 309 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index a1cca6c79440..80e7088be49f 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -1,6 +1,6 @@ //! Activation frames -use crate::avm2::function::{Avm2Function, Avm2MethodEntry}; +use crate::avm2::function::Avm2MethodEntry; use crate::avm2::object::Object; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; @@ -169,13 +169,12 @@ impl<'gc> Activation<'gc> { pub fn from_action( context: &mut UpdateContext<'_, 'gc, '_>, - action: &Avm2Function<'gc>, + method: Avm2MethodEntry, + scope: Option>>, this: Option>, arguments: &[Value<'gc>], base_proto: Option>, ) -> Result { - let method = action.method.clone(); - let scope = action.scope; let num_locals = method.body().num_locals; let num_declared_arguments = method.method().params.len() as u32; let local_registers = GcCell::allocate( diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 887c1738705f..67fcb2eb6691 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -1,9 +1,10 @@ //! AVM2 classes -use crate::avm2::function::Executable; +use crate::avm2::function::Method; use crate::avm2::names::{Multiname, Namespace, QName}; -use crate::avm2::r#trait::Trait; -use gc_arena::Collect; +use crate::avm2::r#trait::{Trait, TraitKind}; +use crate::avm2::Error; +use gc_arena::{Collect, Gc}; use std::rc::Rc; use swf::avm2::types::{AbcFile, Class as AbcClass, Index, Instance as AbcInstance}; @@ -88,22 +89,166 @@ pub struct Class<'gc> { /// The instance initializer for this class. /// /// Must be called each time a new class instance is constructed. - instance_initializer: Executable<'gc>, + instance_init: Method<'gc>, /// Instance traits for a given class. /// /// These are accessed as normal instance properties; they should not be /// present on prototypes, but instead should shadow any prototype /// properties that would match. - instance_traits: Vec>, + instance_traits: Vec>>, /// The class initializer for this class. /// /// Must be called once prior to any use of this class. - class_init: Executable<'gc>, + class_init: Method<'gc>, /// Static traits for a given class. /// /// These are accessed as constructor properties. - class_traits: Vec>, + class_traits: Vec>>, +} + +/// Find traits in a list of traits matching a name. +/// +/// This function also enforces final/override bits on the traits, and will +/// raise `VerifyError`s as needed. +/// +/// TODO: This is an O(n^2) algorithm, it sucks. +fn do_trait_lookup<'gc>( + name: &QName, + known_traits: &mut Vec>>, + all_traits: &[Gc<'gc, Trait<'gc>>], +) -> Result<(), Error> { + for trait_entry in all_traits { + if name == trait_entry.name() { + for known_trait in known_traits.iter() { + match (&trait_entry.kind(), &known_trait.kind()) { + (TraitKind::Getter { .. }, TraitKind::Setter { .. }) => continue, + (TraitKind::Setter { .. }, TraitKind::Getter { .. }) => continue, + _ => {} + }; + + if known_trait.is_final() { + return Err("Attempting to override a final definition".into()); + } + + if !trait_entry.is_override() { + return Err("Definition override is not marked as override".into()); + } + } + + known_traits.push(trait_entry.clone()); + } + } + + Ok(()) +} + +impl<'gc> Class<'gc> { + pub fn name(&self) -> &QName { + &self.name + } + + pub fn super_class_name(&self) -> &Option { + &self.super_class + } + + /// Given a name, append class traits matching the name to a list of known + /// traits. + /// + /// This function adds it's result onto the list of known traits, with the + /// caveat that duplicate entries will be replaced (if allowed). As such, this + /// function should be run on the class hierarchy from top to bottom. + /// + /// If a given trait has an invalid name, attempts to override a final trait, + /// or overlaps an existing trait without being an override, then this function + /// returns an error. + pub fn lookup_class_traits( + &self, + name: &QName, + known_traits: &mut Vec>>, + ) -> Result<(), Error> { + do_trait_lookup(name, known_traits, &self.class_traits) + } + + /// Determines if this class provides a given trait on itself. + pub fn has_class_trait(&self, name: &QName) -> bool { + for trait_entry in self.class_traits.iter() { + if name == trait_entry.name() { + return true; + } + } + + false + } + + /// Look for a class trait with a given local name, and return it's + /// namespace. + /// + /// TODO: Matching multiple namespaces with the same local name is at least + /// claimed by the AVM2 specification to be a `VerifyError`. + pub fn resolve_any_class_trait(&self, local_name: &str) -> Option { + for trait_entry in self.class_traits.iter() { + if local_name == trait_entry.name().local_name() { + return Some(trait_entry.name().namespace().clone()); + } + } + + None + } + + /// Given a name, append instance traits matching the name to a list of + /// known traits. + /// + /// This function adds it's result onto the list of known traits, with the + /// caveat that duplicate entries will be replaced (if allowed). As such, this + /// function should be run on the class hierarchy from top to bottom. + /// + /// If a given trait has an invalid name, attempts to override a final trait, + /// or overlaps an existing trait without being an override, then this function + /// returns an error. + pub fn lookup_instance_traits( + &self, + name: &QName, + known_traits: &mut Vec>>, + ) -> Result<(), Error> { + do_trait_lookup(name, known_traits, &self.instance_traits) + } + + /// Determines if this class provides a given trait on it's instances. + pub fn has_instance_trait(&self, name: &QName) -> bool { + for trait_entry in self.instance_traits.iter() { + if name == trait_entry.name() { + return true; + } + } + + false + } + + /// Look for an instance trait with a given local name, and return it's + /// namespace. + /// + /// TODO: Matching multiple namespaces with the same local name is at least + /// claimed by the AVM2 specification to be a `VerifyError`. + pub fn resolve_any_instance_trait(&self, local_name: &str) -> Option { + for trait_entry in self.instance_traits.iter() { + if local_name == trait_entry.name().local_name() { + return Some(trait_entry.name().namespace().clone()); + } + } + + None + } + + /// Get this class's instance initializer. + pub fn instance_init(&self) -> Method<'gc> { + self.instance_init + } + + /// Get this class's class initializer. + pub fn class_init(&self) -> Method<'gc> { + self.class_init + } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 1ee3733c93e1..0790ce019c8d 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -1,21 +1,20 @@ //! AVM2 executables. use crate::avm2::activation::Activation; -use crate::avm2::class::Avm2ClassEntry; +use crate::avm2::class::Class; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; +use crate::avm2::r#trait::Trait; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; use crate::avm2::script_object::{ScriptObjectClass, ScriptObjectData}; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use gc_arena::{Collect, CollectionContext, GcCell, MutationContext}; +use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext}; use std::fmt; use std::rc::Rc; -use swf::avm2::types::{ - AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody, Trait as AbcTrait, -}; +use swf::avm2::types::{AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody}; /// Represents a function defined in Ruffle's code. /// @@ -125,6 +124,18 @@ impl<'gc> fmt::Debug for Method<'gc> { } } +impl<'gc> From> for Method<'gc> { + fn from(nf: NativeFunction<'gc>) -> Self { + Self::Native(nf) + } +} + +impl<'gc> From for Method<'gc> { + fn from(a2me: Avm2MethodEntry) -> Self { + Self::Entry(a2me) + } +} + /// Represents an AVM2 function. #[derive(Collect, Clone, Debug)] #[collect(no_drop)] @@ -142,37 +153,64 @@ pub struct Avm2Function<'gc> { pub reciever: Option>, } -impl<'gc> Avm2Function<'gc> { - pub fn from_method( - method: Avm2MethodEntry, - scope: Option>>, - reciever: Option>, - ) -> Self { - Self { - method, - scope, - reciever, - } - } -} - /// Represents code that can be executed by some means. #[derive(Clone)] pub enum Executable<'gc> { - Native(NativeFunction<'gc>), - Action(Avm2Function<'gc>), + /// Code defined in Ruffle's binary. + /// + /// The second parameter stores the bound reciever for this function. + Native(NativeFunction<'gc>, Option>), + + /// Code defined in a loaded ABC file. + Action { + /// The method code to execute from a given ABC file. + method: Avm2MethodEntry, + + /// The scope stack to pull variables from. + scope: Option>>, + + /// The reciever that this function is always called with. + /// + /// If `None`, then the reciever provided by the caller is used. A + /// `Some` value indicates a bound executable. + reciever: Option>, + }, } unsafe impl<'gc> Collect for Executable<'gc> { fn trace(&self, cc: CollectionContext) { match self { - Self::Action(a2f) => a2f.trace(cc), - Self::Native(_nf) => {} + Self::Action { + method, + scope, + reciever, + } => { + method.trace(cc); + scope.trace(cc); + reciever.trace(cc); + } + Self::Native(_nf, reciever) => reciever.trace(cc), } } } impl<'gc> Executable<'gc> { + /// Convert a method into an executable. + pub fn from_method( + method: Method<'gc>, + scope: Option>>, + reciever: Option>, + ) -> Self { + match method { + Method::Native(nf) => Self::Native(nf, reciever), + Method::Entry(a2me) => Self::Action { + method: a2me, + scope, + reciever, + }, + } + } + /// Execute a method. /// /// The function will either be called directly if it is a Rust builtin, or @@ -189,12 +227,25 @@ impl<'gc> Executable<'gc> { base_proto: Option>, ) -> Result, Error> { match self { - Executable::Native(nf) => nf(avm, context, unbound_reciever, arguments), - Executable::Action(a2f) => { - let reciever = a2f.reciever.or(unbound_reciever); + Executable::Native(nf, reciever) => { + nf(avm, context, reciever.or(unbound_reciever), arguments) + } + Executable::Action { + method, + scope, + reciever, + } => { + let reciever = reciever.or(unbound_reciever); let activation = GcCell::allocate( context.gc_context, - Activation::from_action(context, &a2f, reciever, arguments, base_proto)?, + Activation::from_action( + context, + method.clone(), + scope.clone(), + reciever, + arguments, + base_proto, + )?, ); avm.insert_stack_frame(activation); @@ -207,27 +258,25 @@ impl<'gc> Executable<'gc> { impl<'gc> fmt::Debug for Executable<'gc> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Action(a2f) => fmt.debug_tuple("Executable::Action").field(a2f).finish(), - Self::Native(nf) => fmt + Self::Action { + method, + scope, + reciever, + } => fmt + .debug_struct("Executable::Action") + .field("method", method) + .field("scope", scope) + .field("reciever", reciever) + .finish(), + Self::Native(nf, reciever) => fmt .debug_tuple("Executable::Native") .field(&format!("{:p}", nf)) + .field(reciever) .finish(), } } } -impl<'gc> From> for Executable<'gc> { - fn from(nf: NativeFunction<'gc>) -> Self { - Self::Native(nf) - } -} - -impl<'gc> From> for Executable<'gc> { - fn from(a2f: Avm2Function<'gc>) -> Self { - Self::Action(a2f) - } -} - /// An Object which can be called to execute it's function code. #[derive(Collect, Debug, Clone, Copy)] #[collect(no_drop)] @@ -244,15 +293,15 @@ pub struct FunctionObjectData<'gc> { } impl<'gc> FunctionObject<'gc> { - /// Construct a class from an ABC class/instance pair. + /// Construct a class. /// /// This function returns both the class itself, and the static class /// initializer method that you should call before interacting with the /// class. The latter should be called using the former as a reciever. - pub fn from_abc_class( + pub fn from_class( avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - class: Avm2ClassEntry, + class: Gc<'gc, Class<'gc>>, mut base_class: Object<'gc>, scope: Option>>, ) -> Result<(Object<'gc>, Object<'gc>), Error> { @@ -265,40 +314,20 @@ impl<'gc> FunctionObject<'gc> { )? .as_object() .map_err(|_| { - let super_name = QName::from_abc_multiname( - &class.abc(), - class.instance().super_name.clone(), - ); - - if let Ok(super_name) = super_name { - format!( - "Could not resolve superclass prototype {:?}", - super_name.local_name() - ) - .into() - } else { - format!( - "Could not resolve superclass prototype, and got this error when getting it's name: {:?}", - super_name.unwrap_err() - ) - .into() - } + format!( + "Could not resolve superclass prototype {:?}", + class + .super_class_name() + .map(|p| p.local_name()) + .unwrap_or(Some("Object")) + ) + .into() }); - let mut class_proto = super_proto?.derive(avm, context, class.clone(), scope)?; + let mut class_proto = super_proto?.derive(avm, context, class, scope)?; let fn_proto = avm.prototypes().function; let class_constr_proto = avm.prototypes().class; - let initializer_index = class.instance().init_method.clone(); - let initializer: Result = - Avm2MethodEntry::from_method_index(class.abc(), initializer_index.clone()).ok_or_else( - || { - format!( - "Instance initializer method index {} does not exist", - initializer_index.0 - ) - .into() - }, - ); + let initializer = class.instance_init(); let mut constr: Object<'gc> = FunctionObject(GcCell::allocate( context.gc_context, @@ -307,7 +336,7 @@ impl<'gc> FunctionObject<'gc> { Some(fn_proto), ScriptObjectClass::ClassConstructor(class.clone(), scope), ), - exec: Some(Avm2Function::from_method(initializer?, scope, None).into()), + exec: Some(Executable::from_method(initializer, scope, None).into()), }, )) .into(); @@ -323,19 +352,10 @@ impl<'gc> FunctionObject<'gc> { constr.into(), )?; - let class_initializer_index = class.class().init_method.clone(); - let class_initializer: Result = - Avm2MethodEntry::from_method_index(class.abc(), class_initializer_index.clone()) - .ok_or_else(|| { - format!( - "Class initializer method index {} does not exist", - class_initializer_index.0 - ) - .into() - }); - let class_constr = FunctionObject::from_abc_method( + let class_initializer = class.class_init(); + let class_constr = FunctionObject::from_method( context.gc_context, - class_initializer?, + class_initializer, scope, class_constr_proto, None, @@ -348,14 +368,14 @@ impl<'gc> FunctionObject<'gc> { /// /// The given `reciever`, if supplied, will override any user-specified /// `this` parameter. - pub fn from_abc_method( + pub fn from_method( mc: MutationContext<'gc, '_>, - method: Avm2MethodEntry, + method: Method<'gc>, scope: Option>>, fn_proto: Object<'gc>, reciever: Option>, ) -> Object<'gc> { - let exec = Some(Avm2Function::from_method(method, scope, reciever).into()); + let exec = Some(Executable::from_method(method, scope, reciever)); FunctionObject(GcCell::allocate( mc, @@ -377,7 +397,7 @@ impl<'gc> FunctionObject<'gc> { mc, FunctionObjectData { base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass), - exec: Some(nf.into()), + exec: Some(Executable::from_method(nf.into(), None, None)), }, )) .into() @@ -394,7 +414,7 @@ impl<'gc> FunctionObject<'gc> { mc, FunctionObjectData { base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass), - exec: Some(constr.into()), + exec: Some(Executable::from_method(constr.into(), None, None)), }, )) .into(); @@ -500,14 +520,14 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_method(id) } - fn get_trait(self, name: &QName) -> Result, Error> { + fn get_trait(self, name: &QName) -> Result>>, Error> { self.0.read().base.get_trait(name) } fn get_provided_trait( &self, name: &QName, - known_traits: &mut Vec, + known_traits: &mut Vec>>, ) -> Result<(), Error> { self.0.read().base.get_provided_trait(name, known_traits) } @@ -516,10 +536,6 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_scope() } - fn get_abc(self) -> Option> { - self.0.read().base.get_abc() - } - fn resolve_any(self, local_name: &str) -> Result, Error> { self.0.read().base.resolve_any(local_name) } @@ -620,7 +636,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { &self, _avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - class: Avm2ClassEntry, + class: Gc<'gc, Class<'gc>>, scope: Option>>, ) -> Result, Error> { let this: Object<'gc> = Object::FunctionObject(*self); @@ -638,8 +654,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn to_string(&self) -> Result, Error> { if let ScriptObjectClass::ClassConstructor(class, ..) = self.0.read().base.class() { - let name = QName::from_abc_multiname(&class.abc(), class.instance().name.clone())?; - Ok(format!("[class {}]", name.local_name()).into()) + Ok(format!("[class {}]", class.name().local_name()).into()) } else { Ok("function Function() {}".into()) } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 7fc082a923b2..4e0e9ab639ee 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1,18 +1,17 @@ //! AVM2 objects. -use crate::avm2::class::Avm2ClassEntry; -use crate::avm2::function::{Avm2MethodEntry, Executable, FunctionObject}; +use crate::avm2::class::Class; +use crate::avm2::function::{Executable, FunctionObject}; use crate::avm2::names::{Multiname, Namespace, QName}; +use crate::avm2::r#trait::{Trait, TraitKind}; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; -use crate::avm2::value::{abc_default_value, Value}; +use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use gc_arena::{Collect, GcCell, MutationContext}; +use gc_arena::{Collect, Gc, GcCell, MutationContext}; use ruffle_macros::enum_trait_object; use std::fmt::Debug; -use std::rc::Rc; -use swf::avm2::types::{AbcFile, Trait as AbcTrait, TraitKind as AbcTraitKind}; /// Represents an object that can be directly interacted with by the AVM2 /// runtime. @@ -45,7 +44,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy ) -> Result, Error> { if !self.has_instantiated_property(name) { for abc_trait in self.get_trait(name)? { - self.install_trait(avm, context, &abc_trait, reciever)?; + self.install_trait(avm, context, abc_trait, reciever)?; } } @@ -99,7 +98,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy ) -> Result<(), Error> { if !self.has_instantiated_property(name) { for abc_trait in self.get_trait(name)? { - self.install_trait(avm, context, &abc_trait, reciever)?; + self.install_trait(avm, context, abc_trait, reciever)?; } } @@ -143,7 +142,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy ) -> Result<(), Error> { if !self.has_instantiated_property(name) { for abc_trait in self.get_trait(name)? { - self.install_trait(avm, context, &abc_trait, reciever)?; + self.install_trait(avm, context, abc_trait, reciever)?; } } @@ -193,7 +192,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// This function returns `None` if no such trait exists, or the object /// does not have traits. It returns `Err` if *any* trait in the object is /// malformed in some way. - fn get_trait(self, name: &QName) -> Result, Error>; + fn get_trait(self, name: &QName) -> Result>>, Error>; /// Populate a list of traits that this object provides. /// @@ -204,7 +203,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn get_provided_trait( &self, name: &QName, - known_traits: &mut Vec, + known_traits: &mut Vec>>, ) -> Result<(), Error>; /// Retrieves the scope chain of the object at time of it's creation. @@ -215,14 +214,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// this scope chain. fn get_scope(self) -> Option>>; - /// Retrieves the ABC file that this object, or it's class, was defined in. - /// - /// Objects that were not defined in an ABC file or created from a class - /// defined in an ABC file will return `None`. This can happen for things - /// such as object or array literals. If this object does not have an ABC - /// file, then it must also not have traits. - fn get_abc(self) -> Option>; - /// Resolve a multiname into a single QName, if any of the namespaces /// match. fn resolve_multiname(self, multiname: &Multiname) -> Result, Error> { @@ -415,16 +406,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &mut self, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - trait_entry: &AbcTrait, + trait_entry: Gc<'gc, Trait<'gc>>, reciever: Object<'gc>, ) -> Result<(), Error> { - let scope = self.get_scope(); - let abc: Result, Error> = self.get_abc().ok_or_else(|| { - "Object with traits must have an ABC file!" - .to_string() - .into() - }); - self.install_foreign_trait(avm, context, abc?, trait_entry, scope, reciever) + self.install_foreign_trait(avm, context, trait_entry, self.get_scope(), reciever) } /// Install a trait from anywyere. @@ -432,73 +417,78 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &mut self, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - abc: Rc, - trait_entry: &AbcTrait, + trait_entry: Gc<'gc, Trait<'gc>>, scope: Option>>, reciever: Object<'gc>, ) -> Result<(), Error> { let fn_proto = avm.prototypes().function; - let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?; + let trait_name = trait_entry.name().clone(); avm_debug!( "Installing trait {:?} of kind {:?}", trait_name, trait_entry.kind ); - match &trait_entry.kind { - AbcTraitKind::Slot { slot_id, value, .. } => { - let value = if let Some(value) = value { - abc_default_value(&abc, value)? - } else { - Value::Undefined - }; - self.install_slot(context.gc_context, trait_name, *slot_id, value); + match trait_entry.kind() { + TraitKind::Slot { + slot_id, + default_value, + .. + } => { + self.install_slot( + context.gc_context, + trait_name, + *slot_id, + default_value.unwrap_or(Value::Undefined), + ); } - AbcTraitKind::Method { + TraitKind::Method { disp_id, method, .. } => { - let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); - let function = FunctionObject::from_abc_method( + let function = FunctionObject::from_method( context.gc_context, - method, + method.clone(), scope, fn_proto, Some(reciever), ); self.install_method(context.gc_context, trait_name, *disp_id, function); } - AbcTraitKind::Getter { + TraitKind::Getter { disp_id, method, .. } => { - let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); - let function = FunctionObject::from_abc_method( + let function = FunctionObject::from_method( context.gc_context, - method, + method.clone(), scope, fn_proto, Some(reciever), ); self.install_getter(context.gc_context, trait_name, *disp_id, function)?; } - AbcTraitKind::Setter { + TraitKind::Setter { disp_id, method, .. } => { - let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); - let function = FunctionObject::from_abc_method( + let function = FunctionObject::from_method( context.gc_context, - method, + method.clone(), scope, fn_proto, Some(reciever), ); self.install_setter(context.gc_context, trait_name, *disp_id, function)?; } - AbcTraitKind::Class { slot_id, class } => { - let type_entry = Avm2ClassEntry::from_class_index(abc, class.clone()).unwrap(); - let super_name = QName::from_abc_multiname( - &type_entry.abc(), - type_entry.instance().super_name.clone(), - )?; + TraitKind::Class { slot_id, class } => { + //TODO: what happens if this happens on a class defined as a + //class trait, without a superclass? How do we get `Object` + //then? + let super_name = if let Some(sc_name) = class.super_class_name() { + self.resolve_multiname(sc_name)? + .unwrap_or(QName::dynamic_name("Object")) + } else { + QName::dynamic_name("Object") + }; + let super_class: Result, Error> = self .get_property(reciever, &super_name, avm, context)? .as_object() @@ -506,47 +496,46 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy format!("Could not resolve superclass {:?}", super_name.local_name()).into() }); - let (class, _cinit) = FunctionObject::from_abc_class( - avm, - context, - type_entry.clone(), - super_class?, - scope, - )?; - let class_name = QName::from_abc_multiname( - &type_entry.abc(), - type_entry.instance().name.clone(), - )?; - self.install_const(context.gc_context, class_name, *slot_id, class.into()); + let (class_object, _cinit) = + FunctionObject::from_class(avm, context, *class, super_class?, scope)?; + self.install_const( + context.gc_context, + class.name().clone(), + *slot_id, + class_object.into(), + ); } - AbcTraitKind::Function { + TraitKind::Function { slot_id, function, .. } => { - let method = Avm2MethodEntry::from_method_index(abc, function.clone()).unwrap(); - let mut function = FunctionObject::from_abc_method( + let mut fobject = FunctionObject::from_method( context.gc_context, - method, + function.clone(), scope, fn_proto, None, ); let es3_proto = ScriptObject::object(context.gc_context, avm.prototypes().object); - function.install_slot( + fobject.install_slot( context.gc_context, QName::new(Namespace::public_namespace(), "prototype"), 0, es3_proto.into(), ); - self.install_const(context.gc_context, trait_name, *slot_id, function.into()); + self.install_const(context.gc_context, trait_name, *slot_id, fobject.into()); } - AbcTraitKind::Const { slot_id, value, .. } => { - let value = if let Some(value) = value { - abc_default_value(&abc, value)? - } else { - Value::Undefined - }; - self.install_const(context.gc_context, trait_name, *slot_id, value); + TraitKind::Const { + slot_id, + default_value, + .. + } => { + self.install_const( + context.gc_context, + trait_name, + *slot_id, + default_value.unwrap_or(Value::Undefined), + ); } } @@ -602,7 +591,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &self, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - class: Avm2ClassEntry, + class: Gc<'gc, Class<'gc>>, scope: Option>>, ) -> Result, Error>; diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index fc120d9fac44..41fd1c8ae4c4 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -1,21 +1,20 @@ //! Default AVM2 object impl -use crate::avm2::class::Avm2ClassEntry; +use crate::avm2::class::Class; use crate::avm2::function::Executable; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::property::Property; +use crate::avm2::r#trait::Trait; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; use crate::avm2::slot::Slot; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use gc_arena::{Collect, GcCell, MutationContext}; +use gc_arena::{Collect, Gc, GcCell, MutationContext}; use std::collections::HashMap; use std::fmt::Debug; -use std::rc::Rc; -use swf::avm2::types::{AbcFile, Trait as AbcTrait, TraitKind as AbcTraitKind}; /// Default implementation of `avm2::Object`. #[derive(Clone, Collect, Debug, Copy)] @@ -34,10 +33,10 @@ pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>); #[collect(no_drop)] pub enum ScriptObjectClass<'gc> { /// Instantiate instance traits, for prototypes. - InstancePrototype(Avm2ClassEntry, Option>>), + InstancePrototype(Gc<'gc, Class<'gc>>, Option>>), /// Instantiate class traits, for class constructors. - ClassConstructor(Avm2ClassEntry, Option>>), + ClassConstructor(Gc<'gc, Class<'gc>>, Option>>), /// Do not instantiate any class or instance traits. NoClass, @@ -153,14 +152,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_method(id) } - fn get_trait(self, name: &QName) -> Result, Error> { + fn get_trait(self, name: &QName) -> Result>>, Error> { self.0.read().get_trait(name) } fn get_provided_trait( &self, name: &QName, - known_traits: &mut Vec, + known_traits: &mut Vec>>, ) -> Result<(), Error> { self.0.read().get_provided_trait(name, known_traits) } @@ -169,10 +168,6 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_scope() } - fn get_abc(self) -> Option> { - self.0.read().get_abc() - } - fn resolve_any(self, local_name: &str) -> Result, Error> { self.0.read().resolve_any(local_name) } @@ -246,7 +241,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { &self, _avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - class: Avm2ClassEntry, + class: Gc<'gc, Class<'gc>>, scope: Option>>, ) -> Result, Error> { let this: Object<'gc> = Object::ScriptObject(*self); @@ -352,7 +347,7 @@ impl<'gc> ScriptObject<'gc> { pub fn prototype( mc: MutationContext<'gc, '_>, proto: Object<'gc>, - class: Avm2ClassEntry, + class: Gc<'gc, Class<'gc>>, scope: Option>>, ) -> Object<'gc> { let script_class = ScriptObjectClass::InstancePrototype(class, scope); @@ -365,51 +360,6 @@ impl<'gc> ScriptObject<'gc> { } } -/// Given a list of traits from an ABC file, find the one that matches this -/// name. -/// -/// This function adds it's result onto the list of known traits, with the -/// caveat that duplicate entries will be replaced (if allowed). As such, this -/// function should be run on the class hierarchy from top to bottom. -/// -/// If a given trait has an invalid name, attempts to override a final trait, -/// or overlaps an existing trait without being an override, then this function -/// returns an error. -/// -/// TODO: This is an O(n^2) algorithm, it sucks. -fn do_trait_lookup( - name: &QName, - known_traits: &mut Vec, - abc: Rc, - traits: &[AbcTrait], -) -> Result<(), Error> { - for trait_entry in traits.iter() { - let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?; - - if name == &trait_name { - for known_trait in known_traits.iter() { - match (&trait_entry.kind, &known_trait.kind) { - (AbcTraitKind::Getter { .. }, AbcTraitKind::Setter { .. }) => continue, - (AbcTraitKind::Setter { .. }, AbcTraitKind::Getter { .. }) => continue, - _ => {} - }; - - if known_trait.is_final { - return Err("Attempting to override a final definition".into()); - } - - if !trait_entry.is_override { - return Err("Definition override is not marked as override".into()); - } - } - - known_traits.push(trait_entry.clone()); - } - } - - Ok(()) -} - impl<'gc> ScriptObjectData<'gc> { pub fn base_new(proto: Option>, trait_source: ScriptObjectClass<'gc>) -> Self { ScriptObjectData { @@ -576,7 +526,7 @@ impl<'gc> ScriptObjectData<'gc> { self.methods.get(id as usize).and_then(|v| *v) } - pub fn get_trait(&self, name: &QName) -> Result, Error> { + pub fn get_trait(&self, name: &QName) -> Result>>, Error> { match &self.class { //Class constructors have local traits only. ScriptObjectClass::ClassConstructor(..) => { @@ -614,14 +564,14 @@ impl<'gc> ScriptObjectData<'gc> { pub fn get_provided_trait( &self, name: &QName, - known_traits: &mut Vec, + known_traits: &mut Vec>>, ) -> Result<(), Error> { match &self.class { ScriptObjectClass::ClassConstructor(class, ..) => { - do_trait_lookup(name, known_traits, class.abc(), &class.class().traits) + class.lookup_class_traits(name, known_traits) } ScriptObjectClass::InstancePrototype(class, ..) => { - do_trait_lookup(name, known_traits, class.abc(), &class.instance().traits) + class.lookup_instance_traits(name, known_traits) } ScriptObjectClass::NoClass => Ok(()), } @@ -656,30 +606,10 @@ impl<'gc> ScriptObjectData<'gc> { pub fn provides_trait(&self, name: &QName) -> Result { match &self.class { - ScriptObjectClass::ClassConstructor(class, ..) => { - for trait_entry in class.class().traits.iter() { - let trait_name = - QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; - - if name == &trait_name { - return Ok(true); - } - } - } - ScriptObjectClass::InstancePrototype(class, ..) => { - for trait_entry in class.instance().traits.iter() { - let trait_name = - QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; - - if name == &trait_name { - return Ok(true); - } - } - } - ScriptObjectClass::NoClass => {} - }; - - Ok(false) + ScriptObjectClass::ClassConstructor(class, ..) => Ok(class.has_class_trait(name)), + ScriptObjectClass::InstancePrototype(class, ..) => Ok(class.has_instance_trait(name)), + ScriptObjectClass::NoClass => Ok(false), + } } pub fn get_scope(&self) -> Option>> { @@ -690,14 +620,6 @@ impl<'gc> ScriptObjectData<'gc> { } } - pub fn get_abc(&self) -> Option> { - match &self.class { - ScriptObjectClass::ClassConstructor(class, ..) => Some(class.abc()), - ScriptObjectClass::InstancePrototype(class, ..) => Some(class.abc()), - ScriptObjectClass::NoClass => self.proto().and_then(|proto| proto.get_abc()), - } - } - pub fn resolve_any(&self, local_name: &str) -> Result, Error> { for (key, _value) in self.values.iter() { if key.local_name() == local_name { @@ -722,29 +644,13 @@ impl<'gc> ScriptObjectData<'gc> { match &self.class { ScriptObjectClass::ClassConstructor(class, ..) => { - for trait_entry in class.class().traits.iter() { - let trait_name = - QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; - - if local_name == trait_name.local_name() { - return Ok(Some(trait_name.namespace().clone())); - } - } + Ok(class.resolve_any_class_trait(local_name)) } ScriptObjectClass::InstancePrototype(class, ..) => { - for trait_entry in class.instance().traits.iter() { - let trait_name = - QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; - - if local_name == trait_name.local_name() { - return Ok(Some(trait_name.namespace().clone())); - } - } + Ok(class.resolve_any_instance_trait(local_name)) } - ScriptObjectClass::NoClass => {} - }; - - Ok(None) + ScriptObjectClass::NoClass => Ok(None), + } } pub fn has_own_property(&self, name: &QName) -> Result { diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index 2539cb793b38..d05d7533d93c 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -76,3 +76,21 @@ pub enum TraitKind<'gc> { default_value: Option>, }, } + +impl<'gc> Trait<'gc> { + pub fn name(&self) -> &QName { + &self.name + } + + pub fn kind(&self) -> &TraitKind<'gc> { + &self.kind + } + + pub fn is_final(&self) -> bool { + self.is_final + } + + pub fn is_override(&self) -> bool { + self.is_override + } +} From b4f944b37bad0cdd6fe1a345671f58d22e5a6154 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 1 Jul 2020 23:21:30 -0400 Subject: [PATCH 148/189] Wrap ABC loading inside of a `TranslationUnit`. --- core/src/avm2.rs | 3 +- core/src/avm2/class.rs | 133 +++++++++++++++++++++++++++++++-- core/src/avm2/function.rs | 19 ++--- core/src/avm2/object.rs | 17 +++-- core/src/avm2/script.rs | 92 +++++++++++++++++++++++ core/src/avm2/script_object.rs | 34 +++++---- core/src/avm2/trait.rs | 102 ++++++++++++++++++++++++- 7 files changed, 357 insertions(+), 43 deletions(-) create mode 100644 core/src/avm2/script.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index b2827b61f12e..77c6588831b4 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -39,6 +39,7 @@ mod object; mod property; mod return_value; mod scope; +mod script; mod script_object; mod slot; mod r#trait; @@ -1291,7 +1292,7 @@ impl<'gc> Avm2<'gc> { let method_entry = self.table_method(index)?; let scope = self.current_stack_frame().unwrap().read().scope(); - let mut new_fn = FunctionObject::from_abc_method( + let mut new_fn = FunctionObject::from_method( context.gc_context, method_entry, scope, diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 67fcb2eb6691..6114ed53cf45 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -3,8 +3,9 @@ use crate::avm2::function::Method; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::r#trait::{Trait, TraitKind}; +use crate::avm2::script::TranslationUnit; use crate::avm2::Error; -use gc_arena::{Collect, Gc}; +use gc_arena::{Collect, GcCell, MutationContext}; use std::rc::Rc; use swf::avm2::types::{AbcFile, Class as AbcClass, Index, Instance as AbcInstance}; @@ -96,7 +97,7 @@ pub struct Class<'gc> { /// These are accessed as normal instance properties; they should not be /// present on prototypes, but instead should shadow any prototype /// properties that would match. - instance_traits: Vec>>, + instance_traits: Vec>, /// The class initializer for this class. /// @@ -106,7 +107,10 @@ pub struct Class<'gc> { /// Static traits for a given class. /// /// These are accessed as constructor properties. - class_traits: Vec>>, + class_traits: Vec>, + + /// Whether or not this `Class` has loaded it's traits or not. + traits_loaded: bool, } /// Find traits in a list of traits matching a name. @@ -117,8 +121,8 @@ pub struct Class<'gc> { /// TODO: This is an O(n^2) algorithm, it sucks. fn do_trait_lookup<'gc>( name: &QName, - known_traits: &mut Vec>>, - all_traits: &[Gc<'gc, Trait<'gc>>], + known_traits: &mut Vec>, + all_traits: &[Trait<'gc>], ) -> Result<(), Error> { for trait_entry in all_traits { if name == trait_entry.name() { @@ -146,6 +150,121 @@ fn do_trait_lookup<'gc>( } impl<'gc> Class<'gc> { + /// Construct a class from a `TranslationUnit` and it's class index. + /// + /// The returned class will be allocated, but no traits will be loaded. The + /// caller is responsible for storing the class in the `TranslationUnit` + /// and calling `load_traits` to complete the trait-loading process. + pub fn from_abc_index( + unit: &mut TranslationUnit<'gc>, + class_index: u32, + mc: MutationContext<'gc, '_>, + ) -> Result, Error> { + let abc_class: Result<&AbcClass, Error> = unit + .abc() + .classes + .get(class_index as usize) + .ok_or("LoadError: Class index not valid".into()); + let abc_class = abc_class?; + + let abc_instance: Result<&AbcInstance, Error> = unit + .abc() + .instances + .get(class_index as usize) + .ok_or("LoadError: Instance index not valid".into()); + let abc_instance = abc_instance?; + + let name = QName::from_abc_multiname(&unit.abc(), abc_instance.name)?; + let super_class = if abc_instance.super_name.0 == 0 { + None + } else { + Some(Multiname::from_abc_multiname_static( + &unit.abc(), + abc_instance.super_name, + )?) + }; + + let protected_namespace = if let Some(ns) = abc_instance.protected_namespace { + Some(Namespace::from_abc_namespace(&unit.abc(), ns)?) + } else { + None + }; + + let mut interfaces = Vec::new(); + for interface_name in abc_instance.interfaces { + interfaces.push(Multiname::from_abc_multiname_static( + &unit.abc(), + interface_name, + )?); + } + + let instance_init = unit.load_method(abc_instance.init_method.0)?; + let class_init = unit.load_method(abc_class.init_method.0)?; + + Ok(GcCell::allocate( + mc, + Self { + name, + super_class, + is_sealed: abc_instance.is_sealed, + is_final: abc_instance.is_final, + is_interface: abc_instance.is_interface, + protected_namespace, + interfaces, + instance_init, + instance_traits: Vec::new(), + class_init, + class_traits: Vec::new(), + traits_loaded: false, + }, + )) + } + + /// Finish the class-loading process by loading traits. + /// + /// This process must be done after the `Class` has been stored in the + /// `TranslationUnit`. Failing to do so runs the risk of runaway recursion + /// or double-borrows. It should be done before the class is actually + /// instantiated into an `Object`. + pub fn load_traits( + &mut self, + unit: &mut TranslationUnit<'gc>, + class_index: u32, + mc: MutationContext<'gc, '_>, + ) -> Result<(), Error> { + if self.traits_loaded { + return Ok(()); + } + + self.traits_loaded = true; + + let abc_class: Result<&AbcClass, Error> = unit + .abc() + .classes + .get(class_index as usize) + .ok_or_else(|| "LoadError: Class index not valid".into()); + let abc_class = abc_class?; + + let abc_instance: Result<&AbcInstance, Error> = unit + .abc() + .instances + .get(class_index as usize) + .ok_or_else(|| "LoadError: Instance index not valid".into()); + let abc_instance = abc_instance?; + + for abc_trait in abc_instance.traits { + self.instance_traits + .push(Trait::from_abc_trait(unit, &abc_trait, mc)?); + } + + for abc_trait in abc_class.traits { + self.class_traits + .push(Trait::from_abc_trait(unit, &abc_trait, mc)?); + } + + Ok(()) + } + pub fn name(&self) -> &QName { &self.name } @@ -167,7 +286,7 @@ impl<'gc> Class<'gc> { pub fn lookup_class_traits( &self, name: &QName, - known_traits: &mut Vec>>, + known_traits: &mut Vec>, ) -> Result<(), Error> { do_trait_lookup(name, known_traits, &self.class_traits) } @@ -211,7 +330,7 @@ impl<'gc> Class<'gc> { pub fn lookup_instance_traits( &self, name: &QName, - known_traits: &mut Vec>>, + known_traits: &mut Vec>, ) -> Result<(), Error> { do_trait_lookup(name, known_traits, &self.instance_traits) } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 0790ce019c8d..517f25872032 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -11,7 +11,7 @@ use crate::avm2::script_object::{ScriptObjectClass, ScriptObjectData}; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext}; +use gc_arena::{Collect, CollectionContext, GcCell, MutationContext}; use std::fmt; use std::rc::Rc; use swf::avm2::types::{AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody}; @@ -301,10 +301,11 @@ impl<'gc> FunctionObject<'gc> { pub fn from_class( avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - class: Gc<'gc, Class<'gc>>, + class: GcCell<'gc, Class<'gc>>, mut base_class: Object<'gc>, scope: Option>>, ) -> Result<(Object<'gc>, Object<'gc>), Error> { + let class_read = class.read(); let super_proto: Result, Error> = base_class .get_property( base_class, @@ -316,7 +317,7 @@ impl<'gc> FunctionObject<'gc> { .map_err(|_| { format!( "Could not resolve superclass prototype {:?}", - class + class_read .super_class_name() .map(|p| p.local_name()) .unwrap_or(Some("Object")) @@ -327,7 +328,7 @@ impl<'gc> FunctionObject<'gc> { let fn_proto = avm.prototypes().function; let class_constr_proto = avm.prototypes().class; - let initializer = class.instance_init(); + let initializer = class_read.instance_init(); let mut constr: Object<'gc> = FunctionObject(GcCell::allocate( context.gc_context, @@ -352,7 +353,7 @@ impl<'gc> FunctionObject<'gc> { constr.into(), )?; - let class_initializer = class.class_init(); + let class_initializer = class_read.class_init(); let class_constr = FunctionObject::from_method( context.gc_context, class_initializer, @@ -520,14 +521,14 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_method(id) } - fn get_trait(self, name: &QName) -> Result>>, Error> { + fn get_trait(self, name: &QName) -> Result>, Error> { self.0.read().base.get_trait(name) } fn get_provided_trait( &self, name: &QName, - known_traits: &mut Vec>>, + known_traits: &mut Vec>, ) -> Result<(), Error> { self.0.read().base.get_provided_trait(name, known_traits) } @@ -636,7 +637,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { &self, _avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - class: Gc<'gc, Class<'gc>>, + class: GcCell<'gc, Class<'gc>>, scope: Option>>, ) -> Result, Error> { let this: Object<'gc> = Object::FunctionObject(*self); @@ -654,7 +655,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn to_string(&self) -> Result, Error> { if let ScriptObjectClass::ClassConstructor(class, ..) = self.0.read().base.class() { - Ok(format!("[class {}]", class.name().local_name()).into()) + Ok(format!("[class {}]", class.read().name().local_name()).into()) } else { Ok("function Function() {}".into()) } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 4e0e9ab639ee..eabfa40c2dd1 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -9,7 +9,7 @@ use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use gc_arena::{Collect, Gc, GcCell, MutationContext}; +use gc_arena::{Collect, GcCell, MutationContext}; use ruffle_macros::enum_trait_object; use std::fmt::Debug; @@ -192,7 +192,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// This function returns `None` if no such trait exists, or the object /// does not have traits. It returns `Err` if *any* trait in the object is /// malformed in some way. - fn get_trait(self, name: &QName) -> Result>>, Error>; + fn get_trait(self, name: &QName) -> Result>, Error>; /// Populate a list of traits that this object provides. /// @@ -203,7 +203,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn get_provided_trait( &self, name: &QName, - known_traits: &mut Vec>>, + known_traits: &mut Vec>, ) -> Result<(), Error>; /// Retrieves the scope chain of the object at time of it's creation. @@ -406,7 +406,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &mut self, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - trait_entry: Gc<'gc, Trait<'gc>>, + trait_entry: Trait<'gc>, reciever: Object<'gc>, ) -> Result<(), Error> { self.install_foreign_trait(avm, context, trait_entry, self.get_scope(), reciever) @@ -417,7 +417,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &mut self, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - trait_entry: Gc<'gc, Trait<'gc>>, + trait_entry: Trait<'gc>, scope: Option>>, reciever: Object<'gc>, ) -> Result<(), Error> { @@ -479,10 +479,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy self.install_setter(context.gc_context, trait_name, *disp_id, function)?; } TraitKind::Class { slot_id, class } => { + let class_read = class.read(); //TODO: what happens if this happens on a class defined as a //class trait, without a superclass? How do we get `Object` //then? - let super_name = if let Some(sc_name) = class.super_class_name() { + let super_name = if let Some(sc_name) = class_read.super_class_name() { self.resolve_multiname(sc_name)? .unwrap_or(QName::dynamic_name("Object")) } else { @@ -500,7 +501,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy FunctionObject::from_class(avm, context, *class, super_class?, scope)?; self.install_const( context.gc_context, - class.name().clone(), + class_read.name().clone(), *slot_id, class_object.into(), ); @@ -591,7 +592,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &self, avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - class: Gc<'gc, Class<'gc>>, + class: GcCell<'gc, Class<'gc>>, scope: Option>>, ) -> Result, Error>; diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs new file mode 100644 index 000000000000..9c45a241510a --- /dev/null +++ b/core/src/avm2/script.rs @@ -0,0 +1,92 @@ +//! Whole script representation + +use crate::avm2::activation::Avm2ScriptEntry; +use crate::avm2::class::Class; +use crate::avm2::function::{Avm2MethodEntry, Method}; +use crate::avm2::Error; +use gc_arena::{Collect, GcCell, MutationContext}; +use std::collections::HashMap; +use std::rc::Rc; +use swf::avm2::types::{AbcFile, Index}; + +#[derive(Clone, Debug, Collect)] +#[collect(require_static)] +pub struct CollectWrapper(T); + +/// A loaded ABC file, with any loaded ABC items alongside it. +/// +/// A `TranslationUnit` is constructed when ABC loading begins, and it stores +/// all loaded ABC items (classes, methods, and scripts) as they are loaded. +/// Unit items are loaded lazily and retained in the `TranslationUnit` for +/// later retrieval. +/// +/// Loaded versions of ABC items consist of the types `Class`, `Method`, and +/// `Script`, all of which correspond to their `swf` equivalents, but with +/// names preloaded. This roughly corresponds to the logical "loading" phase of +/// ABC execution as documented in the AVM2 Overview. "Linking" takes place by +/// constructing the appropriate runtime object for that item. +#[derive(Clone, Debug, Collect)] +#[collect(no_drop)] +pub struct TranslationUnit<'gc> { + /// The ABC file that all of the following loaded data comes from. + abc: CollectWrapper>, + + /// All classes loaded from the ABC's class list. + classes: HashMap>>, + + /// All methods loaded from the ABC's method list. + methods: HashMap>, + + /// All scripts loaded from the ABC's scripts list. + scripts: HashMap>, +} + +impl<'gc> TranslationUnit<'gc> { + pub fn from_abc(abc: Rc) -> Self { + Self { + abc: CollectWrapper(abc), + classes: HashMap::new(), + methods: HashMap::new(), + scripts: HashMap::new(), + } + } + + /// Retrieve the underlying `AbcFile` for this translation unit. + pub fn abc(&self) -> Rc { + self.abc.0 + } + + /// Load a method from the ABC file and return it's method definition. + pub fn load_method(&mut self, method_index: u32) -> Result, Error> { + if let Some(method) = self.methods.get(&method_index) { + return Ok(method.clone()); + } + + let method: Result = + Avm2MethodEntry::from_method_index(self.abc.0, Index::new(method_index)) + .ok_or_else(|| "Method index does not exist".into()); + let method = method?.into(); + + self.methods.insert(method_index, method); + + return Ok(method); + } + + /// Load a class from the ABC file and return it's class definition. + pub fn load_class( + &mut self, + class_index: u32, + mc: MutationContext<'gc, '_>, + ) -> Result>, Error> { + if let Some(class) = self.classes.get(&class_index) { + return Ok(class.clone()); + } + + let class = Class::from_abc_index(&mut self, class_index, mc)?; + self.classes.insert(class_index, class); + + class.write(mc).load_traits(&mut self, class_index, mc)?; + + return Ok(class); + } +} diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 41fd1c8ae4c4..80bfa3b8f0d3 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -12,7 +12,7 @@ use crate::avm2::slot::Slot; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; -use gc_arena::{Collect, Gc, GcCell, MutationContext}; +use gc_arena::{Collect, GcCell, MutationContext}; use std::collections::HashMap; use std::fmt::Debug; @@ -33,10 +33,10 @@ pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>); #[collect(no_drop)] pub enum ScriptObjectClass<'gc> { /// Instantiate instance traits, for prototypes. - InstancePrototype(Gc<'gc, Class<'gc>>, Option>>), + InstancePrototype(GcCell<'gc, Class<'gc>>, Option>>), /// Instantiate class traits, for class constructors. - ClassConstructor(Gc<'gc, Class<'gc>>, Option>>), + ClassConstructor(GcCell<'gc, Class<'gc>>, Option>>), /// Do not instantiate any class or instance traits. NoClass, @@ -152,14 +152,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_method(id) } - fn get_trait(self, name: &QName) -> Result>>, Error> { + fn get_trait(self, name: &QName) -> Result>, Error> { self.0.read().get_trait(name) } fn get_provided_trait( &self, name: &QName, - known_traits: &mut Vec>>, + known_traits: &mut Vec>, ) -> Result<(), Error> { self.0.read().get_provided_trait(name, known_traits) } @@ -241,7 +241,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { &self, _avm: &mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - class: Gc<'gc, Class<'gc>>, + class: GcCell<'gc, Class<'gc>>, scope: Option>>, ) -> Result, Error> { let this: Object<'gc> = Object::ScriptObject(*self); @@ -347,7 +347,7 @@ impl<'gc> ScriptObject<'gc> { pub fn prototype( mc: MutationContext<'gc, '_>, proto: Object<'gc>, - class: Gc<'gc, Class<'gc>>, + class: GcCell<'gc, Class<'gc>>, scope: Option>>, ) -> Object<'gc> { let script_class = ScriptObjectClass::InstancePrototype(class, scope); @@ -526,7 +526,7 @@ impl<'gc> ScriptObjectData<'gc> { self.methods.get(id as usize).and_then(|v| *v) } - pub fn get_trait(&self, name: &QName) -> Result>>, Error> { + pub fn get_trait(&self, name: &QName) -> Result>, Error> { match &self.class { //Class constructors have local traits only. ScriptObjectClass::ClassConstructor(..) => { @@ -564,14 +564,14 @@ impl<'gc> ScriptObjectData<'gc> { pub fn get_provided_trait( &self, name: &QName, - known_traits: &mut Vec>>, + known_traits: &mut Vec>, ) -> Result<(), Error> { match &self.class { ScriptObjectClass::ClassConstructor(class, ..) => { - class.lookup_class_traits(name, known_traits) + class.read().lookup_class_traits(name, known_traits) } ScriptObjectClass::InstancePrototype(class, ..) => { - class.lookup_instance_traits(name, known_traits) + class.read().lookup_instance_traits(name, known_traits) } ScriptObjectClass::NoClass => Ok(()), } @@ -606,8 +606,12 @@ impl<'gc> ScriptObjectData<'gc> { pub fn provides_trait(&self, name: &QName) -> Result { match &self.class { - ScriptObjectClass::ClassConstructor(class, ..) => Ok(class.has_class_trait(name)), - ScriptObjectClass::InstancePrototype(class, ..) => Ok(class.has_instance_trait(name)), + ScriptObjectClass::ClassConstructor(class, ..) => { + Ok(class.read().has_class_trait(name)) + } + ScriptObjectClass::InstancePrototype(class, ..) => { + Ok(class.read().has_instance_trait(name)) + } ScriptObjectClass::NoClass => Ok(false), } } @@ -644,10 +648,10 @@ impl<'gc> ScriptObjectData<'gc> { match &self.class { ScriptObjectClass::ClassConstructor(class, ..) => { - Ok(class.resolve_any_class_trait(local_name)) + Ok(class.read().resolve_any_class_trait(local_name)) } ScriptObjectClass::InstancePrototype(class, ..) => { - Ok(class.resolve_any_instance_trait(local_name)) + Ok(class.read().resolve_any_instance_trait(local_name)) } ScriptObjectClass::NoClass => Ok(None), } diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index d05d7533d93c..0726de6c7b32 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -3,8 +3,11 @@ use crate::avm2::class::Class; use crate::avm2::function::Method; use crate::avm2::names::{Multiname, QName}; -use crate::avm2::value::Value; -use gc_arena::{Collect, Gc}; +use crate::avm2::script::TranslationUnit; +use crate::avm2::value::{abc_default_value, Value}; +use crate::avm2::Error; +use gc_arena::{Collect, GcCell, MutationContext}; +use swf::avm2::types::{Trait as AbcTrait, TraitKind as AbcTraitKind}; /// Represents a trait as loaded into the VM. /// @@ -62,7 +65,7 @@ pub enum TraitKind<'gc> { /// objects. Class { slot_id: u32, - class: Gc<'gc, Class<'gc>>, + class: GcCell<'gc, Class<'gc>>, }, /// A free function (not an instance method) that can be called. @@ -78,6 +81,99 @@ pub enum TraitKind<'gc> { } impl<'gc> Trait<'gc> { + /// Convert an ABC trait into a loaded trait. + pub fn from_abc_trait( + unit: &mut TranslationUnit<'gc>, + abc_trait: &AbcTrait, + mc: MutationContext<'gc, '_>, + ) -> Result { + let name = QName::from_abc_multiname(&unit.abc(), abc_trait.name)?; + + Ok(match abc_trait.kind { + AbcTraitKind::Slot { + slot_id, + type_name, + value, + } => Trait { + name, + is_final: abc_trait.is_final, + is_override: abc_trait.is_override, + kind: TraitKind::Slot { + slot_id, + type_name: Multiname::from_abc_multiname_static(&unit.abc(), type_name)?, + default_value: if let Some(dv) = value { + Some(abc_default_value(&unit.abc(), &dv)?) + } else { + None + }, + }, + }, + AbcTraitKind::Method { disp_id, method } => Trait { + name, + is_final: abc_trait.is_final, + is_override: abc_trait.is_override, + kind: TraitKind::Method { + disp_id, + method: unit.load_method(method.0)?, + }, + }, + AbcTraitKind::Getter { disp_id, method } => Trait { + name, + is_final: abc_trait.is_final, + is_override: abc_trait.is_override, + kind: TraitKind::Getter { + disp_id, + method: unit.load_method(method.0)?, + }, + }, + AbcTraitKind::Setter { disp_id, method } => Trait { + name, + is_final: abc_trait.is_final, + is_override: abc_trait.is_override, + kind: TraitKind::Setter { + disp_id, + method: unit.load_method(method.0)?, + }, + }, + AbcTraitKind::Class { slot_id, class } => Trait { + name, + is_final: abc_trait.is_final, + is_override: abc_trait.is_override, + kind: TraitKind::Class { + slot_id, + class: unit.load_class(class.0, mc)?, + }, + }, + AbcTraitKind::Function { slot_id, function } => Trait { + name, + is_final: abc_trait.is_final, + is_override: abc_trait.is_override, + kind: TraitKind::Function { + slot_id, + function: unit.load_method(function.0)?, + }, + }, + AbcTraitKind::Const { + slot_id, + type_name, + value, + } => Trait { + name, + is_final: abc_trait.is_final, + is_override: abc_trait.is_override, + kind: TraitKind::Const { + slot_id, + type_name: Multiname::from_abc_multiname_static(&unit.abc(), type_name)?, + default_value: if let Some(dv) = value { + Some(abc_default_value(&unit.abc(), &dv)?) + } else { + None + }, + }, + }, + }) + } + pub fn name(&self) -> &QName { &self.name } From f549d0146e0abe739850b807fa53909bc496e254 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 2 Jul 2020 00:01:07 -0400 Subject: [PATCH 149/189] Fix compilation bugs involved with automatic script initializer execution. --- core/src/avm2.rs | 25 ++++---- core/src/avm2/activation.rs | 10 +-- core/src/avm2/function.rs | 11 ++++ core/src/avm2/script.rs | 120 +++++++++++++++++++++++++++++++++++- 4 files changed, 142 insertions(+), 24 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 77c6588831b4..4eb9a9d38485 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,6 +1,6 @@ //! ActionScript Virtual Machine 2 (AS3) support -use crate::avm2::activation::{Activation, Avm2ScriptEntry}; +use crate::avm2::activation::Activation; use crate::avm2::class::Avm2ClassEntry; use crate::avm2::function::{Avm2MethodEntry, FunctionObject}; use crate::avm2::globals::SystemPrototypes; @@ -8,6 +8,7 @@ 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}; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; use crate::context::UpdateContext; @@ -18,7 +19,7 @@ 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, Script as AbcScript, + Namespace as AbcNamespace, Op, }; use swf::read::SwfRead; @@ -99,29 +100,25 @@ impl<'gc> Avm2<'gc> { let mut read = Reader::new(abc.as_ref()); let abc_file = Rc::new(read.read()?); + let tunit = TranslationUnit::from_abc(abc_file); for i in (0..abc_file.scripts.len()).rev() { - let entrypoint_script: Index = Index::new(i as u32); - let entrypoint: Result = - Avm2ScriptEntry::from_script_index(abc_file.clone(), entrypoint_script.clone()) - .ok_or_else(|| { - format!("Script method {} does not exist", entrypoint_script.0).into() - }); - let entrypoint = entrypoint?; + let script = tunit.load_script(i as u32, context.gc_context)?; let scope = Scope::push_scope(None, self.globals(), context.gc_context); - for trait_entry in entrypoint.script().traits.iter() { + // TODO: Lazyinit means we shouldn't do this until traits are + // actually mentioned... + for trait_entry in script.read().traits()?.iter() { self.globals().install_foreign_trait( self, context, - abc_file.clone(), - trait_entry, + trait_entry.clone(), Some(scope), self.globals(), )?; } - self.insert_stack_frame_for_script(context, entrypoint)?; + self.insert_stack_frame_for_script(context, script)?; } Ok(()) @@ -146,7 +143,7 @@ impl<'gc> Avm2<'gc> { pub fn insert_stack_frame_for_script( &mut self, context: &mut UpdateContext<'_, 'gc, '_>, - script: Avm2ScriptEntry, + script: GcCell<'gc, Script<'gc>>, ) -> Result<(), Error> { self.stack_frames.push(GcCell::allocate( context.gc_context, diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 80e7088be49f..ef0d7eb42fd8 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -3,6 +3,7 @@ use crate::avm2::function::Avm2MethodEntry; use crate::avm2::object::Object; use crate::avm2::scope::Scope; +use crate::avm2::script::Script; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; use crate::avm2::Error; @@ -134,15 +135,10 @@ pub struct Activation<'gc> { impl<'gc> Activation<'gc> { pub fn from_script( context: &mut UpdateContext<'_, 'gc, '_>, - script: Avm2ScriptEntry, + script: GcCell<'gc, Script<'gc>>, global: Object<'gc>, ) -> Result { - let method: Result = - Avm2MethodEntry::from_method_index(script.abc(), script.script().init_method.clone()) - .ok_or_else(|| { - format!("Script index {} is not a valid script", script.abc_script).into() - }); - let method = method?; + let method = script.read().init().as_entry()?; let scope = Some(Scope::push_scope(None, global, context.gc_context)); let num_locals = method.body().num_locals; let local_registers = diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 517f25872032..31b11ca1eadd 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -136,6 +136,17 @@ impl<'gc> From for Method<'gc> { } } +impl<'gc> Method<'gc> { + pub fn as_entry(self) -> Result { + match self { + Method::Native(_) => { + Err("Attempted to unwrap a native method as a user-defined one".into()) + } + Method::Entry(a2me) => Ok(a2me), + } + } +} + /// Represents an AVM2 function. #[derive(Collect, Clone, Debug)] #[collect(no_drop)] diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index 9c45a241510a..235e46cde778 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -1,13 +1,13 @@ //! Whole script representation -use crate::avm2::activation::Avm2ScriptEntry; use crate::avm2::class::Class; use crate::avm2::function::{Avm2MethodEntry, Method}; +use crate::avm2::r#trait::Trait; use crate::avm2::Error; use gc_arena::{Collect, GcCell, MutationContext}; use std::collections::HashMap; use std::rc::Rc; -use swf::avm2::types::{AbcFile, Index}; +use swf::avm2::types::{AbcFile, Index, Script as AbcScript}; #[derive(Clone, Debug, Collect)] #[collect(require_static)] @@ -38,7 +38,7 @@ pub struct TranslationUnit<'gc> { methods: HashMap>, /// All scripts loaded from the ABC's scripts list. - scripts: HashMap>, + scripts: HashMap>>, } impl<'gc> TranslationUnit<'gc> { @@ -89,4 +89,118 @@ impl<'gc> TranslationUnit<'gc> { return Ok(class); } + + /// Load a script from the ABC file and return it's script definition. + pub fn load_script( + &mut self, + script_index: u32, + mc: MutationContext<'gc, '_>, + ) -> Result>, Error> { + if let Some(scripts) = self.scripts.get(&script_index) { + return Ok(scripts.clone()); + } + + let script = Script::from_abc_index(&mut self, script_index, mc)?; + self.scripts.insert(script_index, script); + + script.write(mc).load_traits(&mut self, script_index, mc)?; + + return Ok(script); + } +} + +/// A loaded Script from an ABC file. +#[derive(Clone, Debug, Collect)] +#[collect(no_drop)] +pub struct Script<'gc> { + /// The initializer method to run for the script. + init: Method<'gc>, + + /// Traits that this script uses. + traits: Vec>, + + /// Whether or not we loaded our traits. + traits_loaded: bool, +} + +impl<'gc> Script<'gc> { + /// Construct a script from a `TranslationUnit` and it's script index. + /// + /// The returned script will be allocated, but no traits will be loaded. + /// The caller is responsible for storing the class in the + /// `TranslationUnit` and calling `load_traits` to complete the + /// trait-loading process. + pub fn from_abc_index( + unit: &mut TranslationUnit<'gc>, + script_index: u32, + mc: MutationContext<'gc, '_>, + ) -> Result, Error> { + let script: Result<&AbcScript, Error> = unit + .abc() + .scripts + .get(script_index as usize) + .ok_or_else(|| "LoadError: Script index not valid".into()); + let script = script?; + + let init = unit.load_method(script.init_method.0)?; + + Ok(GcCell::allocate( + mc, + Self { + init, + traits: Vec::new(), + traits_loaded: false, + }, + )) + } + + /// Finish the class-loading process by loading traits. + /// + /// This process must be done after the `Script` has been stored in the + /// `TranslationUnit`. Failing to do so runs the risk of runaway recursion + /// or double-borrows. It should be done before the script is actually + /// executed. + pub fn load_traits( + &mut self, + unit: &mut TranslationUnit<'gc>, + script_index: u32, + mc: MutationContext<'gc, '_>, + ) -> Result<(), Error> { + if self.traits_loaded { + return Ok(()); + } + + self.traits_loaded = true; + + let script: Result<&AbcScript, Error> = unit + .abc() + .scripts + .get(script_index as usize) + .ok_or_else(|| "LoadError: Script index not valid".into()); + let script = script?; + + for abc_trait in script.traits { + self.traits + .push(Trait::from_abc_trait(unit, &abc_trait, mc)?); + } + + Ok(()) + } + + /// Return the entrypoint for the script. + pub fn init(&self) -> Method<'gc> { + self.init + } + + /// Return traits for this script. + /// + /// This function will return an error if it is incorrectly called before + /// traits are loaded. + pub fn traits(&self) -> Result<&[Trait<'gc>], Error> { + if !self.traits_loaded { + return Err("LoadError: Script traits accessed before they were loaded!".into()); + } + + Ok(&self.traits) + } } From 60f3ae3ba7e1d116f6b3254cb357f6470eb67a03 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 2 Jul 2020 18:23:01 -0400 Subject: [PATCH 150/189] Remove `Avm2ScriptEntry`. It is now obsolete and unused. --- core/src/avm2/activation.rs | 40 ------------------------------------- 1 file changed, 40 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index ef0d7eb42fd8..dafc8c720bc5 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -10,46 +10,6 @@ use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::{Collect, GcCell, MutationContext}; use smallvec::SmallVec; -use std::rc::Rc; -use swf::avm2::types::{AbcFile, Index, Script as AbcScript}; - -/// Represents a reference to an AVM2 script. -#[derive(Collect, Clone, Debug)] -#[collect(require_static)] -pub struct Avm2ScriptEntry { - /// The ABC file this function was defined in. - pub abc: Rc, - - /// The ABC method this function uses. - pub abc_script: u32, -} - -impl Avm2ScriptEntry { - /// Take a script index and ABC file, and produce an `Avm2ScriptEntry`. - /// - /// This function returns `None` if the given script does not exist within - /// the given ABC file. - pub fn from_script_index(abc: Rc, script_index: Index) -> Option { - if abc.scripts.get(script_index.0 as usize).is_some() { - return Some(Self { - abc, - abc_script: script_index.0, - }); - } - - None - } - - /// Get the underlying ABC file. - pub fn abc(&self) -> Rc { - self.abc.clone() - } - - /// Get a reference to the ABC script entry this refers to. - pub fn script(&self) -> &AbcScript { - self.abc.scripts.get(self.abc_script as usize).unwrap() - } -} /// Represents a particular register set. /// From 4467bc3193460599076b68a1fbb050427fe32551 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 2 Jul 2020 18:48:37 -0400 Subject: [PATCH 151/189] Make `TranslationUnit` a GC-mandatory type (only referred to by `GcCell`). --- core/src/avm2.rs | 2 +- core/src/avm2/class.rs | 8 ++--- core/src/avm2/script.rs | 77 ++++++++++++++++++++++++++--------------- core/src/avm2/trait.rs | 10 +++--- 4 files changed, 60 insertions(+), 37 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 4eb9a9d38485..20e37b013c9c 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -100,7 +100,7 @@ impl<'gc> Avm2<'gc> { let mut read = Reader::new(abc.as_ref()); let abc_file = Rc::new(read.read()?); - let tunit = TranslationUnit::from_abc(abc_file); + 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)?; diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 6114ed53cf45..99ba2e958269 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -156,7 +156,7 @@ impl<'gc> Class<'gc> { /// caller is responsible for storing the class in the `TranslationUnit` /// and calling `load_traits` to complete the trait-loading process. pub fn from_abc_index( - unit: &mut TranslationUnit<'gc>, + unit: TranslationUnit<'gc>, class_index: u32, mc: MutationContext<'gc, '_>, ) -> Result, Error> { @@ -198,8 +198,8 @@ impl<'gc> Class<'gc> { )?); } - let instance_init = unit.load_method(abc_instance.init_method.0)?; - let class_init = unit.load_method(abc_class.init_method.0)?; + let instance_init = unit.load_method(abc_instance.init_method.0, mc)?; + let class_init = unit.load_method(abc_class.init_method.0, mc)?; Ok(GcCell::allocate( mc, @@ -228,7 +228,7 @@ impl<'gc> Class<'gc> { /// instantiated into an `Object`. pub fn load_traits( &mut self, - unit: &mut TranslationUnit<'gc>, + unit: TranslationUnit<'gc>, class_index: u32, mc: MutationContext<'gc, '_>, ) -> Result<(), Error> { diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index 235e46cde778..c4e557570b28 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -6,6 +6,7 @@ use crate::avm2::r#trait::Trait; use crate::avm2::Error; use gc_arena::{Collect, GcCell, MutationContext}; use std::collections::HashMap; +use std::mem::drop; use std::rc::Rc; use swf::avm2::types::{AbcFile, Index, Script as AbcScript}; @@ -13,6 +14,10 @@ use swf::avm2::types::{AbcFile, Index, Script as AbcScript}; #[collect(require_static)] pub struct CollectWrapper(T); +#[derive(Clone, Debug, Collect)] +#[collect(no_drop)] +pub struct TranslationUnit<'gc>(GcCell<'gc, TranslationUnitData<'gc>>); + /// A loaded ABC file, with any loaded ABC items alongside it. /// /// A `TranslationUnit` is constructed when ABC loading begins, and it stores @@ -27,7 +32,7 @@ pub struct CollectWrapper(T); /// constructing the appropriate runtime object for that item. #[derive(Clone, Debug, Collect)] #[collect(no_drop)] -pub struct TranslationUnit<'gc> { +pub struct TranslationUnitData<'gc> { /// The ABC file that all of the following loaded data comes from. abc: CollectWrapper>, @@ -42,68 +47,86 @@ pub struct TranslationUnit<'gc> { } impl<'gc> TranslationUnit<'gc> { - pub fn from_abc(abc: Rc) -> Self { - Self { - abc: CollectWrapper(abc), - classes: HashMap::new(), - methods: HashMap::new(), - scripts: HashMap::new(), - } + pub fn from_abc(abc: Rc, mc: MutationContext<'gc, '_>) -> Self { + Self(GcCell::allocate( + mc, + TranslationUnitData { + abc: CollectWrapper(abc), + classes: HashMap::new(), + methods: HashMap::new(), + scripts: HashMap::new(), + }, + )) } /// Retrieve the underlying `AbcFile` for this translation unit. - pub fn abc(&self) -> Rc { - self.abc.0 + pub fn abc(self) -> Rc { + self.0.read().abc.0 } /// Load a method from the ABC file and return it's method definition. - pub fn load_method(&mut self, method_index: u32) -> Result, Error> { - if let Some(method) = self.methods.get(&method_index) { + pub fn load_method( + self, + method_index: u32, + mc: MutationContext<'gc, '_>, + ) -> Result, Error> { + let write = self.0.write(mc); + if let Some(method) = write.methods.get(&method_index) { return Ok(method.clone()); } + let abc = write.abc.0; + + drop(write); + let method: Result = - Avm2MethodEntry::from_method_index(self.abc.0, Index::new(method_index)) + Avm2MethodEntry::from_method_index(abc, Index::new(method_index)) .ok_or_else(|| "Method index does not exist".into()); let method = method?.into(); - self.methods.insert(method_index, method); + self.0.write(mc).methods.insert(method_index, method); return Ok(method); } /// Load a class from the ABC file and return it's class definition. pub fn load_class( - &mut self, + self, class_index: u32, mc: MutationContext<'gc, '_>, ) -> Result>, Error> { - if let Some(class) = self.classes.get(&class_index) { + let write = self.0.write(mc); + if let Some(class) = write.classes.get(&class_index) { return Ok(class.clone()); } - let class = Class::from_abc_index(&mut self, class_index, mc)?; - self.classes.insert(class_index, class); + drop(write); - class.write(mc).load_traits(&mut self, class_index, mc)?; + let class = Class::from_abc_index(self, class_index, mc)?; + self.0.write(mc).classes.insert(class_index, class); + + class.write(mc).load_traits(self, class_index, mc)?; return Ok(class); } /// Load a script from the ABC file and return it's script definition. pub fn load_script( - &mut self, + self, script_index: u32, mc: MutationContext<'gc, '_>, ) -> Result>, Error> { - if let Some(scripts) = self.scripts.get(&script_index) { + let write = self.0.write(mc); + if let Some(scripts) = write.scripts.get(&script_index) { return Ok(scripts.clone()); } - let script = Script::from_abc_index(&mut self, script_index, mc)?; - self.scripts.insert(script_index, script); + drop(write); + + let script = Script::from_abc_index(self, script_index, mc)?; + self.0.write(mc).scripts.insert(script_index, script); - script.write(mc).load_traits(&mut self, script_index, mc)?; + script.write(mc).load_traits(self, script_index, mc)?; return Ok(script); } @@ -131,7 +154,7 @@ impl<'gc> Script<'gc> { /// `TranslationUnit` and calling `load_traits` to complete the /// trait-loading process. pub fn from_abc_index( - unit: &mut TranslationUnit<'gc>, + unit: TranslationUnit<'gc>, script_index: u32, mc: MutationContext<'gc, '_>, ) -> Result, Error> { @@ -142,7 +165,7 @@ impl<'gc> Script<'gc> { .ok_or_else(|| "LoadError: Script index not valid".into()); let script = script?; - let init = unit.load_method(script.init_method.0)?; + let init = unit.load_method(script.init_method.0, mc)?; Ok(GcCell::allocate( mc, @@ -162,7 +185,7 @@ impl<'gc> Script<'gc> { /// executed. pub fn load_traits( &mut self, - unit: &mut TranslationUnit<'gc>, + unit: TranslationUnit<'gc>, script_index: u32, mc: MutationContext<'gc, '_>, ) -> Result<(), Error> { diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index 0726de6c7b32..41a70ff633ef 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -83,7 +83,7 @@ pub enum TraitKind<'gc> { impl<'gc> Trait<'gc> { /// Convert an ABC trait into a loaded trait. pub fn from_abc_trait( - unit: &mut TranslationUnit<'gc>, + unit: TranslationUnit<'gc>, abc_trait: &AbcTrait, mc: MutationContext<'gc, '_>, ) -> Result { @@ -114,7 +114,7 @@ impl<'gc> Trait<'gc> { is_override: abc_trait.is_override, kind: TraitKind::Method { disp_id, - method: unit.load_method(method.0)?, + method: unit.load_method(method.0, mc)?, }, }, AbcTraitKind::Getter { disp_id, method } => Trait { @@ -123,7 +123,7 @@ impl<'gc> Trait<'gc> { is_override: abc_trait.is_override, kind: TraitKind::Getter { disp_id, - method: unit.load_method(method.0)?, + method: unit.load_method(method.0, mc)?, }, }, AbcTraitKind::Setter { disp_id, method } => Trait { @@ -132,7 +132,7 @@ impl<'gc> Trait<'gc> { is_override: abc_trait.is_override, kind: TraitKind::Setter { disp_id, - method: unit.load_method(method.0)?, + method: unit.load_method(method.0, mc)?, }, }, AbcTraitKind::Class { slot_id, class } => Trait { @@ -150,7 +150,7 @@ impl<'gc> Trait<'gc> { is_override: abc_trait.is_override, kind: TraitKind::Function { slot_id, - function: unit.load_method(function.0)?, + function: unit.load_method(function.0, mc)?, }, }, AbcTraitKind::Const { From eaebd3c63c3b515a9d279a10396275d669d1a3df Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 2 Jul 2020 19:02:54 -0400 Subject: [PATCH 152/189] Make `Avm2MethodEntry` hold it's `TranslationUnit` rather than an `AbcFile`. --- core/src/avm2.rs | 12 ++++++---- core/src/avm2/activation.rs | 6 ++--- core/src/avm2/function.rs | 46 +++++++++++++++++++++++++------------ core/src/avm2/script.rs | 2 +- 4 files changed, 43 insertions(+), 23 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 20e37b013c9c..ca1dbab6aa6c 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -233,7 +233,7 @@ impl<'gc> Avm2<'gc> { frame_ref.lock()?; let method = frame_ref.method(); - let abc = method.abc.as_ref().clone(); + let abc = method.abc(); let _method_index = method.abc_method; let method_body_index = method.abc_method_body as usize; @@ -367,7 +367,11 @@ impl<'gc> Avm2<'gc> { /// Retrieve the current constant pool for the currently executing function. fn current_abc(&self) -> Option> { self.current_stack_frame() - .map(|sf| sf.read().method().abc.clone()) + .map(|sf| sf.read().method().abc()) + } + + fn current_translation_unit(&self) -> Option> { + self.current_stack_frame().map(|sf| sf.read().method().translation_unit()) } /// Retrieve a int from the current constant pool. @@ -407,8 +411,8 @@ impl<'gc> Avm2<'gc> { } /// Retrieve a method entry from the current ABC file's method table. - fn table_method(&mut self, index: Index) -> Result { - Avm2MethodEntry::from_method_index(self.current_abc().unwrap(), index.clone()) + fn table_method(&mut self, index: Index) -> Result, Error> { + Avm2MethodEntry::from_method_index(self.current_translation_unit().unwrap(), index.clone()) .ok_or_else(|| format!("Method index {} does not exist", index.0).into()) } diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index dafc8c720bc5..4e85097b4c8c 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -50,7 +50,7 @@ impl<'gc> RegisterSet<'gc> { #[collect(no_drop)] pub struct Activation<'gc> { /// The AVM method entry we're executing code out of. - method: Avm2MethodEntry, + method: Avm2MethodEntry<'gc>, /// The current location of the instruction stream being executed. pc: usize, @@ -125,7 +125,7 @@ impl<'gc> Activation<'gc> { pub fn from_action( context: &mut UpdateContext<'_, 'gc, '_>, - method: Avm2MethodEntry, + method: Avm2MethodEntry<'gc>, scope: Option>>, this: Option>, arguments: &[Value<'gc>], @@ -184,7 +184,7 @@ impl<'gc> Activation<'gc> { } /// Obtain a reference to the method being executed. - pub fn method(&self) -> &Avm2MethodEntry { + pub fn method(&self) -> &Avm2MethodEntry<'gc> { &self.method } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 31b11ca1eadd..3cf03e663737 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -7,6 +7,7 @@ use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::r#trait::Trait; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; +use crate::avm2::script::TranslationUnit; use crate::avm2::script_object::{ScriptObjectClass, ScriptObjectData}; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; @@ -39,10 +40,10 @@ pub type NativeFunction<'gc> = fn( /// Represents a reference to an AVM2 method and body. #[derive(Collect, Clone, Debug)] -#[collect(require_static)] -pub struct Avm2MethodEntry { +#[collect(no_drop)] +pub struct Avm2MethodEntry<'gc> { /// The ABC file this function was defined in. - pub abc: Rc, + pub txunit: TranslationUnit<'gc>, /// The ABC method this function uses. pub abc_method: u32, @@ -51,17 +52,22 @@ pub struct Avm2MethodEntry { pub abc_method_body: u32, } -impl Avm2MethodEntry { +impl<'gc> Avm2MethodEntry<'gc> { /// Construct an `Avm2MethodEntry` from an `AbcFile` and method index. /// /// The method body index will be determined by searching through the ABC /// for a matching method. If none exists, this function returns `None`. - pub fn from_method_index(abc: Rc, abc_method: Index) -> Option { + pub fn from_method_index( + txunit: TranslationUnit<'gc>, + abc_method: Index, + ) -> Option { + let abc = txunit.abc(); + if abc.methods.get(abc_method.0 as usize).is_some() { for (index, method_body) in abc.method_bodies.iter().enumerate() { if method_body.method.0 == abc_method.0 { return Some(Self { - abc, + txunit, abc_method: abc_method.0, abc_method_body: index as u32, }); @@ -75,17 +81,27 @@ impl Avm2MethodEntry { /// Get the underlying ABC file. #[allow(dead_code)] pub fn abc(&self) -> Rc { - self.abc.clone() + self.txunit.abc().clone() + } + + /// Get the underlying translation unit this method was defined in. + pub fn translation_unit(&self) -> TranslationUnit<'gc> { + self.txunit } /// Get a reference to the ABC method entry this refers to. pub fn method(&self) -> &AbcMethod { - self.abc.methods.get(self.abc_method as usize).unwrap() + self.txunit + .abc() + .methods + .get(self.abc_method as usize) + .unwrap() } /// Get a reference to the ABC method body entry this refers to. pub fn body(&self) -> &AbcMethodBody { - self.abc + self.txunit + .abc() .method_bodies .get(self.abc_method_body as usize) .unwrap() @@ -100,7 +116,7 @@ pub enum Method<'gc> { Native(NativeFunction<'gc>), /// An ABC-provided method entry. - Entry(Avm2MethodEntry), + Entry(Avm2MethodEntry<'gc>), } unsafe impl<'gc> Collect for Method<'gc> { @@ -130,14 +146,14 @@ impl<'gc> From> for Method<'gc> { } } -impl<'gc> From for Method<'gc> { - fn from(a2me: Avm2MethodEntry) -> Self { +impl<'gc> From> for Method<'gc> { + fn from(a2me: Avm2MethodEntry<'gc>) -> Self { Self::Entry(a2me) } } impl<'gc> Method<'gc> { - pub fn as_entry(self) -> Result { + pub fn as_entry(self) -> Result, Error> { match self { Method::Native(_) => { Err("Attempted to unwrap a native method as a user-defined one".into()) @@ -152,7 +168,7 @@ impl<'gc> Method<'gc> { #[collect(no_drop)] pub struct Avm2Function<'gc> { /// The AVM method entry used to create this function. - pub method: Avm2MethodEntry, + pub method: Avm2MethodEntry<'gc>, /// Closure scope stack at time of creation pub scope: Option>>, @@ -175,7 +191,7 @@ pub enum Executable<'gc> { /// Code defined in a loaded ABC file. Action { /// The method code to execute from a given ABC file. - method: Avm2MethodEntry, + method: Avm2MethodEntry<'gc>, /// The scope stack to pull variables from. scope: Option>>, diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index c4e557570b28..3bb1a968edbf 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -80,7 +80,7 @@ impl<'gc> TranslationUnit<'gc> { drop(write); let method: Result = - Avm2MethodEntry::from_method_index(abc, Index::new(method_index)) + Avm2MethodEntry::from_method_index(self, Index::new(method_index)) .ok_or_else(|| "Method index does not exist".into()); let method = method?.into(); From 232c29dc5eb744db0d52a3443a03cd51c2a14ee0 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 2 Jul 2020 19:06:01 -0400 Subject: [PATCH 153/189] Fix remaining problems with method loading using `callstatic` --- core/src/avm2.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index ca1dbab6aa6c..af41d424dd35 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -371,7 +371,8 @@ impl<'gc> Avm2<'gc> { } fn current_translation_unit(&self) -> Option> { - self.current_stack_frame().map(|sf| sf.read().method().translation_unit()) + self.current_stack_frame() + .map(|sf| sf.read().method().translation_unit()) } /// Retrieve a int from the current constant pool. @@ -751,9 +752,9 @@ impl<'gc> Avm2<'gc> { 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_abc_method( + let function = FunctionObject::from_method( context.gc_context, - method, + method.into(), scope, self.system_prototypes.function, None, @@ -1295,7 +1296,7 @@ impl<'gc> Avm2<'gc> { let mut new_fn = FunctionObject::from_method( context.gc_context, - method_entry, + method_entry.into(), scope, self.system_prototypes.function, None, From 7684736bf73581a8b933e80416f184cf20c09adb Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 2 Jul 2020 19:12:16 -0400 Subject: [PATCH 154/189] `table_class` should resolve `Class`es straight from the current translation unit. --- core/src/avm2.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index af41d424dd35..cec4583b278d 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,7 +1,7 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::Activation; -use crate::avm2::class::Avm2ClassEntry; +use crate::avm2::class::Class; use crate::avm2::function::{Avm2MethodEntry, FunctionObject}; use crate::avm2::globals::SystemPrototypes; use crate::avm2::names::{Multiname, Namespace, QName}; @@ -418,9 +418,14 @@ impl<'gc> Avm2<'gc> { } /// Retrieve a class entry from the current ABC file's method table. - fn table_class(&mut self, index: Index) -> Result { - Avm2ClassEntry::from_class_index(self.current_abc().unwrap(), index.clone()) - .ok_or_else(|| format!("Class index {} does not exist", index.0).into()) + fn table_class( + &mut self, + index: Index, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result>, Error> { + self.current_translation_unit() + .unwrap() + .load_class(index.0, context.gc_context) } /// Run a single action from a given action reader. @@ -1321,11 +1326,11 @@ impl<'gc> Avm2<'gc> { index: Index, ) -> Result<(), Error> { let base_class = self.pop().as_object()?; - let class_entry = self.table_class(index)?; + let class_entry = self.table_class(index, context)?; let scope = self.current_stack_frame().unwrap().read().scope(); let (new_class, class_init) = - FunctionObject::from_abc_class(self, context, class_entry, base_class, scope)?; + FunctionObject::from_class(self, context, class_entry, base_class, scope)?; class_init.call(Some(new_class), &[], self, context, None)?; From 0d2235d2e02af9a30def356295d906c3b816a37e Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 2 Jul 2020 19:51:32 -0400 Subject: [PATCH 155/189] Resolve all remaining compilation issues with this refactor. --- core/src/avm2.rs | 2 +- core/src/avm2/class.rs | 34 ++++++++++++++++------------------ core/src/avm2/function.rs | 22 ++++++++++++++-------- core/src/avm2/object.rs | 4 ++-- core/src/avm2/script.rs | 27 ++++++++++++++------------- core/src/avm2/trait.rs | 28 +++++++++++++++++----------- 6 files changed, 64 insertions(+), 53 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index cec4583b278d..501f05b975f3 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -100,7 +100,7 @@ impl<'gc> Avm2<'gc> { 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); + let tunit = TranslationUnit::from_abc(abc_file.clone(), context.gc_context); for i in (0..abc_file.scripts.len()).rev() { let script = tunit.load_script(i as u32, context.gc_context)?; diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 99ba2e958269..b7a6ca18047a 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -160,41 +160,40 @@ impl<'gc> Class<'gc> { class_index: u32, mc: MutationContext<'gc, '_>, ) -> Result, Error> { - let abc_class: Result<&AbcClass, Error> = unit - .abc() + let abc = unit.abc(); + let abc_class: Result<&AbcClass, Error> = abc .classes .get(class_index as usize) .ok_or("LoadError: Class index not valid".into()); let abc_class = abc_class?; - let abc_instance: Result<&AbcInstance, Error> = unit - .abc() + let abc_instance: Result<&AbcInstance, Error> = abc .instances .get(class_index as usize) .ok_or("LoadError: Instance index not valid".into()); let abc_instance = abc_instance?; - let name = QName::from_abc_multiname(&unit.abc(), abc_instance.name)?; + let name = QName::from_abc_multiname(&unit.abc(), abc_instance.name.clone())?; let super_class = if abc_instance.super_name.0 == 0 { None } else { Some(Multiname::from_abc_multiname_static( &unit.abc(), - abc_instance.super_name, + abc_instance.super_name.clone(), )?) }; - let protected_namespace = if let Some(ns) = abc_instance.protected_namespace { - Some(Namespace::from_abc_namespace(&unit.abc(), ns)?) + let protected_namespace = if let Some(ns) = &abc_instance.protected_namespace { + Some(Namespace::from_abc_namespace(&unit.abc(), ns.clone())?) } else { None }; let mut interfaces = Vec::new(); - for interface_name in abc_instance.interfaces { + for interface_name in abc_instance.interfaces.iter() { interfaces.push(Multiname::from_abc_multiname_static( &unit.abc(), - interface_name, + interface_name.clone(), )?); } @@ -238,26 +237,25 @@ impl<'gc> Class<'gc> { self.traits_loaded = true; - let abc_class: Result<&AbcClass, Error> = unit - .abc() + let abc = unit.abc(); + let abc_class: Result<&AbcClass, Error> = abc .classes .get(class_index as usize) .ok_or_else(|| "LoadError: Class index not valid".into()); let abc_class = abc_class?; - let abc_instance: Result<&AbcInstance, Error> = unit - .abc() + let abc_instance: Result<&AbcInstance, Error> = abc .instances .get(class_index as usize) .ok_or_else(|| "LoadError: Instance index not valid".into()); let abc_instance = abc_instance?; - for abc_trait in abc_instance.traits { + for abc_trait in abc_instance.traits.iter() { self.instance_traits .push(Trait::from_abc_trait(unit, &abc_trait, mc)?); } - for abc_trait in abc_class.traits { + for abc_trait in abc_class.traits.iter() { self.class_traits .push(Trait::from_abc_trait(unit, &abc_trait, mc)?); } @@ -363,11 +361,11 @@ impl<'gc> Class<'gc> { /// Get this class's instance initializer. pub fn instance_init(&self) -> Method<'gc> { - self.instance_init + self.instance_init.clone() } /// Get this class's class initializer. pub fn class_init(&self) -> Method<'gc> { - self.class_init + self.class_init.clone() } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 3cf03e663737..0a2e2506c345 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -17,6 +17,10 @@ use std::fmt; use std::rc::Rc; use swf::avm2::types::{AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody}; +#[derive(Clone, Debug, Collect)] +#[collect(require_static)] +pub struct CollectWrapper(T); + /// Represents a function defined in Ruffle's code. /// /// Parameters are as follows: @@ -42,9 +46,12 @@ pub type NativeFunction<'gc> = fn( #[derive(Collect, Clone, Debug)] #[collect(no_drop)] pub struct Avm2MethodEntry<'gc> { - /// The ABC file this function was defined in. + /// The translation unit this function was defined in. pub txunit: TranslationUnit<'gc>, + /// The underlying ABC file of the above translation unit. + pub abc: CollectWrapper>, + /// The ABC method this function uses. pub abc_method: u32, @@ -68,6 +75,7 @@ impl<'gc> Avm2MethodEntry<'gc> { if method_body.method.0 == abc_method.0 { return Some(Self { txunit, + abc: CollectWrapper(txunit.abc()), abc_method: abc_method.0, abc_method_body: index as u32, }); @@ -91,17 +99,14 @@ impl<'gc> Avm2MethodEntry<'gc> { /// Get a reference to the ABC method entry this refers to. pub fn method(&self) -> &AbcMethod { - self.txunit - .abc() - .methods - .get(self.abc_method as usize) - .unwrap() + &self.abc.0.methods.get(self.abc_method as usize).unwrap() } /// Get a reference to the ABC method body entry this refers to. pub fn body(&self) -> &AbcMethodBody { - self.txunit - .abc() + &self + .abc + .0 .method_bodies .get(self.abc_method_body as usize) .unwrap() @@ -346,6 +351,7 @@ impl<'gc> FunctionObject<'gc> { "Could not resolve superclass prototype {:?}", class_read .super_class_name() + .as_ref() .map(|p| p.local_name()) .unwrap_or(Some("Object")) ) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index eabfa40c2dd1..b88312f5d58b 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -439,7 +439,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy context.gc_context, trait_name, *slot_id, - default_value.unwrap_or(Value::Undefined), + default_value.clone().unwrap_or(Value::Undefined), ); } TraitKind::Method { @@ -535,7 +535,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy context.gc_context, trait_name, *slot_id, - default_value.unwrap_or(Value::Undefined), + default_value.clone().unwrap_or(Value::Undefined), ); } } diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index 3bb1a968edbf..a0010a556732 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -14,7 +14,7 @@ use swf::avm2::types::{AbcFile, Index, Script as AbcScript}; #[collect(require_static)] pub struct CollectWrapper(T); -#[derive(Clone, Debug, Collect)] +#[derive(Copy, Clone, Debug, Collect)] #[collect(no_drop)] pub struct TranslationUnit<'gc>(GcCell<'gc, TranslationUnitData<'gc>>); @@ -61,7 +61,7 @@ impl<'gc> TranslationUnit<'gc> { /// Retrieve the underlying `AbcFile` for this translation unit. pub fn abc(self) -> Rc { - self.0.read().abc.0 + self.0.read().abc.0.clone() } /// Load a method from the ABC file and return it's method definition. @@ -75,16 +75,17 @@ impl<'gc> TranslationUnit<'gc> { return Ok(method.clone()); } - let abc = write.abc.0; - drop(write); - let method: Result = + let method: Result, Error> = Avm2MethodEntry::from_method_index(self, Index::new(method_index)) .ok_or_else(|| "Method index does not exist".into()); - let method = method?.into(); + let method: Method<'gc> = method?.into(); - self.0.write(mc).methods.insert(method_index, method); + self.0 + .write(mc) + .methods + .insert(method_index, method.clone()); return Ok(method); } @@ -158,8 +159,8 @@ impl<'gc> Script<'gc> { script_index: u32, mc: MutationContext<'gc, '_>, ) -> Result, Error> { - let script: Result<&AbcScript, Error> = unit - .abc() + let abc = unit.abc(); + let script: Result<&AbcScript, Error> = abc .scripts .get(script_index as usize) .ok_or_else(|| "LoadError: Script index not valid".into()); @@ -195,14 +196,14 @@ impl<'gc> Script<'gc> { self.traits_loaded = true; - let script: Result<&AbcScript, Error> = unit - .abc() + let abc = unit.abc(); + let script: Result<&AbcScript, Error> = abc .scripts .get(script_index as usize) .ok_or_else(|| "LoadError: Script index not valid".into()); let script = script?; - for abc_trait in script.traits { + for abc_trait in script.traits.iter() { self.traits .push(Trait::from_abc_trait(unit, &abc_trait, mc)?); } @@ -212,7 +213,7 @@ impl<'gc> Script<'gc> { /// Return the entrypoint for the script. pub fn init(&self) -> Method<'gc> { - self.init + self.init.clone() } /// Return traits for this script. diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index 41a70ff633ef..456946c39071 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -87,9 +87,9 @@ impl<'gc> Trait<'gc> { abc_trait: &AbcTrait, mc: MutationContext<'gc, '_>, ) -> Result { - let name = QName::from_abc_multiname(&unit.abc(), abc_trait.name)?; + let name = QName::from_abc_multiname(&unit.abc(), abc_trait.name.clone())?; - Ok(match abc_trait.kind { + Ok(match &abc_trait.kind { AbcTraitKind::Slot { slot_id, type_name, @@ -99,8 +99,11 @@ impl<'gc> Trait<'gc> { is_final: abc_trait.is_final, is_override: abc_trait.is_override, kind: TraitKind::Slot { - slot_id, - type_name: Multiname::from_abc_multiname_static(&unit.abc(), type_name)?, + slot_id: *slot_id, + type_name: Multiname::from_abc_multiname_static( + &unit.abc(), + type_name.clone(), + )?, default_value: if let Some(dv) = value { Some(abc_default_value(&unit.abc(), &dv)?) } else { @@ -113,7 +116,7 @@ impl<'gc> Trait<'gc> { is_final: abc_trait.is_final, is_override: abc_trait.is_override, kind: TraitKind::Method { - disp_id, + disp_id: *disp_id, method: unit.load_method(method.0, mc)?, }, }, @@ -122,7 +125,7 @@ impl<'gc> Trait<'gc> { is_final: abc_trait.is_final, is_override: abc_trait.is_override, kind: TraitKind::Getter { - disp_id, + disp_id: *disp_id, method: unit.load_method(method.0, mc)?, }, }, @@ -131,7 +134,7 @@ impl<'gc> Trait<'gc> { is_final: abc_trait.is_final, is_override: abc_trait.is_override, kind: TraitKind::Setter { - disp_id, + disp_id: *disp_id, method: unit.load_method(method.0, mc)?, }, }, @@ -140,7 +143,7 @@ impl<'gc> Trait<'gc> { is_final: abc_trait.is_final, is_override: abc_trait.is_override, kind: TraitKind::Class { - slot_id, + slot_id: *slot_id, class: unit.load_class(class.0, mc)?, }, }, @@ -149,7 +152,7 @@ impl<'gc> Trait<'gc> { is_final: abc_trait.is_final, is_override: abc_trait.is_override, kind: TraitKind::Function { - slot_id, + slot_id: *slot_id, function: unit.load_method(function.0, mc)?, }, }, @@ -162,8 +165,11 @@ impl<'gc> Trait<'gc> { is_final: abc_trait.is_final, is_override: abc_trait.is_override, kind: TraitKind::Const { - slot_id, - type_name: Multiname::from_abc_multiname_static(&unit.abc(), type_name)?, + slot_id: *slot_id, + type_name: Multiname::from_abc_multiname_static( + &unit.abc(), + type_name.clone(), + )?, default_value: if let Some(dv) = value { Some(abc_default_value(&unit.abc(), &dv)?) } else { From 041cb0b5c31f980e06adba1bc5df19f531ef0580 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 2 Jul 2020 21:05:32 -0400 Subject: [PATCH 156/189] Resolve multiname constant zero as an error rather than a panic. While some code that references pool multinames has zero as a valid index, we cannot validate exactly what the zero index is for a given index. Hence, callers instantiating multinames must check for zero and substitute the correct zero-value interpretation for their given type. If zero is an invalid value, it should ideally throw a different error than what's provided here. --- core/src/avm2/names.rs | 18 ++++++++++++++++-- core/src/avm2/trait.rs | 18 ++++++++++-------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 153a72ba0623..e80ad07852e3 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -196,7 +196,10 @@ impl Multiname { multiname_index: Index, avm: &mut Avm2<'_>, ) -> Result { - let actual_index = multiname_index.0 as usize - 1; + let actual_index: Result = (multiname_index.0 as usize) + .checked_sub(1) + .ok_or("Attempted to resolve a multiname at index zero. This is a bug.".into()); + let actual_index = actual_index?; let abc_multiname: Result<&AbcMultiname, Error> = file .constant_pool .multinames @@ -255,7 +258,10 @@ impl Multiname { file: &AbcFile, multiname_index: Index, ) -> Result { - let actual_index = multiname_index.0 as usize - 1; + let actual_index: Result = (multiname_index.0 as usize).checked_sub(1).ok_or( + "Attempted to resolve a (static) multiname at index zero. This is a bug.".into(), + ); + let actual_index = actual_index?; let abc_multiname: Result<&AbcMultiname, Error> = file .constant_pool .multinames @@ -284,6 +290,14 @@ impl Multiname { }) } + /// Indicates the any type (any name in any namespace). + pub fn any() -> Self { + Self { + ns: vec![Namespace::Any], + name: None, + } + } + pub fn namespace_set(&self) -> impl Iterator { self.ns.iter() } diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index 456946c39071..3b425c7d2463 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -100,10 +100,11 @@ impl<'gc> Trait<'gc> { is_override: abc_trait.is_override, kind: TraitKind::Slot { slot_id: *slot_id, - type_name: Multiname::from_abc_multiname_static( - &unit.abc(), - type_name.clone(), - )?, + type_name: if type_name.0 == 0 { + Multiname::any() + } else { + Multiname::from_abc_multiname_static(&unit.abc(), type_name.clone())? + }, default_value: if let Some(dv) = value { Some(abc_default_value(&unit.abc(), &dv)?) } else { @@ -166,10 +167,11 @@ impl<'gc> Trait<'gc> { is_override: abc_trait.is_override, kind: TraitKind::Const { slot_id: *slot_id, - type_name: Multiname::from_abc_multiname_static( - &unit.abc(), - type_name.clone(), - )?, + type_name: if type_name.0 == 0 { + Multiname::any() + } else { + Multiname::from_abc_multiname_static(&unit.abc(), type_name.clone())? + }, default_value: if let Some(dv) = value { Some(abc_default_value(&unit.abc(), &dv)?) } else { From 12fc13da7f7512a1b98ce42f4a044c0d9c8f5e0d Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 2 Jul 2020 21:11:10 -0400 Subject: [PATCH 157/189] Clippy compliance for the last batch of commits. --- core/src/avm2/activation.rs | 2 +- core/src/avm2/class.rs | 4 ++-- core/src/avm2/function.rs | 10 +++++----- core/src/avm2/names.rs | 9 +++++---- core/src/avm2/object.rs | 2 +- core/src/avm2/script.rs | 10 +++++----- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 4e85097b4c8c..d8fc49894680 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -98,7 +98,7 @@ impl<'gc> Activation<'gc> { script: GcCell<'gc, Script<'gc>>, global: Object<'gc>, ) -> Result { - let method = script.read().init().as_entry()?; + let method = script.read().init().into_entry()?; let scope = Some(Scope::push_scope(None, global, context.gc_context)); let num_locals = method.body().num_locals; let local_registers = diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index b7a6ca18047a..6b6dbe186112 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -164,13 +164,13 @@ impl<'gc> Class<'gc> { let abc_class: Result<&AbcClass, Error> = abc .classes .get(class_index as usize) - .ok_or("LoadError: Class index not valid".into()); + .ok_or_else(|| "LoadError: Class index not valid".into()); let abc_class = abc_class?; let abc_instance: Result<&AbcInstance, Error> = abc .instances .get(class_index as usize) - .ok_or("LoadError: Instance index not valid".into()); + .ok_or_else(|| "LoadError: Instance index not valid".into()); let abc_instance = abc_instance?; let name = QName::from_abc_multiname(&unit.abc(), abc_instance.name.clone())?; diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 0a2e2506c345..6ab478e621ed 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -89,7 +89,7 @@ impl<'gc> Avm2MethodEntry<'gc> { /// Get the underlying ABC file. #[allow(dead_code)] pub fn abc(&self) -> Rc { - self.txunit.abc().clone() + self.txunit.abc() } /// Get the underlying translation unit this method was defined in. @@ -158,7 +158,7 @@ impl<'gc> From> for Method<'gc> { } impl<'gc> Method<'gc> { - pub fn as_entry(self) -> Result, Error> { + pub fn into_entry(self) -> Result, Error> { match self { Method::Native(_) => { Err("Attempted to unwrap a native method as a user-defined one".into()) @@ -273,7 +273,7 @@ impl<'gc> Executable<'gc> { Activation::from_action( context, method.clone(), - scope.clone(), + *scope, reciever, arguments, base_proto, @@ -368,9 +368,9 @@ impl<'gc> FunctionObject<'gc> { FunctionObjectData { base: ScriptObjectData::base_new( Some(fn_proto), - ScriptObjectClass::ClassConstructor(class.clone(), scope), + ScriptObjectClass::ClassConstructor(class, scope), ), - exec: Some(Executable::from_method(initializer, scope, None).into()), + exec: Some(Executable::from_method(initializer, scope, None)), }, )) .into(); diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index e80ad07852e3..61f06021d2a0 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -198,7 +198,7 @@ impl Multiname { ) -> Result { let actual_index: Result = (multiname_index.0 as usize) .checked_sub(1) - .ok_or("Attempted to resolve a multiname at index zero. This is a bug.".into()); + .ok_or_else(|| "Attempted to resolve a multiname at index zero. This is a bug.".into()); let actual_index = actual_index?; let abc_multiname: Result<&AbcMultiname, Error> = file .constant_pool @@ -258,9 +258,10 @@ impl Multiname { file: &AbcFile, multiname_index: Index, ) -> Result { - let actual_index: Result = (multiname_index.0 as usize).checked_sub(1).ok_or( - "Attempted to resolve a (static) multiname at index zero. This is a bug.".into(), - ); + let actual_index: Result = + (multiname_index.0 as usize).checked_sub(1).ok_or_else(|| { + "Attempted to resolve a (static) multiname at index zero. This is a bug.".into() + }); let actual_index = actual_index?; let abc_multiname: Result<&AbcMultiname, Error> = file .constant_pool diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index b88312f5d58b..104295457a0f 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -485,7 +485,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy //then? let super_name = if let Some(sc_name) = class_read.super_class_name() { self.resolve_multiname(sc_name)? - .unwrap_or(QName::dynamic_name("Object")) + .unwrap_or_else(|| QName::dynamic_name("Object")) } else { QName::dynamic_name("Object") }; diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index a0010a556732..179292981d58 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -87,7 +87,7 @@ impl<'gc> TranslationUnit<'gc> { .methods .insert(method_index, method.clone()); - return Ok(method); + Ok(method) } /// Load a class from the ABC file and return it's class definition. @@ -98,7 +98,7 @@ impl<'gc> TranslationUnit<'gc> { ) -> Result>, Error> { let write = self.0.write(mc); if let Some(class) = write.classes.get(&class_index) { - return Ok(class.clone()); + return Ok(*class); } drop(write); @@ -108,7 +108,7 @@ impl<'gc> TranslationUnit<'gc> { class.write(mc).load_traits(self, class_index, mc)?; - return Ok(class); + Ok(class) } /// Load a script from the ABC file and return it's script definition. @@ -119,7 +119,7 @@ impl<'gc> TranslationUnit<'gc> { ) -> Result>, Error> { let write = self.0.write(mc); if let Some(scripts) = write.scripts.get(&script_index) { - return Ok(scripts.clone()); + return Ok(*scripts); } drop(write); @@ -129,7 +129,7 @@ impl<'gc> TranslationUnit<'gc> { script.write(mc).load_traits(self, script_index, mc)?; - return Ok(script); + Ok(script) } } From b6e05519cd9330590724ec2c448a11e7350dfb0d Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 2 Jul 2020 21:19:31 -0400 Subject: [PATCH 158/189] Remove `Avm2ClassEntry`. It is no longer used. --- core/src/avm2/class.rs | 56 +----------------------------------------- 1 file changed, 1 insertion(+), 55 deletions(-) diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 6b6dbe186112..70b847c03be7 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -6,61 +6,7 @@ use crate::avm2::r#trait::{Trait, TraitKind}; use crate::avm2::script::TranslationUnit; use crate::avm2::Error; use gc_arena::{Collect, GcCell, MutationContext}; -use std::rc::Rc; -use swf::avm2::types::{AbcFile, Class as AbcClass, Index, Instance as AbcInstance}; - -/// Represents a reference to an AVM2 class. -/// -/// For some reason, this comes in two parts, one for static properties (called -/// the "class") and one for dynamic properties (called the "instance", even -/// though it really defines what ES3/AS2 would call a prototype) -#[derive(Collect, Clone, Debug)] -#[collect(require_static)] -pub struct Avm2ClassEntry { - /// The ABC file this function was defined in. - pub abc: Rc, - - /// The ABC class (used to define static properties). - /// - /// This is also the index of the ABC instance, which holds instance - /// properties. - pub abc_class: u32, -} - -impl Avm2ClassEntry { - /// Construct an `Avm2MethodEntry` from an `AbcFile` and method index. - /// - /// This function returns `None` if the given class index does not resolve - /// to a valid ABC class, or a valid ABC instance. As mentioned in the type - /// documentation, ABC classes and instances are intended to be paired. - pub fn from_class_index(abc: Rc, abc_class: Index) -> Option { - if abc.classes.get(abc_class.0 as usize).is_some() - && abc.instances.get(abc_class.0 as usize).is_some() - { - return Some(Self { - abc, - abc_class: abc_class.0, - }); - } - - None - } - - /// Get the underlying ABC file. - pub fn abc(&self) -> Rc { - self.abc.clone() - } - - /// Get a reference to the ABC class entry this refers to. - pub fn class(&self) -> &AbcClass { - self.abc.classes.get(self.abc_class as usize).unwrap() - } - - /// Get a reference to the ABC class instance entry this refers to. - pub fn instance(&self) -> &AbcInstance { - self.abc.instances.get(self.abc_class as usize).unwrap() - } -} +use swf::avm2::types::{Class as AbcClass, Instance as AbcInstance}; /// A loaded ABC Class which can be used to construct objects with. #[derive(Clone, Debug, Collect)] From 098b034de0ef7254652944758d306cc665bcecd8 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 2 Jul 2020 21:49:53 -0400 Subject: [PATCH 159/189] Refactor method-related structs into a separate method module. This also renames `NativeFunction` and `Avm2MethodEntry` to `NativeMethod` and `BytecodeMethod`, respectively. --- core/src/avm2.rs | 8 +- core/src/avm2/activation.rs | 8 +- core/src/avm2/class.rs | 2 +- core/src/avm2/function.rs | 165 ++---------------------------------- core/src/avm2/globals.rs | 7 +- core/src/avm2/method.rs | 163 +++++++++++++++++++++++++++++++++++ core/src/avm2/script.rs | 6 +- core/src/avm2/trait.rs | 2 +- 8 files changed, 187 insertions(+), 174 deletions(-) create mode 100644 core/src/avm2/method.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 501f05b975f3..739e8660855e 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -2,8 +2,9 @@ use crate::avm2::activation::Activation; use crate::avm2::class::Class; -use crate::avm2::function::{Avm2MethodEntry, FunctionObject}; +use crate::avm2::function::FunctionObject; use crate::avm2::globals::SystemPrototypes; +use crate::avm2::method::BytecodeMethod; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::return_value::ReturnValue; @@ -35,6 +36,7 @@ mod activation; mod class; mod function; mod globals; +mod method; mod names; mod object; mod property; @@ -412,8 +414,8 @@ impl<'gc> Avm2<'gc> { } /// Retrieve a method entry from the current ABC file's method table. - fn table_method(&mut self, index: Index) -> Result, Error> { - Avm2MethodEntry::from_method_index(self.current_translation_unit().unwrap(), index.clone()) + fn table_method(&mut self, index: Index) -> Result, Error> { + BytecodeMethod::from_method_index(self.current_translation_unit().unwrap(), index.clone()) .ok_or_else(|| format!("Method index {} does not exist", index.0).into()) } diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index d8fc49894680..4cf6016c91eb 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -1,6 +1,6 @@ //! Activation frames -use crate::avm2::function::Avm2MethodEntry; +use crate::avm2::method::BytecodeMethod; use crate::avm2::object::Object; use crate::avm2::scope::Scope; use crate::avm2::script::Script; @@ -50,7 +50,7 @@ impl<'gc> RegisterSet<'gc> { #[collect(no_drop)] pub struct Activation<'gc> { /// The AVM method entry we're executing code out of. - method: Avm2MethodEntry<'gc>, + method: BytecodeMethod<'gc>, /// The current location of the instruction stream being executed. pc: usize, @@ -125,7 +125,7 @@ impl<'gc> Activation<'gc> { pub fn from_action( context: &mut UpdateContext<'_, 'gc, '_>, - method: Avm2MethodEntry<'gc>, + method: BytecodeMethod<'gc>, scope: Option>>, this: Option>, arguments: &[Value<'gc>], @@ -184,7 +184,7 @@ impl<'gc> Activation<'gc> { } /// Obtain a reference to the method being executed. - pub fn method(&self) -> &Avm2MethodEntry<'gc> { + pub fn method(&self) -> &BytecodeMethod<'gc> { &self.method } diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 70b847c03be7..9edaae217069 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -1,6 +1,6 @@ //! AVM2 classes -use crate::avm2::function::Method; +use crate::avm2::method::Method; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::r#trait::{Trait, TraitKind}; use crate::avm2::script::TranslationUnit; diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 6ab478e621ed..da621b11479d 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -2,178 +2,25 @@ use crate::avm2::activation::Activation; use crate::avm2::class::Class; +use crate::avm2::method::{BytecodeMethod, Method, NativeMethod}; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::r#trait::Trait; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; -use crate::avm2::script::TranslationUnit; use crate::avm2::script_object::{ScriptObjectClass, ScriptObjectData}; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use gc_arena::{Collect, CollectionContext, GcCell, MutationContext}; use std::fmt; -use std::rc::Rc; -use swf::avm2::types::{AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody}; - -#[derive(Clone, Debug, Collect)] -#[collect(require_static)] -pub struct CollectWrapper(T); - -/// Represents a function defined in Ruffle's code. -/// -/// Parameters are as follows: -/// -/// * The AVM2 runtime -/// * The action context -/// * The current `this` object -/// * The arguments this function was called with -/// -/// Native functions are allowed to return a value or `None`. `None` indicates -/// that the given value will not be returned on the stack and instead will -/// resolve on the AVM stack, as if you had called a non-native function. If -/// your function yields `None`, you must ensure that the top-most activation -/// in the AVM1 runtime will return with the value of this function. -pub type NativeFunction<'gc> = fn( - &mut Avm2<'gc>, - &mut UpdateContext<'_, 'gc, '_>, - Option>, - &[Value<'gc>], -) -> Result, Error>; - -/// Represents a reference to an AVM2 method and body. -#[derive(Collect, Clone, Debug)] -#[collect(no_drop)] -pub struct Avm2MethodEntry<'gc> { - /// The translation unit this function was defined in. - pub txunit: TranslationUnit<'gc>, - - /// The underlying ABC file of the above translation unit. - pub abc: CollectWrapper>, - - /// The ABC method this function uses. - pub abc_method: u32, - - /// The ABC method body this function uses. - pub abc_method_body: u32, -} - -impl<'gc> Avm2MethodEntry<'gc> { - /// Construct an `Avm2MethodEntry` from an `AbcFile` and method index. - /// - /// The method body index will be determined by searching through the ABC - /// for a matching method. If none exists, this function returns `None`. - pub fn from_method_index( - txunit: TranslationUnit<'gc>, - abc_method: Index, - ) -> Option { - let abc = txunit.abc(); - - if abc.methods.get(abc_method.0 as usize).is_some() { - for (index, method_body) in abc.method_bodies.iter().enumerate() { - if method_body.method.0 == abc_method.0 { - return Some(Self { - txunit, - abc: CollectWrapper(txunit.abc()), - abc_method: abc_method.0, - abc_method_body: index as u32, - }); - } - } - } - - None - } - - /// Get the underlying ABC file. - #[allow(dead_code)] - pub fn abc(&self) -> Rc { - self.txunit.abc() - } - - /// Get the underlying translation unit this method was defined in. - pub fn translation_unit(&self) -> TranslationUnit<'gc> { - self.txunit - } - - /// Get a reference to the ABC method entry this refers to. - pub fn method(&self) -> &AbcMethod { - &self.abc.0.methods.get(self.abc_method as usize).unwrap() - } - - /// Get a reference to the ABC method body entry this refers to. - pub fn body(&self) -> &AbcMethodBody { - &self - .abc - .0 - .method_bodies - .get(self.abc_method_body as usize) - .unwrap() - } -} - -/// An uninstantiated method that can either be natively implemented or sourced -/// from an ABC file. -#[derive(Clone)] -pub enum Method<'gc> { - /// A native method. - Native(NativeFunction<'gc>), - - /// An ABC-provided method entry. - Entry(Avm2MethodEntry<'gc>), -} - -unsafe impl<'gc> Collect for Method<'gc> { - fn trace(&self, cc: CollectionContext) { - match self { - Method::Native(_nf) => {} - Method::Entry(a2me) => a2me.trace(cc), - } - } -} - -impl<'gc> fmt::Debug for Method<'gc> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Method::Native(_nf) => f - .debug_tuple("Method::Native") - .field(&"".to_string()) - .finish(), - Method::Entry(a2me) => f.debug_tuple("Method::Entry").field(a2me).finish(), - } - } -} - -impl<'gc> From> for Method<'gc> { - fn from(nf: NativeFunction<'gc>) -> Self { - Self::Native(nf) - } -} - -impl<'gc> From> for Method<'gc> { - fn from(a2me: Avm2MethodEntry<'gc>) -> Self { - Self::Entry(a2me) - } -} - -impl<'gc> Method<'gc> { - pub fn into_entry(self) -> Result, Error> { - match self { - Method::Native(_) => { - Err("Attempted to unwrap a native method as a user-defined one".into()) - } - Method::Entry(a2me) => Ok(a2me), - } - } -} /// Represents an AVM2 function. #[derive(Collect, Clone, Debug)] #[collect(no_drop)] pub struct Avm2Function<'gc> { /// The AVM method entry used to create this function. - pub method: Avm2MethodEntry<'gc>, + pub method: BytecodeMethod<'gc>, /// Closure scope stack at time of creation pub scope: Option>>, @@ -191,12 +38,12 @@ pub enum Executable<'gc> { /// Code defined in Ruffle's binary. /// /// The second parameter stores the bound reciever for this function. - Native(NativeFunction<'gc>, Option>), + Native(NativeMethod<'gc>, Option>), /// Code defined in a loaded ABC file. Action { /// The method code to execute from a given ABC file. - method: Avm2MethodEntry<'gc>, + method: BytecodeMethod<'gc>, /// The scope stack to pull variables from. scope: Option>>, @@ -424,7 +271,7 @@ impl<'gc> FunctionObject<'gc> { /// Construct a builtin function object from a Rust function. pub fn from_builtin( mc: MutationContext<'gc, '_>, - nf: NativeFunction<'gc>, + nf: NativeMethod<'gc>, fn_proto: Object<'gc>, ) -> Object<'gc> { FunctionObject(GcCell::allocate( @@ -440,7 +287,7 @@ impl<'gc> FunctionObject<'gc> { /// Construct a builtin type from a Rust constructor and prototype. pub fn from_builtin_constr( mc: MutationContext<'gc, '_>, - constr: NativeFunction<'gc>, + constr: NativeMethod<'gc>, mut prototype: Object<'gc>, fn_proto: Object<'gc>, ) -> Result, Error> { diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 842f9a03f421..4986131048af 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -1,6 +1,7 @@ //! Global scope built-ins -use crate::avm2::function::{FunctionObject, NativeFunction}; +use crate::avm2::function::FunctionObject; +use crate::avm2::method::NativeMethod; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::return_value::ReturnValue; @@ -44,7 +45,7 @@ fn function<'gc>( mut global_scope: Object<'gc>, package: &str, name: &str, - nf: NativeFunction<'gc>, + nf: NativeMethod<'gc>, fn_proto: Object<'gc>, ) { global_scope @@ -62,7 +63,7 @@ fn class<'gc>( mut global_scope: Object<'gc>, package: &str, name: &str, - constr: NativeFunction<'gc>, + constr: NativeMethod<'gc>, proto: Object<'gc>, fn_proto: Object<'gc>, ) { diff --git a/core/src/avm2/method.rs b/core/src/avm2/method.rs new file mode 100644 index 000000000000..1bd5d6a320e3 --- /dev/null +++ b/core/src/avm2/method.rs @@ -0,0 +1,163 @@ +//! AVM2 methods + +use crate::avm2::object::Object; +use crate::avm2::return_value::ReturnValue; +use crate::avm2::script::TranslationUnit; +use crate::avm2::value::Value; +use crate::avm2::{Avm2, Error}; +use crate::context::UpdateContext; +use gc_arena::{Collect, CollectionContext}; +use std::fmt; +use std::rc::Rc; +use swf::avm2::types::{AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody}; + +#[derive(Clone, Debug, Collect)] +#[collect(require_static)] +pub struct CollectWrapper(T); + +/// Represents a function defined in Ruffle's code. +/// +/// Parameters are as follows: +/// +/// * The AVM2 runtime +/// * The action context +/// * The current `this` object +/// * The arguments this function was called with +/// +/// Native functions are allowed to return a value or `None`. `None` indicates +/// that the given value will not be returned on the stack and instead will +/// resolve on the AVM stack, as if you had called a non-native function. If +/// your function yields `None`, you must ensure that the top-most activation +/// in the AVM1 runtime will return with the value of this function. +pub type NativeMethod<'gc> = fn( + &mut Avm2<'gc>, + &mut UpdateContext<'_, 'gc, '_>, + Option>, + &[Value<'gc>], +) -> Result, Error>; + +/// Represents a reference to an AVM2 method and body. +#[derive(Collect, Clone, Debug)] +#[collect(no_drop)] +pub struct BytecodeMethod<'gc> { + /// The translation unit this function was defined in. + pub txunit: TranslationUnit<'gc>, + + /// The underlying ABC file of the above translation unit. + pub abc: CollectWrapper>, + + /// The ABC method this function uses. + pub abc_method: u32, + + /// The ABC method body this function uses. + pub abc_method_body: u32, +} + +impl<'gc> BytecodeMethod<'gc> { + /// Construct an `BytecodeMethod` from an `AbcFile` and method index. + /// + /// The method body index will be determined by searching through the ABC + /// for a matching method. If none exists, this function returns `None`. + pub fn from_method_index( + txunit: TranslationUnit<'gc>, + abc_method: Index, + ) -> Option { + let abc = txunit.abc(); + + if abc.methods.get(abc_method.0 as usize).is_some() { + for (index, method_body) in abc.method_bodies.iter().enumerate() { + if method_body.method.0 == abc_method.0 { + return Some(Self { + txunit, + abc: CollectWrapper(txunit.abc()), + abc_method: abc_method.0, + abc_method_body: index as u32, + }); + } + } + } + + None + } + + /// Get the underlying ABC file. + #[allow(dead_code)] + pub fn abc(&self) -> Rc { + self.txunit.abc() + } + + /// Get the underlying translation unit this method was defined in. + pub fn translation_unit(&self) -> TranslationUnit<'gc> { + self.txunit + } + + /// Get a reference to the ABC method entry this refers to. + pub fn method(&self) -> &AbcMethod { + &self.abc.0.methods.get(self.abc_method as usize).unwrap() + } + + /// Get a reference to the ABC method body entry this refers to. + pub fn body(&self) -> &AbcMethodBody { + &self + .abc + .0 + .method_bodies + .get(self.abc_method_body as usize) + .unwrap() + } +} + +/// An uninstantiated method that can either be natively implemented or sourced +/// from an ABC file. +#[derive(Clone)] +pub enum Method<'gc> { + /// A native method. + Native(NativeMethod<'gc>), + + /// An ABC-provided method entry. + Entry(BytecodeMethod<'gc>), +} + +unsafe impl<'gc> Collect for Method<'gc> { + fn trace(&self, cc: CollectionContext) { + match self { + Method::Native(_nf) => {} + Method::Entry(a2me) => a2me.trace(cc), + } + } +} + +impl<'gc> fmt::Debug for Method<'gc> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Method::Native(_nf) => f + .debug_tuple("Method::Native") + .field(&"".to_string()) + .finish(), + Method::Entry(a2me) => f.debug_tuple("Method::Entry").field(a2me).finish(), + } + } +} + +impl<'gc> From> for Method<'gc> { + fn from(nf: NativeMethod<'gc>) -> Self { + Self::Native(nf) + } +} + +impl<'gc> From> for Method<'gc> { + fn from(a2me: BytecodeMethod<'gc>) -> Self { + Self::Entry(a2me) + } +} + +impl<'gc> Method<'gc> { + pub fn into_entry(self) -> Result, Error> { + match self { + Method::Native(_) => { + Err("Attempted to unwrap a native method as a user-defined one".into()) + } + Method::Entry(a2me) => Ok(a2me), + } + } +} diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index 179292981d58..684b5a9391c9 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -1,7 +1,7 @@ //! Whole script representation use crate::avm2::class::Class; -use crate::avm2::function::{Avm2MethodEntry, Method}; +use crate::avm2::method::{BytecodeMethod, Method}; use crate::avm2::r#trait::Trait; use crate::avm2::Error; use gc_arena::{Collect, GcCell, MutationContext}; @@ -77,8 +77,8 @@ impl<'gc> TranslationUnit<'gc> { drop(write); - let method: Result, Error> = - Avm2MethodEntry::from_method_index(self, Index::new(method_index)) + let method: Result, Error> = + BytecodeMethod::from_method_index(self, Index::new(method_index)) .ok_or_else(|| "Method index does not exist".into()); let method: Method<'gc> = method?.into(); diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index 3b425c7d2463..071d4291b386 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -1,7 +1,7 @@ //! Active trait definitions use crate::avm2::class::Class; -use crate::avm2::function::Method; +use crate::avm2::method::Method; use crate::avm2::names::{Multiname, QName}; use crate::avm2::script::TranslationUnit; use crate::avm2::value::{abc_default_value, Value}; From 5b5bf0719e8e4eb96b1d583aea7631a9491f2f5f Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 2 Jul 2020 21:58:54 -0400 Subject: [PATCH 160/189] Remove `Avm2Function` as it is no longer used. --- core/src/avm2/function.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index da621b11479d..84d959144d6e 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -15,23 +15,6 @@ use crate::context::UpdateContext; use gc_arena::{Collect, CollectionContext, GcCell, MutationContext}; use std::fmt; -/// Represents an AVM2 function. -#[derive(Collect, Clone, Debug)] -#[collect(no_drop)] -pub struct Avm2Function<'gc> { - /// The AVM method entry used to create this function. - pub method: BytecodeMethod<'gc>, - - /// Closure scope stack at time of creation - pub scope: Option>>, - - /// The reciever this method is attached to. - /// - /// Objects without a reciever are free functions that can be invoked with - /// any desired parameter for `this`. - pub reciever: Option>, -} - /// Represents code that can be executed by some means. #[derive(Clone)] pub enum Executable<'gc> { From 97e005622b63de57c2912f4527cc1f75bbb5e026 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 4 Jul 2020 17:18:41 -0400 Subject: [PATCH 161/189] Invert the role of `Avm2` and it's `Activation`, similar to what was done with `Avm1` and it's `Activation`. This also results in a far reduced role for `ReturnValue`, since I also took the liberty of removing most of it's use. Furthermore, I also made it apply equally to native and AVM2 code, which ensures all native implementations of methods don't double-borrow. In AVM1, `ReturnValue` was actually removed entirely, because it's not needed. I attempted to do the same, but the fact that we're currently embedding `ScriptObjectData` in native objects means that we need it for virtual properties. Otherwise, virtual property implementations will see locked objects, which is bad. --- core/src/avm2.rs | 1416 +---------------- core/src/avm2/activation.rs | 1372 +++++++++++++++- core/src/avm2/function.rs | 103 +- core/src/avm2/globals.rs | 10 +- core/src/avm2/globals/class.rs | 8 +- .../globals/flash/display/displayobject.rs | 10 +- .../flash/display/displayobjectcontainer.rs | 10 +- .../flash/display/interactiveobject.rs | 10 +- .../avm2/globals/flash/display/movieclip.rs | 10 +- core/src/avm2/globals/flash/display/sprite.rs | 10 +- .../globals/flash/events/eventdispatcher.rs | 10 +- core/src/avm2/globals/function.rs | 20 +- core/src/avm2/globals/object.rs | 51 +- core/src/avm2/method.rs | 8 +- core/src/avm2/object.rs | 58 +- core/src/avm2/property.rs | 36 +- core/src/avm2/return_value.rs | 227 +-- core/src/avm2/scope.rs | 28 +- core/src/avm2/script_object.rs | 86 +- core/src/player.rs | 2 - 20 files changed, 1657 insertions(+), 1828 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 739e8660855e..d1da0b356cde 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,28 +1,17 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::Activation; -use crate::avm2::class::Class; -use crate::avm2::function::FunctionObject; use crate::avm2::globals::SystemPrototypes; -use crate::avm2::method::BytecodeMethod; -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}; -use crate::avm2::script_object::ScriptObject; +use crate::avm2::script::Script; +use crate::avm2::script::TranslationUnit; 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; #[macro_export] macro_rules! avm_debug { @@ -58,9 +47,6 @@ type Error = Box; #[derive(Collect)] #[collect(no_drop)] pub struct Avm2<'gc> { - /// All activation records for the current interpreter. - stack_frames: Vec>>, - /// Values currently present on the operand stack. stack: Vec>, @@ -77,7 +63,6 @@ impl<'gc> Avm2<'gc> { let (globals, system_prototypes) = globals::construct_global_scope(mc); Self { - stack_frames: Vec::new(), stack: Vec::new(), globals, system_prototypes, @@ -89,6 +74,17 @@ impl<'gc> Avm2<'gc> { &self.system_prototypes } + /// Run a script's initializer method. + pub fn run_script_initializer( + &mut self, + script: GcCell<'gc, Script<'gc>>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result<(), Error> { + let mut init_activation = Activation::from_script(self, context, script, self.globals)?; + + init_activation.run_stack_frame_for_script(context, script) + } + /// Load an ABC file embedded in a `SwfSlice`. /// /// The `SwfSlice` must resolve to the contents of an ABC file. @@ -106,21 +102,25 @@ impl<'gc> Avm2<'gc> { 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); + let mut globals = self.globals(); + let scope = Scope::push_scope(None, globals, context.gc_context); + let mut null_activation = Activation::from_nothing(self, context); // TODO: Lazyinit means we shouldn't do this until traits are // actually mentioned... for trait_entry in script.read().traits()?.iter() { - self.globals().install_foreign_trait( - self, + globals.install_foreign_trait( + &mut null_activation, context, trait_entry.clone(), Some(scope), - self.globals(), + globals, )?; } - self.insert_stack_frame_for_script(context, script)?; + drop(null_activation); + + self.run_script_initializer(script, context)?; } Ok(()) @@ -130,191 +130,6 @@ impl<'gc> Avm2<'gc> { self.globals } - /// Get the current stack frame (`Activation` object). - pub fn current_stack_frame(&self) -> Option>> { - 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( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - func: F, - ) -> Result - where - F: FnOnce( - &mut Self, - &mut Reader>, - &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, - { - 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()?; - - let method = frame_ref.method(); - let abc = method.abc(); - let _method_index = method.abc_method; - 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>) { let value = value.into(); @@ -344,1191 +159,4 @@ impl<'gc> Avm2<'gc> { args } - - fn register_value(&self, index: u32) -> Result, 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>, - ) -> 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> { - self.current_stack_frame() - .map(|sf| sf.read().method().abc()) - } - - fn current_translation_unit(&self) -> Option> { - 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) -> Result { - value::abc_int(&self.current_abc().unwrap(), index) - } - - /// Retrieve a int from the current constant pool. - fn pool_uint(&self, index: Index) -> Result { - value::abc_uint(&self.current_abc().unwrap(), index) - } - - /// Retrieve a double from the current constant pool. - fn pool_double(&self, index: Index) -> Result { - value::abc_double(&self.current_abc().unwrap(), index) - } - - /// Retrieve a string from the current constant pool. - fn pool_string(&self, index: Index) -> Result { - value::abc_string(&self.current_abc().unwrap(), index) - } - - /// Retrieve a namespace from the current constant pool. - fn pool_namespace(&self, index: Index) -> Result { - Namespace::from_abc_namespace(&self.current_abc().unwrap(), index) - } - - /// Retrieve a multiname from the current constant pool. - fn pool_multiname(&mut self, index: Index) -> Result { - 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) -> Result { - 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) -> Result, Error> { - BytecodeMethod::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, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result>, 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>, - ) -> 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(), - 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), - Op::GetProperty { index } => self.op_get_property(context, index), - Op::SetProperty { index } => self.op_set_property(context, index), - Op::InitProperty { index } => self.op_init_property(context, index), - 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), - Op::GetScopeObject { index } => self.op_get_scope_object(index), - 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), - 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), - 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), - 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), - Op::StrictEquals => self.op_strict_equals(), - 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) -> 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) -> Result<(), Error> { - self.push(self.pool_int(value)?); - Ok(()) - } - - fn op_push_namespace(&mut self, value: Index) -> 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) -> 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) -> 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(()) - } - - 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, - arg_count: u32, - ) -> Result<(), Error> { - let args = self.pop_args(arg_count); - let receiver = self.pop().as_object()?; - let function: Result, 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, - 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 = receiver - .resolve_multiname(&multiname)? - .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); - let name = name?; - let base_proto = receiver.get_base_proto(&name)?; - let function = receiver - .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, - 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 = receiver - .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, - 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 = receiver - .resolve_multiname(&multiname)? - .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); - let name = name?; - let base_proto = receiver.get_base_proto(&name)?; - let function = receiver - .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, - 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, - 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 = receiver - .resolve_multiname(&multiname)? - .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); - let base_proto: Result, 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?; - let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround - - 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, - 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 = receiver - .resolve_multiname(&multiname)? - .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); - let base_proto: Result, 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?; - let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround - - 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) - } - - fn op_get_property( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - index: Index, - ) -> Result<(), Error> { - let multiname = self.pool_multiname(index)?; - let mut object = self.pop().as_object()?; - - let name: Result = object.resolve_multiname(&multiname)?.ok_or_else(|| { - format!("Could not resolve property {:?}", multiname.local_name()).into() - }); - - let value = object.get_property(object, &name?, self, context)?; - self.push(value); - - Ok(()) - } - - fn op_set_property( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - index: Index, - ) -> Result<(), Error> { - let value = self.pop(); - let multiname = self.pool_multiname(index)?; - let mut object = self.pop().as_object()?; - - if let Some(name) = object.resolve_multiname(&multiname)? { - object.set_property(object, &name, value, self, context) - } 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) - } - } - - fn op_init_property( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - index: Index, - ) -> Result<(), Error> { - let value = self.pop(); - let multiname = self.pool_multiname(index)?; - let mut object = self.pop().as_object()?; - - if let Some(name) = object.resolve_multiname(&multiname)? { - object.init_property(object, &name, value, self, context) - } 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) - } - } - - fn op_delete_property( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - index: Index, - ) -> Result<(), Error> { - let multiname = self.pool_multiname(index)?; - let object = self.pop().as_object()?; - - if let Some(name) = object.resolve_multiname(&multiname)? { - 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, - ) -> Result<(), Error> { - let multiname = self.pool_multiname(index)?; - let object = self.pop().as_object()?; - let base_proto: Result, 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?; - let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround - - let name: Result = 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, - ) -> Result<(), Error> { - let value = self.pop(); - let multiname = self.pool_multiname(index)?; - let object = self.pop().as_object()?; - let base_proto: Result, 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?; - let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround - - let name: Result = base.resolve_multiname(&multiname)?.ok_or_else(|| { - format!( - "Could not resolve {:?} as super property", - multiname.local_name() - ) - .into() - }); - - 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(()) - } - - 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(()) - } - - 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, - ) -> Result<(), Error> { - let multiname = self.pool_multiname(index)?; - avm_debug!("Resolving {:?}", multiname); - 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, - ) -> Result<(), Error> { - let multiname = self.pool_multiname(index)?; - avm_debug!("Resolving {:?}", multiname); - let found: Result, 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, - ) -> Result<(), Error> { - let multiname = self.pool_multiname_static(index)?; - avm_debug!("Resolving {:?}", multiname); - let found: Result, 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()); - let result: Value<'gc> = found?.resolve(self, context)?; - - self.push(result); - - Ok(()) - } - - 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) - } - - fn op_construct( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - arg_count: u32, - ) -> Result<(), Error> { - let args = self.pop_args(arg_count); - let mut ctor = self.pop().as_object()?; - - let proto = ctor - .get_property( - ctor, - &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())?; - - self.push(object); - - Ok(()) - } - - fn op_construct_prop( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - index: Index, - arg_count: u32, - ) -> Result<(), Error> { - let args = self.pop_args(arg_count); - let multiname = self.pool_multiname(index)?; - let mut source = self.pop().as_object()?; - - let ctor_name: Result = - source.resolve_multiname(&multiname)?.ok_or_else(|| { - format!("Could not resolve property {:?}", multiname.local_name()).into() - }); - let mut ctor = source - .get_property(source, &ctor_name?, self, context)? - .as_object()?; - let proto = ctor - .get_property( - ctor, - &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))?; - - self.push(object); - - 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, 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() - }); - let mut base_proto = base_proto?; - - let function = base_proto - .get_property(receiver, &name, self, context)? - .as_object()?; - - function.call(Some(receiver), &args, self, context, Some(base_proto))?; - - Ok(()) - } - - 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> { - let mut object = ScriptObject::object(context.gc_context, self.system_prototypes.object); - - for _ in 0..num_args { - let value = self.pop(); - let name = self.pop(); - - object.set_property( - object, - &QName::new(Namespace::public_namespace(), name.as_string()?), - value, - self, - context, - )?; - } - - self.push(object); - - Ok(()) - } - - fn op_new_function( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - index: Index, - ) -> 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, - ) -> 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(); - - 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>) -> Result<(), Error> { - reader.seek(offset as i64)?; - - Ok(()) - } - - fn op_if_true(&mut self, offset: i32, reader: &mut Reader>) -> 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>, - ) -> 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>, - ) -> 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>, - ) -> Result<(), Error> { - let value2 = self.pop(); - let value1 = self.pop(); - - if value1 != value2 { - reader.seek(offset as i64)?; - } - - Ok(()) - } - - fn op_strict_equals(&mut self) -> Result<(), Error> { - let value2 = self.pop(); - let value1 = self.pop(); - - self.push(value1 == value2); - - Ok(()) - } - - 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)? - } else { - Value::Undefined - }; - - self.push(value); - - Ok(()) - } - - #[allow(unused_variables)] - fn op_debug( - &mut self, - is_local_register: bool, - register_name: Index, - 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) -> 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(()) - } } diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 4cf6016c91eb..d6a362edc447 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -1,15 +1,24 @@ //! Activation frames +use crate::avm2::class::Class; +use crate::avm2::function::FunctionObject; use crate::avm2::method::BytecodeMethod; -use crate::avm2::object::Object; +use crate::avm2::names::{Multiname, Namespace, QName}; +use crate::avm2::object::{Object, TObject}; use crate::avm2::scope::Scope; use crate::avm2::script::Script; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::Error; +use crate::avm2::{value, Avm2, Error}; use crate::context::UpdateContext; use gc_arena::{Collect, GcCell, MutationContext}; use smallvec::SmallVec; +use std::io::Cursor; +use swf::avm2::read::Reader; +use swf::avm2::types::{ + Class as AbcClass, Index, Method as AbcMethod, Multiname as AbcMultiname, + Namespace as AbcNamespace, Op, +}; /// Represents a particular register set. /// @@ -45,15 +54,18 @@ impl<'gc> RegisterSet<'gc> { } } +#[derive(Debug, Clone)] +enum FrameControl<'gc> { + Continue, + Return(Value<'gc>), +} + /// Represents a single activation of a given AVM2 function or keyframe. -#[derive(Clone, Collect)] +#[derive(Collect)] #[collect(no_drop)] -pub struct Activation<'gc> { - /// The AVM method entry we're executing code out of. - method: BytecodeMethod<'gc>, - - /// The current location of the instruction stream being executed. - pc: usize, +pub struct Activation<'a, 'gc: 'a> { + /// The AVM2 instance we execute under. + avm2: &'a mut Avm2<'gc>, /// The immutable value of `this`. this: Option>, @@ -92,8 +104,35 @@ pub struct Activation<'gc> { base_proto: Option>, } -impl<'gc> Activation<'gc> { +impl<'a, 'gc: 'a> Activation<'a, 'gc> { + /// Construct an activation that does not represent any particular scope. + /// + /// This exists primarily for non-AVM2 related manipulations of the + /// interpreter environment that require an activation. For example, + /// loading traits into an object, or running tests. + /// + /// It is a logic error to attempt to run AVM2 code in a nothing + /// `Activation`. + pub fn from_nothing(avm2: &'a mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) -> Self { + let local_registers = GcCell::allocate(context.gc_context, RegisterSet::new(0)); + + Self { + avm2, + this: None, + arguments: None, + is_executing: false, + local_registers, + return_value: None, + local_scope: ScriptObject::bare_object(context.gc_context), + scope: None, + base_proto: None, + } + } + + /// Construct an activation for the execution of a particular script's + /// initializer method. pub fn from_script( + avm2: &'a mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, script: GcCell<'gc, Script<'gc>>, global: Object<'gc>, @@ -110,8 +149,7 @@ impl<'gc> Activation<'gc> { .unwrap() = global.into(); Ok(Self { - method, - pc: 0, + avm2, this: Some(global), arguments: None, is_executing: false, @@ -123,14 +161,17 @@ impl<'gc> Activation<'gc> { }) } - pub fn from_action( + /// Construct an activation for the execution of a particular bytecode + /// method. + pub fn from_method( + avm2: &'a mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, method: BytecodeMethod<'gc>, scope: Option>>, this: Option>, arguments: &[Value<'gc>], base_proto: Option>, - ) -> Result { + ) -> Self { let num_locals = method.body().num_locals; let num_declared_arguments = method.method().params.len() as u32; let local_registers = GcCell::allocate( @@ -150,9 +191,8 @@ impl<'gc> Activation<'gc> { } } - Ok(Self { - method, - pc: 0, + Self { + avm2, this, arguments: None, is_executing: false, @@ -161,7 +201,20 @@ impl<'gc> Activation<'gc> { local_scope: ScriptObject::bare_object(context.gc_context), scope, base_proto, - }) + } + } + + /// Execute a script initializer. + pub fn run_stack_frame_for_script( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + script: GcCell<'gc, Script<'gc>>, + ) -> Result<(), Error> { + let init = script.read().init().into_entry()?; + + self.run_actions(init, context)?; + + Ok(()) } /// Attempts to lock the activation frame for execution. @@ -183,24 +236,13 @@ impl<'gc> Activation<'gc> { self.is_executing = false; } - /// Obtain a reference to the method being executed. - pub fn method(&self) -> &BytecodeMethod<'gc> { - &self.method - } - - /// Get the PC value. - pub fn pc(&self) -> usize { - self.pc - } - - /// Set the PC value. - pub fn set_pc(&mut self, new_pc: usize) { - self.pc = new_pc; - } - /// Retrieve a local register. - pub fn local_register(&self, id: u32) -> Option> { - self.local_registers.read().get(id).cloned() + pub fn local_register(&self, id: u32) -> Result, Error> { + self.local_registers + .read() + .get(id) + .cloned() + .ok_or_else(|| format!("Out of bounds register read: {}", id).into()) } /// Get the current scope stack. @@ -221,16 +263,20 @@ impl<'gc> Activation<'gc> { id: u32, value: impl Into>, mc: MutationContext<'gc, '_>, - ) -> bool { + ) -> Result<(), Error> { if let Some(r) = self.local_registers.write(mc).get_mut(id) { *r = value.into(); - true + Ok(()) } else { - false + Err(format!("Out of bounds register write: {}", id).into()) } } + pub fn avm2(&mut self) -> &mut Avm2<'gc> { + self.avm2 + } + /// Set the return value. pub fn set_return_value(&mut self, value: Value<'gc>) { self.return_value = Some(value); @@ -241,4 +287,1254 @@ impl<'gc> Activation<'gc> { pub fn base_proto(&self) -> Option> { self.base_proto } + + /// Retrieve a int from the current constant pool. + fn pool_int(&self, method: BytecodeMethod<'gc>, index: Index) -> Result { + value::abc_int(&method.abc(), index) + } + + /// Retrieve a int from the current constant pool. + fn pool_uint(&self, method: BytecodeMethod<'gc>, index: Index) -> Result { + value::abc_uint(&method.abc(), index) + } + + /// Retrieve a double from the current constant pool. + fn pool_double(&self, method: BytecodeMethod<'gc>, index: Index) -> Result { + value::abc_double(&method.abc(), index) + } + + /// Retrieve a string from the current constant pool. + fn pool_string( + &self, + method: BytecodeMethod<'gc>, + index: Index, + ) -> Result { + value::abc_string(&method.abc(), index) + } + + /// Retrieve a namespace from the current constant pool. + fn pool_namespace( + &self, + method: BytecodeMethod<'gc>, + index: Index, + ) -> Result { + Namespace::from_abc_namespace(&method.abc(), index) + } + + /// Retrieve a multiname from the current constant pool. + fn pool_multiname( + &mut self, + method: BytecodeMethod<'gc>, + index: Index, + ) -> Result { + Multiname::from_abc_multiname(&method.abc(), index, self.avm2) + } + + /// Retrieve a static, or non-runtime, multiname from the current constant + /// pool. + fn pool_multiname_static( + &mut self, + method: BytecodeMethod<'gc>, + index: Index, + ) -> Result { + Multiname::from_abc_multiname_static(&method.abc(), index) + } + + /// Retrieve a method entry from the current ABC file's method table. + fn table_method( + &mut self, + method: BytecodeMethod<'gc>, + index: Index, + ) -> Result, Error> { + BytecodeMethod::from_method_index(method.translation_unit(), 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, + method: BytecodeMethod<'gc>, + index: Index, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result>, Error> { + method + .translation_unit() + .load_class(index.0, context.gc_context) + } + + pub fn run_actions( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + let clone_method = method.clone(); + let mut read = Reader::new(Cursor::new(clone_method.body().code.as_ref())); + + loop { + let result = self.do_next_opcode(method.clone(), context, &mut read); + match result { + Ok(FrameControl::Return(value)) => break Ok(value), + Ok(FrameControl::Continue) => {} + Err(e) => break Err(e), + } + } + } + + /// Run a single action from a given action reader. + fn do_next_opcode( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + reader: &mut Reader>, + ) -> 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(method, value), + Op::PushFalse => self.op_push_false(), + Op::PushInt { value } => self.op_push_int(method, value), + Op::PushNamespace { value } => self.op_push_namespace(method, 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(method, value), + Op::PushTrue => self.op_push_true(), + Op::PushUint { value } => self.op_push_uint(method, value), + Op::PushUndefined => self.op_push_undefined(), + Op::Pop => self.op_pop(), + 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(method, context, index, num_args) + } + Op::CallPropLex { index, num_args } => { + self.op_call_prop_lex(method, context, index, num_args) + } + Op::CallPropVoid { index, num_args } => { + self.op_call_prop_void(method, context, index, num_args) + } + Op::CallStatic { index, num_args } => { + self.op_call_static(method, context, index, num_args) + } + Op::CallSuper { index, num_args } => { + self.op_call_super(method, context, index, num_args) + } + Op::CallSuperVoid { index, num_args } => { + self.op_call_super_void(method, context, index, num_args) + } + Op::ReturnValue => self.op_return_value(), + Op::ReturnVoid => self.op_return_void(), + Op::GetProperty { index } => self.op_get_property(method, context, index), + Op::SetProperty { index } => self.op_set_property(method, context, index), + Op::InitProperty { index } => self.op_init_property(method, context, index), + Op::DeleteProperty { index } => self.op_delete_property(method, context, index), + Op::GetSuper { index } => self.op_get_super(method, context, index), + Op::SetSuper { index } => self.op_set_super(method, context, index), + Op::PushScope => self.op_push_scope(context), + Op::PushWith => self.op_push_with(context), + Op::PopScope => self.op_pop_scope(), + Op::GetScopeObject { index } => self.op_get_scope_object(index), + Op::GetGlobalScope => self.op_get_global_scope(), + Op::FindProperty { index } => self.op_find_property(method, context, index), + Op::FindPropStrict { index } => self.op_find_prop_strict(method, context, index), + Op::GetLex { index } => self.op_get_lex(method, context, index), + 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), + Op::Construct { num_args } => self.op_construct(context, num_args), + Op::ConstructProp { index, num_args } => { + self.op_construct_prop(method, context, index, num_args) + } + Op::ConstructSuper { num_args } => self.op_construct_super(context, num_args), + 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(method, context, index), + Op::NewClass { index } => self.op_new_class(method, 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), + Op::StrictEquals => self.op_strict_equals(), + 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(FrameControl::Continue), + Op::Debug { + is_local_register, + register_name, + register, + } => self.op_debug(method, is_local_register, register_name, register), + Op::DebugFile { file_name } => self.op_debug_file(method, file_name), + Op::DebugLine { line_num } => self.op_debug_line(line_num), + _ => self.unknown_op(op), + }; + + if let Err(e) = result { + log::error!("AVM2 error: {}", e); + return Err(e); + } + result + } else if let Ok(None) = op { + log::error!("Unknown opcode!"); + Err("Unknown opcode!".into()) + } else if let Err(e) = op { + log::error!("Parse error: {:?}", e); + Err(e.into()) + } else { + unreachable!(); + } + } + + 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.avm2.push(value); + Ok(FrameControl::Continue) + } + + fn op_push_double( + &mut self, + method: BytecodeMethod<'gc>, + value: Index, + ) -> Result, Error> { + self.avm2.push(self.pool_double(method, value)?); + Ok(FrameControl::Continue) + } + + fn op_push_false(&mut self) -> Result, Error> { + self.avm2.push(false); + Ok(FrameControl::Continue) + } + + fn op_push_int( + &mut self, + method: BytecodeMethod<'gc>, + value: Index, + ) -> Result, Error> { + self.avm2.push(self.pool_int(method, value)?); + Ok(FrameControl::Continue) + } + + fn op_push_namespace( + &mut self, + method: BytecodeMethod<'gc>, + value: Index, + ) -> Result, Error> { + self.avm2.push(self.pool_namespace(method, value)?); + Ok(FrameControl::Continue) + } + + fn op_push_nan(&mut self) -> Result, Error> { + self.avm2.push(std::f64::NAN); + Ok(FrameControl::Continue) + } + + fn op_push_null(&mut self) -> Result, Error> { + self.avm2.push(Value::Null); + Ok(FrameControl::Continue) + } + + fn op_push_short(&mut self, value: u32) -> Result, Error> { + self.avm2.push(value); + Ok(FrameControl::Continue) + } + + fn op_push_string( + &mut self, + method: BytecodeMethod<'gc>, + value: Index, + ) -> Result, Error> { + self.avm2.push(self.pool_string(method, value)?); + Ok(FrameControl::Continue) + } + + fn op_push_true(&mut self) -> Result, Error> { + self.avm2.push(true); + Ok(FrameControl::Continue) + } + + fn op_push_uint( + &mut self, + method: BytecodeMethod<'gc>, + value: Index, + ) -> Result, Error> { + self.avm2.push(self.pool_uint(method, value)?); + Ok(FrameControl::Continue) + } + + fn op_push_undefined(&mut self) -> Result, Error> { + self.avm2.push(Value::Undefined); + Ok(FrameControl::Continue) + } + + fn op_pop(&mut self) -> Result, Error> { + self.avm2.pop(); + + Ok(FrameControl::Continue) + } + + fn op_dup(&mut self) -> Result, Error> { + self.avm2 + .push(self.avm2.stack.last().cloned().unwrap_or(Value::Undefined)); + + Ok(FrameControl::Continue) + } + + fn op_get_local(&mut self, register_index: u32) -> Result, Error> { + self.avm2.push(self.local_register(register_index)?); + Ok(FrameControl::Continue) + } + + fn op_set_local( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + register_index: u32, + ) -> Result, Error> { + let value = self.avm2.pop(); + + self.set_local_register(register_index, value, context.gc_context)?; + + Ok(FrameControl::Continue) + } + + fn op_kill( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + register_index: u32, + ) -> Result, Error> { + self.set_local_register(register_index, Value::Undefined, context.gc_context)?; + + Ok(FrameControl::Continue) + } + + fn op_call( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + arg_count: u32, + ) -> Result, Error> { + let args = self.avm2.pop_args(arg_count); + let receiver = self.avm2.pop().as_object().ok(); + let function = self.avm2.pop().as_object()?; + let base_proto = receiver.and_then(|r| r.proto()); + let value = function.call(receiver, &args, self, context, base_proto)?; + + self.avm2.push(value); + + Ok(FrameControl::Continue) + } + + fn op_call_method( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result, Error> { + let args = self.avm2.pop_args(arg_count); + let receiver = self.avm2.pop().as_object()?; + let function: Result, 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.avm2.push(value); + + Ok(FrameControl::Continue) + } + + fn op_call_property( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result, Error> { + let args = self.avm2.pop_args(arg_count); + let multiname = self.pool_multiname(method, index)?; + let mut receiver = self.avm2.pop().as_object()?; + let name: Result = receiver + .resolve_multiname(&multiname)? + .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); + let name = name?; + let base_proto = receiver.get_base_proto(&name)?; + let function = receiver + .get_property(receiver, &name, self, context)? + .as_object()?; + let value = function.call(Some(receiver), &args, self, context, base_proto)?; + + self.avm2.push(value); + + Ok(FrameControl::Continue) + } + + fn op_call_prop_lex( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result, Error> { + let args = self.avm2.pop_args(arg_count); + let multiname = self.pool_multiname(method, index)?; + let mut receiver = self.avm2.pop().as_object()?; + let name: Result = receiver + .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.avm2.push(value); + + Ok(FrameControl::Continue) + } + + fn op_call_prop_void( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result, Error> { + let args = self.avm2.pop_args(arg_count); + let multiname = self.pool_multiname(method, index)?; + let mut receiver = self.avm2.pop().as_object()?; + let name: Result = receiver + .resolve_multiname(&multiname)? + .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); + let name = name?; + let base_proto = receiver.get_base_proto(&name)?; + let function = receiver + .get_property(receiver, &name, self, context)? + .as_object()?; + + function.call(Some(receiver), &args, self, context, base_proto)?; + + Ok(FrameControl::Continue) + } + + fn op_call_static( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result, Error> { + let args = self.avm2.pop_args(arg_count); + let receiver = self.avm2.pop().as_object()?; + let method = self.table_method(method, index)?; + let scope = self.scope(); //TODO: Is this correct? + let function = FunctionObject::from_method( + context.gc_context, + method.into(), + scope, + self.avm2.prototypes().function, + None, + ); + let value = function.call(Some(receiver), &args, self, context, receiver.proto())?; + + self.avm2.push(value); + + Ok(FrameControl::Continue) + } + + fn op_call_super( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result, Error> { + let args = self.avm2.pop_args(arg_count); + let multiname = self.pool_multiname(method, index)?; + let receiver = self.avm2.pop().as_object()?; + let name: Result = receiver + .resolve_multiname(&multiname)? + .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); + let base_proto: Result, Error> = + self.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?; + let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround + + let function = base + .get_property(receiver, &name?, self, context)? + .as_object()?; + + let value = function.call(Some(receiver), &args, self, context, Some(base_proto))?; + + self.avm2.push(value); + + Ok(FrameControl::Continue) + } + + fn op_call_super_void( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result, Error> { + let args = self.avm2.pop_args(arg_count); + let multiname = self.pool_multiname(method, index)?; + let receiver = self.avm2.pop().as_object()?; + let name: Result = receiver + .resolve_multiname(&multiname)? + .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); + let base_proto: Result, Error> = + self.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?; + let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround + + let function = base + .get_property(receiver, &name?, self, context)? + .as_object()?; + + function.call(Some(receiver), &args, self, context, Some(base_proto))?; + + Ok(FrameControl::Continue) + } + + fn op_return_value(&mut self) -> Result, Error> { + let return_value = self.avm2.pop(); + + Ok(FrameControl::Return(return_value)) + } + + fn op_return_void(&mut self) -> Result, Error> { + Ok(FrameControl::Return(Value::Undefined)) + } + + fn op_get_property( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result, Error> { + let multiname = self.pool_multiname(method, index)?; + let mut object = self.avm2.pop().as_object()?; + + let name: Result = object.resolve_multiname(&multiname)?.ok_or_else(|| { + format!("Could not resolve property {:?}", multiname.local_name()).into() + }); + + let value = object.get_property(object, &name?, self, context)?; + self.avm2.push(value); + + Ok(FrameControl::Continue) + } + + fn op_set_property( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result, Error> { + let value = self.avm2.pop(); + let multiname = self.pool_multiname(method, index)?; + let mut object = self.avm2.pop().as_object()?; + + if let Some(name) = object.resolve_multiname(&multiname)? { + object.set_property(object, &name, value, self, context)?; + } 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)?; + } + + Ok(FrameControl::Continue) + } + + fn op_init_property( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result, Error> { + let value = self.avm2.pop(); + let multiname = self.pool_multiname(method, index)?; + let mut object = self.avm2.pop().as_object()?; + + if let Some(name) = object.resolve_multiname(&multiname)? { + object.init_property(object, &name, value, self, context)?; + } 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)?; + } + + Ok(FrameControl::Continue) + } + + fn op_delete_property( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result, Error> { + let multiname = self.pool_multiname(method, index)?; + let object = self.avm2.pop().as_object()?; + + if let Some(name) = object.resolve_multiname(&multiname)? { + self.avm2 + .push(object.delete_property(context.gc_context, &name)) + } else { + self.avm2.push(false) + } + + Ok(FrameControl::Continue) + } + + fn op_get_super( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result, Error> { + let multiname = self.pool_multiname(method, index)?; + let object = self.avm2.pop().as_object()?; + let base_proto: Result, Error> = self + .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?; + let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround + + let name: Result = 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.avm2.push(value); + + Ok(FrameControl::Continue) + } + + fn op_set_super( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result, Error> { + let value = self.avm2.pop(); + let multiname = self.pool_multiname(method, index)?; + let object = self.avm2.pop().as_object()?; + let base_proto: Result, Error> = self + .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?; + let mut base = base_proto.construct(self, context, &[])?; //TODO: very hacky workaround + + let name: Result = base.resolve_multiname(&multiname)?.ok_or_else(|| { + format!( + "Could not resolve {:?} as super property", + multiname.local_name() + ) + .into() + }); + + base.set_property(object, &name?, value, self, context)?; + + Ok(FrameControl::Continue) + } + + fn op_push_scope( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + let object = self.avm2.pop().as_object()?; + let scope_stack = self.scope(); + let new_scope = Scope::push_scope(scope_stack, object, context.gc_context); + + self.set_scope(Some(new_scope)); + + Ok(FrameControl::Continue) + } + + fn op_push_with( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + let object = self.avm2.pop().as_object()?; + let scope_stack = self.scope(); + let new_scope = Scope::push_with(scope_stack, object, context.gc_context); + + self.set_scope(Some(new_scope)); + + Ok(FrameControl::Continue) + } + + fn op_pop_scope(&mut self) -> Result, Error> { + let scope_stack = self.scope(); + let new_scope = scope_stack.and_then(|s| s.read().pop_scope()); + + self.set_scope(new_scope); + + Ok(FrameControl::Continue) + } + + fn op_get_scope_object(&mut self, mut index: u8) -> Result, Error> { + let mut scope = self.scope(); + + while index > 0 { + if let Some(child_scope) = scope { + scope = child_scope.read().parent_cell(); + } + + index -= 1; + } + + self.avm2.push( + scope + .map(|s| s.read().locals().clone().into()) + .unwrap_or(Value::Undefined), + ); + + Ok(FrameControl::Continue) + } + + fn op_get_global_scope(&mut self) -> Result, Error> { + let mut scope = self.scope(); + + while let Some(this_scope) = scope { + let parent = this_scope.read().parent_cell(); + if parent.is_none() { + break; + } + + scope = parent; + } + + self.avm2.push( + scope + .map(|s| s.read().locals().clone().into()) + .unwrap_or(Value::Undefined), + ); + + Ok(FrameControl::Continue) + } + + fn op_find_property( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result, Error> { + let multiname = self.pool_multiname(method, index)?; + avm_debug!("Resolving {:?}", multiname); + let result = if let Some(scope) = self.scope() { + scope.read().find(&multiname, self, context)? + } else { + None + }; + + self.avm2 + .push(result.map(|o| o.into()).unwrap_or(Value::Undefined)); + + Ok(FrameControl::Continue) + } + + fn op_find_prop_strict( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result, Error> { + let multiname = self.pool_multiname(method, index)?; + avm_debug!("Resolving {:?}", multiname); + let found: Result, Error> = if let Some(scope) = self.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.avm2.push(result); + + Ok(FrameControl::Continue) + } + + fn op_get_lex( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result, Error> { + let multiname = self.pool_multiname_static(method, index)?; + avm_debug!("Resolving {:?}", multiname); + let found: Result, Error> = if let Some(scope) = self.scope() { + scope + .write(context.gc_context) + .resolve(&multiname, self, context)? + } else { + None + } + .ok_or_else(|| format!("Property does not exist: {:?}", multiname.local_name()).into()); + let result: Value<'gc> = found?; + + self.avm2.push(result); + + Ok(FrameControl::Continue) + } + + fn op_get_slot(&mut self, index: u32) -> Result, Error> { + let object = self.avm2.pop().as_object()?; + let value = object.get_slot(index)?; + + self.avm2.push(value); + + Ok(FrameControl::Continue) + } + + fn op_set_slot( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: u32, + ) -> Result, Error> { + let object = self.avm2.pop().as_object()?; + let value = self.avm2.pop(); + + object.set_slot(index, value, context.gc_context)?; + + Ok(FrameControl::Continue) + } + + fn op_get_global_slot(&mut self, index: u32) -> Result, Error> { + let value = self.avm2.globals().get_slot(index)?; + + self.avm2.push(value); + + Ok(FrameControl::Continue) + } + + fn op_set_global_slot( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: u32, + ) -> Result, Error> { + let value = self.avm2.pop(); + + self.avm2 + .globals() + .set_slot(index, value, context.gc_context)?; + + Ok(FrameControl::Continue) + } + + fn op_construct( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + arg_count: u32, + ) -> Result, Error> { + let args = self.avm2.pop_args(arg_count); + let mut ctor = self.avm2.pop().as_object()?; + + let proto = ctor + .get_property( + ctor, + &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())?; + + self.avm2.push(object); + + Ok(FrameControl::Continue) + } + + fn op_construct_prop( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result, Error> { + let args = self.avm2.pop_args(arg_count); + let multiname = self.pool_multiname(method, index)?; + let mut source = self.avm2.pop().as_object()?; + + let ctor_name: Result = + source.resolve_multiname(&multiname)?.ok_or_else(|| { + format!("Could not resolve property {:?}", multiname.local_name()).into() + }); + let mut ctor = source + .get_property(source, &ctor_name?, self, context)? + .as_object()?; + let proto = ctor + .get_property( + ctor, + &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))?; + + self.avm2.push(object); + + Ok(FrameControl::Continue) + } + + fn op_construct_super( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + arg_count: u32, + ) -> Result, Error> { + let args = self.avm2.pop_args(arg_count); + let receiver = self.avm2.pop().as_object()?; + let name = QName::new(Namespace::public_namespace(), "constructor"); + let base_proto: Result, Error> = + self.base_proto().and_then(|p| p.proto()).ok_or_else(|| { + "Attempted to call super constructor without a superclass." + .to_string() + .into() + }); + let mut base_proto = base_proto?; + + let function = base_proto + .get_property(receiver, &name, self, context)? + .as_object()?; + + function.call(Some(receiver), &args, self, context, Some(base_proto))?; + + Ok(FrameControl::Continue) + } + + fn op_new_activation( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + self.avm2 + .push(ScriptObject::bare_object(context.gc_context)); + + Ok(FrameControl::Continue) + } + + fn op_new_object( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + num_args: u32, + ) -> Result, Error> { + let mut object = ScriptObject::object(context.gc_context, self.avm2.prototypes().object); + + for _ in 0..num_args { + let value = self.avm2.pop(); + let name = self.avm2.pop(); + + object.set_property( + object, + &QName::new(Namespace::public_namespace(), name.as_string()?), + value, + self, + context, + )?; + } + + self.avm2.push(object); + + Ok(FrameControl::Continue) + } + + fn op_new_function( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result, Error> { + let method_entry = self.table_method(method, index)?; + let scope = self.scope(); + + let mut new_fn = FunctionObject::from_method( + context.gc_context, + method_entry.into(), + scope, + self.avm2.prototypes().function, + None, + ); + let es3_proto = ScriptObject::object(context.gc_context, self.avm2.prototypes().object); + + new_fn.install_slot( + context.gc_context, + QName::new(Namespace::public_namespace(), "prototype"), + 0, + es3_proto.into(), + ); + + self.avm2.push(new_fn); + + Ok(FrameControl::Continue) + } + + fn op_new_class( + &mut self, + method: BytecodeMethod<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + ) -> Result, Error> { + let base_class = self.avm2.pop().as_object()?; + let class_entry = self.table_class(method, index, context)?; + let scope = self.scope(); + + 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.avm2.push(new_class); + + Ok(FrameControl::Continue) + } + + fn op_coerce_a(&mut self) -> Result, Error> { + Ok(FrameControl::Continue) + } + + fn op_jump( + &mut self, + offset: i32, + reader: &mut Reader>, + ) -> Result, Error> { + reader.seek(offset as i64)?; + + Ok(FrameControl::Continue) + } + + fn op_if_true( + &mut self, + offset: i32, + reader: &mut Reader>, + ) -> Result, Error> { + let value = self.avm2.pop().as_bool()?; + + if value { + reader.seek(offset as i64)?; + } + + Ok(FrameControl::Continue) + } + + fn op_if_false( + &mut self, + offset: i32, + reader: &mut Reader>, + ) -> Result, Error> { + let value = self.avm2.pop().as_bool()?; + + if !value { + reader.seek(offset as i64)?; + } + + Ok(FrameControl::Continue) + } + + fn op_if_strict_eq( + &mut self, + offset: i32, + reader: &mut Reader>, + ) -> Result, Error> { + let value2 = self.avm2.pop(); + let value1 = self.avm2.pop(); + + if value1 == value2 { + reader.seek(offset as i64)?; + } + + Ok(FrameControl::Continue) + } + + fn op_if_strict_ne( + &mut self, + offset: i32, + reader: &mut Reader>, + ) -> Result, Error> { + let value2 = self.avm2.pop(); + let value1 = self.avm2.pop(); + + if value1 != value2 { + reader.seek(offset as i64)?; + } + + Ok(FrameControl::Continue) + } + + fn op_strict_equals(&mut self) -> Result, Error> { + let value2 = self.avm2.pop(); + let value1 = self.avm2.pop(); + + self.avm2.push(value1 == value2); + + Ok(FrameControl::Continue) + } + + fn op_has_next(&mut self) -> Result, Error> { + //TODO: After adding ints, change this to ints. + let cur_index = self.avm2.pop().as_number()?; + let object = self.avm2.pop().as_object()?; + + let next_index = cur_index as u32 + 1; + + if object.get_enumerant_name(next_index).is_some() { + self.avm2.push(next_index as f32); + } else { + self.avm2.push(0.0); + } + + Ok(FrameControl::Continue) + } + + 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.local_register(index_register)?.as_number()?; + let mut object = Some(self.local_register(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.avm2.push(next_index != 0); + self.set_local_register(index_register, next_index, context.gc_context)?; + self.set_local_register( + object_register, + object.map(|v| v.into()).unwrap_or(Value::Null), + context.gc_context, + )?; + + Ok(FrameControl::Continue) + } + + fn op_next_name(&mut self) -> Result, Error> { + //TODO: After adding ints, change this to ints. + let cur_index = self.avm2.pop().as_number()?; + let object = self.avm2.pop().as_object()?; + + let name = object + .get_enumerant_name(cur_index as u32) + .map(|n| n.local_name().into()); + + self.avm2.push(name.unwrap_or(Value::Undefined)); + + Ok(FrameControl::Continue) + } + + fn op_next_value( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + //TODO: After adding ints, change this to ints. + let cur_index = self.avm2.pop().as_number()?; + let mut object = self.avm2.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)? + } else { + Value::Undefined + }; + + self.avm2.push(value); + + Ok(FrameControl::Continue) + } + + #[allow(unused_variables)] + fn op_debug( + &mut self, + method: BytecodeMethod<'gc>, + is_local_register: bool, + register_name: Index, + register: u8, + ) -> Result, Error> { + if is_local_register { + let register_name = self.pool_string(method, register_name)?; + let value = self.local_register(register as u32)?; + + avm_debug!("Debug: {} = {:?}", register_name, value); + } else { + avm_debug!("Unknown debugging mode!"); + } + + Ok(FrameControl::Continue) + } + + #[allow(unused_variables)] + fn op_debug_file( + &mut self, + method: BytecodeMethod<'gc>, + file_name: Index, + ) -> Result, Error> { + let file_name = self.pool_string(method, file_name)?; + + avm_debug!("File: {}", file_name); + + Ok(FrameControl::Continue) + } + + #[allow(unused_variables)] + fn op_debug_line(&mut self, line_num: u32) -> Result, Error> { + avm_debug!("Line: {}", line_num); + + Ok(FrameControl::Continue) + } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 84d959144d6e..6979249307fc 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -6,11 +6,10 @@ use crate::avm2::method::{BytecodeMethod, Method, NativeMethod}; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::r#trait::Trait; -use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; use crate::avm2::script_object::{ScriptObjectClass, ScriptObjectData}; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::{Collect, CollectionContext, GcCell, MutationContext}; use std::fmt; @@ -76,42 +75,43 @@ impl<'gc> Executable<'gc> { /// Execute a method. /// /// The function will either be called directly if it is a Rust builtin, or - /// placed on the stack of the passed-in AVM2 otherwise. As a result, we - /// return a `ReturnValue` which can be used to force execution of the - /// given stack frame and obtain it's return value or to push said value - /// onto the AVM operand stack. + /// executed on the same AVM2 instance as the activation passed in here. + /// The value returned in either case will be provided here. + /// + /// It is a panicing logic error to attempt to execute user code while any + /// reachable object is currently under a GcCell write lock. pub fn exec( &self, unbound_reciever: Option>, arguments: &[Value<'gc>], - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, base_proto: Option>, - ) -> Result, Error> { + ) -> Result, Error> { match self { - Executable::Native(nf, reciever) => { - nf(avm, context, reciever.or(unbound_reciever), arguments) - } + Executable::Native(nf, reciever) => nf( + activation, + context, + reciever.or(unbound_reciever), + arguments, + ), Executable::Action { method, scope, reciever, } => { let reciever = reciever.or(unbound_reciever); - let activation = GcCell::allocate( - context.gc_context, - Activation::from_action( - context, - method.clone(), - *scope, - reciever, - arguments, - base_proto, - )?, + let mut activation = Activation::from_method( + activation.avm2(), + context, + method.clone(), + *scope, + reciever, + arguments, + base_proto, ); - avm.insert_stack_frame(activation); - Ok(activation.into()) + activation.run_actions(method.clone(), context) } } } @@ -161,7 +161,7 @@ impl<'gc> FunctionObject<'gc> { /// initializer method that you should call before interacting with the /// class. The latter should be called using the former as a reciever. pub fn from_class( - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, class: GcCell<'gc, Class<'gc>>, mut base_class: Object<'gc>, @@ -172,7 +172,7 @@ impl<'gc> FunctionObject<'gc> { .get_property( base_class, &QName::new(Namespace::public_namespace(), "prototype"), - avm, + activation, context, )? .as_object() @@ -187,9 +187,9 @@ impl<'gc> FunctionObject<'gc> { ) .into() }); - let mut class_proto = super_proto?.derive(avm, context, class, scope)?; - let fn_proto = avm.prototypes().function; - let class_constr_proto = avm.prototypes().class; + let mut class_proto = super_proto?.derive(activation, context, class, scope)?; + let fn_proto = activation.avm2().prototypes().function; + let class_constr_proto = activation.avm2().prototypes().class; let initializer = class_read.instance_init(); @@ -303,13 +303,15 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self, reciever: Object<'gc>, name: &QName, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { - self.0 - .read() - .base - .get_property_local(reciever, name, avm, context) + let read = self.0.read(); + let rv = read.base.get_property_local(reciever, name, activation)?; + + drop(read); + + rv.resolve(activation, context) } fn set_property_local( @@ -317,16 +319,17 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { reciever: Object<'gc>, name: &QName, value: Value<'gc>, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error> { - let rv = self - .0 - .write(context.gc_context) + let mut write = self.0.write(context.gc_context); + let rv = write .base - .set_property_local(reciever, name, value, avm, context)?; + .set_property_local(reciever, name, value, activation, context)?; - rv.resolve(avm, context)?; + drop(write); + + rv.resolve(activation, context)?; Ok(()) } @@ -336,16 +339,17 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { reciever: Object<'gc>, name: &QName, value: Value<'gc>, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error> { - let rv = self - .0 - .write(context.gc_context) + let mut write = self.0.write(context.gc_context); + let rv = write .base - .init_property_local(reciever, name, value, avm, context)?; + .init_property_local(reciever, name, value, activation, context)?; + + drop(write); - rv.resolve(avm, context)?; + rv.resolve(activation, context)?; Ok(()) } @@ -468,13 +472,12 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self, reciever: Option>, arguments: &[Value<'gc>], - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, base_proto: Option>, ) -> Result, Error> { if let Some(exec) = &self.0.read().exec { - exec.exec(reciever, arguments, avm, context, base_proto)? - .resolve(avm, context) + exec.exec(reciever, arguments, activation, context, base_proto) } else { Err("Not a callable function!".into()) } @@ -482,7 +485,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn construct( &self, - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], ) -> Result, Error> { @@ -498,7 +501,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn derive( &self, - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, class: GcCell<'gc, Class<'gc>>, scope: Option>>, diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 4986131048af..5972ee9275eb 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -1,13 +1,13 @@ //! Global scope built-ins +use crate::avm2::activation::Activation; use crate::avm2::function::FunctionObject; use crate::avm2::method::NativeMethod; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, TObject}; -use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::{Collect, MutationContext}; use std::f64::NAN; @@ -18,16 +18,16 @@ mod function; mod object; fn trace<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Option>, args: &[Value<'gc>], -) -> Result, Error> { +) -> Result, Error> { if let Some(s) = args.get(0) { log::info!(target: "avm_trace", "{}", s.clone().coerce_string()); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } /// This structure represents all system builtins' prototypes. diff --git a/core/src/avm2/globals/class.rs b/core/src/avm2/globals/class.rs index e7144bb79543..c738f2403cc2 100644 --- a/core/src/avm2/globals/class.rs +++ b/core/src/avm2/globals/class.rs @@ -1,10 +1,10 @@ //! `Class` builtin/prototype +use crate::avm2::activation::Activation; use crate::avm2::object::Object; -use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::MutationContext; @@ -13,11 +13,11 @@ use gc_arena::MutationContext; /// Notably, you cannot construct new classes this way, so this returns an /// error. pub fn constructor<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], -) -> Result, Error> { +) -> Result, Error> { Err("Classes cannot be constructed.".into()) } diff --git a/core/src/avm2/globals/flash/display/displayobject.rs b/core/src/avm2/globals/flash/display/displayobject.rs index 9542041a28ed..30278237d059 100644 --- a/core/src/avm2/globals/flash/display/displayobject.rs +++ b/core/src/avm2/globals/flash/display/displayobject.rs @@ -1,21 +1,21 @@ //! `flash.display.DisplayObject` builtin/prototype +use crate::avm2::activation::Activation; use crate::avm2::object::Object; -use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::MutationContext; /// Implements `flash.display.DisplayObject`'s constructor. pub fn constructor<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], -) -> Result, Error> { - Ok(Value::Undefined.into()) +) -> Result, Error> { + Ok(Value::Undefined) } /// Construct `DisplayObject.prototype`. diff --git a/core/src/avm2/globals/flash/display/displayobjectcontainer.rs b/core/src/avm2/globals/flash/display/displayobjectcontainer.rs index 74a5d248ea9a..7b09b5c5ac33 100644 --- a/core/src/avm2/globals/flash/display/displayobjectcontainer.rs +++ b/core/src/avm2/globals/flash/display/displayobjectcontainer.rs @@ -1,21 +1,21 @@ //! `flash.display.DisplayObjectContainer` builtin/prototype +use crate::avm2::activation::Activation; use crate::avm2::object::Object; -use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::MutationContext; /// Implements `flash.display.DisplayObjectContainer`'s constructor. pub fn constructor<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], -) -> Result, Error> { - Ok(Value::Undefined.into()) +) -> Result, Error> { + Ok(Value::Undefined) } /// Construct `DisplayObjectContainer.prototype`. diff --git a/core/src/avm2/globals/flash/display/interactiveobject.rs b/core/src/avm2/globals/flash/display/interactiveobject.rs index fe736a3a8e20..8fc2844fd620 100644 --- a/core/src/avm2/globals/flash/display/interactiveobject.rs +++ b/core/src/avm2/globals/flash/display/interactiveobject.rs @@ -1,21 +1,21 @@ //! `flash.display.InteractiveObject` builtin/prototype +use crate::avm2::activation::Activation; use crate::avm2::object::Object; -use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::MutationContext; /// Implements `flash.display.InteractiveObject`'s constructor. pub fn constructor<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], -) -> Result, Error> { - Ok(Value::Undefined.into()) +) -> Result, Error> { + Ok(Value::Undefined) } /// Construct `InteractiveObject.prototype`. diff --git a/core/src/avm2/globals/flash/display/movieclip.rs b/core/src/avm2/globals/flash/display/movieclip.rs index 6e39e7c94783..1a97f8ea4741 100644 --- a/core/src/avm2/globals/flash/display/movieclip.rs +++ b/core/src/avm2/globals/flash/display/movieclip.rs @@ -1,21 +1,21 @@ //! `flash.display.MovieClip` builtin/prototype +use crate::avm2::activation::Activation; use crate::avm2::object::Object; -use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::MutationContext; /// Implements `flash.display.MovieClip`'s constructor. pub fn constructor<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], -) -> Result, Error> { - Ok(Value::Undefined.into()) +) -> Result, Error> { + Ok(Value::Undefined) } /// Construct `MovieClip.prototype`. diff --git a/core/src/avm2/globals/flash/display/sprite.rs b/core/src/avm2/globals/flash/display/sprite.rs index 110afca1526e..8763921d627b 100644 --- a/core/src/avm2/globals/flash/display/sprite.rs +++ b/core/src/avm2/globals/flash/display/sprite.rs @@ -1,21 +1,21 @@ //! `flash.display.Sprite` builtin/prototype +use crate::avm2::activation::Activation; use crate::avm2::object::Object; -use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::MutationContext; /// Implements `flash.display.Sprite`'s constructor. pub fn constructor<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], -) -> Result, Error> { - Ok(Value::Undefined.into()) +) -> Result, Error> { + Ok(Value::Undefined) } /// Construct `Sprite.prototype`. diff --git a/core/src/avm2/globals/flash/events/eventdispatcher.rs b/core/src/avm2/globals/flash/events/eventdispatcher.rs index ea16792cf768..172a8dddac01 100644 --- a/core/src/avm2/globals/flash/events/eventdispatcher.rs +++ b/core/src/avm2/globals/flash/events/eventdispatcher.rs @@ -1,21 +1,21 @@ //! `flash.events.EventDispatcher` builtin/prototype +use crate::avm2::activation::Activation; use crate::avm2::object::Object; -use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::MutationContext; /// Implements `flash.events.EventDispatcher`'s constructor. pub fn constructor<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], -) -> Result, Error> { - Ok(Value::Undefined.into()) +) -> Result, Error> { + Ok(Value::Undefined) } /// Construct `EventDispatcher.prototype`. diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs index 4f3fba60b1cb..7f9ffc8ffe88 100644 --- a/core/src/avm2/globals/function.rs +++ b/core/src/avm2/globals/function.rs @@ -1,42 +1,40 @@ //! Function builtin and prototype +use crate::avm2::activation::Activation; use crate::avm2::function::FunctionObject; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, TObject}; -use crate::avm2::return_value::ReturnValue; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::MutationContext; /// Implements `Function` pub fn constructor<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], -) -> Result, Error> { - Ok(Value::Undefined.into()) +) -> Result, Error> { + Ok(Value::Undefined) } /// Implements `Function.prototype.call` fn call<'gc>( - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, func: Option>, args: &[Value<'gc>], -) -> Result, Error> { +) -> Result, Error> { let this = args.get(0).and_then(|v| v.as_object().ok()); let base_proto = this.and_then(|that| that.proto()); if let Some(func) = func { if args.len() > 1 { - Ok(func - .call(this, &args[1..], avm, context, base_proto)? - .into()) + Ok(func.call(this, &args[1..], activation, context, base_proto)?) } else { - Ok(func.call(this, &[], avm, context, base_proto)?.into()) + Ok(func.call(this, &[], activation, context, base_proto)?) } } else { Err("Not a callable function".into()) diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 5bea902070b1..f1a1e14424be 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -1,70 +1,65 @@ //! Object builtin and prototype +use crate::avm2::activation::Activation; use crate::avm2::function::FunctionObject; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, TObject}; -use crate::avm2::return_value::ReturnValue; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::MutationContext; /// Implements `Object` pub fn constructor<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], -) -> Result, Error> { - Ok(Value::Undefined.into()) +) -> Result, Error> { + Ok(Value::Undefined) } /// Implements `Object.prototype.toString` fn to_string<'gc>( - _: &mut Avm2<'gc>, + _: &mut Activation<'_, 'gc>, _: &mut UpdateContext<'_, 'gc, '_>, this: Option>, _: &[Value<'gc>], -) -> Result, Error> { +) -> Result, Error> { Ok(this .map(|t| t.to_string()) - .unwrap_or(Ok(Value::Undefined))? - .into()) + .unwrap_or(Ok(Value::Undefined))?) } /// Implements `Object.prototype.toLocaleString` fn to_locale_string<'gc>( - _: &mut Avm2<'gc>, + _: &mut Activation<'_, 'gc>, _: &mut UpdateContext<'_, 'gc, '_>, this: Option>, _: &[Value<'gc>], -) -> Result, Error> { +) -> Result, Error> { Ok(this .map(|t| t.to_string()) - .unwrap_or(Ok(Value::Undefined))? - .into()) + .unwrap_or(Ok(Value::Undefined))?) } /// Implements `Object.prototype.valueOf` fn value_of<'gc>( - _: &mut Avm2<'gc>, + _: &mut Activation<'_, 'gc>, _: &mut UpdateContext<'_, 'gc, '_>, this: Option>, _: &[Value<'gc>], -) -> Result, Error> { - Ok(this - .map(|t| t.value_of()) - .unwrap_or(Ok(Value::Undefined))? - .into()) +) -> Result, Error> { + Ok(this.map(|t| t.value_of()).unwrap_or(Ok(Value::Undefined))?) } /// `Object.prototype.hasOwnProperty` pub fn has_own_property<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Option>, args: &[Value<'gc>], -) -> Result, Error> { +) -> Result, Error> { let this: Result, Error> = this.ok_or_else(|| "No valid this parameter".into()); let this = this?; let name: Result<&Value<'gc>, Error> = args.get(0).ok_or_else(|| "No name specified".into()); @@ -82,11 +77,11 @@ pub fn has_own_property<'gc>( /// `Object.prototype.isPrototypeOf` pub fn is_prototype_of<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Option>, args: &[Value<'gc>], -) -> Result, Error> { +) -> Result, Error> { let search_proto: Result, Error> = this.ok_or_else(|| "No valid this parameter".into()); let search_proto = search_proto?; @@ -105,11 +100,11 @@ pub fn is_prototype_of<'gc>( /// `Object.prototype.propertyIsEnumerable` pub fn property_is_enumerable<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Option>, args: &[Value<'gc>], -) -> Result, Error> { +) -> Result, Error> { let this: Result, Error> = this.ok_or_else(|| "No valid this parameter".into()); let this = this?; let name: Result<&Value<'gc>, Error> = args.get(0).ok_or_else(|| "No name specified".into()); @@ -127,11 +122,11 @@ pub fn property_is_enumerable<'gc>( /// `Object.prototype.setPropertyIsEnumerable` pub fn set_property_is_enumerable<'gc>( - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Option>, args: &[Value<'gc>], -) -> Result, Error> { +) -> Result, Error> { let this: Result, Error> = this.ok_or_else(|| "No valid this parameter".into()); let this = this?; let name: Result<&Value<'gc>, Error> = args.get(0).ok_or_else(|| "No name specified".into()); @@ -149,7 +144,7 @@ pub fn set_property_is_enumerable<'gc>( } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } /// Partially construct `Object.prototype`. diff --git a/core/src/avm2/method.rs b/core/src/avm2/method.rs index 1bd5d6a320e3..c2189143cf32 100644 --- a/core/src/avm2/method.rs +++ b/core/src/avm2/method.rs @@ -1,10 +1,10 @@ //! AVM2 methods +use crate::avm2::activation::Activation; use crate::avm2::object::Object; -use crate::avm2::return_value::ReturnValue; use crate::avm2::script::TranslationUnit; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::{Collect, CollectionContext}; use std::fmt; @@ -30,11 +30,11 @@ pub struct CollectWrapper(T); /// your function yields `None`, you must ensure that the top-most activation /// in the AVM1 runtime will return with the value of this function. pub type NativeMethod<'gc> = fn( - &mut Avm2<'gc>, + &mut Activation<'_, 'gc>, &mut UpdateContext<'_, 'gc, '_>, Option>, &[Value<'gc>], -) -> Result, Error>; +) -> Result, Error>; /// Represents a reference to an AVM2 method and body. #[derive(Collect, Clone, Debug)] diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 104295457a0f..056da591d75e 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1,5 +1,6 @@ //! AVM2 objects. +use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::function::{Executable, FunctionObject}; use crate::avm2::names::{Multiname, Namespace, QName}; @@ -7,7 +8,7 @@ use crate::avm2::r#trait::{Trait, TraitKind}; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::{Collect, GcCell, MutationContext}; use ruffle_macros::enum_trait_object; @@ -30,7 +31,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy self, reciever: Object<'gc>, name: &QName, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error>; @@ -39,23 +40,23 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &mut self, reciever: Object<'gc>, name: &QName, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { if !self.has_instantiated_property(name) { for abc_trait in self.get_trait(name)? { - self.install_trait(avm, context, abc_trait, reciever)?; + self.install_trait(activation, context, abc_trait, reciever)?; } } let has_no_getter = self.has_own_virtual_setter(name) && !self.has_own_virtual_getter(name); if self.has_own_property(name)? && !has_no_getter { - return self.get_property_local(reciever, name, avm, context); + return self.get_property_local(reciever, name, activation, context); } if let Some(mut proto) = self.proto() { - return proto.get_property(reciever, name, avm, context); + return proto.get_property(reciever, name, activation, context); } Ok(Value::Undefined) @@ -83,7 +84,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy reciever: Object<'gc>, name: &QName, value: Value<'gc>, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error>; @@ -93,17 +94,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy reciever: Object<'gc>, name: &QName, value: Value<'gc>, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error> { if !self.has_instantiated_property(name) { for abc_trait in self.get_trait(name)? { - self.install_trait(avm, context, abc_trait, reciever)?; + self.install_trait(activation, context, abc_trait, reciever)?; } } if self.has_own_virtual_setter(name) { - return self.set_property_local(reciever, name, value, avm, context); + return self.set_property_local(reciever, name, value, activation, context); } let mut proto = self.proto(); @@ -112,13 +113,13 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy //we're calling a virtual setter. If you call `set_property` on //a non-virtual you will actually alter the prototype. if my_proto.has_own_virtual_setter(name) { - return my_proto.set_property(reciever, name, value, avm, context); + return my_proto.set_property(reciever, name, value, activation, context); } proto = my_proto.proto(); } - reciever.set_property_local(reciever, name, value, avm, context) + reciever.set_property_local(reciever, name, value, activation, context) } /// Init a property on this specific object. @@ -127,7 +128,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy reciever: Object<'gc>, name: &QName, value: Value<'gc>, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error>; @@ -137,17 +138,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy reciever: Object<'gc>, name: &QName, value: Value<'gc>, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error> { if !self.has_instantiated_property(name) { for abc_trait in self.get_trait(name)? { - self.install_trait(avm, context, abc_trait, reciever)?; + self.install_trait(activation, context, abc_trait, reciever)?; } } if self.has_own_virtual_setter(name) { - return self.init_property_local(reciever, name, value, avm, context); + return self.init_property_local(reciever, name, value, activation, context); } let mut proto = self.proto(); @@ -156,13 +157,13 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy //we're calling a virtual setter. If you call `set_property` on //a non-virtual you will actually alter the prototype. if my_proto.has_own_virtual_setter(name) { - return my_proto.init_property(reciever, name, value, avm, context); + return my_proto.init_property(reciever, name, value, activation, context); } proto = my_proto.proto(); } - reciever.init_property_local(reciever, name, value, avm, context) + reciever.init_property_local(reciever, name, value, activation, context) } /// Retrieve a slot by it's index. @@ -404,24 +405,24 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// object. fn install_trait( &mut self, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, trait_entry: Trait<'gc>, reciever: Object<'gc>, ) -> Result<(), Error> { - self.install_foreign_trait(avm, context, trait_entry, self.get_scope(), reciever) + self.install_foreign_trait(activation, context, trait_entry, self.get_scope(), reciever) } /// Install a trait from anywyere. fn install_foreign_trait( &mut self, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, trait_entry: Trait<'gc>, scope: Option>>, reciever: Object<'gc>, ) -> Result<(), Error> { - let fn_proto = avm.prototypes().function; + let fn_proto = activation.avm2().prototypes().function; let trait_name = trait_entry.name().clone(); avm_debug!( "Installing trait {:?} of kind {:?}", @@ -491,14 +492,14 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy }; let super_class: Result, Error> = self - .get_property(reciever, &super_name, avm, context)? + .get_property(reciever, &super_name, activation, context)? .as_object() .map_err(|_e| { format!("Could not resolve superclass {:?}", super_name.local_name()).into() }); let (class_object, _cinit) = - FunctionObject::from_class(avm, context, *class, super_class?, scope)?; + FunctionObject::from_class(activation, context, *class, super_class?, scope)?; self.install_const( context.gc_context, class_read.name().clone(), @@ -516,7 +517,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn_proto, None, ); - let es3_proto = ScriptObject::object(context.gc_context, avm.prototypes().object); + let es3_proto = + ScriptObject::object(context.gc_context, activation.avm2().prototypes().object); fobject.install_slot( context.gc_context, @@ -548,7 +550,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy self, _reciever: Option>, _arguments: &[Value<'gc>], - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _base_proto: Option>, ) -> Result, Error> { @@ -574,7 +576,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// purely so that host objects can be constructed by the VM. fn construct( &self, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], ) -> Result, Error>; @@ -590,7 +592,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// traits. fn derive( &self, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, class: GcCell<'gc, Class<'gc>>, scope: Option>>, diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index b51794e9b7d9..75e21dfe0c22 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -5,8 +5,7 @@ use crate::avm2::function::Executable; use crate::avm2::object::{Object, TObject}; use crate::avm2::return_value::ReturnValue; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; -use crate::context::UpdateContext; +use crate::avm2::Error; use enumset::{EnumSet, EnumSetType}; use gc_arena::{Collect, CollectionContext}; @@ -136,15 +135,16 @@ impl<'gc> Property<'gc> { /// user-defined. pub fn get( &self, - avm: &mut Avm2<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, ) -> Result, Error> { match self { - Property::Virtual { get: Some(get), .. } => { - get.exec(Some(this), &[], avm, context, base_proto) - } + Property::Virtual { get: Some(get), .. } => Ok(ReturnValue::defer_execution( + get.clone(), + Some(this), + vec![], + base_proto, + )), Property::Virtual { get: None, .. } => Ok(Value::Undefined.into()), Property::Stored { value, .. } => Ok(value.to_owned().into()), Property::Slot { slot_id, .. } => this.get_slot(*slot_id).map(|v| v.into()), @@ -160,8 +160,6 @@ impl<'gc> Property<'gc> { /// is encountered. pub fn set( &mut self, - avm: &mut Avm2<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, new_value: impl Into>, @@ -169,13 +167,12 @@ impl<'gc> Property<'gc> { match self { Property::Virtual { set, .. } => { if let Some(function) = set { - return function.exec( + return Ok(ReturnValue::defer_execution( + function.clone(), Some(this), - &[new_value.into()], - avm, - context, + vec![new_value.into()], base_proto, - ); + )); } Ok(Value::Undefined.into()) @@ -207,8 +204,6 @@ impl<'gc> Property<'gc> { /// is encountered. pub fn init( &mut self, - avm: &mut Avm2<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, new_value: impl Into>, @@ -216,13 +211,12 @@ impl<'gc> Property<'gc> { match self { Property::Virtual { set, .. } => { if let Some(function) = set { - return function.exec( + return Ok(ReturnValue::defer_execution( + function.clone(), Some(this), - &[new_value.into()], - avm, - context, + vec![new_value.into()], base_proto, - ); + )); } Ok(Value::Undefined.into()) diff --git a/core/src/avm2/return_value.rs b/core/src/avm2/return_value.rs index a9d692350fb6..6d0e56d651ee 100644 --- a/core/src/avm2/return_value.rs +++ b/core/src/avm2/return_value.rs @@ -1,190 +1,113 @@ //! Return value enum use crate::avm2::activation::Activation; -use crate::avm2::names::Namespace; +use crate::avm2::function::Executable; use crate::avm2::object::Object; -use crate::avm2::{Avm2, Error, Value}; +use crate::avm2::{Error, Value}; use crate::context::UpdateContext; -use gc_arena::{Collect, GcCell}; use std::fmt; -/// Represents the return value of a function call. +/// Represents the return value of a function call that has not yet executed. /// -/// Since function calls can result in invoking native code or adding a new -/// activation onto the AVM stack, you need another type to represent how the -/// return value will be delivered to you. +/// It is a panicking logic error to attempt to run AVM2 code while any +/// reachable object is in a locked state. Ergo, it is sometimes necessary to +/// be able to return what *should* be called rather than actually running the +/// code on the current Rust stack. This type exists to force deferred +/// execution of some child AVM2 frame. /// -/// This function contains a handful of utility methods for deciding what to do -/// with a given value regardless of how it is delivered to the calling -/// function. +/// It is also possible to stuff a regular `Value` in here - this is provided +/// for the convenience of functions that may be able to resolve a value +/// without needing a free stack. `ReturnValue` should not be used as a generic +/// wrapper for `Value`, as it can also defer actual execution, and it should +/// be resolved at the earliest safe opportunity. /// -/// It is `must_use` - failing to use a return value is a compiler warning. We -/// provide a helper function specifically to indicate that you aren't -/// interested in the result of a call. +/// It is `must_use` - failing to resolve a return value is a compiler warning. #[must_use = "Return values must be used"] -#[derive(Clone, Collect)] -#[collect(no_drop)] pub enum ReturnValue<'gc> { - /// Indicates that the return value is available immediately. + /// ReturnValue has already been computed. + /// + /// This exists primarily for functions that don't necessarily need to + /// always defer code execution - say, if they already have the result and + /// do not need a free stack frame to run an activation on. Immediate(Value<'gc>), - /// Indicates that the return value is the result of a given user-defined - /// function call. The activation record returned is the frame that needs - /// to return to get your value. - ResultOf(GcCell<'gc, Activation<'gc>>), -} - -impl PartialEq for ReturnValue<'_> { - fn eq(&self, other: &Self) -> bool { - use ReturnValue::*; - - match (self, other) { - (Immediate(val1), Immediate(val2)) => val1 == val2, - (ResultOf(frame1), ResultOf(frame2)) => GcCell::ptr_eq(*frame1, *frame2), - _ => false, - } - } + /// ReturnValue has not yet been computed. + /// + /// This exists for functions that do need to reference the result of user + /// code in order to produce their result. + ResultOf { + executable: Executable<'gc>, + unbound_reciever: Option>, + arguments: Vec>, + base_proto: Option>, + }, } impl fmt::Debug for ReturnValue<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use ReturnValue::*; - match self { - Immediate(val) => write!(f, "Immediate({:?})", val), - ResultOf(_frame) => write!(f, "ResultOf()"), + Self::Immediate(v) => f.debug_tuple("ReturnValue::Immediate").field(v).finish(), + Self::ResultOf { + executable: _executable, + unbound_reciever, + arguments, + base_proto, + } => f + .debug_struct("ReturnValue") + .field("executable", &"") + .field("unbound_reciever", unbound_reciever) + .field("arguments", arguments) + .field("base_proto", base_proto) + .finish(), } } } impl<'gc> ReturnValue<'gc> { - /// Mark a given return value as intended to be pushed onto the stack. - /// - /// The natural result of a stack frame retiring is to be pushed, so this - /// only ensures that Immediate values are pushed. - pub fn push(self, avm: &mut Avm2<'gc>) { - use ReturnValue::*; - - match self { - Immediate(val) => avm.push(val), - ResultOf(_frame) => {} - }; + /// Construct a new return value. + pub fn defer_execution( + executable: Executable<'gc>, + unbound_reciever: Option>, + arguments: Vec>, + base_proto: Option>, + ) -> Self { + Self::ResultOf { + executable, + unbound_reciever, + arguments, + base_proto, + } } - /// Force a return value to resolve on the Rust stack by recursing back - /// into the AVM. + /// Resolve the underlying deferred execution. + /// + /// All return values must eventually resolved - it is a compile error to + /// fail to do so. pub fn resolve( self, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { - use ReturnValue::*; - match self { - Immediate(val) => Ok(val), - ResultOf(frame) => { - avm.run_current_frame(context, frame)?; - - Ok(avm.pop()) - } - } - } - - pub fn is_immediate(&self) -> bool { - use ReturnValue::*; - - if let Immediate(_v) = self { - true - } else { - false - } - } - - /// Panic if a value is not immediate. - /// - /// This should only be used in test assertions. - #[cfg(test)] - pub fn unwrap_immediate(self) -> Value<'gc> { - use ReturnValue::*; - - match self { - Immediate(val) => val, - _ => panic!("Unwrapped a non-immediate return value"), + Self::Immediate(v) => Ok(v), + Self::ResultOf { + executable, + unbound_reciever, + arguments, + base_proto, + } => executable.exec( + unbound_reciever, + &arguments, + activation, + context, + base_proto, + ), } } } impl<'gc> From> for ReturnValue<'gc> { - fn from(val: Value<'gc>) -> Self { - ReturnValue::Immediate(val) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(string: String) -> Self { - ReturnValue::Immediate(Value::String(string)) - } -} - -impl<'gc> From<&str> for ReturnValue<'gc> { - fn from(string: &str) -> Self { - ReturnValue::Immediate(Value::String(string.to_owned())) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: bool) -> Self { - ReturnValue::Immediate(Value::Bool(value)) - } -} - -impl<'gc, T> From for ReturnValue<'gc> -where - Object<'gc>: From, -{ - fn from(value: T) -> Self { - ReturnValue::Immediate(Value::Object(Object::from(value))) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: f64) -> Self { - ReturnValue::Immediate(Value::Number(value)) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: f32) -> Self { - ReturnValue::Immediate(Value::Number(f64::from(value))) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: u8) -> Self { - ReturnValue::Immediate(Value::Number(f64::from(value))) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: i32) -> Self { - ReturnValue::Immediate(Value::Number(f64::from(value))) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: u32) -> Self { - ReturnValue::Immediate(Value::Number(f64::from(value))) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: Namespace) -> Self { - ReturnValue::Immediate(Value::Namespace(value)) - } -} - -impl<'gc> From>> for ReturnValue<'gc> { - fn from(frame: GcCell<'gc, Activation<'gc>>) -> Self { - ReturnValue::ResultOf(frame) + fn from(v: Value<'gc>) -> Self { + Self::Immediate(v) } } diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index 025946b17940..b7dac37cbb4a 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -1,9 +1,10 @@ //! Represents AVM2 scope chain resolution. +use crate::avm2::activation::Activation; use crate::avm2::names::Multiname; use crate::avm2::object::{Object, TObject}; -use crate::avm2::return_value::ReturnValue; -use crate::avm2::{Avm2, Error}; +use crate::avm2::value::Value; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::{Collect, GcCell, MutationContext}; use std::cell::Ref; @@ -99,7 +100,7 @@ impl<'gc> Scope<'gc> { pub fn find( &self, name: &Multiname, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result>, Error> { if let Some(qname) = self.locals().resolve_multiname(name)? { @@ -109,7 +110,7 @@ impl<'gc> Scope<'gc> { } if let Some(scope) = self.parent() { - return scope.find(name, avm, context); + return scope.find(name, activation, context); } Ok(None) @@ -122,21 +123,24 @@ impl<'gc> Scope<'gc> { pub fn resolve( &mut self, name: &Multiname, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result>, Error> { + ) -> Result>, Error> { if let Some(qname) = self.locals().resolve_multiname(name)? { if self.locals().has_property(&qname)? { - return Ok(Some( - self.values - .get_property(self.values, &qname, avm, context)? - .into(), - )); + return Ok(Some(self.values.get_property( + self.values, + &qname, + activation, + context, + )?)); } } if let Some(parent) = self.parent { - return parent.write(context.gc_context).resolve(name, avm, context); + return parent + .write(context.gc_context) + .resolve(name, activation, context); } //TODO: Should undefined variables halt execution? diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 80bfa3b8f0d3..fa06a05e6df1 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -1,5 +1,6 @@ //! Default AVM2 object impl +use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::function::Executable; use crate::avm2::names::{Namespace, QName}; @@ -10,7 +11,7 @@ use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; use crate::avm2::slot::Slot; use crate::avm2::value::Value; -use crate::avm2::{Avm2, Error}; +use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::{Collect, GcCell, MutationContext}; use std::collections::HashMap; @@ -74,12 +75,15 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self, reciever: Object<'gc>, name: &QName, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { - self.0 + let rv = self + .0 .read() - .get_property_local(reciever, name, avm, context) + .get_property_local(reciever, name, activation)?; + + rv.resolve(activation, context) } fn set_property_local( @@ -87,15 +91,15 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { reciever: Object<'gc>, name: &QName, value: Value<'gc>, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error> { let rv = self .0 .write(context.gc_context) - .set_property_local(reciever, name, value, avm, context)?; + .set_property_local(reciever, name, value, activation, context)?; - rv.resolve(avm, context)?; + rv.resolve(activation, context)?; Ok(()) } @@ -105,15 +109,15 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { reciever: Object<'gc>, name: &QName, value: Value<'gc>, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error> { let rv = self .0 .write(context.gc_context) - .init_property_local(reciever, name, value, avm, context)?; + .init_property_local(reciever, name, value, activation, context)?; - rv.resolve(avm, context)?; + rv.resolve(activation, context)?; Ok(()) } @@ -229,7 +233,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn construct( &self, - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], ) -> Result, Error> { @@ -239,7 +243,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn derive( &self, - _avm: &mut Avm2<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, class: GcCell<'gc, Class<'gc>>, scope: Option>>, @@ -376,23 +380,14 @@ impl<'gc> ScriptObjectData<'gc> { &self, reciever: Object<'gc>, name: &QName, - avm: &mut Avm2<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error> { + activation: &mut Activation<'_, 'gc>, + ) -> Result, Error> { let prop = self.values.get(name); if let Some(prop) = prop { - prop.get( - avm, - context, - reciever, - avm.current_stack_frame() - .and_then(|sf| sf.read().base_proto()) - .or(self.proto), - )? - .resolve(avm, context) + prop.get(reciever, activation.base_proto().or(self.proto)) } else { - Ok(Value::Undefined) + Ok(Value::Undefined.into()) } } @@ -401,25 +396,26 @@ impl<'gc> ScriptObjectData<'gc> { reciever: Object<'gc>, name: &QName, value: Value<'gc>, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { - if let Some(prop) = self.values.get_mut(name) { + let slot_id = if let Some(prop) = self.values.get(name) { if let Some(slot_id) = prop.slot_id() { - self.set_slot(slot_id, value, context.gc_context)?; - Ok(Value::Undefined.into()) + Some(slot_id) } else { - let proto = self.proto; - prop.set( - avm, - context, - reciever, - avm.current_stack_frame() - .and_then(|sf| sf.read().base_proto()) - .or(proto), - value, - ) + None } + } else { + None + }; + + if let Some(slot_id) = slot_id { + self.set_slot(slot_id, value, context.gc_context)?; + Ok(Value::Undefined.into()) + } else if self.values.contains_key(name) { + let prop = self.values.get_mut(name).unwrap(); + let proto = self.proto; + prop.set(reciever, activation.base_proto().or(proto), value) } else { //TODO: Not all classes are dynamic like this self.enumerants.push(name.clone()); @@ -435,7 +431,7 @@ impl<'gc> ScriptObjectData<'gc> { reciever: Object<'gc>, name: &QName, value: Value<'gc>, - avm: &mut Avm2<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { if let Some(prop) = self.values.get_mut(name) { @@ -444,15 +440,7 @@ impl<'gc> ScriptObjectData<'gc> { Ok(Value::Undefined.into()) } else { let proto = self.proto; - prop.init( - avm, - context, - reciever, - avm.current_stack_frame() - .and_then(|sf| sf.read().base_proto()) - .or(proto), - value, - ) + prop.init(reciever, activation.base_proto().or(proto), value) } } else { //TODO: Not all classes are dynamic like this diff --git a/core/src/player.rs b/core/src/player.rs index 4b8a61e17e5a..5ced943baf64 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -878,8 +878,6 @@ impl Player { } } } - // Execute the stack frame (if any). - let _ = avm2.run_stack_till_empty(context); } } From 090fe56bd32cdf6696cfd468885030d1978c0858 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 4 Jul 2020 17:56:27 -0400 Subject: [PATCH 162/189] Wrap `BytecodeMethod` (and the bytecode half of `Executable`) in a `Gc`. This is inspired by Dinnerbone's similar PR on the AVM1 side, where the Action half of that VM's `Executable` was reduced from 128 bytes to 16 by shoving it in a `Gc`. This won't be as dramatic but should still save some memory. In fact, it should save a *lot* of memory in bytecode execution, where thanks to the previous commit's rebase, we now need to clone the current method once *for each instruction executed*. That is terrible, but should stop now. --- core/src/avm2/activation.rs | 106 ++++++++++++++++++++---------------- core/src/avm2/function.rs | 101 +++++++++++++++++----------------- core/src/avm2/method.rs | 35 +++++++----- core/src/avm2/property.rs | 6 +- core/src/avm2/script.rs | 6 +- 5 files changed, 136 insertions(+), 118 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index d6a362edc447..e936a06c15db 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -11,7 +11,7 @@ use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; use crate::avm2::{value, Avm2, Error}; use crate::context::UpdateContext; -use gc_arena::{Collect, GcCell, MutationContext}; +use gc_arena::{Collect, Gc, GcCell, MutationContext}; use smallvec::SmallVec; use std::io::Cursor; use swf::avm2::read::Reader; @@ -137,7 +137,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { script: GcCell<'gc, Script<'gc>>, global: Object<'gc>, ) -> Result { - let method = script.read().init().into_entry()?; + let method = script.read().init().into_bytecode()?; let scope = Some(Scope::push_scope(None, global, context.gc_context)); let num_locals = method.body().num_locals; let local_registers = @@ -166,7 +166,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { pub fn from_method( avm2: &'a mut Avm2<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, scope: Option>>, this: Option>, arguments: &[Value<'gc>], @@ -210,7 +210,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { context: &mut UpdateContext<'_, 'gc, '_>, script: GcCell<'gc, Script<'gc>>, ) -> Result<(), Error> { - let init = script.read().init().into_entry()?; + let init = script.read().init().into_bytecode()?; self.run_actions(init, context)?; @@ -289,24 +289,36 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { } /// Retrieve a int from the current constant pool. - fn pool_int(&self, method: BytecodeMethod<'gc>, index: Index) -> Result { + fn pool_int( + &self, + method: Gc<'gc, BytecodeMethod<'gc>>, + index: Index, + ) -> Result { value::abc_int(&method.abc(), index) } /// Retrieve a int from the current constant pool. - fn pool_uint(&self, method: BytecodeMethod<'gc>, index: Index) -> Result { + fn pool_uint( + &self, + method: Gc<'gc, BytecodeMethod<'gc>>, + index: Index, + ) -> Result { value::abc_uint(&method.abc(), index) } /// Retrieve a double from the current constant pool. - fn pool_double(&self, method: BytecodeMethod<'gc>, index: Index) -> Result { + fn pool_double( + &self, + method: Gc<'gc, BytecodeMethod<'gc>>, + index: Index, + ) -> Result { value::abc_double(&method.abc(), index) } /// Retrieve a string from the current constant pool. fn pool_string( &self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, ) -> Result { value::abc_string(&method.abc(), index) @@ -315,7 +327,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { /// Retrieve a namespace from the current constant pool. fn pool_namespace( &self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, ) -> Result { Namespace::from_abc_namespace(&method.abc(), index) @@ -324,7 +336,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { /// Retrieve a multiname from the current constant pool. fn pool_multiname( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, ) -> Result { Multiname::from_abc_multiname(&method.abc(), index, self.avm2) @@ -334,7 +346,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { /// pool. fn pool_multiname_static( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, ) -> Result { Multiname::from_abc_multiname_static(&method.abc(), index) @@ -343,17 +355,18 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { /// Retrieve a method entry from the current ABC file's method table. fn table_method( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, - ) -> Result, Error> { - BytecodeMethod::from_method_index(method.translation_unit(), index.clone()) + mc: MutationContext<'gc, '_>, + ) -> Result>, Error> { + BytecodeMethod::from_method_index(method.translation_unit(), index.clone(), mc) .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, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result>, Error> { @@ -364,14 +377,13 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { pub fn run_actions( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { - let clone_method = method.clone(); - let mut read = Reader::new(Cursor::new(clone_method.body().code.as_ref())); + let mut read = Reader::new(Cursor::new(method.body().code.as_ref())); loop { - let result = self.do_next_opcode(method.clone(), context, &mut read); + let result = self.do_next_opcode(method, context, &mut read); match result { Ok(FrameControl::Return(value)) => break Ok(value), Ok(FrameControl::Continue) => {} @@ -383,7 +395,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { /// Run a single action from a given action reader. fn do_next_opcode( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, reader: &mut Reader>, ) -> Result, Error> { @@ -511,7 +523,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_push_double( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, value: Index, ) -> Result, Error> { self.avm2.push(self.pool_double(method, value)?); @@ -525,7 +537,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_push_int( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, value: Index, ) -> Result, Error> { self.avm2.push(self.pool_int(method, value)?); @@ -534,7 +546,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_push_namespace( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, value: Index, ) -> Result, Error> { self.avm2.push(self.pool_namespace(method, value)?); @@ -558,7 +570,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_push_string( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, value: Index, ) -> Result, Error> { self.avm2.push(self.pool_string(method, value)?); @@ -572,7 +584,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_push_uint( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, value: Index, ) -> Result, Error> { self.avm2.push(self.pool_uint(method, value)?); @@ -661,7 +673,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_call_property( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, arg_count: u32, @@ -686,7 +698,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_call_prop_lex( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, arg_count: u32, @@ -709,7 +721,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_call_prop_void( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, arg_count: u32, @@ -733,14 +745,14 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_call_static( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, arg_count: u32, ) -> Result, Error> { let args = self.avm2.pop_args(arg_count); let receiver = self.avm2.pop().as_object()?; - let method = self.table_method(method, index)?; + let method = self.table_method(method, index, context.gc_context)?; let scope = self.scope(); //TODO: Is this correct? let function = FunctionObject::from_method( context.gc_context, @@ -758,7 +770,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_call_super( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, arg_count: u32, @@ -791,7 +803,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_call_super_void( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, arg_count: u32, @@ -832,7 +844,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_get_property( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { @@ -851,7 +863,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_set_property( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { @@ -876,7 +888,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_init_property( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { @@ -901,7 +913,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_delete_property( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { @@ -920,7 +932,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_get_super( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { @@ -950,7 +962,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_set_super( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { @@ -1055,7 +1067,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_find_property( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { @@ -1075,7 +1087,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_find_prop_strict( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { @@ -1096,7 +1108,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_get_lex( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { @@ -1188,7 +1200,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_construct_prop( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, arg_count: u32, @@ -1283,11 +1295,11 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_new_function( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { - let method_entry = self.table_method(method, index)?; + let method_entry = self.table_method(method, index, context.gc_context)?; let scope = self.scope(); let mut new_fn = FunctionObject::from_method( @@ -1313,7 +1325,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_new_class( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { @@ -1501,7 +1513,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { #[allow(unused_variables)] fn op_debug( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, is_local_register: bool, register_name: Index, register: u8, @@ -1521,7 +1533,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { #[allow(unused_variables)] fn op_debug_file( &mut self, - method: BytecodeMethod<'gc>, + method: Gc<'gc, BytecodeMethod<'gc>>, file_name: Index, ) -> Result, Error> { let file_name = self.pool_string(method, file_name)?; diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 6979249307fc..75e43efbcab4 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -11,11 +11,29 @@ use crate::avm2::script_object::{ScriptObjectClass, ScriptObjectData}; use crate::avm2::value::Value; use crate::avm2::Error; use crate::context::UpdateContext; -use gc_arena::{Collect, CollectionContext, GcCell, MutationContext}; +use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext}; use std::fmt; +/// Represents code written in AVM2 bytecode that can be executed by some +/// means. +#[derive(Clone, Collect)] +#[collect(no_drop)] +pub struct BytecodeExecutable<'gc> { + /// The method code to execute from a given ABC file. + method: Gc<'gc, BytecodeMethod<'gc>>, + + /// The scope stack to pull variables from. + scope: Option>>, + + /// The reciever that this function is always called with. + /// + /// If `None`, then the reciever provided by the caller is used. A + /// `Some` value indicates a bound executable. + reciever: Option>, +} + /// Represents code that can be executed by some means. -#[derive(Clone)] +#[derive(Copy, Clone)] pub enum Executable<'gc> { /// Code defined in Ruffle's binary. /// @@ -23,33 +41,13 @@ pub enum Executable<'gc> { Native(NativeMethod<'gc>, Option>), /// Code defined in a loaded ABC file. - Action { - /// The method code to execute from a given ABC file. - method: BytecodeMethod<'gc>, - - /// The scope stack to pull variables from. - scope: Option>>, - - /// The reciever that this function is always called with. - /// - /// If `None`, then the reciever provided by the caller is used. A - /// `Some` value indicates a bound executable. - reciever: Option>, - }, + Action(Gc<'gc, BytecodeExecutable<'gc>>), } unsafe impl<'gc> Collect for Executable<'gc> { fn trace(&self, cc: CollectionContext) { match self { - Self::Action { - method, - scope, - reciever, - } => { - method.trace(cc); - scope.trace(cc); - reciever.trace(cc); - } + Self::Action(be) => be.trace(cc), Self::Native(_nf, reciever) => reciever.trace(cc), } } @@ -61,14 +59,18 @@ impl<'gc> Executable<'gc> { method: Method<'gc>, scope: Option>>, reciever: Option>, + mc: MutationContext<'gc, '_>, ) -> Self { match method { Method::Native(nf) => Self::Native(nf, reciever), - Method::Entry(a2me) => Self::Action { - method: a2me, - scope, - reciever, - }, + Method::Entry(a2me) => Self::Action(Gc::allocate( + mc, + BytecodeExecutable { + method: a2me, + scope, + reciever, + }, + )), } } @@ -95,23 +97,19 @@ impl<'gc> Executable<'gc> { reciever.or(unbound_reciever), arguments, ), - Executable::Action { - method, - scope, - reciever, - } => { - let reciever = reciever.or(unbound_reciever); + Executable::Action(bm) => { + let reciever = bm.reciever.or(unbound_reciever); let mut activation = Activation::from_method( activation.avm2(), context, - method.clone(), - *scope, + bm.method, + bm.scope, reciever, arguments, base_proto, ); - activation.run_actions(method.clone(), context) + activation.run_actions(bm.method, context) } } } @@ -120,15 +118,11 @@ impl<'gc> Executable<'gc> { impl<'gc> fmt::Debug for Executable<'gc> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Action { - method, - scope, - reciever, - } => fmt + Self::Action(be) => fmt .debug_struct("Executable::Action") - .field("method", method) - .field("scope", scope) - .field("reciever", reciever) + .field("method", &be.method) + .field("scope", &be.scope) + .field("reciever", &be.reciever) .finish(), Self::Native(nf, reciever) => fmt .debug_tuple("Executable::Native") @@ -200,7 +194,12 @@ impl<'gc> FunctionObject<'gc> { Some(fn_proto), ScriptObjectClass::ClassConstructor(class, scope), ), - exec: Some(Executable::from_method(initializer, scope, None)), + exec: Some(Executable::from_method( + initializer, + scope, + None, + context.gc_context, + )), }, )) .into(); @@ -239,7 +238,7 @@ impl<'gc> FunctionObject<'gc> { fn_proto: Object<'gc>, reciever: Option>, ) -> Object<'gc> { - let exec = Some(Executable::from_method(method, scope, reciever)); + let exec = Some(Executable::from_method(method, scope, reciever, mc)); FunctionObject(GcCell::allocate( mc, @@ -261,7 +260,7 @@ impl<'gc> FunctionObject<'gc> { mc, FunctionObjectData { base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass), - exec: Some(Executable::from_method(nf.into(), None, None)), + exec: Some(Executable::from_method(nf.into(), None, None, mc)), }, )) .into() @@ -278,7 +277,7 @@ impl<'gc> FunctionObject<'gc> { mc, FunctionObjectData { base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass), - exec: Some(Executable::from_method(constr.into(), None, None)), + exec: Some(Executable::from_method(constr.into(), None, None, mc)), }, )) .into(); @@ -465,7 +464,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { } fn as_executable(&self) -> Option> { - self.0.read().exec.clone() + self.0.read().exec } fn call( diff --git a/core/src/avm2/method.rs b/core/src/avm2/method.rs index c2189143cf32..3d9e49d25e21 100644 --- a/core/src/avm2/method.rs +++ b/core/src/avm2/method.rs @@ -6,7 +6,7 @@ use crate::avm2::script::TranslationUnit; use crate::avm2::value::Value; use crate::avm2::Error; use crate::context::UpdateContext; -use gc_arena::{Collect, CollectionContext}; +use gc_arena::{Collect, CollectionContext, Gc, MutationContext}; use std::fmt; use std::rc::Rc; use swf::avm2::types::{AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody}; @@ -61,18 +61,22 @@ impl<'gc> BytecodeMethod<'gc> { pub fn from_method_index( txunit: TranslationUnit<'gc>, abc_method: Index, - ) -> Option { + mc: MutationContext<'gc, '_>, + ) -> Option> { let abc = txunit.abc(); if abc.methods.get(abc_method.0 as usize).is_some() { for (index, method_body) in abc.method_bodies.iter().enumerate() { if method_body.method.0 == abc_method.0 { - return Some(Self { - txunit, - abc: CollectWrapper(txunit.abc()), - abc_method: abc_method.0, - abc_method_body: index as u32, - }); + return Some(Gc::allocate( + mc, + Self { + txunit, + abc: CollectWrapper(txunit.abc()), + abc_method: abc_method.0, + abc_method_body: index as u32, + }, + )); } } } @@ -115,7 +119,7 @@ pub enum Method<'gc> { Native(NativeMethod<'gc>), /// An ABC-provided method entry. - Entry(BytecodeMethod<'gc>), + Entry(Gc<'gc, BytecodeMethod<'gc>>), } unsafe impl<'gc> Collect for Method<'gc> { @@ -145,19 +149,22 @@ impl<'gc> From> for Method<'gc> { } } -impl<'gc> From> for Method<'gc> { - fn from(a2me: BytecodeMethod<'gc>) -> Self { - Self::Entry(a2me) +impl<'gc> From>> for Method<'gc> { + fn from(bm: Gc<'gc, BytecodeMethod<'gc>>) -> Self { + Self::Entry(bm) } } impl<'gc> Method<'gc> { - pub fn into_entry(self) -> Result, Error> { + /// Access the bytecode of this method. + /// + /// This function returns `Err` if there is no bytecode for this method. + pub fn into_bytecode(self) -> Result>, Error> { match self { Method::Native(_) => { Err("Attempted to unwrap a native method as a user-defined one".into()) } - Method::Entry(a2me) => Ok(a2me), + Method::Entry(bm) => Ok(bm), } } } diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 75e21dfe0c22..c9c12e550ad3 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -140,7 +140,7 @@ impl<'gc> Property<'gc> { ) -> Result, Error> { match self { Property::Virtual { get: Some(get), .. } => Ok(ReturnValue::defer_execution( - get.clone(), + *get, Some(this), vec![], base_proto, @@ -168,7 +168,7 @@ impl<'gc> Property<'gc> { Property::Virtual { set, .. } => { if let Some(function) = set { return Ok(ReturnValue::defer_execution( - function.clone(), + *function, Some(this), vec![new_value.into()], base_proto, @@ -212,7 +212,7 @@ impl<'gc> Property<'gc> { Property::Virtual { set, .. } => { if let Some(function) = set { return Ok(ReturnValue::defer_execution( - function.clone(), + *function, Some(this), vec![new_value.into()], base_proto, diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index 684b5a9391c9..c17dcc73c5b6 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -4,7 +4,7 @@ use crate::avm2::class::Class; use crate::avm2::method::{BytecodeMethod, Method}; use crate::avm2::r#trait::Trait; use crate::avm2::Error; -use gc_arena::{Collect, GcCell, MutationContext}; +use gc_arena::{Collect, Gc, GcCell, MutationContext}; use std::collections::HashMap; use std::mem::drop; use std::rc::Rc; @@ -77,8 +77,8 @@ impl<'gc> TranslationUnit<'gc> { drop(write); - let method: Result, Error> = - BytecodeMethod::from_method_index(self, Index::new(method_index)) + let method: Result>, Error> = + BytecodeMethod::from_method_index(self, Index::new(method_index), mc) .ok_or_else(|| "Method index does not exist".into()); let method: Method<'gc> = method?.into(); From 5d89d4ed857a3a0ff34cbcd6b72ce764c300c159 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 7 Jul 2020 21:28:24 -0400 Subject: [PATCH 163/189] Allow methods to not hold a body. Interface methods are specifically not allowed to be called: as a result, they don't get a method body. Existing code assumed a 1:1 relationship between methods and bodies, which causes spurious errors. --- core/src/avm2/activation.rs | 21 +++++++++++++++------ core/src/avm2/function.rs | 2 +- core/src/avm2/method.rs | 29 +++++++++++++++++++---------- core/src/avm2/object.rs | 2 +- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index e936a06c15db..ac2073707756 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -139,7 +139,10 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { ) -> Result { let method = script.read().init().into_bytecode()?; let scope = Some(Scope::push_scope(None, global, context.gc_context)); - let num_locals = method.body().num_locals; + let body: Result<_, Error> = method + .body() + .ok_or_else(|| "Cannot execute non-native method (for script) without body".into()); + let num_locals = body?.num_locals; let local_registers = GcCell::allocate(context.gc_context, RegisterSet::new(num_locals + 1)); @@ -171,8 +174,11 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { this: Option>, arguments: &[Value<'gc>], base_proto: Option>, - ) -> Self { - let num_locals = method.body().num_locals; + ) -> Result { + let body: Result<_, Error> = method + .body() + .ok_or_else(|| "Cannot execute non-native method without body".into()); + let num_locals = body?.num_locals; let num_declared_arguments = method.method().params.len() as u32; let local_registers = GcCell::allocate( context.gc_context, @@ -191,7 +197,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { } } - Self { + Ok(Self { avm2, this, arguments: None, @@ -201,7 +207,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { local_scope: ScriptObject::bare_object(context.gc_context), scope, base_proto, - } + }) } /// Execute a script initializer. @@ -380,7 +386,10 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { method: Gc<'gc, BytecodeMethod<'gc>>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { - let mut read = Reader::new(Cursor::new(method.body().code.as_ref())); + let body: Result<_, Error> = method + .body() + .ok_or_else(|| "Cannot execute non-native method without body".into()); + let mut read = Reader::new(Cursor::new(body?.code.as_ref())); loop { let result = self.do_next_opcode(method, context, &mut read); diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 75e43efbcab4..62e8be5b5a20 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -107,7 +107,7 @@ impl<'gc> Executable<'gc> { reciever, arguments, base_proto, - ); + )?; activation.run_actions(bm.method, context) } diff --git a/core/src/avm2/method.rs b/core/src/avm2/method.rs index 3d9e49d25e21..1ca6cbb5ac1f 100644 --- a/core/src/avm2/method.rs +++ b/core/src/avm2/method.rs @@ -50,7 +50,7 @@ pub struct BytecodeMethod<'gc> { pub abc_method: u32, /// The ABC method body this function uses. - pub abc_method_body: u32, + pub abc_method_body: Option, } impl<'gc> BytecodeMethod<'gc> { @@ -74,14 +74,22 @@ impl<'gc> BytecodeMethod<'gc> { txunit, abc: CollectWrapper(txunit.abc()), abc_method: abc_method.0, - abc_method_body: index as u32, + abc_method_body: Some(index as u32), }, )); } } } - None + Some(Gc::allocate( + mc, + Self { + txunit, + abc: CollectWrapper(txunit.abc()), + abc_method: abc_method.0, + abc_method_body: None, + }, + )) } /// Get the underlying ABC file. @@ -101,13 +109,14 @@ impl<'gc> BytecodeMethod<'gc> { } /// Get a reference to the ABC method body entry this refers to. - pub fn body(&self) -> &AbcMethodBody { - &self - .abc - .0 - .method_bodies - .get(self.abc_method_body as usize) - .unwrap() + /// + /// Some methods do not have bodies; this returns `None` in that case. + pub fn body(&self) -> Option<&AbcMethodBody> { + if let Some(abc_method_body) = self.abc_method_body { + self.abc.0.method_bodies.get(abc_method_body as usize) + } else { + None + } } } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 056da591d75e..4ecc5fd38d48 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -427,7 +427,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy avm_debug!( "Installing trait {:?} of kind {:?}", trait_name, - trait_entry.kind + trait_entry.kind() ); match trait_entry.kind() { From 8f2d3315f3340b458867389bc0b8043850515d3c Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 7 Jul 2020 22:10:23 -0400 Subject: [PATCH 164/189] Allow the construction of classes with no base class. This is primarily used for interfaces, in case you can't guess by the previous commit. --- core/src/avm2/activation.rs | 8 ++++- core/src/avm2/function.rs | 55 ++++++++++++++++++++-------------- core/src/avm2/object.rs | 31 +++++++++---------- core/src/avm2/script_object.rs | 18 +++++++++++ 4 files changed, 74 insertions(+), 38 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index ac2073707756..efa9a8104ecd 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -1338,7 +1338,13 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { - let base_class = self.avm2.pop().as_object()?; + let base_value = self.avm2.pop(); + let base_class = match base_value { + Value::Object(o) => Some(o), + Value::Null => None, + _ => return Err("Base class for new class is not Object or null.".into()), + }; + let class_entry = self.table_class(method, index, context)?; let scope = self.scope(); diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 62e8be5b5a20..08b54c11fb6d 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -7,7 +7,7 @@ use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::r#trait::Trait; use crate::avm2::scope::Scope; -use crate::avm2::script_object::{ScriptObjectClass, ScriptObjectData}; +use crate::avm2::script_object::{ScriptObject, ScriptObjectClass, ScriptObjectData}; use crate::avm2::value::Value; use crate::avm2::Error; use crate::context::UpdateContext; @@ -154,34 +154,45 @@ impl<'gc> FunctionObject<'gc> { /// This function returns both the class itself, and the static class /// initializer method that you should call before interacting with the /// class. The latter should be called using the former as a reciever. + /// + /// `base_class` is allowed to be `None`, corresponding to a `null` value + /// in the VM. This corresponds to no base class, and in practice appears + /// to be limited to interfaces (at least by the AS3 compiler in Animate + /// CC 2020.) pub fn from_class( activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, class: GcCell<'gc, Class<'gc>>, - mut base_class: Object<'gc>, + base_class: Option>, scope: Option>>, ) -> Result<(Object<'gc>, Object<'gc>), Error> { let class_read = class.read(); - let super_proto: Result, Error> = base_class - .get_property( - base_class, - &QName::new(Namespace::public_namespace(), "prototype"), - activation, - context, - )? - .as_object() - .map_err(|_| { - format!( - "Could not resolve superclass prototype {:?}", - class_read - .super_class_name() - .as_ref() - .map(|p| p.local_name()) - .unwrap_or(Some("Object")) - ) - .into() - }); - let mut class_proto = super_proto?.derive(activation, context, class, scope)?; + let mut class_proto = if let Some(mut base_class) = base_class { + let super_proto: Result<_, Error> = base_class + .get_property( + base_class, + &QName::new(Namespace::public_namespace(), "prototype"), + activation, + context, + )? + .as_object() + .map_err(|_| { + format!( + "Could not resolve superclass prototype {:?}", + class_read + .super_class_name() + .as_ref() + .map(|p| p.local_name()) + .unwrap_or(Some("Object")) + ) + .into() + }); + + super_proto?.derive(activation, context, class, scope)? + } else { + ScriptObject::bare_object(context.gc_context) + }; + let fn_proto = activation.avm2().prototypes().function; let class_constr_proto = activation.avm2().prototypes().class; diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 4ecc5fd38d48..cc734ed85e70 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -481,25 +481,26 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } TraitKind::Class { slot_id, class } => { let class_read = class.read(); - //TODO: what happens if this happens on a class defined as a - //class trait, without a superclass? How do we get `Object` - //then? - let super_name = if let Some(sc_name) = class_read.super_class_name() { - self.resolve_multiname(sc_name)? - .unwrap_or_else(|| QName::dynamic_name("Object")) + let super_class = if let Some(sc_name) = class_read.super_class_name() { + let super_name = self + .resolve_multiname(sc_name)? + .unwrap_or_else(|| QName::dynamic_name("Object")); + + let super_class: Result, Error> = self + .get_property(reciever, &super_name, activation, context)? + .as_object() + .map_err(|_e| { + format!("Could not resolve superclass {:?}", super_name.local_name()) + .into() + }); + + Some(super_class?) } else { - QName::dynamic_name("Object") + None }; - let super_class: Result, Error> = self - .get_property(reciever, &super_name, activation, context)? - .as_object() - .map_err(|_e| { - format!("Could not resolve superclass {:?}", super_name.local_name()).into() - }); - let (class_object, _cinit) = - FunctionObject::from_class(activation, context, *class, super_class?, scope)?; + FunctionObject::from_class(activation, context, *class, super_class, scope)?; self.install_const( context.gc_context, class_read.name().clone(), diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index fa06a05e6df1..58fe8d5f3bf3 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -338,6 +338,24 @@ impl<'gc> ScriptObject<'gc> { .into() } + /// Construct a bare class prototype with no base class. + /// + /// This appears to be used specifically for interfaces, which have no base + /// class. + pub fn bare_prototype( + mc: MutationContext<'gc, '_>, + class: GcCell<'gc, Class<'gc>>, + scope: Option>>, + ) -> Object<'gc> { + let script_class = ScriptObjectClass::InstancePrototype(class, scope); + + ScriptObject(GcCell::allocate( + mc, + ScriptObjectData::base_new(None, script_class), + )) + .into() + } + /// Construct an object with a prototype. pub fn object(mc: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> { ScriptObject(GcCell::allocate( From 563a515189d5abaa7570c88798d179d07a435c29 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 7 Jul 2020 22:11:00 -0400 Subject: [PATCH 165/189] Add a test for loading interfaces into the AVM2. This test will fail if the AVM2 implementation does not support bodyless methods or bare classes properly. --- core/tests/regression_tests.rs | 1 + core/tests/swfs/avm2/es4_interfaces/Test.as | 98 ++++++++++++++++++ .../tests/swfs/avm2/es4_interfaces/output.txt | 30 ++++++ core/tests/swfs/avm2/es4_interfaces/test.fla | Bin 0 -> 3985 bytes core/tests/swfs/avm2/es4_interfaces/test.swf | Bin 0 -> 1394 bytes 5 files changed, 129 insertions(+) create mode 100644 core/tests/swfs/avm2/es4_interfaces/Test.as create mode 100644 core/tests/swfs/avm2/es4_interfaces/output.txt create mode 100644 core/tests/swfs/avm2/es4_interfaces/test.fla create mode 100644 core/tests/swfs/avm2/es4_interfaces/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 1054639474bd..3ba9325bc40b 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -258,6 +258,7 @@ swf_tests! { (as3_if_stricteq, "avm2/if_stricteq", 1), (as3_if_strictne, "avm2/if_strictne", 1), (as3_strict_equality, "avm2/strict_equality", 1), + (as3_es4_interfaces, "avm2/es4_interfaces", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/es4_interfaces/Test.as b/core/tests/swfs/avm2/es4_interfaces/Test.as new file mode 100644 index 000000000000..37616d3465a5 --- /dev/null +++ b/core/tests/swfs/avm2/es4_interfaces/Test.as @@ -0,0 +1,98 @@ +package { + public class Test { + } +} + +interface ITest2 { + { + trace("TEST FAIL: ITest2 class constructor should not run"); + } + + function method(); + function method2(); +} + +class Test2 implements ITest2 { + { + trace("Class constructor"); + } + + function Test2() { + trace("Instance constructor"); + } + + public function method() { + trace("Instance method"); + } + + public function method2() { + trace("Instance method 2"); + } +} + +interface ITest3 extends ITest2 { + { + trace("TEST FAIL: ITest3 class constructor should not run"); + } + + function method3() +} + +class Test3 extends Test2 implements ITest3 { + { + trace("Child class constructor"); + } + + function Test3() { + trace("Child instance constructor pre-super"); + super(); + trace("Child instance constructor post-super"); + } + + public override function method() { + trace("Child instance method pre-super"); + super.method(); + trace("Child instance method post-super"); + } + + public function method3() { + trace("Child instance method3 pre-super"); + super.method(); + trace("Child instance method3 post-super"); + } +} + +class Test4 extends Test3 { + { + trace("Grandchild class constructor"); + } + + function Test4() { + trace("Grandchild instance constructor pre-super"); + super(); + trace("Grandchild instance constructor post-super"); + } + + public override function method2() { + trace("Grandchild instance method2 pre-super"); + super.method2(); + trace("Grandchild instance method2 post-super"); + } + + public override function method3() { + trace("Grandchild instance method3 pre-super"); + super.method3(); + trace("Grandchild instance method3 post-super"); + } +} + +trace("Script initializer"); +var x = new Test3(); +x.method(); +x.method2(); +x.method3(); + +var y = new Test4(); +y.method(); +y.method2(); +y.method3(); \ No newline at end of file diff --git a/core/tests/swfs/avm2/es4_interfaces/output.txt b/core/tests/swfs/avm2/es4_interfaces/output.txt new file mode 100644 index 000000000000..3b93ab9bbf0d --- /dev/null +++ b/core/tests/swfs/avm2/es4_interfaces/output.txt @@ -0,0 +1,30 @@ +Class constructor +Child class constructor +Grandchild class constructor +Script initializer +Child instance constructor pre-super +Instance constructor +Child instance constructor post-super +Child instance method pre-super +Instance method +Child instance method post-super +Instance method 2 +Child instance method3 pre-super +Instance method +Child instance method3 post-super +Grandchild instance constructor pre-super +Child instance constructor pre-super +Instance constructor +Child instance constructor post-super +Grandchild instance constructor post-super +Child instance method pre-super +Instance method +Child instance method post-super +Grandchild instance method2 pre-super +Instance method 2 +Grandchild instance method2 post-super +Grandchild instance method3 pre-super +Child instance method3 pre-super +Instance method +Child instance method3 post-super +Grandchild instance method3 post-super diff --git a/core/tests/swfs/avm2/es4_interfaces/test.fla b/core/tests/swfs/avm2/es4_interfaces/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..84376c02ef3abb67f80e2788b6e658e5112c93b8 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/es4_interfaces/test.swf b/core/tests/swfs/avm2/es4_interfaces/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..7ac3d13d97ff266721525311b92c1aac892b252f GIT binary patch literal 1394 zcmV-&1&#VcS5qsE3jhFkoRwDFcG^Z1oson_!rZUIF*d|Ft?dM4L~b}&fG;{Xe=gih<26&4+M+gP~ zL`cGDJ1iiCD$K`^AGeYSErm`~xs6<>muuOzw}oc|xwh5Wy1#RF|JN7to0kRjK0XI} zj1HQ5PtR;1^giaFqX2wDC{Mc=NPMkZc7fj3=q;~VIptotO2@^*6jDCX?M}b7{`JzL zG_7^J>=-ds;~Yp%DcqXU6?k#Vmonu`8TnGCd?~IkWzv^2=}VdPrCj0b0-q{bnk-qG zOf3~=EZ;bxtk@78wYysRm1gu_8z*|#=;?(^v~6l9`o{VfRO2W|>+`kaU-YK!{WLA_ zzOL}pd*odwWaQLy4u&S*jhn7ar7NR6RYy}TAEOeroOC_xq`neFk7H}s*t@(a9Ltu*VKBg z=Im!(9V0*gz-wPrUe35OOFG)Lp9l(kq0(NYB zP%ad-R{vPfH~S}rrw2-*D9d*W$EQZuHhSrcJ)Ixl)8)~D4`{8{4js9rm#+|yM?UxP z=x2J*wt~-Sw+#nLvzr~=^l!Uo%Kch^T?@xRYud({?jni*@RfeghRV9h9}G;x)|LN% zyz0ROmZF}xkd&?oX(=OJPb?=^q;HbT!jr@{hgl34>t7)_N^p#@3BpN)OA&TnOkjc~ zf?>Qs_(j4li3@X#f6!eJ# ziZEgpMFUwB3x$O_`0o(GC`tYE;L8v5B@h!AFgydxA74AI(0T6QfO2JU6AUspeupyHZUd1+!^i*$hiyQZ5rMKVdz3w2H_?!cPR7V zT=)>eyEJ?R!Y~!OUhcVGA`IpU^|Ax0yMW)P+UL|u6{0eY_CObrVgOMA|Axd{J z#Jox}hq%MSFsLS7ib;2Ph_k9cbI5kQFkDq7O7Ud-A9M};npoFjEn4ekYz7IPYn809tSfIn`;cGBpN zfBt@sPy%Do>+Z88#@@LgJjGLoRq-B1s}2XS!~&c+-+t=Shl+!9XxNU_mKcWtHpM`J z^`WdIYBnP`vU20P+*pyS^opHAq*o+ClZ Date: Tue, 7 Jul 2020 23:21:13 -0400 Subject: [PATCH 166/189] Add trait methods for getting and setting the interfaces list. --- core/src/avm2/function.rs | 8 ++++++++ core/src/avm2/object.rs | 6 ++++++ core/src/avm2/script_object.rs | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 08b54c11fb6d..02b9aa54325a 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -608,4 +608,12 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { ) { self.0.write(mc).base.install_const(name, id, value) } + + fn interfaces(&self) -> Vec> { + self.0.read().base.interfaces() + } + + fn set_interfaces(&self, context: MutationContext<'gc, '_>, iface_list: Vec>) { + self.0.write(context).base.set_interfaces(iface_list) + } } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index cc734ed85e70..4f8434fa17ab 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -616,6 +616,12 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// primitive value. Typically, this would be a number of some kind. fn value_of(&self) -> Result, Error>; + /// Enumerate all interfaces implemented by this object. + fn interfaces(&self) -> Vec>; + + /// Set the interface list for this object. + fn set_interfaces(&self, context: MutationContext<'gc, '_>, iface_list: Vec>); + /// Get a raw pointer value for this object. fn as_ptr(&self) -> *const ObjectPtr; diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 58fe8d5f3bf3..23ddaa60feee 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -68,6 +68,9 @@ pub struct ScriptObjectData<'gc> { /// Enumeratable property names. enumerants: Vec, + + /// Interfaces implemented by this object. (prototypes only) + interfaces: Vec>, } impl<'gc> TObject<'gc> for ScriptObject<'gc> { @@ -323,6 +326,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { ) { self.0.write(mc).install_const(name, id, value) } + + fn interfaces(&self) -> Vec> { + self.0.read().interfaces() + } + + fn set_interfaces(&self, context: MutationContext<'gc, '_>, iface_list: Vec>) { + self.0.write(context).set_interfaces(iface_list) + } } impl<'gc> ScriptObject<'gc> { @@ -391,6 +402,7 @@ impl<'gc> ScriptObjectData<'gc> { proto, class: trait_source, enumerants: Vec::new(), + interfaces: Vec::new(), } } @@ -871,4 +883,14 @@ impl<'gc> ScriptObjectData<'gc> { } } } + + /// Enumerate all interfaces implemented by this object. + pub fn interfaces(&self) -> Vec> { + self.interfaces.clone() + } + + /// Set the interface list for this object. + pub fn set_interfaces(&mut self, iface_list: Vec>) { + self.interfaces = iface_list; + } } From 3fae8b3832cfaea2566aec80b2339e961c5a1528 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 7 Jul 2020 23:52:29 -0400 Subject: [PATCH 167/189] Read the interface list when instantiating classes, resolve them, and stick them in the prototype for later use. --- core/src/avm2/class.rs | 4 ++++ core/src/avm2/function.rs | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 9edaae217069..93c87790cc61 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -314,4 +314,8 @@ impl<'gc> Class<'gc> { pub fn class_init(&self) -> Method<'gc> { self.class_init.clone() } + + pub fn interfaces(&self) -> &[Multiname] { + &self.interfaces + } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 02b9aa54325a..4365310c5a50 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -193,6 +193,28 @@ impl<'gc> FunctionObject<'gc> { ScriptObject::bare_object(context.gc_context) }; + let mut interfaces = Vec::new(); + let interface_names = class.read().interfaces().to_vec(); + for interface_name in interface_names { + let interface = if let Some(scope) = scope { + scope + .write(context.gc_context) + .resolve(&interface_name, activation, context)? + } else { + None + }; + + if interface.is_none() { + return Err(format!("Could not resolve interface {:?}", interface_name).into()); + } + + interfaces.push(interface.unwrap().as_object()?); + } + + if !interfaces.is_empty() { + class_proto.set_interfaces(context.gc_context, interfaces); + } + let fn_proto = activation.avm2().prototypes().function; let class_constr_proto = activation.avm2().prototypes().class; From a0cb0527953c721f55e73cdbdf862bf917f3672b Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 8 Jul 2020 22:30:03 -0400 Subject: [PATCH 168/189] Add `is_instance_of` trait method to `TObject` --- core/src/avm2/object.rs | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 4f8434fa17ab..26ece6b4f958 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -622,6 +622,50 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Set the interface list for this object. fn set_interfaces(&self, context: MutationContext<'gc, '_>, iface_list: Vec>); + /// Determine if this object is an instance of a given type. + /// + /// The given object should be the constructor for the given type we are + /// checking against this object. It's prototype will be searched in the + /// prototype chain of this object. If `check_interfaces` is enabled, then + /// the interfaces listed on each prototype will also be checked. + #[allow(unused_mut)] //it's not unused + fn is_instance_of( + &self, + activation: &mut Activation<'_, 'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + mut constructor: Object<'gc>, + check_interfaces: bool, + ) -> Result { + let type_proto = constructor + .get_property( + constructor, + &QName::dynamic_name("prototype"), + activation, + context, + )? + .as_object()?; + let mut my_proto = self.proto(); + + //TODO: Is it a verification error to do `obj instanceof bare_object`? + while let Some(proto) = my_proto { + if Object::ptr_eq(proto, type_proto) { + return Ok(true); + } + + if check_interfaces { + for interface in proto.interfaces() { + if Object::ptr_eq(interface, type_proto) { + return Ok(true); + } + } + } + + my_proto = proto.proto() + } + + Ok(false) + } + /// Get a raw pointer value for this object. fn as_ptr(&self) -> *const ObjectPtr; From c917b3d411514c89e28061bc6835eef7edf1b55d Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 8 Jul 2020 22:30:29 -0400 Subject: [PATCH 169/189] Implement `istype`, `istypelate`, and `instanceof`. --- core/src/avm2/activation.rs | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index efa9a8104ecd..9d5367d6db54 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -493,6 +493,9 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { } => self.op_has_next_2(context, object_register, index_register), Op::NextName => self.op_next_name(), Op::NextValue => self.op_next_value(context), + Op::IsType { index } => self.op_is_type(method, context, index), + Op::IsTypeLate => self.op_is_type_late(context), + Op::InstanceOf => self.op_instance_of(context), Op::Label => Ok(FrameControl::Continue), Op::Debug { is_local_register, @@ -1525,6 +1528,63 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { Ok(FrameControl::Continue) } + fn op_is_type( + &mut self, + method: Gc<'gc, BytecodeMethod<'gc>>, + context: &mut UpdateContext<'_, 'gc, '_>, + type_name_index: Index, + ) -> Result, Error> { + let value = self.avm2.pop().as_object()?; + + let type_name = self.pool_multiname_static(method, type_name_index)?; + let type_object = if let Some(scope) = self.scope() { + scope.read().find(&type_name, self, context)? + } else { + None + }; + + if let Some(type_object) = type_object { + let is_instance_of = value.is_instance_of(self, context, type_object, true)?; + self.avm2.push(is_instance_of); + } else { + return Err(format!( + "Attempted to check against nonexistent type {:?}", + type_name + ) + .into()); + } + + Ok(FrameControl::Continue) + } + + fn op_is_type_late( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + let type_object = self.avm2.pop().as_object()?; + let value = self.avm2.pop().as_object()?; + + let is_instance_of = value.is_instance_of(self, context, type_object, true)?; + + self.avm2.push(is_instance_of); + + Ok(FrameControl::Continue) + } + + fn op_instance_of( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error> { + let type_object = self.avm2.pop().as_object()?; + let value = self.avm2.pop().as_object()?; + + let is_instance_of = value.is_instance_of(self, context, type_object, false)?; + + self.avm2.push(is_instance_of); + + Ok(FrameControl::Continue) + } + #[allow(unused_variables)] fn op_debug( &mut self, From 39a4566f201aaa04a13c8553b5be5ef7b1d342ee Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 8 Jul 2020 22:31:00 -0400 Subject: [PATCH 170/189] Instances should be listed as their prototypes (empty as they are) --- core/src/avm2/function.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 4365310c5a50..e81857db4e2d 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -208,7 +208,17 @@ impl<'gc> FunctionObject<'gc> { return Err(format!("Could not resolve interface {:?}", interface_name).into()); } - interfaces.push(interface.unwrap().as_object()?); + let mut interface = interface.unwrap().as_object()?; + let iface_proto = interface + .get_property( + interface, + &QName::new(Namespace::public_namespace(), "prototype"), + activation, + context, + )? + .as_object()?; + + interfaces.push(iface_proto); } if !interfaces.is_empty() { From 64e5b462590760e41aecc30396b28d4f738bb0f9 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Wed, 8 Jul 2020 22:35:20 -0400 Subject: [PATCH 171/189] Add tests for `instanceof` and `is` operators. --- core/tests/regression_tests.rs | 2 + core/tests/swfs/avm2/instanceof/Test.as | 100 +++++++++++++++++++++ core/tests/swfs/avm2/instanceof/output.txt | 24 +++++ core/tests/swfs/avm2/instanceof/test.fla | Bin 0 -> 3985 bytes core/tests/swfs/avm2/instanceof/test.swf | Bin 0 -> 1380 bytes core/tests/swfs/avm2/istype/Test.as | 100 +++++++++++++++++++++ core/tests/swfs/avm2/istype/output.txt | 24 +++++ core/tests/swfs/avm2/istype/test.fla | Bin 0 -> 3985 bytes core/tests/swfs/avm2/istype/test.swf | Bin 0 -> 1379 bytes 9 files changed, 250 insertions(+) create mode 100644 core/tests/swfs/avm2/instanceof/Test.as create mode 100644 core/tests/swfs/avm2/instanceof/output.txt create mode 100644 core/tests/swfs/avm2/instanceof/test.fla create mode 100644 core/tests/swfs/avm2/instanceof/test.swf create mode 100644 core/tests/swfs/avm2/istype/Test.as create mode 100644 core/tests/swfs/avm2/istype/output.txt create mode 100644 core/tests/swfs/avm2/istype/test.fla create mode 100644 core/tests/swfs/avm2/istype/test.swf diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 3ba9325bc40b..d64b30258b36 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -259,6 +259,8 @@ swf_tests! { (as3_if_strictne, "avm2/if_strictne", 1), (as3_strict_equality, "avm2/strict_equality", 1), (as3_es4_interfaces, "avm2/es4_interfaces", 1), + (as3_istype, "avm2/istype", 1), + (as3_instanceof, "avm2/instanceof", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/instanceof/Test.as b/core/tests/swfs/avm2/instanceof/Test.as new file mode 100644 index 000000000000..cc0881d7a7f0 --- /dev/null +++ b/core/tests/swfs/avm2/instanceof/Test.as @@ -0,0 +1,100 @@ +package { + public class Test { + } +} + +interface ITest2 { + function method(); + function method2(); +} + +class Test2 implements ITest2 { + function Test2() { + } + + public function method() { + trace("Instance method"); + } + + public function method2() { + trace("Instance method 2"); + } +} + +interface ITest3 extends ITest2 { + function method3() +} + +class Test3 extends Test2 implements ITest3 { + function Test3() { + } + + public override function method() { + trace("Child instance method pre-super"); + super.method(); + trace("Child instance method post-super"); + } + + public function method3() { + trace("Child instance method3 pre-super"); + super.method(); + trace("Child instance method3 post-super"); + } +} + +class Test4 extends Test3 { + function Test4() { + } + + public override function method2() { + trace("Grandchild instance method2 pre-super"); + super.method2(); + trace("Grandchild instance method2 post-super"); + } + + public override function method3() { + trace("Grandchild instance method3 pre-super"); + super.method3(); + trace("Grandchild instance method3 post-super"); + } +} + +var x = new Test3(); + +trace("//x instanceof Object"); +trace(x instanceof Object); + +trace("//x instanceof Test2"); +trace(x instanceof Test2); + +trace("//x instanceof ITest2"); +trace(x instanceof ITest2); + +trace("//x instanceof Test3"); +trace(x instanceof Test3); + +trace("//x instanceof ITest3"); +trace(x instanceof ITest3); + +trace("//x instanceof Test4"); +trace(x instanceof Test4); + +var y = new Test4(); + +trace("//y instanceof Object"); +trace(y instanceof Object); + +trace("//y instanceof Test2"); +trace(y instanceof Test2); + +trace("//y instanceof ITest2"); +trace(y instanceof ITest2); + +trace("//y instanceof Test3"); +trace(y instanceof Test3); + +trace("//y instanceof ITest3"); +trace(y instanceof ITest3); + +trace("//y instanceof Test4"); +trace(y instanceof Test4); \ No newline at end of file diff --git a/core/tests/swfs/avm2/instanceof/output.txt b/core/tests/swfs/avm2/instanceof/output.txt new file mode 100644 index 000000000000..ce3c28823a2a --- /dev/null +++ b/core/tests/swfs/avm2/instanceof/output.txt @@ -0,0 +1,24 @@ +//x instanceof Object +true +//x instanceof Test2 +true +//x instanceof ITest2 +false +//x instanceof Test3 +true +//x instanceof ITest3 +false +//x instanceof Test4 +false +//y instanceof Object +true +//y instanceof Test2 +true +//y instanceof ITest2 +false +//y instanceof Test3 +true +//y instanceof ITest3 +false +//y instanceof Test4 +true diff --git a/core/tests/swfs/avm2/instanceof/test.fla b/core/tests/swfs/avm2/instanceof/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..84376c02ef3abb67f80e2788b6e658e5112c93b8 GIT binary patch literal 3985 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/instanceof/test.swf b/core/tests/swfs/avm2/instanceof/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..c3074ac4ced9de302f9b98e962b08827b2e0ba96 GIT binary patch literal 1380 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 literal 0 HcmV?d00001 diff --git a/core/tests/swfs/avm2/istype/test.swf b/core/tests/swfs/avm2/istype/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..2e62a627a2c951e017f39c7b64e9b21c7cb06d20 GIT binary patch literal 1379 zcmV-p1)TarS5qry3jhFkoQ;*+cG^Z1hG!(9L9#$#PO@WSh{?fr0vM6T!EH`Bc5vf3 zD{&jzq6P|Lp%!BbvJ-CmBz=cGKwg1f^c8xI@4HsIM*}eiJ2guq?fvaN|NKY;dXHRh z5mNs|NW*B;??wpaxlf-yJ&7Q+=sTKAtR>!c66-Gyti4yw`hM%p zwdK=gMfSM-Q_oRkPRP)y%YnYhXTzrJQ`7a~Jl&b6PtIdFJBhRIk#%Lw~e$nFh(k9&F{SIQsO%u?s5UT>PE>S4z$(MjD>_g%ePsvI9= zN>q1(CXTiR@IEL@8O@wW0}Q#!)n%I)&y^N%sbtqw%LhYP(SS2 zUuw`#72+2)e3rPr-Kn%{)$`gir#d&j*mW5({ZeA%&OE8q`UTG|+!w+MY#RPK_2LTV_ zE>3{Jg^?g~k_!{UxiNA}7)dgMRKYVC%_yQou*b`}<4E;lh?xiNePUO-qB5GM|uL4y$pmCrvpmEShQH^W;7j86I zf;3Ij1T^|ajW?k2CREmF<(AX93~7d@E70ht>#!wfF%}73G6{2%bjcLZ00)#RtpUxz zRGm)M`!6Ik$U>T>>0M|HjvDVl<2qFC(#rk*3ojbvApMG_8_*cy!Q8)GKMlQ{5iWD- zGFL>=28LWTNUVq(JcKhwgq65xLxfy2Jh39*kReV($j-o3H zTm}BXm^YLi1yurrN8sWfb@A8;l>=q&1C8?l*J-fnK+%C)0PoXa+kw0T3jlL8*m0oZ zz~=xrXt3)**@1fizox-U2dWNyWn3}T9TioAgMFxfL)mvw4-SpI0r-FhMF%n?KLUJ6 zgP$D8j2HkP(cotXG9zVxk7-bGATv@0_=E;E2dW>oLimrr|9XZ{7-LCh&pm>%JOkVH zBtPH{yo1s5Kme@qfWywOz*snWI=~6kug~r+a03pia~ue)_R}V!cRiXeUrm=6)8$yY l98Z`1a1_NT`!swf&j%Vzc^H@<{uiA4$BZ69 Date: Fri, 10 Jul 2020 19:25:43 -0400 Subject: [PATCH 172/189] Allow borrowing string values from an ABC file instead of cloning everything. --- core/src/avm2/activation.rs | 2 +- core/src/avm2/names.rs | 32 +++++++++++++++++++------------- core/src/avm2/value.rs | 13 ++++++++----- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 9d5367d6db54..59b4642c9263 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -327,7 +327,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, ) -> Result { - value::abc_string(&method.abc(), index) + Ok(value::abc_string(&method.abc(), index)?.to_string()) } /// Retrieve a namespace from the current constant pool. diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 61f06021d2a0..cb42f4661966 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -41,17 +41,23 @@ impl Namespace { .ok_or_else(|| format!("Unknown namespace constant {}", namespace_index.0).into()); Ok(match abc_namespace? { - AbcNamespace::Namespace(idx) => Self::Namespace(abc_string(file, idx.clone())?), - AbcNamespace::Package(idx) => Self::Package(abc_string(file, idx.clone())?), + AbcNamespace::Namespace(idx) => { + Self::Namespace(abc_string(file, idx.clone())?.to_string()) + } + AbcNamespace::Package(idx) => Self::Package(abc_string(file, idx.clone())?.to_string()), AbcNamespace::PackageInternal(idx) => { - Self::PackageInternal(abc_string(file, idx.clone())?) + Self::PackageInternal(abc_string(file, idx.clone())?.to_string()) + } + AbcNamespace::Protected(idx) => { + Self::Protected(abc_string(file, idx.clone())?.to_string()) + } + AbcNamespace::Explicit(idx) => { + Self::Explicit(abc_string(file, idx.clone())?.to_string()) } - AbcNamespace::Protected(idx) => Self::Protected(abc_string(file, idx.clone())?), - AbcNamespace::Explicit(idx) => Self::Explicit(abc_string(file, idx.clone())?), AbcNamespace::StaticProtected(idx) => { - Self::StaticProtected(abc_string(file, idx.clone())?) + Self::StaticProtected(abc_string(file, idx.clone())?.to_string()) } - AbcNamespace::Private(idx) => Self::Private(abc_string(file, idx.clone())?), + AbcNamespace::Private(idx) => Self::Private(abc_string(file, idx.clone())?.to_string()), }) } @@ -131,7 +137,7 @@ impl QName { Ok(match abc_multiname? { AbcMultiname::QName { namespace, name } => Self { ns: Namespace::from_abc_namespace(file, namespace.clone())?, - name: abc_string(file, name.clone())?, + name: abc_string(file, name.clone())?.to_string(), }, _ => return Err("Attempted to pull QName from non-QName multiname".into()), }) @@ -210,14 +216,14 @@ impl Multiname { AbcMultiname::QName { namespace, name } | AbcMultiname::QNameA { namespace, name } => { Self { ns: vec![Namespace::from_abc_namespace(file, namespace.clone())?], - name: abc_string_option(file, name.clone())?, + name: abc_string_option(file, name.clone())?.map(|s| s.to_string()), } } AbcMultiname::RTQName { name } | AbcMultiname::RTQNameA { name } => { let ns = avm.pop().as_namespace()?.clone(); Self { ns: vec![ns], - name: abc_string_option(file, name.clone())?, + name: abc_string_option(file, name.clone())?.map(|s| s.to_string()), } } AbcMultiname::RTQNameL | AbcMultiname::RTQNameLA => { @@ -237,7 +243,7 @@ impl Multiname { name, } => Self { ns: Self::abc_namespace_set(file, namespace_set.clone())?, - name: abc_string_option(file, name.clone())?, + name: abc_string_option(file, name.clone())?.map(|s| s.to_string()), }, AbcMultiname::MultinameL { namespace_set } | AbcMultiname::MultinameLA { namespace_set } => { @@ -273,7 +279,7 @@ impl Multiname { AbcMultiname::QName { namespace, name } | AbcMultiname::QNameA { namespace, name } => { Self { ns: vec![Namespace::from_abc_namespace(file, namespace.clone())?], - name: abc_string_option(file, name.clone())?, + name: abc_string_option(file, name.clone())?.map(|s| s.to_string()), } } AbcMultiname::Multiname { @@ -285,7 +291,7 @@ impl Multiname { name, } => Self { ns: Self::abc_namespace_set(file, namespace_set.clone())?, - name: abc_string_option(file, name.clone())?, + name: abc_string_option(file, name.clone())?.map(|s| s.to_string()), }, _ => return Err(format!("Multiname {} is not static", multiname_index.0).into()), }) diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index ef92a4808817..c6d78e0bfdc4 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -176,15 +176,15 @@ pub fn abc_double(file: &AbcFile, index: Index) -> Result { /// Retrieve a string from an ABC constant pool, yielding `""` if the string /// is the zero string. -pub fn abc_string(file: &AbcFile, index: Index) -> Result { +pub fn abc_string<'a>(file: &'a AbcFile, index: Index) -> Result<&'a str, Error> { if index.0 == 0 { - return Ok("".to_string()); + return Ok(""); } file.constant_pool .strings .get(index.0 as usize - 1) - .cloned() + .map(|s| s.as_str()) .ok_or_else(|| format!("Unknown string constant {}", index.0).into()) } @@ -195,7 +195,10 @@ pub fn abc_string(file: &AbcFile, index: Index) -> Result /// should cause the runtime to halt. `None` indicates that the zero string is /// in use, which callers are free to interpret as necessary (although this /// usually means "any name"). -pub fn abc_string_option(file: &AbcFile, index: Index) -> Result, Error> { +pub fn abc_string_option<'a>( + file: &'a AbcFile, + index: Index, +) -> Result, Error> { if index.0 == 0 { return Ok(None); } @@ -203,7 +206,7 @@ pub fn abc_string_option(file: &AbcFile, index: Index) -> Result Date: Fri, 10 Jul 2020 23:58:41 -0400 Subject: [PATCH 173/189] `coerce_string` may return a static string, which we shouldn't clone. --- core/src/avm2/value.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index c6d78e0bfdc4..48729e77e096 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -4,6 +4,7 @@ use crate::avm2::names::Namespace; use crate::avm2::object::Object; use crate::avm2::Error; use gc_arena::Collect; +use std::borrow::Cow; use std::f64::NAN; use swf::avm2::types::{AbcFile, DefaultValue as AbcDefaultValue, Index}; @@ -257,12 +258,12 @@ impl<'gc> Value<'gc> { } /// Coerce a value into a string. - pub fn coerce_string(self) -> String { + pub fn coerce_string(self) -> Cow<'static, str> { match self { - Value::String(s) => s, - Value::Bool(true) => "true".to_string(), - Value::Bool(false) => "false".to_string(), - _ => "".to_string(), + Value::String(s) => Cow::Owned(s), + Value::Bool(true) => Cow::Borrowed("true"), + Value::Bool(false) => Cow::Borrowed("false"), + _ => Cow::Borrowed(""), } } From a406bdada2d128cb5da5920a140b8dc47a79488f Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 11 Jul 2020 00:18:45 -0400 Subject: [PATCH 174/189] Don't attempt to log debugging information if we're not in a debug build. --- core/src/avm2/activation.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 59b4642c9263..9a08cba4d176 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -1586,6 +1586,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { } #[allow(unused_variables)] + #[cfg(avm_debug)] fn op_debug( &mut self, method: Gc<'gc, BytecodeMethod<'gc>>, @@ -1606,6 +1607,19 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { } #[allow(unused_variables)] + #[cfg(not(avm_debug))] + fn op_debug( + &mut self, + method: Gc<'gc, BytecodeMethod<'gc>>, + is_local_register: bool, + register_name: Index, + register: u8, + ) -> Result, Error> { + Ok(FrameControl::Continue) + } + + #[allow(unused_variables)] + #[cfg(avm_debug)] fn op_debug_file( &mut self, method: Gc<'gc, BytecodeMethod<'gc>>, @@ -1618,6 +1632,16 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { Ok(FrameControl::Continue) } + #[allow(unused_variables)] + #[cfg(not(avm_debug))] + fn op_debug_file( + &mut self, + method: Gc<'gc, BytecodeMethod<'gc>>, + file_name: Index, + ) -> Result, Error> { + Ok(FrameControl::Continue) + } + #[allow(unused_variables)] fn op_debug_line(&mut self, line_num: u32) -> Result, Error> { avm_debug!("Line: {}", line_num); From 508fcd6e9e0f4486ccb9221e74e6b2988120a1f0 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 11 Jul 2020 13:19:38 -0400 Subject: [PATCH 175/189] `pool_string` should return a `Ref` just like `value::abc_string`. --- core/src/avm2/activation.rs | 27 ++++++++++++++++++++++----- core/src/avm2/method.rs | 6 ++++++ core/src/avm2/script.rs | 7 +++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 9a08cba4d176..a17527c308f2 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -13,6 +13,7 @@ use crate::avm2::{value, Avm2, Error}; use crate::context::UpdateContext; use gc_arena::{Collect, Gc, GcCell, MutationContext}; use smallvec::SmallVec; +use std::cell::Ref; use std::io::Cursor; use swf::avm2::read::Reader; use swf::avm2::types::{ @@ -322,12 +323,27 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { } /// Retrieve a string from the current constant pool. - fn pool_string( + fn pool_string<'b>( &self, - method: Gc<'gc, BytecodeMethod<'gc>>, + method: &'b BytecodeMethod<'gc>, index: Index, - ) -> Result { - Ok(value::abc_string(&method.abc(), index)?.to_string()) + ) -> Result, Error> { + let mut maybe_error = None; + let maybe_ref = Ref::map(method.abc_ref(), |abc| { + match value::abc_string(abc, index) { + Ok(s) => s, + Err(e) => { + maybe_error = Some(e); + "" + } + } + }); + + if let Some(err) = maybe_error { + Err(err) + } else { + Ok(maybe_ref) + } } /// Retrieve a namespace from the current constant pool. @@ -585,7 +601,8 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { method: Gc<'gc, BytecodeMethod<'gc>>, value: Index, ) -> Result, Error> { - self.avm2.push(self.pool_string(method, value)?); + self.avm2 + .push(self.pool_string(&method, value)?.to_string()); Ok(FrameControl::Continue) } diff --git a/core/src/avm2/method.rs b/core/src/avm2/method.rs index 1ca6cbb5ac1f..b5f1e821a323 100644 --- a/core/src/avm2/method.rs +++ b/core/src/avm2/method.rs @@ -7,6 +7,7 @@ use crate::avm2::value::Value; use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::{Collect, CollectionContext, Gc, MutationContext}; +use std::cell::Ref; use std::fmt; use std::rc::Rc; use swf::avm2::types::{AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody}; @@ -98,6 +99,11 @@ impl<'gc> BytecodeMethod<'gc> { self.txunit.abc() } + /// Retrieve a reference to the underlying ABC file. + pub fn abc_ref(&self) -> Ref { + self.txunit.abc_ref() + } + /// Get the underlying translation unit this method was defined in. pub fn translation_unit(&self) -> TranslationUnit<'gc> { self.txunit diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index c17dcc73c5b6..c579bc60e313 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -5,6 +5,7 @@ use crate::avm2::method::{BytecodeMethod, Method}; use crate::avm2::r#trait::Trait; use crate::avm2::Error; use gc_arena::{Collect, Gc, GcCell, MutationContext}; +use std::cell::Ref; use std::collections::HashMap; use std::mem::drop; use std::rc::Rc; @@ -64,6 +65,12 @@ impl<'gc> TranslationUnit<'gc> { self.0.read().abc.0.clone() } + /// Retrieve a reference to the underlying `AbcFile` for this translation + /// unit. + pub fn abc_ref(&self) -> Ref { + Ref::map(self.0.read(), |tu| &*tu.abc.0) + } + /// Load a method from the ABC file and return it's method definition. pub fn load_method( self, From 2021cec9d36aa3085a526362418837c588a6a352 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 13 Jul 2020 22:09:34 -0400 Subject: [PATCH 176/189] Impl `Copy`, `Eq`, `PartialOrd`, `Ord`, and `Hash` for `AvmString`. --- core/src/avm1/string.rs | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/core/src/avm1/string.rs b/core/src/avm1/string.rs index 8b2e27de2141..391e0069e1c2 100644 --- a/core/src/avm1/string.rs +++ b/core/src/avm1/string.rs @@ -1,15 +1,17 @@ use gc_arena::{Collect, Gc, MutationContext}; +use std::cmp::{Eq, Ord, Ordering, PartialOrd}; use std::fmt; +use std::hash::{Hash, Hasher}; use std::ops::Deref; -#[derive(Debug, Clone, Collect)] +#[derive(Debug, Clone, Copy, Collect)] #[collect(no_drop)] enum Source<'gc> { Owned(Gc<'gc, String>), Static(&'static str), } -#[derive(Debug, Clone, Collect)] +#[derive(Debug, Clone, Copy, Collect)] #[collect(no_drop)] pub struct AvmString<'gc> { source: Source<'gc>, @@ -78,6 +80,29 @@ impl<'gc> PartialEq> for AvmString<'gc> { } } +impl<'gc> Eq for AvmString<'gc> {} + +impl<'gc> PartialOrd> for AvmString<'gc> { + fn partial_cmp(&self, other: &AvmString<'gc>) -> Option { + self.as_ref().partial_cmp(other.as_ref()) + } +} + +impl<'gc> Ord for AvmString<'gc> { + fn cmp(&self, other: &AvmString<'gc>) -> Ordering { + self.as_ref().cmp(other.as_ref()) + } +} + +impl<'gc> Hash for AvmString<'gc> { + fn hash(&self, state: &mut H) + where + H: Hasher, + { + self.as_ref().hash(state) + } +} + macro_rules! impl_eq { ($lhs:ty, $rhs: ty) => { #[allow(unused_lifetimes)] From 61a3ff8ae64a56b7bfce8c6ee8cbeb489a906f3e Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 13 Jul 2020 22:21:18 -0400 Subject: [PATCH 177/189] Replace `String` or `&str` references with `AvmString` everywhere in the AVM2 runtime. --- core/src/avm2/activation.rs | 84 ++++++++++------------- core/src/avm2/class.rs | 35 +++++----- core/src/avm2/function.rs | 68 ++++++++++-------- core/src/avm2/globals.rs | 13 ++-- core/src/avm2/globals/object.rs | 26 +++---- core/src/avm2/names.rs | 118 +++++++++++++++++--------------- core/src/avm2/object.rs | 70 ++++++++++--------- core/src/avm2/scope.rs | 4 +- core/src/avm2/script_object.rs | 115 +++++++++++++++++-------------- core/src/avm2/trait.rs | 18 ++--- core/src/avm2/value.rs | 76 ++++++++++++-------- 11 files changed, 340 insertions(+), 287 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index a17527c308f2..55fcab0a3dcf 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -1,5 +1,6 @@ //! Activation frames +use crate::avm1::AvmString; use crate::avm2::class::Class; use crate::avm2::function::FunctionObject; use crate::avm2::method::BytecodeMethod; @@ -13,7 +14,6 @@ use crate::avm2::{value, Avm2, Error}; use crate::context::UpdateContext; use gc_arena::{Collect, Gc, GcCell, MutationContext}; use smallvec::SmallVec; -use std::cell::Ref; use std::io::Cursor; use swf::avm2::read::Reader; use swf::avm2::types::{ @@ -327,23 +327,9 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { &self, method: &'b BytecodeMethod<'gc>, index: Index, - ) -> Result, Error> { - let mut maybe_error = None; - let maybe_ref = Ref::map(method.abc_ref(), |abc| { - match value::abc_string(abc, index) { - Ok(s) => s, - Err(e) => { - maybe_error = Some(e); - "" - } - } - }); - - if let Some(err) = maybe_error { - Err(err) - } else { - Ok(maybe_ref) - } + mc: MutationContext<'gc, '_>, + ) -> Result, Error> { + value::abc_string_copy(&method.abc_ref(), index, mc) } /// Retrieve a namespace from the current constant pool. @@ -351,8 +337,9 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { &self, method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, - ) -> Result { - Namespace::from_abc_namespace(&method.abc(), index) + mc: MutationContext<'gc, '_>, + ) -> Result, Error> { + Namespace::from_abc_namespace(&method.abc(), index, mc) } /// Retrieve a multiname from the current constant pool. @@ -360,8 +347,9 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { &mut self, method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, - ) -> Result { - Multiname::from_abc_multiname(&method.abc(), index, self.avm2) + mc: MutationContext<'gc, '_>, + ) -> Result, Error> { + Multiname::from_abc_multiname(&method.abc(), index, self.avm2, mc) } /// Retrieve a static, or non-runtime, multiname from the current constant @@ -370,8 +358,9 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { &mut self, method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, - ) -> Result { - Multiname::from_abc_multiname_static(&method.abc(), index) + mc: MutationContext<'gc, '_>, + ) -> Result, Error> { + Multiname::from_abc_multiname_static(&method.abc(), index, mc) } /// Retrieve a method entry from the current ABC file's method table. @@ -433,11 +422,11 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { Op::PushDouble { value } => self.op_push_double(method, value), Op::PushFalse => self.op_push_false(), Op::PushInt { value } => self.op_push_int(method, value), - Op::PushNamespace { value } => self.op_push_namespace(method, value), + Op::PushNamespace { value } => self.op_push_namespace(context, method, 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(method, value), + Op::PushString { value } => self.op_push_string(context, method, value), Op::PushTrue => self.op_push_true(), Op::PushUint { value } => self.op_push_uint(method, value), Op::PushUndefined => self.op_push_undefined(), @@ -574,10 +563,12 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_push_namespace( &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, method: Gc<'gc, BytecodeMethod<'gc>>, value: Index, ) -> Result, Error> { - self.avm2.push(self.pool_namespace(method, value)?); + self.avm2 + .push(self.pool_namespace(method, value, context.gc_context)?); Ok(FrameControl::Continue) } @@ -598,11 +589,12 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { fn op_push_string( &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, method: Gc<'gc, BytecodeMethod<'gc>>, value: Index, ) -> Result, Error> { self.avm2 - .push(self.pool_string(&method, value)?.to_string()); + .push(self.pool_string(&method, value, context.gc_context)?); Ok(FrameControl::Continue) } @@ -708,7 +700,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { arg_count: u32, ) -> Result, Error> { let args = self.avm2.pop_args(arg_count); - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; let mut receiver = self.avm2.pop().as_object()?; let name: Result = receiver .resolve_multiname(&multiname)? @@ -733,7 +725,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { arg_count: u32, ) -> Result, Error> { let args = self.avm2.pop_args(arg_count); - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; let mut receiver = self.avm2.pop().as_object()?; let name: Result = receiver .resolve_multiname(&multiname)? @@ -756,7 +748,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { arg_count: u32, ) -> Result, Error> { let args = self.avm2.pop_args(arg_count); - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; let mut receiver = self.avm2.pop().as_object()?; let name: Result = receiver .resolve_multiname(&multiname)? @@ -805,7 +797,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { arg_count: u32, ) -> Result, Error> { let args = self.avm2.pop_args(arg_count); - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; let receiver = self.avm2.pop().as_object()?; let name: Result = receiver .resolve_multiname(&multiname)? @@ -838,7 +830,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { arg_count: u32, ) -> Result, Error> { let args = self.avm2.pop_args(arg_count); - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; let receiver = self.avm2.pop().as_object()?; let name: Result = receiver .resolve_multiname(&multiname)? @@ -877,7 +869,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; let mut object = self.avm2.pop().as_object()?; let name: Result = object.resolve_multiname(&multiname)?.ok_or_else(|| { @@ -897,7 +889,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { index: Index, ) -> Result, Error> { let value = self.avm2.pop(); - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; let mut object = self.avm2.pop().as_object()?; if let Some(name) = object.resolve_multiname(&multiname)? { @@ -905,7 +897,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { } 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 + let local_name: Result, Error> = multiname .local_name() .ok_or_else(|| "Cannot set property using any name".into()); let name = QName::dynamic_name(local_name?); @@ -922,7 +914,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { index: Index, ) -> Result, Error> { let value = self.avm2.pop(); - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; let mut object = self.avm2.pop().as_object()?; if let Some(name) = object.resolve_multiname(&multiname)? { @@ -930,7 +922,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { } 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 + let local_name: Result, Error> = multiname .local_name() .ok_or_else(|| "Cannot set property using any name".into()); let name = QName::dynamic_name(local_name?); @@ -946,7 +938,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; let object = self.avm2.pop().as_object()?; if let Some(name) = object.resolve_multiname(&multiname)? { @@ -965,7 +957,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; let object = self.avm2.pop().as_object()?; let base_proto: Result, Error> = self .base_proto() @@ -996,7 +988,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { index: Index, ) -> Result, Error> { let value = self.avm2.pop(); - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; let object = self.avm2.pop().as_object()?; let base_proto: Result, Error> = self .base_proto() @@ -1100,7 +1092,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; avm_debug!("Resolving {:?}", multiname); let result = if let Some(scope) = self.scope() { scope.read().find(&multiname, self, context)? @@ -1120,7 +1112,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; avm_debug!("Resolving {:?}", multiname); let found: Result, Error> = if let Some(scope) = self.scope() { scope.read().find(&multiname, self, context)? @@ -1141,7 +1133,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { context: &mut UpdateContext<'_, 'gc, '_>, index: Index, ) -> Result, Error> { - let multiname = self.pool_multiname_static(method, index)?; + let multiname = self.pool_multiname_static(method, index, context.gc_context)?; avm_debug!("Resolving {:?}", multiname); let found: Result, Error> = if let Some(scope) = self.scope() { scope @@ -1235,7 +1227,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { arg_count: u32, ) -> Result, Error> { let args = self.avm2.pop_args(arg_count); - let multiname = self.pool_multiname(method, index)?; + let multiname = self.pool_multiname(method, index, context.gc_context)?; let mut source = self.avm2.pop().as_object()?; let ctor_name: Result = @@ -1553,7 +1545,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { ) -> Result, Error> { let value = self.avm2.pop().as_object()?; - let type_name = self.pool_multiname_static(method, type_name_index)?; + let type_name = self.pool_multiname_static(method, type_name_index, context.gc_context)?; let type_object = if let Some(scope) = self.scope() { scope.read().find(&type_name, self, context)? } else { diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 93c87790cc61..467f2193ea17 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -1,5 +1,6 @@ //! AVM2 classes +use crate::avm1::AvmString; use crate::avm2::method::Method; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::r#trait::{Trait, TraitKind}; @@ -13,10 +14,10 @@ use swf::avm2::types::{Class as AbcClass, Instance as AbcInstance}; #[collect(no_drop)] pub struct Class<'gc> { /// The name of the class. - name: QName, + name: QName<'gc>, /// The name of this class's superclass. - super_class: Option, + super_class: Option>, /// If this class is sealed (dynamic property writes should fail) is_sealed: bool, @@ -28,10 +29,10 @@ pub struct Class<'gc> { is_interface: bool, /// The namespace that protected traits of this class are stored into. - protected_namespace: Option, + protected_namespace: Option>, /// The list of interfaces this class implements. - interfaces: Vec, + interfaces: Vec>, /// The instance initializer for this class. /// @@ -66,7 +67,7 @@ pub struct Class<'gc> { /// /// TODO: This is an O(n^2) algorithm, it sucks. fn do_trait_lookup<'gc>( - name: &QName, + name: &QName<'gc>, known_traits: &mut Vec>, all_traits: &[Trait<'gc>], ) -> Result<(), Error> { @@ -119,18 +120,19 @@ impl<'gc> Class<'gc> { .ok_or_else(|| "LoadError: Instance index not valid".into()); let abc_instance = abc_instance?; - let name = QName::from_abc_multiname(&unit.abc(), abc_instance.name.clone())?; + let name = QName::from_abc_multiname(&unit.abc(), abc_instance.name.clone(), mc)?; let super_class = if abc_instance.super_name.0 == 0 { None } else { Some(Multiname::from_abc_multiname_static( &unit.abc(), abc_instance.super_name.clone(), + mc, )?) }; let protected_namespace = if let Some(ns) = &abc_instance.protected_namespace { - Some(Namespace::from_abc_namespace(&unit.abc(), ns.clone())?) + Some(Namespace::from_abc_namespace(&unit.abc(), ns.clone(), mc)?) } else { None }; @@ -140,6 +142,7 @@ impl<'gc> Class<'gc> { interfaces.push(Multiname::from_abc_multiname_static( &unit.abc(), interface_name.clone(), + mc, )?); } @@ -209,11 +212,11 @@ impl<'gc> Class<'gc> { Ok(()) } - pub fn name(&self) -> &QName { + pub fn name(&self) -> &QName<'gc> { &self.name } - pub fn super_class_name(&self) -> &Option { + pub fn super_class_name(&self) -> &Option> { &self.super_class } @@ -229,14 +232,14 @@ impl<'gc> Class<'gc> { /// returns an error. pub fn lookup_class_traits( &self, - name: &QName, + name: &QName<'gc>, known_traits: &mut Vec>, ) -> Result<(), Error> { do_trait_lookup(name, known_traits, &self.class_traits) } /// Determines if this class provides a given trait on itself. - pub fn has_class_trait(&self, name: &QName) -> bool { + pub fn has_class_trait(&self, name: &QName<'gc>) -> bool { for trait_entry in self.class_traits.iter() { if name == trait_entry.name() { return true; @@ -251,7 +254,7 @@ impl<'gc> Class<'gc> { /// /// TODO: Matching multiple namespaces with the same local name is at least /// claimed by the AVM2 specification to be a `VerifyError`. - pub fn resolve_any_class_trait(&self, local_name: &str) -> Option { + pub fn resolve_any_class_trait(&self, local_name: AvmString<'gc>) -> Option> { for trait_entry in self.class_traits.iter() { if local_name == trait_entry.name().local_name() { return Some(trait_entry.name().namespace().clone()); @@ -273,14 +276,14 @@ impl<'gc> Class<'gc> { /// returns an error. pub fn lookup_instance_traits( &self, - name: &QName, + name: &QName<'gc>, known_traits: &mut Vec>, ) -> Result<(), Error> { do_trait_lookup(name, known_traits, &self.instance_traits) } /// Determines if this class provides a given trait on it's instances. - pub fn has_instance_trait(&self, name: &QName) -> bool { + pub fn has_instance_trait(&self, name: &QName<'gc>) -> bool { for trait_entry in self.instance_traits.iter() { if name == trait_entry.name() { return true; @@ -295,7 +298,7 @@ impl<'gc> Class<'gc> { /// /// TODO: Matching multiple namespaces with the same local name is at least /// claimed by the AVM2 specification to be a `VerifyError`. - pub fn resolve_any_instance_trait(&self, local_name: &str) -> Option { + pub fn resolve_any_instance_trait(&self, local_name: AvmString<'gc>) -> Option> { for trait_entry in self.instance_traits.iter() { if local_name == trait_entry.name().local_name() { return Some(trait_entry.name().namespace().clone()); @@ -315,7 +318,7 @@ impl<'gc> Class<'gc> { self.class_init.clone() } - pub fn interfaces(&self) -> &[Multiname] { + pub fn interfaces(&self) -> &[Multiname<'gc>] { &self.interfaces } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index e81857db4e2d..53491977e409 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -1,5 +1,6 @@ //! AVM2 executables. +use crate::avm1::AvmString; use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::method::{BytecodeMethod, Method, NativeMethod}; @@ -183,7 +184,7 @@ impl<'gc> FunctionObject<'gc> { .super_class_name() .as_ref() .map(|p| p.local_name()) - .unwrap_or(Some("Object")) + .unwrap_or(Some("Object".into())) ) .into() }); @@ -344,7 +345,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn get_property_local( self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { @@ -359,7 +360,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn set_property_local( self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, value: Value<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -379,7 +380,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn init_property_local( self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, value: Value<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -396,11 +397,19 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { Ok(()) } - fn is_property_overwritable(self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool { + fn is_property_overwritable( + self, + gc_context: MutationContext<'gc, '_>, + name: &QName<'gc>, + ) -> bool { self.0.write(gc_context).base.is_property_overwritable(name) } - fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool { + fn delete_property( + &self, + gc_context: MutationContext<'gc, '_>, + multiname: &QName<'gc>, + ) -> bool { self.0.write(gc_context).base.delete_property(multiname) } @@ -430,13 +439,13 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_method(id) } - fn get_trait(self, name: &QName) -> Result>, Error> { + fn get_trait(self, name: &QName<'gc>) -> Result>, Error> { self.0.read().base.get_trait(name) } fn get_provided_trait( &self, - name: &QName, + name: &QName<'gc>, known_traits: &mut Vec>, ) -> Result<(), Error> { self.0.read().base.get_provided_trait(name, known_traits) @@ -446,35 +455,38 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.get_scope() } - fn resolve_any(self, local_name: &str) -> Result, Error> { + fn resolve_any(self, local_name: AvmString<'gc>) -> Result>, Error> { self.0.read().base.resolve_any(local_name) } - fn resolve_any_trait(self, local_name: &str) -> Result, Error> { + fn resolve_any_trait( + self, + local_name: AvmString<'gc>, + ) -> Result>, Error> { self.0.read().base.resolve_any_trait(local_name) } - fn has_own_property(self, name: &QName) -> Result { + fn has_own_property(self, name: &QName<'gc>) -> Result { self.0.read().base.has_own_property(name) } - fn has_trait(self, name: &QName) -> Result { + fn has_trait(self, name: &QName<'gc>) -> Result { self.0.read().base.has_trait(name) } - fn provides_trait(self, name: &QName) -> Result { + fn provides_trait(self, name: &QName<'gc>) -> Result { self.0.read().base.provides_trait(name) } - fn has_instantiated_property(self, name: &QName) -> bool { + fn has_instantiated_property(self, name: &QName<'gc>) -> bool { self.0.read().base.has_instantiated_property(name) } - fn has_own_virtual_getter(self, name: &QName) -> bool { + fn has_own_virtual_getter(self, name: &QName<'gc>) -> bool { self.0.read().base.has_own_virtual_getter(name) } - fn has_own_virtual_setter(self, name: &QName) -> bool { + fn has_own_virtual_setter(self, name: &QName<'gc>) -> bool { self.0.read().base.has_own_virtual_setter(name) } @@ -482,18 +494,18 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { self.0.read().base.proto() } - fn get_enumerant_name(&self, index: u32) -> Option { + fn get_enumerant_name(&self, index: u32) -> Option> { self.0.read().base.get_enumerant_name(index) } - fn property_is_enumerable(&self, name: &QName) -> bool { + fn property_is_enumerable(&self, name: &QName<'gc>) -> bool { self.0.read().base.property_is_enumerable(name) } fn set_local_property_is_enumerable( &self, mc: MutationContext<'gc, '_>, - name: &QName, + name: &QName<'gc>, is_enumerable: bool, ) -> Result<(), Error> { self.0 @@ -561,22 +573,22 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { .into()) } - fn to_string(&self) -> Result, Error> { + fn to_string(&self, mc: MutationContext<'gc, '_>) -> Result, Error> { if let ScriptObjectClass::ClassConstructor(class, ..) = self.0.read().base.class() { - Ok(format!("[class {}]", class.read().name().local_name()).into()) + Ok(AvmString::new(mc, format!("[class {}]", class.read().name().local_name())).into()) } else { Ok("function Function() {}".into()) } } - fn value_of(&self) -> Result, Error> { + fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result, Error> { Ok(Value::Object(Object::from(*self))) } fn install_method( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, disp_id: u32, function: Object<'gc>, ) { @@ -589,7 +601,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn install_getter( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, disp_id: u32, function: Object<'gc>, ) -> Result<(), Error> { @@ -602,7 +614,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn install_setter( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, disp_id: u32, function: Object<'gc>, ) -> Result<(), Error> { @@ -615,7 +627,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn install_dynamic_property( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, value: Value<'gc>, ) -> Result<(), Error> { self.0.write(mc).base.install_dynamic_property(name, value) @@ -624,7 +636,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn install_slot( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, id: u32, value: Value<'gc>, ) { @@ -634,7 +646,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn install_const( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, id: u32, value: Value<'gc>, ) { diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 5972ee9275eb..00a0d7cbe183 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -1,5 +1,6 @@ //! Global scope built-ins +use crate::avm1::AvmString; use crate::avm2::activation::Activation; use crate::avm2::function::FunctionObject; use crate::avm2::method::NativeMethod; @@ -43,8 +44,8 @@ pub struct SystemPrototypes<'gc> { fn function<'gc>( mc: MutationContext<'gc, '_>, mut global_scope: Object<'gc>, - package: &str, - name: &str, + package: impl Into>, + name: impl Into>, nf: NativeMethod<'gc>, fn_proto: Object<'gc>, ) { @@ -61,8 +62,8 @@ fn function<'gc>( fn class<'gc>( mc: MutationContext<'gc, '_>, mut global_scope: Object<'gc>, - package: &str, - name: &str, + package: impl Into>, + name: impl Into>, constr: NativeMethod<'gc>, proto: Object<'gc>, fn_proto: Object<'gc>, @@ -82,8 +83,8 @@ fn class<'gc>( fn constant<'gc>( mc: MutationContext<'gc, '_>, mut global_scope: Object<'gc>, - package: &str, - name: &str, + package: impl Into>, + name: impl Into>, value: Value<'gc>, ) { global_scope.install_const(mc, QName::new(Namespace::package(package), name), 0, value) diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index f1a1e14424be..45d0504d1ee1 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -22,35 +22,37 @@ pub fn constructor<'gc>( /// Implements `Object.prototype.toString` fn to_string<'gc>( _: &mut Activation<'_, 'gc>, - _: &mut UpdateContext<'_, 'gc, '_>, + context: &mut UpdateContext<'_, 'gc, '_>, this: Option>, _: &[Value<'gc>], ) -> Result, Error> { Ok(this - .map(|t| t.to_string()) + .map(|t| t.to_string(context.gc_context)) .unwrap_or(Ok(Value::Undefined))?) } /// Implements `Object.prototype.toLocaleString` fn to_locale_string<'gc>( _: &mut Activation<'_, 'gc>, - _: &mut UpdateContext<'_, 'gc, '_>, + context: &mut UpdateContext<'_, 'gc, '_>, this: Option>, _: &[Value<'gc>], ) -> Result, Error> { Ok(this - .map(|t| t.to_string()) + .map(|t| t.to_string(context.gc_context)) .unwrap_or(Ok(Value::Undefined))?) } /// Implements `Object.prototype.valueOf` fn value_of<'gc>( _: &mut Activation<'_, 'gc>, - _: &mut UpdateContext<'_, 'gc, '_>, + context: &mut UpdateContext<'_, 'gc, '_>, this: Option>, _: &[Value<'gc>], ) -> Result, Error> { - Ok(this.map(|t| t.value_of()).unwrap_or(Ok(Value::Undefined))?) + Ok(this + .map(|t| t.value_of(context.gc_context)) + .unwrap_or(Ok(Value::Undefined))?) } /// `Object.prototype.hasOwnProperty` @@ -65,9 +67,9 @@ pub fn has_own_property<'gc>( let name: Result<&Value<'gc>, Error> = args.get(0).ok_or_else(|| "No name specified".into()); let name = name?.as_string()?; - if let Some(ns) = this.resolve_any(&name)? { + if let Some(ns) = this.resolve_any(name)? { if !ns.is_private() { - let qname = QName::new(ns, &name); + let qname = QName::new(ns, name); return Ok(this.has_own_property(&qname)?.into()); } } @@ -110,9 +112,9 @@ pub fn property_is_enumerable<'gc>( let name: Result<&Value<'gc>, Error> = args.get(0).ok_or_else(|| "No name specified".into()); let name = name?.as_string()?; - if let Some(ns) = this.resolve_any(&name)? { + if let Some(ns) = this.resolve_any(name)? { if !ns.is_private() { - let qname = QName::new(ns, &name); + let qname = QName::new(ns, name); return Ok(this.property_is_enumerable(&qname).into()); } } @@ -137,9 +139,9 @@ pub fn set_property_is_enumerable<'gc>( .unwrap_or(Value::Bool(true)) .as_bool()?; - if let Some(ns) = this.resolve_any(&name)? { + if let Some(ns) = this.resolve_any(name)? { if !ns.is_private() { - let qname = QName::new(ns, &name); + let qname = QName::new(ns, name); this.set_local_property_is_enumerable(context.gc_context, &qname, is_enum)?; } } diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index cb42f4661966..e43bb0321e15 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -1,8 +1,9 @@ //! AVM2 names & namespacing -use crate::avm2::value::{abc_string, abc_string_option}; +use crate::avm1::AvmString; +use crate::avm2::value::{abc_string_copy, abc_string_option}; use crate::avm2::{Avm2, Error}; -use gc_arena::Collect; +use gc_arena::{Collect, MutationContext}; use swf::avm2::types::{ AbcFile, Index, Multiname as AbcMultiname, Namespace as AbcNamespace, NamespaceSet as AbcNamespaceSet, @@ -11,23 +12,24 @@ use swf::avm2::types::{ /// Represents the name of a namespace. #[derive(Clone, Collect, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[collect(no_drop)] -pub enum Namespace { - Namespace(String), - Package(String), - PackageInternal(String), - Protected(String), - Explicit(String), - StaticProtected(String), - Private(String), +pub enum Namespace<'gc> { + Namespace(AvmString<'gc>), + Package(AvmString<'gc>), + PackageInternal(AvmString<'gc>), + Protected(AvmString<'gc>), + Explicit(AvmString<'gc>), + StaticProtected(AvmString<'gc>), + Private(AvmString<'gc>), Any, } -impl Namespace { +impl<'gc> Namespace<'gc> { /// Read a namespace declaration from the ABC constant pool and copy it to /// a namespace value. pub fn from_abc_namespace( file: &AbcFile, namespace_index: Index, + mc: MutationContext<'gc, '_>, ) -> Result { if namespace_index.0 == 0 { return Ok(Self::Any); @@ -42,35 +44,33 @@ impl Namespace { Ok(match abc_namespace? { AbcNamespace::Namespace(idx) => { - Self::Namespace(abc_string(file, idx.clone())?.to_string()) + Self::Namespace(abc_string_copy(file, idx.clone(), mc)?) } - AbcNamespace::Package(idx) => Self::Package(abc_string(file, idx.clone())?.to_string()), + AbcNamespace::Package(idx) => Self::Package(abc_string_copy(file, idx.clone(), mc)?), AbcNamespace::PackageInternal(idx) => { - Self::PackageInternal(abc_string(file, idx.clone())?.to_string()) + Self::PackageInternal(abc_string_copy(file, idx.clone(), mc)?) } AbcNamespace::Protected(idx) => { - Self::Protected(abc_string(file, idx.clone())?.to_string()) - } - AbcNamespace::Explicit(idx) => { - Self::Explicit(abc_string(file, idx.clone())?.to_string()) + Self::Protected(abc_string_copy(file, idx.clone(), mc)?) } + AbcNamespace::Explicit(idx) => Self::Explicit(abc_string_copy(file, idx.clone(), mc)?), AbcNamespace::StaticProtected(idx) => { - Self::StaticProtected(abc_string(file, idx.clone())?.to_string()) + Self::StaticProtected(abc_string_copy(file, idx.clone(), mc)?) } - AbcNamespace::Private(idx) => Self::Private(abc_string(file, idx.clone())?.to_string()), + AbcNamespace::Private(idx) => Self::Private(abc_string_copy(file, idx.clone(), mc)?), }) } pub fn public_namespace() -> Self { - Namespace::Package("".to_string()) + Namespace::Package("".into()) } pub fn as3_namespace() -> Self { - Namespace::Namespace("http://adobe.com/AS3/2006/builtin".to_string()) + Namespace::Namespace("http://adobe.com/AS3/2006/builtin".into()) } - pub fn package(package_name: &str) -> Self { - Namespace::Package(package_name.to_string()) + pub fn package(package_name: impl Into>) -> Self { + Namespace::Package(package_name.into()) } pub fn is_any(&self) -> bool { @@ -99,23 +99,23 @@ impl Namespace { /// order. #[derive(Clone, Collect, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[collect(no_drop)] -pub struct QName { - ns: Namespace, - name: String, +pub struct QName<'gc> { + ns: Namespace<'gc>, + name: AvmString<'gc>, } -impl QName { - pub fn new(ns: Namespace, name: &str) -> Self { +impl<'gc> QName<'gc> { + pub fn new(ns: Namespace<'gc>, name: impl Into>) -> Self { Self { ns, - name: name.to_string(), + name: name.into(), } } - pub fn dynamic_name(local_part: &str) -> Self { + pub fn dynamic_name(local_part: impl Into>) -> Self { Self { ns: Namespace::public_namespace(), - name: local_part.to_string(), + name: local_part.into(), } } @@ -126,6 +126,7 @@ impl QName { pub fn from_abc_multiname( file: &AbcFile, multiname_index: Index, + mc: MutationContext<'gc, '_>, ) -> Result { let actual_index = multiname_index.0 as usize - 1; let abc_multiname: Result<&AbcMultiname, Error> = file @@ -136,18 +137,18 @@ impl QName { Ok(match abc_multiname? { AbcMultiname::QName { namespace, name } => Self { - ns: Namespace::from_abc_namespace(file, namespace.clone())?, - name: abc_string(file, name.clone())?.to_string(), + ns: Namespace::from_abc_namespace(file, namespace.clone(), mc)?, + name: abc_string_copy(file, name.clone(), mc)?, }, _ => return Err("Attempted to pull QName from non-QName multiname".into()), }) } - pub fn local_name(&self) -> &str { - &self.name + pub fn local_name(&self) -> AvmString<'gc> { + self.name } - pub fn namespace(&self) -> &Namespace { + pub fn namespace(&self) -> &Namespace<'gc> { &self.ns } } @@ -161,18 +162,19 @@ impl QName { /// The existence of a `name` of `None` indicates the `Any` name. #[derive(Clone, Debug, Collect)] #[collect(no_drop)] -pub struct Multiname { - ns: Vec, - name: Option, +pub struct Multiname<'gc> { + ns: Vec>, + name: Option>, } -impl Multiname { +impl<'gc> Multiname<'gc> { /// Read a namespace set from the ABC constant pool, and return a list of /// copied namespaces. fn abc_namespace_set( file: &AbcFile, namespace_set_index: Index, - ) -> Result, Error> { + mc: MutationContext<'gc, '_>, + ) -> Result>, Error> { if namespace_set_index.0 == 0 { //TODO: What is namespace set zero? return Ok(vec![]); @@ -189,7 +191,7 @@ impl Multiname { let mut result = vec![]; for ns in ns_set? { - result.push(Namespace::from_abc_namespace(file, ns.clone())?) + result.push(Namespace::from_abc_namespace(file, ns.clone(), mc)?) } Ok(result) @@ -200,7 +202,8 @@ impl Multiname { pub fn from_abc_multiname( file: &AbcFile, multiname_index: Index, - avm: &mut Avm2<'_>, + avm: &mut Avm2<'gc>, + mc: MutationContext<'gc, '_>, ) -> Result { let actual_index: Result = (multiname_index.0 as usize) .checked_sub(1) @@ -215,15 +218,15 @@ impl Multiname { Ok(match abc_multiname? { AbcMultiname::QName { namespace, name } | AbcMultiname::QNameA { namespace, name } => { Self { - ns: vec![Namespace::from_abc_namespace(file, namespace.clone())?], - name: abc_string_option(file, name.clone())?.map(|s| s.to_string()), + ns: vec![Namespace::from_abc_namespace(file, namespace.clone(), mc)?], + name: abc_string_option(file, name.clone(), mc)?, } } AbcMultiname::RTQName { name } | AbcMultiname::RTQNameA { name } => { let ns = avm.pop().as_namespace()?.clone(); Self { ns: vec![ns], - name: abc_string_option(file, name.clone())?.map(|s| s.to_string()), + name: abc_string_option(file, name.clone(), mc)?, } } AbcMultiname::RTQNameL | AbcMultiname::RTQNameLA => { @@ -242,14 +245,14 @@ impl Multiname { namespace_set, name, } => Self { - ns: Self::abc_namespace_set(file, namespace_set.clone())?, - name: abc_string_option(file, name.clone())?.map(|s| s.to_string()), + ns: Self::abc_namespace_set(file, namespace_set.clone(), mc)?, + name: abc_string_option(file, name.clone(), mc)?, }, AbcMultiname::MultinameL { namespace_set } | AbcMultiname::MultinameLA { namespace_set } => { let name = avm.pop().as_string()?.clone(); Self { - ns: Self::abc_namespace_set(file, namespace_set.clone())?, + ns: Self::abc_namespace_set(file, namespace_set.clone(), mc)?, name: Some(name), } } @@ -263,6 +266,7 @@ impl Multiname { pub fn from_abc_multiname_static( file: &AbcFile, multiname_index: Index, + mc: MutationContext<'gc, '_>, ) -> Result { let actual_index: Result = (multiname_index.0 as usize).checked_sub(1).ok_or_else(|| { @@ -278,8 +282,8 @@ impl Multiname { Ok(match abc_multiname? { AbcMultiname::QName { namespace, name } | AbcMultiname::QNameA { namespace, name } => { Self { - ns: vec![Namespace::from_abc_namespace(file, namespace.clone())?], - name: abc_string_option(file, name.clone())?.map(|s| s.to_string()), + ns: vec![Namespace::from_abc_namespace(file, namespace.clone(), mc)?], + name: abc_string_option(file, name.clone(), mc)?, } } AbcMultiname::Multiname { @@ -290,8 +294,8 @@ impl Multiname { namespace_set, name, } => Self { - ns: Self::abc_namespace_set(file, namespace_set.clone())?, - name: abc_string_option(file, name.clone())?.map(|s| s.to_string()), + ns: Self::abc_namespace_set(file, namespace_set.clone(), mc)?, + name: abc_string_option(file, name.clone(), mc)?, }, _ => return Err(format!("Multiname {} is not static", multiname_index.0).into()), }) @@ -305,11 +309,11 @@ impl Multiname { } } - pub fn namespace_set(&self) -> impl Iterator { + pub fn namespace_set(&self) -> impl Iterator> { self.ns.iter() } - pub fn local_name(&self) -> Option<&str> { - self.name.as_deref() + pub fn local_name(&self) -> Option> { + self.name } } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 26ece6b4f958..e39590822340 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1,5 +1,6 @@ //! AVM2 objects. +use crate::avm1::AvmString; use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::function::{Executable, FunctionObject}; @@ -30,7 +31,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn get_property_local( self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error>; @@ -39,7 +40,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn get_property( &mut self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { @@ -66,7 +67,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// /// This function returns `None` for non-trait properties, such as actually /// defined prototype methods for ES3-style classes. - fn get_base_proto(self, name: &QName) -> Result>, Error> { + fn get_base_proto(self, name: &QName<'gc>) -> Result>, Error> { if self.provides_trait(name)? { return Ok(Some(self.into())); } @@ -82,7 +83,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn set_property_local( self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, value: Value<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -92,7 +93,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn set_property( &mut self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, value: Value<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -126,7 +127,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn init_property_local( self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, value: Value<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -136,7 +137,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn init_property( &mut self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, value: Value<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -193,7 +194,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// This function returns `None` if no such trait exists, or the object /// does not have traits. It returns `Err` if *any* trait in the object is /// malformed in some way. - fn get_trait(self, name: &QName) -> Result>, Error>; + fn get_trait(self, name: &QName<'gc>) -> Result>, Error>; /// Populate a list of traits that this object provides. /// @@ -203,7 +204,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// has a given trait. fn get_provided_trait( &self, - name: &QName, + name: &QName<'gc>, known_traits: &mut Vec>, ) -> Result<(), Error>; @@ -217,7 +218,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Resolve a multiname into a single QName, if any of the namespaces /// match. - fn resolve_multiname(self, multiname: &Multiname) -> Result, Error> { + fn resolve_multiname(self, multiname: &Multiname<'gc>) -> Result>, Error> { for ns in multiname.namespace_set() { if ns.is_any() { if let Some(name) = multiname.local_name() { @@ -251,17 +252,18 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Trait names will be resolve on class constructors and object instances, /// but not prototypes. If you want to search a prototype's provided traits /// you must walk the prototype chain using `resolve_any_trait`. - fn resolve_any(self, local_name: &str) -> Result, Error>; + fn resolve_any(self, local_name: AvmString<'gc>) -> Result>, Error>; /// Given a local name of a trait, find the namespace it resides in, if any. /// /// This function only works for names which are trait properties, not /// dynamic or prototype properties. Furthermore, instance prototypes *will* /// resolve trait names here, contrary to their behavior in `resolve_any.` - fn resolve_any_trait(self, local_name: &str) -> Result, Error>; + fn resolve_any_trait(self, local_name: AvmString<'gc>) + -> Result>, Error>; /// Indicates whether or not a property exists on an object. - fn has_property(self, name: &QName) -> Result { + fn has_property(self, name: &QName<'gc>) -> Result { if self.has_own_property(name)? { Ok(true) } else if let Some(proto) = self.proto() { @@ -273,38 +275,42 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Indicates whether or not a property or trait exists on an object and is /// not part of the prototype chain. - fn has_own_property(self, name: &QName) -> Result; + fn has_own_property(self, name: &QName<'gc>) -> Result; /// Returns true if an object has one or more traits of a given name. - fn has_trait(self, name: &QName) -> Result; + fn has_trait(self, name: &QName<'gc>) -> Result; /// Returns true if an object is part of a class that defines a trait of a /// given name on itself (as opposed to merely inheriting a superclass /// trait.) - fn provides_trait(self, name: &QName) -> Result; + fn provides_trait(self, name: &QName<'gc>) -> Result; /// Indicates whether or not a property or *instantiated* trait exists on /// an object and is not part of the prototype chain. /// /// Unlike `has_own_property`, this will not yield `true` for traits this /// object can have but has not yet instantiated. - fn has_instantiated_property(self, name: &QName) -> bool; + fn has_instantiated_property(self, name: &QName<'gc>) -> bool; /// Check if a particular object contains a virtual getter by the given /// name. - fn has_own_virtual_getter(self, name: &QName) -> bool; + fn has_own_virtual_getter(self, name: &QName<'gc>) -> bool; /// Check if a particular object contains a virtual setter by the given /// name. - fn has_own_virtual_setter(self, name: &QName) -> bool; + fn has_own_virtual_setter(self, name: &QName<'gc>) -> bool; /// Indicates whether or not a property is overwritable. - fn is_property_overwritable(self, gc_context: MutationContext<'gc, '_>, _name: &QName) -> bool; + fn is_property_overwritable( + self, + gc_context: MutationContext<'gc, '_>, + _name: &QName<'gc>, + ) -> bool; /// Delete a named property from the object. /// /// Returns false if the property cannot be deleted. - fn delete_property(&self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool; + fn delete_property(&self, gc_context: MutationContext<'gc, '_>, name: &QName<'gc>) -> bool; /// Retrieve the `__proto__` of a given object. /// @@ -322,18 +328,18 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Objects are responsible for maintaining a consistently ordered and /// indexed list of enumerable names which can be queried by this /// mechanism. - fn get_enumerant_name(&self, index: u32) -> Option; + fn get_enumerant_name(&self, index: u32) -> Option>; /// Determine if a property is currently enumerable. /// /// Properties that do not exist are also not enumerable. - fn property_is_enumerable(&self, name: &QName) -> bool; + fn property_is_enumerable(&self, name: &QName<'gc>) -> bool; /// Mark a dynamic property on this object as enumerable. fn set_local_property_is_enumerable( &self, mc: MutationContext<'gc, '_>, - name: &QName, + name: &QName<'gc>, is_enumerable: bool, ) -> Result<(), Error>; @@ -341,7 +347,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn install_method( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, disp_id: u32, function: Object<'gc>, ); @@ -350,7 +356,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn install_getter( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, disp_id: u32, function: Object<'gc>, ) -> Result<(), Error>; @@ -359,7 +365,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn install_setter( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, disp_id: u32, function: Object<'gc>, ) -> Result<(), Error>; @@ -368,7 +374,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn install_dynamic_property( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, value: Value<'gc>, ) -> Result<(), Error>; @@ -376,7 +382,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn install_slot( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, id: u32, value: Value<'gc>, ); @@ -385,7 +391,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn install_const( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, id: u32, value: Value<'gc>, ); @@ -607,14 +613,14 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// coercions happen by defining `toString` in a downstream class or /// prototype; this is then picked up by the VM runtime when doing /// coercions. - fn to_string(&self) -> Result, Error>; + fn to_string(&self, mc: MutationContext<'gc, '_>) -> Result, Error>; /// Implement the result of calling `Object.prototype.valueOf` on this /// object class. /// /// `valueOf` is a method used to request an object be coerced to a /// primitive value. Typically, this would be a number of some kind. - fn value_of(&self) -> Result, Error>; + fn value_of(&self, mc: MutationContext<'gc, '_>) -> Result, Error>; /// Enumerate all interfaces implemented by this object. fn interfaces(&self) -> Vec>; diff --git a/core/src/avm2/scope.rs b/core/src/avm2/scope.rs index b7dac37cbb4a..37d594ec2bec 100644 --- a/core/src/avm2/scope.rs +++ b/core/src/avm2/scope.rs @@ -99,7 +99,7 @@ impl<'gc> Scope<'gc> { /// This function yields `None` if no such scope exists. pub fn find( &self, - name: &Multiname, + name: &Multiname<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result>, Error> { @@ -122,7 +122,7 @@ impl<'gc> Scope<'gc> { /// property's value. pub fn resolve( &mut self, - name: &Multiname, + name: &Multiname<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result>, Error> { diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 23ddaa60feee..b40a892a2530 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -1,5 +1,6 @@ //! Default AVM2 object impl +use crate::avm1::AvmString; use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::function::Executable; @@ -52,7 +53,7 @@ pub enum ScriptObjectClass<'gc> { #[collect(no_drop)] pub struct ScriptObjectData<'gc> { /// Properties stored on this object. - values: HashMap>, + values: HashMap, Property<'gc>>, /// Slots stored on this object. slots: Vec>, @@ -67,7 +68,7 @@ pub struct ScriptObjectData<'gc> { class: ScriptObjectClass<'gc>, /// Enumeratable property names. - enumerants: Vec, + enumerants: Vec>, /// Interfaces implemented by this object. (prototypes only) interfaces: Vec>, @@ -77,7 +78,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn get_property_local( self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { @@ -92,7 +93,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn set_property_local( self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, value: Value<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -110,7 +111,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn init_property_local( self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, value: Value<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -125,11 +126,15 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { Ok(()) } - fn is_property_overwritable(self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool { + fn is_property_overwritable( + self, + gc_context: MutationContext<'gc, '_>, + name: &QName<'gc>, + ) -> bool { self.0.write(gc_context).is_property_overwritable(name) } - fn delete_property(&self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool { + fn delete_property(&self, gc_context: MutationContext<'gc, '_>, name: &QName<'gc>) -> bool { self.0.write(gc_context).delete_property(name) } @@ -159,13 +164,13 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_method(id) } - fn get_trait(self, name: &QName) -> Result>, Error> { + fn get_trait(self, name: &QName<'gc>) -> Result>, Error> { self.0.read().get_trait(name) } fn get_provided_trait( &self, - name: &QName, + name: &QName<'gc>, known_traits: &mut Vec>, ) -> Result<(), Error> { self.0.read().get_provided_trait(name, known_traits) @@ -175,35 +180,38 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().get_scope() } - fn resolve_any(self, local_name: &str) -> Result, Error> { + fn resolve_any(self, local_name: AvmString<'gc>) -> Result>, Error> { self.0.read().resolve_any(local_name) } - fn resolve_any_trait(self, local_name: &str) -> Result, Error> { + fn resolve_any_trait( + self, + local_name: AvmString<'gc>, + ) -> Result>, Error> { self.0.read().resolve_any_trait(local_name) } - fn has_own_property(self, name: &QName) -> Result { + fn has_own_property(self, name: &QName<'gc>) -> Result { self.0.read().has_own_property(name) } - fn has_trait(self, name: &QName) -> Result { + fn has_trait(self, name: &QName<'gc>) -> Result { self.0.read().has_trait(name) } - fn provides_trait(self, name: &QName) -> Result { + fn provides_trait(self, name: &QName<'gc>) -> Result { self.0.read().provides_trait(name) } - fn has_instantiated_property(self, name: &QName) -> bool { + fn has_instantiated_property(self, name: &QName<'gc>) -> bool { self.0.read().has_instantiated_property(name) } - fn has_own_virtual_getter(self, name: &QName) -> bool { + fn has_own_virtual_getter(self, name: &QName<'gc>) -> bool { self.0.read().has_own_virtual_getter(name) } - fn has_own_virtual_setter(self, name: &QName) -> bool { + fn has_own_virtual_setter(self, name: &QName<'gc>) -> bool { self.0.read().has_own_virtual_setter(name) } @@ -211,18 +219,18 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0.read().proto } - fn get_enumerant_name(&self, index: u32) -> Option { + fn get_enumerant_name(&self, index: u32) -> Option> { self.0.read().get_enumerant_name(index) } - fn property_is_enumerable(&self, name: &QName) -> bool { + fn property_is_enumerable(&self, name: &QName<'gc>) -> bool { self.0.read().property_is_enumerable(name) } fn set_local_property_is_enumerable( &self, mc: MutationContext<'gc, '_>, - name: &QName, + name: &QName<'gc>, is_enumerable: bool, ) -> Result<(), Error> { self.0 @@ -260,18 +268,18 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { )) } - fn to_string(&self) -> Result, Error> { + fn to_string(&self, _mc: MutationContext<'gc, '_>) -> Result, Error> { Ok("[object Object]".into()) } - fn value_of(&self) -> Result, Error> { + fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result, Error> { Ok(Value::Object(Object::from(*self))) } fn install_method( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, disp_id: u32, function: Object<'gc>, ) { @@ -281,7 +289,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn install_getter( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, disp_id: u32, function: Object<'gc>, ) -> Result<(), Error> { @@ -291,7 +299,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn install_setter( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, disp_id: u32, function: Object<'gc>, ) -> Result<(), Error> { @@ -301,7 +309,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn install_dynamic_property( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, value: Value<'gc>, ) -> Result<(), Error> { self.0.write(mc).install_dynamic_property(name, value) @@ -310,7 +318,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn install_slot( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, id: u32, value: Value<'gc>, ) { @@ -320,7 +328,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn install_const( &mut self, mc: MutationContext<'gc, '_>, - name: QName, + name: QName<'gc>, id: u32, value: Value<'gc>, ) { @@ -409,7 +417,7 @@ impl<'gc> ScriptObjectData<'gc> { pub fn get_property_local( &self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error> { let prop = self.values.get(name); @@ -424,7 +432,7 @@ impl<'gc> ScriptObjectData<'gc> { pub fn set_property_local( &mut self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, value: Value<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -459,7 +467,7 @@ impl<'gc> ScriptObjectData<'gc> { pub fn init_property_local( &mut self, reciever: Object<'gc>, - name: &QName, + name: &QName<'gc>, value: Value<'gc>, activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, @@ -481,14 +489,14 @@ impl<'gc> ScriptObjectData<'gc> { } } - pub fn is_property_overwritable(&self, name: &QName) -> bool { + pub fn is_property_overwritable(&self, name: &QName<'gc>) -> bool { self.values .get(name) .map(|p| p.is_overwritable()) .unwrap_or(true) } - pub fn delete_property(&mut self, name: &QName) -> bool { + pub fn delete_property(&mut self, name: &QName<'gc>) -> bool { let can_delete = if let Some(prop) = self.values.get(name) { prop.can_delete() } else { @@ -544,7 +552,7 @@ impl<'gc> ScriptObjectData<'gc> { self.methods.get(id as usize).and_then(|v| *v) } - pub fn get_trait(&self, name: &QName) -> Result>, Error> { + pub fn get_trait(&self, name: &QName<'gc>) -> Result>, Error> { match &self.class { //Class constructors have local traits only. ScriptObjectClass::ClassConstructor(..) => { @@ -581,7 +589,7 @@ impl<'gc> ScriptObjectData<'gc> { pub fn get_provided_trait( &self, - name: &QName, + name: &QName<'gc>, known_traits: &mut Vec>, ) -> Result<(), Error> { match &self.class { @@ -595,7 +603,7 @@ impl<'gc> ScriptObjectData<'gc> { } } - pub fn has_trait(&self, name: &QName) -> Result { + pub fn has_trait(&self, name: &QName<'gc>) -> Result { match &self.class { //Class constructors have local traits only. ScriptObjectClass::ClassConstructor(..) => self.provides_trait(name), @@ -622,7 +630,7 @@ impl<'gc> ScriptObjectData<'gc> { } } - pub fn provides_trait(&self, name: &QName) -> Result { + pub fn provides_trait(&self, name: &QName<'gc>) -> Result { match &self.class { ScriptObjectClass::ClassConstructor(class, ..) => { Ok(class.read().has_class_trait(name)) @@ -642,7 +650,7 @@ impl<'gc> ScriptObjectData<'gc> { } } - pub fn resolve_any(&self, local_name: &str) -> Result, Error> { + pub fn resolve_any(&self, local_name: AvmString<'gc>) -> Result>, Error> { for (key, _value) in self.values.iter() { if key.local_name() == local_name { return Ok(Some(key.namespace().clone())); @@ -656,7 +664,10 @@ impl<'gc> ScriptObjectData<'gc> { } } - pub fn resolve_any_trait(&self, local_name: &str) -> Result, Error> { + pub fn resolve_any_trait( + &self, + local_name: AvmString<'gc>, + ) -> Result>, Error> { if let Some(proto) = self.proto { let proto_trait_name = proto.resolve_any_trait(local_name)?; if let Some(ns) = proto_trait_name { @@ -675,22 +686,22 @@ impl<'gc> ScriptObjectData<'gc> { } } - pub fn has_own_property(&self, name: &QName) -> Result { + pub fn has_own_property(&self, name: &QName<'gc>) -> Result { Ok(self.values.get(name).is_some() || self.has_trait(name)?) } - pub fn has_instantiated_property(&self, name: &QName) -> bool { + pub fn has_instantiated_property(&self, name: &QName<'gc>) -> bool { self.values.get(name).is_some() } - pub fn has_own_virtual_getter(&self, name: &QName) -> bool { + pub fn has_own_virtual_getter(&self, name: &QName<'gc>) -> bool { match self.values.get(name) { Some(Property::Virtual { get: Some(_), .. }) => true, _ => false, } } - pub fn has_own_virtual_setter(&self, name: &QName) -> bool { + pub fn has_own_virtual_setter(&self, name: &QName<'gc>) -> bool { match self.values.get(name) { Some(Property::Virtual { set: Some(_), .. }) => true, _ => false, @@ -701,7 +712,7 @@ impl<'gc> ScriptObjectData<'gc> { self.proto } - pub fn get_enumerant_name(&self, index: u32) -> Option { + pub fn get_enumerant_name(&self, index: u32) -> Option> { // NOTE: AVM2 object enumeration is one of the weakest parts of an // otherwise well-designed VM. Notably, because of the way they // implemented `hasnext` and `hasnext2`, all enumerants start from ONE. @@ -713,13 +724,13 @@ impl<'gc> ScriptObjectData<'gc> { self.enumerants.get(true_index).cloned() } - pub fn property_is_enumerable(&self, name: &QName) -> bool { + pub fn property_is_enumerable(&self, name: &QName<'gc>) -> bool { self.enumerants.contains(name) } pub fn set_local_property_is_enumerable( &mut self, - name: &QName, + name: &QName<'gc>, is_enumerable: bool, ) -> Result<(), Error> { if is_enumerable && self.values.contains_key(name) && !self.enumerants.contains(name) { @@ -750,7 +761,7 @@ impl<'gc> ScriptObjectData<'gc> { } /// Install a method into the object. - pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) { + pub fn install_method(&mut self, name: QName<'gc>, disp_id: u32, function: Object<'gc>) { if disp_id > 0 { if self.methods.len() <= disp_id as usize { self.methods @@ -770,7 +781,7 @@ impl<'gc> ScriptObjectData<'gc> { /// installing them in either order. pub fn install_getter( &mut self, - name: QName, + name: QName<'gc>, disp_id: u32, function: Object<'gc>, ) -> Result<(), Error> { @@ -805,7 +816,7 @@ impl<'gc> ScriptObjectData<'gc> { /// installing them in either order. pub fn install_setter( &mut self, - name: QName, + name: QName<'gc>, disp_id: u32, function: Object<'gc>, ) -> Result<(), Error> { @@ -835,7 +846,7 @@ impl<'gc> ScriptObjectData<'gc> { pub fn install_dynamic_property( &mut self, - name: QName, + name: QName<'gc>, value: Value<'gc>, ) -> Result<(), Error> { self.values @@ -849,7 +860,7 @@ impl<'gc> ScriptObjectData<'gc> { /// Slot number zero indicates a slot ID that is unknown and should be /// allocated by the VM - as far as I know, there is no way to discover /// slot IDs, so we don't allocate a slot for them at all. - pub fn install_slot(&mut self, name: QName, id: u32, value: Value<'gc>) { + pub fn install_slot(&mut self, name: QName<'gc>, id: u32, value: Value<'gc>) { if id == 0 { self.values.insert(name, Property::new_stored(value)); } else { @@ -869,7 +880,7 @@ impl<'gc> ScriptObjectData<'gc> { /// Slot number zero indicates a slot ID that is unknown and should be /// allocated by the VM - as far as I know, there is no way to discover /// slot IDs, so we don't allocate a slot for them at all. - pub fn install_const(&mut self, name: QName, id: u32, value: Value<'gc>) { + pub fn install_const(&mut self, name: QName<'gc>, id: u32, value: Value<'gc>) { if id == 0 { self.values.insert(name, Property::new_const(value)); } else { diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index 071d4291b386..981dd7ac7956 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -23,7 +23,7 @@ use swf::avm2::types::{Trait as AbcTrait, TraitKind as AbcTraitKind}; #[collect(no_drop)] pub struct Trait<'gc> { /// The name of this trait. - name: QName, + name: QName<'gc>, /// Whether or not traits in downstream classes are allowed to override /// this trait. @@ -48,7 +48,7 @@ pub enum TraitKind<'gc> { /// to. Slot { slot_id: u32, - type_name: Multiname, + type_name: Multiname<'gc>, default_value: Option>, }, @@ -75,7 +75,7 @@ pub enum TraitKind<'gc> { /// be overridden. Const { slot_id: u32, - type_name: Multiname, + type_name: Multiname<'gc>, default_value: Option>, }, } @@ -87,7 +87,7 @@ impl<'gc> Trait<'gc> { abc_trait: &AbcTrait, mc: MutationContext<'gc, '_>, ) -> Result { - let name = QName::from_abc_multiname(&unit.abc(), abc_trait.name.clone())?; + let name = QName::from_abc_multiname(&unit.abc(), abc_trait.name.clone(), mc)?; Ok(match &abc_trait.kind { AbcTraitKind::Slot { @@ -103,10 +103,10 @@ impl<'gc> Trait<'gc> { type_name: if type_name.0 == 0 { Multiname::any() } else { - Multiname::from_abc_multiname_static(&unit.abc(), type_name.clone())? + Multiname::from_abc_multiname_static(&unit.abc(), type_name.clone(), mc)? }, default_value: if let Some(dv) = value { - Some(abc_default_value(&unit.abc(), &dv)?) + Some(abc_default_value(&unit.abc(), &dv, mc)?) } else { None }, @@ -170,10 +170,10 @@ impl<'gc> Trait<'gc> { type_name: if type_name.0 == 0 { Multiname::any() } else { - Multiname::from_abc_multiname_static(&unit.abc(), type_name.clone())? + Multiname::from_abc_multiname_static(&unit.abc(), type_name.clone(), mc)? }, default_value: if let Some(dv) = value { - Some(abc_default_value(&unit.abc(), &dv)?) + Some(abc_default_value(&unit.abc(), &dv, mc)?) } else { None }, @@ -182,7 +182,7 @@ impl<'gc> Trait<'gc> { }) } - pub fn name(&self) -> &QName { + pub fn name(&self) -> &QName<'gc> { &self.name } diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 48729e77e096..c34726236a17 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -1,10 +1,10 @@ //! AVM2 values +use crate::avm1::AvmString; use crate::avm2::names::Namespace; use crate::avm2::object::Object; use crate::avm2::Error; -use gc_arena::Collect; -use std::borrow::Cow; +use gc_arena::{Collect, MutationContext}; use std::f64::NAN; use swf::avm2::types::{AbcFile, DefaultValue as AbcDefaultValue, Index}; @@ -18,20 +18,20 @@ pub enum Value<'gc> { Null, Bool(bool), Number(f64), - String(String), - Namespace(Namespace), + String(AvmString<'gc>), + Namespace(Namespace<'gc>), Object(Object<'gc>), } -impl<'gc> From for Value<'gc> { - fn from(string: String) -> Self { +impl<'gc> From> for Value<'gc> { + fn from(string: AvmString<'gc>) -> Self { Value::String(string) } } -impl<'gc> From<&str> for Value<'gc> { - fn from(string: &str) -> Self { - Value::String(string.to_owned()) +impl<'gc> From<&'static str> for Value<'gc> { + fn from(string: &'static str) -> Self { + Value::String(string.into()) } } @@ -98,8 +98,8 @@ impl<'gc> From for Value<'gc> { } } -impl<'gc> From for Value<'gc> { - fn from(value: Namespace) -> Self { +impl<'gc> From> for Value<'gc> { + fn from(value: Namespace<'gc>) -> Self { Value::Namespace(value) } } @@ -175,8 +175,8 @@ pub fn abc_double(file: &AbcFile, index: Index) -> Result { .ok_or_else(|| format!("Unknown double constant {}", index.0).into()) } -/// Retrieve a string from an ABC constant pool, yielding `""` if the string -/// is the zero string. +/// Borrow a string from an ABC constant pool, yielding `""` if the string is +/// the zero string. pub fn abc_string<'a>(file: &'a AbcFile, index: Index) -> Result<&'a str, Error> { if index.0 == 0 { return Ok(""); @@ -189,6 +189,24 @@ pub fn abc_string<'a>(file: &'a AbcFile, index: Index) -> Result<&'a str .ok_or_else(|| format!("Unknown string constant {}", index.0).into()) } +/// Copy a string from an ABC constant pool, yielding `""` if the string is the +/// zero string. +pub fn abc_string_copy<'gc>( + file: &AbcFile, + index: Index, + mc: MutationContext<'gc, '_>, +) -> Result, Error> { + if index.0 == 0 { + return Ok("".into()); + } + + file.constant_pool + .strings + .get(index.0 as usize - 1) + .map(|s| AvmString::new(mc, s.as_str())) + .ok_or_else(|| format!("Unknown string constant {}", index.0).into()) +} + /// Retrieve a string from an ABC constant pool, yielding `None` if the string /// is the zero string. /// @@ -196,10 +214,11 @@ pub fn abc_string<'a>(file: &'a AbcFile, index: Index) -> Result<&'a str /// should cause the runtime to halt. `None` indicates that the zero string is /// in use, which callers are free to interpret as necessary (although this /// usually means "any name"). -pub fn abc_string_option<'a>( - file: &'a AbcFile, +pub fn abc_string_option<'gc>( + file: &AbcFile, index: Index, -) -> Result, Error> { + mc: MutationContext<'gc, '_>, +) -> Result>, Error> { if index.0 == 0 { return Ok(None); } @@ -207,7 +226,7 @@ pub fn abc_string_option<'a>( file.constant_pool .strings .get(index.0 as usize - 1) - .map(|s| s.as_str()) + .map(|s| AvmString::new(mc, s.as_str())) .map(Some) .ok_or_else(|| format!("Unknown string constant {}", index.0).into()) } @@ -216,12 +235,15 @@ pub fn abc_string_option<'a>( pub fn abc_default_value<'gc>( file: &AbcFile, default: &AbcDefaultValue, + mc: MutationContext<'gc, '_>, ) -> Result, Error> { match default { AbcDefaultValue::Int(i) => abc_int(file, *i).map(|v| v.into()), AbcDefaultValue::Uint(u) => abc_uint(file, *u).map(|v| v.into()), AbcDefaultValue::Double(d) => abc_double(file, *d).map(|v| v.into()), - AbcDefaultValue::String(s) => abc_string(file, s.clone()).map(|v| v.into()), + AbcDefaultValue::String(s) => { + abc_string(file, s.clone()).map(|v| AvmString::new(mc, v).into()) + } AbcDefaultValue::True => Ok(true.into()), AbcDefaultValue::False => Ok(false.into()), AbcDefaultValue::Null => Ok(Value::Null), @@ -233,7 +255,7 @@ pub fn abc_default_value<'gc>( | AbcDefaultValue::Explicit(ns) | AbcDefaultValue::StaticProtected(ns) | AbcDefaultValue::Private(ns) => { - Namespace::from_abc_namespace(file, ns.clone()).map(|v| v.into()) + Namespace::from_abc_namespace(file, ns.clone(), mc).map(|v| v.into()) } } } @@ -250,20 +272,20 @@ impl<'gc> Value<'gc> { /// Demand a string value, erroring out if one is not found. /// /// TODO: This should be replaced with `coerce_string` where possible. - pub fn as_string(&self) -> Result<&String, Error> { + pub fn as_string(&self) -> Result, Error> { match self { - Value::String(s) => Ok(s), + Value::String(s) => Ok(*s), _ => Err(format!("Expected String, found {:?}", self).into()), } } /// Coerce a value into a string. - pub fn coerce_string(self) -> Cow<'static, str> { + pub fn coerce_string(self) -> AvmString<'gc> { match self { - Value::String(s) => Cow::Owned(s), - Value::Bool(true) => Cow::Borrowed("true"), - Value::Bool(false) => Cow::Borrowed("false"), - _ => Cow::Borrowed(""), + Value::String(s) => s, + Value::Bool(true) => "true".into(), + Value::Bool(false) => "false".into(), + _ => "".into(), } } @@ -282,7 +304,7 @@ impl<'gc> Value<'gc> { } } - pub fn as_namespace(&self) -> Result<&Namespace, Error> { + pub fn as_namespace(&self) -> Result<&Namespace<'gc>, Error> { match self { Value::Namespace(ns) => Ok(ns), _ => Err(format!("Expected Namespace, found {:?}", self).into()), From e1b9b823fc2a6c44ace4070d87e215df2d74d1bf Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 13 Jul 2020 23:41:46 -0400 Subject: [PATCH 178/189] Remove `abc_string` and replace it with `abc_string_copy`. All code that pulls strings from the ABC file now uses `AvmString`s. --- core/src/avm2/activation.rs | 2 +- core/src/avm2/function.rs | 2 +- core/src/avm2/names.rs | 26 +++++++++++--------------- core/src/avm2/value.rs | 20 ++------------------ 4 files changed, 15 insertions(+), 35 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 55fcab0a3dcf..5816f26e53a7 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -329,7 +329,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { index: Index, mc: MutationContext<'gc, '_>, ) -> Result, Error> { - value::abc_string_copy(&method.abc_ref(), index, mc) + value::abc_string(&method.abc_ref(), index, mc) } /// Retrieve a namespace from the current constant pool. diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 53491977e409..669716c1de17 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -184,7 +184,7 @@ impl<'gc> FunctionObject<'gc> { .super_class_name() .as_ref() .map(|p| p.local_name()) - .unwrap_or(Some("Object".into())) + .unwrap_or_else(|| Some("Object".into())) ) .into() }); diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index e43bb0321e15..169e5afb1956 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -1,7 +1,7 @@ //! AVM2 names & namespacing use crate::avm1::AvmString; -use crate::avm2::value::{abc_string_copy, abc_string_option}; +use crate::avm2::value::{abc_string, abc_string_option}; use crate::avm2::{Avm2, Error}; use gc_arena::{Collect, MutationContext}; use swf::avm2::types::{ @@ -43,21 +43,17 @@ impl<'gc> Namespace<'gc> { .ok_or_else(|| format!("Unknown namespace constant {}", namespace_index.0).into()); Ok(match abc_namespace? { - AbcNamespace::Namespace(idx) => { - Self::Namespace(abc_string_copy(file, idx.clone(), mc)?) - } - AbcNamespace::Package(idx) => Self::Package(abc_string_copy(file, idx.clone(), mc)?), + AbcNamespace::Namespace(idx) => Self::Namespace(abc_string(file, idx.clone(), mc)?), + AbcNamespace::Package(idx) => Self::Package(abc_string(file, idx.clone(), mc)?), AbcNamespace::PackageInternal(idx) => { - Self::PackageInternal(abc_string_copy(file, idx.clone(), mc)?) - } - AbcNamespace::Protected(idx) => { - Self::Protected(abc_string_copy(file, idx.clone(), mc)?) + Self::PackageInternal(abc_string(file, idx.clone(), mc)?) } - AbcNamespace::Explicit(idx) => Self::Explicit(abc_string_copy(file, idx.clone(), mc)?), + AbcNamespace::Protected(idx) => Self::Protected(abc_string(file, idx.clone(), mc)?), + AbcNamespace::Explicit(idx) => Self::Explicit(abc_string(file, idx.clone(), mc)?), AbcNamespace::StaticProtected(idx) => { - Self::StaticProtected(abc_string_copy(file, idx.clone(), mc)?) + Self::StaticProtected(abc_string(file, idx.clone(), mc)?) } - AbcNamespace::Private(idx) => Self::Private(abc_string_copy(file, idx.clone(), mc)?), + AbcNamespace::Private(idx) => Self::Private(abc_string(file, idx.clone(), mc)?), }) } @@ -138,7 +134,7 @@ impl<'gc> QName<'gc> { Ok(match abc_multiname? { AbcMultiname::QName { namespace, name } => Self { ns: Namespace::from_abc_namespace(file, namespace.clone(), mc)?, - name: abc_string_copy(file, name.clone(), mc)?, + name: abc_string(file, name.clone(), mc)?, }, _ => return Err("Attempted to pull QName from non-QName multiname".into()), }) @@ -231,7 +227,7 @@ impl<'gc> Multiname<'gc> { } AbcMultiname::RTQNameL | AbcMultiname::RTQNameLA => { let ns = avm.pop().as_namespace()?.clone(); - let name = avm.pop().as_string()?.clone(); + let name = avm.pop().as_string()?; Self { ns: vec![ns], name: Some(name), @@ -250,7 +246,7 @@ impl<'gc> Multiname<'gc> { }, AbcMultiname::MultinameL { namespace_set } | AbcMultiname::MultinameLA { namespace_set } => { - let name = avm.pop().as_string()?.clone(); + let name = avm.pop().as_string()?; Self { ns: Self::abc_namespace_set(file, namespace_set.clone(), mc)?, name: Some(name), diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index c34726236a17..41df261cc806 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -175,23 +175,9 @@ pub fn abc_double(file: &AbcFile, index: Index) -> Result { .ok_or_else(|| format!("Unknown double constant {}", index.0).into()) } -/// Borrow a string from an ABC constant pool, yielding `""` if the string is -/// the zero string. -pub fn abc_string<'a>(file: &'a AbcFile, index: Index) -> Result<&'a str, Error> { - if index.0 == 0 { - return Ok(""); - } - - file.constant_pool - .strings - .get(index.0 as usize - 1) - .map(|s| s.as_str()) - .ok_or_else(|| format!("Unknown string constant {}", index.0).into()) -} - /// Copy a string from an ABC constant pool, yielding `""` if the string is the /// zero string. -pub fn abc_string_copy<'gc>( +pub fn abc_string<'gc>( file: &AbcFile, index: Index, mc: MutationContext<'gc, '_>, @@ -241,9 +227,7 @@ pub fn abc_default_value<'gc>( AbcDefaultValue::Int(i) => abc_int(file, *i).map(|v| v.into()), AbcDefaultValue::Uint(u) => abc_uint(file, *u).map(|v| v.into()), AbcDefaultValue::Double(d) => abc_double(file, *d).map(|v| v.into()), - AbcDefaultValue::String(s) => { - abc_string(file, s.clone()).map(|v| AvmString::new(mc, v).into()) - } + AbcDefaultValue::String(s) => abc_string(file, s.clone(), mc).map(|v| v.into()), AbcDefaultValue::True => Ok(true.into()), AbcDefaultValue::False => Ok(false.into()), AbcDefaultValue::Null => Ok(Value::Null), From 70e9e7e9e93be9c6b28ecd2d09a87648a02bbad4 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 13 Jul 2020 23:42:07 -0400 Subject: [PATCH 179/189] Add support for cached/interned pool strings in the `TranslationUnit`. --- core/src/avm2/script.rs | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index c579bc60e313..53143fa2b7d5 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -1,5 +1,6 @@ //! Whole script representation +use crate::avm1::AvmString; use crate::avm2::class::Class; use crate::avm2::method::{BytecodeMethod, Method}; use crate::avm2::r#trait::Trait; @@ -45,6 +46,9 @@ pub struct TranslationUnitData<'gc> { /// All scripts loaded from the ABC's scripts list. scripts: HashMap>>, + + /// All strings loaded from the ABC's strings list. + strings: HashMap>, } impl<'gc> TranslationUnit<'gc> { @@ -56,6 +60,7 @@ impl<'gc> TranslationUnit<'gc> { classes: HashMap::new(), methods: HashMap::new(), scripts: HashMap::new(), + strings: HashMap::new(), }, )) } @@ -138,6 +143,57 @@ impl<'gc> TranslationUnit<'gc> { Ok(script) } + + /// Load a string from the ABC's constant pool. + /// + /// This function yields an error if no such string index exists. + /// + /// This function yields `None` to signal string index zero, which callers + /// are free to interpret as the context demands. + pub fn pool_string_option( + self, + string_index: u32, + mc: MutationContext<'gc, '_>, + ) -> Result>, Error> { + let mut write = self.0.write(mc); + if let Some(string) = write.strings.get(&string_index) { + return Ok(Some(*string)); + } + + if string_index == 0 { + return Ok(None); + } + + let avm_string = AvmString::new( + mc, + write + .abc + .0 + .constant_pool + .strings + .get(string_index as usize - 1) + .ok_or_else(|| format!("Unknown string constant {}", string_index))?, + ); + write.strings.insert(string_index, avm_string); + + Ok(Some(avm_string)) + } + + /// Load a string from the ABC's constant pool. + /// + /// This function yields an error if no such string index exists. + /// + /// String index 0 is always `""`. If you need to instead treat 0 as + /// something else, then please use `pool_string_option`. + pub fn pool_string( + self, + string_index: u32, + mc: MutationContext<'gc, '_>, + ) -> Result, Error> { + Ok(self + .pool_string_option(string_index, mc)? + .unwrap_or_else(|| "".into())) + } } /// A loaded Script from an ABC file. From f4c5075086f7d7f8a7164130454035117872e584 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 14 Jul 2020 20:05:25 -0400 Subject: [PATCH 180/189] Run all string constant retrieval through the `TranslationUnit`, preventing us from making multiple copies of the same string. For good measure, most of the other methods in `value` for retrieving pool primitives now also use `TranslationUnit` instead of `AbcFile`. This is the result of a handful of cascading changes throughout the project, and itself caused a few more. --- core/src/avm2/activation.rs | 14 +++--- core/src/avm2/class.rs | 8 ++-- core/src/avm2/names.rs | 86 +++++++++++++++++++++++-------------- core/src/avm2/script.rs | 2 +- core/src/avm2/trait.rs | 10 ++--- core/src/avm2/value.rs | 75 +++++++++----------------------- 6 files changed, 90 insertions(+), 105 deletions(-) diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 5816f26e53a7..46fb2f07fe7f 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -301,7 +301,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, ) -> Result { - value::abc_int(&method.abc(), index) + value::abc_int(method.translation_unit(), index) } /// Retrieve a int from the current constant pool. @@ -310,7 +310,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, ) -> Result { - value::abc_uint(&method.abc(), index) + value::abc_uint(method.translation_unit(), index) } /// Retrieve a double from the current constant pool. @@ -319,7 +319,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { method: Gc<'gc, BytecodeMethod<'gc>>, index: Index, ) -> Result { - value::abc_double(&method.abc(), index) + value::abc_double(method.translation_unit(), index) } /// Retrieve a string from the current constant pool. @@ -329,7 +329,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { index: Index, mc: MutationContext<'gc, '_>, ) -> Result, Error> { - value::abc_string(&method.abc_ref(), index, mc) + method.translation_unit().pool_string(index.0, mc) } /// Retrieve a namespace from the current constant pool. @@ -339,7 +339,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { index: Index, mc: MutationContext<'gc, '_>, ) -> Result, Error> { - Namespace::from_abc_namespace(&method.abc(), index, mc) + Namespace::from_abc_namespace(method.translation_unit(), index, mc) } /// Retrieve a multiname from the current constant pool. @@ -349,7 +349,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { index: Index, mc: MutationContext<'gc, '_>, ) -> Result, Error> { - Multiname::from_abc_multiname(&method.abc(), index, self.avm2, mc) + Multiname::from_abc_multiname(method.translation_unit(), index, self.avm2, mc) } /// Retrieve a static, or non-runtime, multiname from the current constant @@ -360,7 +360,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> { index: Index, mc: MutationContext<'gc, '_>, ) -> Result, Error> { - Multiname::from_abc_multiname_static(&method.abc(), index, mc) + Multiname::from_abc_multiname_static(method.translation_unit(), index, mc) } /// Retrieve a method entry from the current ABC file's method table. diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 467f2193ea17..078a6e878ef4 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -120,19 +120,19 @@ impl<'gc> Class<'gc> { .ok_or_else(|| "LoadError: Instance index not valid".into()); let abc_instance = abc_instance?; - let name = QName::from_abc_multiname(&unit.abc(), abc_instance.name.clone(), mc)?; + let name = QName::from_abc_multiname(unit, abc_instance.name.clone(), mc)?; let super_class = if abc_instance.super_name.0 == 0 { None } else { Some(Multiname::from_abc_multiname_static( - &unit.abc(), + unit, abc_instance.super_name.clone(), mc, )?) }; let protected_namespace = if let Some(ns) = &abc_instance.protected_namespace { - Some(Namespace::from_abc_namespace(&unit.abc(), ns.clone(), mc)?) + Some(Namespace::from_abc_namespace(unit, ns.clone(), mc)?) } else { None }; @@ -140,7 +140,7 @@ impl<'gc> Class<'gc> { let mut interfaces = Vec::new(); for interface_name in abc_instance.interfaces.iter() { interfaces.push(Multiname::from_abc_multiname_static( - &unit.abc(), + unit, interface_name.clone(), mc, )?); diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 169e5afb1956..63321cba82c2 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -1,12 +1,11 @@ //! AVM2 names & namespacing use crate::avm1::AvmString; -use crate::avm2::value::{abc_string, abc_string_option}; +use crate::avm2::script::TranslationUnit; use crate::avm2::{Avm2, Error}; use gc_arena::{Collect, MutationContext}; use swf::avm2::types::{ - AbcFile, Index, Multiname as AbcMultiname, Namespace as AbcNamespace, - NamespaceSet as AbcNamespaceSet, + Index, Multiname as AbcMultiname, Namespace as AbcNamespace, NamespaceSet as AbcNamespaceSet, }; /// Represents the name of a namespace. @@ -27,7 +26,7 @@ impl<'gc> Namespace<'gc> { /// Read a namespace declaration from the ABC constant pool and copy it to /// a namespace value. pub fn from_abc_namespace( - file: &AbcFile, + translation_unit: TranslationUnit<'gc>, namespace_index: Index, mc: MutationContext<'gc, '_>, ) -> Result { @@ -36,24 +35,29 @@ impl<'gc> Namespace<'gc> { } let actual_index = namespace_index.0 as usize - 1; - let abc_namespace: Result<&AbcNamespace, Error> = file + let abc = translation_unit.abc(); + let abc_namespace: Result<_, Error> = abc .constant_pool .namespaces .get(actual_index) .ok_or_else(|| format!("Unknown namespace constant {}", namespace_index.0).into()); Ok(match abc_namespace? { - AbcNamespace::Namespace(idx) => Self::Namespace(abc_string(file, idx.clone(), mc)?), - AbcNamespace::Package(idx) => Self::Package(abc_string(file, idx.clone(), mc)?), + AbcNamespace::Namespace(idx) => { + Self::Namespace(translation_unit.pool_string(idx.0, mc)?) + } + AbcNamespace::Package(idx) => Self::Package(translation_unit.pool_string(idx.0, mc)?), AbcNamespace::PackageInternal(idx) => { - Self::PackageInternal(abc_string(file, idx.clone(), mc)?) + Self::PackageInternal(translation_unit.pool_string(idx.0, mc)?) + } + AbcNamespace::Protected(idx) => { + Self::Protected(translation_unit.pool_string(idx.0, mc)?) } - AbcNamespace::Protected(idx) => Self::Protected(abc_string(file, idx.clone(), mc)?), - AbcNamespace::Explicit(idx) => Self::Explicit(abc_string(file, idx.clone(), mc)?), + AbcNamespace::Explicit(idx) => Self::Explicit(translation_unit.pool_string(idx.0, mc)?), AbcNamespace::StaticProtected(idx) => { - Self::StaticProtected(abc_string(file, idx.clone(), mc)?) + Self::StaticProtected(translation_unit.pool_string(idx.0, mc)?) } - AbcNamespace::Private(idx) => Self::Private(abc_string(file, idx.clone(), mc)?), + AbcNamespace::Private(idx) => Self::Private(translation_unit.pool_string(idx.0, mc)?), }) } @@ -120,12 +124,13 @@ impl<'gc> QName<'gc> { /// This function returns an Err if the multiname does not exist or is not /// a `QName`. pub fn from_abc_multiname( - file: &AbcFile, + translation_unit: TranslationUnit<'gc>, multiname_index: Index, mc: MutationContext<'gc, '_>, ) -> Result { let actual_index = multiname_index.0 as usize - 1; - let abc_multiname: Result<&AbcMultiname, Error> = file + let abc = translation_unit.abc(); + let abc_multiname: Result<_, Error> = abc .constant_pool .multinames .get(actual_index) @@ -133,8 +138,8 @@ impl<'gc> QName<'gc> { Ok(match abc_multiname? { AbcMultiname::QName { namespace, name } => Self { - ns: Namespace::from_abc_namespace(file, namespace.clone(), mc)?, - name: abc_string(file, name.clone(), mc)?, + ns: Namespace::from_abc_namespace(translation_unit, namespace.clone(), mc)?, + name: translation_unit.pool_string(name.0, mc)?, }, _ => return Err("Attempted to pull QName from non-QName multiname".into()), }) @@ -167,7 +172,7 @@ impl<'gc> Multiname<'gc> { /// Read a namespace set from the ABC constant pool, and return a list of /// copied namespaces. fn abc_namespace_set( - file: &AbcFile, + translation_unit: TranslationUnit<'gc>, namespace_set_index: Index, mc: MutationContext<'gc, '_>, ) -> Result>, Error> { @@ -177,7 +182,8 @@ impl<'gc> Multiname<'gc> { } let actual_index = namespace_set_index.0 as usize - 1; - let ns_set: Result<&AbcNamespaceSet, Error> = file + let abc = translation_unit.abc(); + let ns_set: Result<_, Error> = abc .constant_pool .namespace_sets .get(actual_index) @@ -187,7 +193,11 @@ impl<'gc> Multiname<'gc> { let mut result = vec![]; for ns in ns_set? { - result.push(Namespace::from_abc_namespace(file, ns.clone(), mc)?) + result.push(Namespace::from_abc_namespace( + translation_unit, + ns.clone(), + mc, + )?) } Ok(result) @@ -196,7 +206,7 @@ impl<'gc> Multiname<'gc> { /// Read a multiname from the ABC constant pool, copying it into the most /// general form of multiname. pub fn from_abc_multiname( - file: &AbcFile, + translation_unit: TranslationUnit<'gc>, multiname_index: Index, avm: &mut Avm2<'gc>, mc: MutationContext<'gc, '_>, @@ -205,7 +215,8 @@ impl<'gc> Multiname<'gc> { .checked_sub(1) .ok_or_else(|| "Attempted to resolve a multiname at index zero. This is a bug.".into()); let actual_index = actual_index?; - let abc_multiname: Result<&AbcMultiname, Error> = file + let abc = translation_unit.abc(); + let abc_multiname: Result<_, Error> = abc .constant_pool .multinames .get(actual_index) @@ -214,15 +225,19 @@ impl<'gc> Multiname<'gc> { Ok(match abc_multiname? { AbcMultiname::QName { namespace, name } | AbcMultiname::QNameA { namespace, name } => { Self { - ns: vec![Namespace::from_abc_namespace(file, namespace.clone(), mc)?], - name: abc_string_option(file, name.clone(), mc)?, + ns: vec![Namespace::from_abc_namespace( + translation_unit, + namespace.clone(), + mc, + )?], + name: translation_unit.pool_string_option(name.0, mc)?, } } AbcMultiname::RTQName { name } | AbcMultiname::RTQNameA { name } => { let ns = avm.pop().as_namespace()?.clone(); Self { ns: vec![ns], - name: abc_string_option(file, name.clone(), mc)?, + name: translation_unit.pool_string_option(name.0, mc)?, } } AbcMultiname::RTQNameL | AbcMultiname::RTQNameLA => { @@ -241,14 +256,14 @@ impl<'gc> Multiname<'gc> { namespace_set, name, } => Self { - ns: Self::abc_namespace_set(file, namespace_set.clone(), mc)?, - name: abc_string_option(file, name.clone(), mc)?, + ns: Self::abc_namespace_set(translation_unit, namespace_set.clone(), mc)?, + name: translation_unit.pool_string_option(name.0, mc)?, }, AbcMultiname::MultinameL { namespace_set } | AbcMultiname::MultinameLA { namespace_set } => { let name = avm.pop().as_string()?; Self { - ns: Self::abc_namespace_set(file, namespace_set.clone(), mc)?, + ns: Self::abc_namespace_set(translation_unit, namespace_set.clone(), mc)?, name: Some(name), } } @@ -260,7 +275,7 @@ impl<'gc> Multiname<'gc> { /// This function prohibits the use of runtime-qualified and late-bound /// names. Runtime multinames will instead result in an error. pub fn from_abc_multiname_static( - file: &AbcFile, + translation_unit: TranslationUnit<'gc>, multiname_index: Index, mc: MutationContext<'gc, '_>, ) -> Result { @@ -269,7 +284,8 @@ impl<'gc> Multiname<'gc> { "Attempted to resolve a (static) multiname at index zero. This is a bug.".into() }); let actual_index = actual_index?; - let abc_multiname: Result<&AbcMultiname, Error> = file + let abc = translation_unit.abc(); + let abc_multiname: Result<_, Error> = abc .constant_pool .multinames .get(actual_index) @@ -278,8 +294,12 @@ impl<'gc> Multiname<'gc> { Ok(match abc_multiname? { AbcMultiname::QName { namespace, name } | AbcMultiname::QNameA { namespace, name } => { Self { - ns: vec![Namespace::from_abc_namespace(file, namespace.clone(), mc)?], - name: abc_string_option(file, name.clone(), mc)?, + ns: vec![Namespace::from_abc_namespace( + translation_unit, + namespace.clone(), + mc, + )?], + name: translation_unit.pool_string_option(name.0, mc)?, } } AbcMultiname::Multiname { @@ -290,8 +310,8 @@ impl<'gc> Multiname<'gc> { namespace_set, name, } => Self { - ns: Self::abc_namespace_set(file, namespace_set.clone(), mc)?, - name: abc_string_option(file, name.clone(), mc)?, + ns: Self::abc_namespace_set(translation_unit, namespace_set.clone(), mc)?, + name: translation_unit.pool_string_option(name.0, mc)?, }, _ => return Err(format!("Multiname {} is not static", multiname_index.0).into()), }) diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index 53143fa2b7d5..ae6cfa3e32d8 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -260,7 +260,7 @@ impl<'gc> Script<'gc> { self.traits_loaded = true; let abc = unit.abc(); - let script: Result<&AbcScript, Error> = abc + let script: Result<_, Error> = abc .scripts .get(script_index as usize) .ok_or_else(|| "LoadError: Script index not valid".into()); diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index 981dd7ac7956..49f52209fa49 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -87,7 +87,7 @@ impl<'gc> Trait<'gc> { abc_trait: &AbcTrait, mc: MutationContext<'gc, '_>, ) -> Result { - let name = QName::from_abc_multiname(&unit.abc(), abc_trait.name.clone(), mc)?; + let name = QName::from_abc_multiname(unit, abc_trait.name.clone(), mc)?; Ok(match &abc_trait.kind { AbcTraitKind::Slot { @@ -103,10 +103,10 @@ impl<'gc> Trait<'gc> { type_name: if type_name.0 == 0 { Multiname::any() } else { - Multiname::from_abc_multiname_static(&unit.abc(), type_name.clone(), mc)? + Multiname::from_abc_multiname_static(unit, type_name.clone(), mc)? }, default_value: if let Some(dv) = value { - Some(abc_default_value(&unit.abc(), &dv, mc)?) + Some(abc_default_value(unit, &dv, mc)?) } else { None }, @@ -170,10 +170,10 @@ impl<'gc> Trait<'gc> { type_name: if type_name.0 == 0 { Multiname::any() } else { - Multiname::from_abc_multiname_static(&unit.abc(), type_name.clone(), mc)? + Multiname::from_abc_multiname_static(unit, type_name.clone(), mc)? }, default_value: if let Some(dv) = value { - Some(abc_default_value(&unit.abc(), &dv, mc)?) + Some(abc_default_value(unit, &dv, mc)?) } else { None }, diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 41df261cc806..a60c1560b8e3 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -3,10 +3,11 @@ use crate::avm1::AvmString; use crate::avm2::names::Namespace; use crate::avm2::object::Object; +use crate::avm2::script::TranslationUnit; use crate::avm2::Error; use gc_arena::{Collect, MutationContext}; use std::f64::NAN; -use swf::avm2::types::{AbcFile, DefaultValue as AbcDefaultValue, Index}; +use swf::avm2::types::{DefaultValue as AbcDefaultValue, Index}; /// An AVM2 value. /// @@ -139,95 +140,59 @@ impl PartialEq for Value<'_> { } } -pub fn abc_int(file: &AbcFile, index: Index) -> Result { +pub fn abc_int(translation_unit: TranslationUnit<'_>, index: Index) -> Result { if index.0 == 0 { return Ok(0); } - file.constant_pool + translation_unit + .abc_ref() + .constant_pool .ints .get(index.0 as usize - 1) .cloned() .ok_or_else(|| format!("Unknown int constant {}", index.0).into()) } -pub fn abc_uint(file: &AbcFile, index: Index) -> Result { +pub fn abc_uint(translation_unit: TranslationUnit<'_>, index: Index) -> Result { if index.0 == 0 { return Ok(0); } - file.constant_pool + translation_unit + .abc_ref() + .constant_pool .uints .get(index.0 as usize - 1) .cloned() .ok_or_else(|| format!("Unknown uint constant {}", index.0).into()) } -pub fn abc_double(file: &AbcFile, index: Index) -> Result { +pub fn abc_double(translation_unit: TranslationUnit<'_>, index: Index) -> Result { if index.0 == 0 { return Ok(NAN); } - file.constant_pool + translation_unit + .abc_ref() + .constant_pool .doubles .get(index.0 as usize - 1) .cloned() .ok_or_else(|| format!("Unknown double constant {}", index.0).into()) } -/// Copy a string from an ABC constant pool, yielding `""` if the string is the -/// zero string. -pub fn abc_string<'gc>( - file: &AbcFile, - index: Index, - mc: MutationContext<'gc, '_>, -) -> Result, Error> { - if index.0 == 0 { - return Ok("".into()); - } - - file.constant_pool - .strings - .get(index.0 as usize - 1) - .map(|s| AvmString::new(mc, s.as_str())) - .ok_or_else(|| format!("Unknown string constant {}", index.0).into()) -} - -/// Retrieve a string from an ABC constant pool, yielding `None` if the string -/// is the zero string. -/// -/// This function still yields `Err` for out-of-bounds string constants, which -/// should cause the runtime to halt. `None` indicates that the zero string is -/// in use, which callers are free to interpret as necessary (although this -/// usually means "any name"). -pub fn abc_string_option<'gc>( - file: &AbcFile, - index: Index, - mc: MutationContext<'gc, '_>, -) -> Result>, Error> { - if index.0 == 0 { - return Ok(None); - } - - file.constant_pool - .strings - .get(index.0 as usize - 1) - .map(|s| AvmString::new(mc, s.as_str())) - .map(Some) - .ok_or_else(|| format!("Unknown string constant {}", index.0).into()) -} - /// Retrieve a default value as an AVM2 `Value`. pub fn abc_default_value<'gc>( - file: &AbcFile, + translation_unit: TranslationUnit<'gc>, default: &AbcDefaultValue, mc: MutationContext<'gc, '_>, ) -> Result, Error> { match default { - AbcDefaultValue::Int(i) => abc_int(file, *i).map(|v| v.into()), - AbcDefaultValue::Uint(u) => abc_uint(file, *u).map(|v| v.into()), - AbcDefaultValue::Double(d) => abc_double(file, *d).map(|v| v.into()), - AbcDefaultValue::String(s) => abc_string(file, s.clone(), mc).map(|v| v.into()), + AbcDefaultValue::Int(i) => abc_int(translation_unit, *i).map(|v| v.into()), + AbcDefaultValue::Uint(u) => abc_uint(translation_unit, *u).map(|v| v.into()), + AbcDefaultValue::Double(d) => abc_double(translation_unit, *d).map(|v| v.into()), + AbcDefaultValue::String(s) => translation_unit.pool_string(s.0, mc).map(|v| v.into()), AbcDefaultValue::True => Ok(true.into()), AbcDefaultValue::False => Ok(false.into()), AbcDefaultValue::Null => Ok(Value::Null), @@ -239,7 +204,7 @@ pub fn abc_default_value<'gc>( | AbcDefaultValue::Explicit(ns) | AbcDefaultValue::StaticProtected(ns) | AbcDefaultValue::Private(ns) => { - Namespace::from_abc_namespace(file, ns.clone(), mc).map(|v| v.into()) + Namespace::from_abc_namespace(translation_unit, ns.clone(), mc).map(|v| v.into()) } } } From e354c53075a3dd37b965a1d0dca0af8ecb10a77d Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 14 Jul 2020 20:14:23 -0400 Subject: [PATCH 181/189] Remove any remaining uses of `abc_ref`. Holding a `Ref` on a garbage-collected object inherently extends any borrow locks on that object. Since ABC files are references already, taking a `Ref` to them only helps to skip the refcount update. This is less useful than expected: in most situations, using `abc_ref` causes double-borrow panics. The few methods that can use it are going to be fragile in the face of future refactors, so I'm nipping the problem in the bud now. --- core/src/avm2/method.rs | 6 ------ core/src/avm2/script.rs | 7 ------- core/src/avm2/value.rs | 6 +++--- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/core/src/avm2/method.rs b/core/src/avm2/method.rs index b5f1e821a323..1ca6cbb5ac1f 100644 --- a/core/src/avm2/method.rs +++ b/core/src/avm2/method.rs @@ -7,7 +7,6 @@ use crate::avm2::value::Value; use crate::avm2::Error; use crate::context::UpdateContext; use gc_arena::{Collect, CollectionContext, Gc, MutationContext}; -use std::cell::Ref; use std::fmt; use std::rc::Rc; use swf::avm2::types::{AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody}; @@ -99,11 +98,6 @@ impl<'gc> BytecodeMethod<'gc> { self.txunit.abc() } - /// Retrieve a reference to the underlying ABC file. - pub fn abc_ref(&self) -> Ref { - self.txunit.abc_ref() - } - /// Get the underlying translation unit this method was defined in. pub fn translation_unit(&self) -> TranslationUnit<'gc> { self.txunit diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index ae6cfa3e32d8..9bdbc694f1dd 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -6,7 +6,6 @@ use crate::avm2::method::{BytecodeMethod, Method}; use crate::avm2::r#trait::Trait; use crate::avm2::Error; use gc_arena::{Collect, Gc, GcCell, MutationContext}; -use std::cell::Ref; use std::collections::HashMap; use std::mem::drop; use std::rc::Rc; @@ -70,12 +69,6 @@ impl<'gc> TranslationUnit<'gc> { self.0.read().abc.0.clone() } - /// Retrieve a reference to the underlying `AbcFile` for this translation - /// unit. - pub fn abc_ref(&self) -> Ref { - Ref::map(self.0.read(), |tu| &*tu.abc.0) - } - /// Load a method from the ABC file and return it's method definition. pub fn load_method( self, diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index a60c1560b8e3..65adff7ef39b 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -146,7 +146,7 @@ pub fn abc_int(translation_unit: TranslationUnit<'_>, index: Index) -> Resu } translation_unit - .abc_ref() + .abc() .constant_pool .ints .get(index.0 as usize - 1) @@ -160,7 +160,7 @@ pub fn abc_uint(translation_unit: TranslationUnit<'_>, index: Index) -> Res } translation_unit - .abc_ref() + .abc() .constant_pool .uints .get(index.0 as usize - 1) @@ -174,7 +174,7 @@ pub fn abc_double(translation_unit: TranslationUnit<'_>, index: Index) -> R } translation_unit - .abc_ref() + .abc() .constant_pool .doubles .get(index.0 as usize - 1) From ccacc540bffd2af3c5ba72aeda3fe56bdff5b486 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 18 Jul 2020 16:12:24 -0400 Subject: [PATCH 182/189] Remove dead code on all now-in-use structs and methods. --- core/src/avm2/method.rs | 1 - core/src/avm2/slot.rs | 1 - swf/src/avm2/opcode.rs | 1 - swf/src/avm2/read.rs | 2 -- 4 files changed, 5 deletions(-) diff --git a/core/src/avm2/method.rs b/core/src/avm2/method.rs index 1ca6cbb5ac1f..6efbdb123572 100644 --- a/core/src/avm2/method.rs +++ b/core/src/avm2/method.rs @@ -93,7 +93,6 @@ impl<'gc> BytecodeMethod<'gc> { } /// Get the underlying ABC file. - #[allow(dead_code)] pub fn abc(&self) -> Rc { self.txunit.abc() } diff --git a/core/src/avm2/slot.rs b/core/src/avm2/slot.rs index 2456363ed6f5..7c2b070a790b 100644 --- a/core/src/avm2/slot.rs +++ b/core/src/avm2/slot.rs @@ -8,7 +8,6 @@ use gc_arena::{Collect, CollectionContext}; /// Represents a single slot on an object. #[derive(Clone, Debug)] -#[allow(dead_code)] pub enum Slot<'gc> { /// An unoccupied slot. /// diff --git a/swf/src/avm2/opcode.rs b/swf/src/avm2/opcode.rs index 77c110aaf5fd..8a255cfa3f45 100644 --- a/swf/src/avm2/opcode.rs +++ b/swf/src/avm2/opcode.rs @@ -1,6 +1,5 @@ #![allow(clippy::useless_attribute)] -#[allow(dead_code)] #[derive(Debug, PartialEq, Clone, Copy, FromPrimitive)] pub enum OpCode { Add = 0xA0, diff --git a/swf/src/avm2/read.rs b/swf/src/avm2/read.rs index 3c64459bc77c..f38fa75a452c 100644 --- a/swf/src/avm2/read.rs +++ b/swf/src/avm2/read.rs @@ -100,7 +100,6 @@ impl Reader { self.read_u30() } - #[allow(dead_code)] fn read_i24(&mut self) -> Result { Ok(i32::from(self.read_u8()? as i8) | (i32::from(self.read_u8()? as i8) << 8) @@ -527,7 +526,6 @@ impl Reader { }) } - #[allow(dead_code)] pub fn read_op(&mut self) -> Result> { use crate::avm2::opcode::OpCode; use num_traits::FromPrimitive; From 37b6b89d26bc1531220419d8e49ba1930958b8bc Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 18 Jul 2020 16:20:58 -0400 Subject: [PATCH 183/189] Add a stub AVM2 string representation to allow for both VMs' strings to diverge. --- core/src/avm2.rs | 1 + core/src/avm2/activation.rs | 2 +- core/src/avm2/class.rs | 2 +- core/src/avm2/function.rs | 2 +- core/src/avm2/globals.rs | 2 +- core/src/avm2/names.rs | 2 +- core/src/avm2/object.rs | 2 +- core/src/avm2/script.rs | 2 +- core/src/avm2/script_object.rs | 2 +- core/src/avm2/string.rs | 3 +++ core/src/avm2/value.rs | 2 +- 11 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 core/src/avm2/string.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index d1da0b356cde..a38380bec2c2 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -34,6 +34,7 @@ mod scope; mod script; mod script_object; mod slot; +mod string; mod r#trait; mod value; diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 46fb2f07fe7f..cc8a04f2995c 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -1,6 +1,5 @@ //! Activation frames -use crate::avm1::AvmString; use crate::avm2::class::Class; use crate::avm2::function::FunctionObject; use crate::avm2::method::BytecodeMethod; @@ -9,6 +8,7 @@ use crate::avm2::object::{Object, TObject}; use crate::avm2::scope::Scope; use crate::avm2::script::Script; use crate::avm2::script_object::ScriptObject; +use crate::avm2::string::AvmString; use crate::avm2::value::Value; use crate::avm2::{value, Avm2, Error}; use crate::context::UpdateContext; diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 078a6e878ef4..d13358b642f9 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -1,10 +1,10 @@ //! AVM2 classes -use crate::avm1::AvmString; use crate::avm2::method::Method; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::r#trait::{Trait, TraitKind}; use crate::avm2::script::TranslationUnit; +use crate::avm2::string::AvmString; use crate::avm2::Error; use gc_arena::{Collect, GcCell, MutationContext}; use swf::avm2::types::{Class as AbcClass, Instance as AbcInstance}; diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 669716c1de17..445782bad2ba 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -1,6 +1,5 @@ //! AVM2 executables. -use crate::avm1::AvmString; use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::method::{BytecodeMethod, Method, NativeMethod}; @@ -9,6 +8,7 @@ use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::r#trait::Trait; use crate::avm2::scope::Scope; use crate::avm2::script_object::{ScriptObject, ScriptObjectClass, ScriptObjectData}; +use crate::avm2::string::AvmString; use crate::avm2::value::Value; use crate::avm2::Error; use crate::context::UpdateContext; diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 00a0d7cbe183..db02b0d6359c 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -1,12 +1,12 @@ //! Global scope built-ins -use crate::avm1::AvmString; use crate::avm2::activation::Activation; use crate::avm2::function::FunctionObject; use crate::avm2::method::NativeMethod; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, TObject}; use crate::avm2::script_object::ScriptObject; +use crate::avm2::string::AvmString; use crate::avm2::value::Value; use crate::avm2::Error; use crate::context::UpdateContext; diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 63321cba82c2..2e8332413116 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -1,7 +1,7 @@ //! AVM2 names & namespacing -use crate::avm1::AvmString; use crate::avm2::script::TranslationUnit; +use crate::avm2::string::AvmString; use crate::avm2::{Avm2, Error}; use gc_arena::{Collect, MutationContext}; use swf::avm2::types::{ diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index e39590822340..46f6a38ccde0 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1,6 +1,5 @@ //! AVM2 objects. -use crate::avm1::AvmString; use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::function::{Executable, FunctionObject}; @@ -8,6 +7,7 @@ use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::r#trait::{Trait, TraitKind}; use crate::avm2::scope::Scope; use crate::avm2::script_object::ScriptObject; +use crate::avm2::string::AvmString; use crate::avm2::value::Value; use crate::avm2::Error; use crate::context::UpdateContext; diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index 9bdbc694f1dd..eb94eb670090 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -1,9 +1,9 @@ //! Whole script representation -use crate::avm1::AvmString; use crate::avm2::class::Class; use crate::avm2::method::{BytecodeMethod, Method}; use crate::avm2::r#trait::Trait; +use crate::avm2::string::AvmString; use crate::avm2::Error; use gc_arena::{Collect, Gc, GcCell, MutationContext}; use std::collections::HashMap; diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index b40a892a2530..3e0b6dcdd9b4 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -1,6 +1,5 @@ //! Default AVM2 object impl -use crate::avm1::AvmString; use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::function::Executable; @@ -11,6 +10,7 @@ use crate::avm2::r#trait::Trait; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; use crate::avm2::slot::Slot; +use crate::avm2::string::AvmString; use crate::avm2::value::Value; use crate::avm2::Error; use crate::context::UpdateContext; diff --git a/core/src/avm2/string.rs b/core/src/avm2/string.rs new file mode 100644 index 000000000000..da58c3f2fa68 --- /dev/null +++ b/core/src/avm2/string.rs @@ -0,0 +1,3 @@ +//! AVM2 String representation + +pub use crate::avm1::AvmString; diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 65adff7ef39b..58498f9c6187 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -1,9 +1,9 @@ //! AVM2 values -use crate::avm1::AvmString; use crate::avm2::names::Namespace; use crate::avm2::object::Object; use crate::avm2::script::TranslationUnit; +use crate::avm2::string::AvmString; use crate::avm2::Error; use gc_arena::{Collect, MutationContext}; use std::f64::NAN; From dc962f2abdaea37930d86c8e92a665eda1efbf4b Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 18 Jul 2020 16:41:35 -0400 Subject: [PATCH 184/189] Add AVM2 equivalent of `PropertyMap` for further expansion. --- core/src/avm2.rs | 1 + core/src/avm2/property_map.rs | 7 +++++++ core/src/avm2/script_object.rs | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 core/src/avm2/property_map.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index a38380bec2c2..31d797234679 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -29,6 +29,7 @@ mod method; mod names; mod object; mod property; +mod property_map; mod return_value; mod scope; mod script; diff --git a/core/src/avm2/property_map.rs b/core/src/avm2/property_map.rs new file mode 100644 index 000000000000..c9d762c38b03 --- /dev/null +++ b/core/src/avm2/property_map.rs @@ -0,0 +1,7 @@ +//! Property map + +use crate::avm2::names::QName; +use std::collections::HashMap; + +/// Type which represents named properties on an object. +pub type PropertyMap<'gc, V> = HashMap, V>; diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 3e0b6dcdd9b4..c306f9836ea5 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -6,6 +6,7 @@ use crate::avm2::function::Executable; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::property::Property; +use crate::avm2::property_map::PropertyMap; use crate::avm2::r#trait::Trait; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; @@ -53,7 +54,7 @@ pub enum ScriptObjectClass<'gc> { #[collect(no_drop)] pub struct ScriptObjectData<'gc> { /// Properties stored on this object. - values: HashMap, Property<'gc>>, + values: PropertyMap<'gc, Property<'gc>>, /// Slots stored on this object. slots: Vec>, From c41519037635fd314c450b41e2ac853a9ebb9ceb Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 18 Jul 2020 16:48:10 -0400 Subject: [PATCH 185/189] Zero-index multinames should generate a validation error in `QName::from_abc_multiname`. --- core/src/avm2/names.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 2e8332413116..6595dc66df0f 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -128,6 +128,10 @@ impl<'gc> QName<'gc> { multiname_index: Index, mc: MutationContext<'gc, '_>, ) -> Result { + if multiname_index.0 == 0 { + return Err("Attempted to load a trait name of index zero".into()); + } + let actual_index = multiname_index.0 as usize - 1; let abc = translation_unit.abc(); let abc_multiname: Result<_, Error> = abc From 262bb148f1c8e5e8eaf9fd0ea7415c8a4fa9d441 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 18 Jul 2020 17:02:32 -0400 Subject: [PATCH 186/189] Rename `a2me` to `entry` (or `method` in one case where it lets me simplify a struct declaration) --- core/src/avm2/function.rs | 4 ++-- core/src/avm2/method.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 445782bad2ba..686a83230837 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -64,10 +64,10 @@ impl<'gc> Executable<'gc> { ) -> Self { match method { Method::Native(nf) => Self::Native(nf, reciever), - Method::Entry(a2me) => Self::Action(Gc::allocate( + Method::Entry(method) => Self::Action(Gc::allocate( mc, BytecodeExecutable { - method: a2me, + method, scope, reciever, }, diff --git a/core/src/avm2/method.rs b/core/src/avm2/method.rs index 6efbdb123572..b0daccdb98a3 100644 --- a/core/src/avm2/method.rs +++ b/core/src/avm2/method.rs @@ -134,7 +134,7 @@ unsafe impl<'gc> Collect for Method<'gc> { fn trace(&self, cc: CollectionContext) { match self { Method::Native(_nf) => {} - Method::Entry(a2me) => a2me.trace(cc), + Method::Entry(entry) => entry.trace(cc), } } } @@ -146,7 +146,7 @@ impl<'gc> fmt::Debug for Method<'gc> { .debug_tuple("Method::Native") .field(&"".to_string()) .finish(), - Method::Entry(a2me) => f.debug_tuple("Method::Entry").field(a2me).finish(), + Method::Entry(entry) => f.debug_tuple("Method::Entry").field(entry).finish(), } } } From 575a9b091a91695b229639770d91c8ea04588268 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 18 Jul 2020 17:02:52 -0400 Subject: [PATCH 187/189] Use FnvHashMap for the translation unit pools. --- core/src/avm2/script.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index eb94eb670090..bd58d7d0a7ea 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -5,8 +5,8 @@ use crate::avm2::method::{BytecodeMethod, Method}; use crate::avm2::r#trait::Trait; use crate::avm2::string::AvmString; use crate::avm2::Error; +use fnv::FnvHashMap; use gc_arena::{Collect, Gc, GcCell, MutationContext}; -use std::collections::HashMap; use std::mem::drop; use std::rc::Rc; use swf::avm2::types::{AbcFile, Index, Script as AbcScript}; @@ -38,16 +38,16 @@ pub struct TranslationUnitData<'gc> { abc: CollectWrapper>, /// All classes loaded from the ABC's class list. - classes: HashMap>>, + classes: FnvHashMap>>, /// All methods loaded from the ABC's method list. - methods: HashMap>, + methods: FnvHashMap>, /// All scripts loaded from the ABC's scripts list. - scripts: HashMap>>, + scripts: FnvHashMap>>, /// All strings loaded from the ABC's strings list. - strings: HashMap>, + strings: FnvHashMap>, } impl<'gc> TranslationUnit<'gc> { @@ -56,10 +56,10 @@ impl<'gc> TranslationUnit<'gc> { mc, TranslationUnitData { abc: CollectWrapper(abc), - classes: HashMap::new(), - methods: HashMap::new(), - scripts: HashMap::new(), - strings: HashMap::new(), + classes: FnvHashMap::default(), + methods: FnvHashMap::default(), + scripts: FnvHashMap::default(), + strings: FnvHashMap::default(), }, )) } From 44b924d7b4f1473f56d06499881ed3c1e8da9b58 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 18 Jul 2020 17:26:11 -0400 Subject: [PATCH 188/189] `Script` should not hold write locks when probing it's internal caches. --- core/src/avm2/script.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index bd58d7d0a7ea..8e41b83829bf 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -75,12 +75,12 @@ impl<'gc> TranslationUnit<'gc> { method_index: u32, mc: MutationContext<'gc, '_>, ) -> Result, Error> { - let write = self.0.write(mc); - if let Some(method) = write.methods.get(&method_index) { + let read = self.0.read(); + if let Some(method) = read.methods.get(&method_index) { return Ok(method.clone()); } - drop(write); + drop(read); let method: Result>, Error> = BytecodeMethod::from_method_index(self, Index::new(method_index), mc) @@ -101,12 +101,12 @@ impl<'gc> TranslationUnit<'gc> { class_index: u32, mc: MutationContext<'gc, '_>, ) -> Result>, Error> { - let write = self.0.write(mc); - if let Some(class) = write.classes.get(&class_index) { + let read = self.0.read(); + if let Some(class) = read.classes.get(&class_index) { return Ok(*class); } - drop(write); + drop(read); let class = Class::from_abc_index(self, class_index, mc)?; self.0.write(mc).classes.insert(class_index, class); @@ -122,12 +122,12 @@ impl<'gc> TranslationUnit<'gc> { script_index: u32, mc: MutationContext<'gc, '_>, ) -> Result>, Error> { - let write = self.0.write(mc); - if let Some(scripts) = write.scripts.get(&script_index) { + let read = self.0.read(); + if let Some(scripts) = read.scripts.get(&script_index) { return Ok(*scripts); } - drop(write); + drop(read); let script = Script::from_abc_index(self, script_index, mc)?; self.0.write(mc).scripts.insert(script_index, script); From 7adabc8166f8809a38950bb6c964ccb468e66ca9 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 18 Jul 2020 17:28:07 -0400 Subject: [PATCH 189/189] Use `unwrap_or_default` --- core/src/avm2/script.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index 8e41b83829bf..05fc6dfd366a 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -185,7 +185,7 @@ impl<'gc> TranslationUnit<'gc> { ) -> Result, Error> { Ok(self .pool_string_option(string_index, mc)? - .unwrap_or_else(|| "".into())) + .unwrap_or_default()) } }