mirror of
https://github.com/dart-lang/sdk
synced 2024-10-15 00:10:36 +00:00
[vm] Unsound mode cleanup: remove --strict_null_safety_checks
This flag only makes sense without sound null safety. VM no longer supports unsound mode, so it can be removed. In sound mode this flag is always true. TEST=ci Change-Id: I4a5fa60d9b351b6281efab9462ef2dd2c5565a7c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/362182 Reviewed-by: Siva Annamalai <asiva@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
parent
1dcfc3f938
commit
a6ded2fd08
|
@ -180,7 +180,6 @@
|
|||
"../vm/dart/slow_path_shared_stub_test.dart",
|
||||
"../vm/dart/stack_overflow_shared_test.dart",
|
||||
"../vm/dart/stacktrace_mixin_application_test.dart",
|
||||
"../vm/dart/strict_null_safety_checks_in_weak_mode_test.dart",
|
||||
"../vm/dart/string_equals_test.dart",
|
||||
"../vm/dart/string_intern_test.dart",
|
||||
"../vm/dart/switchable_call_monomorphic_regression_42517_test.dart",
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// This test verifies that with --strict_null_safety_checks runtime casts and
|
||||
// required parameters have strong mode semantics in weak mode.
|
||||
// Derived from tests/language/nnbd/subtyping/type_casts_strong_test.dart
|
||||
// and tests/language/nnbd/required_named_parameters/required_named_args_strong_test.dart.
|
||||
|
||||
// VMOptions=--strict_null_safety_checks --optimization_counter_threshold=10 --deterministic
|
||||
// Requirements=nnbd-weak
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
class C {}
|
||||
|
||||
class W<T> {
|
||||
@pragma('vm:never-inline')
|
||||
asT(arg) => arg as T;
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
asNullableT(arg) => arg as T?;
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
asXT(arg) => arg as X<T>;
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
asNullableXT(arg) => arg as X<T>?;
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
asXNullableT(arg) => arg as X<T?>;
|
||||
}
|
||||
|
||||
class X<T> {}
|
||||
|
||||
class Y {}
|
||||
|
||||
class Z extends Y {}
|
||||
|
||||
testCasts() {
|
||||
// Testing 'arg as T', T = Y
|
||||
final wy = new W<Y>();
|
||||
wy.asT(new Y());
|
||||
wy.asT(new Z());
|
||||
Expect.throwsTypeError(() {
|
||||
wy.asT(null);
|
||||
});
|
||||
Expect.throwsTypeError(() {
|
||||
wy.asT(new C());
|
||||
});
|
||||
|
||||
// Testing 'arg as T?', T = Y
|
||||
wy.asNullableT(new Y());
|
||||
wy.asNullableT(new Z());
|
||||
wy.asNullableT(null);
|
||||
Expect.throwsTypeError(() {
|
||||
wy.asNullableT(new C());
|
||||
});
|
||||
|
||||
// Testing 'arg as X<T>', T = Y
|
||||
wy.asXT(new X<Y>());
|
||||
wy.asXT(new X<Z>());
|
||||
Expect.throwsTypeError(() {
|
||||
wy.asXT(null);
|
||||
});
|
||||
Expect.throwsTypeError(() {
|
||||
wy.asXT(new X<dynamic>());
|
||||
});
|
||||
Expect.throwsTypeError(() {
|
||||
wy.asXT(new X<Y?>());
|
||||
});
|
||||
|
||||
// Testing 'arg as X<T>?', T = Y
|
||||
wy.asNullableXT(new X<Y>());
|
||||
wy.asNullableXT(new X<Z>());
|
||||
wy.asNullableXT(null);
|
||||
Expect.throwsTypeError(() {
|
||||
wy.asNullableXT(new X<dynamic>());
|
||||
});
|
||||
Expect.throwsTypeError(() {
|
||||
wy.asNullableXT(new X<Y?>());
|
||||
});
|
||||
|
||||
// Testing 'arg as X<T?>', T = Y
|
||||
wy.asXNullableT(new X<Y>());
|
||||
wy.asXNullableT(new X<Z>());
|
||||
wy.asXNullableT(new X<Y?>());
|
||||
Expect.throwsTypeError(() {
|
||||
wy.asXNullableT(null);
|
||||
});
|
||||
Expect.throwsTypeError(() {
|
||||
wy.asXNullableT(new X<dynamic>());
|
||||
});
|
||||
|
||||
// Testing 'arg as X<T>', T = Y?
|
||||
final wny = new W<Y?>();
|
||||
wny.asXT(new X<Y>());
|
||||
wny.asXT(new X<Z>());
|
||||
wny.asXT(new X<Y?>());
|
||||
Expect.throwsTypeError(() {
|
||||
wny.asXT(null);
|
||||
});
|
||||
Expect.throwsTypeError(() {
|
||||
wny.asXT(new X<dynamic>());
|
||||
});
|
||||
}
|
||||
|
||||
func(String q0, {bool p3 = false, required int p1, required String p2}) => "";
|
||||
|
||||
testRequiredParameters() {
|
||||
dynamic f = func;
|
||||
|
||||
// Invalid: Subtype may not redeclare optional parameters as required.
|
||||
Expect.throwsTypeError(() {
|
||||
Function(
|
||||
String p0, {
|
||||
required int p1,
|
||||
String p2,
|
||||
}) t2 = f;
|
||||
});
|
||||
|
||||
// Invalid: Subtype may not declare new required named parameters.
|
||||
Expect.throwsTypeError(() {
|
||||
Function(
|
||||
String p0, {
|
||||
required int p1,
|
||||
}) t3 = f;
|
||||
});
|
||||
|
||||
// Invalid: Invocation with explicit null required named argument.
|
||||
Expect.throwsTypeError(() {
|
||||
f("", p1: null, p2: null);
|
||||
});
|
||||
Expect.throwsTypeError(() {
|
||||
Function.apply(f, [""], {#p1: null, #p2: null});
|
||||
});
|
||||
|
||||
// Invalid: Invocation that omits a required named argument.
|
||||
Expect.throwsNoSuchMethodError(() {
|
||||
f("", p1: 100);
|
||||
});
|
||||
Expect.throwsNoSuchMethodError(() {
|
||||
Function.apply(f, [""], {#p1: 100});
|
||||
});
|
||||
}
|
||||
|
||||
main() {
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
testCasts();
|
||||
testRequiredParameters();
|
||||
}
|
||||
}
|
|
@ -2901,8 +2901,7 @@ void FlowGraphCompiler::GenerateCallerChecksForAssertAssignable(
|
|||
|
||||
if (dst_type.IsObjectType()) {
|
||||
// Special case: non-nullable Object.
|
||||
ASSERT(dst_type.IsNonNullable() &&
|
||||
isolate_group()->use_strict_null_safety_checks());
|
||||
ASSERT(dst_type.IsNonNullable());
|
||||
__ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
|
||||
__ BranchIf(NOT_EQUAL, done);
|
||||
// Fall back to type testing stub in caller to throw the exception.
|
||||
|
@ -2924,8 +2923,7 @@ void FlowGraphCompiler::GenerateCallerChecksForAssertAssignable(
|
|||
// Special case: Instantiate the type parameter on the caller side, invoking
|
||||
// the TTS of the corresponding type parameter in the caller.
|
||||
const TypeParameter& type_param = TypeParameter::Cast(dst_type);
|
||||
if (isolate_group()->use_strict_null_safety_checks() &&
|
||||
!type_param.IsNonNullable()) {
|
||||
if (!type_param.IsNonNullable()) {
|
||||
// If the type parameter is nullable when running in strong mode, we need
|
||||
// to handle null before calling the TTS because the type parameter may be
|
||||
// instantiated with a non-nullable type, where the TTS rejects null.
|
||||
|
|
|
@ -282,22 +282,15 @@ void FlowGraphCompiler::GenerateAssertAssignable(
|
|||
if (dst_type.IsNull()) {
|
||||
__ Comment("AssertAssignable for runtime type");
|
||||
// kDstTypeReg should already contain the destination type.
|
||||
const bool null_safety =
|
||||
IsolateGroup::Current()->use_strict_null_safety_checks();
|
||||
GenerateNonLazyDeoptableStubCall(
|
||||
source,
|
||||
null_safety ? StubCode::TypeIsTopTypeForSubtypingNullSafe()
|
||||
: StubCode::TypeIsTopTypeForSubtyping(),
|
||||
UntaggedPcDescriptors::kOther, locs);
|
||||
GenerateNonLazyDeoptableStubCall(source,
|
||||
StubCode::TypeIsTopTypeForSubtyping(),
|
||||
UntaggedPcDescriptors::kOther, locs);
|
||||
// TypeTestABI::kSubtypeTestCacheReg is 0 if the type is a top type.
|
||||
__ BranchIfZero(TypeTestABI::kSubtypeTestCacheReg, &is_assignable,
|
||||
compiler::Assembler::kNearJump);
|
||||
|
||||
GenerateNonLazyDeoptableStubCall(
|
||||
source,
|
||||
null_safety ? StubCode::NullIsAssignableToTypeNullSafe()
|
||||
: StubCode::NullIsAssignableToType(),
|
||||
UntaggedPcDescriptors::kOther, locs);
|
||||
GenerateNonLazyDeoptableStubCall(source, StubCode::NullIsAssignableToType(),
|
||||
UntaggedPcDescriptors::kOther, locs);
|
||||
// TypeTestABI::kSubtypeTestCacheReg is 0 if the object is null and is
|
||||
// assignable.
|
||||
__ BranchIfZero(TypeTestABI::kSubtypeTestCacheReg, &is_assignable,
|
||||
|
|
|
@ -634,11 +634,10 @@ ISOLATE_UNIT_TEST_CASE(TypePropagator_RedefineCanBeSentinelWithCannotBe) {
|
|||
{
|
||||
BlockBuilder builder(H.flow_graph(), b3);
|
||||
v7 = builder.AddDefinition(new RedefinitionInstr(new Value(v3)));
|
||||
CompileType int_type = CompileType::FromAbstractType(
|
||||
Type::Handle(Type::IntType()),
|
||||
/*can_be_null=*/
|
||||
!IsolateGroup::Current()->use_strict_null_safety_checks(),
|
||||
/*can_be_sentinel=*/false);
|
||||
CompileType int_type =
|
||||
CompileType::FromAbstractType(Type::Handle(Type::IntType()),
|
||||
/*can_be_null=*/true,
|
||||
/*can_be_sentinel=*/false);
|
||||
v7->AsRedefinition()->set_constrained_type(new CompileType(int_type));
|
||||
builder.AddInstruction(new GotoInstr(b4, S.GetNextDeoptId()));
|
||||
}
|
||||
|
|
|
@ -2966,9 +2966,6 @@ Fragment FlowGraphBuilder::TestClosureFunctionNamedParameterRequired(
|
|||
const ClosureCallInfo& info,
|
||||
Fragment set,
|
||||
Fragment not_set) {
|
||||
// Required named arguments only exist if null_safety is enabled.
|
||||
if (!IG->use_strict_null_safety_checks()) return not_set;
|
||||
|
||||
Fragment check_required;
|
||||
// We calculate the index to dereference in the parameter names array.
|
||||
check_required += LoadLocal(info.vars->current_param_index);
|
||||
|
@ -3140,10 +3137,6 @@ Fragment FlowGraphBuilder::BuildClosureCallNamedArgumentsCheck(
|
|||
// When no named arguments are provided, we just need to check for possible
|
||||
// required named arguments.
|
||||
if (info.descriptor.NamedCount() == 0) {
|
||||
// No work to do if there are no possible required named parameters.
|
||||
if (!IG->use_strict_null_safety_checks()) {
|
||||
return Fragment();
|
||||
}
|
||||
// If the below changes, we can no longer assume that flag slots existing
|
||||
// means there are required parameters.
|
||||
static_assert(compiler::target::kNumParameterFlags == 1,
|
||||
|
|
|
@ -197,8 +197,6 @@ Fragment PrologueBuilder::BuildParameterHandling() {
|
|||
copy_args_prologue.current = next_missing;
|
||||
|
||||
} else if (num_opt_named_params > 0) {
|
||||
const bool check_required_params =
|
||||
IsolateGroup::Current()->use_strict_null_safety_checks();
|
||||
const intptr_t first_name_offset =
|
||||
compiler::target::ArgumentsDescriptor::first_named_entry_offset() -
|
||||
compiler::target::Array::data_offset();
|
||||
|
@ -265,8 +263,7 @@ Fragment PrologueBuilder::BuildParameterHandling() {
|
|||
good += Drop();
|
||||
}
|
||||
|
||||
const bool required = check_required_params &&
|
||||
function_.IsRequiredAt(opt_param_position[i]);
|
||||
const bool required = function_.IsRequiredAt(opt_param_position[i]);
|
||||
|
||||
if (required) {
|
||||
copy_args_prologue += good;
|
||||
|
|
|
@ -706,8 +706,7 @@ static void EnsureIsTypeOrFunctionTypeOrTypeParameter(Assembler* assembler,
|
|||
// non-zero otherwise.
|
||||
//
|
||||
// All registers other than outputs and non-preserved scratches are preserved.
|
||||
static void GenerateTypeIsTopTypeForSubtyping(Assembler* assembler,
|
||||
bool null_safety) {
|
||||
void StubCodeCompiler::GenerateTypeIsTopTypeForSubtypingStub() {
|
||||
// The only case where the original value of kSubtypeTestCacheReg is needed
|
||||
// after the stub call is on IA32, where it's currently preserved on the stack
|
||||
// before calling the stub (as it's also CODE_REG on that architecture), so we
|
||||
|
@ -755,13 +754,11 @@ static void GenerateTypeIsTopTypeForSubtyping(Assembler* assembler,
|
|||
__ BranchIf(EQUAL, &unwrap_future_or, compiler::Assembler::kNearJump);
|
||||
__ CompareImmediate(scratch2_reg, kInstanceCid);
|
||||
__ BranchIf(NOT_EQUAL, &done, compiler::Assembler::kNearJump);
|
||||
if (null_safety) {
|
||||
// Instance type isn't a top type if non-nullable in null safe mode.
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
scratch1_reg, static_cast<int8_t>(Nullability::kNonNullable),
|
||||
scratch2_reg);
|
||||
__ BranchIf(EQUAL, &done, compiler::Assembler::kNearJump);
|
||||
}
|
||||
// Instance type isn't a top type if non-nullable.
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
scratch1_reg, static_cast<int8_t>(Nullability::kNonNullable),
|
||||
scratch2_reg);
|
||||
__ BranchIf(EQUAL, &done, compiler::Assembler::kNearJump);
|
||||
__ Bind(&is_top_type);
|
||||
__ LoadImmediate(output_reg, 0);
|
||||
__ Bind(&done);
|
||||
|
@ -786,14 +783,6 @@ static void GenerateTypeIsTopTypeForSubtyping(Assembler* assembler,
|
|||
__ Jump(&check_top_type, compiler::Assembler::kNearJump);
|
||||
}
|
||||
|
||||
void StubCodeCompiler::GenerateTypeIsTopTypeForSubtypingStub() {
|
||||
GenerateTypeIsTopTypeForSubtyping(assembler, /*null_safety=*/false);
|
||||
}
|
||||
|
||||
void StubCodeCompiler::GenerateTypeIsTopTypeForSubtypingNullSafeStub() {
|
||||
GenerateTypeIsTopTypeForSubtyping(assembler, /*null_safety=*/true);
|
||||
}
|
||||
|
||||
// Version of Instance::NullIsAssignableTo(other, inst_tav, fun_tav) used when
|
||||
// the destination type was not known at compile time. Must be kept in sync.
|
||||
//
|
||||
|
@ -811,8 +800,7 @@ void StubCodeCompiler::GenerateTypeIsTopTypeForSubtypingNullSafeStub() {
|
|||
// non-zero otherwise.
|
||||
//
|
||||
// All registers other than outputs and non-preserved scratches are preserved.
|
||||
static void GenerateNullIsAssignableToType(Assembler* assembler,
|
||||
bool null_safety) {
|
||||
void StubCodeCompiler::GenerateNullIsAssignableToTypeStub() {
|
||||
// The only case where the original value of kSubtypeTestCacheReg is needed
|
||||
// after the stub call is on IA32, where it's currently preserved on the stack
|
||||
// before calling the stub (as it's also CODE_REG on that architecture), so we
|
||||
|
@ -839,87 +827,83 @@ static void GenerateNullIsAssignableToType(Assembler* assembler,
|
|||
compiler::Label is_assignable, done;
|
||||
// Initialize the first scratch register (and thus the output register) with
|
||||
// the destination type. We do this before the check to ensure the output
|
||||
// register has a non-zero value if !null_safety and kInstanceReg is not null.
|
||||
// register has a non-zero value if kInstanceReg is not null.
|
||||
__ MoveRegister(kCurrentTypeReg, TypeTestABI::kDstTypeReg);
|
||||
__ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
|
||||
if (null_safety) {
|
||||
compiler::Label check_null_assignable;
|
||||
// Skip checking the type if not null.
|
||||
__ BranchIf(NOT_EQUAL, &done);
|
||||
__ Bind(&check_null_assignable);
|
||||
// scratch1_reg: Current type to check.
|
||||
EnsureIsTypeOrFunctionTypeOrTypeParameter(assembler, kCurrentTypeReg,
|
||||
kScratchReg);
|
||||
compiler::Label is_not_type;
|
||||
__ CompareClassId(kCurrentTypeReg, kTypeCid, kScratchReg);
|
||||
__ BranchIf(NOT_EQUAL, &is_not_type, compiler::Assembler::kNearJump);
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
kCurrentTypeReg, static_cast<int8_t>(Nullability::kNonNullable),
|
||||
kScratchReg);
|
||||
__ BranchIf(NOT_EQUAL, &is_assignable);
|
||||
// FutureOr is a special case because it may have the non-nullable bit set,
|
||||
// but FutureOr<T> functions as the union of T and Future<T>, so it must be
|
||||
// unwrapped to see if T is nullable.
|
||||
__ LoadTypeClassId(kScratchReg, kCurrentTypeReg);
|
||||
__ CompareImmediate(kScratchReg, kFutureOrCid);
|
||||
__ BranchIf(NOT_EQUAL, &done);
|
||||
__ LoadCompressedField(
|
||||
kScratchReg,
|
||||
compiler::FieldAddress(kCurrentTypeReg,
|
||||
compiler::target::Type::arguments_offset()));
|
||||
__ CompareObject(kScratchReg, Object::null_object());
|
||||
// If the arguments are null, then unwrapping gives the dynamic type,
|
||||
// which can take null.
|
||||
__ BranchIf(EQUAL, &is_assignable);
|
||||
__ LoadCompressedField(
|
||||
kCurrentTypeReg,
|
||||
compiler::FieldAddress(
|
||||
kScratchReg, compiler::target::TypeArguments::type_at_offset(0)));
|
||||
__ Jump(&check_null_assignable, compiler::Assembler::kNearJump);
|
||||
__ Bind(&is_not_type);
|
||||
// Null is assignable to a type parameter only if it is nullable or if the
|
||||
// instantiation is nullable.
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
kCurrentTypeReg, static_cast<int8_t>(Nullability::kNonNullable),
|
||||
kScratchReg);
|
||||
__ BranchIf(NOT_EQUAL, &is_assignable);
|
||||
|
||||
// Don't set kScratchReg in here as on IA32, that's the function TAV reg.
|
||||
auto handle_case = [&](Register tav) {
|
||||
// We can reuse kCurrentTypeReg to hold the index because we no longer
|
||||
// need the type parameter afterwards.
|
||||
auto const kIndexReg = kCurrentTypeReg;
|
||||
// If the TAV is null, resolving gives the (nullable) dynamic type.
|
||||
__ CompareObject(tav, NullObject());
|
||||
__ BranchIf(EQUAL, &is_assignable, Assembler::kNearJump);
|
||||
// Resolve the type parameter to its instantiated type and loop.
|
||||
__ LoadFieldFromOffset(kIndexReg, kCurrentTypeReg,
|
||||
target::TypeParameter::index_offset(),
|
||||
kUnsignedTwoBytes);
|
||||
__ LoadIndexedCompressed(kCurrentTypeReg, tav,
|
||||
target::TypeArguments::types_offset(),
|
||||
kIndexReg);
|
||||
__ Jump(&check_null_assignable);
|
||||
};
|
||||
compiler::Label check_null_assignable;
|
||||
// Skip checking the type if not null.
|
||||
__ BranchIf(NOT_EQUAL, &done);
|
||||
__ Bind(&check_null_assignable);
|
||||
// scratch1_reg: Current type to check.
|
||||
EnsureIsTypeOrFunctionTypeOrTypeParameter(assembler, kCurrentTypeReg,
|
||||
kScratchReg);
|
||||
compiler::Label is_not_type;
|
||||
__ CompareClassId(kCurrentTypeReg, kTypeCid, kScratchReg);
|
||||
__ BranchIf(NOT_EQUAL, &is_not_type, compiler::Assembler::kNearJump);
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
kCurrentTypeReg, static_cast<int8_t>(Nullability::kNonNullable),
|
||||
kScratchReg);
|
||||
__ BranchIf(NOT_EQUAL, &is_assignable);
|
||||
// FutureOr is a special case because it may have the non-nullable bit set,
|
||||
// but FutureOr<T> functions as the union of T and Future<T>, so it must be
|
||||
// unwrapped to see if T is nullable.
|
||||
__ LoadTypeClassId(kScratchReg, kCurrentTypeReg);
|
||||
__ CompareImmediate(kScratchReg, kFutureOrCid);
|
||||
__ BranchIf(NOT_EQUAL, &done);
|
||||
__ LoadCompressedField(
|
||||
kScratchReg,
|
||||
compiler::FieldAddress(kCurrentTypeReg,
|
||||
compiler::target::Type::arguments_offset()));
|
||||
__ CompareObject(kScratchReg, Object::null_object());
|
||||
// If the arguments are null, then unwrapping gives the dynamic type,
|
||||
// which can take null.
|
||||
__ BranchIf(EQUAL, &is_assignable);
|
||||
__ LoadCompressedField(
|
||||
kCurrentTypeReg,
|
||||
compiler::FieldAddress(
|
||||
kScratchReg, compiler::target::TypeArguments::type_at_offset(0)));
|
||||
__ Jump(&check_null_assignable, compiler::Assembler::kNearJump);
|
||||
__ Bind(&is_not_type);
|
||||
// Null is assignable to a type parameter only if it is nullable or if the
|
||||
// instantiation is nullable.
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
kCurrentTypeReg, static_cast<int8_t>(Nullability::kNonNullable),
|
||||
kScratchReg);
|
||||
__ BranchIf(NOT_EQUAL, &is_assignable);
|
||||
|
||||
Label function_type_param;
|
||||
__ LoadFromSlot(kScratchReg, TypeTestABI::kDstTypeReg,
|
||||
Slot::AbstractType_flags());
|
||||
__ BranchIfBit(kScratchReg,
|
||||
target::UntaggedTypeParameter::kIsFunctionTypeParameterBit,
|
||||
NOT_ZERO, &function_type_param, Assembler::kNearJump);
|
||||
handle_case(TypeTestABI::kInstantiatorTypeArgumentsReg);
|
||||
__ Bind(&function_type_param);
|
||||
// Don't set kScratchReg in here as on IA32, that's the function TAV reg.
|
||||
auto handle_case = [&](Register tav) {
|
||||
// We can reuse kCurrentTypeReg to hold the index because we no longer
|
||||
// need the type parameter afterwards.
|
||||
auto const kIndexReg = kCurrentTypeReg;
|
||||
// If the TAV is null, resolving gives the (nullable) dynamic type.
|
||||
__ CompareObject(tav, NullObject());
|
||||
__ BranchIf(EQUAL, &is_assignable, Assembler::kNearJump);
|
||||
// Resolve the type parameter to its instantiated type and loop.
|
||||
__ LoadFieldFromOffset(kIndexReg, kCurrentTypeReg,
|
||||
target::TypeParameter::index_offset(),
|
||||
kUnsignedTwoBytes);
|
||||
__ LoadIndexedCompressed(kCurrentTypeReg, tav,
|
||||
target::TypeArguments::types_offset(), kIndexReg);
|
||||
__ Jump(&check_null_assignable);
|
||||
};
|
||||
|
||||
Label function_type_param;
|
||||
__ LoadFromSlot(kScratchReg, TypeTestABI::kDstTypeReg,
|
||||
Slot::AbstractType_flags());
|
||||
__ BranchIfBit(kScratchReg,
|
||||
target::UntaggedTypeParameter::kIsFunctionTypeParameterBit,
|
||||
NOT_ZERO, &function_type_param, Assembler::kNearJump);
|
||||
handle_case(TypeTestABI::kInstantiatorTypeArgumentsReg);
|
||||
__ Bind(&function_type_param);
|
||||
#if defined(TARGET_ARCH_IA32)
|
||||
// Function TAV is on top of stack because we're using that register as
|
||||
// kScratchReg.
|
||||
__ LoadFromStack(TypeTestABI::kFunctionTypeArgumentsReg, 0);
|
||||
// Function TAV is on top of stack because we're using that register as
|
||||
// kScratchReg.
|
||||
__ LoadFromStack(TypeTestABI::kFunctionTypeArgumentsReg, 0);
|
||||
#endif
|
||||
handle_case(TypeTestABI::kFunctionTypeArgumentsReg);
|
||||
} else {
|
||||
// Null in non-null-safe mode is always assignable.
|
||||
__ BranchIf(NOT_EQUAL, &done, compiler::Assembler::kNearJump);
|
||||
}
|
||||
handle_case(TypeTestABI::kFunctionTypeArgumentsReg);
|
||||
|
||||
__ Bind(&is_assignable);
|
||||
__ LoadImmediate(kOutputReg, 0);
|
||||
__ Bind(&done);
|
||||
|
@ -930,13 +914,6 @@ static void GenerateNullIsAssignableToType(Assembler* assembler,
|
|||
__ Ret();
|
||||
}
|
||||
|
||||
void StubCodeCompiler::GenerateNullIsAssignableToTypeStub() {
|
||||
GenerateNullIsAssignableToType(assembler, /*null_safety=*/false);
|
||||
}
|
||||
|
||||
void StubCodeCompiler::GenerateNullIsAssignableToTypeNullSafeStub() {
|
||||
GenerateNullIsAssignableToType(assembler, /*null_safety=*/true);
|
||||
}
|
||||
#if !defined(TARGET_ARCH_IA32)
|
||||
// The <X>TypeTestStubs are used to test whether a given value is of a given
|
||||
// type. All variants have the same calling convention:
|
||||
|
|
|
@ -109,9 +109,7 @@ constexpr bool FLAG_support_il_printer = false;
|
|||
P(enable_asserts, bool, false, "Enable assert statements.") \
|
||||
P(inline_alloc, bool, true, "Whether to use inline allocation fast paths.") \
|
||||
R(null_assertions, false, bool, false, "Obsolete, ignored.") \
|
||||
R(strict_null_safety_checks, false, bool, false, \
|
||||
"Enable strict type checks for non-nullable types and required " \
|
||||
"parameters.") \
|
||||
R(strict_null_safety_checks, false, bool, false, "Obsolete, ignored.") \
|
||||
P(enable_mirrors, bool, true, \
|
||||
"Disable to make importing dart:mirrors an error.") \
|
||||
P(enable_ffi, bool, true, "Disable to make importing dart:ffi an error.") \
|
||||
|
|
|
@ -469,10 +469,6 @@ class IsolateGroup : public IntrusiveDListEntry<IsolateGroup> {
|
|||
NullSafetyBit::update(null_safety, isolate_group_flags_);
|
||||
}
|
||||
|
||||
bool use_strict_null_safety_checks() const {
|
||||
return null_safety() || FLAG_strict_null_safety_checks;
|
||||
}
|
||||
|
||||
bool should_load_vmservice() const {
|
||||
return ShouldLoadVmServiceBit::decode(isolate_group_flags_);
|
||||
}
|
||||
|
|
|
@ -9466,7 +9466,6 @@ bool Function::AreValidArguments(const ArgumentsDescriptor& args_desc,
|
|||
}
|
||||
// Verify that all argument names are valid parameter names.
|
||||
Thread* thread = Thread::Current();
|
||||
auto isolate_group = thread->isolate_group();
|
||||
Zone* zone = thread->zone();
|
||||
String& argument_name = String::Handle(zone);
|
||||
String& parameter_name = String::Handle(zone);
|
||||
|
@ -9496,33 +9495,31 @@ bool Function::AreValidArguments(const ArgumentsDescriptor& args_desc,
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (isolate_group->use_strict_null_safety_checks()) {
|
||||
// Verify that all required named parameters are filled.
|
||||
for (intptr_t j = num_parameters - NumOptionalNamedParameters();
|
||||
j < num_parameters; j++) {
|
||||
if (IsRequiredAt(j)) {
|
||||
parameter_name = ParameterNameAt(j);
|
||||
ASSERT(parameter_name.IsSymbol());
|
||||
bool found = false;
|
||||
for (intptr_t i = 0; i < num_named_arguments; i++) {
|
||||
argument_name = args_desc.NameAt(i);
|
||||
ASSERT(argument_name.IsSymbol());
|
||||
if (argument_name.Equals(parameter_name)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
// Verify that all required named parameters are filled.
|
||||
for (intptr_t j = num_parameters - NumOptionalNamedParameters();
|
||||
j < num_parameters; j++) {
|
||||
if (IsRequiredAt(j)) {
|
||||
parameter_name = ParameterNameAt(j);
|
||||
ASSERT(parameter_name.IsSymbol());
|
||||
bool found = false;
|
||||
for (intptr_t i = 0; i < num_named_arguments; i++) {
|
||||
argument_name = args_desc.NameAt(i);
|
||||
ASSERT(argument_name.IsSymbol());
|
||||
if (argument_name.Equals(parameter_name)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (!found) {
|
||||
if (error_message != nullptr) {
|
||||
const intptr_t kMessageBufferSize = 64;
|
||||
char message_buffer[kMessageBufferSize];
|
||||
Utils::SNPrint(message_buffer, kMessageBufferSize,
|
||||
"missing required named parameter '%s'",
|
||||
parameter_name.ToCString());
|
||||
*error_message = String::New(message_buffer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!found) {
|
||||
if (error_message != nullptr) {
|
||||
const intptr_t kMessageBufferSize = 64;
|
||||
char message_buffer[kMessageBufferSize];
|
||||
Utils::SNPrint(message_buffer, kMessageBufferSize,
|
||||
"missing required named parameter '%s'",
|
||||
parameter_name.ToCString());
|
||||
*error_message = String::New(message_buffer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10243,7 +10240,6 @@ bool FunctionType::IsSubtypeOf(
|
|||
}
|
||||
Thread* thread = Thread::Current();
|
||||
Zone* zone = thread->zone();
|
||||
auto isolate_group = thread->isolate_group();
|
||||
FunctionTypeMapping scope(zone, &function_type_equivalence, *this, other);
|
||||
|
||||
// Check the type parameters and bounds of generic functions.
|
||||
|
@ -10309,32 +10305,30 @@ bool FunctionType::IsSubtypeOf(
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (isolate_group->use_strict_null_safety_checks()) {
|
||||
// Check that for each required named parameter in this function, there's a
|
||||
// corresponding required named parameter in the other function.
|
||||
String& param_name = other_param_name;
|
||||
for (intptr_t j = num_params - num_opt_named_params; j < num_params; j++) {
|
||||
if (IsRequiredAt(j)) {
|
||||
param_name = ParameterNameAt(j);
|
||||
ASSERT(param_name.IsSymbol());
|
||||
bool found = false;
|
||||
for (intptr_t i = other_num_fixed_params; i < other_num_params; i++) {
|
||||
ASSERT(String::Handle(zone, other.ParameterNameAt(i)).IsSymbol());
|
||||
if (other.ParameterNameAt(i) == param_name.ptr()) {
|
||||
found = true;
|
||||
if (!other.IsRequiredAt(i)) {
|
||||
TRACE_TYPE_CHECKS_VERBOSE(
|
||||
" - result: false (mismatch in required named "
|
||||
"parameters)\n");
|
||||
return false;
|
||||
}
|
||||
// Check that for each required named parameter in this function, there's a
|
||||
// corresponding required named parameter in the other function.
|
||||
String& param_name = other_param_name;
|
||||
for (intptr_t j = num_params - num_opt_named_params; j < num_params; j++) {
|
||||
if (IsRequiredAt(j)) {
|
||||
param_name = ParameterNameAt(j);
|
||||
ASSERT(param_name.IsSymbol());
|
||||
bool found = false;
|
||||
for (intptr_t i = other_num_fixed_params; i < other_num_params; i++) {
|
||||
ASSERT(String::Handle(zone, other.ParameterNameAt(i)).IsSymbol());
|
||||
if (other.ParameterNameAt(i) == param_name.ptr()) {
|
||||
found = true;
|
||||
if (!other.IsRequiredAt(i)) {
|
||||
TRACE_TYPE_CHECKS_VERBOSE(
|
||||
" - result: false (mismatch in required named "
|
||||
"parameters)\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
TRACE_TYPE_CHECKS_VERBOSE(
|
||||
" - result: false (required named parameter not found)\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
TRACE_TYPE_CHECKS_VERBOSE(
|
||||
" - result: false (required named parameter not found)\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20729,10 +20723,6 @@ bool Instance::IsAssignableTo(
|
|||
const TypeArguments& other_instantiator_type_arguments,
|
||||
const TypeArguments& other_function_type_arguments) const {
|
||||
ASSERT(!other.IsDynamicType());
|
||||
// In weak mode type casts, whether in legacy or opted-in libraries, the null
|
||||
// instance is detected and handled in inlined code and therefore cannot be
|
||||
// encountered here as a Dart null receiver.
|
||||
ASSERT(IsolateGroup::Current()->use_strict_null_safety_checks() || !IsNull());
|
||||
// In strong mode, compute NNBD_SUBTYPE(runtimeType, other).
|
||||
// In weak mode, compute LEGACY_SUBTYPE(runtimeType, other).
|
||||
return RuntimeTypeIsSubtypeOf(other, other_instantiator_type_arguments,
|
||||
|
@ -20774,13 +20764,6 @@ bool Instance::NullIsInstanceOf(
|
|||
// Must be kept in sync with GenerateNullIsAssignableToType in
|
||||
// stub_code_compiler.cc if any changes are made.
|
||||
bool Instance::NullIsAssignableTo(const AbstractType& other) {
|
||||
Thread* thread = Thread::Current();
|
||||
auto isolate_group = thread->isolate_group();
|
||||
|
||||
// In weak mode, Null is a bottom type (according to LEGACY_SUBTYPE).
|
||||
if (!isolate_group->use_strict_null_safety_checks()) {
|
||||
return true;
|
||||
}
|
||||
// "Left Null" rule: null is assignable when destination type is either
|
||||
// legacy or nullable. Otherwise it is not assignable or we cannot tell
|
||||
// without instantiating type parameter.
|
||||
|
@ -20788,8 +20771,7 @@ bool Instance::NullIsAssignableTo(const AbstractType& other) {
|
|||
return true;
|
||||
}
|
||||
if (other.IsFutureOrType()) {
|
||||
return NullIsAssignableTo(
|
||||
AbstractType::Handle(thread->zone(), other.UnwrapFutureOr()));
|
||||
return NullIsAssignableTo(AbstractType::Handle(other.UnwrapFutureOr()));
|
||||
}
|
||||
// Since the TAVs are not available, for non-nullable type parameters
|
||||
// this returns a conservative approximation of "not assignable" .
|
||||
|
@ -20822,11 +20804,6 @@ bool Instance::RuntimeTypeIsSubtypeOf(
|
|||
return true;
|
||||
}
|
||||
Thread* thread = Thread::Current();
|
||||
auto isolate_group = thread->isolate_group();
|
||||
// In weak testing mode, Null type is a subtype of any type.
|
||||
if (IsNull() && !isolate_group->use_strict_null_safety_checks()) {
|
||||
return true;
|
||||
}
|
||||
Zone* zone = thread->zone();
|
||||
const Class& cls = Class::Handle(zone, clazz());
|
||||
if (cls.IsClosureClass()) {
|
||||
|
@ -20921,7 +20898,6 @@ bool Instance::RuntimeTypeIsSubtypeOf(
|
|||
}
|
||||
}
|
||||
if (IsNull()) {
|
||||
ASSERT(isolate_group->use_strict_null_safety_checks());
|
||||
if (instantiated_other.IsNullType()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -21328,8 +21304,7 @@ bool AbstractType::IsNullabilityEquivalent(Thread* thread,
|
|||
Nullability this_type_nullability = nullability();
|
||||
Nullability other_type_nullability = other_type.nullability();
|
||||
if (kind == TypeEquality::kInSubtypeTest) {
|
||||
if (thread->isolate_group()->use_strict_null_safety_checks() &&
|
||||
this_type_nullability == Nullability::kNullable &&
|
||||
if (this_type_nullability == Nullability::kNullable &&
|
||||
other_type_nullability == Nullability::kNonNullable) {
|
||||
return false;
|
||||
}
|
||||
|
@ -21550,10 +21525,7 @@ bool AbstractType::IsTopTypeForSubtyping() const {
|
|||
return true;
|
||||
}
|
||||
if (cid == kInstanceCid) { // Object type.
|
||||
// NNBD weak mode uses LEGACY_SUBTYPE for assignability / 'as' tests,
|
||||
// and non-nullable Object is a top type according to LEGACY_SUBTYPE.
|
||||
return !IsNonNullable() ||
|
||||
!IsolateGroup::Current()->use_strict_null_safety_checks();
|
||||
return !IsNonNullable();
|
||||
}
|
||||
if (cid == kFutureOrCid) {
|
||||
// FutureOr<T> where T is a top type behaves as a top type.
|
||||
|
@ -21742,7 +21714,6 @@ bool AbstractType::IsSubtypeOf(
|
|||
return result;
|
||||
}
|
||||
Thread* thread = Thread::Current();
|
||||
auto isolate_group = thread->isolate_group();
|
||||
Zone* zone = thread->zone();
|
||||
// Type parameters cannot be handled by Class::IsSubtypeOf().
|
||||
// When comparing two uninstantiated function types, one returning type
|
||||
|
@ -21794,16 +21765,14 @@ bool AbstractType::IsSubtypeOf(
|
|||
// Any type that can be the type of a closure is a subtype of Function or
|
||||
// non-nullable Object.
|
||||
if (other.IsObjectType() || other.IsDartFunctionType()) {
|
||||
const bool result = !isolate_group->use_strict_null_safety_checks() ||
|
||||
!IsNullable() || !other.IsNonNullable();
|
||||
const bool result = !IsNullable() || !other.IsNonNullable();
|
||||
TRACE_TYPE_CHECKS_VERBOSE(" - result: %s (function vs non-function)\n",
|
||||
(result ? "true" : "false"));
|
||||
return result;
|
||||
}
|
||||
if (other.IsFunctionType()) {
|
||||
// Check for two function types.
|
||||
if (isolate_group->use_strict_null_safety_checks() && IsNullable() &&
|
||||
other.IsNonNullable()) {
|
||||
if (IsNullable() && other.IsNonNullable()) {
|
||||
TRACE_TYPE_CHECKS_VERBOSE(
|
||||
" - result: false (function nullability)\n");
|
||||
return false;
|
||||
|
@ -21833,16 +21802,14 @@ bool AbstractType::IsSubtypeOf(
|
|||
// Record types cannot be handled by Class::IsSubtypeOf().
|
||||
if (IsRecordType()) {
|
||||
if (other.IsObjectType() || other.IsDartRecordType()) {
|
||||
const bool result = !isolate_group->use_strict_null_safety_checks() ||
|
||||
!IsNullable() || !other.IsNonNullable();
|
||||
const bool result = !IsNullable() || !other.IsNonNullable();
|
||||
TRACE_TYPE_CHECKS_VERBOSE(" - result: %s (record vs non-record)\n",
|
||||
(result ? "true" : "false"));
|
||||
return result;
|
||||
}
|
||||
if (other.IsRecordType()) {
|
||||
// Check for two record types.
|
||||
if (isolate_group->use_strict_null_safety_checks() && IsNullable() &&
|
||||
other.IsNonNullable()) {
|
||||
if (IsNullable() && other.IsNonNullable()) {
|
||||
TRACE_TYPE_CHECKS_VERBOSE(" - result: false (record nullability)\n");
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -7464,12 +7464,7 @@ ISOLATE_UNIT_TEST_CASE(ClosureType_SubtypeOfFunctionType) {
|
|||
EXPECT_SUBTYPE(closure_type_legacy, function_type_nonnullable);
|
||||
EXPECT_SUBTYPE(closure_type_nullable, function_type_nullable);
|
||||
EXPECT_SUBTYPE(closure_type_nullable, function_type_legacy);
|
||||
// Nullable types are not a subtype of non-nullable types in strict mode.
|
||||
if (IsolateGroup::Current()->use_strict_null_safety_checks()) {
|
||||
EXPECT_NOT_SUBTYPE(closure_type_nullable, function_type_nonnullable);
|
||||
} else {
|
||||
EXPECT_SUBTYPE(closure_type_nullable, function_type_nonnullable);
|
||||
}
|
||||
EXPECT_NOT_SUBTYPE(closure_type_nullable, function_type_nonnullable);
|
||||
|
||||
const auto& async_lib = Library::Handle(Library::AsyncLibrary());
|
||||
const auto& future_or_class =
|
||||
|
@ -7502,13 +7497,8 @@ ISOLATE_UNIT_TEST_CASE(ClosureType_SubtypeOfFunctionType) {
|
|||
EXPECT_SUBTYPE(closure_type_legacy, future_or_function_type_nonnullable);
|
||||
EXPECT_SUBTYPE(closure_type_nullable, future_or_function_type_nullable);
|
||||
EXPECT_SUBTYPE(closure_type_nullable, future_or_function_type_legacy);
|
||||
// Nullable types are not a subtype of non-nullable types in strict mode.
|
||||
if (IsolateGroup::Current()->use_strict_null_safety_checks()) {
|
||||
EXPECT_NOT_SUBTYPE(closure_type_nullable,
|
||||
future_or_function_type_nonnullable);
|
||||
} else {
|
||||
EXPECT_SUBTYPE(closure_type_nullable, future_or_function_type_nonnullable);
|
||||
}
|
||||
EXPECT_NOT_SUBTYPE(closure_type_nullable,
|
||||
future_or_function_type_nonnullable);
|
||||
}
|
||||
|
||||
ISOLATE_UNIT_TEST_CASE(FunctionType_IsSubtypeOfNonNullableObject) {
|
||||
|
@ -7526,11 +7516,7 @@ ISOLATE_UNIT_TEST_CASE(FunctionType_IsSubtypeOfNonNullableObject) {
|
|||
FinalizeAndCanonicalize(&type_nullable_function_int_nullary);
|
||||
|
||||
EXPECT_SUBTYPE(type_function_int_nullary, type_object);
|
||||
if (IsolateGroup::Current()->use_strict_null_safety_checks()) {
|
||||
EXPECT_NOT_SUBTYPE(type_nullable_function_int_nullary, type_object);
|
||||
} else {
|
||||
EXPECT_SUBTYPE(type_nullable_function_int_nullary, type_object);
|
||||
}
|
||||
EXPECT_NOT_SUBTYPE(type_nullable_function_int_nullary, type_object);
|
||||
}
|
||||
|
||||
#undef EXPECT_NOT_SUBTYPE
|
||||
|
|
|
@ -1204,10 +1204,8 @@ DEFINE_RUNTIME_ENTRY(TypeCheck, 7) {
|
|||
}
|
||||
#endif // defined(TARGET_ARCH_IA32)
|
||||
|
||||
// These are guaranteed on the calling side.
|
||||
// This is guaranteed on the calling side.
|
||||
ASSERT(!dst_type.IsDynamicType());
|
||||
ASSERT(!src_instance.IsNull() ||
|
||||
isolate->group()->use_strict_null_safety_checks());
|
||||
|
||||
const bool is_instance_of = src_instance.IsAssignableTo(
|
||||
dst_type, instantiator_type_arguments, function_type_arguments);
|
||||
|
|
|
@ -107,9 +107,7 @@ namespace dart {
|
|||
V(AssertSubtype) \
|
||||
V(AssertAssignable) \
|
||||
V(TypeIsTopTypeForSubtyping) \
|
||||
V(TypeIsTopTypeForSubtypingNullSafe) \
|
||||
V(NullIsAssignableToType) \
|
||||
V(NullIsAssignableToTypeNullSafe) \
|
||||
V(Subtype1TestCache) \
|
||||
V(Subtype2TestCache) \
|
||||
V(Subtype3TestCache) \
|
||||
|
|
|
@ -343,8 +343,7 @@ void TypeTestingStubGenerator::BuildOptimizedTypeTestStubFastCases(
|
|||
ASSERT(!type.IsTopTypeForSubtyping());
|
||||
|
||||
if (type.IsObjectType()) {
|
||||
ASSERT(type.IsNonNullable() &&
|
||||
hi->thread()->isolate_group()->use_strict_null_safety_checks());
|
||||
ASSERT(type.IsNonNullable());
|
||||
compiler::Label is_null;
|
||||
__ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
|
||||
__ BranchIf(EQUAL, &is_null, compiler::Assembler::kNearJump);
|
||||
|
@ -1084,8 +1083,6 @@ void TypeTestingStubGenerator::BuildOptimizedTypeParameterArgumentValueCheck(
|
|||
? TypeTestABI::kInstantiatorTypeArgumentsReg
|
||||
: TypeTestABI::kFunctionTypeArgumentsReg;
|
||||
|
||||
const bool strict_null_safety =
|
||||
hi->thread()->isolate_group()->use_strict_null_safety_checks();
|
||||
compiler::Label is_subtype;
|
||||
// TODO(dartbug.com/46920): Currently only canonical equality (identity)
|
||||
// and some top and bottom types are checked.
|
||||
|
@ -1117,24 +1114,20 @@ void TypeTestingStubGenerator::BuildOptimizedTypeParameterArgumentValueCheck(
|
|||
__ CompareImmediate(TTSInternalRegs::kScratchReg, kVoidCid);
|
||||
__ BranchIf(EQUAL, &is_subtype);
|
||||
__ CompareImmediate(TTSInternalRegs::kScratchReg, kInstanceCid);
|
||||
if (strict_null_safety) {
|
||||
__ BranchIf(NOT_EQUAL, &check_subtype_type_class_ids);
|
||||
// If non-nullable Object, then the subtype must be legacy or non-nullable.
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
TTSInternalRegs::kSuperTypeArgumentReg,
|
||||
static_cast<int8_t>(Nullability::kNonNullable),
|
||||
TTSInternalRegs::kScratchReg);
|
||||
__ BranchIf(NOT_EQUAL, &is_subtype);
|
||||
__ Comment("Checking for legacy or non-nullable instance type argument");
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
TTSInternalRegs::kSubTypeArgumentReg,
|
||||
static_cast<int8_t>(Nullability::kNullable),
|
||||
TTSInternalRegs::kScratchReg);
|
||||
__ BranchIf(EQUAL, check_failed);
|
||||
__ Jump(&is_subtype);
|
||||
} else {
|
||||
__ BranchIf(EQUAL, &is_subtype, compiler::Assembler::kNearJump);
|
||||
}
|
||||
__ BranchIf(NOT_EQUAL, &check_subtype_type_class_ids);
|
||||
// If non-nullable Object, then the subtype must be legacy or non-nullable.
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
TTSInternalRegs::kSuperTypeArgumentReg,
|
||||
static_cast<int8_t>(Nullability::kNonNullable),
|
||||
TTSInternalRegs::kScratchReg);
|
||||
__ BranchIf(NOT_EQUAL, &is_subtype);
|
||||
__ Comment("Checking for legacy or non-nullable instance type argument");
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
TTSInternalRegs::kSubTypeArgumentReg,
|
||||
static_cast<int8_t>(Nullability::kNullable),
|
||||
TTSInternalRegs::kScratchReg);
|
||||
__ BranchIf(EQUAL, check_failed);
|
||||
__ Jump(&is_subtype);
|
||||
|
||||
__ Bind(&check_subtype_type_class_ids);
|
||||
__ Comment("Checking instance type argument for possible bottom types");
|
||||
|
@ -1150,15 +1143,13 @@ void TypeTestingStubGenerator::BuildOptimizedTypeParameterArgumentValueCheck(
|
|||
__ CompareImmediate(TTSInternalRegs::kScratchReg, kNullCid);
|
||||
// Last possible check, so fall back to slow stub on failure.
|
||||
__ BranchIf(NOT_EQUAL, check_failed);
|
||||
if (strict_null_safety) {
|
||||
// Only nullable or legacy types can be a supertype of Null.
|
||||
__ Comment("Checking for legacy or nullable instantiated type parameter");
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
TTSInternalRegs::kSuperTypeArgumentReg,
|
||||
static_cast<int8_t>(Nullability::kNonNullable),
|
||||
TTSInternalRegs::kScratchReg);
|
||||
__ BranchIf(EQUAL, check_failed);
|
||||
}
|
||||
// Only nullable or legacy types can be a supertype of Null.
|
||||
__ Comment("Checking for legacy or nullable instantiated type parameter");
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
TTSInternalRegs::kSuperTypeArgumentReg,
|
||||
static_cast<int8_t>(Nullability::kNonNullable),
|
||||
TTSInternalRegs::kScratchReg);
|
||||
__ BranchIf(EQUAL, check_failed);
|
||||
|
||||
__ Bind(&is_subtype);
|
||||
}
|
||||
|
@ -1176,9 +1167,7 @@ void TypeTestingStubGenerator::BuildOptimizedTypeArgumentValueCheck(
|
|||
return;
|
||||
}
|
||||
|
||||
const bool strict_null_safety =
|
||||
hi->thread()->isolate_group()->use_strict_null_safety_checks();
|
||||
ASSERT(!type.IsObjectType() || (strict_null_safety && type.IsNonNullable()));
|
||||
ASSERT(!type.IsObjectType() || type.IsNonNullable());
|
||||
|
||||
if (assembler->EmittingComments()) {
|
||||
TextBuffer buffer(128);
|
||||
|
@ -1211,7 +1200,7 @@ void TypeTestingStubGenerator::BuildOptimizedTypeArgumentValueCheck(
|
|||
} else {
|
||||
__ Comment("Checks for Object type");
|
||||
}
|
||||
if (strict_null_safety && type.IsNonNullable()) {
|
||||
if (type.IsNonNullable()) {
|
||||
// Nullable types cannot be a subtype of a non-nullable type.
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
TTSInternalRegs::kSubTypeArgumentReg,
|
||||
|
@ -1230,7 +1219,7 @@ void TypeTestingStubGenerator::BuildOptimizedTypeArgumentValueCheck(
|
|||
|
||||
__ Comment("Checks for Type");
|
||||
__ Bind(&sub_is_type);
|
||||
if (strict_null_safety && type.IsNonNullable()) {
|
||||
if (type.IsNonNullable()) {
|
||||
// Nullable types cannot be a subtype of a non-nullable type in strict mode.
|
||||
__ CompareAbstractTypeNullabilityWith(
|
||||
TTSInternalRegs::kSubTypeArgumentReg,
|
||||
|
|
|
@ -1659,9 +1659,6 @@ ISOLATE_UNIT_TEST_CASE(TTS_Future) {
|
|||
THR_Print(" Testing Future<int Function()?>\n");
|
||||
THR_Print("********************************************************\n\n");
|
||||
|
||||
const bool strict_null_safety =
|
||||
thread->isolate_group()->use_strict_null_safety_checks();
|
||||
|
||||
// And here, obj is an object of type Future<int Function()?>.
|
||||
//
|
||||
// True positive from TTS:
|
||||
|
@ -1705,15 +1702,6 @@ ISOLATE_UNIT_TEST_CASE(TTS_Future) {
|
|||
RunTTSTest(type_future_t, {obj_futurenullablefunction,
|
||||
tav_nullable_function_int_nullary, tav_null});
|
||||
|
||||
if (!strict_null_safety) {
|
||||
RunTTSTest(type_future_object,
|
||||
{obj_futurenullablefunction, tav_null, tav_null});
|
||||
RunTTSTest(type_future_function,
|
||||
{obj_futurenullablefunction, tav_null, tav_null});
|
||||
RunTTSTest(type_future_t,
|
||||
{obj_futurenullablefunction, tav_object, tav_null});
|
||||
}
|
||||
|
||||
// False negative from TTS (caught by runtime or STC):
|
||||
// obj as Future<int Function()?> : No specialization.
|
||||
// obj as Future<int Function()*> : No specialization.
|
||||
|
@ -1740,16 +1728,6 @@ ISOLATE_UNIT_TEST_CASE(TTS_Future) {
|
|||
FalseNegative({obj_futurenullablefunction,
|
||||
tav_legacy_function_int_nullary, tav_null}));
|
||||
|
||||
if (!strict_null_safety) {
|
||||
RunTTSTest(type_future_function_int_nullary,
|
||||
FalseNegative({obj_futurenullablefunction, tav_null, tav_null}));
|
||||
RunTTSTest(type_future_t, FalseNegative({obj_futurenullablefunction,
|
||||
tav_function, tav_null}));
|
||||
RunTTSTest(type_future_t,
|
||||
FalseNegative({obj_futurenullablefunction,
|
||||
tav_function_int_nullary, tav_null}));
|
||||
}
|
||||
|
||||
// Errors:
|
||||
// obj as Future<_Closure?> : Type arg is not a supertype
|
||||
// obj as Future<_Closure*> : Type arg is not a supertype
|
||||
|
@ -1784,20 +1762,18 @@ ISOLATE_UNIT_TEST_CASE(TTS_Future) {
|
|||
RunTTSTest(type_future_t,
|
||||
Failure({obj_futurenullablefunction, tav_closure, tav_null}));
|
||||
|
||||
if (strict_null_safety) {
|
||||
RunTTSTest(type_future_function_int_nullary,
|
||||
Failure({obj_futurenullablefunction, tav_null, tav_null}));
|
||||
RunTTSTest(type_future_object,
|
||||
Failure({obj_futurenullablefunction, tav_null, tav_null}));
|
||||
RunTTSTest(type_future_function,
|
||||
Failure({obj_futurenullablefunction, tav_null, tav_null}));
|
||||
RunTTSTest(type_future_t,
|
||||
Failure({obj_futurenullablefunction, tav_object, tav_null}));
|
||||
RunTTSTest(type_future_t,
|
||||
Failure({obj_futurenullablefunction, tav_function, tav_null}));
|
||||
RunTTSTest(type_future_t, Failure({obj_futurenullablefunction,
|
||||
tav_function_int_nullary, tav_null}));
|
||||
}
|
||||
RunTTSTest(type_future_function_int_nullary,
|
||||
Failure({obj_futurenullablefunction, tav_null, tav_null}));
|
||||
RunTTSTest(type_future_object,
|
||||
Failure({obj_futurenullablefunction, tav_null, tav_null}));
|
||||
RunTTSTest(type_future_function,
|
||||
Failure({obj_futurenullablefunction, tav_null, tav_null}));
|
||||
RunTTSTest(type_future_t,
|
||||
Failure({obj_futurenullablefunction, tav_object, tav_null}));
|
||||
RunTTSTest(type_future_t,
|
||||
Failure({obj_futurenullablefunction, tav_function, tav_null}));
|
||||
RunTTSTest(type_future_t, Failure({obj_futurenullablefunction,
|
||||
tav_function_int_nullary, tav_null}));
|
||||
}
|
||||
|
||||
ISOLATE_UNIT_TEST_CASE(TTS_Regress40964) {
|
||||
|
@ -1953,10 +1929,9 @@ ISOLATE_UNIT_TEST_CASE(TTS_Object) {
|
|||
Type::Handle(IsolateGroup::Current()->object_store()->object_type());
|
||||
const auto& tav_null = Object::null_type_arguments();
|
||||
|
||||
// Object* is a top type, so its TTS won't specialize at all. Object is not,
|
||||
// Non-nullable Object is not a top type,
|
||||
// so its TTS specializes the first time it is invoked.
|
||||
const bool should_specialize =
|
||||
IsolateGroup::Current()->use_strict_null_safety_checks();
|
||||
const bool should_specialize = true;
|
||||
auto make_test_case = [&](const Instance& instance) -> TTSTestCase {
|
||||
return {instance, tav_null, tav_null};
|
||||
};
|
||||
|
@ -2136,22 +2111,14 @@ ISOLATE_UNIT_TEST_CASE(TTS_Partial) {
|
|||
state.InvokeExistingStub({obj_b_never, tav_null, tav_d});
|
||||
state.InvokeExistingStub({obj_b_null, tav_null, tav_nullable_c});
|
||||
state.InvokeExistingStub({obj_b_null, tav_null, tav_legacy_c});
|
||||
if (IsolateGroup::Current()->use_strict_null_safety_checks()) {
|
||||
state.InvokeExistingStub(Failure({obj_b_null, tav_null, tav_c}));
|
||||
} else {
|
||||
state.InvokeExistingStub({obj_b_null, tav_null, tav_c});
|
||||
}
|
||||
state.InvokeExistingStub(Failure({obj_b_null, tav_null, tav_c}));
|
||||
|
||||
state.InvokeExistingStub({obj_b_e, tav_null, tav_nullable_object});
|
||||
state.InvokeExistingStub({obj_b_e_nullable, tav_null, tav_nullable_object});
|
||||
state.InvokeExistingStub({obj_b_e, tav_null, tav_legacy_object});
|
||||
state.InvokeExistingStub({obj_b_e_nullable, tav_null, tav_legacy_object});
|
||||
state.InvokeExistingStub({obj_b_e, tav_null, tav_object});
|
||||
if (IsolateGroup::Current()->use_strict_null_safety_checks()) {
|
||||
state.InvokeExistingStub(Failure({obj_b_e_nullable, tav_null, tav_object}));
|
||||
} else {
|
||||
state.InvokeExistingStub({obj_b_e_nullable, tav_null, tav_object});
|
||||
}
|
||||
state.InvokeExistingStub(Failure({obj_b_e_nullable, tav_null, tav_object}));
|
||||
}
|
||||
|
||||
ISOLATE_UNIT_TEST_CASE(TTS_Partial_Incremental) {
|
||||
|
|
Loading…
Reference in a new issue