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:
committed by
Commit Queue
parent
de4a69fea3
commit
310d91aee8
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user