blob: b7241795aa05db56833bd1a080060b0e621f7559 [file] [edit]
import * as assert from "../assert.js";
// memarg's offset field is a u64 for every memory; only its value is restricted by the memory's
// address type. An in-range offset written with a wider-than-minimal LEB is still well formed.
const SECTION_TYPE = 1;
const SECTION_FUNCTION = 3;
const SECTION_MEMORY = 5;
const SECTION_CODE = 10;
function leb(value) {
let bytes = [];
do {
let byte = value & 0x7f;
value >>>= 7;
if (value)
byte |= 0x80;
bytes.push(byte);
} while (value);
return bytes;
}
// The value's minimal encoding, padded out to byteCount bytes with redundant continuation bytes.
function paddedLeb(value, byteCount) {
let bytes = [];
for (let i = 0; i < byteCount; ++i) {
bytes.push(Number(value & 0x7fn) | (i + 1 < byteCount ? 0x80 : 0));
value >>= 7n;
}
if (value)
throw new Error(`${value} does not fit in ${byteCount} LEB bytes`);
return bytes;
}
function section(id, payload) {
return [id, ...leb(payload.length), ...payload];
}
function moduleBytes(isMemory64, body) {
let bytes = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
bytes.push(...section(SECTION_TYPE, [0x01, 0x60, 0x00, 0x00]));
bytes.push(...section(SECTION_FUNCTION, [0x01, 0x00]));
bytes.push(...section(SECTION_MEMORY, [0x01, isMemory64 ? 0x04 : 0x00, 0x01]));
const code = [0x00, ...body, 0x0b];
bytes.push(...section(SECTION_CODE, [0x01, ...leb(code.length), ...code]));
return new Uint8Array(bytes);
}
// i32.const 0 / i32.load align=4 offset=<offset> / drop
const load32 = (offsetBytes) => [0x41, 0x00, 0x28, 0x02, ...offsetBytes, 0x1a];
// i64.const 0 / i32.load align=4 offset=<offset> / drop
const load64 = (offsetBytes) => [0x42, 0x00, 0x28, 0x02, ...offsetBytes, 0x1a];
function assertValid(description, isMemory64, body) {
try {
new WebAssembly.Module(moduleBytes(isMemory64, body));
} catch (error) {
throw new Error(`${description}: expected to compile, got ${error}`);
}
}
function assertInvalid(description, isMemory64, body) {
try {
new WebAssembly.Module(moduleBytes(isMemory64, body));
} catch (error) {
assert.truthy(error instanceof WebAssembly.CompileError, `${description}: expected CompileError, got ${error}`);
return;
}
throw new Error(`${description}: module was accepted but is invalid`);
}
// An in-range offset is accepted at every encoded width, up to u64's maximum of ten bytes.
for (let byteCount = 1; byteCount <= 10; ++byteCount) {
assertValid(`memory32 offset 0 in ${byteCount} LEB bytes`, false, load32(paddedLeb(0n, byteCount)));
assertValid(`memory64 offset 0 in ${byteCount} LEB bytes`, true, load64(paddedLeb(0n, byteCount)));
}
for (let byteCount = 5; byteCount <= 10; ++byteCount)
assertValid(`memory32 offset 0xffffffff in ${byteCount} LEB bytes`, false, load32(paddedLeb(0xffffffffn, byteCount)));
// An offset that does not fit the memory's address type is still rejected.
assertInvalid("memory32 offset 2^32", false, load32(paddedLeb(0x100000000n, 5)));
assertInvalid("memory32 offset 2^64-1", false, load32(paddedLeb(0xffffffffffffffffn, 10)));
assertValid("memory64 offset 2^32", true, load64(paddedLeb(0x100000000n, 5)));
assertValid("memory64 offset 2^64-1", true, load64(paddedLeb(0xffffffffffffffffn, 10)));
// Eleven bytes cannot encode a u64.
assertInvalid("memory64 offset in 11 LEB bytes", true, load64([...paddedLeb(0n, 10).slice(0, 9), 0x80, 0x00]));