Skip to content

Commit 2c0b605

Browse files
committed
Add optional message argument to assert statements in the VM.
Add flag --assert-message to control the feature. BUG=#24215 [email protected] Review-Url: https://codereview.chromium.org/2574003003 .
1 parent 871f478 commit 2c0b605

File tree

9 files changed

+211
-34
lines changed

9 files changed

+211
-34
lines changed

runtime/lib/errors.cc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ static RawScript* FindScript(DartFrameIterator* iterator) {
2020
// the inlining meta-data so we cannot walk the inline-aware stack trace.
2121
// Second, the script text itself is missing so whatever script is returned
2222
// from here will be missing the assertion expression text.
23-
iterator->NextFrame(); // Skip _AssertionError._checkAssertion frame
23+
iterator->NextFrame(); // Skip _AssertionError._evaluateAssertion frame
2424
return Exceptions::GetCallerScript(iterator);
2525
}
2626
StackFrame* stack_frame = iterator->NextFrame();
@@ -62,16 +62,18 @@ static RawScript* FindScript(DartFrameIterator* iterator) {
6262
// Allocate and throw a new AssertionError.
6363
// Arg0: index of the first token of the failed assertion.
6464
// Arg1: index of the first token after the failed assertion.
65+
// Arg2: Message object or null.
6566
// Return value: none, throws an exception.
66-
DEFINE_NATIVE_ENTRY(AssertionError_throwNew, 2) {
67+
DEFINE_NATIVE_ENTRY(AssertionError_throwNew, 3) {
6768
// No need to type check the arguments. This function can only be called
6869
// internally from the VM.
6970
const TokenPosition assertion_start =
7071
TokenPosition(Smi::CheckedHandle(arguments->NativeArgAt(0)).Value());
7172
const TokenPosition assertion_end =
7273
TokenPosition(Smi::CheckedHandle(arguments->NativeArgAt(1)).Value());
7374

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

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

9699
Exceptions::ThrowByType(Exceptions::kAssertion, args);
97100
UNREACHABLE();

runtime/lib/errors_patch.dart

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,47 +18,56 @@
1818

1919
class _AssertionError extends Error implements AssertionError {
2020
_AssertionError._create(
21-
this._failedAssertion, this._url, this._line, this._column);
21+
this._failedAssertion, this._url, this._line, this._column,
22+
this.message);
2223

23-
static _throwNew(int assertionStart, int assertionEnd)
24-
native "AssertionError_throwNew";
2524

26-
static void _checkAssertion(condition, int start, int end) {
25+
// AssertionError_throwNew in errors.cc fishes the assertion source code
26+
// out of the script. It expects a Dart stack frame from class
27+
// _AssertionError. Thus we need a Dart stub that calls the native code.
28+
static _throwNew(int assertionStart, int assertionEnd, Object message) {
29+
_doThrowNew(assertionStart, assertionEnd, message);
30+
}
31+
32+
static _doThrowNew(int assertionStart, int assertionEnd, Object message)
33+
native "AssertionError_throwNew";
34+
35+
static _evaluateAssertion(condition) {
2736
if (condition is Function) {
2837
condition = condition();
2938
}
30-
if (!condition) {
31-
_throwNew(start, end);
32-
}
39+
return condition;
3340
}
3441

35-
static void _checkConstAssertion(bool condition, int start, int end) {
36-
if (!condition) {
37-
_throwNew(start, end);
38-
}
42+
String get _messageString {
43+
if (message == null) return "is not true.";
44+
if (message is String) return message;
45+
return Error.safeToString(message);
3946
}
4047

4148
String toString() {
4249
if (_url == null) {
43-
return _failedAssertion;
50+
if (message == null) return _failedAssertion;
51+
return "'$_failedAssertion': $_messageString";
4452
}
4553
var columnInfo = "";
4654
if (_column > 0) {
4755
// Only add column information if it is valid.
4856
columnInfo = " pos $_column";
4957
}
5058
return "'$_url': Failed assertion: line $_line$columnInfo: "
51-
"'$_failedAssertion' is not true.";
59+
"'$_failedAssertion': $_messageString";
5260
}
5361
final String _failedAssertion;
5462
final String _url;
5563
final int _line;
5664
final int _column;
65+
final Object message;
5766
}
5867

5968
class _TypeError extends _AssertionError implements TypeError {
60-
_TypeError._create(String url, int line, int column, this._errorMsg)
61-
: super._create("is assignable", url, line, column);
69+
_TypeError._create(String url, int line, int column, String errorMsg)
70+
: super._create("is assignable", url, line, column, errorMsg);
6271

6372
static _throwNew(int location,
6473
Object src_value,
@@ -78,9 +87,7 @@ class _TypeError extends _AssertionError implements TypeError {
7887
}
7988
}
8089

81-
String toString() => _errorMsg;
82-
83-
final String _errorMsg;
90+
String toString() => super.message;
8491
}
8592

8693
class _CastError extends Error implements CastError {

runtime/vm/bootstrap_natives.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ namespace dart {
159159
V(DateTime_timeZoneName, 1) \
160160
V(DateTime_timeZoneOffsetInSeconds, 1) \
161161
V(DateTime_localTimeZoneAdjustmentInSeconds, 0) \
162-
V(AssertionError_throwNew, 2) \
162+
V(AssertionError_throwNew, 3) \
163163
V(Async_rethrow, 2) \
164164
V(StackTrace_current, 0) \
165165
V(TypeError_throwNew, 5) \

runtime/vm/code_generator.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ DEFINE_RUNTIME_ENTRY(NonBoolTypeError, 1) {
585585
Instance::CheckedHandle(zone, arguments.ArgAt(0));
586586

587587
if (src_instance.IsNull()) {
588-
const Array& args = Array::Handle(zone, Array::New(4));
588+
const Array& args = Array::Handle(zone, Array::New(5));
589589
args.SetAt(
590590
0, String::Handle(
591591
zone,
@@ -596,6 +596,7 @@ DEFINE_RUNTIME_ENTRY(NonBoolTypeError, 1) {
596596
args.SetAt(1, String::Handle(zone, String::null()));
597597
args.SetAt(2, Smi::Handle(zone, Smi::New(0)));
598598
args.SetAt(3, Smi::Handle(zone, Smi::New(0)));
599+
args.SetAt(4, String::Handle(zone, String::null()));
599600

600601
Exceptions::ThrowByType(Exceptions::kAssertion, args);
601602
UNREACHABLE();

runtime/vm/parser.cc

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ DEFINE_FLAG(bool,
7070
assert_initializer,
7171
false,
7272
"Allow asserts in initializer lists.");
73+
DEFINE_FLAG(bool, assert_message, false, "Allow message in assert statements");
7374

7475
DECLARE_FLAG(bool, profile_vm);
7576
DECLARE_FLAG(bool, trace_service);
@@ -9164,32 +9165,91 @@ AstNode* Parser::ParseAssertStatement(bool is_const) {
91649165
const TokenPosition condition_pos = TokenPos();
91659166
if (!I->asserts()) {
91669167
SkipExpr();
9168+
if (FLAG_assert_message && (CurrentToken() == Token::kCOMMA)) {
9169+
ConsumeToken();
9170+
SkipExpr();
9171+
}
91679172
ExpectToken(Token::kRPAREN);
91689173
return NULL;
91699174
}
9170-
AstNode* condition = ParseAwaitableExpr(kAllowConst, kConsumeCascades, NULL);
9175+
9176+
BoolScope saved_seen_await(&parsed_function()->have_seen_await_expr_, false);
9177+
AstNode* condition = ParseExpr(kAllowConst, kConsumeCascades);
91719178
if (is_const && !condition->IsPotentiallyConst()) {
91729179
ReportError(condition_pos,
91739180
"initializer assert expression must be compile time constant.");
91749181
}
91759182
const TokenPosition condition_end = TokenPos();
9183+
AstNode* message = NULL;
9184+
TokenPosition message_pos = TokenPosition::kNoSource;
9185+
if (FLAG_assert_message && CurrentToken() == Token::kCOMMA) {
9186+
ConsumeToken();
9187+
message_pos = TokenPos();
9188+
message = ParseExpr(kAllowConst, kConsumeCascades);
9189+
if (is_const && !message->IsPotentiallyConst()) {
9190+
ReportError(
9191+
message_pos,
9192+
"initializer assert expression must be compile time constant.");
9193+
}
9194+
}
91769195
ExpectToken(Token::kRPAREN);
91779196

9197+
if (!is_const) {
9198+
// Check for assertion condition being a function if not const.
9199+
ArgumentListNode* arguments = new (Z) ArgumentListNode(condition_pos);
9200+
arguments->Add(condition);
9201+
condition = MakeStaticCall(
9202+
Symbols::AssertionError(),
9203+
Library::PrivateCoreLibName(Symbols::EvaluateAssertion()), arguments);
9204+
}
9205+
AstNode* not_condition =
9206+
new (Z) UnaryOpNode(condition_pos, Token::kNOT, condition);
9207+
9208+
// Build call to _AsertionError._throwNew(start, end, message)
91789209
ArgumentListNode* arguments = new (Z) ArgumentListNode(condition_pos);
9179-
arguments->Add(condition);
91809210
arguments->Add(new (Z) LiteralNode(
91819211
condition_pos,
9182-
Integer::ZoneHandle(Z, Integer::New(condition_pos.value(), Heap::kOld))));
9212+
Integer::ZoneHandle(Z, Integer::New(condition_pos.Pos()))));
91839213
arguments->Add(new (Z) LiteralNode(
91849214
condition_end,
9185-
Integer::ZoneHandle(Z, Integer::New(condition_end.value(), Heap::kOld))));
9215+
Integer::ZoneHandle(Z, Integer::New(condition_end.Pos()))));
9216+
if (message == NULL) {
9217+
message = new (Z) LiteralNode(condition_end, Instance::ZoneHandle(Z));
9218+
}
9219+
arguments->Add(message);
91869220
AstNode* assert_throw = MakeStaticCall(
91879221
Symbols::AssertionError(),
9188-
Library::PrivateCoreLibName(is_const ? Symbols::CheckConstAssertion()
9189-
: Symbols::CheckAssertion()),
9190-
arguments);
9222+
Library::PrivateCoreLibName(Symbols::ThrowNew()), arguments);
91919223

9192-
return assert_throw;
9224+
AstNode* assertion_check = NULL;
9225+
if (parsed_function()->have_seen_await()) {
9226+
// The await transformation must be done manually because assertions
9227+
// are parsed as statements, not expressions. Thus, we need to check
9228+
// explicitely whether the arguments contain await operators. (Note that
9229+
// we must not parse the arguments with ParseAwaitableExpr(). In the
9230+
// corner case of assert(await a, await b), this would create two
9231+
// sibling scopes containing the temporary values for a and b. Both
9232+
// values would be allocated in the same internal context variable.)
9233+
//
9234+
// Build !condition ? _AsertionError._throwNew(...) : null;
9235+
// We need to use a conditional expression because the await transformer
9236+
// cannot transform if statements.
9237+
assertion_check = new (Z) ConditionalExprNode(
9238+
condition_pos, not_condition, assert_throw,
9239+
new (Z) LiteralNode(condition_pos, Object::null_instance()));
9240+
OpenBlock();
9241+
AwaitTransformer at(current_block_->statements, async_temp_scope_);
9242+
AstNode* transformed_assertion = at.Transform(assertion_check);
9243+
SequenceNode* preamble = CloseBlock();
9244+
preamble->Add(transformed_assertion);
9245+
assertion_check = preamble;
9246+
} else {
9247+
// Build if (!condition) _AsertionError._throwNew(...)
9248+
assertion_check = new (Z)
9249+
IfNode(condition_pos, not_condition,
9250+
NodeAsSequenceNode(condition_pos, assert_throw, NULL), NULL);
9251+
}
9252+
return assertion_check;
91939253
}
91949254

91959255

runtime/vm/symbols.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@ class ObjectPointerVisitor;
7575
V(_CompileTimeError, "_CompileTimeError") \
7676
V(ThrowNew, "_throwNew") \
7777
V(ThrowNewIfNotLoaded, "_throwNewIfNotLoaded") \
78-
V(CheckAssertion, "_checkAssertion") \
79-
V(CheckConstAssertion, "_checkConstAssertion") \
78+
V(EvaluateAssertion, "_evaluateAssertion") \
8079
V(Symbol, "Symbol") \
8180
V(SymbolCtor, "Symbol.") \
8281
V(List, "List") \

sdk/lib/core/errors.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ class Error {
9696
* Error thrown by the runtime system when an assert statement fails.
9797
*/
9898
class AssertionError extends Error {
99-
AssertionError();
99+
/** Message describing the assertion error. */
100+
final Object message;
101+
AssertionError([this.message]);
100102
String toString() => "Assertion failed";
101103
}
102104

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// SharedOptions=--assert-message
6+
7+
import "dart:async";
8+
9+
import "package:async_helper/async_helper.dart";
10+
import "package:expect/expect.dart";
11+
12+
main() {
13+
// Only run with asserts enabled mode.
14+
bool assertsEnabled = false;
15+
assert(assertsEnabled = true);
16+
if (!assertsEnabled) return;
17+
18+
// Basics.
19+
assert(true, "");
20+
assert(() => true, "");
21+
22+
int x = null;
23+
// Successful asserts won't execute message.
24+
assert(true, x + 42);
25+
assert(true, throw "unreachable");
26+
27+
// Can use any value as message.
28+
try {
29+
assert(false, 42);
30+
} on AssertionError catch (e) {
31+
Expect.equals(42, e.message);
32+
}
33+
34+
try {
35+
assert(false, "");
36+
} on AssertionError catch (e) {
37+
Expect.equals("", e.message);
38+
}
39+
40+
try {
41+
assert(false, null);
42+
} on AssertionError catch (e) {
43+
Expect.equals(null, e.message);
44+
}
45+
46+
// Test expression can throw.
47+
try {
48+
assert(throw "test", throw "message");
49+
} on String catch (e) {
50+
Expect.equals("test", e);
51+
}
52+
53+
// Message expression can throw.
54+
try {
55+
assert(false, throw "message");
56+
} on String catch (e) {
57+
Expect.equals("message", e);
58+
}
59+
60+
// Failing asserts evaluate message after test.
61+
var list = [];
62+
try {
63+
assert((list..add(1)).isEmpty, (list..add(3)).length);
64+
} on AssertionError catch (e) {
65+
Expect.equals(2, e.message);
66+
Expect.listEquals([1, 3], list);
67+
}
68+
69+
asyncStart();
70+
asyncTests().then((_) { asyncEnd(); });
71+
}
72+
73+
74+
Future asyncTests() async {
75+
// You can await in both condition and message.
76+
assert(true, await 0);
77+
assert(await true, 1);
78+
assert(await true, await 2);
79+
80+
// Successful asserts won't await/evaluate message.
81+
void unreachable() => throw "unreachable";
82+
assert(await true, await unreachable());
83+
84+
try {
85+
assert(false, await 3);
86+
} on AssertionError catch (e) {
87+
Expect.equals(3, e.message);
88+
}
89+
90+
var falseFuture = new Future.value(false);
91+
var numFuture = new Future.value(4);
92+
93+
try {
94+
assert(await falseFuture, await numFuture);
95+
} on AssertionError catch (e) {
96+
Expect.equals(4, e.message);
97+
}
98+
99+
try {
100+
assert(await falseFuture, await new Future.error("error"));
101+
} on String catch (e) {
102+
Expect.equals("error", e);
103+
}
104+
}

tests/language/language_dart2js.status

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ multiline_newline_test/06: MissingCompileTimeError # Issue 23888
139139
multiline_newline_test/none: RuntimeError # Issue 23888
140140

141141
[ $compiler == dart2js && $checked ]
142+
assert_message_test: Fail #28106
142143
async_return_types_test/nestedFuture: Fail # Issue 26429
143144
async_return_types_test/wrongTypeParameter: Fail # Issue 26429
144145
regress_26133_test: RuntimeError # Issue 26429

0 commit comments

Comments
 (0)