[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:
Alexander Markov 2020-06-18 20:02:42 +00:00 committed by commit-bot@chromium.org
parent db8a105de7
commit 7bbf2921db
11 changed files with 207 additions and 3 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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, "~/") \

View file

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

View file

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

View file

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

View file

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