Commit a61aaed9 authored by Manos Koukoutos's avatar Manos Koukoutos Committed by Commit Bot

[wasm-gc] Allow reference types to function signatures

Changes:
- Remove restriction that function types cannot be used as ref types.
- Introduce WasmModule::has_type().
- Remove deferred signature checks in module-decoder. Instead, check if
  type indices are out of bounds in consume_value_type (was bugged
  before).
- Remove obsolete GetCanonicalRttIndex.
- Refine type of ref.func.
- Statically check immediate type against table type for call_indirect.
- Dynamic check for call_indirect should only happen when for funcref
  (currently the only function supertype).
- Allocate a different map per function signature (with Map::Copy).
- Introduce function type equivalence and (trivial) subtyping.
- Add a few elementary tests.

Bug: v8:7748
Change-Id: If57d0bfd856c9eb3784191f3de423f53dfd26ef1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2335190
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69250}
parent 408e7240
......@@ -2890,18 +2890,24 @@ Node* WasmGraphBuilder::BuildIndirectCall(uint32_t table_index,
key = graph()->NewNode(machine->Word32And(), key, mask);
}
// Load signature from the table and check.
int32_t expected_sig_id = env_->module->signature_ids[sig_index];
Node* int32_scaled_key = Uint32ToUintptr(
graph()->NewNode(machine->Word32Shl(), key, Int32Constant(2)));
Node* loaded_sig = SetEffect(
graph()->NewNode(machine->Load(MachineType::Int32()), ift_sig_ids,
int32_scaled_key, effect(), control()));
Node* sig_match = graph()->NewNode(machine->WordEqual(), loaded_sig,
Int32Constant(expected_sig_id));
// Check that the dynamic type of the function is a subtype of its static
// (table) type. Currently, the only subtyping between function types is
// (ref null $t) <: funcref for all $t: function_type.
// TODO(7748): Expand this with function subtyping.
if (env_->module->tables[table_index].type == wasm::kWasmFuncRef) {
int32_t expected_sig_id = env_->module->signature_ids[sig_index];
TrapIfFalse(wasm::kTrapFuncSigMismatch, sig_match, position);
Node* loaded_sig = SetEffect(
graph()->NewNode(machine->Load(MachineType::Int32()), ift_sig_ids,
int32_scaled_key, effect(), control()));
Node* sig_match = graph()->NewNode(machine->WordEqual(), loaded_sig,
Int32Constant(expected_sig_id));
TrapIfFalse(wasm::kTrapFuncSigMismatch, sig_match, position);
}
Node* tagged_scaled_key;
if (kTaggedSize == kInt32Size) {
......@@ -5358,10 +5364,9 @@ Node* WasmGraphBuilder::RttCanon(wasm::HeapType type) {
UNREACHABLE();
}
}
int map_index = wasm::GetCanonicalRttIndex(env_->module, type.ref_index());
Node* maps_list =
LOAD_INSTANCE_FIELD(ManagedObjectMaps, MachineType::TaggedPointer());
return LOAD_FIXED_ARRAY_SLOT_PTR(maps_list, map_index);
return LOAD_FIXED_ARRAY_SLOT_PTR(maps_list, type.ref_index());
}
Node* WasmGraphBuilder::RttSub(wasm::HeapType type, Node* parent_rtt) {
......
......@@ -1922,14 +1922,6 @@ Handle<JSFunction> Factory::NewFunction(const NewFunctionArgs& args) {
// bootstrapping.
(args.maybe_builtin_id_ == Builtins::kEmptyFunction ||
args.maybe_builtin_id_ == Builtins::kProxyConstructor));
} else {
DCHECK(
(*map == *isolate()->sloppy_function_map()) ||
(*map == *isolate()->sloppy_function_without_prototype_map()) ||
(*map == *isolate()->sloppy_function_with_readonly_prototype_map()) ||
(*map == *isolate()->strict_function_map()) ||
(*map == *isolate()->strict_function_without_prototype_map()) ||
(*map == *isolate()->wasm_exported_function_map()));
}
#endif
......
......@@ -1231,14 +1231,25 @@ class WasmDecoder : public Decoder {
error("function table has to exist to execute call_indirect");
return false;
}
if (!VALIDATE(module_->tables[imm.table_index].type == kWasmFuncRef)) {
error("table of call_indirect must be of type funcref");
if (!VALIDATE(IsSubtypeOf(module_->tables[imm.table_index].type,
kWasmFuncRef, module_))) {
error("table of call_indirect must be of a function type");
return false;
}
if (!Complete(imm)) {
errorf(pc, "invalid signature index: #%u", imm.sig_index);
return false;
}
// Check that the dynamic signature for this call is a subtype of the static
// type of the table the function is defined in.
if (!VALIDATE(IsSubtypeOf(ValueType::Ref(imm.sig_index, kNonNullable),
module_->tables[imm.table_index].type,
module_))) {
errorf(pc,
"call_indirect: Signature of function %u is not a subtype of "
"table %u",
imm.sig_index, imm.table_index);
}
return true;
}
......@@ -1435,12 +1446,8 @@ class WasmDecoder : public Decoder {
return false;
}
if (!VALIDATE(imm.type.is_generic() ||
module_->has_array(imm.type.ref_index()) ||
module_->has_struct(imm.type.ref_index()))) {
errorf(
pc,
"Type index %u does not refer to a struct or array type definition",
imm.type.ref_index());
module_->has_type(imm.type.ref_index()))) {
errorf(pc, "Type index %u is out of bounds", imm.type.ref_index());
return false;
}
return true;
......@@ -2638,7 +2645,10 @@ class WasmFullDecoder : public WasmDecoder<validate> {
CHECK_PROTOTYPE_OPCODE(reftypes);
FunctionIndexImmediate<validate> imm(this, this->pc_ + 1);
if (!this->Validate(this->pc_ + 1, imm)) return 0;
Value* value = Push(ValueType::Ref(HeapType::kFunc, kNonNullable));
HeapType heap_type(this->enabled_.has_typed_funcref()
? this->module_->functions[imm.index].sig_index
: HeapType::kFunc);
Value* value = Push(ValueType::Ref(heap_type, kNonNullable));
CALL_INTERFACE_IF_REACHABLE(RefFunc, imm.index, value);
return 1 + imm.length;
}
......
This diff is collapsed.
......@@ -185,7 +185,14 @@ Handle<Map> AllocateSubRtt(Isolate* isolate,
} else if (module->has_array(type)) {
rtt = wasm::CreateArrayMap(isolate, module, type, parent);
} else {
UNREACHABLE();
DCHECK(module->has_signature(type));
// Currently, parent rtts for functions are meaningless,
// since (rtt.test func rtt) iff (func.map == rtt).
// Therefore, we simply create a fresh function map here.
// TODO(7748): Canonicalize rtts to make them work for identical function
// types.
rtt = Map::Copy(isolate, isolate->wasm_exported_function_map(),
"fresh function map");
}
cache = RttSubtypes::Insert(isolate, cache, type, rtt);
parent->wasm_type_info().set_subtypes(*cache);
......@@ -567,27 +574,32 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
// Must happen before {InitGlobals} because globals can refer to these maps.
//--------------------------------------------------------------------------
if (enabled_.has_gc()) {
int count = GetCanonicalRttIndex(
module_, static_cast<uint32_t>(module_->type_kinds.size()));
Handle<FixedArray> maps =
isolate_->factory()->NewUninitializedFixedArray(count);
Handle<FixedArray> maps = isolate_->factory()->NewUninitializedFixedArray(
static_cast<int>(module_->type_kinds.size()));
// TODO(7748): Do we want a different sentinel here?
Handle<Map> anyref_sentinel_map = isolate_->factory()->null_map();
int map_index = 0;
for (int i = 0; i < static_cast<int>(module_->type_kinds.size()); i++) {
if (module_->type_kinds[i] == kWasmStructTypeCode) {
Handle<Map> map =
CreateStructMap(isolate_, module_, i, anyref_sentinel_map);
maps->set(map_index++, *map);
}
if (module_->type_kinds[i] == kWasmArrayTypeCode) {
Handle<Map> map =
CreateArrayMap(isolate_, module_, i, anyref_sentinel_map);
maps->set(map_index++, *map);
for (int map_index = 0;
map_index < static_cast<int>(module_->type_kinds.size());
map_index++) {
Handle<Map> map;
switch (module_->type_kinds[map_index]) {
case kWasmStructTypeCode:
map = CreateStructMap(isolate_, module_, map_index,
anyref_sentinel_map);
break;
case kWasmArrayTypeCode:
map =
CreateArrayMap(isolate_, module_, map_index, anyref_sentinel_map);
break;
case kWasmFunctionTypeCode:
// TODO(7748): Canonicalize rtts to make them work for identical
// function types.
map = Map::Copy(isolate_, isolate_->wasm_exported_function_map(),
"fresh function map");
break;
}
maps->set(map_index, *map);
}
// {GetCanonicalRttIndex} must be in sync with the for-loop above.
DCHECK_EQ(count, map_index);
instance->set_managed_object_maps(*maps);
}
......@@ -1516,8 +1528,7 @@ Handle<Object> InstanceBuilder::RecursivelyEvaluateGlobalInitializer(
UNREACHABLE();
}
// Non-generic types fall through.
int map_index = GetCanonicalRttIndex(
module_, static_cast<uint32_t>(init.immediate().heap_type));
int map_index = init.immediate().heap_type;
return handle(instance->managed_object_maps().get(map_index), isolate_);
}
case WasmInitExpr::kRttSub: {
......
......@@ -83,21 +83,6 @@ int GetExportWrapperIndex(const WasmModule* module, const FunctionSig* sig,
return result;
}
// static
int GetCanonicalRttIndex(const WasmModule* module, uint32_t type_index) {
int rtt_index = 0;
const std::vector<uint8_t>& type_kinds = module->type_kinds;
// This logic must be in sync with the code in module-instantiate.cc that
// initializes the "managed_object_maps" list on the instance.
for (uint32_t i = 0; i < type_index; i++) {
if (type_kinds[i] == wasm::kWasmStructTypeCode ||
type_kinds[i] == wasm::kWasmArrayTypeCode) {
rtt_index++;
}
}
return rtt_index;
}
// static
int GetWasmFunctionOffset(const WasmModule* module, uint32_t func_index) {
const std::vector<WasmFunction>& functions = module->functions;
......
......@@ -296,6 +296,9 @@ struct V8_EXPORT_PRIVATE WasmModule {
std::vector<TypeDefinition> types; // by type index
std::vector<uint8_t> type_kinds; // by type index
std::vector<uint32_t> signature_ids; // by signature index
bool has_type(uint32_t index) const { return index < types.size(); }
void add_signature(const FunctionSig* sig) {
types.push_back(TypeDefinition(sig));
type_kinds.push_back(kWasmFunctionTypeCode);
......@@ -403,10 +406,6 @@ V8_EXPORT_PRIVATE int MaxNumExportWrappers(const WasmModule* module);
int GetExportWrapperIndex(const WasmModule* module, const FunctionSig* sig,
bool is_import);
// Returns the index of the canonical RTT of the given struct/array type
// in the instance's list of canonical RTTs.
int GetCanonicalRttIndex(const WasmModule* module, uint32_t type_index);
// Return the byte offset of the function identified by the given index.
// The offset will be relative to the start of the module bytes.
// Returns -1 if the function index is invalid.
......
......@@ -1844,7 +1844,17 @@ Handle<WasmExportedFunction> WasmExportedFunction::New(
Handle<Map> function_map;
switch (instance->module()->origin) {
case wasm::kWasmOrigin:
function_map = isolate->wasm_exported_function_map();
if (instance->module_object()
.native_module()
->enabled_features()
.has_gc()) {
uint32_t sig_index =
instance->module()->functions[func_index].sig_index;
function_map = handle(
Map::cast(instance->managed_object_maps().get(sig_index)), isolate);
} else {
function_map = isolate->wasm_exported_function_map();
}
break;
case wasm::kAsmJsSloppyOrigin:
function_map = isolate->sloppy_function_map();
......
......@@ -62,6 +62,33 @@ bool IsStructTypeEquivalent(uint32_t type_index_1, uint32_t type_index_2,
}
return true;
}
bool IsFunctionTypeEquivalent(uint32_t type_index_1, uint32_t type_index_2,
const WasmModule* module) {
if (module->type_kinds[type_index_1] != kWasmFunctionTypeCode ||
module->type_kinds[type_index_2] != kWasmFunctionTypeCode) {
return false;
}
const FunctionSig* sig1 = module->types[type_index_1].function_sig;
const FunctionSig* sig2 = module->types[type_index_2].function_sig;
if (sig1->parameter_count() != sig2->parameter_count() ||
sig1->return_count() != sig2->return_count()) {
return false;
}
auto iter1 = sig1->all();
auto iter2 = sig2->all();
// Temporarily cache type equivalence for the recursive call.
module->cache_type_equivalence(type_index_1, type_index_2);
for (int i = 0; i < iter1.size(); i++) {
if (iter1[i] != iter2[i]) {
module->uncache_type_equivalence(type_index_1, type_index_2);
return false;
}
}
return true;
}
bool IsEquivalent(ValueType type1, ValueType type2, const WasmModule* module) {
if (type1 == type2) return true;
......@@ -76,7 +103,8 @@ bool IsEquivalent(ValueType type1, ValueType type2, const WasmModule* module) {
return true;
}
return IsArrayTypeEquivalent(type1.ref_index(), type2.ref_index(), module) ||
IsStructTypeEquivalent(type1.ref_index(), type2.ref_index(), module);
IsStructTypeEquivalent(type1.ref_index(), type2.ref_index(), module) ||
IsFunctionTypeEquivalent(type1.ref_index(), type2.ref_index(), module);
}
bool IsStructSubtype(uint32_t subtype_index, uint32_t supertype_index,
......@@ -130,9 +158,16 @@ bool IsArraySubtype(uint32_t subtype_index, uint32_t supertype_index,
return true;
}
}
// TODO(7748): Expand this with function subtyping.
bool IsFunctionSubtype(uint32_t subtype_index, uint32_t supertype_index,
const WasmModule* module) {
return IsFunctionTypeEquivalent(subtype_index, supertype_index, module);
}
} // namespace
// TODO(7748): Extend this with function and any-heap subtyping.
// TODO(7748): Extend this with any-heap subtyping.
V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(ValueType subtype,
ValueType supertype,
const WasmModule* module) {
......@@ -145,16 +180,22 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(ValueType subtype,
HeapType sub_heap = subtype.heap_type();
HeapType super_heap = supertype.heap_type();
DCHECK(!module->has_signature(sub_heap.representation()) &&
!module->has_signature(super_heap.representation()));
if (sub_heap == super_heap) {
return true;
}
// eqref is a supertype of i31ref and all custom types.
// eqref is a supertype of i31ref, array, and struct types.
if (super_heap.representation() == HeapType::kEq) {
return (sub_heap.is_index() || sub_heap.representation() == HeapType::kI31);
return (sub_heap.is_index() &&
!module->has_signature(sub_heap.ref_index())) ||
sub_heap.representation() == HeapType::kI31;
}
// funcref is a supertype of all function types.
if (super_heap.representation() == HeapType::kFunc) {
return sub_heap.is_index() && module->has_signature(sub_heap.ref_index());
}
// At the moment, generic heap types are not subtyping-related otherwise.
if (sub_heap.is_generic() || super_heap.is_generic()) {
return false;
......@@ -168,10 +209,11 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(ValueType subtype,
}
return IsStructSubtype(sub_heap.ref_index(), super_heap.ref_index(),
module) ||
IsArraySubtype(sub_heap.ref_index(), super_heap.ref_index(), module);
IsArraySubtype(sub_heap.ref_index(), super_heap.ref_index(), module) ||
IsFunctionSubtype(sub_heap.ref_index(), super_heap.ref_index(),
module);
}
// TODO(7748): Extend this with function subtyping.
ValueType CommonSubtype(ValueType a, ValueType b, const WasmModule* module) {
if (a == b) return a;
if (IsSubtypeOf(a, b, module)) return a;
......
......@@ -86,6 +86,8 @@ class WasmGCTester {
return builder_.AddArrayType(zone.New<ArrayType>(element_type, mutability));
}
byte DefineSignature(FunctionSig* sig) { return builder_.AddSignature(sig); }
void CompileModule() {
ZoneBuffer buffer(&zone);
builder_.WriteTo(&buffer);
......@@ -793,6 +795,78 @@ TEST(ArrayNewMap) {
CHECK_EQ(Handle<WasmArray>::cast(result)->map(), *map);
}
TEST(FunctionRefs) {
WasmGCTester tester;
const byte func_index =
tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_I32V(42), kExprEnd});
const byte sig_index = 0;
const byte other_sig_index = tester.DefineSignature(tester.sigs.d_d());
// This is just so func_index counts as "declared".
tester.AddGlobal(ValueType::Ref(sig_index, kNullable), false,
WasmInitExpr::RefFuncConst(func_index));
ValueType func_type = ValueType::Ref(sig_index, kNonNullable);
FunctionSig sig_func(1, 0, &func_type);
ValueType rtt1 = ValueType::Rtt(sig_index, 1);
FunctionSig sig_rtt1(1, 0, &rtt1);
const byte rtt_canon = tester.DefineFunction(
&sig_rtt1, {}, {WASM_RTT_CANON(sig_index), kExprEnd});
ValueType rtt2 = ValueType::Rtt(sig_index, 2);
FunctionSig sig_rtt2(1, 0, &rtt2);
const byte rtt_sub = tester.DefineFunction(
&sig_rtt2, {},
{WASM_RTT_SUB(sig_index, WASM_RTT_CANON(kLocalFuncRef)), kExprEnd});
const byte cast = tester.DefineFunction(
&sig_func, {kWasmFuncRef},
{WASM_SET_LOCAL(0, WASM_REF_FUNC(func_index)),
WASM_REF_CAST(kLocalFuncRef, sig_index, WASM_GET_LOCAL(0),
WASM_RTT_CANON(sig_index)),
kExprEnd});
const byte cast_reference = tester.DefineFunction(
&sig_func, {}, {WASM_REF_FUNC(sig_index), kExprEnd});
const byte test = tester.DefineFunction(
tester.sigs.i_v(), {kWasmFuncRef},
{WASM_SET_LOCAL(0, WASM_REF_FUNC(func_index)),
WASM_REF_TEST(kLocalFuncRef, other_sig_index, WASM_GET_LOCAL(0),
WASM_RTT_CANON(other_sig_index)),
kExprEnd});
tester.CompileModule();
Handle<Object> result_canon =
tester.GetResultObject(rtt_canon).ToHandleChecked();
CHECK(result_canon->IsMap());
Handle<Map> map_canon = Handle<Map>::cast(result_canon);
CHECK(map_canon->IsJSFunctionMap());
Handle<Object> result_sub = tester.GetResultObject(rtt_sub).ToHandleChecked();
CHECK(result_sub->IsMap());
Handle<Map> map_sub = Handle<Map>::cast(result_sub);
CHECK(map_sub->IsJSFunctionMap());
Handle<Object> result_cast = tester.GetResultObject(cast).ToHandleChecked();
CHECK(result_cast->IsJSFunction());
Handle<JSFunction> cast_function = Handle<JSFunction>::cast(result_cast);
Handle<Object> result_cast_reference =
tester.GetResultObject(cast_reference).ToHandleChecked();
CHECK(result_cast_reference->IsJSFunction());
Handle<JSFunction> cast_function_reference =
Handle<JSFunction>::cast(result_cast_reference);
CHECK_EQ(cast_function->code().raw_instruction_start(),
cast_function_reference->code().raw_instruction_start());
tester.CheckResult(test, 0);
}
TEST(RefTestCastNull) {
WasmGCTester tester;
byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)});
......
......@@ -3516,7 +3516,6 @@ TEST_F(FunctionBodyDecoderTest, RefNull) {
module = builder.module();
byte struct_type_index = builder.AddStruct({F(kWasmI32, true)});
byte array_type_index = builder.AddArray(kWasmI32, true);
byte func_index = builder.AddSignature(sigs.i_i());
uint32_t type_reprs[] = {
struct_type_index, array_type_index, HeapType::kExn, HeapType::kFunc,
HeapType::kEq, HeapType::kExtern, HeapType::kI31};
......@@ -3527,14 +3526,8 @@ TEST_F(FunctionBodyDecoderTest, RefNull) {
ExpectValidates(&sig, {WASM_REF_NULL(WASM_HEAP_TYPE(HeapType(type_repr)))});
}
// It fails for undeclared types.
ExpectFailure(
sigs.v_v(), {WASM_REF_NULL(42), kExprDrop}, kAppendEnd,
"Type index 42 does not refer to a struct or array type definition");
// It fails for user-defined function types.
// TODO(7748): This will go once function types are fully implemented.
ExpectFailure(
sigs.v_v(), {WASM_REF_NULL(func_index), kExprDrop}, kAppendEnd,
"Type index 2 does not refer to a struct or array type definition");
ExpectFailure(sigs.v_v(), {WASM_REF_NULL(42), kExprDrop}, kAppendEnd,
"Type index 42 is out of bounds");
}
TEST_F(FunctionBodyDecoderTest, RefIsNull) {
......
......@@ -628,9 +628,7 @@ TEST_F(WasmModuleVerifyTest, RefNullGlobalInvalid1) {
static const byte data[] = {SECTION(Global, ENTRY_COUNT(1), kLocalOptRef, 0,
1, WASM_REF_NULL(0), kExprEnd)};
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_NOT_OK(
result,
"Type index 0 does not refer to a struct or array type definition");
EXPECT_NOT_OK(result, "Type index 0 is out of bounds");
}
TEST_F(WasmModuleVerifyTest, RefNullGlobalInvalid2) {
......@@ -2865,42 +2863,24 @@ TEST_F(WasmModuleVerifyTest, GcStructIdsPass) {
EXPECT_OK(result);
}
TEST_F(WasmModuleVerifyTest, GcTypeIdsUndefinedIndex) {
WASM_FEATURE_SCOPE(gc);
WASM_FEATURE_SCOPE(typed_funcref);
TEST_F(WasmModuleVerifyTest, OutOfBoundsTypeInGlobal) {
WASM_FEATURE_SCOPE(reftypes);
static const byte data[] = {SECTION(
Type, ENTRY_COUNT(1),
WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(WASM_OPT_REF(1), true)))};
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_NOT_OK(result, "reference to undeclared struct/array");
}
TEST_F(WasmModuleVerifyTest, GcTypeIdsIllegalIndex) {
WASM_FEATURE_SCOPE(gc);
WASM_FEATURE_SCOPE(typed_funcref);
WASM_FEATURE_SCOPE(reftypes);
static const byte data[] = {SECTION(
Type, ENTRY_COUNT(2),
WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(WASM_OPT_REF(1), true)),
WASM_FUNCTION_DEF(ENTRY_COUNT(1), kLocalI32, ENTRY_COUNT(1), kLocalI32))};
static const byte data[] = {SECTION(Global, ENTRY_COUNT(1), kLocalRef, 0,
WASM_REF_NULL(0), kExprEnd)};
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_NOT_OK(result, "cannot build reference to function type index");
EXPECT_NOT_OK(result, "Type index 0 is out of bounds");
}
TEST_F(WasmModuleVerifyTest, GcTypeIdsFunSigIllegalIndex) {
WASM_FEATURE_SCOPE(gc);
WASM_FEATURE_SCOPE(typed_funcref);
TEST_F(WasmModuleVerifyTest, OutOfBoundsTypeInType) {
WASM_FEATURE_SCOPE(reftypes);
static const byte data[] = {SECTION(
Type, ENTRY_COUNT(1),
WASM_FUNCTION_DEF(U32V_1(1), kLocalI32, U32V_1(1), WASM_OPT_REF(0)))};
WASM_FEATURE_SCOPE(typed_funcref);
WASM_FEATURE_SCOPE(gc);
static const byte data[] = {
SECTION(Type, ENTRY_COUNT(1),
WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kLocalRef, true)))};
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_NOT_OK(result, "cannot build reference to function type index");
EXPECT_NOT_OK(result, "Type index 1 is out of bounds");
}
TEST_F(WasmModuleVerifyTest, IllegalPackedFields) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment