// Some non-boundary tests for {table,memory}.{init,drop,copy}. The table // case is more complex and appears first. The memory case is a simplified // version of it.
// .. and this one imports those 5 functions. It adds 5 of its own, creates a // 30 element table using both active and passive initialisers, with a mixture // of the imported and local functions. |setup| and |check| are exported. // |setup| uses the supplied |insn| to modify the table somehow. |check| will // indirect-call the table entry number specified as a parameter. That will // either return a value 0 to 9 indicating the function called, or will throw an // exception if the table entry is empty. function gen_tab_impmod_t(insn)
{
let t =
`(module
;; -------- Types --------
(type (func (result i32))) ;; type #0
;; -------- Imports --------
(import"a""if0" (func (result i32))) ;; index 0
(import"a""if1" (func (result i32)))
(import"a""if2" (func (result i32)))
(import"a""if3" (func (result i32)))
(import"a""if4" (func (result i32))) ;; index 4
;; -------- Tables --------
(table 30 30 funcref)
;; -------- Table initialisers --------
(elem (i32.const 2) 3 1 4 1)
(elem func 2 7 1 8)
(elem (i32.const 12) 7 5 2 3 6)
(elem func 5 9 2 7 6)
;; -------- Functions --------
(func (result i32) (i32.const 5)) ;; index 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)) ;; index 9
(func (export "setup")
${insn})
(func (export "check") (param i32) (result i32)
;; call the selected table entry, which will either return a value,
;; or will cause an exception.
local.get 0 ;; callIx
call_indirect (type 0) ;; and its return value is our return value.
)
)`; return t;
};
// This is the test driver. It constructs the abovementioned module, using // the given |instruction| to modify the table, and then probes the table // by making indirect calls, one for each element of |expected_result_vector|. // The results are compared to those in the vector.
function tab_test(instruction, expected_result_vector)
{
let tab_expmod_b = wasmTextToBinary(tab_expmod_t);
let tab_expmod_i = new Instance(new Module(tab_expmod_b));
let tab_impmod_t = gen_tab_impmod_t(instruction);
let tab_impmod_b = wasmTextToBinary(tab_impmod_t);
let inst = new Instance(new Module(tab_impmod_b),
{a:{if0:tab_expmod_i.exports.ef0,
if1:tab_expmod_i.exports.ef1,
if2:tab_expmod_i.exports.ef2,
if3:tab_expmod_i.exports.ef3,
if4:tab_expmod_i.exports.ef4
}});
inst.exports.setup();
for (let i = 0; i < expected_result_vector.length; i++) {
let expected = expected_result_vector[i];
let actual = undefined; try {
actual = inst.exports.check(i);
assertEq(actual !== null, true);
} catch (e) { if (!(e instanceof Error &&
e.message.match(/indirect call to null/))) throw e; // actual remains undefined
}
assertEq(actual, expected, "tab_test fail: insn = '" + instruction + "', index = " +
i + ", expected = " + expected + ", actual = " + actual);
}
}
// Using 'e' for empty (undefined) spaces in the table, to make it easier // to count through the vector entries when debugging.
let e = undefined;
// This just gives the initial state of the table, with its active // initialisers applied
tab_test("nop",
[e,e,3,1,4, 1,e,e,e,e, e,e,7,5,2, 3,6,e,e,e, e,e,e,e,e, e,e,e,e,e]);
(func (export "testfn")
${insn}
;; There's no return value. The JS driver can just pull out the
;; final memory and examine it.
)
)`; return t;
};
function mem_test(instruction, expected_result_vector)
{
let mem_mod_t = gen_mem_mod_t(instruction);
let mem_mod_b = wasmTextToBinary(mem_mod_t);
let inst = new Instance(new Module(mem_mod_b));
inst.exports.testfn();
let buf = new Uint8Array(inst.exports.memory0.buffer);
for (let i = 0; i < expected_result_vector.length; i++) {
let expected = expected_result_vector[i];
let actual = buf[i];
assertEq(actual, expected, "mem_test fail: insn = '" + instruction + "', index = " +
i + ", expected = " + expected + ", actual = " + actual);
}
}
e = 0;
// This just gives the initial state of the memory, with its active // initialisers applied.
mem_test("nop",
[e,e,3,1,4, 1,e,e,e,e, e,e,7,5,2, 3,6,e,e,e, e,e,e,e,e, e,e,e,e,e]);
function checkDataCount(count, err) {
let binary = moduleWithSections(
[v2vSigSection,
dataCountSection(count),
dataSection([
{offset: 0, elems: []},
{offset: 0, elems: []},
])
]);
assertErrorMessage(() => new WebAssembly.Module(binary),
WebAssembly.CompileError,
err);
}
// DataCount section is present but value is too low for the number of data segments
checkDataCount(1, /number of data segments does not match declared count/); // DataCount section is present but value is too high for the number of data segments
checkDataCount(3, /number of data segments does not match declared count/);
// DataCount section is not present but memory.init or data.drop uses it function checkNoDataCount(body, err) {
let binary = moduleWithSections(
[v2vSigSection,
declSection([0]),
memorySection(1),
bodySection(
[funcBody({locals:[], body})])]);
assertErrorMessage(() => new WebAssembly.Module(binary),
WebAssembly.CompileError,
err);
}
checkNoDataCount([I32ConstCode, 0,
I32ConstCode, 0,
I32ConstCode, 0,
MiscPrefix, MemoryInitCode, 0, 0],
/(memory.init requires a DataCount section)|(unknown data segment)/);
checkNoDataCount([MiscPrefix, DataDropCode, 0],
/(data.drop requires a DataCount section)|(unknown data segment)/);
//---------------------------------------------------------------------// //---------------------------------------------------------------------// // Some further tests for memory.copy and memory.fill. First, validation // tests.
// Prefixed opcodes
function checkMiscPrefixed(opcode, expect_failure) {
let binary = moduleWithSections(
[v2vSigSection, declSection([0]), memorySection(1),
bodySection(
[funcBody(
{locals:[],
body:[I32ConstCode, 0x0,
I32ConstCode, 0x0,
I32ConstCode, 0x0,
MiscPrefix, ...opcode]})])]); if (expect_failure) {
assertErrorMessage(() => new WebAssembly.Module(binary),
WebAssembly.CompileError, /(unrecognized opcode)|(Unknown.*subopcode)/);
} else {
assertEq(WebAssembly.validate(binary), true);
}
}
//----------------------------------------------------------- // Verification cases for memory.copy/fill opcode encodings
checkMiscPrefixed([MemoryCopyCode, 0x00, 0x00], false); // memory.copy src=0 dest=0
checkMiscPrefixed([MemoryFillCode, 0x00], false); // memory.fill mem=0
checkMiscPrefixed([0x13], true); // table.size+1, which is currently unassigned
//----------------------------------------------------------- // Verification cases for memory.copy/fill arguments
// Invalid argument types
{ const tys = ['i32', 'f32', 'i64', 'f64']; const ops = ['copy', 'fill']; 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 text =
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.${op} (${ty1}.const 10) (${ty2}.const 20) (${ty3}.const 30))
)
)`;
assertErrorMessage(() => wasmEvalText(text),
WebAssembly.CompileError, /type mismatch/);
}}}}
}
// Not enough, or too many, args
{ for (let op of ['copy', 'fill']) {
let text1 =
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(i32.const 10)
(i32.const 20)
memory.${op}
)
)`;
assertErrorMessage(() => wasmEvalText(text1),
WebAssembly.CompileError,
/(popping value from empty stack)|(expected i32 but nothing on stack)/);
let text2 =
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(i32.const 10)
(i32.const 20)
(i32.const 30)
(i32.const 40)
memory.${op}
)
)`;
assertErrorMessage(() => wasmEvalText(text2),
WebAssembly.CompileError,
/(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/);
}
}
// Module doesn't have a memory
{ for (let op of ['copy', 'fill']) {
let text =
`(module
(func (export "testfn")
(memory.${op} (i32.const 10) (i32.const 20) (i32.const 30))
)
)`;
assertErrorMessage(() => wasmEvalText(text),
WebAssembly.CompileError,
/memory index/);
}
}
//---------------------------------------------------------------------// //---------------------------------------------------------------------// // Run tests
//----------------------------------------------------------- // Test helpers function checkRange(arr, minIx, maxIxPlusOne, expectedValue)
{ for (let i = minIx; i < maxIxPlusOne; i++) {
assertEq(arr[i], expectedValue);
}
}
//----------------------------------------------------------- // Test cases for memory.fill
// Range valid
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.fill (i32.const 0xFF00) (i32.const 0x55) (i32.const 256))
)
)`
);
inst.exports.testfn();
let b = new Uint8Array(inst.exports.memory.buffer);
checkRange(b, 0x00000, 0x0FF00, 0x00);
checkRange(b, 0x0FF00, 0x10000, 0x55);
}
// Range invalid
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.fill (i32.const 0xFF00) (i32.const 0x55) (i32.const 257))
)
)`
);
assertErrorMessage(() => inst.exports.testfn(),
WebAssembly.RuntimeError, /index out of bounds/);
}
// Wraparound the end of 32-bit offset space
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.fill (i32.const 0xFFFFFF00) (i32.const 0x55) (i32.const 257))
)
)`
);
assertErrorMessage(() => inst.exports.testfn(),
WebAssembly.RuntimeError, /index out of bounds/);
}
// Zero len with offset in-bounds is a no-op
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.fill (i32.const 0x12) (i32.const 0x55) (i32.const 0))
)
)`
);
inst.exports.testfn();
let b = new Uint8Array(inst.exports.memory.buffer);
checkRange(b, 0x00000, 0x10000, 0x00);
}
// Zero len with offset out-of-bounds is OK
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.fill (i32.const 0x10000) (i32.const 0x55) (i32.const 0))
)
)`
);
inst.exports.testfn();
}
//----------------------------------------------------------- // Test cases for memory.copy
// Both ranges valid. Copy 5 bytes backwards by 1 (overlapping). // result = 0x00--(09) 0x55--(11) 0x00--(pagesize-20)
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.fill (i32.const 10) (i32.const 0x55) (i32.const 10))
(memory.copy (i32.const 9) (i32.const 10) (i32.const 5))
)
)`
);
inst.exports.testfn();
let b = new Uint8Array(inst.exports.memory.buffer);
checkRange(b, 0, 0+9, 0x00);
checkRange(b, 9, 9+11, 0x55);
checkRange(b, 9+11, 0x10000, 0x00);
}
// Both ranges valid. Copy 5 bytes forwards by 1 (overlapping). // result = 0x00--(10) 0x55--(11) 0x00--(pagesize-19)
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.fill (i32.const 10) (i32.const 0x55) (i32.const 10))
(memory.copy (i32.const 16) (i32.const 15) (i32.const 5))
)
)`
);
inst.exports.testfn();
let b = new Uint8Array(inst.exports.memory.buffer);
checkRange(b, 0, 0+10, 0x00);
checkRange(b, 10, 10+11, 0x55);
checkRange(b, 10+11, 0x10000, 0x00);
}
// Destination range invalid
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.copy (i32.const 0xFF00) (i32.const 0x8000) (i32.const 257))
)
)`
);
assertErrorMessage(() => inst.exports.testfn(),
WebAssembly.RuntimeError, /index out of bounds/);
}
// Destination wraparound the end of 32-bit offset space
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.copy (i32.const 0xFFFFFF00) (i32.const 0x4000) (i32.const 257))
)
)`
);
assertErrorMessage(() => inst.exports.testfn(),
WebAssembly.RuntimeError, /index out of bounds/);
}
// Source range invalid
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.copy (i32.const 0x8000) (i32.const 0xFF00) (i32.const 257))
)
)`
);
assertErrorMessage(() => inst.exports.testfn(),
WebAssembly.RuntimeError, /index out of bounds/);
}
// Source wraparound the end of 32-bit offset space
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.copy (i32.const 0x4000) (i32.const 0xFFFFFF00) (i32.const 257))
)
)`
);
assertErrorMessage(() => inst.exports.testfn(),
WebAssembly.RuntimeError, /index out of bounds/);
}
// Zero len with both offsets in-bounds is a no-op
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.fill (i32.const 0x0000) (i32.const 0x55) (i32.const 0x8000))
(memory.fill (i32.const 0x8000) (i32.const 0xAA) (i32.const 0x8000))
(memory.copy (i32.const 0x9000) (i32.const 0x7000) (i32.const 0))
)
)`
);
inst.exports.testfn();
let b = new Uint8Array(inst.exports.memory.buffer);
checkRange(b, 0x00000, 0x08000, 0x55);
checkRange(b, 0x08000, 0x10000, 0xAA);
}
// Zero len with dest offset out-of-bounds at the edge of memory
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.copy (i32.const 0x10000) (i32.const 0x7000) (i32.const 0))
)
)`
);
inst.exports.testfn();
}
// Ditto, but one element further out
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.copy (i32.const 0x10001) (i32.const 0x7000) (i32.const 0))
)
)`
);
assertErrorMessage(() => inst.exports.testfn(),
WebAssembly.RuntimeError, /index out of bounds/);
}
// Zero len with src offset out-of-bounds at the edge of memory
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.copy (i32.const 0x9000) (i32.const 0x10000) (i32.const 0))
)
)`
);
inst.exports.testfn();
}
// Ditto, but one element further out
{
let inst = wasmEvalText(
`(module
(memory (export "memory") 1 1)
(func (export "testfn")
(memory.copy (i32.const 0x9000) (i32.const 0x10001) (i32.const 0))
)
)`
);
assertErrorMessage(() => inst.exports.testfn(),
WebAssembly.RuntimeError, /index out of bounds/);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.