Make async error injection set stack trace on errors.

Also removed a bunch of `CheckNotNullable`s that shouldn't be necessary any more. Any remaining non-sound-null-safety code runs today, and no more should be written. (And if it is, it'll mostly just err somewhere else, with a worse error message.)

Tested: New test added. Removed older tests checking for unsound null-safety.
Change-Id: I28626909cd8c1f91db6c61fc2b93042ed1b085dd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/380780
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Lasse Nielsen <lrn@google.com>
This commit is contained in:
Lasse R.H. Nielsen
2024-08-27 12:51:26 +00:00
committed by Commit Queue
parent de4a69fea3
commit 310d91aee8
31 changed files with 413 additions and 204 deletions
+3
View File
@@ -718,6 +718,9 @@ class ClassIdNumbering {
firstClassId + concreteClassCount - 1,
firstClassId + concreteClassCount + abstractClassCount - 1);
}
Range getConcreteSubclassRange(Class klass) =>
_concreteSubclassIdRange[klass]!;
}
// A range of class ids, both ends inclusive.
+11
View File
@@ -1154,6 +1154,17 @@ class Intrinsifier {
codeGen.wrap(value, w.RefType.extern(nullable: true));
b.ref_is_null();
return w.NumType.i32;
case "isSubClassOf":
final baseClass =
(node.arguments.types.single as InterfaceType).classNode;
final range =
translator.classIdNumbering.getConcreteSubclassRange(baseClass);
final object = node.arguments.positional.single;
codeGen.wrap(object, w.RefType.any(nullable: false));
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.emitClassIdRangeCheck([range]);
return w.NumType.i32;
}
}
+13
View File
@@ -53,6 +53,9 @@ class _WasmTransformer extends Transformer {
final Procedure _streamControllerSetOnListen;
final Procedure _streamControllerSetOnResume;
final Procedure _trySetStackTraceForwarder;
final Procedure _trySetStackTrace;
final List<_AsyncStarFrame> _asyncStarFrames = [];
bool _enclosingIsAsyncStar = false;
@@ -104,6 +107,10 @@ class _WasmTransformer extends Transformer {
.getProcedure('dart:async', 'StreamController', 'set:onListen'),
_streamControllerSetOnResume = coreTypes.index
.getProcedure('dart:async', 'StreamController', 'set:onResume'),
_trySetStackTraceForwarder = coreTypes.index
.getTopLevelProcedure('dart:async', '_trySetStackTrace'),
_trySetStackTrace = coreTypes.index
.getProcedure('dart:core', 'Error', '_trySetStackTrace'),
_listFactorySpecializer = ListFactorySpecializer(coreTypes),
_pushPopWasmArrayTransformer = PushPopWasmArrayTransformer(coreTypes);
@@ -716,6 +723,12 @@ class _WasmTransformer extends Transformer {
@override
TreeNode visitStaticInvocation(StaticInvocation node) {
node.transformChildren(this);
// Forward calls in `dart:async` to private `dart:core` method.
if (node.target == _trySetStackTraceForwarder) {
node.target = _trySetStackTrace;
}
return _pushPopWasmArrayTransformer.transformStaticInvocation(
_listFactorySpecializer.transformStaticInvocation(node));
}
@@ -10,24 +10,24 @@ library;
// #1 executeTypesMacro (package:_macros/src/executor/execute_macro.dart:39:21)
// #2 MacroExpansionClient._executeTypesPhase.<anonymous closure> (package:_macros/src/executor/client.dart:219:17)
// #3 runPhase.<anonymous closure> (package:_macros/src/executor/client.dart:385:30)
// #4 _rootRun (dart:async/zone.dart:1399:13)
// #5 _CustomZone.run (dart:async/zone.dart:1301:19)
// #6 _CustomZone.runGuarded (dart:async/zone.dart:1209:7)
// #4 _rootRun (dart:async/zone.dart:1414:13)
// #5 _CustomZone.run (dart:async/zone.dart:1317:19)
// #6 _CustomZone.runGuarded (dart:async/zone.dart:1225:7)
// #7 runPhase (package:_macros/src/executor/client.dart:385:8)
// #8 MacroExpansionClient._executeTypesPhase (package:_macros/src/executor/client.dart:218:43)
// #9 MacroExpansionClient._handleMessage.<anonymous closure> (package:_macros/src/executor/client.dart:145:18)
// #10 _rootRun (dart:async/zone.dart:1399:13)
// #11 _CustomZone.run (dart:async/zone.dart:1301:19)
// #10 _rootRun (dart:async/zone.dart:1414:13)
// #11 _CustomZone.run (dart:async/zone.dart:1317:19)
// #12 withRemoteInstanceZone (package:_macros/src/executor/remote_instance.dart:172:15)
// #13 MacroExpansionClient._handleMessage (package:_macros/src/executor/client.dart:118:11)
// #14 new MacroExpansionClient._.<anonymous closure> (package:_macros/src/executor/client.dart:37:39)
// #15 _rootRunUnary (dart:async/zone.dart:1415:13)
// #16 _CustomZone.runUnary (dart:async/zone.dart:1308:19)
// #17 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1217:7)
// #18 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:365:11)
// #15 _rootRunUnary (dart:async/zone.dart:1430:13)
// #16 _CustomZone.runUnary (dart:async/zone.dart:1324:19)
// #17 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1233:7)
// #18 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:366:11)
// #19 _BufferingStreamSubscription._add (dart:async/stream_impl.dart:297:7)
// #20 _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:784:19)
// #21 _StreamController._add (dart:async/stream_controller.dart:658:7)
// #20 _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:777:19)
// #21 _StreamController._add (dart:async/stream_controller.dart:651:7)
// #22 _StreamController.add (dart:async/stream_controller.dart:606:5)
// #23 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
//
@@ -42,24 +42,24 @@ library;
// #1 executeDeclarationsMacro (package:_macros/src/executor/execute_macro.dart:107:21)
// #2 MacroExpansionClient._executeDeclarationsPhase.<anonymous closure> (package:_macros/src/executor/client.dart:248:11)
// #3 runPhase.<anonymous closure> (package:_macros/src/executor/client.dart:385:30)
// #4 _rootRun (dart:async/zone.dart:1399:13)
// #5 _CustomZone.run (dart:async/zone.dart:1301:19)
// #6 _CustomZone.runGuarded (dart:async/zone.dart:1209:7)
// #4 _rootRun (dart:async/zone.dart:1414:13)
// #5 _CustomZone.run (dart:async/zone.dart:1317:19)
// #6 _CustomZone.runGuarded (dart:async/zone.dart:1225:7)
// #7 runPhase (package:_macros/src/executor/client.dart:385:8)
// #8 MacroExpansionClient._executeDeclarationsPhase (package:_macros/src/executor/client.dart:247:43)
// #9 MacroExpansionClient._handleMessage.<anonymous closure> (package:_macros/src/executor/client.dart:135:18)
// #10 _rootRun (dart:async/zone.dart:1399:13)
// #11 _CustomZone.run (dart:async/zone.dart:1301:19)
// #10 _rootRun (dart:async/zone.dart:1414:13)
// #11 _CustomZone.run (dart:async/zone.dart:1317:19)
// #12 withRemoteInstanceZone (package:_macros/src/executor/remote_instance.dart:172:15)
// #13 MacroExpansionClient._handleMessage (package:_macros/src/executor/client.dart:118:11)
// #14 new MacroExpansionClient._.<anonymous closure> (package:_macros/src/executor/client.dart:37:39)
// #15 _rootRunUnary (dart:async/zone.dart:1415:13)
// #16 _CustomZone.runUnary (dart:async/zone.dart:1308:19)
// #17 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1217:7)
// #18 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:365:11)
// #15 _rootRunUnary (dart:async/zone.dart:1430:13)
// #16 _CustomZone.runUnary (dart:async/zone.dart:1324:19)
// #17 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1233:7)
// #18 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:366:11)
// #19 _BufferingStreamSubscription._add (dart:async/stream_impl.dart:297:7)
// #20 _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:784:19)
// #21 _StreamController._add (dart:async/stream_controller.dart:658:7)
// #20 _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:777:19)
// #21 _StreamController._add (dart:async/stream_controller.dart:651:7)
// #22 _StreamController.add (dart:async/stream_controller.dart:606:5)
// #23 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
//
@@ -74,24 +74,24 @@ library;
// #1 executeDefinitionMacro (package:_macros/src/executor/execute_macro.dart:167:21)
// #2 MacroExpansionClient._executeDefinitionsPhase.<anonymous closure> (package:_macros/src/executor/client.dart:276:17)
// #3 runPhase.<anonymous closure> (package:_macros/src/executor/client.dart:385:30)
// #4 _rootRun (dart:async/zone.dart:1399:13)
// #5 _CustomZone.run (dart:async/zone.dart:1301:19)
// #6 _CustomZone.runGuarded (dart:async/zone.dart:1209:7)
// #4 _rootRun (dart:async/zone.dart:1414:13)
// #5 _CustomZone.run (dart:async/zone.dart:1317:19)
// #6 _CustomZone.runGuarded (dart:async/zone.dart:1225:7)
// #7 runPhase (package:_macros/src/executor/client.dart:385:8)
// #8 MacroExpansionClient._executeDefinitionsPhase (package:_macros/src/executor/client.dart:275:43)
// #9 MacroExpansionClient._handleMessage.<anonymous closure> (package:_macros/src/executor/client.dart:140:18)
// #10 _rootRun (dart:async/zone.dart:1399:13)
// #11 _CustomZone.run (dart:async/zone.dart:1301:19)
// #10 _rootRun (dart:async/zone.dart:1414:13)
// #11 _CustomZone.run (dart:async/zone.dart:1317:19)
// #12 withRemoteInstanceZone (package:_macros/src/executor/remote_instance.dart:172:15)
// #13 MacroExpansionClient._handleMessage (package:_macros/src/executor/client.dart:118:11)
// #14 new MacroExpansionClient._.<anonymous closure> (package:_macros/src/executor/client.dart:37:39)
// #15 _rootRunUnary (dart:async/zone.dart:1415:13)
// #16 _CustomZone.runUnary (dart:async/zone.dart:1308:19)
// #17 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1217:7)
// #18 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:365:11)
// #15 _rootRunUnary (dart:async/zone.dart:1430:13)
// #16 _CustomZone.runUnary (dart:async/zone.dart:1324:19)
// #17 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1233:7)
// #18 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:366:11)
// #19 _BufferingStreamSubscription._add (dart:async/stream_impl.dart:297:7)
// #20 _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:784:19)
// #21 _StreamController._add (dart:async/stream_controller.dart:658:7)
// #20 _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:777:19)
// #21 _StreamController._add (dart:async/stream_controller.dart:651:7)
// #22 _StreamController.add (dart:async/stream_controller.dart:606:5)
// #23 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
//
+8
View File
@@ -182,4 +182,12 @@ DEFINE_NATIVE_ENTRY(Error_throwWithStackTrace, 0, 2) {
return Object::null();
}
// Sets stack trace on object, if it's an `Error` with no `Error.stackTrace`.
DEFINE_NATIVE_ENTRY(Error_trySetStackTrace, 0, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(Instance, error, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Instance, stacktrace, arguments->NativeArgAt(1));
Exceptions::TrySetStackTrace(zone, error, stacktrace);
return Object::null();
}
} // namespace dart
+1
View File
@@ -140,6 +140,7 @@ namespace dart {
V(AssertionError_throwNew, 3) \
V(AssertionError_throwNewSource, 5) \
V(Error_throwWithStackTrace, 2) \
V(Error_trySetStackTrace, 2) \
V(StackTrace_current, 0) \
V(TypeError_throwNew, 4) \
V(Stopwatch_now, 0) \
+12 -6
View File
@@ -822,12 +822,7 @@ static void ThrowExceptionHelper(Thread* thread,
// 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);
}
Exceptions::TrySetStackTrace(zone, exception, stacktrace);
}
} else {
// Get stacktrace field of class Error to determine whether we have a
@@ -1023,6 +1018,17 @@ void Exceptions::ThrowWithStackTrace(Thread* thread,
/*bypass_debugger=*/false);
}
void Exceptions::TrySetStackTrace(Zone* zone,
const Instance& error,
const Instance& stacktrace) {
const Field& stacktrace_field =
Field::Handle(zone, dart::LookupStackTraceField(error));
if (!stacktrace_field.IsNull() &&
(error.GetField(stacktrace_field) == Object::null())) {
error.SetField(stacktrace_field, stacktrace);
}
}
void Exceptions::PropagateError(const Error& error) {
ASSERT(!error.IsNull());
Thread* thread = Thread::Current();
+5
View File
@@ -41,6 +41,11 @@ class Exceptions : AllStatic {
// Propagate an error to the entry frame, skipping over Dart frames.
DART_NORETURN static void PropagateToEntry(const Error& error);
// Sets `Error._stackTrace` if possible.
static void TrySetStackTrace(Zone* zone,
const Instance& exception,
const Instance& stacktrace);
// Helpers to create and throw errors.
static StackTracePtr CurrentStackTrace();
static ScriptPtr GetCallerScript(DartFrameIterator* iterator);
@@ -12,6 +12,13 @@ import 'dart:_foreign_helper'
show JS, JS_RAW_EXCEPTION, JSExportName, RAW_DART_FUNCTION_REF;
import 'dart:_runtime' as dart;
@patch
void _trySetStackTrace(Object error, StackTrace stackTrace) {
if (error is Error) {
dart.trySetStackTrace(error, stackTrace);
}
}
/// This function adapts ES6 generators to implement Dart's async/await.
///
/// It's designed to interact with Dart's Future and follow Dart async/await
@@ -208,6 +208,15 @@ StackTrace? stackTraceForError(Error error) {
return stackTrace(jsError);
}
/// Sets stack trace on error, if possible.
void trySetStackTrace(Error error, StackTrace stackTrace) {
var jsError = JS('', '#[#]', error, _jsError);
if (jsError == null) {
// Link Dart stack trace to error object.
JS('', 'new #(#, #)', RethrownDartError, error, stackTrace);
}
}
/// Implements `rethrow` of [error], allowing rethrow in an expression context.
///
/// Note: [error] must be the raw JS error caught in the JS catch, not the
@@ -10,6 +10,7 @@ import 'dart:_js_helper'
ExceptionAndStackTrace,
convertDartClosureToJS,
getTraceFromException,
Primitives,
requiresPreamble,
wrapException,
unwrapException;
@@ -19,6 +20,13 @@ import 'dart:_foreign_helper'
import 'dart:_async_status_codes' as async_status_codes;
@patch
void _trySetStackTrace(Object error, StackTrace stackTrace) {
if (error is Error) {
Primitives.trySetStackTrace(error, stackTrace);
}
}
@patch
class _AsyncRun {
@patch
@@ -1098,6 +1098,15 @@ class Primitives {
if (jsError == null) return null;
return getTraceFromException(jsError);
}
static void trySetStackTrace(Error error, StackTrace stackTrace) {
var jsError = JS('', r'#.$thrownJsError', error);
if (jsError == null) {
jsError = wrapException(error);
JS('', r'#.$thrownJsError = #', error, jsError);
JS('void', '#.stack = #', jsError, stackTrace.toString());
}
}
}
/// Called by generated code to throw an illegal-argument exception,
@@ -1193,7 +1202,7 @@ String checkString(value) {
return value;
}
/// Wrap the given Dart object and record a stack trace.
/// Wrap the given Dart object as a JS `Error` that can carry a stack trace.
///
/// The code in [unwrapException] deals with getting the original Dart
/// object out of the wrapper again.
@@ -17,6 +17,10 @@ part "timer_patch.dart";
@pragma("vm:external-name", "DartAsync_fatal")
external _fatal(msg);
@patch
@pragma("vm:external-name", "Error_trySetStackTrace")
external void _trySetStackTrace(Object error, StackTrace stackTrace);
// This function is used when lowering `await for` statements.
void _asyncStarMoveNextHelper(var stream) {
if (stream is! _StreamImpl) {
+7 -2
View File
@@ -6,14 +6,19 @@ import 'dart:_wasm';
part 'timer_patch.dart';
// Modular kernel transformer will make calls to this method be re-directed to
// call dart:core:Error._trySetStackTrace instead.
@patch
external void _trySetStackTrace(Object error, StackTrace stackTrace);
typedef _AsyncResumeFun = WasmFunction<
void Function(
_AsyncSuspendState,
// Value of the last `await`
Object?,
// If the last `await` throwed an error, the error value
// If the last `await` threw an error, the error value
Object?,
// If the last `await` throwed an error, the stack trace
// If the last `await` threw an error, the stack trace
StackTrace?)>;
@pragma("wasm:entry-point")
@@ -23,6 +23,15 @@ class Error {
@pragma("wasm:entry-point")
StackTrace? _stackTrace;
static void _trySetStackTrace(Object object, StackTrace stackTrace) {
// Guard against implementors of [Error] that do not have the stack trace
// field by ensuring the error object is a direct/indirect subclass.
if (isSubClassOf<Error>(object)) {
final error = unsafeCast<Error>(object);
error._stackTrace ??= stackTrace;
}
}
}
class _Error extends Error {
+6
View File
@@ -395,3 +395,9 @@ extension WasmExternRefToJSAny on WasmExternRef {
// on JS interop types are assumed to be JS interop functions, not methods that
// are patched in patch files. So instead we just use a plain function here.
external WasmExternRef? externRefForJSAny(JSAny object);
// Tests whether the given object's class is a subclass of T.
//
// NOTICE: If the object's class is a subtype of T but not a subclass this will
// return `false`.
external bool isSubClassOf<T>(Object object);
+3 -1
View File
@@ -13,9 +13,11 @@ final class AsyncError implements Error {
final StackTrace stackTrace;
AsyncError(Object error, StackTrace? stackTrace)
: error = checkNotNullable(error, "error"),
: error = error,
stackTrace = stackTrace ?? defaultStackTrace(error);
AsyncError._(this.error, this.stackTrace);
/// A default stack trace for an error.
///
/// If [error] is an [Error] and it has an [Error.stackTrace],
+5 -13
View File
@@ -245,16 +245,9 @@ abstract class _BroadcastStreamController<T>
}
void addError(Object error, [StackTrace? stackTrace]) {
checkNotNullable(error, "error");
if (!_mayAddEvent) throw _addEventError();
AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
if (replacement != null) {
error = replacement.error;
stackTrace = replacement.stackTrace;
} else {
stackTrace ??= AsyncError.defaultStackTrace(error);
}
_sendError(error, stackTrace);
AsyncError(:error, :stackTrace) = _interceptUserError(error, stackTrace);
_addError(error, stackTrace);
}
Future<void> close() {
@@ -476,14 +469,13 @@ class _AsBroadcastStreamController<T> extends _SyncBroadcastStreamController<T>
}
void addError(Object error, [StackTrace? stackTrace]) {
checkNotNullable(error, "error");
stackTrace ??= AsyncError.defaultStackTrace(error);
if (!_mayAddEvent) throw _addEventError();
AsyncError(:error, :stackTrace) = _interceptUserError(error, stackTrace);
if (!isClosed && _isFiring) {
_addPendingEvent(new _DelayedError(error, stackTrace));
return;
}
if (!_mayAddEvent) throw _addEventError();
_sendError(error, stackTrace);
_addError(error, stackTrace);
_flushPending();
}
+9 -24
View File
@@ -313,12 +313,7 @@ abstract interface class Future<T> {
result = computation();
} catch (error, stackTrace) {
var future = new _Future<T>();
AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
if (replacement != null) {
future._asyncCompleteError(replacement.error, replacement.stackTrace);
} else {
future._asyncCompleteError(error, stackTrace);
}
_asyncCompleteWithErrorCallback(future, error, stackTrace);
return future;
}
return result is Future<T> ? result : _Future<T>.value(result);
@@ -373,16 +368,7 @@ abstract interface class Future<T> {
/// final error = await getFuture(); // Throws.
/// ```
factory Future.error(Object error, [StackTrace? stackTrace]) {
// TODO(40614): Remove once non-nullability is sound.
checkNotNullable(error, "error");
if (!identical(Zone.current, _rootZone)) {
AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
if (replacement != null) {
error = replacement.error;
stackTrace = replacement.stackTrace;
}
}
stackTrace ??= AsyncError.defaultStackTrace(error);
AsyncError(:error, :stackTrace) = _interceptUserError(error, stackTrace);
return new _Future<T>.immediateError(error, stackTrace);
}
@@ -1335,27 +1321,26 @@ abstract interface class Completer<T> {
// Helper function completing a _Future with error, but checking the zone
// for error replacement and missing stack trace first.
// Only used for errors that are *caught*.
// A user provided error object should use `_interceptUserError` which
// also sets `Error.stackTrace`.
void _completeWithErrorCallback(
_Future result, Object error, StackTrace? stackTrace) {
AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
_Future result, Object error, StackTrace stackTrace) {
var replacement = _interceptError(error, stackTrace);
if (replacement != null) {
error = replacement.error;
stackTrace = replacement.stackTrace;
} else {
stackTrace ??= AsyncError.defaultStackTrace(error);
}
result._completeError(error, stackTrace);
}
// Like [_completeWithErrorCallback] but completes asynchronously.
void _asyncCompleteWithErrorCallback(
_Future result, Object error, StackTrace? stackTrace) {
AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
_Future result, Object error, StackTrace stackTrace) {
var replacement = _interceptError(error, stackTrace);
if (replacement != null) {
error = replacement.error;
stackTrace = replacement.stackTrace;
} else {
stackTrace ??= AsyncError.defaultStackTrace(error);
}
result._asyncCompleteError(error, stackTrace);
}
+50 -9
View File
@@ -4,6 +4,55 @@
part of dart.async;
/// Sets [stackTrace] as [Error.stackTrace] on [error], if that is an [Error].
///
/// If [error] is not an instance of a class which extends [Error],
/// or if it already has an [Error.stackTrace] set, nothing happens.
@pragma("wasm:entry-point")
external void _trySetStackTrace(Object error, StackTrace stackTrace);
/// Calls the [Zone.errorCallback] of the current zone with the error and stack.
///
/// If a non-`null` result is returned, it tries to set the stack trace
/// on the returned error to the returned stack trace.
AsyncError? _interceptError(Object error, StackTrace? stackTrace) {
var zone = Zone._current;
if (identical(zone, _rootZone)) return null;
var replacement = zone.errorCallback(error, stackTrace);
if (replacement == null) return null;
_trySetStackTrace(replacement.error, replacement.stackTrace);
return replacement;
}
/// Used for user-provided error and stack trace that are to become async
/// errors.
///
/// Allows `Zone.current.errorCallback` to intercept and modify the error,
/// and sets the stack trace on the error as if it was thrown.
AsyncError _interceptUserError(Object error, StackTrace? stackTrace) {
var zone = Zone.current;
if (!identical(zone, _rootZone)) {
var replacement = _interceptError(error, stackTrace);
if (replacement != null) return replacement;
}
if (stackTrace == null) {
// Inlines `AsyncError.defaultStackTrace`, to reuse the `is Error` check
// result to also not do `_trySetStackTrace` on known non-`Error`s.
if (error is Error) {
stackTrace = error.stackTrace;
if (stackTrace == null) {
stackTrace = StackTrace.empty;
_trySetStackTrace(error, stackTrace);
}
} else {
stackTrace = StackTrace.empty;
}
} else {
_trySetStackTrace(error, stackTrace); // Maybe it's an Error.
}
return AsyncError._(error, stackTrace);
}
abstract class _Completer<T> implements Completer<T> {
@pragma("wasm:entry-point")
@pragma("vm:entry-point")
@@ -14,16 +63,8 @@ abstract class _Completer<T> implements Completer<T> {
@pragma("wasm:entry-point")
void completeError(Object error, [StackTrace? stackTrace]) {
// TODO(40614): Remove once non-nullability is sound.
checkNotNullable(error, "error");
if (!future._mayComplete) throw new StateError("Future already completed");
AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
if (replacement != null) {
error = replacement.error;
stackTrace = replacement.stackTrace;
} else {
stackTrace ??= AsyncError.defaultStackTrace(error);
}
AsyncError(:error, :stackTrace) = _interceptUserError(error, stackTrace);
_completeError(error, stackTrace);
}
+2 -3
View File
@@ -212,10 +212,9 @@ abstract mixin class Stream<T> {
/// stack trace as well.
@Since("2.5")
factory Stream.error(Object error, [StackTrace? stackTrace]) {
// TODO(40614): Remove once non-nullability is sound.
checkNotNullable(error, "error");
AsyncError(:error, :stackTrace) = _interceptUserError(error, stackTrace);
return (_AsyncStreamController<T>(null, null, null, null)
.._addError(error, stackTrace ?? AsyncError.defaultStackTrace(error))
.._addError(error, stackTrace)
.._closeUnchecked())
.stream;
}
+1 -8
View File
@@ -608,15 +608,8 @@ abstract class _StreamController<T> implements _StreamControllerBase<T> {
/// Send or enqueue an error event.
void addError(Object error, [StackTrace? stackTrace]) {
checkNotNullable(error, "error");
if (!_mayAddEvent) throw _badEventState();
AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
if (replacement != null) {
error = replacement.error;
stackTrace = replacement.stackTrace;
} else {
stackTrace ??= AsyncError.defaultStackTrace(error);
}
AsyncError(:error, :stackTrace) = _interceptUserError(error, stackTrace);
_addError(error, stackTrace);
}
+1
View File
@@ -301,6 +301,7 @@ class _BufferingStreamSubscription<T>
}
void _addError(Object error, StackTrace stackTrace) {
_trySetStackTrace(error, stackTrace);
if (_isCanceled) return;
if (_canFire) {
_sendError(error, stackTrace); // Reports cancel after sending.
+6 -8
View File
@@ -9,13 +9,11 @@ _runUserCode<T>(T userCode(), onSuccess(T value),
onError(Object error, StackTrace stackTrace)) {
try {
onSuccess(userCode());
} catch (e, s) {
AsyncError? replacement = Zone.current.errorCallback(e, s);
if (replacement == null) {
onError(e, s);
} catch (error, stackTrace) {
AsyncError? replacement = _interceptError(error, stackTrace);
if (replacement != null) {
onError(replacement.error, replacement.stackTrace);
} else {
var error = replacement.error;
var stackTrace = replacement.stackTrace;
onError(error, stackTrace);
}
}
@@ -35,7 +33,7 @@ void _cancelAndError(StreamSubscription subscription, _Future future,
void _cancelAndErrorWithReplacement(StreamSubscription subscription,
_Future future, Object error, StackTrace stackTrace) {
AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
AsyncError? replacement = _interceptError(error, stackTrace);
if (replacement != null) {
error = replacement.error;
stackTrace = replacement.stackTrace;
@@ -168,7 +166,7 @@ class _ForwardingStreamSubscription<S, T>
void _addErrorWithReplacement(
_EventSink sink, Object error, StackTrace stackTrace) {
AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
var replacement = _interceptError(error, stackTrace);
if (replacement != null) {
error = replacement.error;
stackTrace = replacement.stackTrace;
-1
View File
@@ -213,7 +213,6 @@ class _HandlerEventSink<S, T> implements EventSink<S> {
}
void addError(Object error, [StackTrace? stackTrace]) {
checkNotNullable(error, "error");
var sink = _sink;
if (sink == null) {
throw StateError("Sink is closed");
+27 -15
View File
@@ -154,20 +154,37 @@ typedef RegisterBinaryCallbackHandler
/// The type of a custom [Zone.errorCallback] implementation function.
///
/// Receives the [Zone] that the handler was registered on as [self],
/// a delegate forwarding to the handlers of [self]'s parent zone as [parent],
/// and the current zone where the error was uncaught as [zone],
/// which will have [self] as a parent zone.
///
/// The [error] and [stackTrace] are the error and stack trace
/// passed to [Zone.errorCallback] of [zone].
///
/// The function should return either `null` if it doesn't want
/// to replace the original error and stack trace,
/// or an [AsyncError] containing a replacement error and stack trace
/// which will be used to replace the originals.
/// The function will be called when a synchronous error becomes an
/// asynchronous error, either by being caught, for example by a `Future.then`
/// callback throwing, or when used to create an asynchronous error
/// programmatically, for example using `Future.error` or `Completer.complete`.
///
/// The error callback handler must not throw.
/// If the function does not want to replace the error or stack trace,
/// it should just return `parent.errorCallback(zone, error, stackTrace)`,
/// giving the parent zone the chance to intercept.
///
/// If the function does want to replace the error and/or stack trace,
/// say with `error2` and `stackTrace2`, it should still allow the
/// parent zone to intercept those errors, for examples as:
/// ```dart
/// return parent.errorCallback(zone, error, stackTrace) ??
/// AsyncError(error, stackTrace);
/// ```
///
/// The function returns either `null` if the original error and stack trace
/// is unchanged, avoiding any allocation in the most common case,
/// or an [AsyncError] containing a replacement error and stack trace
/// which will be used in place of the originals as the asynchronous error.
///
/// The [self] [Zone] is the zone the handler was registered on,
/// the [parent] delegate forwards to the handlers of [self]'s parent zone,
/// and [zone] is the current zone where the error was uncaught,
/// which will have [self] as an ancestor zone.
///
/// The error callback handler **must not** throw.
///
/// The function must only access zone-related functionality through
/// [self], [parent] or [zone].
@@ -989,7 +1006,6 @@ base class _ZoneDelegate implements ZoneDelegate {
}
AsyncError? errorCallback(Zone zone, Object error, StackTrace? stackTrace) {
checkNotNullable(error, "error");
var implementation = _delegationTarget._errorCallback;
_Zone implZone = implementation.zone;
if (identical(implZone, _rootZone)) return null;
@@ -1338,7 +1354,6 @@ base class _CustomZone extends _Zone {
}
AsyncError? errorCallback(Object error, StackTrace? stackTrace) {
checkNotNullable(error, "error");
var implementation = this._errorCallback;
final _Zone implementationZone = implementation.zone;
if (identical(implementationZone, _rootZone)) return null;
@@ -1745,7 +1760,6 @@ R runZoned<R>(R body(),
{Map<Object?, Object?>? zoneValues,
ZoneSpecification? zoneSpecification,
@Deprecated("Use runZonedGuarded instead") Function? onError}) {
checkNotNullable(body, "body");
if (onError != null) {
// TODO: Remove this when code have been migrated off using [onError].
if (onError is! void Function(Object, StackTrace)) {
@@ -1789,8 +1803,6 @@ R runZoned<R>(R body(),
@Since("2.8")
R? runZonedGuarded<R>(R body(), void onError(Object error, StackTrace stack),
{Map<Object?, Object?>? zoneValues, ZoneSpecification? zoneSpecification}) {
checkNotNullable(body, "body");
checkNotNullable(onError, "onError");
_Zone parentZone = Zone._current;
HandleUncaughtErrorHandler errorHandler = (Zone self, ZoneDelegate parent,
Zone zone, Object error, StackTrace stackTrace) {
+157
View File
@@ -0,0 +1,157 @@
// Copyright (c) 2024, 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';
// Checks that an `Error` object that is "thrown" through async functionality
// gets a stack trace set.
import 'package:expect/expect.dart';
import 'package:async_helper/async_helper.dart';
main() async {
asyncStart();
for ((String, Object, StackTrace, Object, String, AsyncError?)
Function() setUp in [
() {
// Without error callback. Inject and expect same fresh error.
var error = StateError("Quo!");
var stack = StackTrace.fromString("Secret stack");
return ("", error, stack, error, stack.toString(), null);
},
() {
// With error callback, same error, new stack.
var error = StateError("Quo");
var stack = StackTrace.fromString("Secret stack");
var stack2 = StackTrace.fromString("Other stack");
var errorCallback = AsyncError(error, stack2);
return (
"errorCallback: fresh both",
error,
stack,
error,
stack2.toString(),
errorCallback
);
},
() {
// With error callback, new error, stack.
var error = StateError("Quo");
var stack = StackTrace.fromString("Secret stack");
var error2 = StateError("Quid");
var stack2 = StackTrace.fromString("Other stack");
var errorCallback = AsyncError(error2, stack2);
return (
"errorCallback: fresh stack",
error,
stack,
error2,
stack2.toString(),
errorCallback
);
},
]) {
Future<void> test(String name,
Future<(Object, StackTrace)> Function(Object, StackTrace) body) {
var (msg, error, stack, expectError, expectStackString, errorCallback) =
setUp();
print("Test: $name${msg.isNotEmpty ? ", $msg" : msg}");
return asyncTest(() => runZoned(() => body(error, stack),
zoneSpecification: ZoneSpecification(
errorCallback: (s, p, z, e, st) => errorCallback)).then((es) {
var (e, s) = es;
Expect.identical(expectError, e, name);
Expect.equals(expectStackString, s.toString(), name);
Expect.equals(
expectStackString, (e as Error).stackTrace.toString(), name);
}));
}
// Sanity check: Plain throws.
() {
var (name, error, stack, error2, stack2, errorCallback) = setUp();
try {
Error.throwWithStackTrace(error, stack);
} catch (e, s) {
Expect.identical(error, e, "Error.throw");
Expect.equals(stack.toString(), s.toString(), "Error.throw");
Expect.equals(stack.toString(), (e as Error).stackTrace.toString(),
"Error.throw");
}
}();
// Futures.
// Immediate error.
await test("Future.error",
(error, stack) => futureError(Future.error(error, stack)));
// Through controller, async.
await test("Completer().completeError", (error, stack) {
var completer = Completer<void>();
completer.completeError(error, stack);
return futureError(completer.future);
});
// Through controller, sync.
await test("Completer.sync().completeError", (error, stack) {
var completer = Completer<void>.sync();
var future = completer.future..ignore();
completer.completeError(error, stack);
return futureError(future);
});
// Streams.
// Singleton error.
await test("Stream.error",
(error, stack) => streamError(Stream.error(error, stack)));
// Controller errors.
for (var broadcast in [false, true]) {
for (var sync in [false, true]) {
await test(
"Stream${broadcast ? ".broadcast" : ""}${sync ? "(sync)" : ""}",
(error, stack) {
var controller = broadcast
? StreamController<void>.broadcast(sync: sync)
: StreamController<void>(sync: sync);
var future = streamError(controller.stream)..ignore();
controller.addError(error, stack);
return future;
});
}
}
}
asyncEnd();
}
// --------------------------------------------------------------------
// Helper functions
(Object, StackTrace) captureError(Object e, StackTrace s) => (e, s);
(Object, StackTrace) fail(_) {
Expect.fail("No error from future") as Never;
}
/// Captures error of future.
Future<(Object, StackTrace)> futureError(Future<void> future) =>
future.then(fail, onError: captureError);
/// Captures first error of stream.
Future<(Object, StackTrace)> streamError(Stream<void> stream) {
var c = Completer<(Object, StackTrace)>();
var sub = stream.listen((_) {
// No values expected.
}, onError: (Object e, StackTrace s) {
c.complete((e, s));
}, onDone: () {
Expect.fail("No error from stream");
}, cancelOnError: true);
return c.future;
}
-14
View File
@@ -1,14 +0,0 @@
// 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.
import 'dart:async';
import 'package:expect/expect.dart';
main() {
// The error cannot be null.
Expect.throwsTypeError(() {
Future.error(null as dynamic);
});
}
@@ -1,49 +0,0 @@
// 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.
import 'dart:async';
import 'package:expect/expect.dart';
main() {
// Single-cast async.
var controller = StreamController();
Expect.throwsTypeError(() {
controller.addError(null as dynamic);
});
Expect.throwsTypeError(() {
controller.sink.addError(null as dynamic);
});
// Single-cast sync.
controller = StreamController(sync: true);
Expect.throwsTypeError(() {
controller.addError(null as dynamic);
});
Expect.throwsTypeError(() {
controller.sink.addError(null as dynamic);
});
// Broadcast async.
controller = StreamController.broadcast();
Expect.throwsTypeError(() {
controller.addError(null as dynamic);
});
Expect.throwsTypeError(() {
controller.sink.addError(null as dynamic);
});
// Broadcast sync.
controller = StreamController.broadcast(sync: true);
Expect.throwsTypeError(() {
controller.addError(null as dynamic);
});
Expect.throwsTypeError(() {
controller.sink.addError(null as dynamic);
});
}
@@ -1,14 +0,0 @@
// 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.
import 'dart:async';
import 'package:expect/expect.dart';
main() {
// The error cannot be null.
Expect.throwsTypeError(() {
AsyncError(null as dynamic, StackTrace.current);
});
}
@@ -48,9 +48,12 @@ Null expectErrorOnly(e, s) {
}
AsyncError? replace(self, parent, zone, e, s) {
if (e == "ignore") return null; // For testing handleError throwing.
Expect.identical(error1, e); // Ensure replacement only called once
return new AsyncError(error2, stack2);
if (identical(error1, e)) {
// Give parent chance to replace your replacements.
return parent.errorCallback(zone, error2, stack2) ??
AsyncError(error2, stack2);
}
return parent.errorCallback(zone, e, s);
}
var replaceZoneSpec = new ZoneSpecification(errorCallback: replace);