mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
[vm/nnbd] Non-null assertions in NNBD weak mode
Fixes https://github.com/dart-lang/sdk/issues/42357 Change-Id: I07341ba02361201ec2130fb7b6cb87a25b9b5f51 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/151662 Commit-Queue: Alexander Markov <alexmarkov@google.com> Reviewed-by: Leaf Petersen <leafp@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Régis Crelier <regis@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
parent
db8a105de7
commit
7bbf2921db
|
@ -107,6 +107,42 @@ DEFINE_NATIVE_ENTRY(AssertionError_throwNew, 0, 3) {
|
|||
return Object::null();
|
||||
}
|
||||
|
||||
// Allocate and throw a new AssertionError.
|
||||
// Arg0: Source code snippet of failed assertion.
|
||||
// Arg1: Line number.
|
||||
// Arg2: Column number.
|
||||
// Arg3: Message object or null.
|
||||
// Return value: none, throws an exception.
|
||||
DEFINE_NATIVE_ENTRY(AssertionError_throwNewSource, 0, 4) {
|
||||
// No need to type check the arguments. This function can only be called
|
||||
// internally from the VM.
|
||||
const String& failed_assertion =
|
||||
String::CheckedHandle(zone, arguments->NativeArgAt(0));
|
||||
const intptr_t line =
|
||||
Smi::CheckedHandle(zone, arguments->NativeArgAt(1)).Value();
|
||||
const intptr_t column =
|
||||
Smi::CheckedHandle(zone, arguments->NativeArgAt(2)).Value();
|
||||
const Instance& message =
|
||||
Instance::CheckedHandle(zone, arguments->NativeArgAt(3));
|
||||
|
||||
const Array& args = Array::Handle(zone, Array::New(5));
|
||||
|
||||
DartFrameIterator iterator(thread,
|
||||
StackFrameIterator::kNoCrossThreadIteration);
|
||||
iterator.NextFrame(); // Skip native call.
|
||||
const Script& script = Script::Handle(zone, FindScript(&iterator));
|
||||
|
||||
args.SetAt(0, failed_assertion);
|
||||
args.SetAt(1, String::Handle(zone, script.url()));
|
||||
args.SetAt(2, Smi::Handle(zone, Smi::New(line)));
|
||||
args.SetAt(3, Smi::Handle(zone, Smi::New(column)));
|
||||
args.SetAt(4, message);
|
||||
|
||||
Exceptions::ThrowByType(Exceptions::kAssertion, args);
|
||||
UNREACHABLE();
|
||||
return Object::null();
|
||||
}
|
||||
|
||||
// Allocate and throw a new TypeError or CastError.
|
||||
// Arg0: index of the token of the failed type check.
|
||||
// Arg1: src value.
|
||||
|
|
|
@ -158,6 +158,7 @@ namespace dart {
|
|||
V(DateTime_timeZoneOffsetInSeconds, 1) \
|
||||
V(DateTime_localTimeZoneAdjustmentInSeconds, 0) \
|
||||
V(AssertionError_throwNew, 3) \
|
||||
V(AssertionError_throwNewSource, 4) \
|
||||
V(Async_rethrow, 2) \
|
||||
V(StackTrace_asyncStackTraceHelper, 1) \
|
||||
V(StackTrace_clearAsyncThreadStackTrace, 0) \
|
||||
|
|
|
@ -988,7 +988,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
|
|||
// The RawParameter variables should be set to null to avoid retaining more
|
||||
// objects than necessary during GC.
|
||||
const Fragment body =
|
||||
ClearRawParameters(dart_function) +
|
||||
ClearRawParameters(dart_function) + B->BuildNullAssertions() +
|
||||
BuildFunctionBody(dart_function, first_parameter, is_constructor);
|
||||
|
||||
auto extra_entry_point_style = ChooseEntryPointStyle(
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "vm/compiler/frontend/kernel_translation_helper.h"
|
||||
#include "vm/compiler/frontend/prologue_builder.h"
|
||||
#include "vm/compiler/jit/compiler.h"
|
||||
#include "vm/kernel_isolate.h"
|
||||
#include "vm/kernel_loader.h"
|
||||
#include "vm/longjump.h"
|
||||
#include "vm/native_entry.h"
|
||||
|
@ -2572,6 +2573,7 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfFieldAccessor(
|
|||
body += CheckAssignable(setter_value->type(), setter_value->name(),
|
||||
AssertAssignableInstr::kParameterCheck);
|
||||
}
|
||||
body += BuildNullAssertions();
|
||||
if (field.is_late()) {
|
||||
if (is_method) {
|
||||
body += Drop();
|
||||
|
@ -3139,6 +3141,83 @@ void FlowGraphBuilder::SetCurrentTryCatchBlock(TryCatchBlock* try_catch_block) {
|
|||
: try_catch_block->try_index());
|
||||
}
|
||||
|
||||
bool FlowGraphBuilder::NeedsNullAssertion(const AbstractType& type) {
|
||||
if (!type.IsNonNullable()) {
|
||||
return false;
|
||||
}
|
||||
if (type.IsTypeParameter()) {
|
||||
return NeedsNullAssertion(
|
||||
AbstractType::Handle(Z, TypeParameter::Cast(type).bound()));
|
||||
}
|
||||
if (type.IsFutureOrType()) {
|
||||
return NeedsNullAssertion(AbstractType::Handle(Z, type.UnwrapFutureOr()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Fragment FlowGraphBuilder::NullAssertion(LocalVariable* variable) {
|
||||
Fragment code;
|
||||
if (!NeedsNullAssertion(variable->type())) {
|
||||
return code;
|
||||
}
|
||||
|
||||
TargetEntryInstr* then;
|
||||
TargetEntryInstr* otherwise;
|
||||
|
||||
code += LoadLocal(variable);
|
||||
code += NullConstant();
|
||||
code += BranchIfEqual(&then, &otherwise);
|
||||
|
||||
if (throw_new_null_assertion_ == nullptr) {
|
||||
const Class& klass = Class::ZoneHandle(
|
||||
Z, Library::LookupCoreClass(Symbols::AssertionError()));
|
||||
ASSERT(!klass.IsNull());
|
||||
throw_new_null_assertion_ =
|
||||
&Function::ZoneHandle(Z, klass.LookupStaticFunctionAllowPrivate(
|
||||
Symbols::ThrowNewNullAssertion()));
|
||||
ASSERT(!throw_new_null_assertion_->IsNull());
|
||||
}
|
||||
|
||||
const Script& script =
|
||||
Script::Handle(Z, parsed_function_->function().script());
|
||||
intptr_t line = -1;
|
||||
intptr_t column = -1;
|
||||
script.GetTokenLocation(variable->token_pos(), &line, &column);
|
||||
|
||||
// Build equivalent of `throw _AssertionError._throwNewNullAssertion(name)`
|
||||
// expression. We build throw (even through _throwNewNullAssertion already
|
||||
// throws) because call is not a valid last instruction for the block.
|
||||
// Blocks can only terminate with explicit control flow instructions
|
||||
// (Branch, Goto, Return or Throw).
|
||||
Fragment null_code(then);
|
||||
null_code += Constant(variable->name());
|
||||
null_code += IntConstant(line);
|
||||
null_code += IntConstant(column);
|
||||
null_code += StaticCall(variable->token_pos(), *throw_new_null_assertion_, 3,
|
||||
ICData::kStatic);
|
||||
null_code += ThrowException(TokenPosition::kNoSource);
|
||||
null_code += Drop();
|
||||
|
||||
return Fragment(code.entry, otherwise);
|
||||
}
|
||||
|
||||
Fragment FlowGraphBuilder::BuildNullAssertions() {
|
||||
Fragment code;
|
||||
if (I->null_safety() || !I->asserts() || !FLAG_null_assertions ||
|
||||
!KernelIsolate::GetExperimentalFlag("non-nullable")) {
|
||||
return code;
|
||||
}
|
||||
|
||||
const Function& dart_function = parsed_function_->function();
|
||||
for (intptr_t i = dart_function.NumImplicitParameters(),
|
||||
n = dart_function.NumParameters();
|
||||
i < n; ++i) {
|
||||
LocalVariable* variable = parsed_function_->ParameterVariable(i);
|
||||
code += NullAssertion(variable);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
} // namespace kernel
|
||||
|
||||
} // namespace dart
|
||||
|
|
|
@ -260,6 +260,16 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
|
|||
Fragment* implicit_checks,
|
||||
Fragment* implicit_redefinitions);
|
||||
|
||||
// Returns true if null assertion is needed for
|
||||
// a parameter of given type.
|
||||
bool NeedsNullAssertion(const AbstractType& type);
|
||||
|
||||
// Builds null assertion for the given parameter.
|
||||
Fragment NullAssertion(LocalVariable* variable);
|
||||
|
||||
// Builds null assertions for all parameters (if needed).
|
||||
Fragment BuildNullAssertions();
|
||||
|
||||
// Builds flow graph for noSuchMethod forwarder.
|
||||
//
|
||||
// If throw_no_such_method_error is set to true, an
|
||||
|
@ -407,6 +417,9 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
|
|||
|
||||
ActiveClass active_class_;
|
||||
|
||||
// Cached _AssertionError._throwNewNullAssertion.
|
||||
Function* throw_new_null_assertion_ = nullptr;
|
||||
|
||||
friend class BreakableBlock;
|
||||
friend class CatchBlock;
|
||||
friend class ProgramState;
|
||||
|
|
|
@ -115,6 +115,8 @@ constexpr bool kDartUseBackgroundCompilation = true;
|
|||
"Dump megamorphic cache statistics") \
|
||||
R(dump_symbol_stats, false, bool, false, "Dump symbol table statistics") \
|
||||
R(enable_asserts, false, bool, false, "Enable assert statements.") \
|
||||
R(null_assertions, false, bool, false, \
|
||||
"Enable null assertions for parameters.") \
|
||||
P(enable_kernel_expression_compilation, bool, true, \
|
||||
"Compile expressions with the Kernel front-end.") \
|
||||
P(enable_mirrors, bool, true, \
|
||||
|
|
|
@ -273,6 +273,7 @@ class ObjectPointerVisitor;
|
|||
V(SymbolCtor, "Symbol.") \
|
||||
V(ThrowNew, "_throwNew") \
|
||||
V(ThrowNewInvocation, "_throwNewInvocation") \
|
||||
V(ThrowNewNullAssertion, "_throwNewNullAssertion") \
|
||||
V(TopLevel, "::") \
|
||||
V(TransferableTypedData, "TransferableTypedData") \
|
||||
V(TruncDivOperator, "~/") \
|
||||
|
|
|
@ -307,7 +307,7 @@ class _StreamImpl<T> {
|
|||
}
|
||||
|
||||
@pragma("vm:entry-point", "call")
|
||||
void _completeOnAsyncReturn(Completer completer, Object value) {
|
||||
void _completeOnAsyncReturn(Completer completer, Object? value) {
|
||||
completer.complete(value);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,8 +36,16 @@ class _AssertionError extends Error implements AssertionError {
|
|||
_doThrowNew(assertionStart, assertionEnd, message);
|
||||
}
|
||||
|
||||
@pragma("vm:entry-point", "call")
|
||||
@pragma('vm:never-inline')
|
||||
static _throwNewNullAssertion(String name, int line, int column) {
|
||||
_doThrowNewSource('$name != null', line, column, null);
|
||||
}
|
||||
|
||||
static _doThrowNew(int assertionStart, int assertionEnd, Object? message)
|
||||
native "AssertionError_throwNew";
|
||||
static _doThrowNewSource(String failedAssertion, int line, int column, Object? message)
|
||||
native "AssertionError_throwNewSource";
|
||||
|
||||
@pragma("vm:entry-point", "call")
|
||||
static _evaluateAssertion(condition) {
|
||||
|
@ -75,7 +83,7 @@ class _AssertionError extends Error implements AssertionError {
|
|||
}
|
||||
|
||||
final String _failedAssertion;
|
||||
final String _url;
|
||||
final String? _url;
|
||||
final int _line;
|
||||
final int _column;
|
||||
final Object? message;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// 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.
|
||||
|
||||
// Opted-in library for parameter_checks_test.dart.
|
||||
|
||||
import 'dart:async' show FutureOr;
|
||||
|
||||
foo1(int a) {}
|
||||
foo2(int a, [int b = 1, String c = '']) {}
|
||||
foo3({int a = 0, required int b}) {}
|
||||
foo4a<T>(T a) {}
|
||||
foo4b<T extends Object>(T a) {}
|
||||
foo5a<T>(FutureOr<T> a) {}
|
||||
foo5b<T extends Object>(FutureOr<T> a) {}
|
||||
foo6a<T extends FutureOr<S>, S extends U, U extends int?>(T a) {}
|
||||
foo6b<T extends FutureOr<S>, S extends U, U extends int>(T a) {}
|
||||
|
||||
void Function(int) bar() => (int x) {};
|
|
@ -0,0 +1,45 @@
|
|||
// 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.
|
||||
|
||||
// Test for null assertions for parameters in NNBD weak mode.
|
||||
|
||||
// Requirements=nnbd-weak
|
||||
// VMOptions=--enable-asserts --null-assertions
|
||||
|
||||
// Opt out of Null Safety:
|
||||
// @dart = 2.6
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
import 'parameter_checks_opted_in.dart';
|
||||
|
||||
main() {
|
||||
Expect.throws(() {
|
||||
foo1(null);
|
||||
}, (e) => e is AssertionError && e.toString().contains("a != null"));
|
||||
Expect.throws(() {
|
||||
foo2(1, null);
|
||||
}, (e) => e is AssertionError && e.toString().contains("b != null"));
|
||||
Expect.throws(() {
|
||||
foo3();
|
||||
}, (e) => e is AssertionError && e.toString().contains("b != null"));
|
||||
Expect.throws(() {
|
||||
foo3(b: null);
|
||||
}, (e) => e is AssertionError && e.toString().contains("b != null"));
|
||||
foo4a<int>(null);
|
||||
Expect.throws(() {
|
||||
foo4b<int>(null);
|
||||
}, (e) => e is AssertionError && e.toString().contains("a != null"));
|
||||
foo5a<int>(null);
|
||||
Expect.throws(() {
|
||||
foo5b<int>(null);
|
||||
}, (e) => e is AssertionError && e.toString().contains("a != null"));
|
||||
foo6a<int, int, int>(null);
|
||||
Expect.throws(() {
|
||||
foo6b<int, int, int>(null);
|
||||
}, (e) => e is AssertionError && e.toString().contains("a != null"));
|
||||
Expect.throws(() {
|
||||
bar().call(null);
|
||||
}, (e) => e is AssertionError && e.toString().contains("x != null"));
|
||||
}
|
Loading…
Reference in a new issue