[wasm][reference-types] Implement declarative segments

Implement the latest spec changes:
  - Allow declarative segments to behave like passive & dropped segments.
  - Enforce that only declared functions may be returned or used in globals
    as funcref.
  - Ensure that table fill does not modify any entries if OOB.

Spec tests for select and br_table are still failing due to proposal issue

Bug: v8:10156

R=ahaas@chromium.org

Change-Id: I5b95be36a67bc7482a84b848908cc4cbdf94af03
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2027458
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Commit-Queue: Emanuel Ziegler <ecmziegler@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66297}
This commit is contained in:
Emanuel Ziegler 2020-02-17 17:21:02 +01:00 committed by Commit Bot
parent 94cdc18ad7
commit de17316ad2
18 changed files with 369 additions and 63 deletions

View File

@ -584,11 +584,11 @@ RUNTIME_FUNCTION(Runtime_WasmTableFill) {
// Even when table.fill goes out-of-bounds, as many entries as possible are
// put into the table. Only afterwards we trap.
uint32_t fill_count = std::min(count, table_size - start);
WasmTableObject::Fill(isolate, table, start, value, fill_count);
if (fill_count < count) {
return ThrowTableOutOfBounds(isolate, instance);
}
WasmTableObject::Fill(isolate, table, start, value, fill_count);
return ReadOnlyRoots(isolate).undefined_value();
}

View File

@ -1138,6 +1138,11 @@ class WasmDecoder : public Decoder {
errorf(pc, "invalid function index: %u", imm.index);
return false;
}
if (!VALIDATE(module_ != nullptr &&
module_->functions[imm.index].declared)) {
this->errorf(pc, "undeclared reference to function #%u", imm.index);
return false;
}
return true;
}

View File

