diff options
Diffstat (limited to 'js/src/jit-test/tests/wasm/passive-segs-boundary.js')
-rw-r--r-- | js/src/jit-test/tests/wasm/passive-segs-boundary.js | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/passive-segs-boundary.js b/js/src/jit-test/tests/wasm/passive-segs-boundary.js new file mode 100644 index 0000000000..18dee4a303 --- /dev/null +++ b/js/src/jit-test/tests/wasm/passive-segs-boundary.js @@ -0,0 +1,442 @@ +// Perform a test which, +// +// * if errKind is defined, is expected to fail with an exception +// characterised by errKind and errText. +// +// * if errKind is undefined, is expected to succeed, in which case errKind +// and errText are ignored. +// +// The function body will be [insn1, insn2] and is constructed according to +// four booleans: +// +// * isMem controls whether the module is constructed with memory or +// table initializers. +// +// * haveStorage determines whether there is actually a memory or table to +// work with. +// +// * haveInitA controls whether active initializers are added. +// +// * haveInitP controls whether passive initializers are added. + +function do_test(insn1, insn2, errKind, errText, + isMem, haveStorage, haveInitA, haveInitP) +{ + let preamble; + if (isMem) { + let mem_def = haveStorage ? "(memory 1 1)" : ""; + let mem_ia1 = `(data (i32.const 2) "\\03\\01\\04\\01")`; + let mem_ia2 = `(data (i32.const 12) "\\07\\05\\02\\03\\06")`; + let mem_ip1 = `(data "\\02\\07\\01\\08")`; + let mem_ip2 = `(data "\\05\\09\\02\\07\\06")`; + let mem_init = ``; + if (haveInitA && haveInitP) + mem_init = `${mem_ia1} ${mem_ip1} ${mem_ia2} ${mem_ip2}`; + else if (haveInitA && !haveInitP) mem_init = `${mem_ia1} ${mem_ia2}`; + else if (!haveInitA && haveInitP) mem_init = `${mem_ip1} ${mem_ip2}`; + preamble + = `;; -------- Memories -------- + ${mem_def} + ;; -------- Memory initialisers -------- + ${mem_init} + `; + } else { + let tab_def = haveStorage ? "(table 30 30 funcref)" : ""; + let tab_ia1 = `(elem (i32.const 2) 3 1 4 1)`; + let tab_ia2 = `(elem (i32.const 12) 7 5 2 3 6)`; + let tab_ip1 = `(elem func 2 7 1 8)`; + let tab_ip2 = `(elem func 5 9 2 7 6)`; + let tab_init = ``; + if (haveInitA && haveInitP) + tab_init = `${tab_ia1} ${tab_ip1} ${tab_ia2} ${tab_ip2}`; + else if (haveInitA && !haveInitP) tab_init = `${tab_ia1} ${tab_ia2}`; + else if (!haveInitA && haveInitP) tab_init = `${tab_ip1} ${tab_ip2}`; + preamble + = `;; -------- Tables -------- + ${tab_def} + ;; -------- Table initialisers -------- + ${tab_init} + ;; ------ Functions (0..9) referred by the table/esegs ------ + (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)) + `; + } + + let txt = "(module\n" + preamble + + `;; -------- testfn -------- + (func (export "testfn") + ${insn1} + ${insn2} + ) + )`; + + if (!!errKind) { + assertErrorMessage( + () => { + let inst = wasmEvalText(txt); + inst.exports.testfn(); + }, + errKind, + errText + ); + } else { + let inst = wasmEvalText(txt); + assertEq(undefined, inst.exports.testfn()); + } +} + + +// Some handy specialisations of do_test(). + +function mem_test(insn1, insn2, errKind, errText, + haveStorage=true, haveInitA=true, haveInitP=true) { + do_test(insn1, insn2, errKind, errText, + /*isMem=*/true, haveStorage, haveInitA, haveInitP); +} + +function mem_test_nofail(insn1, insn2, + haveStorage=true, haveInitA=true, haveInitP=true) { + do_test(insn1, insn2, undefined, undefined, + /*isMem=*/true, haveStorage, haveInitA, haveInitP); +} + +function tab_test(insn1, insn2, errKind, errText, + haveStorage=true, haveInitA=true, haveInitP=true) { + do_test(insn1, insn2, errKind, errText, + /*isMem=*/false, haveStorage, haveInitA, haveInitP); +} + +function tab_test_nofail(insn1, insn2, + haveStorage=true, haveInitA=true, haveInitP=true) { + do_test(insn1, insn2, undefined, undefined, + /*isMem=*/false, haveStorage, haveInitA, haveInitP); +} + + +//---- memory.{drop,init,copy} ------------------------------------------------- + +// The tested semantics for memory.drop, in the case where there's no memory, +// are as follows. table.drop is analogous. +// +// no memory, no data segments: +// drop with any args -> fail OOB +// [because there's nothing to drop] +// +// no memory, data segments, at least one of which is active: +// -> always fails, regardless of insns +// [because active segments implicitly reference memory 0] +// +// no memory, data segments, all of which are passive: +// drop, segment index is OOB -> fail OOB +// [because it refers to non existent segment] +// +// drop, segment index is IB -> OK + +// drop with no memory and no data segments +mem_test("data.drop 0", "", + WebAssembly.CompileError, /(data.drop segment index out of range)|(unknown data segment 0)/, + /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/false); + +// drop with no memory but with both passive and active segments, ix in range +// and refers to a passive segment +mem_test("data.drop 3", "", + WebAssembly.CompileError, + /active data segment requires a memory section/, + /*haveStorage=*/false, /*haveInitA=*/true, /*haveInitP=*/true); + +// drop with no memory but with passive segments only, ix out of range +mem_test("data.drop 2", "", + WebAssembly.CompileError, /(data.drop segment index out of range)|(unknown data segment 2)/, + /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/true); + +// drop with no memory but with passive segments only, ix in range +mem_test_nofail("data.drop 1", "", + /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/true); + + +// init with no memory and no data segs +mem_test("(memory.init 1 (i32.const 1234) (i32.const 1) (i32.const 1))", "", + WebAssembly.CompileError, /(can't touch memory without memory)|(unknown memory 0)/, + /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/false); + +// drop with data seg ix out of range +mem_test("data.drop 4", "", + WebAssembly.CompileError, /(data.drop segment index out of range)|(unknown data segment 4)/); + +// init with data seg ix out of range +mem_test("(memory.init 4 (i32.const 1234) (i32.const 1) (i32.const 1))", "", + WebAssembly.CompileError, /(memory.init segment index out of range)|(unknown data segment 4)/); + +// drop with data seg ix indicating an active segment +mem_test("data.drop 2", ""); + +// init with data seg ix indicating an active segment +mem_test("(memory.init 2 (i32.const 1234) (i32.const 1) (i32.const 1))", "", + WebAssembly.RuntimeError, /index out of bounds/); + +// init, using a data seg ix more than once is OK +mem_test_nofail( + "(memory.init 1 (i32.const 1234) (i32.const 1) (i32.const 1))", + "(memory.init 1 (i32.const 4321) (i32.const 1) (i32.const 1))"); + +// drop, then drop +mem_test("data.drop 1", + "data.drop 1"); + +// drop, then init +mem_test("data.drop 1", + "(memory.init 1 (i32.const 1234) (i32.const 1) (i32.const 1))", + WebAssembly.RuntimeError, /index out of bounds/); + +// init: seg ix is valid passive, but length to copy > len of seg +mem_test("", + "(memory.init 1 (i32.const 1234) (i32.const 0) (i32.const 5))", + WebAssembly.RuntimeError, /index out of bounds/); + +// init: seg ix is valid passive, but implies copying beyond end of seg +mem_test("", + "(memory.init 1 (i32.const 1234) (i32.const 2) (i32.const 3))", + WebAssembly.RuntimeError, /index out of bounds/); + +// init: seg ix is valid passive, but implies copying beyond end of dst +mem_test("", + "(memory.init 1 (i32.const 0xFFFE) (i32.const 1) (i32.const 3))", + WebAssembly.RuntimeError, /index out of bounds/); + +// init: seg ix is valid passive, zero len, but src offset out of bounds at the +// edge +mem_test("", + "(memory.init 1 (i32.const 1234) (i32.const 4) (i32.const 0))"); + +// init: seg ix is valid passive, zero len, but src offset out of bounds one +// past the edge +mem_test("", + "(memory.init 1 (i32.const 1234) (i32.const 5) (i32.const 0))", + WebAssembly.RuntimeError, /index out of bounds/); + +// init: seg ix is valid passive, zero len, but dst offset out of bounds at the +// edge +mem_test("", + "(memory.init 1 (i32.const 0x10000) (i32.const 2) (i32.const 0))"); + +// init: seg ix is valid passive, zero len, but dst offset out of bounds one +// past the edge +mem_test("", + "(memory.init 1 (i32.const 0x10001) (i32.const 2) (i32.const 0))", + WebAssembly.RuntimeError, /index out of bounds/); + +// drop: too many args +mem_test("data.drop 1 (i32.const 42)", "", + WebAssembly.CompileError, + /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/); + +// init: too many args +mem_test("(memory.init 1 (i32.const 1) (i32.const 1) (i32.const 1) (i32.const 1))", + "", + WebAssembly.CompileError, /(unused values)|(values remaining on stack at end of block)/); + +// init: too few args +mem_test("(memory.init 1 (i32.const 1) (i32.const 1))", "", + WebAssembly.CompileError, + /(popping value from empty stack)|(expected i32 but nothing on stack)/); + +// invalid argument types +{ + const tys = ['i32', 'f32', 'i64', 'f64']; + + for (let ty1 of tys) { + for (let ty2 of tys) { + for (let ty3 of tys) { + if (ty1 == 'i32' && ty2 == 'i32' && ty3 == 'i32') + continue; // this is the only valid case + let i1 = `(memory.init 1 (${ty1}.const 1) (${ty2}.const 1) (${ty3}.const 1))`; + mem_test(i1, "", WebAssembly.CompileError, /type mismatch/); + }}} +} + +// +//---- table.{drop,init} -------------------------------------------------- + +// drop with no tables and no elem segments +tab_test("elem.drop 0", "", + WebAssembly.CompileError, + /(element segment index out of range for elem.drop)|(segment index out of bounds)/, + /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/false); + +// drop with no tables but with both passive and active segments, ix in range +// and refers to a passive segment +tab_test("elem.drop 3", "", + WebAssembly.CompileError, + /active elem segment requires a table/, + /*haveStorage=*/false, /*haveInitA=*/true, /*haveInitP=*/true); + +// drop with no tables but with passive segments only, ix out of range +tab_test("elem.drop 2", "", + WebAssembly.CompileError, + /(element segment index out of range for elem.drop)|(segment index out of bounds)/, + /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/true); + +// drop with no tables but with passive segments only, ix in range +tab_test_nofail("elem.drop 1", "", + /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/true); + + +// init with no table +tab_test("(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))", "", + WebAssembly.CompileError, /(table index out of range)|(table index out of bounds)/, + /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/false); + +// drop with elem seg ix out of range +tab_test("elem.drop 4", "", + WebAssembly.CompileError, /(element segment index out of range for elem.drop)|(segment index out of bounds)/); + +// init with elem seg ix out of range +tab_test("(table.init 4 (i32.const 12) (i32.const 1) (i32.const 1))", "", + WebAssembly.CompileError, /(table.init segment index out of range)|(segment index out of bounds)/); + +// drop with elem seg ix indicating an active segment +tab_test("elem.drop 2", ""); + +// init with elem seg ix indicating an active segment +tab_test("(table.init 2 (i32.const 12) (i32.const 1) (i32.const 1))", "", + WebAssembly.RuntimeError, /index out of bounds/); + +// init, using an elem seg ix more than once is OK +tab_test_nofail( + "(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))", + "(table.init 1 (i32.const 21) (i32.const 1) (i32.const 1))"); + +// drop, then drop +tab_test("elem.drop 1", + "elem.drop 1"); + +// drop, then init +tab_test("elem.drop 1", + "(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))", + WebAssembly.RuntimeError, /index out of bounds/); + +// init: seg ix is valid passive, but length to copy > len of seg +tab_test("", + "(table.init 1 (i32.const 12) (i32.const 0) (i32.const 5))", + WebAssembly.RuntimeError, /index out of bounds/); + +// init: seg ix is valid passive, but implies copying beyond end of seg +tab_test("", + "(table.init 1 (i32.const 12) (i32.const 2) (i32.const 3))", + WebAssembly.RuntimeError, /index out of bounds/); + +// init: seg ix is valid passive, but implies copying beyond end of dst +tab_test("", + "(table.init 1 (i32.const 28) (i32.const 1) (i32.const 3))", + WebAssembly.RuntimeError, /index out of bounds/); + +// init: seg ix is valid passive, zero len, but src offset out of bounds at the +// edge +tab_test("", + "(table.init 1 (i32.const 12) (i32.const 4) (i32.const 0))"); + +// init: seg ix is valid passive, zero len, but src offset out of bounds one +// past the edge +tab_test("", + "(table.init 1 (i32.const 12) (i32.const 5) (i32.const 0))", + WebAssembly.RuntimeError, /index out of bounds/); + +// init: seg ix is valid passive, zero len, but dst offset out of bounds +tab_test("", + "(table.init 1 (i32.const 30) (i32.const 2) (i32.const 0))"); + +// init: seg ix is valid passive, zero len, but dst offset out of bounds one +// past the edge +tab_test("", + "(table.init 1 (i32.const 31) (i32.const 2) (i32.const 0))", + WebAssembly.RuntimeError, /index out of bounds/); + +// drop: too many args +tab_test("elem.drop 1 (i32.const 42)", "", + WebAssembly.CompileError, + /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/); + +// init: too many args +tab_test("(table.init 1 (i32.const 1) (i32.const 1) (i32.const 1) (i32.const 1))", + "", + WebAssembly.CompileError, /(unused values)|(values remaining on stack at end of block)/); + +// init: too few args +tab_test("(table.init 1 (i32.const 1) (i32.const 1))", "", + WebAssembly.CompileError, + /(popping value from empty stack)|(expected i32 but nothing on stack)/); + +// invalid argument types +{ + const tys = ['i32', 'f32', 'i64', 'f64']; + + const ops = ['table.init 1', 'table.copy']; + for (let ty1 of tys) { + for (let ty2 of tys) { + for (let ty3 of tys) { + for (let op of ops) { + if (ty1 == 'i32' && ty2 == 'i32' && ty3 == 'i32') + continue; // this is the only valid case + let i1 = `(${op} (${ty1}.const 1) (${ty2}.const 1) (${ty3}.const 1))`; + tab_test(i1, "", WebAssembly.CompileError, /type mismatch/); + }}}} +} + + +//---- table.copy --------------------------------------------------------- + +// There are no immediates here, only 3 dynamic args. So we're limited to +// runtime boundary checks. + +// passive-segs-smoketest.js tests the normal, non-exception cases of +// table.copy. Here we just test the boundary-failure cases. The +// table's valid indices are 0 .. 29 inclusive. + +// copy: dst range invalid +tab_test("(table.copy (i32.const 28) (i32.const 1) (i32.const 3))", + "", + WebAssembly.RuntimeError, /index out of bounds/); + +// copy: dst wraparound end of 32 bit offset space +tab_test("(table.copy (i32.const 0xFFFFFFFE) (i32.const 1) (i32.const 2))", + "", + WebAssembly.RuntimeError, /index out of bounds/); + +// copy: src range invalid +tab_test("(table.copy (i32.const 15) (i32.const 25) (i32.const 6))", + "", + WebAssembly.RuntimeError, /index out of bounds/); + +// copy: src wraparound end of 32 bit offset space +tab_test("(table.copy (i32.const 15) (i32.const 0xFFFFFFFE) (i32.const 2))", + "", + WebAssembly.RuntimeError, /index out of bounds/); + +// copy: zero length with both offsets in-bounds is OK +tab_test_nofail( + "(table.copy (i32.const 15) (i32.const 25) (i32.const 0))", + ""); + +// copy: zero length with dst offset out of bounds at the edge +tab_test("(table.copy (i32.const 30) (i32.const 15) (i32.const 0))", + ""); + +// copy: zero length with dst offset out of bounds one past the edge +tab_test("(table.copy (i32.const 31) (i32.const 15) (i32.const 0))", + "", WebAssembly.RuntimeError, /index out of bounds/); + +// copy: zero length with src offset out of bounds at the edge +tab_test("(table.copy (i32.const 15) (i32.const 30) (i32.const 0))", + ""); + +// copy: zero length with src offset out of bounds one past the edge +tab_test("(table.copy (i32.const 15) (i32.const 31) (i32.const 0))", + "", WebAssembly.RuntimeError, /index out of bounds/); |