v8/test/mjsunit/wasm/streaming-error-position.js
Clemens Backes 2ceed1a59e [wasm] Revalidate module after streaming error
This refactors how we generate any decoding errors during streaming
compilation: Instead of generating an error message, we only remember
that decoding failed. After all bytes have been received, we then
synchronously re-validate the bytes. This ensures consistent error
messages between all decoding and compilation pipelines.

In order to achieve this, we now unconditionally store the full wire
bytes in the {StreamingDecoder}. This partially overlaps with the
section buffers that we already store, but we cannot continue filling
section buffers after a decoder error. This will be cleaned up in a
follow-up CL.

We can also remove most of the buffer-offset tracking, which will also
be done in a follow-up.

R=ahaas@chromium.org

Bug: v8:13447
Change-Id: I1d506356de6a0070c3bf2b26470dbf781f4f62e3
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4066922
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84636}
2022-12-05 10:46:31 +00:00

432 lines
14 KiB
JavaScript

// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --wasm-test-streaming --expose-wasm
'use strict';
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
function testErrorPositionAsyncOnly(bytes, pos, message) {
let buffer = bytes.trunc_buffer();
// Only test the streaming decoder since this kind of error is out of sync
// with the non-streaming decoder, hence errors cannot be compared.
assertThrowsAsync(
WebAssembly.compile(buffer), WebAssembly.CompileError,
new RegExp(message + '.*@\\+' + pos));
}
function testErrorPosition(bytes, pos, message) {
let buffer = bytes.trunc_buffer();
// First check the non-streaming decoder as a reference.
assertThrows(
() => new WebAssembly.Module(buffer), WebAssembly.CompileError,
new RegExp(message + '.*@\\+' + pos));
// Next test the actual streaming decoder.
assertThrowsAsync(
WebAssembly.compile(buffer), WebAssembly.CompileError,
new RegExp(message + '.*@\\+' + pos));
}
(function testInvalidMagic() {
let bytes = new Binary;
bytes.emit_bytes([
kWasmH0, kWasmH1 + 1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3
]);
// Error at pos==0 because that's where the magic word is.
testErrorPosition(bytes, 0, 'expected magic word');
})();
(function testInvalidVersion() {
let bytes = new Binary;
bytes.emit_bytes([
kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1 + 1, kWasmV2, kWasmV3
]);
// Error at pos==4 because that's where the version word is.
testErrorPosition(bytes, 4, 'expected version');
})();
(function testSectionLengthInvalidVarint() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_u8(kTypeSectionCode);
bytes.emit_bytes([0x80, 0x80, 0x80, 0x80, 0x80, 0x00]);
let pos = bytes.length - 1 - 1;
testErrorPosition(bytes, pos, 'expected section length');
})();
(function testSectionLengthTooBig() {
let bytes = new Binary;
bytes.emit_header();
let pos = bytes.length;
bytes.emit_u8(kTypeSectionCode);
bytes.emit_u32v(0xffffff23);
testErrorPositionAsyncOnly(
bytes, pos,
'section \\(code 1, "Type"\\) extends past end of the module ' +
'\\(length 4294967075, remaining bytes 0\\)');
})();
(function testFunctionsCountInvalidVarint() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_bytes([
kTypeSectionCode, // section id
1, // section length
0 // number of types
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
1, // section length
0 // number of functions
]);
let pos = bytes.length;
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
]);
// Functions count
bytes.emit_bytes([0x80, 0x80, 0x80, 0x80, 0x80, 0x00]);
testErrorPositionAsyncOnly(
bytes, pos,
'section \\(code 10, "Code"\\) extends past end of the module ' +
'\\(length 20, remaining bytes 6\\)');
})();
(function testFunctionsCountTooBig() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_bytes([
kTypeSectionCode, // section id
1, // section length
0 // number of types
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
1, // section length
0 // number of functions
]);
let pos = bytes.length;
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
]);
// Functions count
bytes.emit_u32v(0xffffff23);
testErrorPositionAsyncOnly(
bytes, pos,
'section \\(code 10, "Code"\\) extends past end of the module ' +
'\\(length 20, remaining bytes 5\\)');
})();
(function testFunctionsCountDoesNotMatch() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_bytes([
kTypeSectionCode, // section id
1, // section length
0 // number of types
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
1, // section length
0 // number of functions
]);
let pos = bytes.length;
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
]);
// Functions count (different than the count in the functions section.
bytes.emit_u32v(5);
testErrorPositionAsyncOnly(
bytes, pos,
'section \\(code 10, "Code"\\) extends past end of the module ' +
'\\(length 20, remaining bytes 1\\)');
})();
(function testBodySizeInvalidVarint() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
]);
let pos = bytes.length;
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
1 // functions count
]);
// Invalid function body size.
bytes.emit_bytes([0x80, 0x80, 0x80, 0x80, 0x80, 0x00]);
testErrorPositionAsyncOnly(
bytes, pos,
'section \\(code 10, "Code"\\) extends past end of the module ' +
'\\(length 20, remaining bytes 7\\)');
})();
(function testBodySizeTooBig() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
]);
let pos = bytes.length;
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
1 // functions count
]);
// Invalid function body size.
bytes.emit_u32v(0xffffff23);
testErrorPositionAsyncOnly(
bytes, pos,
'section \\(code 10, "Code"\\) extends past end of the module ' +
'\\(length 20, remaining bytes 6\\)');
})();
(function testBodySizeDoesNotFit() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
]);
let pos = bytes.length;
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
1 // functions count
]);
// Invalid function body size (does not fit into the code section).
bytes.emit_u32v(20);
testErrorPositionAsyncOnly(
bytes, pos,
'section \\(code 10, "Code"\\) extends past end of the module ' +
'\\(length 20, remaining bytes 2\\)');
})();
(function testBodySizeIsZero() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
]);
let pos = bytes.length;
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
1 // functions count
]);
// Invalid function body size (body size of 0 is invalid).
bytes.emit_u32v(0);
testErrorPositionAsyncOnly(
bytes, pos,
'section \\(code 10, "Code"\\) extends past end of the module ' +
'\\(length 20, remaining bytes 2\\)');
})();
(function testStaleCodeSectionBytes() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
10, // section length (too big)
1, // functions count
2, // body size
0, // locals count
kExprEnd // body
]);
let pos = bytes.length;
// Add some more bytes to avoid early error detection for too big section
// length.
bytes.emit_bytes([0, 0, 0, 0, 0, 0, 0, 0]);
testErrorPositionAsyncOnly(
bytes, pos,
'section was shorter than expected size ' +
'\\(10 bytes expected, 4 decoded\\)');
})();
(function testInvalidCode() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
6, // section length
1, // functions count
4, // body size
0, // locals count
kExprLocalGet, 0, // Access a non-existing local
kExprEnd // --
]);
// Find error at the index of kExprLocalGet.
let pos = bytes.length - 1 - 1;
testErrorPosition(bytes, pos, 'invalid local index');
})();
(function testCodeSectionRepeats() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
4, // section length
1, // functions count
2, // body size
0, // locals count
kExprEnd // body
]);
// TODO(clemensb): Fix error reporting to point to the section start, not the
// payload start.
let pos = bytes.length + 2;
bytes.emit_bytes([
kCodeSectionCode, // section id (repeating)
4, // section length
1, // functions count
2, // body size
0, // locals count
kExprEnd // body
]);
// Find error at the second kCodeSectionCode.
testErrorPositionAsyncOnly(bytes, pos, 'unexpected section <Code>');
})();
(function testCodeSectionSizeZero() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
0, // section length (empty)
]);
// Find error at the code section length.
let pos = bytes.length;
testErrorPositionAsyncOnly(bytes, pos, 'expected functions count');
})();
(function testInvalidSection() {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_bytes([
kTypeSectionCode, // section id
5, // section length
1, // number of types
kWasmFunctionTypeForm, // type
1, // number of parameter
kWasmVoid, // invalid type
0 // number of returns
]);
let pos = bytes.length - 1 - 1;
testErrorPositionAsyncOnly(bytes, pos, 'invalid value type');
})();