blob: fae8ccb07763b0788e6077c7c557bcc797c9c7b3 [file] [edit]
import * as assert from "../assert.js";
/*
* Test for IPInt variable-length LEB128 sub-opcode handling.
*
* This test validates the fix for a bug where certain WebAssembly GC opcodes
* incorrectly assumed fixed-length instruction encoding. The bug occurred because:
* - GC opcodes use a two-level encoding: base opcode (0xFB) + sub-opcode (LEB128)
* - LEB128 allows the same number to be represented multiple ways (e.g., 15 can be 0x0F or 0x8F 0x00)
* - Some opcodes used advancePC(2) (constant) instead of dynamic length tracking
* - This caused IPInt to advance by the wrong number of bytes with redundant encodings
*
* The test manually constructs WASM binaries with redundantly-encoded sub-opcodes
* to verify IPInt handles variable-length encodings correctly.
*
* Fixed opcodes tested:
* - ref.i31 (0x1C), i31.get_s (0x1D), i31.get_u (0x1E)
* - array.len (0x0F), array.fill (0x10), array.copy (0x11)
* - any.convert_extern (0x1A), extern.convert_any (0x1B)
*/
// Helper function to create redundant LEB128 encoding of a value
// For values < 128, we can add redundant continuation bytes
function createRedundantLEB128(value, totalBytes) {
if (totalBytes === 1) {
return [value];
}
// Create redundant encoding by adding continuation bytes
let result = [];
for (let i = 0; i < totalBytes - 1; i++) {
if (i === 0) {
result.push(value | 0x80); // First byte with continuation bit
} else {
result.push(0x80); // Middle bytes: just continuation bit
}
}
result.push(0x00); // Final byte: no continuation bit, value 0
return result;
}
// Helper to calculate LEB128 size for section sizes
function encodeVarUInt32(value) {
let result = [];
do {
let byte = value & 0x7F;
value >>>= 7;
if (value !== 0) {
byte |= 0x80;
}
result.push(byte);
} while (value !== 0);
return result;
}
// ========================================
// ref.i31 Tests
// ========================================
function testRefI31RedundantEncoding() {
// Test ref.i31 with normal encoding (1 byte sub-opcode)
{
let extendedOp = [0x1C]; // ref.i31 normal
let codeBody = [
0x00, // 0 locals
0x41, 0x2A, // i32.const 42
0xFB, ...extendedOp, // ref.i31
0x0B // end
];
let codeSectionSize = encodeVarUInt32(1 + encodeVarUInt32(codeBody.length).length + codeBody.length);
let bytes = new Uint8Array([
// WASM header
0x00, 0x61, 0x73, 0x6D, // Magic
0x01, 0x00, 0x00, 0x00, // Version
// Type section [type 0: () -> i31ref]
0x01, // Section ID
0x05, // Section size
0x01, // 1 type
0x60, // Function type
0x00, // 0 params
0x01, // 1 return
0x6C, // i31ref (ref null i31)
// Function section [function 0 uses type 0]
0x03, // Section ID
0x02, // Section size
0x01, // 1 function
0x00, // Type index 0
// Export section
0x07, // Section ID
0x05, // Section size
0x01, // 1 export
0x01, 0x66, // Name length=1, name="f"
0x00, // Export kind: function
0x00, // Function index 0
// Code section
0x0A, // Section ID
...codeSectionSize,
0x01, // 1 function body
...encodeVarUInt32(codeBody.length),
...codeBody
]);
let module = new WebAssembly.Module(bytes);
let instance = new WebAssembly.Instance(module);
assert.eq(instance.exports.f(), 42);
}
// Test ref.i31 with 2-byte redundant encoding
{
let extendedOp = createRedundantLEB128(0x1C, 2); // ref.i31 redundant: [0x9C, 0x00]
let codeBody = [
0x00, // 0 locals
0x41, 0x2A, // i32.const 42
0xFB, ...extendedOp, // ref.i31 with redundant encoding
0x0B // end
];
let codeSectionSize = encodeVarUInt32(1 + encodeVarUInt32(codeBody.length).length + codeBody.length);
let bytes = new Uint8Array([
// WASM header
0x00, 0x61, 0x73, 0x6D, // Magic
0x01, 0x00, 0x00, 0x00, // Version
// Type section [type 0: () -> i31ref]
0x01, // Section ID
0x05, // Section size
0x01, // 1 type
0x60, // Function type
0x00, // 0 params
0x01, // 1 return
0x6C, // i31ref (ref null i31)
// Function section [function 0 uses type 0]
0x03, // Section ID
0x02, // Section size
0x01, // 1 function
0x00, // Type index 0
// Export section
0x07, // Section ID
0x05, // Section size
0x01, // 1 export
0x01, 0x66, // Name length=1, name="f"
0x00, // Export kind: function
0x00, // Function index 0
// Code section
0x0A, // Section ID
...codeSectionSize,
0x01, // 1 function body
...encodeVarUInt32(codeBody.length),
...codeBody
]);
let module = new WebAssembly.Module(bytes);
let instance = new WebAssembly.Instance(module);
assert.eq(instance.exports.f(), 42);
}
// Test ref.i31 with 3-byte redundant encoding
{
let extendedOp = createRedundantLEB128(0x1C, 3); // ref.i31 redundant: [0x9C, 0x80, 0x00]
let codeBody = [
0x00, // 0 locals
0x41, 0x2A, // i32.const 42
0xFB, ...extendedOp, // ref.i31 with extra redundant encoding
0x0B // end
];
let codeSectionSize = encodeVarUInt32(1 + encodeVarUInt32(codeBody.length).length + codeBody.length);
let bytes = new Uint8Array([
// WASM header
0x00, 0x61, 0x73, 0x6D, // Magic
0x01, 0x00, 0x00, 0x00, // Version
// Type section [type 0: () -> i31ref]
0x01, // Section ID
0x05, // Section size
0x01, // 1 type
0x60, // Function type
0x00, // 0 params
0x01, // 1 return
0x6C, // i31ref (ref null i31)
// Function section [function 0 uses type 0]
0x03, // Section ID
0x02, // Section size
0x01, // 1 function
0x00, // Type index 0
// Export section
0x07, // Section ID
0x05, // Section size
0x01, // 1 export
0x01, 0x66, // Name length=1, name="f"
0x00, // Export kind: function
0x00, // Function index 0
// Code section
0x0A, // Section ID
...codeSectionSize,
0x01, // 1 function body
...encodeVarUInt32(codeBody.length),
...codeBody
]);
let module = new WebAssembly.Module(bytes);
let instance = new WebAssembly.Instance(module);
assert.eq(instance.exports.f(), 42);
}
}
// ========================================
// i31.get_s and i31.get_u Tests
// ========================================
function testI31GetRedundantEncoding() {
// Test i31.get_s with 2-byte redundant encoding
{
let extendedOp1 = [0x1C]; // ref.i31 normal
let extendedOp2 = createRedundantLEB128(0x1D, 2); // i31.get_s redundant
let codeBody = [
0x00, // 0 locals
0x41, 0x2A, // i32.const 42
0xFB, ...extendedOp1, // ref.i31
0xFB, ...extendedOp2, // i31.get_s with redundant encoding
0x0B // end
];
let codeSectionSize = encodeVarUInt32(1 + encodeVarUInt32(codeBody.length).length + codeBody.length);
let bytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7F, // Type: () -> i32
0x03, 0x02, 0x01, 0x00,
0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00,
0x0A, ...codeSectionSize, 0x01, ...encodeVarUInt32(codeBody.length), ...codeBody
]);
let module = new WebAssembly.Module(bytes);
let instance = new WebAssembly.Instance(module);
assert.eq(instance.exports.f(), 42);
}
// Test i31.get_u with 3-byte redundant encoding
{
let extendedOp1 = [0x1C]; // ref.i31 normal
let extendedOp2 = createRedundantLEB128(0x1E, 3); // i31.get_u redundant
let codeBody = [
0x00, // 0 locals
0x41, 0x2A, // i32.const 42
0xFB, ...extendedOp1, // ref.i31
0xFB, ...extendedOp2, // i31.get_u with extra redundant encoding
0x0B // end
];
let codeSectionSize = encodeVarUInt32(1 + encodeVarUInt32(codeBody.length).length + codeBody.length);
let bytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7F, // Type: () -> i32
0x03, 0x02, 0x01, 0x00,
0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00,
0x0A, ...codeSectionSize, 0x01, ...encodeVarUInt32(codeBody.length), ...codeBody
]);
let module = new WebAssembly.Module(bytes);
let instance = new WebAssembly.Instance(module);
assert.eq(instance.exports.f(), 42);
}
}
// Run tests
testRefI31RedundantEncoding();
testI31GetRedundantEncoding();
// ========================================
// array.len Tests
// ========================================
function testArrayLenRedundantEncoding() {
// Test array.len with 2-byte redundant encoding
{
let extendedOp1 = [0x07]; // array.new_default normal
let extendedOp2 = createRedundantLEB128(0x0F, 2); // array.len redundant
let codeBody = [
0x00, // 0 locals
0x41, 0x05, // i32.const 5
0xFB, ...extendedOp1, 0x00, // array.new_default type_index=0
0xFB, ...extendedOp2, // array.len with redundant encoding
0x0B // end
];
let codeSectionSize = encodeVarUInt32(1 + encodeVarUInt32(codeBody.length).length + codeBody.length);
let bytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
// Type section: 2 types
0x01, 0x08, 0x02,
0x5E, 0x7F, 0x01, // Type 0: (array i32 mutable)
0x60, 0x00, 0x01, 0x7F, // Type 1: () -> i32
// Function section
0x03, 0x02, 0x01, 0x01,
// Export section
0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00,
// Code section
0x0A, ...codeSectionSize, 0x01, ...encodeVarUInt32(codeBody.length), ...codeBody
]);
let module = new WebAssembly.Module(bytes);
let instance = new WebAssembly.Instance(module);
assert.eq(instance.exports.f(), 5);
}
// Test array.len with 3-byte redundant encoding
{
let extendedOp1 = [0x07]; // array.new_default normal
let extendedOp2 = createRedundantLEB128(0x0F, 3); // array.len extra redundant
let codeBody = [
0x00, // 0 locals
0x41, 0x0A, // i32.const 10
0xFB, ...extendedOp1, 0x00, // array.new_default type_index=0
0xFB, ...extendedOp2, // array.len with extra redundant encoding
0x0B // end
];
let codeSectionSize = encodeVarUInt32(1 + encodeVarUInt32(codeBody.length).length + codeBody.length);
let bytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
// Type section: 2 types
0x01, 0x08, 0x02,
0x5E, 0x7F, 0x01, // Type 0: (array i32 mutable)
0x60, 0x00, 0x01, 0x7F, // Type 1: () -> i32
// Function section
0x03, 0x02, 0x01, 0x01,
// Export section
0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00,
// Code section
0x0A, ...codeSectionSize, 0x01, ...encodeVarUInt32(codeBody.length), ...codeBody
]);
let module = new WebAssembly.Module(bytes);
let instance = new WebAssembly.Instance(module);
assert.eq(instance.exports.f(), 10);
}
}
// ========================================
// array.fill Tests
// ========================================
function testArrayFillRedundantEncoding() {
// Test array.fill with 2-byte redundant encoding
{
let extendedOp1 = [0x07]; // array.new_default normal
let extendedOp2 = createRedundantLEB128(0x10, 2); // array.fill redundant
let extendedOp3 = [0x0B]; // array.get normal
let codeBody = [
0x01, 0x01, 0x63, 0x00, // 1 local: (ref null 0) = arrayref
// Create array
0x41, 0x05, // i32.const 5
0xFB, ...extendedOp1, 0x00, // array.new_default type_index=0
0x21, 0x00, // local.set 0
// Fill array
0x20, 0x00, // local.get 0
0x41, 0x00, // i32.const 0 (start index)
0x41, 0x2A, // i32.const 42 (value)
0x41, 0x05, // i32.const 5 (count)
0xFB, ...extendedOp2, 0x00, // array.fill with redundant encoding, type_index=0
// Get and return first element
0x20, 0x00, // local.get 0
0x41, 0x00, // i32.const 0
0xFB, ...extendedOp3, 0x00, // array.get type_index=0
0x0B // end
];
let codeSectionSize = encodeVarUInt32(1 + encodeVarUInt32(codeBody.length).length + codeBody.length);
let bytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
// Type section: 2 types
0x01, 0x08, 0x02,
0x5E, 0x7F, 0x01, // Type 0: (array i32 mutable)
0x60, 0x00, 0x01, 0x7F, // Type 1: () -> i32
// Function section
0x03, 0x02, 0x01, 0x01,
// Export section
0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00,
// Code section
0x0A, ...codeSectionSize, 0x01, ...encodeVarUInt32(codeBody.length), ...codeBody
]);
let module = new WebAssembly.Module(bytes);
let instance = new WebAssembly.Instance(module);
assert.eq(instance.exports.f(), 42);
}
}
// ========================================
// array.copy Tests
// ========================================
function testArrayCopyRedundantEncoding() {
// Test array.copy with 2-byte redundant encoding
{
let extendedOp1 = [0x06]; // array.new normal
let extendedOp2 = [0x07]; // array.new_default normal
let extendedOp3 = createRedundantLEB128(0x11, 2); // array.copy redundant
let extendedOp4 = [0x0B]; // array.get normal
let codeBody = [
0x01, 0x02, 0x63, 0x00, // 1 local declaration: 2 locals of type (ref null 0) = arrayref
// Create source array with value 99
0x41, 0xE3, 0x00, // i32.const 99 (LEB128 encoded)
0x41, 0x03, // i32.const 3 (size)
0xFB, ...extendedOp1, 0x00, // array.new type_index=0
0x21, 0x00, // local.set 0 (source array)
// Create destination array
0x41, 0x03, // i32.const 3
0xFB, ...extendedOp2, 0x00, // array.new_default type_index=0
0x21, 0x01, // local.set 1 (dest array)
// Copy from source to dest
0x20, 0x01, // local.get 1 (dest)
0x41, 0x00, // i32.const 0 (dest index)
0x20, 0x00, // local.get 0 (source)
0x41, 0x00, // i32.const 0 (source index)
0x41, 0x03, // i32.const 3 (count)
0xFB, ...extendedOp3, 0x00, 0x00, // array.copy with redundant encoding, dest_type=0, src_type=0
// Get and return first element of dest
0x20, 0x01, // local.get 1
0x41, 0x00, // i32.const 0
0xFB, ...extendedOp4, 0x00, // array.get type_index=0
0x0B // end
];
let codeSectionSize = encodeVarUInt32(1 + encodeVarUInt32(codeBody.length).length + codeBody.length);
let bytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
// Type section: 2 types
0x01, 0x08, 0x02,
0x5E, 0x7F, 0x01, // Type 0: (array i32 mutable)
0x60, 0x00, 0x01, 0x7F, // Type 1: () -> i32
// Function section
0x03, 0x02, 0x01, 0x01,
// Export section
0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00,
// Code section
0x0A, ...codeSectionSize, 0x01, ...encodeVarUInt32(codeBody.length), ...codeBody
]);
let module = new WebAssembly.Module(bytes);
let instance = new WebAssembly.Instance(module);
assert.eq(instance.exports.f(), 99);
}
}
// ========================================
// any.convert_extern and extern.convert_any Tests
// ========================================
function testExternAnyConvertRedundantEncoding() {
// Test extern.convert_any with 2-byte redundant encoding
{
let extendedOp1 = [0x1C]; // ref.i31 normal
let extendedOp2 = createRedundantLEB128(0x1B, 2); // extern.convert_any redundant
let codeBody = [
0x00, // 0 locals
0x41, 0x2A, // i32.const 42
0xFB, ...extendedOp1, // ref.i31
0xFB, ...extendedOp2, // extern.convert_any with redundant encoding
0xD1, // ref.is_null
0x45, // i32.eqz (invert: return 1 if not null, 0 if null)
0x0B // end
];
let codeSectionSize = encodeVarUInt32(1 + encodeVarUInt32(codeBody.length).length + codeBody.length);
let bytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
// Type section
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7F, // Type: () -> i32
// Function section
0x03, 0x02, 0x01, 0x00,
// Export section
0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00,
// Code section
0x0A, ...codeSectionSize, 0x01, ...encodeVarUInt32(codeBody.length), ...codeBody
]);
let module = new WebAssembly.Module(bytes);
let instance = new WebAssembly.Instance(module);
assert.eq(instance.exports.f(), 1); // Should return 1 (not null)
}
// Test any.convert_extern with 2-byte redundant encoding
{
let extendedOp1 = [0x1C]; // ref.i31 normal
let extendedOp2 = [0x1B]; // extern.convert_any normal
let extendedOp3 = createRedundantLEB128(0x1A, 2); // any.convert_extern redundant
let codeBody = [
0x00, // 0 locals
0x41, 0x2A, // i32.const 42
0xFB, ...extendedOp1, // ref.i31
0xFB, ...extendedOp2, // extern.convert_any
0xFB, ...extendedOp3, // any.convert_extern with redundant encoding
0xD1, // ref.is_null
0x45, // i32.eqz (invert: return 1 if not null, 0 if null)
0x0B // end
];
let codeSectionSize = encodeVarUInt32(1 + encodeVarUInt32(codeBody.length).length + codeBody.length);
let bytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
// Type section
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7F, // Type: () -> i32
// Function section
0x03, 0x02, 0x01, 0x00,
// Export section
0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00,
// Code section
0x0A, ...codeSectionSize, 0x01, ...encodeVarUInt32(codeBody.length), ...codeBody
]);
let module = new WebAssembly.Module(bytes);
let instance = new WebAssembly.Instance(module);
assert.eq(instance.exports.f(), 1); // Should return 1 (not null)
}
}
// Run tests
testArrayLenRedundantEncoding();
testArrayFillRedundantEncoding();
testArrayCopyRedundantEncoding();
testExternAnyConvertRedundantEncoding();