diff --git a/js/src/jit-test/tests/wasm/import-export.js b/js/src/jit-test/tests/wasm/import-export.js index 62a0846fe9d1..4916c1e35250 100644 --- a/js/src/jit-test/tests/wasm/import-export.js +++ b/js/src/jit-test/tests/wasm/import-export.js @@ -37,8 +37,8 @@ assertErrorMessage(() => new Memory({initial: 0, maximum: 65537}), RangeError, / assertErrorMessage(() => new Table({initial:2, maximum:1, element:"anyfunc"}), RangeError, /bad Table maximum size/); new Table({ initial: 10000000, element:"anyfunc" }); assertErrorMessage(() => new Table({initial:10000001, element:"anyfunc"}), RangeError, /bad Table initial size/); -new Table({ initial: 0, maximum: 2**32 - 1, element:"anyfunc" }); -assertErrorMessage(() => new Table({initial:0, maximum: 2**32, element:"anyfunc"}), RangeError, /bad Table maximum size/); +new Table({ initial: 0, maximum: 10000000, element:"anyfunc" }); +assertErrorMessage(() => new Table({initial:0, maximum: 10000001, element:"anyfunc"}), RangeError, /bad Table maximum size/); const m1 = new Module(wasmTextToBinary('(module (import "foo" "bar") (import "baz" "quux"))')); assertErrorMessage(() => new Instance(m1), TypeError, /second argument must be an object/); diff --git a/js/src/jit-test/tests/wasm/spec/harness/wasm-module-builder.js b/js/src/jit-test/tests/wasm/spec/harness/wasm-module-builder.js index 0a66f46f61a4..f7b1d04e081b 100644 --- a/js/src/jit-test/tests/wasm/spec/harness/wasm-module-builder.js +++ b/js/src/jit-test/tests/wasm/spec/harness/wasm-module-builder.js @@ -69,12 +69,14 @@ class Binary extends Array { // Emit section name. this.emit_u8(section_code); // Emit the section to a temporary buffer: its full length isn't know yet. - let section = new Binary; + const section = new Binary; content_generator(section); // Emit section length. this.emit_u32v(section.length); // Copy the temporary buffer. - this.push(...section); + for (const b of section) { + this.push(b); + } } } @@ -238,11 +240,22 @@ class WasmModuleBuilder { } appendToTable(array) { + for (let n of array) { + if (typeof n != 'number') + throw new Error('invalid table (entries have to be numbers): ' + array); + } return this.addFunctionTableInit(this.function_table.length, false, array); } + setFunctionTableBounds(min, max) { + this.function_table_length_min = min; + this.function_table_length_max = max; + return this; + } + setFunctionTableLength(length) { - this.function_table_length = length; + this.function_table_length_min = length; + this.function_table_length_max = length; return this; } @@ -320,25 +333,29 @@ class WasmModuleBuilder { } // Add function_table. - if (wasm.function_table_length > 0) { + if (wasm.function_table_length_min > 0) { if (debug) print("emitting table @ " + binary.length); binary.emit_section(kTableSectionCode, section => { section.emit_u8(1); // one table entry section.emit_u8(kWasmAnyFunctionTypeForm); - section.emit_u8(1); - section.emit_u32v(wasm.function_table_length); - section.emit_u32v(wasm.function_table_length); + const max = wasm.function_table_length_max; + const has_max = max !== undefined; + section.emit_u8(has_max ? kResizableMaximumFlag : 0); + section.emit_u32v(wasm.function_table_length_min); + if (has_max) section.emit_u32v(max); }); } // Add memory section - if (wasm.memory != undefined) { + if (wasm.memory !== undefined) { if (debug) print("emitting memory @ " + binary.length); binary.emit_section(kMemorySectionCode, section => { section.emit_u8(1); // one memory entry - section.emit_u32v(kResizableMaximumFlag); + const max = wasm.memory.max; + const has_max = max !== undefined; + section.emit_u32v(has_max ? kResizableMaximumFlag : 0); section.emit_u32v(wasm.memory.min); - section.emit_u32v(wasm.memory.max); + if (has_max) section.emit_u32v(max); }); } @@ -426,9 +443,9 @@ class WasmModuleBuilder { binary.emit_section(kElementSectionCode, section => { var inits = wasm.function_table_inits; section.emit_u32v(inits.length); - section.emit_u8(0); // table index for (let init of inits) { + section.emit_u8(0); // table index if (init.is_global) { section.emit_u8(kExprGetGlobal); } else { diff --git a/js/src/jit-test/tests/wasm/spec/jsapi.js b/js/src/jit-test/tests/wasm/spec/jsapi.js index f9e8a47678e9..efaf26711e06 100644 --- a/js/src/jit-test/tests/wasm/spec/jsapi.js +++ b/js/src/jit-test/tests/wasm/spec/jsapi.js @@ -557,7 +557,7 @@ test(() => { assert_equals(new Table({initial:1, element:"anyfunc"}) instanceof Table, true); assert_equals(new Table({initial:1.5, element:"anyfunc"}) instanceof Table, true); assert_equals(new Table({initial:1, maximum:1.5, element:"anyfunc"}) instanceof Table, true); - assert_equals(new Table({initial:1, maximum:Math.pow(2,32)-1, element:"anyfunc"}) instanceof Table, true); + assertThrows(() => new Table({initial:1, maximum:Math.pow(2,32)-1, element:"anyfunc"}), RangeError); }, "'WebAssembly.Table' constructor function"); test(() => { diff --git a/js/src/jit-test/tests/wasm/spec/limits.js b/js/src/jit-test/tests/wasm/spec/limits.js new file mode 100644 index 000000000000..18bd5dbb61fd --- /dev/null +++ b/js/src/jit-test/tests/wasm/spec/limits.js @@ -0,0 +1,380 @@ +// |jit-test| slow; + +/* + * Copyright 2018 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +let kJSEmbeddingMaxTypes = 1000000; +let kJSEmbeddingMaxFunctions = 1000000; +let kJSEmbeddingMaxImports = 100000; +let kJSEmbeddingMaxExports = 100000; +let kJSEmbeddingMaxGlobals = 1000000; +let kJSEmbeddingMaxDataSegments = 100000; + +let kJSEmbeddingMaxMemoryPages = 65536; +let kJSEmbeddingMaxModuleSize = 1024 * 1024 * 1024; // = 1 GiB +let kJSEmbeddingMaxFunctionSize = 7654321; +let kJSEmbeddingMaxFunctionLocals = 50000; +let kJSEmbeddingMaxFunctionParams = 1000; +let kJSEmbeddingMaxFunctionReturns = 1; +let kJSEmbeddingMaxTableSize = 10000000; +let kJSEmbeddingMaxTableEntries = 10000000; +let kJSEmbeddingMaxTables = 1; +let kJSEmbeddingMaxMemories = 1; + +function verbose(...args) { + if (false) print(...args); +} + +let kTestValidate = true; +let kTestSyncCompile = true; +let kTestAsyncCompile = true; + +//======================================================================= +// HARNESS SNIPPET, DO NOT COMMIT +//======================================================================= +const known_failures = { + // Enter failing tests like follows: + // "'WebAssembly.Instance.prototype.exports' accessor property": + // 'https://bugs.chromium.org/p/v8/issues/detail?id=5507', +}; + +let failures = []; +let unexpected_successes = []; + +let last_promise = new Promise((resolve, reject) => { resolve(); }); + +function test(func, description) { + let maybeErr; + try { func(); } + catch(e) { maybeErr = e; } + if (typeof maybeErr !== 'undefined') { + var known = ""; + if (known_failures[description]) { + known = " (known)"; + } + print(`${description}: FAIL${known}. ${maybeErr}`); + failures.push(description); + } else { + if (known_failures[description]) { + unexpected_successes.push(description); + } + print(`${description}: PASS.`); + } +} + +function promise_test(func, description) { + last_promise = last_promise.then(func) + .then(_ => { + if (known_failures[description]) { + unexpected_successes.push(description); + } + print(`${description}: PASS.`); + }) + .catch(err => { + var known = ""; + if (known_failures[description]) { + known = " (known)"; + } + print(`${description}: FAIL${known}. ${err}`); + failures.push(description); + }); +} +//======================================================================= + +function testLimit(name, min, limit, gen) { + print(""); + print(`==== Test ${name} limit = ${limit} ====`); + function run_validate(count) { + let expected = count <= limit; + verbose(` ${expected ? "(expect ok) " : "(expect fail)"} = ${count}...`); + + // TODO(titzer): builder is slow for large modules; make manual? + let builder = new WasmModuleBuilder(); + gen(builder, count); + let result = WebAssembly.validate(builder.toBuffer()); + + if (result != expected) { + let msg = `UNEXPECTED ${expected ? "FAIL" : "PASS"}: ${name} == ${count}`; + verbose(`=====> ${msg}`); + throw new Error(msg); + } + } + + function run_compile(count) { + let expected = count <= limit; + verbose(` ${expected ? "(expect ok) " : "(expect fail)"} = ${count}...`); + + // TODO(titzer): builder is slow for large modules; make manual? + let builder = new WasmModuleBuilder(); + gen(builder, count); + try { + let result = new WebAssembly.Module(builder.toBuffer()); + } catch (e) { + if (expected) { + let msg = `UNEXPECTED FAIL: ${name} == ${count} (${e})`; + verbose(`=====> ${msg}`); + throw new Error(msg); + } + return; + } + if (!expected) { + let msg = `UNEXPECTED PASS: ${name} == ${count}`; + verbose(`=====> ${msg}`); + throw new Error(msg); + } + } + + function run_async_compile(count) { + let expected = count <= limit; + verbose(` ${expected ? "(expect ok) " : "(expect fail)"} = ${count}...`); + + // TODO(titzer): builder is slow for large modules; make manual? + let builder = new WasmModuleBuilder(); + gen(builder, count); + let buffer = builder.toBuffer(); + WebAssembly.compile(buffer) + .then(result => { + if (!expected) { + let msg = `UNEXPECTED PASS: ${name} == ${count}`; + verbose(`=====> ${msg}`); + throw new Error(msg); + } + }) + .catch(err => { + if (expected) { + let msg = `UNEXPECTED FAIL: ${name} == ${count} (${e})`; + verbose(`=====> ${msg}`); + throw new Error(msg); + } + }) + } + + if (kTestValidate) { + print(""); + test(() => { + run_validate(min); + }, `Validate ${name} mininum`); + print(""); + test(() => { + run_validate(limit); + }, `Validate ${name} limit`); + print(""); + test(() => { + run_validate(limit+1); + }, `Validate ${name} over limit`); + } + + if (kTestSyncCompile) { + print(""); + test(() => { + run_compile(min); + }, `Compile ${name} mininum`); + print(""); + test(() => { + run_compile(limit); + }, `Compile ${name} limit`); + print(""); + test(() => { + run_compile(limit+1); + }, `Compile ${name} over limit`); + } + + if (kTestAsyncCompile) { + print(""); + promise_test(() => { + run_async_compile(min); + }, `Async compile ${name} mininum`); + print(""); + promise_test(() => { + run_async_compile(limit); + }, `Async compile ${name} limit`); + print(""); + promise_test(() => { + run_async_compile(limit+1); + }, `Async compile ${name} over limit`); + } +} + +// A little doodad to disable a test easily +let DISABLED = {testLimit: () => 0}; +let X = DISABLED; + +// passes +testLimit("types", 1, kJSEmbeddingMaxTypes, (builder, count) => { + for (let i = 0; i < count; i++) { + builder.addType(kSig_i_i); + } + }); + +// passes +testLimit("functions", 1, kJSEmbeddingMaxFunctions, (builder, count) => { + let type = builder.addType(kSig_v_v); + let body = [kExprEnd]; + for (let i = 0; i < count; i++) { + builder.addFunction(/*name=*/ undefined, type).addBody(body); + } + }); + +// passes +testLimit("imports", 1, kJSEmbeddingMaxImports, (builder, count) => { + let type = builder.addType(kSig_v_v); + for (let i = 0; i < count; i++) { + builder.addImport("", "", type); + } + }); + +// passes +testLimit("exports", 1, kJSEmbeddingMaxExports, (builder, count) => { + let type = builder.addType(kSig_v_v); + let f = builder.addFunction(/*name=*/ undefined, type); + f.addBody([kExprEnd]); + for (let i = 0; i < count; i++) { + builder.addExport("f" + i, f.index); + } + }); + +// passes +testLimit("globals", 1, kJSEmbeddingMaxGlobals, (builder, count) => { + for (let i = 0; i < count; i++) { + builder.addGlobal(kWasmI32, true); + } + }); + +// passes +testLimit("data segments", 1, kJSEmbeddingMaxDataSegments, (builder, count) => { + let data = []; + builder.addMemory(1, 1, false, false); + for (let i = 0; i < count; i++) { + builder.addDataSegment(0, data); + } + }); + +// fails +// jseward: we expect this to fail, because we support a max initial memory +// page count of 16384, whereas this expects an initial value of 63336 to be +// accepted. +X.testLimit("initial declared memory pages", 1, kJSEmbeddingMaxMemoryPages, + (builder, count) => { + builder.addMemory(count, undefined, false, false); + }); + +// passes +testLimit("maximum declared memory pages", 1, kJSEmbeddingMaxMemoryPages, + (builder, count) => { + builder.addMemory(1, count, false, false); + }); + +// fails +// jseward: we expect this to fail, because we support a max initial memory +// page count of 16384, whereas this expects an initial value of 63336 to be +// accepted. +X.testLimit("initial imported memory pages", 1, kJSEmbeddingMaxMemoryPages, + (builder, count) => { + builder.addImportedMemory("mod", "mem", count, undefined); + }); + +// passes +testLimit("maximum imported memory pages", 1, kJSEmbeddingMaxMemoryPages, + (builder, count) => { + builder.addImportedMemory("mod", "mem", 1, count); + }); + +// disabled +// TODO(titzer): ugh, that's hard to test. +DISABLED.testLimit("module size", 1, kJSEmbeddingMaxModuleSize, + (builder, count) => { + }); + +// passes +testLimit("function size", 2, kJSEmbeddingMaxFunctionSize, (builder, count) => { + let type = builder.addType(kSig_v_v); + let nops = count-2; + let array = new Array(nops); + for (let i = 0; i < nops; i++) array[i] = kExprNop; + array[nops] = kExprEnd; + builder.addFunction(undefined, type).addBody(array); + }); + +// passes +testLimit("function locals", 1, kJSEmbeddingMaxFunctionLocals, (builder, count) => { + let type = builder.addType(kSig_v_v); + builder.addFunction(undefined, type) + .addLocals({i32_count: count}) + .addBody([kExprEnd]); + }); + +// passes +testLimit("function params", 1, kJSEmbeddingMaxFunctionParams, (builder, count) => { + let array = new Array(count); + for (let i = 0; i < count; i++) array[i] = kWasmI32; + let type = builder.addType({params: array, results: []}); + }); + +// passes +testLimit("function params+locals", 1, kJSEmbeddingMaxFunctionLocals - 2, (builder, count) => { + let type = builder.addType(kSig_i_ii); + builder.addFunction(undefined, type) + .addLocals({i32_count: count}) + .addBody([kExprUnreachable, kExprEnd]); + }); + +// passes +testLimit("function returns", 0, kJSEmbeddingMaxFunctionReturns, (builder, count) => { + let array = new Array(count); + for (let i = 0; i < count; i++) array[i] = kWasmI32; + let type = builder.addType({params: [], results: array}); + }); + +// passes +testLimit("initial table size", 1, kJSEmbeddingMaxTableSize, (builder, count) => { + builder.setFunctionTableBounds(count, undefined); + }); + +// passes +testLimit("maximum table size", 1, kJSEmbeddingMaxTableSize, (builder, count) => { + builder.setFunctionTableBounds(1, count); + }); + +// passes +testLimit("table entries", 1, kJSEmbeddingMaxTableEntries, (builder, count) => { + builder.setFunctionTableBounds(1, 1); + let array = []; + for (let i = 0; i < count; i++) { + builder.addFunctionTableInit(0, false, array, false); + } + }); + +// passes +testLimit("tables", 0, kJSEmbeddingMaxTables, (builder, count) => { + for (let i = 0; i < count; i++) { + builder.addImportedTable("", "", 1, 1); + } + }); + +// passed +testLimit("memories", 0, kJSEmbeddingMaxMemories, (builder, count) => { + for (let i = 0; i < count; i++) { + builder.addImportedMemory("", "", 1, 1, false); + } + }); + +//======================================================================= +// HARNESS SNIPPET, DO NOT COMMIT +//======================================================================= +if (false && failures.length > 0) { + throw failures[0]; +} +//======================================================================= diff --git a/js/src/wasm/WasmBinaryConstants.h b/js/src/wasm/WasmBinaryConstants.h index fe887deab251..74bd2366ea6d 100644 --- a/js/src/wasm/WasmBinaryConstants.h +++ b/js/src/wasm/WasmBinaryConstants.h @@ -600,17 +600,21 @@ static const unsigned MaxExports = 100000; static const unsigned MaxGlobals = 1000000; static const unsigned MaxDataSegments = 100000; static const unsigned MaxElemSegments = 10000000; -static const unsigned MaxTableInitialLength = 10000000; -static const unsigned MaxStringBytes = 100000; +static const unsigned MaxTableMaximumLength = 10000000; static const unsigned MaxLocals = 50000; static const unsigned MaxParams = 1000; -static const unsigned MaxBrTableElems = 1000000; -static const unsigned MaxMemoryInitialPages = 16384; -static const unsigned MaxMemoryMaximumPages = 65536; -static const unsigned MaxCodeSectionBytes = 1024 * 1024 * 1024; -static const unsigned MaxModuleBytes = MaxCodeSectionBytes; +static const unsigned MaxMemoryMaximumPages = 65536; +static const unsigned MaxStringBytes = 100000; +static const unsigned MaxModuleBytes = 1024 * 1024 * 1024; static const unsigned MaxFunctionBytes = 7654321; +// These limits pertain to our WebAssembly implementation only. + +static const unsigned MaxTableInitialLength = 10000000; +static const unsigned MaxBrTableElems = 1000000; +static const unsigned MaxMemoryInitialPages = 16384; +static const unsigned MaxCodeSectionBytes = MaxModuleBytes; + // A magic value of the FramePointer to indicate after a return to the entry // stub that an exception has been caught and that we should throw. diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index c0da76bde16a..3f3f07c9eebc 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -1903,7 +1903,7 @@ WasmTableObject::construct(JSContext* cx, unsigned argc, Value* vp) } Limits limits; - if (!GetLimits(cx, obj, MaxTableInitialLength, UINT32_MAX, "Table", &limits, + if (!GetLimits(cx, obj, MaxTableInitialLength, MaxTableMaximumLength, "Table", &limits, Shareable::False)) { return false; diff --git a/js/src/wasm/WasmValidate.cpp b/js/src/wasm/WasmValidate.cpp index 31aad8fea3b3..146e580b9e7d 100644 --- a/js/src/wasm/WasmValidate.cpp +++ b/js/src/wasm/WasmValidate.cpp @@ -1226,7 +1226,12 @@ DecodeTableLimits(Decoder& d, TableDescVector* tables) if (!DecodeLimits(d, &limits)) return false; - if (limits.initial > MaxTableInitialLength) + // If there's a maximum, check it is in range. The check to exclude + // initial > maximum is carried out by the DecodeLimits call above, so + // we don't repeat it here. + if (limits.initial > MaxTableInitialLength || + ((limits.maximum.isSome() && + limits.maximum.value() > MaxTableMaximumLength))) return d.fail("too many table elements"); if (tables->length()) diff --git a/testing/web-platform/mozilla/tests/wasm/js/jsapi.js b/testing/web-platform/mozilla/tests/wasm/js/jsapi.js index 4bf9a7d8a98d..c0b9c4a200b6 100644 --- a/testing/web-platform/mozilla/tests/wasm/js/jsapi.js +++ b/testing/web-platform/mozilla/tests/wasm/js/jsapi.js @@ -526,7 +526,7 @@ test(() => { assert_equals(new Table({initial:1, element:"anyfunc"}) instanceof Table, true); assert_equals(new Table({initial:1.5, element:"anyfunc"}) instanceof Table, true); assert_equals(new Table({initial:1, maximum:1.5, element:"anyfunc"}) instanceof Table, true); - assert_equals(new Table({initial:1, maximum:Math.pow(2,32)-1, element:"anyfunc"}) instanceof Table, true); + assertThrows(() => new Table({initial:1, maximum:Math.pow(2,32)-1, element:"anyfunc"}), RangeError); }, "'WebAssembly.Table' constructor function"); test(() => {