| // META: global=jsshell |
| // META: script=/wasm/jsapi/wasm-module-builder.js |
| // META: timeout=long |
| |
| // Static limits |
| const kJSEmbeddingMaxTypes = 1000000; |
| const kJSEmbeddingMaxFunctions = 1000000; |
| const kJSEmbeddingMaxImports = 1000000; |
| const kJSEmbeddingMaxExports = 1000000; |
| const kJSEmbeddingMaxGlobals = 1000000; |
| const kJSEmbeddingMaxDataSegments = 100000; |
| |
| const kJSEmbeddingMaxModuleSize = 1024 * 1024 * 1024; // = 1 GiB |
| const kJSEmbeddingMaxFunctionSize = 7654321; |
| const kJSEmbeddingMaxFunctionLocals = 50000; |
| const kJSEmbeddingMaxFunctionParams = 1000; |
| const kJSEmbeddingMaxFunctionReturns = 1000; |
| const kJSEmbeddingMaxElementSegments = 10000000; |
| const kJSEmbeddingMaxTables = 100000; |
| const kJSEmbeddingMaxMemories = 1; |
| |
| // Dynamic limits |
| const kJSEmbeddingMaxTableSize = 10000000; |
| |
| // This function runs the {gen} function with the values {min}, {limit}, and |
| // {limit+1}, assuming that values below and including the limit should |
| // pass. {name} is used for test names. |
| function testLimit(name, min, limit, gen) { |
| function get_buffer(count) { |
| const builder = new WasmModuleBuilder(); |
| gen(builder, count); |
| return builder.toBuffer(); |
| } |
| |
| const buffer_with_min = get_buffer(min); |
| const buffer_with_limit = get_buffer(limit); |
| const buffer_with_limit_plus_1 = get_buffer(limit + 1); |
| |
| test(() => { assert_true(WebAssembly.validate(buffer_with_min)); }, |
| `Validate ${name} minimum`); |
| test(() => { assert_true(WebAssembly.validate(buffer_with_limit)); }, |
| `Validate ${name} limit`); |
| test(() => { assert_false(WebAssembly.validate(buffer_with_limit_plus_1)); }, |
| `Validate ${name} over limit`); |
| |
| test(() => { new WebAssembly.Module(buffer_with_min); }, |
| `Compile ${name} minimum`); |
| test(() => { new WebAssembly.Module(buffer_with_limit); }, |
| `Compile ${name} limit`); |
| test(() => { |
| assert_throws(new WebAssembly.CompileError(), |
| () => new WebAssembly.Module(buffer_with_limit_plus_1)); |
| }, `Compile ${name} over limit`); |
| |
| promise_test(t => { return WebAssembly.compile(buffer_with_min); }, |
| `Async compile ${name} minimum`); |
| promise_test(t => { return WebAssembly.compile(buffer_with_limit); }, |
| `Async compile ${name} limit`); |
| promise_test(t => { |
| return promise_rejects(t, new WebAssembly.CompileError(), |
| WebAssembly.compile(buffer_with_limit_plus_1)); |
| }, `Async compile ${name} over limit`); |
| } |
| |
| testLimit("types", 1, kJSEmbeddingMaxTypes, (builder, count) => { |
| for (let i = 0; i < count; i++) { |
| builder.addType(kSig_i_i); |
| } |
| }); |
| |
| testLimit("functions", 1, kJSEmbeddingMaxFunctions, (builder, count) => { |
| const type = builder.addType(kSig_v_v); |
| const body = []; |
| for (let i = 0; i < count; i++) { |
| builder.addFunction(/*name=*/ undefined, type).addBody(body); |
| } |
| }); |
| |
| testLimit("imports", 1, kJSEmbeddingMaxImports, (builder, count) => { |
| const type = builder.addType(kSig_v_v); |
| for (let i = 0; i < count; i++) { |
| builder.addImport("", "", type); |
| } |
| }); |
| |
| testLimit("exports", 1, kJSEmbeddingMaxExports, (builder, count) => { |
| const type = builder.addType(kSig_v_v); |
| const f = builder.addFunction(/*name=*/ undefined, type); |
| f.addBody([]); |
| for (let i = 0; i < count; i++) { |
| builder.addExport("f" + i, f.index); |
| } |
| }); |
| |
| testLimit("globals", 1, kJSEmbeddingMaxGlobals, (builder, count) => { |
| for (let i = 0; i < count; i++) { |
| builder.addGlobal(kWasmI32, true); |
| } |
| }); |
| |
| testLimit("data segments", 1, kJSEmbeddingMaxDataSegments, (builder, count) => { |
| const data = []; |
| builder.addMemory(1, 1, false, false); |
| for (let i = 0; i < count; i++) { |
| builder.addDataSegment(0, data); |
| } |
| }); |
| |
| testLimit("function size", 2, kJSEmbeddingMaxFunctionSize, (builder, count) => { |
| const type = builder.addType(kSig_v_v); |
| const nops = count - 2; |
| const array = new Array(nops); |
| for (let i = 0; i < nops; i++) |
| array[i] = kExprNop; |
| builder.addFunction(undefined, type).addBody(array); |
| }); |
| |
| testLimit("function locals", 1, kJSEmbeddingMaxFunctionLocals, |
| (builder, count) => { |
| const type = builder.addType(kSig_v_v); |
| builder.addFunction(undefined, type) |
| .addLocals({i32_count : count}) |
| .addBody([]); |
| }); |
| |
| testLimit("function params", 1, kJSEmbeddingMaxFunctionParams, |
| (builder, count) => { |
| const array = new Array(count); |
| for (let i = 0; i < count; i++) { |
| array[i] = kWasmI32; |
| } |
| const type = builder.addType({params : array, results : []}); |
| }); |
| |
| testLimit("function params+locals", 1, kJSEmbeddingMaxFunctionLocals - 2, |
| (builder, count) => { |
| const type = builder.addType(kSig_i_ii); |
| builder.addFunction(undefined, type) |
| .addLocals({i32_count : count}) |
| .addBody([ kExprUnreachable ]); |
| }); |
| |
| testLimit("function returns", 0, kJSEmbeddingMaxFunctionReturns, |
| (builder, count) => { |
| const array = new Array(count); |
| for (let i = 0; i < count; i++) { |
| array[i] = kWasmI32; |
| } |
| const type = builder.addType({params : [], results : array}); |
| }); |
| |
| testLimit("element segments", 1, kJSEmbeddingMaxElementSegments, |
| (builder, count) => { |
| builder.setTableBounds(1, 1); |
| const array = []; |
| for (let i = 0; i < count; i++) { |
| builder.addElementSegment(0, false, false, array); |
| } |
| }); |
| |
| testLimit("tables", 0, kJSEmbeddingMaxTables, (builder, count) => { |
| for (let i = 0; i < count; i++) { |
| builder.addImportedTable("", "", 1, 1); |
| } |
| }); |
| |
| testLimit("memories", 0, kJSEmbeddingMaxMemories, (builder, count) => { |
| for (let i = 0; i < count; i++) { |
| builder.addImportedMemory("", "", 1, 1, false); |
| } |
| }); |
| |
| const instantiationShouldFail = 1; |
| const instantiationShouldSucceed = 2; |
| // This function tries to compile and instantiate the module produced |
| // with {gen}. Compilation should work, an error should only happen during |
| // instantiation or runtime. If {instantiationResult} is |
| // {instantiationShouldSucceed}, then {gen} should generate a function called |
| // "grow" which grows the tested aspect and returns "-1" if growing fails. |
| function testDynamicLimit(name, instantiationResult, imports, gen) { |
| const builder = new WasmModuleBuilder(); |
| gen(builder); |
| const buffer = builder.toBuffer(); |
| |
| test(() => { assert_true(WebAssembly.validate(buffer)); }, |
| `Validate ${name} beyond its dynamic limit`); |
| |
| test(() => { new WebAssembly.Module(buffer); }, |
| `Compile ${name} beyond its dynamic limit`); |
| |
| promise_test(t => { return WebAssembly.compile(buffer); }, |
| `Async compile ${name} beyond its dynamic limit.`); |
| |
| test(() => { |
| const compiled_module = new WebAssembly.Module(buffer); |
| if (instantiationResult == instantiationShouldFail) { |
| assert_throws(new RangeError(), |
| () => new WebAssembly.Instance(compiled_module, imports)); |
| } else if (instantiationResult == instantiationShouldSucceed) { |
| const instance = new WebAssembly.Instance(compiled_module, imports); |
| assertEquals(-1, instance.exports.grow()); |
| } |
| }, `Instantiate ${name} over limit`); |
| |
| promise_test(t => { |
| if (instantiationResult == instantiationShouldFail) { |
| return Promise.resolve(); |
| return promise_rejects(t, new RangeError(), |
| WebAssembly.instantiate(buffer, imports)); |
| } else if (instantiationResult == instantiationShouldSucceed) { |
| return WebAssembly.instantiate(buffer, imports) |
| .then(({instance}) => { assertEquals(-1, instance.exports.grow()); }); |
| } else { |
| return Promise.resolve(); |
| } |
| }, `Async instantiate ${name} over limit`); |
| } |
| |
| testDynamicLimit("initial table size", instantiationShouldFail, {}, (builder) => { |
| builder.setTableBounds(kJSEmbeddingMaxTableSize + 1, undefined); |
| }); |
| |
| testDynamicLimit( |
| "maximum table size", instantiationShouldSucceed, {}, (builder) => { |
| builder.setTableBounds(1, kJSEmbeddingMaxTableSize + 1); |
| // table.grow requires the reference types proposal. Instead we just |
| // return -1. |
| builder.addFunction("grow", kSig_i_v) |
| .addBody([ |
| ...wasmI32Const(-1) |
| ]) |
| .exportFunc(); |
| }); |
| |
| test(() => { |
| assert_throws( |
| new RangeError(), |
| () => new WebAssembly.Table( |
| {element : "anyfunc", initial : kJSEmbeddingMaxTableSize + 1})); |
| |
| let memory = new WebAssembly.Table( |
| {initial : 1, maximum : kJSEmbeddingMaxTableSize + 1, element: "anyfunc"}); |
| assert_throws(new RangeError(), |
| () => memory.grow(kJSEmbeddingMaxTableSize)); |
| }, `Grow WebAssembly.Table object beyond the embedder-defined limit`); |
| |
| function testModuleSizeLimit(size, expectPass) { |
| // We do not use `testLimit` here to avoid OOMs due to having multiple big |
| // modules alive at the same time. |
| |
| // Define a WebAssembly module that consists of a single custom section which |
| // has an empty name. The module size will be `size`. |
| let buffer; |
| try { |
| buffer = new Uint8Array(size); |
| } catch (e) { |
| if (e instanceof RangeError) { |
| // Allocation of a big TypedArray may fail. |
| return; |
| } |
| throw e; |
| } |
| const header = [ |
| kWasmH0, kWasmH1, kWasmH2, kWasmH3, // magic word |
| kWasmV0, kWasmV1, kWasmV2, kWasmV3, // version |
| 0 // custom section |
| ]; |
| // We calculate the section length so that the total module size is `size`. |
| // For that we have to calculate the length of the leb encoding of the section |
| // length. |
| const sectionLength = size - header.length - |
| wasmSignedLeb(size).length; |
| const lengthBytes = wasmSignedLeb(sectionLength); |
| buffer.set(header); |
| buffer.set(lengthBytes, header.length); |
| |
| if (expectPass) { |
| test(() => { |
| assert_true(WebAssembly.validate(buffer)); |
| }, `Validate module size limit`); |
| test(() => { |
| new WebAssembly.Module(buffer); |
| }, `Compile module size limit`); |
| promise_test(t => { |
| return WebAssembly.compile(buffer); |
| }, `Async compile module size limit`); |
| } else { |
| test(() => { |
| assert_false(WebAssembly.validate(buffer)); |
| }, `Validate module size over limit`); |
| test(() => { |
| assert_throws( |
| new WebAssembly.CompileError(), |
| () => new WebAssembly.Module(buffer)); |
| }, `Compile module size over limit`); |
| promise_test(t => { |
| return promise_rejects( |
| t, new WebAssembly.CompileError(), |
| WebAssembly.compile(buffer)); |
| }, `Async compile module size over limit`); |
| } |
| } |
| |
| testModuleSizeLimit(kJSEmbeddingMaxModuleSize, true); |
| testModuleSizeLimit(kJSEmbeddingMaxModuleSize + 1, false); |