stack_trace
stack_trace copied to clipboard
Frame.caller() is broken inside `Chain.capture`
The VM started giving us different, significantly truncated StackTrace instances when running in the zone for Chain.capture. Frame.caller() relies on parsing StackTrace.current which no longer contains the caller we were looking for.
Root Cause: https://github.com/dart-lang/sdk/issues/46326
It's possible we could work around this with something like Chain.current() instead of StackTrace.current, but even with that we'd need to do specific detection that we're in the chain zone and handle the result differently.
Inside the Chain.capture the Chain.current() looks like
package:stack_trace/repro.dart 8:17 main.callee
===== asynchronous gap ===========================
dart:async/zone.dart 1286:19 _CustomZone.registerUnaryCallback
dart:async-patch/async_patch.dart 45:22 _asyncThenWrapperHelper
package:stack_trace/repro.dart main.callee
package:stack_trace/repro.dart 12:17 main.caller
package:stack_trace/repro.dart 11:22 main.caller
package:stack_trace/repro.dart 16:17 main.<fn>
package:stack_trace/repro.dart 15:23 main.<fn>
package:stack_trace/src/chain.dart 94:24 Chain.capture.<fn>
dart:async/zone.dart 1354:13 _rootRun
dart:async/zone.dart 1258:19 _CustomZone.run
dart:async/zone.dart 1789:10 _runZoned
dart:async/zone.dart 1711:10 runZoned
package:stack_trace/src/chain.dart 92:12 Chain.capture
package:stack_trace/repro.dart 15:15 main
dart:isolate-patch/isolate_patch.dart 283:19 _delayEntrypointInvocation.<fn>
dart:isolate-patch/isolate_patch.dart 184:12 _RawReceivePortImpl._handleMessage
While outside of it, it looks like
package:stack_trace/repro.dart 8:17 main.callee
===== asynchronous gap ===========================
package:stack_trace/repro.dart 12:5 main.caller
===== asynchronous gap ===========================
package:stack_trace/repro.dart 15:3 main
Slightly related:
The details that we get for Chain.current also changed in that 2.12.0-29.0.dev SDK from what it had been earlier. This isn't a blocker for tweaking the logic to account for the new behavior, since we have a lower bound SDK constraint there anyway, but it's a sign that we should be careful of what details we rely on.
In earlier SDKs the Chain.current inside the chain zone would have been
package:stack_trace/repro.dart 8:17 main.callee
===== asynchronous gap ===========================
dart:async/zone.dart 1121:19 _CustomZone.registerUnaryCallback
dart:async-patch/async_patch.dart 83:23 _asyncThenWrapperHelper
package:stack_trace/repro.dart main.callee
package:stack_trace/repro.dart 12:17 main.caller
package:stack_trace/repro.dart 16:17 main.<fn>
package:stack_trace/src/chain.dart 102:24 Chain.capture.<fn>
dart:async/zone.dart 1190:13 _rootRun
dart:async/zone.dart 1093:19 _CustomZone.run
dart:async/zone.dart 1630:10 _runZoned
dart:async/zone.dart 1550:10 runZoned
package:stack_trace/src/chain.dart 100:12 Chain.capture
package:stack_trace/repro.dart 15:15 main
dart:isolate-patch/isolate_patch.dart 311:19 _startIsolate.<fn>
dart:isolate-patch/isolate_patch.dart 184:12 _RawReceivePortImpl._handleMessage
while in 2.12.0-29.0.dev and newer SDKs the main.caller and main.<fn> frames show up twice:
package:stack_trace/repro.dart 8:17 main.callee
===== asynchronous gap ===========================
dart:async/zone.dart 1121:19 _CustomZone.registerUnaryCallback
dart:async-patch/async_patch.dart 40:23 _asyncThenWrapperHelper
package:stack_trace/repro.dart main.callee
package:stack_trace/repro.dart 12:17 main.caller
package:stack_trace/repro.dart 11:22 main.caller
package:stack_trace/repro.dart 16:17 main.<fn>
package:stack_trace/repro.dart 15:23 main.<fn>
package:stack_trace/src/chain.dart 102:24 Chain.capture.<fn>
dart:async/zone.dart 1190:13 _rootRun
dart:async/zone.dart 1093:19 _CustomZone.run
dart:async/zone.dart 1630:10 _runZoned
dart:async/zone.dart 1550:10 runZoned
package:stack_trace/src/chain.dart 100:12 Chain.capture
package:stack_trace/repro.dart 15:15 main
dart:isolate-patch/isolate_patch.dart 311:19 _startIsolate.<fn>
dart:isolate-patch/isolate_patch.dart 184:12 _RawReceivePortImpl._handleMessage
Following case could be related:
import 'dart:async';
import 'package:stack_trace/stack_trace.dart';
Future<void> main(List<String> arguments) async {
await Chain.capture(() async {
await methodA();
});
}
Future<void> methodA() async {
await methodB();
}
Future<void> methodB() async {
await prepare();
await work();
}
Future<void> prepare() async {}
Future<void> work() async => throw "work error";
Output is following:
Unhandled exception:
work error
bin/dart_async_exceptions.dart 22:30 work
bin/dart_async_exceptions.dart 17:9 methodB
===== asynchronous gap ===========================
dart:async/zone.dart 1294:19 _CustomZone.registerBinaryCallback
dart:async-patch/async_patch.dart 61:8 _asyncErrorWrapperHelper
bin/dart_async_exceptions.dart main.<fn>
package:stack_trace/src/chain.dart 94:24 Chain.capture.<fn>
dart:async/zone.dart 1354:13 _rootRun
dart:async/zone.dart 1258:19 _CustomZone.run
dart:async/zone.dart 1789:10 _runZoned
dart:async/zone.dart 1711:10 runZoned
package:stack_trace/src/chain.dart 92:12 Chain.capture
bin/dart_async_exceptions.dart 6:15 main
dart:isolate-patch/isolate_patch.dart 281:32 _delayEntrypointInvocation.<fn>
dart:isolate-patch/isolate_patch.dart 184:12 _RawReceivePortImpl._handleMessage
As you can see methodA invocation is missing
Now if you remove Chain.capture and just run await methodA(); from main, output is like this:
Unhandled exception:
work error
#0 work (file:///home/kernel/work/stuff/projects/dart-async-exceptions/bin/dart_async_exceptions.dart:22:30)
#1 methodB (file:///home/kernel/work/stuff/projects/dart-async-exceptions/bin/dart_async_exceptions.dart:17:9)
<asynchronous suspension>
#2 methodA (file:///home/kernel/work/stuff/projects/dart-async-exceptions/bin/dart_async_exceptions.dart:12:3)
<asynchronous suspension>
#3 main (file:///home/kernel/work/stuff/projects/dart-async-exceptions/bin/dart_async_exceptions.dart:7:5)
<asynchronous suspension>
Which is nice and correct trace.
Dart version: 2.13.3 (stable)
I think this is fixed by https://github.com/dart-lang/stack_trace/commit/d3e4c4d633eca379462dd4473b8995761155ee84 and https://github.com/dart-lang/sdk/commit/a52f2b96179ebdf29c38cab47e73dd1c6ec6bdff
@mraleph , I just tried with 1.11.1 with latest dart sdk build (as of today: 3.2.0-edge.dd712accf4cb04860dd1ab7a35679e94dc8d6cad (be) (Wed Jul 26 10:58:09 2023 +0000) on "linux_x64") and it still does not solve case I mentioned here: https://github.com/dart-lang/stack_trace/issues/111#issuecomment-868674265
Hmm, indeed stack trace produced by Chain.capture is still broken. I guess something in the stack_trace internals is still not working properly. But StackTrace.current is actually producing normal stuff now (if you print StaceTrace.current at the place where you throw) and so will the debugger.