@ -367,6 +367,7 @@ class ModuleDecoderImpl : public Decoder {
void DecodeSection(SectionCode section_code, Vector<const uint8_t> bytes,
uint32_t offset, bool verify_functions = true) {
VerifyFunctionDeclarations(section_code);
if (failed()) return;
Reset(bytes, offset);
TRACE("Section: %s\n", SectionName(section_code));
@ -548,7 +549,8 @@ class ModuleDecoderImpl : public Decoder {
0, // sig_index
{0, 0}, // code
true, // imported
false}); // exported
false, // exported
false}); // declared
WasmFunction* function = &module_->functions.back();
function->sig_index =
consume_sig_index(module_.get(), &function->sig);
@ -638,7 +640,8 @@ class ModuleDecoderImpl : public Decoder {
0, // sig_index
{0, 0}, // code
false, // imported
false}); // exported
false, // exported
false}); // declared
WasmFunction* function = &module_->functions.back();
function->sig_index = consume_sig_index(module_.get(), &function->sig);
if (!ok()) return;
@ -806,21 +809,18 @@ class ModuleDecoderImpl : public Decoder {
uint32_t element_count =
consume_count("element count", FLAG_wasm_max_table_size);
if (element_count > 0 && module_->tables.size() == 0) {
error(pc_, "The element section requires a table");
}
for (uint32_t i = 0; ok() && i < element_count; ++i) {
const byte* pos = pc();
bool is_active;
WasmElemSegment::Status status;
bool functions_as_elements;
uint32_t table_index;
WasmInitExpr offset;
consume_element_segment_header(&is_active, &functions_as_elements,
consume_element_segment_header(&status, &functions_as_elements,
&table_index, &offset);
if (failed()) return;
if (is_active) {
if (status == WasmElemSegment::kStatusActive) {
if (table_index >= module_->tables.size()) {
errorf(pos, "out of bounds table index %u", table_index);
break;
@ -836,10 +836,11 @@ class ModuleDecoderImpl : public Decoder {
uint32_t num_elem =
consume_count("number of elements", max_table_init_entries());
if (is_active) {
if (status == WasmElemSegment::kStatusActive) {
module_->elem_segments.emplace_back(table_index, offset);
} else {
module_->elem_segments.emplace_back();
module_->elem_segments.emplace_back(
status == WasmElemSegment::kStatusDeclarative);
}
WasmElemSegment* init = &module_->elem_segments.back();
@ -1116,7 +1117,41 @@ class ModuleDecoderImpl : public Decoder {
return true;
}
void VerifyFunctionDeclarations(SectionCode section_code) {
// Since we will only know if a function was properly declared after all the
// element sections have been parsed, but we need to verify the proper use
// within global initialization, we are deferring those checks.
if (deferred_funcref_error_offsets_.empty()) {
// No verifications to do be done.
return;
}
if (!ok()) {
// Previous errors exist.
return;
}
// TODO(ecmziegler): Adjust logic if module order changes (e.g. event
// section).
if (section_code <= kElementSectionCode &&
section_code != kUnknownSectionCode) {
// Before the element section and not at end of decoding.
return;
}
for (auto& func_offset : deferred_funcref_error_offsets_) {
DCHECK_LT(func_offset.first, module_->functions.size());
if (!module_->functions[func_offset.first].declared) {
errorf(func_offset.second, "undeclared reference to function #%u",
func_offset.first);
break;
}
}
deferred_funcref_error_offsets_.clear();
}
ModuleResult FinishDecoding(bool verify_functions = true) {
// Ensure that function verifications were done even if no section followed
// the global section.
VerifyFunctionDeclarations(kUnknownSectionCode);
if (ok() && CheckMismatchedCounts()) {
CalculateGlobalOffsets(module_.get());
}
@ -1231,6 +1266,10 @@ class ModuleDecoderImpl : public Decoder {
kLastKnownModuleSection,
"not enough bits");
WasmError intermediate_error_;
// Map from function index to wire byte offset of first funcref initialization
// in global section. Used for deferred checking and proper error reporting if
// these were not properly declared in the element section.
std::unordered_map<uint32_t, int> deferred_funcref_error_offsets_;
ModuleOrigin origin_;
bool has_seen_unordered_section(SectionCode section_code) {
@ -1566,6 +1605,8 @@ class ModuleDecoderImpl : public Decoder {
errorf(pc() - 1, "invalid function index: %u", imm.index);
break;
}
// Defer check for declaration of function reference.
deferred_funcref_error_offsets_.emplace(imm.index, pc_offset());
expr.kind = WasmInitExpr::kRefFuncConst;
expr.val.function_index = imm.index;
len = imm.length;
@ -1716,7 +1757,7 @@ class ModuleDecoderImpl : public Decoder {
return attribute;
}
void consume_element_segment_header(bool* is_active,
void consume_element_segment_header(WasmElemSegment::Status* status,
bool* functions_as_elements,
uint32_t* table_index,
WasmInitExpr* offset) {
@ -1749,11 +1790,31 @@ class ModuleDecoderImpl : public Decoder {
kIsPassiveMask | kHasTableIndexMask | kFunctionsAsElementsMask;
bool is_passive = flag & kIsPassiveMask;
*is_active = !is_passive;
if (!is_passive) {
*status = WasmElemSegment::kStatusActive;
if (module_->tables.size() == 0) {
error(pc_, "Active element sections require a table");
}
} else if ((flag & kHasTableIndexMask)) { // Special bit combination for
// declarative segments.
*status = WasmElemSegment::kStatusDeclarative;
} else {
*status = WasmElemSegment::kStatusPassive;
if (module_->tables.size() == 0) {
error(pc_, "Passive element sections require a table");
}
}
*functions_as_elements = flag & kFunctionsAsElementsMask;
bool has_table_index = flag & kHasTableIndexMask;
bool has_table_index = (flag & kHasTableIndexMask) &&
*status == WasmElemSegment::kStatusActive;
if (is_passive && !enabled_features_.has_bulk_memory()) {
if (*status == WasmElemSegment::kStatusDeclarative &&
!enabled_features_.has_anyref()) {
error("Declarative element segments require --experimental-wasm-anyref");
return;
}
if (*status == WasmElemSegment::kStatusPassive &&
!enabled_features_.has_bulk_memory()) {
error("Passive element segments require --experimental-wasm-bulk-memory");
return;
}
@ -1770,8 +1831,8 @@ class ModuleDecoderImpl : public Decoder {
"--experimental-wasm-bulk-memory or --experimental-wasm-anyref?");
return;
}
if ((flag & kFullMask) != flag || (!(*is_active) && has_table_index)) {
errorf(pos, "illegal flag value %u. Must be 0, 1, 2, 4, 5 or 6", flag);
if ((flag & kFullMask) != flag) {
errorf(pos, "illegal flag value %u. Must be between 0 and 7", flag);
}
if (has_table_index) {
@ -1780,11 +1841,11 @@ class ModuleDecoderImpl : public Decoder {
*table_index = 0;
}
if (*is_active) {
if (*status == WasmElemSegment::kStatusActive) {
*offset = consume_init_expr(module_.get(), kWasmI32);
}
if (*is_active && !has_table_index) {
if (*status == WasmElemSegment::kStatusActive && !has_table_index) {
// Active segments without table indices are a special case for backwards
// compatibility. These cases have an implicit element kind or element
// type, so we are done already with the segment header.
@ -1859,6 +1920,7 @@ class ModuleDecoderImpl : public Decoder {
uint32_t index =
consume_func_index(module_.get(), &func, "element function index");
if (failed()) return index;
func->declared = true;
DCHECK_NE(func, nullptr);
DCHECK_EQ(index, func->func_index);
DCHECK_NE(index, WasmElemSegment::kNullIndex);

View File

@ -482,7 +482,7 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
// Check that indirect function table segments are within bounds.
//--------------------------------------------------------------------------
for (const WasmElemSegment& elem_segment : module_->elem_segments) {
if (!elem_segment.active) continue;
if (elem_segment.status != WasmElemSegment::kStatusActive) continue;
DCHECK_LT(elem_segment.table_index, table_count);
uint32_t base = EvalUint32InitExpr(instance, elem_segment.offset);
// Because of imported tables, {table_size} has to come from the table
@ -1685,7 +1685,7 @@ void InstanceBuilder::LoadTableSegments(Handle<WasmInstanceObject> instance) {
segment_index < module_->elem_segments.size(); ++segment_index) {
auto& elem_segment = instance->module()->elem_segments[segment_index];
// Passive segments are not copied during instantiation.
if (!elem_segment.active) continue;
if (elem_segment.status != WasmElemSegment::kStatusActive) continue;
uint32_t table_index = elem_segment.table_index;
uint32_t dst = EvalUint32InitExpr(instance, elem_segment.offset);

View File

@ -1951,12 +1951,12 @@ class ThreadImpl {
// Even when table.fill goes out-of-bounds, as many entries as possible
// are put into the table. Only afterwards we trap.
uint32_t fill_count = std::min(count, table_size - start);
WasmTableObject::Fill(isolate_, table, start, value, fill_count);
if (fill_count < count) {
DoTrap(kTrapTableOutOfBounds, pc);
return false;
}
WasmTableObject::Fill(isolate_, table, start, value, fill_count);
*len += imm.length;
return true;
}
@ -4319,7 +4319,13 @@ ControlTransferMap WasmInterpreter::ComputeControlTransfersForTesting(
// Create some dummy structures, to avoid special-casing the implementation
// just for testing.
FunctionSig sig(0, 0, nullptr);
WasmFunction function{&sig, 0, 0, {0, 0}, false, false};
WasmFunction function{&sig, // sig
0, // func_index
0, // sig_index
{0, 0}, // code
false, // imported
false, // exported
false}; // declared
InterpreterCode code{
&function, BodyLocalDecls(zone), start, end, nullptr, nullptr, nullptr};

View File

@ -57,6 +57,7 @@ struct WasmFunction {
WireBytesRef code; // code of this function.
bool imported;
bool exported;
bool declared;
};
// Static representation of a wasm global variable.
@ -115,10 +116,13 @@ struct WasmElemSegment {
// Construct an active segment.
WasmElemSegment(uint32_t table_index, WasmInitExpr offset)
: table_index(table_index), offset(offset), active(true) {}
: table_index(table_index), offset(offset), status(kStatusActive) {}
// Construct a passive segment, which has no table index or offset.
WasmElemSegment() : table_index(0), active(false) {}
// Construct a passive or declarative segment, which has no table index or
// offset.
explicit WasmElemSegment(bool declarative)
: table_index(0),
status(declarative ? kStatusDeclarative : kStatusPassive) {}
// Used in the {entries} vector to represent a `ref.null` entry in a passive
// segment.
@ -127,7 +131,11 @@ struct WasmElemSegment {
uint32_t table_index;
WasmInitExpr offset;
std::vector<uint32_t> entries;
bool active; // true if copied automatically during instantiation.
enum Status {
kStatusActive, // copied automatically during instantiation.
kStatusPassive, // copied explicitly after instantiation.
kStatusDeclarative // purely declarative and never copied.
} status;
};
// Static representation of a wasm import.

View File

@ -1286,7 +1286,11 @@ void WasmInstanceObject::InitElemSegmentArrays(
auto module = module_object->module();
auto num_elem_segments = module->elem_segments.size();
for (size_t i = 0; i < num_elem_segments; ++i) {
instance->dropped_elem_segments()[i] = 0;
instance->dropped_elem_segments()[i] =
module->elem_segments[i].status ==
wasm::WasmElemSegment::kStatusDeclarative
? 1
: 0;
}
}

View File

@ -113,7 +113,13 @@ uint32_t TestingModuleBuilder::AddFunction(FunctionSig* sig, const char* name,
test_module_->functions.reserve(kMaxFunctions);
}
uint32_t index = static_cast<uint32_t>(test_module_->functions.size());
test_module_->functions.push_back({sig, index, 0, {0, 0}, false, false});
test_module_->functions.push_back({sig, // sig
index, // func_index
0, // sig_index
{0, 0}, // code
false, // imported
false, // exported
false}); // declared
if (type == kImport) {
DCHECK_EQ(0, test_module_->num_declared_functions);
++test_module_->num_imported_functions;
@ -289,7 +295,7 @@ uint32_t TestingModuleBuilder::AddPassiveElementSegment(
uint32_t index = static_cast<uint32_t>(test_module_->elem_segments.size());
DCHECK_EQ(index, dropped_elem_segments_.size());
test_module_->elem_segments.emplace_back();
test_module_->elem_segments.emplace_back(false);
auto& elem_segment = test_module_->elem_segments.back();
elem_segment.entries = entries;

View File

@ -27,8 +27,10 @@
#define ACTIVE_NO_INDEX 0
#define PASSIVE 1
#define ACTIVE_WITH_INDEX 2
#define DECLARATIVE 3
#define PASSIVE_WITH_ELEMENTS 5
#define ACTIVE_WITH_ELEMENTS 6
#define DECLARATIVE_WITH_ELEMENTS 7
// The table index field in an element segment was repurposed as a flags field.
// To specify a table index, we have to set the flag value to 2, followed by

View File

@ -222,6 +222,7 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
const function_index = builder.addFunction('hidden', kSig_i_v)
.addBody([kExprI32Const, expected])
.index;
builder.addDeclarativeElementSegment([function_index]);
builder.addFunction('main', kSig_a_v)
.addBody([kExprRefFunc, function_index])
.exportFunc();
@ -237,6 +238,7 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
const foo = builder.addFunction('foo', kSig_i_v)
.addBody([kExprI32Const, expected])
.exportFunc();
builder.addDeclarativeElementSegment([foo.index]);
builder.addFunction('main', kSig_a_v)
.addBody([kExprRefFunc, foo.index])
.exportFunc();

View File

@ -568,7 +568,7 @@ function dummy_func() {
const f_func = builder.addFunction('get_anyfunc_global', kSig_a_v)
.addBody([kExprGlobalGet, g_func.index])
.exportAs('get_anyfunc_global');
builder.addDeclarativeElementSegment([f_ref.index, f_func.index]);
g_ref.function_index = f_ref.index;
g_func.function_index = f_func.index;
@ -580,6 +580,18 @@ function dummy_func() {
instance.exports.get_anyfunc_global());
})();
(function TestRefFuncGlobalInitUndeclared() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
const global_func = builder.addGlobal(kWasmAnyFunc, true);
const func = builder.addFunction('get_anyfunc_global', kSig_v_v).addBody([]);
global_func.function_index = func.index;
assertThrows(
() => builder.toModule(), WebAssembly.CompileError,
/undeclared reference to function/);
})();
(function TestRefFuncGlobalInitWithImport() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
@ -588,6 +600,7 @@ function dummy_func() {
const import_js = builder.addImport('m', 'js', sig_index);
const g_wasm = builder.addGlobal(kWasmAnyFunc, true);
const g_js = builder.addGlobal(kWasmAnyFunc, true);
builder.addDeclarativeElementSegment([import_wasm, import_js]);
g_wasm.function_index = import_wasm;
g_js.function_index = import_js;
builder.addFunction('get_global_wasm', kSig_a_v)

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-anyref
// Flags: --experimental-wasm-anyref --experimental-wasm-bulk-memory
load("test/mjsunit/wasm/wasm-module-builder.js");
@ -45,3 +45,35 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
assertThrows(() => builder.instantiate({ imp: { table: table_func } }),
WebAssembly.LinkError, /imported table does not match the expected type/);
})();
(function TestAnyRefDropDeclarativeElementSegment() {
print(arguments.callee.name);
const builder = new WasmModuleBuilder();
builder.addDeclarativeElementSegment([null]);
builder.addFunction('drop', kSig_v_v)
.addBody([kNumericPrefix, kExprElemDrop, 0])
.exportFunc();
const instance = builder.instantiate();
// Counts as double-drop because declarative segments are dropped on
// initialization and is therefore not expected to throw.
instance.exports.drop();
})();
(function TestAnyRefTableInitFromDeclarativeElementSegment() {
print(arguments.callee.name);
const builder = new WasmModuleBuilder();
const table = builder.addTable(kWasmAnyFunc, 10);
builder.addDeclarativeElementSegment([null]);
builder.addFunction('init', kSig_v_v)
.addBody([
kExprI32Const, 0, kExprI32Const, 0, kExprI32Const, 1, kNumericPrefix,
kExprTableInit, table.index, 0
])
.exportFunc();
const instance = builder.instantiate();
assertTraps(kTrapTableOutOfBounds, () => instance.exports.init());
})();

View File

@ -138,6 +138,7 @@ const dummy_func = exports.set_table_func1;
const function_index = builder.addFunction('hidden', sig_index)
.addBody([kExprI32Const, expected])
.index;
builder.addDeclarativeElementSegment([function_index]);
builder.addFunction('main', kSig_i_v)
.addBody([

View File

@ -93,8 +93,7 @@ function checkAnyRefTable(getter, start, count, value) {
(function testAnyRefTableFillOOB() {
print(arguments.callee.name);
// Fill table out-of-bounds, check if the table got filled as much as
// possible.
// Fill table out-of-bounds, check if the table wasn't altered.
let start = 7;
let value = {foo: 27};
// {maximum + 4} elements definitely don't fit into the table.
@ -103,14 +102,14 @@ function checkAnyRefTable(getter, start, count, value) {
kTrapTableOutOfBounds,
() => instance.exports[`fill${import_ref}`](start, value, count));
checkAnyRefTable(
instance.exports[`get${import_ref}`], start, size - start, value);
instance.exports[`get${import_ref}`], start, size - start, null);
value = 45;
assertTraps(
kTrapTableOutOfBounds,
() => instance.exports[`fill${internal_ref}`](start, value, count));
checkAnyRefTable(
instance.exports[`get${internal_ref}`], start, size - start, value);
instance.exports[`get${internal_ref}`], start, size - start, null);
})();
(function testAnyRefTableFillOOBCountZero() {
@ -160,8 +159,7 @@ function checkAnyFuncTable(call, start, count, value) {
(function testAnyFuncTableFillOOB() {
print(arguments.callee.name);
// Fill table out-of-bounds, check if the table got filled as much as
// possible.
// Fill table out-of-bounds, check if the table wasn't altered.
let start = 7;
let value = 38;
// {maximum + 4} elements definitely don't fit into the table.
@ -171,7 +169,7 @@ function checkAnyFuncTable(call, start, count, value) {
() => instance.exports[`fill${import_func}`](
start, dummy_func(value), count));
checkAnyFuncTable(
instance.exports[`call${import_func}`], start, size - start, value);
instance.exports[`call${import_func}`], start, size - start, null);
value = 46;
assertTraps(
@ -179,7 +177,7 @@ function checkAnyFuncTable(call, start, count, value) {
() => instance.exports[`fill${internal_func}`](
start, dummy_func(value), count));
checkAnyFuncTable(
instance.exports[`call${internal_func}`], start, size - start, value);
instance.exports[`call${internal_func}`], start, size - start, null);
})();
(function testAnyFuncTableFillOOBCountZero() {

View File

@ -82,7 +82,9 @@ let kSharedHasMaximumFlag = 3;
let kActiveNoIndex = 0;
let kPassive = 1;
let kActiveWithIndex = 2;
let kDeclarative = 3;
let kPassiveWithElements = 5;
let kDeclarativeWithElements = 7;
// Function declaration flags
let kDeclFunctionName = 0x01;
@ -906,13 +908,26 @@ class WasmModuleBuilder {
}
addElementSegment(table, base, is_global, array) {
this.element_segments.push({table: table, base: base, is_global: is_global,
array: array, is_active: true});
this.element_segments.push({
table: table,
base: base,
is_global: is_global,
array: array,
is_active: true,
is_declarative: false
});
return this;
}
addPassiveElementSegment(array, is_import = false) {
this.element_segments.push({array: array, is_active: false});
this.element_segments.push(
{array: array, is_active: false, is_declarative: false});
return this;
}
addDeclarativeElementSegment(array, is_import = false) {
this.element_segments.push(
{array: array, is_active: false, is_declarative: true});
return this;
}
@ -1180,9 +1195,20 @@ class WasmModuleBuilder {
for (let index of init.array) {
section.emit_u32v(index);
}
} else if (
init.is_declarative &&
init.array.every(index => index !== null)) {
section.emit_u8(kDeclarative);
section.emit_u8(kExternalFunction);
section.emit_u32v(init.array.length);
for (let index of init.array) {
section.emit_u32v(index);
}
} else {
// Passive segment.
section.emit_u8(kPassiveWithElements); // flags
// Passive or declarative segment with elements.
section.emit_u8(
init.is_declarative ? kDeclarativeWithElements :
kPassiveWithElements); // flags
section.emit_u8(kWasmAnyFunc);
section.emit_u32v(init.array.length);
for (let index of init.array) {

View File

@ -220,13 +220,14 @@ class TestModuleBuilder {
CHECK_LE(mod.signatures.size(), kMaxByteSizedLeb128);
return static_cast<byte>(mod.signatures.size() - 1);
}
byte AddFunction(FunctionSig* sig) {
mod.functions.push_back({sig, // sig
0, // func_index
0, // sig_index
{0, 0}, // code
false, // import
false}); // export
byte AddFunction(FunctionSig* sig, bool declared = true) {
mod.functions.push_back({sig, // sig
0, // func_index
0, // sig_index
{0, 0}, // code
false, // import
false, // export
declared}); // declared
CHECK_LE(mod.functions.size(), kMaxByteSizedLeb128);
return static_cast<byte>(mod.functions.size() - 1);
}
@ -262,7 +263,7 @@ class TestModuleBuilder {
void InitializeTable() { mod.tables.emplace_back(); }
byte AddPassiveElementSegment() {
mod.elem_segments.emplace_back();
mod.elem_segments.emplace_back(false);
auto& init = mod.elem_segments.back();
// Add 5 empty elements.
for (uint32_t j = 0; j < 5; j++) {
@ -271,6 +272,12 @@ class TestModuleBuilder {
return static_cast<byte>(mod.elem_segments.size() - 1);
}
byte AddDeclarativeElementSegment() {
mod.elem_segments.emplace_back(true);
mod.elem_segments.back().entries.push_back(WasmElemSegment::kNullIndex);
return static_cast<byte>(mod.elem_segments.size() - 1);
}
// Set the number of data segments as declared by the DataCount section.
void SetDataSegmentCount(uint32_t data_segment_count) {
// The Data section occurs after the Code section, so we don't need to
@ -3242,6 +3249,57 @@ TEST_F(FunctionBodyDecoderTest, ElemDrop) {
ExpectFailure(sigs.v_v(), {WASM_ELEM_DROP(1)});
}
TEST_F(FunctionBodyDecoderTest, TableInitDeclarativeElem) {
TestModuleBuilder builder;
builder.InitializeTable();
builder.AddDeclarativeElementSegment();
module = builder.module();
WASM_FEATURE_SCOPE(bulk_memory);
WASM_FEATURE_SCOPE(anyref);
byte code[] = {WASM_TABLE_INIT(0, 0, WASM_ZERO, WASM_ZERO, WASM_ZERO),
WASM_END};
for (size_t i = 0; i <= arraysize(code); ++i) {
Validate(i == arraysize(code), sigs.v_v(), VectorOf(code, i), kOmitEnd);
}
}
TEST_F(FunctionBodyDecoderTest, DeclarativeElemDrop) {
TestModuleBuilder builder;
builder.InitializeTable();
builder.AddDeclarativeElementSegment();
module = builder.module();
ExpectFailure(sigs.v_v(), {WASM_ELEM_DROP(0)});
WASM_FEATURE_SCOPE(bulk_memory);
WASM_FEATURE_SCOPE(anyref);
ExpectValidates(sigs.v_v(), {WASM_ELEM_DROP(0)});
ExpectFailure(sigs.v_v(), {WASM_ELEM_DROP(1)});
}
TEST_F(FunctionBodyDecoderTest, RefFuncDeclared) {
TestModuleBuilder builder;
builder.InitializeTable();
byte function_index = builder.AddFunction(sigs.v_i());
module = builder.module();
ExpectFailure(sigs.a_v(), {WASM_REF_FUNC(function_index)});
WASM_FEATURE_SCOPE(bulk_memory);
WASM_FEATURE_SCOPE(anyref);
ExpectValidates(sigs.a_v(), {WASM_REF_FUNC(function_index)});
}
TEST_F(FunctionBodyDecoderTest, RefFuncUndeclared) {
TestModuleBuilder builder;
builder.InitializeTable();
byte function_index = builder.AddFunction(sigs.v_i(), false);
module = builder.module();
WASM_FEATURE_SCOPE(bulk_memory);
WASM_FEATURE_SCOPE(anyref);
ExpectFailure(sigs.a_v(), {WASM_REF_FUNC(function_index)});
}
TEST_F(FunctionBodyDecoderTest, ElemSegmentIndexUnsigned) {
TestModuleBuilder builder;
builder.InitializeTable();

View File

@ -130,6 +130,13 @@ struct CheckLEB1 : std::integral_constant<size_t, num> {
#define EXPECT_FAILURE(data) EXPECT_FAILURE_LEN(data, sizeof(data))
#define EXPECT_FAILURE_WITH_MSG(data, msg) \
do { \
ModuleResult result = DecodeModule(data, data + sizeof(data)); \
EXPECT_FALSE(result.ok()); \
EXPECT_THAT(result.error().message(), HasSubstr(msg)); \
} while (false)
#define EXPECT_OFF_END_FAILURE(data, min) \
do { \
STATIC_ASSERT(min < arraysize(data)); \
@ -252,6 +259,7 @@ TEST_F(WasmModuleVerifyTest, OneGlobal) {
TEST_F(WasmModuleVerifyTest, AnyRefGlobal) {
WASM_FEATURE_SCOPE(anyref);
WASM_FEATURE_SCOPE(bulk_memory);
static const byte data[] = {
// sig#0 ---------------------------------------------------------------
SIGNATURES_SECTION_VOID_VOID,
@ -265,6 +273,16 @@ TEST_F(WasmModuleVerifyTest, AnyRefGlobal) {
kLocalAnyRef, // local type
0, // immutable
WASM_INIT_EXPR_REF_FUNC(1)), // init
SECTION(Element, // section name
ENTRY_COUNT(2), // entry count
DECLARATIVE, // flags 0
kExternalFunction, // type
ENTRY_COUNT(1), // func entry count
FUNC_INDEX(0), // func index
DECLARATIVE_WITH_ELEMENTS, // flags 1
kLocalFuncRef, // local type
ENTRY_COUNT(1), // func ref count
REF_FUNC_ELEMENT(1)), // func ref
TWO_EMPTY_BODIES};
{
@ -290,6 +308,7 @@ TEST_F(WasmModuleVerifyTest, AnyRefGlobal) {
TEST_F(WasmModuleVerifyTest, FuncRefGlobal) {
WASM_FEATURE_SCOPE(anyref);
WASM_FEATURE_SCOPE(bulk_memory);
static const byte data[] = {
// sig#0 ---------------------------------------------------------------
SIGNATURES_SECTION_VOID_VOID,
@ -303,6 +322,16 @@ TEST_F(WasmModuleVerifyTest, FuncRefGlobal) {
kLocalFuncRef, // local type
0, // immutable
WASM_INIT_EXPR_REF_FUNC(1)), // init
SECTION(Element, // section name
ENTRY_COUNT(2), // entry count
DECLARATIVE, // flags 0
kExternalFunction, // type
ENTRY_COUNT(1), // func entry count
FUNC_INDEX(0), // func index
DECLARATIVE_WITH_ELEMENTS, // flags 1
kLocalFuncRef, // local type
ENTRY_COUNT(1), // func ref count
REF_FUNC_ELEMENT(1)), // func ref
TWO_EMPTY_BODIES};
{
// Should decode to two globals.
@ -2507,7 +2536,7 @@ TEST_F(WasmModuleVerifyTest, PassiveElementSegmentWithIndices) {
ONE_EMPTY_FUNCTION(SIG_INDEX(0)),
// table declaration -----------------------------------------------------
SECTION(Table, ENTRY_COUNT(1), kLocalFuncRef, 0, 1),
// element segments -----------------------------------------------------
// element segments ------------------------------------------------------
SECTION(Element, ENTRY_COUNT(1), PASSIVE, kExternalFunction,
ENTRY_COUNT(3), U32V_1(0), U32V_1(0), U32V_1(0)),
// code ------------------------------------------------------------------
@ -2518,6 +2547,67 @@ TEST_F(WasmModuleVerifyTest, PassiveElementSegmentWithIndices) {
EXPECT_OFF_END_FAILURE(data, arraysize(data) - 5);
}
TEST_F(WasmModuleVerifyTest, DeclarativeElementSegmentFuncRef) {
static const byte data[] = {
// sig#0 -----------------------------------------------------------------
SIGNATURES_SECTION_VOID_VOID,
// funcs -----------------------------------------------------------------
ONE_EMPTY_FUNCTION(SIG_INDEX(0)),
// element segments -----------------------------------------------------
SECTION(Element, // section name
ENTRY_COUNT(1), // entry count
DECLARATIVE_WITH_ELEMENTS, // flags
kLocalFuncRef, // local type
U32V_1(0)), // func ref count
// code ------------------------------------------------------------------
ONE_EMPTY_BODY};
EXPECT_FAILURE(data);
WASM_FEATURE_SCOPE(bulk_memory);
EXPECT_FAILURE(data);
WASM_FEATURE_SCOPE(anyref);
EXPECT_VERIFIES(data);
}
TEST_F(WasmModuleVerifyTest, DeclarativeElementSegmentWithInvalidIndex) {
WASM_FEATURE_SCOPE(bulk_memory);
WASM_FEATURE_SCOPE(anyref);
static const byte data[] = {
// sig#0 -----------------------------------------------------------------
SIGNATURES_SECTION_VOID_VOID,
// funcs -----------------------------------------------------------------
ONE_EMPTY_FUNCTION(SIG_INDEX(0)),
// element segments -----------------------------------------------------
SECTION(Element, // section name
ENTRY_COUNT(1), // entry count
DECLARATIVE, // flags
kExternalFunction, // type
ENTRY_COUNT(2), // func index count
U32V_1(0), // func index
U32V_1(1)), // func index
// code ------------------------------------------------------------------
ONE_EMPTY_BODY};
EXPECT_FAILURE_WITH_MSG(data, "element function index 1 out of bounds");
}
TEST_F(WasmModuleVerifyTest, DeclarativeElementSegmentMissingForGlobal) {
WASM_FEATURE_SCOPE(bulk_memory);
WASM_FEATURE_SCOPE(anyref);
static const byte data[] = {
// sig#0 -----------------------------------------------------------------
SIGNATURES_SECTION_VOID_VOID,
// funcs -----------------------------------------------------------------
ONE_EMPTY_FUNCTION(SIG_INDEX(0)),
// global definitions ----------------------------------------------------
SECTION(Global, // section name
ENTRY_COUNT(1), // entry count
kLocalAnyRef, // local type
0, // immutable
WASM_INIT_EXPR_REF_FUNC(0)), // init
// code ------------------------------------------------------------------
ONE_EMPTY_BODY};
EXPECT_FAILURE_WITH_MSG(data, "undeclared reference to function");
}
TEST_F(WasmModuleVerifyTest, DataCountSectionCorrectPlacement) {
static const byte data[] = {SECTION(Element, ENTRY_COUNT(0)),
SECTION(DataCount, ENTRY_COUNT(0)),

View File

@ -24,13 +24,6 @@
'proposals/multi-value/call': [FAIL],
'proposals/multi-value/if': [FAIL],
'proposals/multi-value/func': [FAIL],
# TODO(v8:10156): Spec tests are failing after rebasing the reference-types
# proposal on the bulk-operations proposal.
'proposals/reference-types/elem': [FAIL],
'proposals/reference-types/ref_func': [FAIL],
'proposals/reference-types/table_fill': [FAIL],
'proposals/reference-types/table_grow': [FAIL],
}], # ALWAYS
['arch == mipsel or arch == mips64el or arch == mips or arch == mips64', {