[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:
Alexander Markov 2024-04-10 19:19:31 +00:00 committed by Commit Queue
parent 1dcfc3f938
commit a6ded2fd08
16 changed files with 189 additions and 486 deletions

View file

@ -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",

View file

@ -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();
}
}

View file

@ -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.

View file

@ -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,

View file

@ -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()));
}

View file

@ -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,

View file

@ -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;

View file

@ -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:

View file

@ -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.") \

View file

@ -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_);
}

View file

@ -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;
}

View file

@ -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

View file

@ -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);

View file

@ -107,9 +107,7 @@ namespace dart {
V(AssertSubtype) \
V(AssertAssignable) \
V(TypeIsTopTypeForSubtyping) \
V(TypeIsTopTypeForSubtypingNullSafe) \
V(NullIsAssignableToType) \
V(NullIsAssignableToTypeNullSafe) \
V(Subtype1TestCache) \
V(Subtype2TestCache) \
V(Subtype3TestCache) \

View file

@ -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,

View file

@ -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) {