Repository: ruffle-rs/ruffle (2026-03-07T16:34:13Z)

Issue:
Error on https://flashga.me/embed/cannon-experiment
# Error Info
Error name: Error
Error message: panicked at core/src/avm2/object/array_object.rs:105:34:
RefCell already mutably borrowed

# Player Info
Allows script access: true
Renderer: wgpu
Adapter Backend: Gl
Adapter Name: "Apple M1, or similar"
Adapter Device Type: IntegratedGpu
Adapter Driver Name: ""
Adapter Driver Info: "WebGL 2.0"
Enabled features: Features { features_wgpu: FeaturesWGPU(TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES), features_webgpu: FeaturesWebGPU(FLOAT32_FILTERABLE) }
Available features: Features { features_wgpu: FeaturesWGPU(PUSH_CONSTANTS | CLEAR_TEXTURE), features_webgpu: FeaturesWebGPU(DEPTH32FLOAT_STENCIL8) }
Current limits: Limits { max_texture_dimension_1d: 8192, max_texture_dimension_2d: 8192, max_texture_dimension_3d: 2048, max_texture_array_layers: 256, max_bind_groups: 4, max_bindings_per_bind_group: 1000, max_dynamic_uniform_buffers_per_pipeline_layout: 8, max_dynamic_storage_buffers_per_pipeline_layout: 0, max_sampled_textures_per_shader_stage: 16, max_samplers_per_shader_stage: 16, max_storage_buffers_per_shader_stage: 0, max_storage_textures_per_shader_stage: 0, max_uniform_buffers_per_shader_stage: 11, max_binding_array_elements_per_shader_stage: 0, max_binding_array_sampler_elements_per_shader_stage: 0, max_uniform_buffer_binding_size: 65536, max_storage_buffer_binding_size: 0, max_vertex_buffers: 8, max_buffer_size: 268435456, max_vertex_attributes: 16, max_vertex_buffer_array_stride: 255, min_uniform_buffer_offset_alignment: 256, min_storage_buffer_offset_alignment: 256, max_inter_stage_shader_components: 124, max_color_attachments: 4, max_color_attachment_bytes_per_sample: 32, max_compute_workgroup_storage_size: 0, max_compute_invocations_per_workgroup: 0, max_compute_workgroup_size_x: 0, max_compute_workgroup_size_y: 0, max_compute_workgroup_size_z: 0, max_compute_workgroups_per_dimension: 0, min_subgroup_size: 0, max_subgroup_size: 0, max_push_constant_size: 0, max_non_sampler_bindings: 1000000, max_blas_primitive_count: 0, max_blas_geometry_count: 0, max_tlas_instance_count: 0, max_acceleration_structures_per_shader_stage: 0 }
Surface quality: high
Surface samples: 4
Surface size: (550, 400, 1)

# Page Info
Page URL: https://flashga.me/embed/cannon-experiment?emulator=ruffle&game=%7B%22id%22%3A64875%2C%22name%22%3A%22cannon+experiment%22%2C%22slug%22%3A%22cannon-experiment%22%2C%22file%22%3A%22%5B%7B%5C%22download_link%5C%22%3A%5C%22games%5C%5C%5C%2FOctober2023%5C%5C%5C%2Fcannon_experiment.swf%5C%22%2C%5C%22original_name%5C%22%3A%5C%22cannon_experiment.swf%5C%22%7D%5D%22%2C%22alt_file%22%3A%22https%3A%5C%2F%5C%2Fflashga.me%22%2C%22alt_game%22%3Anull%2C%22width%22%3A720%2C%22height%22%3A500%2C%22origin%22%3Anull%2C%22extension%22%3A%22SWF%22%2C%22flashversion%22%3Anull%2C%22image%22%3A%22games%5C%2FOctober2023%5C%2FuhSPB6ZQHUwoY0pkHZpw.jpg%22%2C%22playable%22%3A1%2C%22resizable%22%3A1%2C%22updatedAt%22%3Anull%2C%22avm%22%3A2%2C%22flashvars%22%3Anull%2C%22emulator%22%3A%22ruffle%22%2C%22orientation%22%3A%22landscape%22%2C%22embed_options%22%3A%22%7B%5C%22wmode%5C%22%3A%5C%22opaque%5C%22%2C+%5C%22allowNetworking%5C%22%3A+%5C%22internal%5C%22%2C+%5C%22allowScriptAccess%5C%22%3A+%5C%22no%5C%22%7D%22%2C%22name_mvkb%22%3A%22%7B%5Cr%5Cn%5C%22KeyR%5C%22%3A+%7B%5C%22name%5C%22%3A+%5C%22Restart%5C%22%2C+%5C%22width%5C%22%3A+75%2C+%5C%22height%5C%22%3A+75%2C+%5C%22round%5C%22%3A+true%2C+%5C%22fontSize%5C%22%3A+13%2C+%5C%22title%5C%22%3A+%5C%22R%5C%22%7D%5Cr%5Cn%7D%22%2C%22style_mvkb%22%3Anull%2C%22left_mvkb%22%3A%22%5B%5C%2282%5C%22%5D%22%2C%22right_mvkb%22%3A%22%5B%5D%22%2C%22category_slug%22%3A%22cannon%22%2C%22updated_at%22%3Anull%2C%22star_user%22%3A1%7D
SWF URL: blob:https://flashga.me/463f16fb-3dae-4e39-8b2d-a9a104225301

