diff --git a/core/src/avm1.rs b/core/src/avm1.rs index ee959f957..f074a7ac0 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -489,13 +489,14 @@ impl<'gc> Avm1<'gc> { pub fn current_register(&self, id: u8) -> Value<'gc> { if self .current_stack_frame() - .map(|sf| sf.read().has_local_registers()) + .map(|sf| sf.read().has_local_register(id)) .unwrap_or(false) { self.current_stack_frame() .unwrap() .read() .local_register(id) + .unwrap_or(Value::Undefined) } else { self.registers .get(id as usize) @@ -515,7 +516,7 @@ impl<'gc> Avm1<'gc> { ) { if self .current_stack_frame() - .map(|sf| sf.read().has_local_registers()) + .map(|sf| sf.read().has_local_register(id)) .unwrap_or(false) { self.current_stack_frame() diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index 09c559c8e..c748a8250 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -41,6 +41,10 @@ impl<'gc> RegisterSet<'gc> { pub fn get_mut(&mut self, num: u8) -> Option<&mut Value<'gc>> { self.0.get_mut(num as usize) } + + pub fn len(&self) -> u8 { + self.0.len() as u8 + } } /// Represents a single activation of a given AVM1 function or keyframe. @@ -275,25 +279,27 @@ impl<'gc> Activation<'gc> { pub fn this_cell(&self) -> GcCell<'gc, Object<'gc>> { self.this } - /// Returns true if this function was called with a local register set. - pub fn has_local_registers(&self) -> bool { - self.local_registers.is_some() + + /// Returns true if this activation has a given local register ID. + pub fn has_local_register(&self, id: u8) -> bool { + self.local_registers + .map(|rs| id < rs.read().len() - 1) + .unwrap_or(false) } pub fn allocate_local_registers(&mut self, num: u8, mc: MutationContext<'gc, '_>) { - self.local_registers = Some(GcCell::allocate(mc, RegisterSet::new(num))); + self.local_registers = match num { + 0 => None, + num => Some(GcCell::allocate(mc, RegisterSet::new(num))), + }; } /// Retrieve a local register. - pub fn local_register(&self, id: u8) -> Value<'gc> { + pub fn local_register(&self, id: u8) -> Option> { if let Some(local_registers) = self.local_registers { - local_registers - .read() - .get(id) - .cloned() - .unwrap_or(Value::Undefined) + local_registers.read().get(id).cloned() } else { - Value::Undefined + None } } diff --git a/core/src/avm1/function.rs b/core/src/avm1/function.rs index 302e7ec7a..77e612660 100644 --- a/core/src/avm1/function.rs +++ b/core/src/avm1/function.rs @@ -29,11 +29,8 @@ pub struct Avm1Function<'gc> { name: Option, /// The number of registers to allocate for this function's private register - /// set. - /// - /// If None, then no register set will be allocated and the preload options - /// have no effect. - register_count: Option, + /// set. Any register beyond this ID will be served from the global one. + register_count: u8, preload_parent: bool, preload_root: bool, @@ -75,7 +72,7 @@ impl<'gc> Avm1Function<'gc> { swf_version, data: actions, name, - register_count: None, + register_count: 0, preload_parent: false, preload_root: false, suppress_super: false, @@ -115,7 +112,7 @@ impl<'gc> Avm1Function<'gc> { swf_version, data: actions, name, - register_count: Some(swf_function.params.capacity() as u8), + register_count: swf_function.params.capacity() as u8, preload_parent: swf_function.preload_parent, preload_root: swf_function.preload_root, suppress_super: swf_function.suppress_super, @@ -142,7 +139,7 @@ impl<'gc> Avm1Function<'gc> { self.scope } - pub fn register_count(&self) -> Option { + pub fn register_count(&self) -> u8 { self.register_count } } @@ -215,52 +212,48 @@ impl<'gc> Executable<'gc> { Some(argcell), ); - if let Some(register_count) = af.register_count() { - frame.allocate_local_registers(register_count, ac.gc_context); + frame.allocate_local_registers(af.register_count(), ac.gc_context); - let mut preload_r = 1; + let mut preload_r = 1; - if af.preload_this { - //TODO: What happens if you specify both suppress and - //preload for this? - frame.set_local_register(preload_r, Value::Object(this), ac.gc_context); - preload_r += 1; - } + if af.preload_this { + //TODO: What happens if you specify both suppress and + //preload for this? + frame.set_local_register(preload_r, Value::Object(this), ac.gc_context); + preload_r += 1; + } - if af.preload_arguments { - //TODO: What happens if you specify both suppress and - //preload for arguments? - frame.set_local_register(preload_r, Value::Object(argcell), ac.gc_context); - preload_r += 1; - } + if af.preload_arguments { + //TODO: What happens if you specify both suppress and + //preload for arguments? + frame.set_local_register(preload_r, Value::Object(argcell), ac.gc_context); + preload_r += 1; + } - if af.preload_super { - //TODO: super not implemented - log::warn!( - "Cannot preload super into register because it's not implemented" - ); - //TODO: What happens if you specify both suppress and - //preload for super? - preload_r += 1; - } + if af.preload_super { + //TODO: super not implemented + log::warn!("Cannot preload super into register because it's not implemented"); + //TODO: What happens if you specify both suppress and + //preload for super? + preload_r += 1; + } - if af.preload_root { - frame.set_local_register(preload_r, avm.root_object(ac), ac.gc_context); - preload_r += 1; - } + if af.preload_root { + frame.set_local_register(preload_r, avm.root_object(ac), ac.gc_context); + preload_r += 1; + } - if af.preload_parent { - frame.set_local_register( - preload_r, - child_scope.read().resolve("_parent", avm, ac, this), - ac.gc_context, - ); - preload_r += 1; - } + if af.preload_parent { + frame.set_local_register( + preload_r, + child_scope.read().resolve("_parent", avm, ac, this), + ac.gc_context, + ); + preload_r += 1; + } - if af.preload_global { - frame.set_local_register(preload_r, avm.global_object(ac), ac.gc_context); - } + if af.preload_global { + frame.set_local_register(preload_r, avm.global_object(ac), ac.gc_context); } //TODO: What happens if the argument registers clash with the diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index e7d3da9a9..097eaa56f 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -53,6 +53,7 @@ swf_tests! { (delete, "avm1/delete", 3), (timeline_function_def, "avm1/timeline_function_def", 3), (root_global_parent, "avm1/root_global_parent", 3), + (register_underflow, "avm1/register_underflow", 1), } /// Loads an SWF and runs it through the Ruffle core for a number of frames. diff --git a/core/tests/swfs/avm1/register_underflow/output.txt b/core/tests/swfs/avm1/register_underflow/output.txt new file mode 100644 index 000000000..52243d5d7 --- /dev/null +++ b/core/tests/swfs/avm1/register_underflow/output.txt @@ -0,0 +1,15 @@ +global reg 0: 0 +global reg 1: 1 +global reg 2: 2 +global reg 3: 3 +Function start f(a), RegisterCount = 3, a => register2 +Function reg 0: undefined +Function reg 1: undefined +Function reg 2: 66 +Function reg 3: 3 +Changing registers to 9. +Function end +global reg 0: 0 +global reg 1: 1 +global reg 2: 2 +global reg 3: 9 diff --git a/core/tests/swfs/avm1/register_underflow/test.swf b/core/tests/swfs/avm1/register_underflow/test.swf new file mode 100644 index 000000000..6d5d28b02 Binary files /dev/null and b/core/tests/swfs/avm1/register_underflow/test.swf differ