From 7c6396debf36d6e3243454af06e819f1cd4d9bb9 Mon Sep 17 00:00:00 2001 From: Ben Visness Date: Tue, 26 May 2026 10:43:43 -0500 Subject: [PATCH 1/2] Improve test coverage around data.drop There were very few tests around data.drop, which can be strange to handle in engines due to bounds check behavior. In particular, this adds a test that would have caught a minor bounds check bug in SpiderMonkey. --- test/core/bulk-memory/memory_init.wast | 41 +++++++++++++++++++- test/core/memory64/memory_init64.wast | 41 +++++++++++++++++++- test/meta/generate_memory_init.js | 53 +++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 4 deletions(-) diff --git a/test/core/bulk-memory/memory_init.wast b/test/core/bulk-memory/memory_init.wast index 672b1c5013..2a8284c652 100644 --- a/test/core/bulk-memory/memory_init.wast +++ b/test/core/bulk-memory/memory_init.wast @@ -208,12 +208,51 @@ (data.drop 0))) (invoke "test") +(module + (memory 1) + (data "\37") + (func (export "test") + (memory.init 0 (i32.const 1234) (i32.const 0) (i32.const 1)))) +(invoke "test") + (module (memory 1) (data "\37") (func (export "test") (data.drop 0) - (memory.init 0 (i32.const 1234) (i32.const 1) (i32.const 1)))) + (memory.init 0 (i32.const 1234) (i32.const 0) (i32.const 1)))) +(assert_trap (invoke "test") "out of bounds memory access") + +(module + (memory 1) + (data "\37") + (func (export "test") + (data.drop 0) + (memory.init 0 (i32.const 1234) (i32.const 0) (i32.const 0)))) +(invoke "test") + +(module + (memory 1) + (data "\37") + (func (export "test") + (data.drop 0) + (memory.init 0 (i32.const 1234) (i32.const 1) (i32.const 0)))) +(assert_trap (invoke "test") "out of bounds memory access") + +(module + (memory 1) + (data "\37") + (func (export "test") + (data.drop 0) + (memory.init 0 (i32.const 0x10000) (i32.const 0) (i32.const 0)))) +(invoke "test") + +(module + (memory 1) + (data "\37") + (func (export "test") + (data.drop 0) + (memory.init 0 (i32.const 0x10001) (i32.const 0) (i32.const 0)))) (assert_trap (invoke "test") "out of bounds memory access") (module diff --git a/test/core/memory64/memory_init64.wast b/test/core/memory64/memory_init64.wast index cdae69a39d..8707bee102 100644 --- a/test/core/memory64/memory_init64.wast +++ b/test/core/memory64/memory_init64.wast @@ -208,12 +208,51 @@ (data.drop 0))) (invoke "test") +(module + (memory i64 1) + (data "\37") + (func (export "test") + (memory.init 0 (i64.const 1234) (i32.const 0) (i32.const 1)))) +(invoke "test") + (module (memory i64 1) (data "\37") (func (export "test") (data.drop 0) - (memory.init 0 (i64.const 1234) (i32.const 1) (i32.const 1)))) + (memory.init 0 (i64.const 1234) (i32.const 0) (i32.const 1)))) +(assert_trap (invoke "test") "out of bounds memory access") + +(module + (memory i64 1) + (data "\37") + (func (export "test") + (data.drop 0) + (memory.init 0 (i64.const 1234) (i32.const 0) (i32.const 0)))) +(invoke "test") + +(module + (memory i64 1) + (data "\37") + (func (export "test") + (data.drop 0) + (memory.init 0 (i64.const 1234) (i32.const 1) (i32.const 0)))) +(assert_trap (invoke "test") "out of bounds memory access") + +(module + (memory i64 1) + (data "\37") + (func (export "test") + (data.drop 0) + (memory.init 0 (i64.const 0x10000) (i32.const 0) (i32.const 0)))) +(invoke "test") + +(module + (memory i64 1) + (data "\37") + (func (export "test") + (data.drop 0) + (memory.init 0 (i64.const 0x10001) (i32.const 0) (i32.const 0)))) (assert_trap (invoke "test") "out of bounds memory access") (module diff --git a/test/meta/generate_memory_init.js b/test/meta/generate_memory_init.js index 5c7f797e16..7c93180c5c 100644 --- a/test/meta/generate_memory_init.js +++ b/test/meta/generate_memory_init.js @@ -91,13 +91,62 @@ print( (invoke "test") `); -// drop, then init +// init from a passive segment succeeds... +print( +`(module + ${PREAMBLE} + (func (export "test") + (memory.init 0 (${memtype}.const 1234) (i32.const 0) (i32.const 1)))) +(invoke "test") +`); + +// ...but dropping the segment first makes the same init trap +print( +`(module + ${PREAMBLE} + (func (export "test") + (data.drop 0) + (memory.init 0 (${memtype}.const 1234) (i32.const 0) (i32.const 1)))) +(assert_trap (invoke "test") "out of bounds memory access") +`); + +// drop, then init: zero length at zero src and in-range dst is OK +print( +`(module + ${PREAMBLE} + (func (export "test") + (data.drop 0) + (memory.init 0 (${memtype}.const 1234) (i32.const 0) (i32.const 0)))) +(invoke "test") +`); + +// drop, then init: zero length, src offset past dropped seg end is invalid +print( +`(module + ${PREAMBLE} + (func (export "test") + (data.drop 0) + (memory.init 0 (${memtype}.const 1234) (i32.const 1) (i32.const 0)))) +(assert_trap (invoke "test") "out of bounds memory access") +`); + +// drop, then init: zero length, dst offset at end of memory is OK print( `(module ${PREAMBLE} (func (export "test") (data.drop 0) - (memory.init 0 (${memtype}.const 1234) (i32.const 1) (i32.const 1)))) + (memory.init 0 (${memtype}.const 0x10000) (i32.const 0) (i32.const 0)))) +(invoke "test") +`); + +// drop, then init: zero length, dst offset past end of memory is invalid +print( +`(module + ${PREAMBLE} + (func (export "test") + (data.drop 0) + (memory.init 0 (${memtype}.const 0x10001) (i32.const 0) (i32.const 0)))) (assert_trap (invoke "test") "out of bounds memory access") `); From f77e53d0026aa41cc6af70c9fe48cc13152f8ec6 Mon Sep 17 00:00:00 2001 From: Ben Visness Date: Tue, 26 May 2026 13:00:00 -0500 Subject: [PATCH 2/2] Add analogous tests for elem.drop --- test/core/bulk-memory/table_init.wast | 120 +++++++++++++++++++++ test/core/memory64/table_init64.wast | 149 ++++++++++++++++++++++++-- test/meta/generate_table_init.js | 55 +++++++++- 3 files changed, 314 insertions(+), 10 deletions(-) diff --git a/test/core/bulk-memory/table_init.wast b/test/core/bulk-memory/table_init.wast index 45de5aa50f..e74c56f34c 100644 --- a/test/core/bulk-memory/table_init.wast +++ b/test/core/bulk-memory/table_init.wast @@ -506,6 +506,30 @@ (elem.drop 1))) (invoke "test") +(module + (table $t0 30 30 funcref) + (table $t1 28 28 funcref) + (elem (table $t0) (i32.const 2) func 3 1 4 1) + (elem funcref + (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) + (elem (table $t0) (i32.const 12) func 7 5 2 3 6) + (elem funcref + (ref.func 5) (ref.func 9) (ref.func 2) (ref.func 7) (ref.func 6)) + (func (result i32) (i32.const 0)) + (func (result i32) (i32.const 1)) + (func (result i32) (i32.const 2)) + (func (result i32) (i32.const 3)) + (func (result i32) (i32.const 4)) + (func (result i32) (i32.const 5)) + (func (result i32) (i32.const 6)) + (func (result i32) (i32.const 7)) + (func (result i32) (i32.const 8)) + (func (result i32) (i32.const 9)) + (func (export "test") + (table.init 1 (i32.const 12) (i32.const 1) (i32.const 1)) + )) +(invoke "test") + (module (table $t0 30 30 funcref) (table $t1 28 28 funcref) @@ -530,6 +554,102 @@ (table.init 1 (i32.const 12) (i32.const 1) (i32.const 1)))) (assert_trap (invoke "test") "out of bounds table access") +(module + (table $t0 30 30 funcref) + (table $t1 28 28 funcref) + (elem (table $t0) (i32.const 2) func 3 1 4 1) + (elem funcref + (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) + (elem (table $t0) (i32.const 12) func 7 5 2 3 6) + (elem funcref + (ref.func 5) (ref.func 9) (ref.func 2) (ref.func 7) (ref.func 6)) + (func (result i32) (i32.const 0)) + (func (result i32) (i32.const 1)) + (func (result i32) (i32.const 2)) + (func (result i32) (i32.const 3)) + (func (result i32) (i32.const 4)) + (func (result i32) (i32.const 5)) + (func (result i32) (i32.const 6)) + (func (result i32) (i32.const 7)) + (func (result i32) (i32.const 8)) + (func (result i32) (i32.const 9)) + (func (export "test") + (elem.drop 1) + (table.init 1 (i32.const 12) (i32.const 0) (i32.const 0)))) +(invoke "test") + +(module + (table $t0 30 30 funcref) + (table $t1 28 28 funcref) + (elem (table $t0) (i32.const 2) func 3 1 4 1) + (elem funcref + (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) + (elem (table $t0) (i32.const 12) func 7 5 2 3 6) + (elem funcref + (ref.func 5) (ref.func 9) (ref.func 2) (ref.func 7) (ref.func 6)) + (func (result i32) (i32.const 0)) + (func (result i32) (i32.const 1)) + (func (result i32) (i32.const 2)) + (func (result i32) (i32.const 3)) + (func (result i32) (i32.const 4)) + (func (result i32) (i32.const 5)) + (func (result i32) (i32.const 6)) + (func (result i32) (i32.const 7)) + (func (result i32) (i32.const 8)) + (func (result i32) (i32.const 9)) + (func (export "test") + (elem.drop 1) + (table.init 1 (i32.const 12) (i32.const 1) (i32.const 0)))) +(assert_trap (invoke "test") "out of bounds table access") + +(module + (table $t0 30 30 funcref) + (table $t1 28 28 funcref) + (elem (table $t0) (i32.const 2) func 3 1 4 1) + (elem funcref + (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) + (elem (table $t0) (i32.const 12) func 7 5 2 3 6) + (elem funcref + (ref.func 5) (ref.func 9) (ref.func 2) (ref.func 7) (ref.func 6)) + (func (result i32) (i32.const 0)) + (func (result i32) (i32.const 1)) + (func (result i32) (i32.const 2)) + (func (result i32) (i32.const 3)) + (func (result i32) (i32.const 4)) + (func (result i32) (i32.const 5)) + (func (result i32) (i32.const 6)) + (func (result i32) (i32.const 7)) + (func (result i32) (i32.const 8)) + (func (result i32) (i32.const 9)) + (func (export "test") + (elem.drop 1) + (table.init 1 (i32.const 30) (i32.const 0) (i32.const 0)))) +(invoke "test") + +(module + (table $t0 30 30 funcref) + (table $t1 28 28 funcref) + (elem (table $t0) (i32.const 2) func 3 1 4 1) + (elem funcref + (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) + (elem (table $t0) (i32.const 12) func 7 5 2 3 6) + (elem funcref + (ref.func 5) (ref.func 9) (ref.func 2) (ref.func 7) (ref.func 6)) + (func (result i32) (i32.const 0)) + (func (result i32) (i32.const 1)) + (func (result i32) (i32.const 2)) + (func (result i32) (i32.const 3)) + (func (result i32) (i32.const 4)) + (func (result i32) (i32.const 5)) + (func (result i32) (i32.const 6)) + (func (result i32) (i32.const 7)) + (func (result i32) (i32.const 8)) + (func (result i32) (i32.const 9)) + (func (export "test") + (elem.drop 1) + (table.init 1 (i32.const 31) (i32.const 0) (i32.const 0)))) +(assert_trap (invoke "test") "out of bounds table access") + (module (table $t0 30 30 funcref) (table $t1 28 28 funcref) diff --git a/test/core/memory64/table_init64.wast b/test/core/memory64/table_init64.wast index dff1da38e6..b631bb5b6a 100644 --- a/test/core/memory64/table_init64.wast +++ b/test/core/memory64/table_init64.wast @@ -21,7 +21,7 @@ (import "a" "ef4" (func (result i32))) ;; index 4 (table $t0 30 30 funcref) (table $t1 30 30 funcref) - + (elem (table $t0) (i32.const 2) func 3 1 4 1) (elem funcref (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) @@ -80,7 +80,7 @@ (import "a" "ef4" (func (result i32))) ;; index 4 (table $t0 30 30 funcref) (table $t1 30 30 funcref) - + (elem (table $t0) (i32.const 2) func 3 1 4 1) (elem funcref (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) @@ -139,7 +139,7 @@ (import "a" "ef4" (func (result i32))) ;; index 4 (table $t0 30 30 funcref) (table $t1 30 30 funcref) - + (elem (table $t0) (i32.const 2) func 3 1 4 1) (elem funcref (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) @@ -206,7 +206,7 @@ (import "a" "ef4" (func (result i32))) ;; index 4 (table $t0 30 30 funcref) (table $t1 30 30 funcref) - + (elem (table $t1) (i32.const 2) func 3 1 4 1) (elem funcref (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) @@ -265,7 +265,7 @@ (import "a" "ef4" (func (result i32))) ;; index 4 (table $t0 30 30 funcref) (table $t1 30 30 funcref) - + (elem (table $t1) (i32.const 2) func 3 1 4 1) (elem funcref (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) @@ -324,7 +324,7 @@ (import "a" "ef4" (func (result i32))) ;; index 4 (table $t0 30 30 funcref) (table $t1 30 30 funcref) - + (elem (table $t1) (i32.const 2) func 3 1 4 1) (elem funcref (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) @@ -691,6 +691,30 @@ (elem.drop 1))) (invoke "test") +(module + (table $t0 30 30 funcref) + (table $t1 28 28 funcref) + (elem (table $t0) (i32.const 2) func 3 1 4 1) + (elem funcref + (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) + (elem (table $t0) (i32.const 12) func 7 5 2 3 6) + (elem funcref + (ref.func 5) (ref.func 9) (ref.func 2) (ref.func 7) (ref.func 6)) + (func (result i32) (i32.const 0)) + (func (result i32) (i32.const 1)) + (func (result i32) (i32.const 2)) + (func (result i32) (i32.const 3)) + (func (result i32) (i32.const 4)) + (func (result i32) (i32.const 5)) + (func (result i32) (i32.const 6)) + (func (result i32) (i32.const 7)) + (func (result i32) (i32.const 8)) + (func (result i32) (i32.const 9)) + (func (export "test") + (table.init 1 (i32.const 12) (i32.const 1) (i32.const 1)) + )) +(invoke "test") + (module (table $t0 30 30 funcref) (table $t1 28 28 funcref) @@ -715,6 +739,102 @@ (table.init 1 (i32.const 12) (i32.const 1) (i32.const 1)))) (assert_trap (invoke "test") "out of bounds table access") +(module + (table $t0 30 30 funcref) + (table $t1 28 28 funcref) + (elem (table $t0) (i32.const 2) func 3 1 4 1) + (elem funcref + (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) + (elem (table $t0) (i32.const 12) func 7 5 2 3 6) + (elem funcref + (ref.func 5) (ref.func 9) (ref.func 2) (ref.func 7) (ref.func 6)) + (func (result i32) (i32.const 0)) + (func (result i32) (i32.const 1)) + (func (result i32) (i32.const 2)) + (func (result i32) (i32.const 3)) + (func (result i32) (i32.const 4)) + (func (result i32) (i32.const 5)) + (func (result i32) (i32.const 6)) + (func (result i32) (i32.const 7)) + (func (result i32) (i32.const 8)) + (func (result i32) (i32.const 9)) + (func (export "test") + (elem.drop 1) + (table.init 1 (i32.const 12) (i32.const 0) (i32.const 0)))) +(invoke "test") + +(module + (table $t0 30 30 funcref) + (table $t1 28 28 funcref) + (elem (table $t0) (i32.const 2) func 3 1 4 1) + (elem funcref + (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) + (elem (table $t0) (i32.const 12) func 7 5 2 3 6) + (elem funcref + (ref.func 5) (ref.func 9) (ref.func 2) (ref.func 7) (ref.func 6)) + (func (result i32) (i32.const 0)) + (func (result i32) (i32.const 1)) + (func (result i32) (i32.const 2)) + (func (result i32) (i32.const 3)) + (func (result i32) (i32.const 4)) + (func (result i32) (i32.const 5)) + (func (result i32) (i32.const 6)) + (func (result i32) (i32.const 7)) + (func (result i32) (i32.const 8)) + (func (result i32) (i32.const 9)) + (func (export "test") + (elem.drop 1) + (table.init 1 (i32.const 12) (i32.const 1) (i32.const 0)))) +(assert_trap (invoke "test") "out of bounds table access") + +(module + (table $t0 30 30 funcref) + (table $t1 28 28 funcref) + (elem (table $t0) (i32.const 2) func 3 1 4 1) + (elem funcref + (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) + (elem (table $t0) (i32.const 12) func 7 5 2 3 6) + (elem funcref + (ref.func 5) (ref.func 9) (ref.func 2) (ref.func 7) (ref.func 6)) + (func (result i32) (i32.const 0)) + (func (result i32) (i32.const 1)) + (func (result i32) (i32.const 2)) + (func (result i32) (i32.const 3)) + (func (result i32) (i32.const 4)) + (func (result i32) (i32.const 5)) + (func (result i32) (i32.const 6)) + (func (result i32) (i32.const 7)) + (func (result i32) (i32.const 8)) + (func (result i32) (i32.const 9)) + (func (export "test") + (elem.drop 1) + (table.init 1 (i32.const 30) (i32.const 0) (i32.const 0)))) +(invoke "test") + +(module + (table $t0 30 30 funcref) + (table $t1 28 28 funcref) + (elem (table $t0) (i32.const 2) func 3 1 4 1) + (elem funcref + (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) + (elem (table $t0) (i32.const 12) func 7 5 2 3 6) + (elem funcref + (ref.func 5) (ref.func 9) (ref.func 2) (ref.func 7) (ref.func 6)) + (func (result i32) (i32.const 0)) + (func (result i32) (i32.const 1)) + (func (result i32) (i32.const 2)) + (func (result i32) (i32.const 3)) + (func (result i32) (i32.const 4)) + (func (result i32) (i32.const 5)) + (func (result i32) (i32.const 6)) + (func (result i32) (i32.const 7)) + (func (result i32) (i32.const 8)) + (func (result i32) (i32.const 9)) + (func (export "test") + (elem.drop 1) + (table.init 1 (i32.const 31) (i32.const 0) (i32.const 0)))) +(assert_trap (invoke "test") "out of bounds table access") + (module (table $t0 30 30 funcref) (table $t1 28 28 funcref) @@ -2332,3 +2452,20 @@ (elem funcref) (elem funcref) (elem funcref) (elem funcref) (elem funcref) (func (table.init 64 (i32.const 0) (i32.const 0) (i32.const 0)))) + +;; Test that element segments are not re-evaluated on every `table.init`. +(module + (type $arr (array (mut arrayref))) + + (table $table i64 2 arrayref) + (elem $elem arrayref (item (array.new_default $arr (i32.const 0)))) + + (func (export "run") (result i32) + (table.init $table $elem (i64.const 0) (i32.const 0) (i32.const 1)) + (table.init $table $elem (i64.const 1) (i32.const 0) (i32.const 1)) + (ref.eq (table.get $table (i64.const 0)) + (table.get $table (i64.const 1))) + ) +) + +(assert_return (invoke "run") (i32.const 1)) diff --git a/test/meta/generate_table_init.js b/test/meta/generate_table_init.js index dbce7e9c33..db6147af57 100644 --- a/test/meta/generate_table_init.js +++ b/test/meta/generate_table_init.js @@ -28,7 +28,7 @@ function emit_a() { function emit_b(insn, table) { let tt = table == 2 ? 'i64' : 'i32'; - let t2 = table == 2 ? '(table $t2 i64 30 30 funcref)' : ''; + let t2 = table == 2 ? ' (table $t2 i64 30 30 funcref)\n' : '\n'; print( ` (module @@ -40,8 +40,7 @@ function emit_b(insn, table) { (import "a" "ef4" (func (result i32))) ;; index 4 (table $t0 30 30 funcref) (table $t1 30 30 funcref) - ${t2} - (elem (table $t${table}) (${tt}.const 2) func 3 1 4 1) +${t2} (elem (table $t${table}) (${tt}.const 2) func 3 1 4 1) (elem funcref (ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8)) (elem (table $t${table}) (${tt}.const 12) func 7 5 2 3 6) @@ -216,11 +215,36 @@ tab_test2("(elem.drop 1)", "(elem.drop 1)", undefined); -// drop, then init +// init from a passive segment succeeds... +tab_test1("(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))", + 0, + undefined); + +// ...but dropping the segment first makes the same init trap tab_test2("(elem.drop 1)", "(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))", "out of bounds table access"); +// drop, then init: zero length at zero src and in-range dst is OK +tab_test2("(elem.drop 1)", + "(table.init 1 (i32.const 12) (i32.const 0) (i32.const 0))", + undefined); + +// drop, then init: zero length, src offset past dropped seg end is invalid +tab_test2("(elem.drop 1)", + "(table.init 1 (i32.const 12) (i32.const 1) (i32.const 0))", + "out of bounds table access"); + +// drop, then init: zero length, dst offset at end of table is OK +tab_test2("(elem.drop 1)", + "(table.init 1 (i32.const 30) (i32.const 0) (i32.const 0))", + undefined); + +// drop, then init: zero length, dst offset past end of table is invalid +tab_test2("(elem.drop 1)", + "(table.init 1 (i32.const 31) (i32.const 0) (i32.const 0))", + "out of bounds table access"); + // init: seg ix is valid passive, but length to copy > len of seg tab_test1("(table.init 1 (i32.const 12) (i32.const 0) (i32.const 5))", 0, "out of bounds table access"); @@ -390,3 +414,26 @@ print( (elem funcref) (elem funcref) (elem funcref) (elem funcref) (elem funcref) (func (table.init 64 (i32.const 0) (i32.const 0) (i32.const 0))))`) + +// Test that element segments are not re-evaluated on every `table.init`. +{ + const tabty = INDEX_TYPE == 'i64' ? ' i64' : ''; + print( +` +;; Test that element segments are not re-evaluated on every \`table.init\`. +(module + (type $arr (array (mut arrayref))) + + (table $table${tabty} 2 arrayref) + (elem $elem arrayref (item (array.new_default $arr (i32.const 0)))) + + (func (export "run") (result i32) + (table.init $table $elem (${INDEX_TYPE}.const 0) (i32.const 0) (i32.const 1)) + (table.init $table $elem (${INDEX_TYPE}.const 1) (i32.const 0) (i32.const 1)) + (ref.eq (table.get $table (${INDEX_TYPE}.const 0)) + (table.get $table (${INDEX_TYPE}.const 1))) + ) +) + +(assert_return (invoke "run") (i32.const 1))`); +}