# Browser Info
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:144.0) Gecko/20100101 Firefox/144.0
Platform: MacIntel
Has touch support: false

# Ruffle Info
Version: 0.2.0-nightly.2025.10.15
Name: 0.2.0-nightly.2025.10.15
Channel: nightly
Built: 2025-10-15T00:09:11.786Z
Commit: 233a781608541521ff87de9afea520a5f20b353f
Is extension: false

# Metadata
width: 720
height: 500
frameRate: 30
numFrames: 2
swfVersion: 10
backgroundColor: undefined
isActionScript3: true
uncompressedLength: 3751140

Error on https://uploads.ungrounded.net/ruffle_wrapper/ruffleembed.html
Note - this error happens when you reach the first batch of collectable marbles.

# Error Info
Error name: Error
Error message: panicked at core/src/avm2/object/array_object.rs:106:34:
RefCell already mutably borrowed
Error stack:
```
Error: panicked at core/src/avm2/object/array_object.rs:106:34:
RefCell already mutably borrowed
    at Ve.e.wbg.__wbg_new_476169e6d59f23ae (https://uploads.ungrounded.net/ruffle_wrapper/lib/core.ruffle.0e86a6287ac454811aba.js:1:56964)
    at ruffle_web.wasm.__wbg_new_476169e6d59f23ae externref shim (wasm://wasm/ruffle_web.wasm-03220f56:wasm-function[11957]:0x955156)
    at ruffle_web.wasm.ruffle_web::global_init::{{closure}}::he6c77c0821b4ab34 (wasm://wasm/ruffle_web.wasm-03220f56:wasm-function[10126]:0x92dac6)
    at ruffle_web.wasm.std::panicking::panic_with_hook::h2f3f743d7642d6f0 (wasm://wasm/ruffle_web.wasm-03220f56:wasm-function[6776]:0x874058)
    at ruffle_web.wasm.core::panicking::panic_fmt::h0ce8f0f8ae811b17 (wasm://wasm/ruffle_web.wasm-03220f56:wasm-function[11045]:0x947d53)
    at ruffle_web.wasm.core::cell::panic_already_mutably_borrowed::h43d67543c7077fb5 (wasm://wasm/ruffle_web.wasm-03220f56:wasm-function[13265]:0x976666)
    at ruffle_web.wasm.ruffle_core::avm2::globals::array::join::hb9e4b2cdaba0fde8 (wasm://wasm/ruffle_web.wasm-03220f56:wasm-function[1888]:0x4cc3ff)
    at ruffle_web.wasm.ruffle_core::avm2::function::exec::hd1f5064c956bfc5e (wasm://wasm/ruffle_web.wasm-03220f56:wasm-function[1608]:0x43f82e)
    at ruffle_web.wasm.ruffle_core::avm2::value::Value::call_method_with_args::h2a2f5f784141c77e (wasm://wasm/ruffle_web.wasm-03220f56:wasm-function[3150]:0x665dff)
    at ruffle_web.wasm.ruffle_core::avm2::function::exec::hd1f5064c956bfc5e (wasm://wasm/ruffle_web.wasm-03220f56:wasm-function[1608]:0x43131c)
```
AVM2 stack:
```
    at Array/http://adobe.com/AS3/2006/builtin::join()
    at MethodInfo-66()
    at Array/http://adobe.com/AS3/2006/builtin::splice()
    at custom.props::Marble/removeMe()
    at custom.props::Marble/spriteStop()
    at flash.display::MovieClip/gotoAndStop()
    at custom.players::PlayerSprite/update()
    at custom.players::Hero/update()
    at Game$/update()
```

