mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 14:39:38 +00:00
Add optional message argument to assert statements in the VM.
Add flag --assert-message to control the feature. BUG=#24215 R=regis@google.com Review-Url: https://codereview.chromium.org/2574003003 .
This commit is contained in:
parent
871f478c63
commit
2c0b605a96
|
@ -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();
|
||||
|
@ -62,8 +62,9 @@ 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 =
|
||||
|
@ -71,7 +72,8 @@ DEFINE_NATIVE_ENTRY(AssertionError_throwNew, 2) {
|
|||
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.
|
||||
|
@ -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();
|
||||
|
|
|
@ -18,29 +18,37 @@
|
|||
|
||||
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) {
|
||||
|
@ -48,17 +56,18 @@ class _AssertionError extends Error implements AssertionError {
|
|||
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,
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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,
|
||||
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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") \
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
||||
|
|
104
tests/language/assert_message_test.dart
Normal file
104
tests/language/assert_message_test.dart
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue