Skip to content

Commit

Permalink
Add optional message argument to assert statements in the VM.
Browse files Browse the repository at this point in the history
Add flag --assert-message to control the feature.

BUG=#24215
R=regis@google.com

Review-Url: https://codereview.chromium.org/2574003003 .
  • Loading branch information
mhausner committed Dec 14, 2016
1 parent 871f478 commit 2c0b605
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 34 deletions.
9 changes: 6 additions & 3 deletions runtime/lib/errors.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ static RawScript* FindScript(DartFrameIterator* iterator) {
// the inlining meta-data so we cannot walk the inline-aware stack trace.
// Second, the script text itself is missing so whatever script is returned
// from here will be missing the assertion expression text.
iterator->NextFrame(); // Skip _AssertionError._checkAssertion frame
iterator->NextFrame(); // Skip _AssertionError._evaluateAssertion frame
return Exceptions::GetCallerScript(iterator);
}
StackFrame* stack_frame = iterator->NextFrame();
Expand Down Expand Up @@ -62,16 +62,18 @@ static RawScript* FindScript(DartFrameIterator* iterator) {
// Allocate and throw a new AssertionError.
// Arg0: index of the first token of the failed assertion.
// Arg1: index of the first token after the failed assertion.
// Arg2: Message object or null.
// Return value: none, throws an exception.
DEFINE_NATIVE_ENTRY(AssertionError_throwNew, 2) {
DEFINE_NATIVE_ENTRY(AssertionError_throwNew, 3) {
// No need to type check the arguments. This function can only be called
// internally from the VM.
const TokenPosition assertion_start =
TokenPosition(Smi::CheckedHandle(arguments->NativeArgAt(0)).Value());
const TokenPosition assertion_end =
TokenPosition(Smi::CheckedHandle(arguments->NativeArgAt(1)).Value());

const Array& args = Array::Handle(Array::New(4));
const Instance& message = Instance::CheckedHandle(arguments->NativeArgAt(2));
const Array& args = Array::Handle(Array::New(5));

DartFrameIterator iterator;
iterator.NextFrame(); // Skip native call.
Expand All @@ -92,6 +94,7 @@ DEFINE_NATIVE_ENTRY(AssertionError_throwNew, 2) {
args.SetAt(1, String::Handle(script.url()));
args.SetAt(2, Smi::Handle(Smi::New(from_line)));
args.SetAt(3, Smi::Handle(Smi::New(script.HasSource() ? from_column : -1)));
args.SetAt(4, message);

Exceptions::ThrowByType(Exceptions::kAssertion, args);
UNREACHABLE();
Expand Down
43 changes: 25 additions & 18 deletions runtime/lib/errors_patch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,56 @@

class _AssertionError extends Error implements AssertionError {
_AssertionError._create(
this._failedAssertion, this._url, this._line, this._column);
this._failedAssertion, this._url, this._line, this._column,
this.message);

static _throwNew(int assertionStart, int assertionEnd)
native "AssertionError_throwNew";

static void _checkAssertion(condition, int start, int end) {
// AssertionError_throwNew in errors.cc fishes the assertion source code
// out of the script. It expects a Dart stack frame from class
// _AssertionError. Thus we need a Dart stub that calls the native code.
static _throwNew(int assertionStart, int assertionEnd, Object message) {
_doThrowNew(assertionStart, assertionEnd, message);
}

static _doThrowNew(int assertionStart, int assertionEnd, Object message)
native "AssertionError_throwNew";

static _evaluateAssertion(condition) {
if (condition is Function) {
condition = condition();
}
if (!condition) {
_throwNew(start, end);
}
return condition;
}

static void _checkConstAssertion(bool condition, int start, int end) {
if (!condition) {
_throwNew(start, end);
}
String get _messageString {
if (message == null) return "is not true.";
if (message is String) return message;
return Error.safeToString(message);
}

String toString() {
if (_url == null) {
return _failedAssertion;
if (message == null) return _failedAssertion;
return "'$_failedAssertion': $_messageString";
}
var columnInfo = "";
if (_column > 0) {
// Only add column information if it is valid.
columnInfo = " pos $_column";
}
return "'$_url': Failed assertion: line $_line$columnInfo: "
"'$_failedAssertion' is not true.";
"'$_failedAssertion': $_messageString";
}
final String _failedAssertion;
final String _url;
final int _line;
final int _column;
final Object message;
}

class _TypeError extends _AssertionError implements TypeError {
_TypeError._create(String url, int line, int column, this._errorMsg)
: super._create("is assignable", url, line, column);
_TypeError._create(String url, int line, int column, String errorMsg)
: super._create("is assignable", url, line, column, errorMsg);

static _throwNew(int location,
Object src_value,
Expand All @@ -78,9 +87,7 @@ class _TypeError extends _AssertionError implements TypeError {
}
}

String toString() => _errorMsg;

final String _errorMsg;
String toString() => super.message;
}

class _CastError extends Error implements CastError {
Expand Down
2 changes: 1 addition & 1 deletion runtime/vm/bootstrap_natives.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ namespace dart {
V(DateTime_timeZoneName, 1) \
V(DateTime_timeZoneOffsetInSeconds, 1) \
V(DateTime_localTimeZoneAdjustmentInSeconds, 0) \
V(AssertionError_throwNew, 2) \
V(AssertionError_throwNew, 3) \
V(Async_rethrow, 2) \
V(StackTrace_current, 0) \
V(TypeError_throwNew, 5) \
Expand Down
3 changes: 2 additions & 1 deletion runtime/vm/code_generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ DEFINE_RUNTIME_ENTRY(NonBoolTypeError, 1) {
Instance::CheckedHandle(zone, arguments.ArgAt(0));

if (src_instance.IsNull()) {
const Array& args = Array::Handle(zone, Array::New(4));
const Array& args = Array::Handle(zone, Array::New(5));
args.SetAt(
0, String::Handle(
zone,
Expand All @@ -596,6 +596,7 @@ DEFINE_RUNTIME_ENTRY(NonBoolTypeError, 1) {
args.SetAt(1, String::Handle(zone, String::null()));
args.SetAt(2, Smi::Handle(zone, Smi::New(0)));
args.SetAt(3, Smi::Handle(zone, Smi::New(0)));
args.SetAt(4, String::Handle(zone, String::null()));

Exceptions::ThrowByType(Exceptions::kAssertion, args);
UNREACHABLE();
Expand Down
76 changes: 68 additions & 8 deletions runtime/vm/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ DEFINE_FLAG(bool,
assert_initializer,
false,
"Allow asserts in initializer lists.");
DEFINE_FLAG(bool, assert_message, false, "Allow message in assert statements");

DECLARE_FLAG(bool, profile_vm);
DECLARE_FLAG(bool, trace_service);
Expand Down Expand Up @@ -9164,32 +9165,91 @@ AstNode* Parser::ParseAssertStatement(bool is_const) {
const TokenPosition condition_pos = TokenPos();
if (!I->asserts()) {
SkipExpr();
if (FLAG_assert_message && (CurrentToken() == Token::kCOMMA)) {
ConsumeToken();
SkipExpr();
}
ExpectToken(Token::kRPAREN);
return NULL;
}
AstNode* condition = ParseAwaitableExpr(kAllowConst, kConsumeCascades, NULL);

BoolScope saved_seen_await(&parsed_function()->have_seen_await_expr_, false);
AstNode* condition = ParseExpr(kAllowConst, kConsumeCascades);
if (is_const && !condition->IsPotentiallyConst()) {
ReportError(condition_pos,
"initializer assert expression must be compile time constant.");
}
const TokenPosition condition_end = TokenPos();
AstNode* message = NULL;
TokenPosition message_pos = TokenPosition::kNoSource;
if (FLAG_assert_message && CurrentToken() == Token::kCOMMA) {
ConsumeToken();
message_pos = TokenPos();
message = ParseExpr(kAllowConst, kConsumeCascades);
if (is_const && !message->IsPotentiallyConst()) {
ReportError(
message_pos,
"initializer assert expression must be compile time constant.");
}
}
ExpectToken(Token::kRPAREN);

if (!is_const) {
// Check for assertion condition being a function if not const.
ArgumentListNode* arguments = new (Z) ArgumentListNode(condition_pos);
arguments->Add(condition);
condition = MakeStaticCall(
Symbols::AssertionError(),
Library::PrivateCoreLibName(Symbols::EvaluateAssertion()), arguments);
}
AstNode* not_condition =
new (Z) UnaryOpNode(condition_pos, Token::kNOT, condition);

// Build call to _AsertionError._throwNew(start, end, message)
ArgumentListNode* arguments = new (Z) ArgumentListNode(condition_pos);
arguments->Add(condition);
arguments->Add(new (Z) LiteralNode(
condition_pos,
Integer::ZoneHandle(Z, Integer::New(condition_pos.value(), Heap::kOld))));
Integer::ZoneHandle(Z, Integer::New(condition_pos.Pos()))));
arguments->Add(new (Z) LiteralNode(
condition_end,
Integer::ZoneHandle(Z, Integer::New(condition_end.value(), Heap::kOld))));
Integer::ZoneHandle(Z, Integer::New(condition_end.Pos()))));
if (message == NULL) {
message = new (Z) LiteralNode(condition_end, Instance::ZoneHandle(Z));
}
arguments->Add(message);
AstNode* assert_throw = MakeStaticCall(
Symbols::AssertionError(),
Library::PrivateCoreLibName(is_const ? Symbols::CheckConstAssertion()
: Symbols::CheckAssertion()),
arguments);
Library::PrivateCoreLibName(Symbols::ThrowNew()), arguments);

return assert_throw;
AstNode* assertion_check = NULL;
if (parsed_function()->have_seen_await()) {
// The await transformation must be done manually because assertions
// are parsed as statements, not expressions. Thus, we need to check
// explicitely whether the arguments contain await operators. (Note that
// we must not parse the arguments with ParseAwaitableExpr(). In the
// corner case of assert(await a, await b), this would create two
// sibling scopes containing the temporary values for a and b. Both
// values would be allocated in the same internal context variable.)
//
// Build !condition ? _AsertionError._throwNew(...) : null;
// We need to use a conditional expression because the await transformer
// cannot transform if statements.
assertion_check = new (Z) ConditionalExprNode(
condition_pos, not_condition, assert_throw,
new (Z) LiteralNode(condition_pos, Object::null_instance()));
OpenBlock();
AwaitTransformer at(current_block_->statements, async_temp_scope_);
AstNode* transformed_assertion = at.Transform(assertion_check);
SequenceNode* preamble = CloseBlock();
preamble->Add(transformed_assertion);
assertion_check = preamble;
} else {
// Build if (!condition) _AsertionError._throwNew(...)
assertion_check = new (Z)
IfNode(condition_pos, not_condition,
NodeAsSequenceNode(condition_pos, assert_throw, NULL), NULL);
}
return assertion_check;
}


Expand Down
3 changes: 1 addition & 2 deletions runtime/vm/symbols.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ class ObjectPointerVisitor;
V(_CompileTimeError, "_CompileTimeError") \
V(ThrowNew, "_throwNew") \
V(ThrowNewIfNotLoaded, "_throwNewIfNotLoaded") \
V(CheckAssertion, "_checkAssertion") \
V(CheckConstAssertion, "_checkConstAssertion") \
V(EvaluateAssertion, "_evaluateAssertion") \
V(Symbol, "Symbol") \
V(SymbolCtor, "Symbol.") \
V(List, "List") \
Expand Down
4 changes: 3 additions & 1 deletion sdk/lib/core/errors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ class Error {
* Error thrown by the runtime system when an assert statement fails.
*/
class AssertionError extends Error {
AssertionError();
/** Message describing the assertion error. */
final Object message;
AssertionError([this.message]);
String toString() => "Assertion failed";
}

Expand Down
104 changes: 104 additions & 0 deletions tests/language/assert_message_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) 2016, 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.

// SharedOptions=--assert-message

import "dart:async";

import "package:async_helper/async_helper.dart";
import "package:expect/expect.dart";

main() {
// Only run with asserts enabled mode.
bool assertsEnabled = false;
assert(assertsEnabled = true);
if (!assertsEnabled) return;

// Basics.
assert(true, "");
assert(() => true, "");

int x = null;
// Successful asserts won't execute message.
assert(true, x + 42);
assert(true, throw "unreachable");

// Can use any value as message.
try {
assert(false, 42);
} on AssertionError catch (e) {
Expect.equals(42, e.message);
}

try {
assert(false, "");
} on AssertionError catch (e) {
Expect.equals("", e.message);
}

try {
assert(false, null);
} on AssertionError catch (e) {
Expect.equals(null, e.message);
}

// Test expression can throw.
try {
assert(throw "test", throw "message");
} on String catch (e) {
Expect.equals("test", e);
}

// Message expression can throw.
try {
assert(false, throw "message");
} on String catch (e) {
Expect.equals("message", e);
}

// Failing asserts evaluate message after test.
var list = [];
try {
assert((list..add(1)).isEmpty, (list..add(3)).length);
} on AssertionError catch (e) {
Expect.equals(2, e.message);
Expect.listEquals([1, 3], list);
}

asyncStart();
asyncTests().then((_) { asyncEnd(); });
}


Future asyncTests() async {
// You can await in both condition and message.
assert(true, await 0);
assert(await true, 1);
assert(await true, await 2);

// Successful asserts won't await/evaluate message.
void unreachable() => throw "unreachable";
assert(await true, await unreachable());

try {
assert(false, await 3);
} on AssertionError catch (e) {
Expect.equals(3, e.message);
}

var falseFuture = new Future.value(false);
var numFuture = new Future.value(4);

try {
assert(await falseFuture, await numFuture);
} on AssertionError catch (e) {
Expect.equals(4, e.message);
}

try {
assert(await falseFuture, await new Future.error("error"));
} on String catch (e) {
Expect.equals("error", e);
}
}
1 change: 1 addition & 0 deletions tests/language/language_dart2js.status
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ multiline_newline_test/06: MissingCompileTimeError # Issue 23888
multiline_newline_test/none: RuntimeError # Issue 23888

[ $compiler == dart2js && $checked ]
assert_message_test: Fail #28106
async_return_types_test/nestedFuture: Fail # Issue 26429
async_return_types_test/wrongTypeParameter: Fail # Issue 26429
regress_26133_test: RuntimeError # Issue 26429
Expand Down

0 comments on commit 2c0b605

Please sign in to comment.