# Player Info
Allows script access: true
Renderer: wgpu
Adapter Backend: Gl
Adapter Name: "ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 (0x00002206) Direct3D11 vs_5_0 ps_5_0, D3D11)"
Adapter Device Type: Other
Adapter Driver Name: ""
Adapter Driver Info: "WebGL 2.0 (OpenGL ES 3.0 Chromium)"
Enabled features: Features { features_wgpu: FeaturesWGPU(TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES), features_webgpu: FeaturesWebGPU(TEXTURE_COMPRESSION_BC | FLOAT32_FILTERABLE) }
Available features: Features { features_wgpu: FeaturesWGPU(PUSH_CONSTANTS | CLEAR_TEXTURE | MULTIVIEW), features_webgpu: FeaturesWebGPU(DEPTH32FLOAT_STENCIL8 | TEXTURE_COMPRESSION_BC_SLICED_3D) }
Current limits: Limits { max_texture_dimension_1d: 16384, max_texture_dimension_2d: 16384, max_texture_dimension_3d: 2048, max_texture_array_layers: 256, max_bind_groups: 4, max_bindings_per_bind_group: 1000, max_dynamic_uniform_buffers_per_pipeline_layout: 8, max_dynamic_storage_buffers_per_pipeline_layout: 0, max_sampled_textures_per_shader_stage: 16, max_samplers_per_shader_stage: 16, max_storage_buffers_per_shader_stage: 0, max_storage_textures_per_shader_stage: 0, max_uniform_buffers_per_shader_stage: 11, max_binding_array_elements_per_shader_stage: 0, max_binding_array_sampler_elements_per_shader_stage: 0, max_uniform_buffer_binding_size: 65536, max_storage_buffer_binding_size: 0, max_vertex_buffers: 8, max_buffer_size: 268435456, max_vertex_attributes: 16, max_vertex_buffer_array_stride: 255, min_uniform_buffer_offset_alignment: 256, min_storage_buffer_offset_alignment: 256, max_inter_stage_shader_components: 120, max_color_attachments: 4, max_color_attachment_bytes_per_sample: 32, max_compute_workgroup_storage_size: 0, max_compute_invocations_per_workgroup: 0, max_compute_workgroup_size_x: 0, max_compute_workgroup_size_y: 0, max_compute_workgroup_size_z: 0, max_compute_workgroups_per_dimension: 0, min_subgroup_size: 0, max_subgroup_size: 0, max_push_constant_size: 0, max_non_sampler_bindings: 1000000, max_blas_primitive_count: 0, max_blas_geometry_count: 0, max_tlas_instance_count: 0, max_acceleration_structures_per_shader_stage: 0 }
Surface quality: high
Surface samples: 4
Surface size: (550, 400, 1)

# Page Info
Page URL: https://uploads.ungrounded.net/ruffle_wrapper/ruffleembed.html?v=1764088390&browsermode=default&props=%7B%22ruffle%22%3A%22%2Fruffle_wrapper%2Flib%2Fruffle.js%3F1763442986%22%2C%22public_path%22%3A%22%2Fruffle_wrapper%2Flib%2F%22%2C%22swf%22%3A%22http%3A%2F%2Fuploads.ungrounded.net%2F650000%2F650363_dynapuff-jump.swf%3F1418781021%22%2C%22vars%22%3A%7B%22ngio_session_id%22%3A%2244330352.26892064ad529645f3c1a30d4c4d3dd0c1df7d54a70157%22%2C%22NewgroundsAPI_PublisherID%22%3A1%2C%22NewgroundsAPI_SandboxID%22%3A%226925da460410e%22%2C%22NewgroundsAPI_SessionID%22%3A%22HilC5oa6RxVmHTxzrJSJ356a192b7913b04c54574d18c28d46e6395428abrX7n%22%2C%22NewgroundsAPI_UserName%22%3A%22TomFulp%22%2C%22NewgroundsAPI_UserID%22%3A1%2C%22ng_username%22%3A%22TomFulp%22%7D%2C%22width%22%3A800%2C%22height%22%3A450%2C%22icon%22%3A%22https%3A%2F%2Fpicon.ngfiles.com%2F650000%2Fflash_650363_card.png%3Ff1601077457%22%2C%22warnOnUnsupportedContent%22%3Atrue%2C%22external%22%3A%22%2Fruffle_wrapper%2Fexternal%2Fruffle-external-input.js%3F1764073049%22%7D
SWF URL: http://uploads.ungrounded.net/650000/650363_dynapuff-jump.swf?1418781021

# Browser Info
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Platform: Win32
Has touch support: false

