summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/binary.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/wasm/binary.js')
-rw-r--r--js/src/jit-test/tests/wasm/binary.js385
1 files changed, 385 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/binary.js b/js/src/jit-test/tests/wasm/binary.js
new file mode 100644
index 000000000..f9455f6a4
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -0,0 +1,385 @@
+load(libdir + "wasm.js");
+load(libdir + "wasm-binary.js");
+
+const CompileError = WebAssembly.CompileError;
+
+const magicError = /failed to match magic number/;
+const unknownSection = /expected user-defined section/;
+
+function sectionError(section) {
+ return RegExp(`failed to start ${section} section`);
+}
+
+function versionError(actual) {
+ var expect = encodingVersion;
+ var str = `binary version 0x${actual.toString(16)} does not match expected version 0x${expect.toString(16)}`;
+ return RegExp(str);
+}
+
+function toU8(array) {
+ for (let b of array)
+ assertEq(b < 256, true);
+ return Uint8Array.from(array);
+}
+
+function varU32(u32) {
+ assertEq(u32 >= 0, true);
+ assertEq(u32 < Math.pow(2,32), true);
+ var bytes = [];
+ do {
+ var byte = u32 & 0x7f;
+ u32 >>>= 7;
+ if (u32 != 0)
+ byte |= 0x80;
+ bytes.push(byte);
+ } while (u32 != 0);
+ return bytes;
+}
+
+function varS32(s32) {
+ assertEq(s32 >= -Math.pow(2,31), true);
+ assertEq(s32 < Math.pow(2,31), true);
+ var bytes = [];
+ do {
+ var byte = s32 & 0x7f;
+ s32 >>= 7;
+ if (s32 != 0 && s32 != -1)
+ byte |= 0x80;
+ bytes.push(byte);
+ } while (s32 != 0 && s32 != -1);
+ return bytes;
+}
+
+const U32MAX_LEB = [255, 255, 255, 255, 15];
+
+const wasmEval = (code, imports) => new WebAssembly.Instance(new WebAssembly.Module(code), imports).exports;
+
+assertErrorMessage(() => wasmEval(toU8([])), CompileError, magicError);
+assertErrorMessage(() => wasmEval(toU8([42])), CompileError, magicError);
+assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2])), CompileError, magicError);
+assertErrorMessage(() => wasmEval(toU8([1,2,3,4])), CompileError, magicError);
+assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3])), CompileError, versionError(0x6d736100));
+assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, 1])), CompileError, versionError(0x6d736100));
+assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, ver0])), CompileError, versionError(0x6d736100));
+assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, ver0, ver1, ver2])), CompileError, versionError(0x6d736100));
+
+// This test should be removed shortly.
+assertEq(WebAssembly.validate(toU8([magic0, magic1, magic2, magic3, 0xd, 0x0, 0x0, 0x0])), true);
+
+function moduleHeaderThen(...rest) {
+ return [magic0, magic1, magic2, magic3, ver0, ver1, ver2, ver3, ...rest];
+}
+
+var o = wasmEval(toU8(moduleHeaderThen()));
+assertEq(Object.getOwnPropertyNames(o).length, 0);
+
+// unfinished known sections
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(typeId))), CompileError, sectionError("type"));
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(importId))), CompileError, sectionError("import"));
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(functionId))), CompileError, sectionError("function"));
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(tableId))), CompileError, sectionError("table"));
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(memoryId))), CompileError, sectionError("memory"));
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(globalId))), CompileError, sectionError("global"));
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(exportId))), CompileError, sectionError("export"));
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(startId))), CompileError, sectionError("start"));
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(elemId))), CompileError, sectionError("elem"));
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(codeId))), CompileError, sectionError("code"));
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(dataId))), CompileError, sectionError("data"));
+
+// unknown sections are unconditionally rejected
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(42))), CompileError, unknownSection);
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(42, 0))), CompileError, unknownSection);
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(42, 1, 0))), CompileError, unknownSection);
+
+// user sections have special rules
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0))), CompileError, sectionError("user-defined")); // no length
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0))), CompileError, sectionError("user-defined")); // no id
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0, 0))), CompileError, sectionError("user-defined")); // payload too small to have id length
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 1))), CompileError, sectionError("user-defined")); // id not present
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 1, 65))), CompileError, sectionError("user-defined")); // id length doesn't fit in section
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 0, 0))), CompileError, sectionError("user-defined")); // second, unfinished user-defined section
+wasmEval(toU8(moduleHeaderThen(0, 1, 0))); // empty id
+wasmEval(toU8(moduleHeaderThen(0, 1, 0, 0, 1, 0))); // 2x empty id
+wasmEval(toU8(moduleHeaderThen(0, 2, 1, 65))); // id = "A"
+
+function string(name) {
+ var nameBytes = name.split('').map(c => {
+ var code = c.charCodeAt(0);
+ assertEq(code < 128, true); // TODO
+ return code
+ });
+ return varU32(nameBytes.length).concat(nameBytes);
+}
+
+function encodedString(name, len) {
+ var name = unescape(encodeURIComponent(name)); // break into string of utf8 code points
+ var nameBytes = name.split('').map(c => c.charCodeAt(0)); // map to array of numbers
+ return varU32(len === undefined ? nameBytes.length : len).concat(nameBytes);
+}
+
+function moduleWithSections(sectionArray) {
+ var bytes = moduleHeaderThen();
+ for (let section of sectionArray) {
+ bytes.push(section.name);
+ bytes.push(...varU32(section.body.length));
+ bytes.push(...section.body);
+ }
+ return toU8(bytes);
+}
+
+function sigSection(sigs) {
+ var body = [];
+ body.push(...varU32(sigs.length));
+ for (let sig of sigs) {
+ body.push(...varU32(FuncCode));
+ body.push(...varU32(sig.args.length));
+ for (let arg of sig.args)
+ body.push(...varU32(arg));
+ body.push(...varU32(sig.ret == VoidCode ? 0 : 1));
+ if (sig.ret != VoidCode)
+ body.push(...varU32(sig.ret));
+ }
+ return { name: typeId, body };
+}
+
+function declSection(decls) {
+ var body = [];
+ body.push(...varU32(decls.length));
+ for (let decl of decls)
+ body.push(...varU32(decl));
+ return { name: functionId, body };
+}
+
+function funcBody(func) {
+ var body = varU32(func.locals.length);
+ for (let local of func.locals)
+ body.push(...varU32(local));
+ body = body.concat(...func.body);
+ body.push(EndCode);
+ body.splice(0, 0, ...varU32(body.length));
+ return body;
+}
+
+function bodySection(bodies) {
+ var body = varU32(bodies.length).concat(...bodies);
+ return { name: codeId, body };
+}
+
+function importSection(imports) {
+ var body = [];
+ body.push(...varU32(imports.length));
+ for (let imp of imports) {
+ body.push(...string(imp.module));
+ body.push(...string(imp.func));
+ body.push(...varU32(FunctionCode));
+ body.push(...varU32(imp.sigIndex));
+ }
+ return { name: importId, body };
+}
+
+function exportSection(exports) {
+ var body = [];
+ body.push(...varU32(exports.length));
+ for (let exp of exports) {
+ body.push(...string(exp.name));
+ body.push(...varU32(FunctionCode));
+ body.push(...varU32(exp.funcIndex));
+ }
+ return { name: exportId, body };
+}
+
+function tableSection(initialSize) {
+ var body = [];
+ body.push(...varU32(1)); // number of tables
+ body.push(...varU32(AnyFuncCode));
+ body.push(...varU32(0x0)); // for now, no maximum
+ body.push(...varU32(initialSize));
+ return { name: tableId, body };
+}
+
+function memorySection(initialSize) {
+ var body = [];
+ body.push(...varU32(1)); // number of memories
+ body.push(...varU32(0x0)); // for now, no maximum
+ body.push(...varU32(initialSize));
+ return { name: memoryId, body };
+}
+
+function elemSection(elemArrays) {
+ var body = [];
+ body.push(...varU32(elemArrays.length));
+ for (let array of elemArrays) {
+ body.push(...varU32(0)); // table index
+ body.push(...varU32(I32ConstCode));
+ body.push(...varS32(array.offset));
+ body.push(...varU32(EndCode));
+ body.push(...varU32(array.elems.length));
+ for (let elem of array.elems)
+ body.push(...varU32(elem));
+ }
+ return { name: elemId, body };
+}
+
+function nameSection(elems) {
+ var body = [];
+ body.push(...string(nameName));
+ body.push(...varU32(elems.length));
+ for (let fn of elems) {
+ body.push(...encodedString(fn.name, fn.nameLen));
+ if (!fn.locals) {
+ body.push(...varU32(0));
+ continue;
+ }
+ body.push(...varU32(fn.locals.length));
+ for (let local of fn.locals)
+ body.push(...encodedString(local.name, local.nameLen));
+ }
+ return { name: userDefinedId, body };
+}
+
+function userDefinedSection(name, ...body) {
+ return { name: userDefinedId, body: [...string(name), ...body] };
+}
+
+const v2vSig = {args:[], ret:VoidCode};
+const i2vSig = {args:[I32Code], ret:VoidCode};
+const v2vBody = funcBody({locals:[], body:[]});
+
+assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: U32MAX_LEB } ])), CompileError, /too many signatures/);
+assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: [1, 0], } ])), CompileError, /expected function form/);
+assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: [1, FuncCode, ...U32MAX_LEB], } ])), CompileError, /too many arguments in signature/);
+
+assertThrowsInstanceOf(() => wasmEval(moduleWithSections([{name: typeId, body: [1]}])), CompileError);
+assertThrowsInstanceOf(() => wasmEval(moduleWithSections([{name: typeId, body: [1, 1, 0]}])), CompileError);
+
+wasmEval(moduleWithSections([sigSection([])]));
+wasmEval(moduleWithSections([sigSection([v2vSig])]));
+wasmEval(moduleWithSections([sigSection([i2vSig])]));
+wasmEval(moduleWithSections([sigSection([v2vSig, i2vSig])]));
+
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[], ret:100}])])), CompileError, /bad type/);
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[100], ret:VoidCode}])])), CompileError, /bad type/);
+
+assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([]), declSection([0])])), CompileError, /signature index out of range/);
+assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([1])])), CompileError, /signature index out of range/);
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0])])), CompileError, /expected function bodies/);
+wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([v2vBody])]));
+
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([v2vBody.concat(v2vBody)])])), CompileError, /byte size mismatch in code section/);
+
+assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([v2vSig]), {name: importId, body:[]}])), CompileError);
+assertErrorMessage(() => wasmEval(moduleWithSections([importSection([{sigIndex:0, module:"a", func:"b"}])])), CompileError, /signature index out of range/);
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), importSection([{sigIndex:1, module:"a", func:"b"}])])), CompileError, /signature index out of range/);
+wasmEval(moduleWithSections([sigSection([v2vSig]), importSection([])]));
+wasmEval(moduleWithSections([sigSection([v2vSig]), importSection([{sigIndex:0, module:"a", func:""}])]), {a:{"":()=>{}}});
+
+wasmEval(moduleWithSections([
+ sigSection([v2vSig]),
+ importSection([{sigIndex:0, module:"a", func:""}]),
+ declSection([0]),
+ bodySection([v2vBody])
+]), {a:{"":()=>{}}});
+
+assertErrorMessage(() => wasmEval(moduleWithSections([ {name: dataId, body: [], } ])), CompileError, /data section requires a memory section/);
+
+wasmEval(moduleWithSections([tableSection(0)]));
+wasmEval(moduleWithSections([elemSection([])]));
+wasmEval(moduleWithSections([tableSection(0), elemSection([])]));
+wasmEval(moduleWithSections([tableSection(1), elemSection([{offset:1, elems:[]}])]));
+assertErrorMessage(() => wasmEval(moduleWithSections([tableSection(1), elemSection([{offset:0, elems:[0]}])])), CompileError, /table element out of range/);
+wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), tableSection(1), elemSection([{offset:0, elems:[0]}]), bodySection([v2vBody])]));
+wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), tableSection(2), elemSection([{offset:0, elems:[0,0]}]), bodySection([v2vBody])]));
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), tableSection(2), elemSection([{offset:0, elems:[0,1]}]), bodySection([v2vBody])])), CompileError, /table element out of range/);
+wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0,0,0]), tableSection(4), elemSection([{offset:0, elems:[0,1,0,2]}]), bodySection([v2vBody, v2vBody, v2vBody])]));
+wasmEval(moduleWithSections([sigSection([v2vSig,i2vSig]), declSection([0,0,1]), tableSection(3), elemSection([{offset:0,elems:[0,1,2]}]), bodySection([v2vBody, v2vBody, v2vBody])]));
+
+function invalidTableSection0() {
+ var body = [];
+ body.push(...varU32(0)); // number of tables
+ return { name: tableId, body };
+}
+
+assertErrorMessage(() => wasmEval(moduleWithSections([invalidTableSection0()])), CompileError, /number of tables must be exactly one/);
+
+wasmEval(moduleWithSections([memorySection(0)]));
+
+function invalidMemorySection0() {
+ var body = [];
+ body.push(...varU32(0)); // number of memories
+ return { name: memoryId, body };
+}
+
+assertErrorMessage(() => wasmEval(moduleWithSections([invalidMemorySection0()])), CompileError, /number of memories must be exactly one/);
+
+// Test early 'end'
+const bodyMismatch = /function body length mismatch/;
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[EndCode]})])])), CompileError, bodyMismatch);
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[UnreachableCode,EndCode]})])])), CompileError, bodyMismatch);
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[EndCode,UnreachableCode]})])])), CompileError, bodyMismatch);
+
+// Deep nesting shouldn't crash or even throw.
+var manyBlocks = [];
+for (var i = 0; i < 20000; i++)
+ manyBlocks.push(BlockCode, VoidCode, EndCode);
+wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:manyBlocks})])]));
+
+// Ignore errors in name section.
+var tooBigNameSection = {
+ name: userDefinedId,
+ body: [...string(nameName), ...varU32(Math.pow(2, 31))] // declare 2**31 functions.
+};
+wasmEval(moduleWithSections([tooBigNameSection]));
+
+// Skip user-defined sections before any expected section
+var userDefSec = userDefinedSection("wee", 42, 13);
+var sigSec = sigSection([v2vSig]);
+var declSec = declSection([0]);
+var bodySec = bodySection([v2vBody]);
+wasmEval(moduleWithSections([userDefSec, sigSec, declSec, bodySec]));
+wasmEval(moduleWithSections([sigSec, userDefSec, declSec, bodySec]));
+wasmEval(moduleWithSections([sigSec, declSec, userDefSec, bodySec]));
+wasmEval(moduleWithSections([sigSec, declSec, bodySec, userDefSec]));
+wasmEval(moduleWithSections([userDefSec, userDefSec, sigSec, declSec, bodySec]));
+wasmEval(moduleWithSections([userDefSec, userDefSec, sigSec, userDefSec, declSec, userDefSec, bodySec]));
+
+// Diagnose nonstandard block signature types.
+for (var bad of [0xff, 0, 1, 0x3f])
+ assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, bad, EndCode]})])])), CompileError, /invalid inline block type/);
+
+// Checking stack trace.
+function runStackTraceTest(namesContent, expectedName) {
+ var sections = [
+ sigSection([v2vSig]),
+ importSection([{sigIndex:0, module:"env", func:"callback"}]),
+ declSection([0]),
+ exportSection([{funcIndex:1, name: "run"}]),
+ bodySection([funcBody({locals: [], body: [CallCode, varU32(0)]})]),
+ userDefinedSection("whoa"),
+ userDefinedSection("wee", 42),
+ ];
+ if (namesContent)
+ sections.push(nameSection(namesContent));
+ sections.push(userDefinedSection("yay", 13));
+
+ var result = "";
+ var callback = () => {
+ var prevFrameEntry = new Error().stack.split('\n')[1];
+ result = prevFrameEntry.split('@')[0];
+ };
+ wasmEval(moduleWithSections(sections), {"env": { callback }}).run();
+ assertEq(result, expectedName);
+};
+
+runStackTraceTest(null, 'wasm-function[1]');
+runStackTraceTest([{name:'blah'}, {name: 'test'}], 'test');
+runStackTraceTest([{name:'blah'}, {name: 'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test');
+runStackTraceTest([{name:'blah'}, {name: 'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test');
+runStackTraceTest([{name:'blah'}, {name: 'test1'}, {name: 'test2'}], 'test1');
+runStackTraceTest([{name:'blah'}, {name: 'test☃'}], 'test☃');
+runStackTraceTest([{name:'blah'}, {name: 'te\xE0\xFF'}], 'te\xE0\xFF');
+runStackTraceTest([{name:'blah'}], 'wasm-function[1]');
+runStackTraceTest([], 'wasm-function[1]');
+// Notice that invalid names section content shall not fail the parsing
+runStackTraceTest([{name:'blah'}, {nameLen: 100, name: 'test'}], 'wasm-function[1]'); // invalid name size
+runStackTraceTest([{name:'blah'}, {name: 'test', locals: [{nameLen: 40, name: 'var1'}]}], 'wasm-function[1]'); // invalid variable name size
+runStackTraceTest([{name:'blah'}, {name: ''}], 'wasm-function[1]'); // empty name