Version 2.15.0-295.0.dev
Merge commit 'dab9d70e97a64b1ad326db40e08b5c24a2e33737' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 81152a0..4a37ecd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -309,6 +309,9 @@
ready to be removed.
Code catching the class should move to catching `Error` instead
(or, for integers, check first for whether it's dividing by zero).
+- Add `Error.throwWithStackTrace` which can `throw` an
+ error with an existing stack trace, instead of creating
+ a new stack trace.
#### `dart:io`
diff --git a/pkg/analyzer_utilities/lib/check/check.dart b/pkg/analyzer_utilities/lib/check/check.dart
index 8d3cc4d..b3147bb 100644
--- a/pkg/analyzer_utilities/lib/check/check.dart
+++ b/pkg/analyzer_utilities/lib/check/check.dart
@@ -5,6 +5,7 @@
import 'package:analyzer_utilities/check/check_target.dart';
import 'package:meta/meta.dart';
+export 'package:analyzer_utilities/check/bool.dart';
export 'package:analyzer_utilities/check/check_target.dart';
export 'package:analyzer_utilities/check/equality.dart';
export 'package:analyzer_utilities/check/int.dart';
diff --git a/pkg/analyzer_utilities/lib/check/iterable.dart b/pkg/analyzer_utilities/lib/check/iterable.dart
index fbadfd9..0e3e0d3 100644
--- a/pkg/analyzer_utilities/lib/check/iterable.dart
+++ b/pkg/analyzer_utilities/lib/check/iterable.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer_utilities/check/check.dart';
+import 'package:meta/meta.dart';
extension IterableExtension<T> on CheckTarget<Iterable<T>> {
void get isEmpty {
@@ -10,4 +11,21 @@
fail('is not empty');
}
}
+
+ void get isNotEmpty {
+ if (value.isEmpty) {
+ fail('is empty');
+ }
+ }
+
+ @UseResult.unless(parameterDefined: 'expected')
+ CheckTarget<int> hasLength([int? expected]) {
+ var actual = value.length;
+
+ if (expected != null && actual != expected) {
+ fail('does not have length ${valueStr(expected)}');
+ }
+
+ return nest(actual, (length) => 'has length $length');
+ }
}
diff --git a/pkg/analyzer_utilities/lib/check/string.dart b/pkg/analyzer_utilities/lib/check/string.dart
index c4dfdf7..3d80003 100644
--- a/pkg/analyzer_utilities/lib/check/string.dart
+++ b/pkg/analyzer_utilities/lib/check/string.dart
@@ -18,11 +18,14 @@
}
}
- @useResult
- CheckTarget<int> hasLength() {
- return nest(
- value.length,
- (length) => 'has length $length',
- );
+ @UseResult.unless(parameterDefined: 'expected')
+ CheckTarget<int> hasLength([int? expected]) {
+ var actual = value.length;
+
+ if (expected != null && actual != expected) {
+ fail('does not have length ${valueStr(expected)}');
+ }
+
+ return nest(actual, (length) => 'has length $length');
}
}
diff --git a/pkg/analyzer_utilities/test/check/check_test.dart b/pkg/analyzer_utilities/test/check/check_test.dart
new file mode 100644
index 0000000..532eea7
--- /dev/null
+++ b/pkg/analyzer_utilities/test/check/check_test.dart
@@ -0,0 +1,166 @@
+// Copyright (c) 2021, 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.
+
+import 'package:analyzer_utilities/check/check.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('type', () {
+ group('bool', () {
+ test('isEqualTo', () {
+ check(true).isEqualTo(true);
+ check(false).isEqualTo(false);
+ _fails(() => check(true).isEqualTo(false));
+ _fails(() => check(false).isEqualTo(true));
+ });
+ test('isFalse', () {
+ check(false).isFalse;
+ _fails(() => check(true).isFalse);
+ });
+ test('isNotEqualTo', () {
+ check(true).isNotEqualTo(false);
+ check(false).isNotEqualTo(true);
+ _fails(() => check(true).isNotEqualTo(true));
+ _fails(() => check(false).isNotEqualTo(false));
+ });
+ test('isTrue', () {
+ check(true).isTrue;
+ _fails(() => check(false).isTrue);
+ });
+ });
+ group('int', () {
+ test('isEqualTo', () {
+ check(0).isEqualTo(0);
+ check(1).isEqualTo(1);
+ check(2).isEqualTo(2);
+ _fails(() => check(0).isEqualTo(1));
+ _fails(() => check(1).isEqualTo(0));
+ });
+ test('isGreaterThan', () {
+ check(2).isGreaterThan(1);
+ check(1).isGreaterThan(0);
+ check(-1).isGreaterThan(-2);
+ _fails(() => check(0).isGreaterThan(0));
+ _fails(() => check(0).isGreaterThan(1));
+ _fails(() => check(1).isGreaterThan(2));
+ _fails(() => check(-2).isGreaterThan(-1));
+ });
+ test('isNotEqualTo', () {
+ check(0).isNotEqualTo(1);
+ check(1).isNotEqualTo(0);
+ check(1).isNotEqualTo(2);
+ check(2).isNotEqualTo(1);
+ _fails(() => check(0).isNotEqualTo(0));
+ _fails(() => check(1).isNotEqualTo(1));
+ _fails(() => check(2).isNotEqualTo(2));
+ });
+ test('isZero', () {
+ check(0).isZero;
+ _fails(() => check(1).isZero);
+ _fails(() => check(-1).isZero);
+ });
+ });
+ group('Iterable', () {
+ test('hasLength', () {
+ check(<int>[]).hasLength().isZero;
+ check(<int>[0]).hasLength().isEqualTo(1);
+ check(<int>[0]).hasLength(1);
+ check(<int>[0, 1]).hasLength().isEqualTo(2);
+ check(<int>[0, 1]).hasLength(2);
+ check(<int>{}).hasLength().isZero;
+ check(<int>{0}).hasLength().isEqualTo(1);
+ check(<int>{0}).hasLength(1);
+ check(<int>{0, 1}).hasLength().isEqualTo(2);
+ check(<int>{0, 1}).hasLength(2);
+ _fails(() => check(<int>[]).hasLength(1));
+ _fails(() => check(<int>[]).hasLength(2));
+ _fails(() => check(<int>{}).hasLength(1));
+ _fails(() => check(<int>{}).hasLength(2));
+ _fails(() => check(<int>[]).hasLength().isEqualTo(1));
+ _fails(() => check(<int>[0]).hasLength().isEqualTo(0));
+ });
+ test('isEmpty', () {
+ check(<int>[]).isEmpty;
+ check(<int>{}).isEmpty;
+ _fails(() => check([0]).isEmpty);
+ _fails(() => check([0, 1]).isEmpty);
+ _fails(() => check({0}).isEmpty);
+ _fails(() => check({0, 1}).isEmpty);
+ });
+ test('isNotEmpty', () {
+ check([0]).isNotEmpty;
+ check([0, 1]).isNotEmpty;
+ check({0}).isNotEmpty;
+ check({0, 1}).isNotEmpty;
+ _fails(() => check(<int>[]).isNotEmpty);
+ _fails(() => check(<int>{}).isNotEmpty);
+ });
+ });
+ group('String', () {
+ test('contains', () {
+ check('abc').contains('a');
+ check('abc').contains('b');
+ check('abc').contains('c');
+ check('abc').contains('ab');
+ check('abc').contains('bc');
+ check('abc').contains(RegExp('a'));
+ check('abc').contains(RegExp('a.'));
+ check('abc').contains(RegExp('a.c'));
+ check('abc').contains(RegExp('.b.'));
+ _fails(() => check('abc').contains('x'));
+ _fails(() => check('abc').contains('ac'));
+ _fails(() => check('abc').contains(RegExp('ac.')));
+ });
+ test('hasLength', () {
+ check('').hasLength().isZero;
+ check('').hasLength(0);
+ check('a').hasLength().isEqualTo(1);
+ check('a').hasLength(1);
+ check('abc').hasLength().isEqualTo(3);
+ check('abc').hasLength(3);
+ _fails(() => check('abc').hasLength(0));
+ _fails(() => check('abc').hasLength(1));
+ _fails(() => check('abc').hasLength(2));
+ });
+ test('isEqualTo', () {
+ check('').isEqualTo('');
+ check('abc').isEqualTo('abc');
+ check('foobar').isEqualTo('foobar');
+ _fails(() => check('abc').isEqualTo('ab'));
+ _fails(() => check('abc').isEqualTo('xyz'));
+ });
+ test('isNotEqualTo', () {
+ check('abc').isNotEqualTo('ab');
+ check('abc').isNotEqualTo('xyz');
+ _fails(() => check('abc').isNotEqualTo('abc'));
+ _fails(() => check('foobar').isNotEqualTo('foobar'));
+ });
+ test('startsWith', () {
+ check('abc').startsWith('a');
+ check('abc').startsWith('ab');
+ check('abc').startsWith('abc');
+ check('abc').startsWith(RegExp('..c'));
+ check('abc').startsWith(RegExp('.*c'));
+ _fails(() => check('abc').startsWith('b'));
+ _fails(() => check('abc').startsWith('x'));
+ _fails(() => check('abc').startsWith(RegExp('.c')));
+ });
+ });
+ group('type', () {
+ test('isA', () {
+ check(0).isA<int>();
+ _fails(() => check(0.0 as dynamic).isA<int>());
+ });
+ });
+ });
+}
+
+void _fails(void Function() f) {
+ try {
+ f();
+ } on TestFailure {
+ return;
+ }
+ fail('expected to fail');
+}
diff --git a/runtime/lib/errors.cc b/runtime/lib/errors.cc
index 42c0acc..7a3415a 100644
--- a/runtime/lib/errors.cc
+++ b/runtime/lib/errors.cc
@@ -220,10 +220,10 @@
}
// Rethrow an error with a stacktrace.
-DEFINE_NATIVE_ENTRY(Async_rethrow, 0, 2) {
+DEFINE_NATIVE_ENTRY(Error_throwWithStackTrace, 0, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(Instance, error, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Instance, stacktrace, arguments->NativeArgAt(1));
- Exceptions::ReThrow(thread, error, stacktrace);
+ Exceptions::ThrowWithStackTrace(thread, error, stacktrace);
return Object::null();
}
diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h
index 6f6d67d..46b44a3 100644
--- a/runtime/vm/bootstrap_natives.h
+++ b/runtime/vm/bootstrap_natives.h
@@ -160,7 +160,7 @@
V(DateTime_localTimeZoneAdjustmentInSeconds, 0) \
V(AssertionError_throwNew, 3) \
V(AssertionError_throwNewSource, 4) \
- V(Async_rethrow, 2) \
+ V(Error_throwWithStackTrace, 2) \
V(StackTrace_current, 0) \
V(TypeError_throwNew, 4) \
V(FallThroughError_throwNew, 1) \
diff --git a/runtime/vm/exceptions.cc b/runtime/vm/exceptions.cc
index 2ea7550..95787d7 100644
--- a/runtime/vm/exceptions.cc
+++ b/runtime/vm/exceptions.cc
@@ -696,8 +696,9 @@
if (exception.IsNull()) {
exception ^=
Exceptions::Create(Exceptions::kNullThrown, Object::empty_array());
- } else if (exception.ptr() == object_store->out_of_memory() ||
- exception.ptr() == object_store->stack_overflow()) {
+ } else if (existing_stacktrace.IsNull() &&
+ (exception.ptr() == object_store->out_of_memory() ||
+ exception.ptr() == object_store->stack_overflow())) {
use_preallocated_stacktrace = true;
}
// Find the exception handler and determine if the handler needs a
@@ -729,11 +730,17 @@
}
} else {
if (!existing_stacktrace.IsNull()) {
- // If we have an existing stack trace then this better be a rethrow. The
- // reverse is not necessarily true (e.g. Dart_PropagateError can cause
- // a rethrow being called without an existing stacktrace.)
- ASSERT(is_rethrow);
stacktrace = existing_stacktrace.ptr();
+ // If this is not a rethrow, it's a "throw with stacktrace".
+ // Set an Error object's stackTrace field if needed.
+ if (!is_rethrow) {
+ const Field& stacktrace_field =
+ Field::Handle(zone, LookupStackTraceField(exception));
+ if (!stacktrace_field.IsNull() &&
+ (exception.GetField(stacktrace_field) == Object::null())) {
+ exception.SetField(stacktrace_field, stacktrace);
+ }
+ }
} else {
// Get stacktrace field of class Error to determine whether we have a
// subclass of Error which carries around its stack trace.
@@ -917,6 +924,13 @@
ThrowExceptionHelper(thread, exception, stacktrace, true);
}
+void Exceptions::ThrowWithStackTrace(Thread* thread,
+ const Instance& exception,
+ const Instance& stacktrace) {
+ // Null object is a valid exception object.
+ ThrowExceptionHelper(thread, exception, stacktrace, false);
+}
+
void Exceptions::PropagateError(const Error& error) {
ASSERT(!error.IsNull());
Thread* thread = Thread::Current();
diff --git a/runtime/vm/exceptions.h b/runtime/vm/exceptions.h
index 090615c..89090a6 100644
--- a/runtime/vm/exceptions.h
+++ b/runtime/vm/exceptions.h
@@ -32,6 +32,9 @@
DART_NORETURN static void ReThrow(Thread* thread,
const Instance& exception,
const Instance& stacktrace);
+ DART_NORETURN static void ThrowWithStackTrace(Thread* thread,
+ const Instance& exception,
+ const Instance& stacktrace);
DART_NORETURN static void PropagateError(const Error& error);
// Propagate an error to the entry frame, skipping over Dart frames.
diff --git a/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart
index 2a991c9..b9cf805 100644
--- a/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart
+++ b/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart
@@ -195,11 +195,6 @@
}
}
-@patch
-void _rethrow(Object error, StackTrace stackTrace) {
- JS('', 'throw #', dart.createErrorWithStack(error, stackTrace));
-}
-
/// Used by the compiler to implement `async*` functions.
///
/// This is inspired by _AsyncStarStreamController in dart-lang/sdk's
diff --git a/sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart
index e07844b..73637d7 100644
--- a/sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart
+++ b/sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart
@@ -282,6 +282,12 @@
@patch
StackTrace? get stackTrace => dart.stackTraceForError(this);
+
+ @patch
+ static Never _throw(Object error, StackTrace stackTrace) {
+ JS("", "throw #", dart.createErrorWithStack(error, stackTrace));
+ throw "unreachable";
+ }
}
@patch
@@ -592,9 +598,7 @@
}
static String _stringFromJSArray(
- /*=JSArray<int>*/ list,
- int start,
- int? endOrNull) {
+ /*=JSArray<int>*/ list, int start, int? endOrNull) {
int len = list.length;
int end = RangeError.checkValidRange(start, endOrNull, len);
if (start > 0 || end < len) {
diff --git a/sdk/lib/_internal/js_runtime/lib/async_patch.dart b/sdk/lib/_internal/js_runtime/lib/async_patch.dart
index 293a271..4888baf 100644
--- a/sdk/lib/_internal/js_runtime/lib/async_patch.dart
+++ b/sdk/lib/_internal/js_runtime/lib/async_patch.dart
@@ -704,10 +704,3 @@
Iterator<T> get iterator =>
new _SyncStarIterator<T>(JS('', '#()', _outerHelper));
}
-
-@patch
-void _rethrow(Object error, StackTrace stackTrace) {
- error = wrapException(error);
- JS('void', '#.stack = #', error, stackTrace.toString());
- JS('void', 'throw #', error);
-}
diff --git a/sdk/lib/_internal/js_runtime/lib/core_patch.dart b/sdk/lib/_internal/js_runtime/lib/core_patch.dart
index 5726f96..57cdac3 100644
--- a/sdk/lib/_internal/js_runtime/lib/core_patch.dart
+++ b/sdk/lib/_internal/js_runtime/lib/core_patch.dart
@@ -22,7 +22,8 @@
Primitives,
quoteStringForRegExp,
getTraceFromException,
- RuntimeError;
+ RuntimeError,
+ wrapException;
import 'dart:_foreign_helper' show JS;
import 'dart:_native_typed_data' show NativeUint8List;
@@ -187,6 +188,14 @@
@patch
StackTrace? get stackTrace => Primitives.extractStackTrace(this);
+
+ @patch
+ static Never _throw(Object error, StackTrace stackTrace) {
+ error = wrapException(error);
+ JS('void', '#.stack = #', error, stackTrace.toString());
+ JS('', 'throw #', error);
+ throw "unreachable";
+ }
}
@patch
diff --git a/sdk/lib/_internal/vm/lib/async_patch.dart b/sdk/lib/_internal/vm/lib/async_patch.dart
index 4e56a79..363ae71 100644
--- a/sdk/lib/_internal/vm/lib/async_patch.dart
+++ b/sdk/lib/_internal/vm/lib/async_patch.dart
@@ -245,10 +245,6 @@
}
@patch
-@pragma("vm:external-name", "Async_rethrow")
-external void _rethrow(Object error, StackTrace stackTrace);
-
-@patch
class _StreamImpl<T> {
/// The closure implementing the async-generator body that is creating events
/// for this stream.
diff --git a/sdk/lib/_internal/vm/lib/errors_patch.dart b/sdk/lib/_internal/vm/lib/errors_patch.dart
index 0e3a676..aaaf11f 100644
--- a/sdk/lib/_internal/vm/lib/errors_patch.dart
+++ b/sdk/lib/_internal/vm/lib/errors_patch.dart
@@ -21,6 +21,10 @@
@pragma("vm:entry-point")
StackTrace? _stackTrace;
+
+ @patch
+ @pragma("vm:external-name", "Error_throwWithStackTrace")
+ external static Never _throw(Object error, StackTrace stackTrace);
}
class _AssertionError extends Error implements AssertionError {
diff --git a/sdk/lib/async/zone.dart b/sdk/lib/async/zone.dart
index e206152..cb63080 100644
--- a/sdk/lib/async/zone.dart
+++ b/sdk/lib/async/zone.dart
@@ -1410,12 +1410,10 @@
void _rootHandleError(Object error, StackTrace stackTrace) {
_schedulePriorityAsyncCallback(() {
- _rethrow(error, stackTrace);
+ Error.throwWithStackTrace(error, stackTrace);
});
}
-external void _rethrow(Object error, StackTrace stackTrace);
-
R _rootRun<R>(Zone? self, ZoneDelegate? parent, Zone zone, R f()) {
if (identical(Zone._current, zone)) return f();
@@ -1669,7 +1667,7 @@
// Methods that can be customized by the zone specification.
void handleUncaughtError(Object error, StackTrace stackTrace) {
- _rootHandleUncaughtError(null, null, this, error, stackTrace);
+ _rootHandleError(error, stackTrace);
}
Zone fork(
diff --git a/sdk/lib/core/errors.dart b/sdk/lib/core/errors.dart
index 3770a2b..57b3afe 100644
--- a/sdk/lib/core/errors.dart
+++ b/sdk/lib/core/errors.dart
@@ -92,6 +92,23 @@
/// trace filled in the first time they are thrown by a `throw`
/// expression.
external StackTrace? get stackTrace;
+
+ /// Throws [error] with associated stack trace [stackTrace].
+ ///
+ /// If [error] extends [Error] and has not yet been thrown,
+ /// its [stackTrace] is set as well, just as if it was thrown by a `throw`.
+ /// The actual stack trace captured along with the [error],
+ /// or set on [error] if it is an [Error],
+ /// may not be the [stackTrace] object itself,
+ /// but will be a stack trace with the same content.
+ @Since("2.15")
+ static Never throwWithStackTrace(Object error, StackTrace stackTrace) {
+ checkNotNullable(error, "error");
+ checkNotNullable(stackTrace, "stackTrace");
+ _throw(error, stackTrace);
+ }
+
+ external static Never _throw(Object error, StackTrace stackTrace);
}
/// Error thrown by the runtime system when an assert statement fails.
diff --git a/tests/corelib/error_throw_with_stacktrace_test.dart b/tests/corelib/error_throw_with_stacktrace_test.dart
new file mode 100644
index 0000000..026df50
--- /dev/null
+++ b/tests/corelib/error_throw_with_stacktrace_test.dart
@@ -0,0 +1,210 @@
+// Copyright (c) 2021, 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.
+
+import "dart:async";
+import "package:expect/expect.dart";
+import "package:async_helper/async_helper.dart";
+
+// Test of Error.throwWithStackTrace.
+
+main() {
+ // Ensure `systemStack` is different from any other stack tracek.
+ var systemStack = (() => StackTrace.current)();
+
+ // Test that an error can be thrown with a system stack trace..
+ {
+ var error = ArgumentError("e1");
+ try {
+ Error.throwWithStackTrace(error, systemStack);
+ Expect.fail("Didn't throw: e1.1");
+ } on Error catch (e, s) {
+ Expect.identical(error, e, "e1.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e1.3");
+ Expect.isNotNull(error.stackTrace, "e1.4");
+ Expect.equals("$systemStack", "${error.stackTrace}", "e1.5");
+ }
+ }
+
+ // Test that an error can be thrown with a user-created stack trace..
+ {
+ var stringStack = StackTrace.fromString("Nonce");
+ var error = ArgumentError("e2");
+ try {
+ Error.throwWithStackTrace(error, stringStack);
+ Expect.fail("Didn't throw: e2.1");
+ } on Error catch (e, s) {
+ Expect.identical(error, e, "e2.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$stringStack", "$s", "e2.3");
+ Expect.isNotNull(error.stackTrace, "e2.4");
+ Expect.equals("$stringStack", "${error.stackTrace}", "e2.5");
+ }
+ }
+
+ // Test that a non-error object can be thrown too.
+ {
+ var exception = FormatException("e3");
+ try {
+ Error.throwWithStackTrace(exception, systemStack);
+ Expect.fail("Didn't throw: e3.1");
+ } on Exception catch (e, s) {
+ Expect.identical(exception, e, "e3.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e3.3");
+ }
+ }
+
+ // Test that an [Error] not extending {Error} can be thrown,
+ // but doesn't (and cannot) set the stack trace.
+ {
+ var error = CustomError("e4");
+ try {
+ Error.throwWithStackTrace(error, systemStack);
+ Expect.fail("Didn't throw: e4.1");
+ } on Error catch (e, s) {
+ Expect.identical(error, e, "e4.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e4.3");
+ Expect.isNull(error.stackTrace, "e4.4");
+ }
+ }
+
+ // Test that an already set stack trace isn't changed.
+ {
+ var error = ArgumentError("e5");
+ StackTrace? originalStack;
+ try {
+ throw error;
+ } on Error catch (e) {
+ originalStack = e.stackTrace;
+ }
+ Expect.isNotNull(originalStack);
+ Expect.notIdentical(originalStack, systemStack);
+ Expect.notEquals("$originalStack", "");
+ try {
+ Error.throwWithStackTrace(error, systemStack);
+ Expect.fail("Didn't throw: e5.1");
+ } on Error catch (e, s) {
+ Expect.identical(error, e, "e5.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e5.3");
+ // Expect the already-set stack trace to stay.
+ Expect.isNotNull(error.stackTrace, "e5.4");
+ Expect.equals("$originalStack", "${error.stackTrace}", "e5.5");
+ }
+ }
+
+ // Works with OutOfMemoryError.
+ {
+ var error = const OutOfMemoryError();
+ try {
+ Error.throwWithStackTrace(error, systemStack);
+ } on Error catch (e, s) {
+ Expect.identical(error, e);
+ Expect.equals("$systemStack", "$s");
+ }
+ }
+
+ // Works with StackOverflowError.
+ {
+ var error = const StackOverflowError();
+ try {
+ Error.throwWithStackTrace(error, systemStack);
+ } on Error catch (e, s) {
+ Expect.identical(error, e);
+ Expect.equals("$systemStack", "$s");
+ }
+ }
+
+ // Also for live, captured, StackOverflowError.
+ {
+ Object error;
+ Never foo() => foo() + 1;
+ try {
+ foo(); // Force stack overflow.
+ } catch (e, s) {
+ error = e;
+ }
+ // Some platforms might use another error than StackOverflowError.
+ // Should work with whichever object gets here.
+ try {
+ Error.throwWithStackTrace(error, systemStack);
+ } on Error catch (e, s) {
+ Expect.identical(error, e);
+ Expect.equals("$systemStack", "$s");
+ }
+ }
+
+ asyncTest(() async {
+ var theFuture = Future.value(null);
+
+ // Test that throwing inside an asynchronous context can be caught.
+ {
+ var error = ArgumentError("e6");
+ try {
+ await theFuture;
+ Error.throwWithStackTrace(error, systemStack);
+ Expect.fail("Didn't throw: e6.1");
+ await theFuture;
+ } on Error catch (e, s) {
+ Expect.identical(error, e, "e6.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e6.3");
+ Expect.isNotNull(error.stackTrace, "e6.4");
+ Expect.equals("$systemStack", "${error.stackTrace}", "e6.5");
+ }
+ }
+
+ // Test that throwing in asynchronous context can be locally uncaught.
+ {
+ asyncStart();
+ var error = ArgumentError("e7");
+ var future = () async {
+ await theFuture;
+ Error.throwWithStackTrace(error, systemStack);
+ Expect.fail("Didn't throw: e7.1");
+ await theFuture;
+ return null; // Force future type to Future<dynamic>
+ }();
+ future.catchError((e, s) {
+ Expect.identical(error, e, "e7.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e7.3");
+ Expect.isNotNull(error.stackTrace, "e7.4");
+ Expect.equals("$systemStack", "${error.stackTrace}", "e7.5");
+ asyncEnd();
+ });
+ }
+
+ // Test throwing an uncaught async error caught by the Zone.
+ {
+ asyncStart();
+ var error = ArgumentError("e8");
+ await runZonedGuarded(() {
+ // Make an uncaught asynchronous error.
+ (() async {
+ await theFuture;
+ Error.throwWithStackTrace(error, systemStack);
+ Expect.fail("Didn't throw: e8.1");
+ await theFuture;
+ }());
+ }, (e, s) {
+ Expect.identical(error, e, "e8.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e8.3");
+ Expect.isNotNull(error.stackTrace, "e8.4");
+ Expect.equals("$systemStack", "${error.stackTrace}", "e8.5");
+ asyncEnd();
+ });
+ }
+ });
+}
+
+class CustomError implements Error {
+ final String message;
+ CustomError(this.message);
+ StackTrace? get stackTrace => null;
+ String toString() => "CustomError: $message";
+}
diff --git a/tests/corelib_2/error_throw_with_stacktrace_test.dart b/tests/corelib_2/error_throw_with_stacktrace_test.dart
new file mode 100644
index 0000000..303b06c
--- /dev/null
+++ b/tests/corelib_2/error_throw_with_stacktrace_test.dart
@@ -0,0 +1,211 @@
+// Copyright (c) 2021, 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.
+
+// @dart = 2.9
+
+import "dart:async";
+import "package:expect/expect.dart";
+import "package:async_helper/async_helper.dart";
+
+// Test of Error.throwWithStackTrace.
+main() {
+ // Ensure `systemStack` is different from any other stack tracek.
+ var systemStack = (() => StackTrace.current)();
+
+ // Test that an error can be thrown with a system stack trace..
+ {
+ var error = ArgumentError("e1");
+ try {
+ Error.throwWithStackTrace(error, systemStack);
+ Expect.fail("Didn't throw: e1.1");
+ } on Error catch (e, s) {
+ Expect.identical(error, e, "e1.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e1.3");
+ Expect.isNotNull(error.stackTrace, "e1.4");
+ Expect.equals("$systemStack", "${error.stackTrace}", "e1.5");
+ }
+ }
+
+ // Test that an error can be thrown with a user-created stack trace..
+ {
+ var stringStack = StackTrace.fromString("Nonce");
+ var error = ArgumentError("e2");
+ try {
+ Error.throwWithStackTrace(error, stringStack);
+ Expect.fail("Didn't throw: e2.1");
+ } on Error catch (e, s) {
+ Expect.identical(error, e, "e2.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$stringStack", "$s", "e2.3");
+ Expect.isNotNull(error.stackTrace, "e2.4");
+ Expect.equals("$stringStack", "${error.stackTrace}", "e2.5");
+ }
+ }
+
+ // Test that a non-error object can be thrown too.
+ {
+ var exception = FormatException("e3");
+ try {
+ Error.throwWithStackTrace(exception, systemStack);
+ Expect.fail("Didn't throw: e3.1");
+ } on Exception catch (e, s) {
+ Expect.identical(exception, e, "e3.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e3.3");
+ }
+ }
+
+ // Test that an [Error] not extending {Error} can be thrown,
+ // but doesn't (and cannot) set the stack trace.
+ {
+ var error = CustomError("e4");
+ try {
+ Error.throwWithStackTrace(error, systemStack);
+ Expect.fail("Didn't throw: e4.1");
+ } on Error catch (e, s) {
+ Expect.identical(error, e, "e4.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e4.3");
+ Expect.isNull(error.stackTrace, "e4.4");
+ }
+ }
+
+ // Test that an already set stack trace isn't changed.
+ {
+ var error = ArgumentError("e5");
+ StackTrace originalStack;
+ try {
+ throw error;
+ } on Error catch (e) {
+ originalStack = e.stackTrace;
+ }
+ Expect.isNotNull(originalStack);
+ Expect.notIdentical(originalStack, systemStack);
+ Expect.notEquals("$originalStack", "");
+ try {
+ Error.throwWithStackTrace(error, systemStack);
+ Expect.fail("Didn't throw: e5.1");
+ } on Error catch (e, s) {
+ Expect.identical(error, e, "e5.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e5.3");
+ // Expect the already-set stack trace to stay.
+ Expect.isNotNull(error.stackTrace, "e5.4");
+ Expect.equals("$originalStack", "${error.stackTrace}", "e5.5");
+ }
+ }
+
+ // Works with OutOfMemoryError.
+ {
+ var error = const OutOfMemoryError();
+ try {
+ Error.throwWithStackTrace(error, systemStack);
+ } on Error catch (e, s) {
+ Expect.identical(error, e);
+ Expect.equals("$systemStack", "$s");
+ }
+ }
+
+ // Works with StackOverflowError.
+ {
+ var error = const StackOverflowError();
+ try {
+ Error.throwWithStackTrace(error, systemStack);
+ } on Error catch (e, s) {
+ Expect.identical(error, e);
+ Expect.equals("$systemStack", "$s");
+ }
+ }
+
+ // Also for live, captured, StackOverflowError.
+ {
+ Object error;
+ int foo() => foo() + 1;
+ try {
+ foo(); // Force stack overflow.
+ } catch (e, s) {
+ error = e;
+ }
+ // Some platforms might use another error than StackOverflowError.
+ // Should work with whichever object gets here.
+ try {
+ Error.throwWithStackTrace(error, systemStack);
+ } on Error catch (e, s) {
+ Expect.identical(error, e);
+ Expect.equals("$systemStack", "$s");
+ }
+ }
+
+ asyncTest(() async {
+ var theFuture = Future.value(null);
+
+ // Test that throwing inside an asynchronous context can be caught.
+ {
+ var error = ArgumentError("e6");
+ try {
+ await theFuture;
+ Error.throwWithStackTrace(error, systemStack);
+ Expect.fail("Didn't throw: e6.1");
+ await theFuture;
+ } on Error catch (e, s) {
+ Expect.identical(error, e, "e6.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e6.3");
+ Expect.isNotNull(error.stackTrace, "e6.4");
+ Expect.equals("$systemStack", "${error.stackTrace}", "e6.5");
+ }
+ }
+
+ // Test that throwing in asynchronous context can be locally uncaught.
+ {
+ asyncStart();
+ var error = ArgumentError("e7");
+ var future = () async {
+ await theFuture;
+ Error.throwWithStackTrace(error, systemStack);
+ Expect.fail("Didn't throw: e7.1");
+ await theFuture;
+ return null; // Force future type to Future<dynamic>
+ }();
+ future.catchError((e, s) {
+ Expect.identical(error, e, "e7.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e7.3");
+ Expect.isNotNull(error.stackTrace, "e7.4");
+ Expect.equals("$systemStack", "${error.stackTrace}", "e7.5");
+ asyncEnd();
+ });
+ }
+
+ // Test throwing an uncaught async error caught by the Zone.
+ {
+ asyncStart();
+ var error = ArgumentError("e8");
+ await runZonedGuarded(() {
+ // Make an uncaught asynchronous error.
+ (() async {
+ await theFuture;
+ Error.throwWithStackTrace(error, systemStack);
+ Expect.fail("Didn't throw: e8.1");
+ await theFuture;
+ }());
+ }, (e, s) {
+ Expect.identical(error, e, "e8.2");
+ // No not expect *identical* stack trace objects.
+ Expect.equals("$systemStack", "$s", "e8.3");
+ Expect.isNotNull(error.stackTrace, "e8.4");
+ Expect.equals("$systemStack", "${error.stackTrace}", "e8.5");
+ asyncEnd();
+ });
+ }
+ });
+}
+
+class CustomError implements Error {
+ final String message;
+ CustomError(this.message);
+ StackTrace get stackTrace => null;
+ String toString() => "CustomError: $message";
+}
diff --git a/tools/VERSION b/tools/VERSION
index 80c6a13..a3fcafc 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 15
PATCH 0
-PRERELEASE 294
+PRERELEASE 295
PRERELEASE_PATCH 0
\ No newline at end of file