# Ruffle Info
Version: 0.2.0-nightly.2025.11.18
Name: 0.2.0-nightly.2025.11.18
Channel: nightly
Built: 2025-11-18T00:07:53.721Z
Commit: 3a9b35ecb0e12d01ec58fe5e5b07c45663bc79f8
Is extension: false

# Metadata
width: 800
height: 450
frameRate: 30
numFrames: 2
swfVersion: 21
backgroundColor: undefined
isActionScript3: true
uncompressedLength: 9297226



Discussion:

Patch:
diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs
index 2033ed8860a1..e4f6ba8b1219 100644
--- a/core/src/avm2/globals/array.rs
+++ b/core/src/avm2/globals/array.rs
@@ -664,14 +664,16 @@ pub fn splice<'gc>(
 ) -> Result<Value<'gc>, Error<'gc>> {
     let this = this.as_object().unwrap();
 
-    let Some(mut array_storage) = this.as_array_storage_mut(activation.gc()) else {
+    let Some(array_object) = this.as_array_object() else {
         return Ok(Value::Undefined);
     };
-    let array_length = array_storage.length();
+
     let Some(start) = args.get_optional(0) else {
         return Ok(Value::Undefined);
     };
 
+    let array_length = array_object.storage().length();
+
     let actual_start = resolve_index(activation, start, array_length)?;
     let delete_count = args
         .get_optional(1)
@@ -684,11 +686,17 @@ pub fn splice<'gc>(
     // FIXME Flash does not iterate over those elements like we do, it's too
     //   inefficient. Flash probably iterates through set properties only.
     for i in actual_start..array_length {
-        let item = array_storage.get(i);
-        array_storage.set(i, resolve_array_hole(activation, this, i, item)?);
+        let item = array_object.storage().get(i);
+        let resolved = resolve_array_hole(activation, this, i, item)?;
+        array_object.storage_mut(activation.gc()).set(i, resolved);
     }
 
-    let ret = array_storage.splice(actual_start, delete_count as usize, args_slice);
+    let ret = array_object.storage_mut(activation.gc()).splice(
+        actual_start,
+        delete_count as usize,
+        args_slice,
+    );
+
     Ok(build_array(activation, ret))
 }
 


Test patch:
diff --git a/tests/tests/swfs/avm2/array_reborrow_bug/Test.as b/tests/tests/swfs/avm2/array_reborrow_bug/Test.as
new file mode 100644
index 000000000000..679c72176937
--- /dev/null
+++ b/tests/tests/swfs/avm2/array_reborrow_bug/Test.as
@@ -0,0 +1,27 @@
+package {
+	import flash.display.Sprite;
+
+	public class Test extends Sprite {
+		public function Test() {
+			// Test splice with sneaky valueOf
+			var arr:Array = ["a", "b", "c", "d"];
+			var sneakyIndex:Object = {
+				valueOf: function():int {
+					trace("valueOf for index called");
+					trace("array length during valueOf: " + arr.length);
+					return 1;
+				}
+			};
+			var sneakyCount:Object = {
+				valueOf: function():int {
+					trace("valueOf for count called");
+					trace("array length during valueOf: " + arr.length);
+					return 2;
+				}
+			};
+			trace("before splice: " + arr);
+			arr.splice(sneakyIndex, sneakyCount, "new");
+			trace("after splice: " + arr);
+		}
+	}
+}
diff --git a/tests/tests/swfs/avm2/array_reborrow_bug/output.txt b/tests/tests/swfs/avm2/array_reborrow_bug/output.txt
new file mode 100644
index 000000000000..21e8df30c959
--- /dev/null
+++ b/tests/tests/swfs/avm2/array_reborrow_bug/output.txt
@@ -0,0 +1,6 @@
+before splice: a,b,c,d
+valueOf for index called
+array length during valueOf: 4
+valueOf for count called
+array length during valueOf: 4
+after splice: a,new,d
diff --git a/tests/tests/swfs/avm2/array_reborrow_bug/test.swf b/tests/tests/swfs/avm2/array_reborrow_bug/test.swf
new file mode 100644
index 000000000000..a15cff07bab3
Binary files /dev/null and b/tests/tests/swfs/avm2/array_reborrow_bug/test.swf differ
diff --git a/tests/tests/swfs/avm2/array_reborrow_bug/test.toml b/tests/tests/swfs/avm2/array_reborrow_bug/test.toml
new file mode 100644
index 000000000000..dbee897f5863
--- /dev/null
+++ b/tests/tests/swfs/avm2/array_reborrow_bug/test.toml
@@ -0,0 +1,1 @@
+num_frames = 1