[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:
parent
94cdc18ad7
commit
de17316ad2
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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());
|
||||
})();
|
||||
|
@ -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([
|
||||
|
@ -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() {
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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)),
|
||||
|
@ -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', {
|
||||
|
Loading…
Reference in New Issue
Block a user