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

[wasm-gc] Disallow type checks from eqref and anyref

As per the latest wasm-gc spec, type checks are only allowed from
subtypes of dataref and funcref. To cast from a more general type,
specialized classification instructions need to be used (will come
later).

Bug: v8:7748
Change-Id: I29de48f445d652c5fc4e266d82e7d2e32cd7b6d3
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2649262
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#72408}
parent 84dec706
...@@ -5821,11 +5821,6 @@ WasmGraphBuilder::Callbacks WasmGraphBuilder::BranchCallbacks( ...@@ -5821,11 +5821,6 @@ WasmGraphBuilder::Callbacks WasmGraphBuilder::BranchCallbacks(
void WasmGraphBuilder::TypeCheck( void WasmGraphBuilder::TypeCheck(
Node* object, Node* rtt, WasmGraphBuilder::ObjectReferenceKnowledge config, Node* object, Node* rtt, WasmGraphBuilder::ObjectReferenceKnowledge config,
bool null_succeeds, Callbacks callbacks) { bool null_succeeds, Callbacks callbacks) {
if (config.object_can_be_i31) {
callbacks.fail_if(gasm_->IsI31(object));
} else {
AssertFalse(mcgraph(), gasm_.get(), gasm_->IsI31(object));
}
if (config.object_can_be_null) { if (config.object_can_be_null) {
(null_succeeds ? callbacks.succeed_if (null_succeeds ? callbacks.succeed_if
: callbacks.fail_if)(gasm_->WordEqual(object, RefNull())); : callbacks.fail_if)(gasm_->WordEqual(object, RefNull()));
......
...@@ -168,7 +168,6 @@ class WasmGraphBuilder { ...@@ -168,7 +168,6 @@ class WasmGraphBuilder {
struct ObjectReferenceKnowledge { struct ObjectReferenceKnowledge {
bool object_can_be_null; bool object_can_be_null;
bool object_must_be_data_ref; bool object_must_be_data_ref;
bool object_can_be_i31;
int8_t rtt_depth; int8_t rtt_depth;
}; };
enum EnforceBoundsCheck : bool { // -- enum EnforceBoundsCheck : bool { // --
......
...@@ -4410,7 +4410,6 @@ class LiftoffCompiler { ...@@ -4410,7 +4410,6 @@ class LiftoffCompiler {
LiftoffRegister rtt_reg = pinned.set(__ PopToRegister(pinned)); LiftoffRegister rtt_reg = pinned.set(__ PopToRegister(pinned));
LiftoffRegister obj_reg = pinned.set(__ PopToRegister(pinned)); LiftoffRegister obj_reg = pinned.set(__ PopToRegister(pinned));
bool obj_can_be_i31 = IsSubtypeOf(kWasmI31Ref, obj.type, decoder->module_);
// Reserve all temporary registers up front, so that the cache state // Reserve all temporary registers up front, so that the cache state
// tracking doesn't get confused by the following conditional jumps. // tracking doesn't get confused by the following conditional jumps.
LiftoffRegister tmp1 = LiftoffRegister tmp1 =
...@@ -4418,9 +4417,6 @@ class LiftoffCompiler { ...@@ -4418,9 +4417,6 @@ class LiftoffCompiler {
? LiftoffRegister(opt_scratch) ? LiftoffRegister(opt_scratch)
: pinned.set(__ GetUnusedRegister(kGpReg, pinned)); : pinned.set(__ GetUnusedRegister(kGpReg, pinned));
LiftoffRegister tmp2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); LiftoffRegister tmp2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
if (obj_can_be_i31) {
__ emit_smi_check(obj_reg.gp(), no_match, LiftoffAssembler::kJumpOnSmi);
}
if (obj.type.is_nullable()) { if (obj.type.is_nullable()) {
LoadNullValue(tmp1.gp(), pinned); LoadNullValue(tmp1.gp(), pinned);
__ emit_cond_jump(kEqual, null_succeeds ? &match : no_match, obj.type, __ emit_cond_jump(kEqual, null_succeeds ? &match : no_match, obj.type,
......
...@@ -4085,12 +4085,20 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -4085,12 +4085,20 @@ class WasmFullDecoder : public WasmDecoder<validate> {
case kExprRefTest: { case kExprRefTest: {
// "Tests whether {obj}'s runtime type is a runtime subtype of {rtt}." // "Tests whether {obj}'s runtime type is a runtime subtype of {rtt}."
Value rtt = Pop(1); Value rtt = Pop(1);
Value obj = Pop(0, kWasmAnyRef); Value obj = Pop(0);
Value* value = Push(kWasmI32); Value* value = Push(kWasmI32);
if (!VALIDATE(rtt.type.is_rtt() || rtt.type.is_bottom())) { if (!VALIDATE(rtt.type.is_rtt() || rtt.type.is_bottom())) {
PopTypeError(1, rtt, "rtt"); PopTypeError(1, rtt, "rtt");
return 0; return 0;
} }
if (!VALIDATE(IsSubtypeOf(obj.type, kWasmFuncRef, this->module_) ||
IsSubtypeOf(obj.type,
ValueType::Ref(HeapType::kData, kNullable),
this->module_) ||
obj.type.is_bottom())) {
PopTypeError(0, obj, "subtype of (ref null func) or (ref null data)");
return 0;
}
if (!obj.type.is_bottom() && !rtt.type.is_bottom()) { if (!obj.type.is_bottom() && !rtt.type.is_bottom()) {
if (!VALIDATE(IsSubtypeOf( if (!VALIDATE(IsSubtypeOf(
ValueType::Ref(rtt.type.ref_index(), kNonNullable), obj.type, ValueType::Ref(rtt.type.ref_index(), kNonNullable), obj.type,
...@@ -4106,11 +4114,19 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -4106,11 +4114,19 @@ class WasmFullDecoder : public WasmDecoder<validate> {
} }
case kExprRefCast: { case kExprRefCast: {
Value rtt = Pop(1); Value rtt = Pop(1);
Value obj = Pop(0, kWasmAnyRef); Value obj = Pop(0);
if (!VALIDATE(rtt.type.is_rtt() || rtt.type.is_bottom())) { if (!VALIDATE(rtt.type.is_rtt() || rtt.type.is_bottom())) {
PopTypeError(1, rtt, "rtt"); PopTypeError(1, rtt, "rtt");
return 0; return 0;
} }
if (!VALIDATE(IsSubtypeOf(obj.type, kWasmFuncRef, this->module_) ||
IsSubtypeOf(obj.type,
ValueType::Ref(HeapType::kData, kNullable),
this->module_) ||
obj.type.is_bottom())) {
PopTypeError(0, obj, "subtype of (ref null func) or (ref null data)");
return 0;
}
if (!obj.type.is_bottom() && !rtt.type.is_bottom()) { if (!obj.type.is_bottom() && !rtt.type.is_bottom()) {
if (!VALIDATE(IsSubtypeOf( if (!VALIDATE(IsSubtypeOf(
ValueType::Ref(rtt.type.ref_index(), kNonNullable), obj.type, ValueType::Ref(rtt.type.ref_index(), kNonNullable), obj.type,
...@@ -4139,9 +4155,12 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -4139,9 +4155,12 @@ class WasmFullDecoder : public WasmDecoder<validate> {
return 0; return 0;
} }
Value obj = Pop(0); Value obj = Pop(0);
if (!VALIDATE(obj.type.is_object_reference_type() || if (!VALIDATE(IsSubtypeOf(obj.type, kWasmFuncRef, this->module_) ||
rtt.type.is_bottom())) { IsSubtypeOf(obj.type,
PopTypeError(0, obj, "reference"); ValueType::Ref(HeapType::kData, kNullable),
this->module_) ||
obj.type.is_bottom())) {
PopTypeError(0, obj, "subtype of (ref null func) or (ref null data)");
return 0; return 0;
} }
// The static type of {obj} must be a supertype of {rtt}'s type. // The static type of {obj} must be a supertype of {rtt}'s type.
......
...@@ -989,7 +989,6 @@ class WasmGraphBuildingInterface { ...@@ -989,7 +989,6 @@ class WasmGraphBuildingInterface {
result.object_can_be_null = object_type.is_nullable(); result.object_can_be_null = object_type.is_nullable();
DCHECK(object_type.is_object_reference_type()); // Checked by validation. DCHECK(object_type.is_object_reference_type()); // Checked by validation.
result.object_must_be_data_ref = is_data_ref_type(object_type, module); result.object_must_be_data_ref = is_data_ref_type(object_type, module);
result.object_can_be_i31 = IsSubtypeOf(kWasmI31Ref, object_type, module);
result.rtt_depth = rtt_type.has_depth() ? rtt_type.depth() : -1; result.rtt_depth = rtt_type.has_depth() ? rtt_type.depth() : -1;
return result; return result;
} }
......
...@@ -347,6 +347,7 @@ WASM_COMPILED_EXEC_TEST(WasmBrOnNull) { ...@@ -347,6 +347,7 @@ WASM_COMPILED_EXEC_TEST(WasmBrOnNull) {
WASM_COMPILED_EXEC_TEST(BrOnCast) { WASM_COMPILED_EXEC_TEST(BrOnCast) {
WasmGCTester tester(execution_tier); WasmGCTester tester(execution_tier);
FLAG_experimental_liftoff_extern_ref = true; FLAG_experimental_liftoff_extern_ref = true;
ValueType kDataRefNull = ValueType::Ref(HeapType::kData, kNullable);
const byte type_index = tester.DefineStruct({F(kWasmI32, true)}); const byte type_index = tester.DefineStruct({F(kWasmI32, true)});
const byte other_type_index = tester.DefineStruct({F(kWasmF32, true)}); const byte other_type_index = tester.DefineStruct({F(kWasmF32, true)});
const byte rtt_index = const byte rtt_index =
...@@ -354,7 +355,7 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) { ...@@ -354,7 +355,7 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) {
WasmInitExpr::RttCanon( WasmInitExpr::RttCanon(
static_cast<HeapType::Representation>(type_index))); static_cast<HeapType::Representation>(type_index)));
const byte kTestStruct = tester.DefineFunction( const byte kTestStruct = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmEqRef}, tester.sigs.i_v(), {kWasmI32, kDataRefNull},
{WASM_BLOCK(WASM_LOCAL_SET(0, WASM_I32V(111)), {WASM_BLOCK(WASM_LOCAL_SET(0, WASM_I32V(111)),
// Pipe a struct through a local so it's statically typed // Pipe a struct through a local so it's statically typed
// as eqref. // as eqref.
...@@ -371,20 +372,8 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) { ...@@ -371,20 +372,8 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) {
WASM_DROP, WASM_LOCAL_SET(0, WASM_I32V(333))), WASM_DROP, WASM_LOCAL_SET(0, WASM_I32V(333))),
WASM_LOCAL_GET(0), kExprEnd}); WASM_LOCAL_GET(0), kExprEnd});
const byte kTestI31 = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmEqRef},
{WASM_BLOCK(WASM_LOCAL_SET(0, WASM_I32V(111)),
// Pipe an i31ref through a local so it's statically typed
// as eqref.
WASM_LOCAL_SET(1, WASM_I31_NEW(WASM_I32V(42))),
WASM_LOCAL_GET(1),
// The i31 is not a struct, so this branch isn't taken.
WASM_BR_ON_CAST(0, WASM_GLOBAL_GET(rtt_index)), WASM_DROP,
WASM_LOCAL_SET(0, WASM_I32V(222))), // Final result.
WASM_LOCAL_GET(0), kExprEnd});
const byte kTestNull = tester.DefineFunction( const byte kTestNull = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmEqRef}, tester.sigs.i_v(), {kWasmI32, kDataRefNull},
{WASM_BLOCK(WASM_LOCAL_SET(0, WASM_I32V(111)), {WASM_BLOCK(WASM_LOCAL_SET(0, WASM_I32V(111)),
WASM_LOCAL_GET(1), // Put a nullref onto the value stack. WASM_LOCAL_GET(1), // Put a nullref onto the value stack.
// Neither of these branches is taken for nullref. // Neither of these branches is taken for nullref.
...@@ -395,7 +384,7 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) { ...@@ -395,7 +384,7 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) {
WASM_LOCAL_GET(0), kExprEnd}); WASM_LOCAL_GET(0), kExprEnd});
const byte kTypedAfterBranch = tester.DefineFunction( const byte kTypedAfterBranch = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmEqRef}, tester.sigs.i_v(), {kWasmI32, kDataRefNull},
{WASM_LOCAL_SET(1, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42), {WASM_LOCAL_SET(1, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42),
WASM_GLOBAL_GET(rtt_index))), WASM_GLOBAL_GET(rtt_index))),
WASM_BLOCK(WASM_LOCAL_SET( WASM_BLOCK(WASM_LOCAL_SET(
...@@ -415,7 +404,6 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) { ...@@ -415,7 +404,6 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) {
tester.CompileModule(); tester.CompileModule();
tester.CheckResult(kTestStruct, 222); tester.CheckResult(kTestStruct, 222);
tester.CheckResult(kTestI31, 222);
tester.CheckResult(kTestNull, 333); tester.CheckResult(kTestNull, 333);
tester.CheckResult(kTypedAfterBranch, 42); tester.CheckResult(kTypedAfterBranch, 42);
} }
...@@ -1116,7 +1104,9 @@ WASM_COMPILED_EXEC_TEST(CastsBenchmark) { ...@@ -1116,7 +1104,9 @@ WASM_COMPILED_EXEC_TEST(CastsBenchmark) {
const byte SuperType = tester.DefineStruct({F(wasm::kWasmI32, true)}); const byte SuperType = tester.DefineStruct({F(wasm::kWasmI32, true)});
const byte SubType = const byte SubType =
tester.DefineStruct({F(wasm::kWasmI32, true), F(wasm::kWasmI32, true)}); tester.DefineStruct({F(wasm::kWasmI32, true), F(wasm::kWasmI32, true)});
const byte ListType = tester.DefineArray(wasm::kWasmEqRef, true);
ValueType kDataRefNull = ValueType::Ref(HeapType::kData, kNullable);
const byte ListType = tester.DefineArray(kDataRefNull, true);
const byte List = const byte List =
tester.AddGlobal(ValueType::Ref(ListType, kNullable), true, tester.AddGlobal(ValueType::Ref(ListType, kNullable), true,
......
...@@ -4229,12 +4229,19 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) { ...@@ -4229,12 +4229,19 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) {
static_cast<HeapType::Representation>( static_cast<HeapType::Representation>(
builder.AddStruct({F(kWasmI16, true), F(kWasmI32, false)})); builder.AddStruct({F(kWasmI16, true), F(kWasmI32, false)}));
HeapType::Representation func_heap_1 =
static_cast<HeapType::Representation>(builder.AddSignature(sigs.i_i()));
HeapType::Representation func_heap_2 =
static_cast<HeapType::Representation>(builder.AddSignature(sigs.i_v()));
// Passing/failing tests due to static subtyping. // Passing/failing tests due to static subtyping.
std::tuple<HeapType::Representation, HeapType::Representation, bool> tests[] = std::tuple<HeapType::Representation, HeapType::Representation, bool> tests[] =
{{HeapType::kAny, array_heap, true}, {{HeapType::kData, array_heap, true},
{HeapType::kAny, super_struct_heap, true}, {HeapType::kData, super_struct_heap, true},
{HeapType::kEq, array_heap, true}, {HeapType::kFunc, func_heap_1, true},
{HeapType::kEq, super_struct_heap, true}, {func_heap_1, func_heap_1, true},
{func_heap_1, func_heap_2, false},
{super_struct_heap, sub_struct_heap, true}, {super_struct_heap, sub_struct_heap, true},
{sub_struct_heap, super_struct_heap, false}, {sub_struct_heap, super_struct_heap, false},
{sub_struct_heap, array_heap, false}, {sub_struct_heap, array_heap, false},
...@@ -4290,24 +4297,28 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) { ...@@ -4290,24 +4297,28 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) {
sigs.v_v(), sigs.v_v(),
{WASM_REF_TEST(WASM_I32V(1), WASM_RTT_CANON(array_heap)), kExprDrop}, {WASM_REF_TEST(WASM_I32V(1), WASM_RTT_CANON(array_heap)), kExprDrop},
kAppendEnd, kAppendEnd,
"ref.test[0] expected type anyref, found i32.const of type i32"); "ref.test[0] expected subtype of (ref null func) or (ref null data), "
"found i32.const of type i32");
ExpectFailure( ExpectFailure(
sigs.v_v(), sigs.v_v(),
{WASM_REF_CAST(WASM_I32V(1), WASM_RTT_CANON(array_heap)), kExprDrop}, {WASM_REF_CAST(WASM_I32V(1), WASM_RTT_CANON(array_heap)), kExprDrop},
kAppendEnd, kAppendEnd,
"ref.cast[0] expected type anyref, found i32.const of type i32"); "ref.cast[0] expected subtype of (ref null func) or (ref null data), "
"found i32.const of type i32");
// Trivial type error. // Trivial type error.
ExpectFailure( ExpectFailure(
sigs.v_v(), sigs.v_v(),
{WASM_REF_TEST(WASM_I32V(1), WASM_RTT_CANON(array_heap)), kExprDrop}, {WASM_REF_TEST(WASM_I32V(1), WASM_RTT_CANON(array_heap)), kExprDrop},
kAppendEnd, kAppendEnd,
"ref.test[0] expected type anyref, found i32.const of type i32"); "ref.test[0] expected subtype of (ref null func) or (ref null data), "
"found i32.const of type i32");
ExpectFailure( ExpectFailure(
sigs.v_v(), sigs.v_v(),
{WASM_REF_CAST(WASM_I32V(1), WASM_RTT_CANON(array_heap)), kExprDrop}, {WASM_REF_CAST(WASM_I32V(1), WASM_RTT_CANON(array_heap)), kExprDrop},
kAppendEnd, kAppendEnd,
"ref.cast[0] expected type anyref, found i32.const of type i32"); "ref.cast[0] expected subtype of (ref null func) or (ref null data), "
"found i32.const of type i32");
} }
// This tests that num_locals_ in decoder remains consistent, even if we fail // This tests that num_locals_ in decoder remains consistent, even if we fail
......
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