Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stacktrace can ignore user-owned file when using tearoff #56918

Closed
FMorschel opened this issue Oct 18, 2024 · 7 comments
Closed

Stacktrace can ignore user-owned file when using tearoff #56918

FMorschel opened this issue Oct 18, 2024 · 7 comments
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. closed-not-planned Closed as we don't intend to take action on the reported issue triaged Issue has been triaged by sub team type-enhancement A request for a change that isn't a bug

Comments

@FMorschel
Copy link
Contributor

FMorschel commented Oct 18, 2024

Repro:

import 'dart:async';

void main() {
  var controller = StreamController<int>();
  var controller2 = StreamController<int>();
  controller2.stream.listen((v) => controller.add(v));
  controller2.add(42);
  controller.close();
  controller2.add(42);
}

This code throws an error:

Connecting to VM Service at ws://127.0.0.1:34837/-D1zY7tPPbM=/ws
Connected to the VM Service.
Unhandled exception:
Bad state: Cannot add event after closing
#0      _StreamController.add (dart:async/stream_controller.dart:605:24)
#1      main.<anonymous closure> (package:bug/bug.dart:6:47)
#2      _RootZone.runUnaryGuarded (dart:async/zone.dart:1609:10)
#3      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:366:11)
#4      _DelayedData.perform (dart:async/stream_impl.dart:542:14)
#5      _PendingEvents.handleNext (dart:async/stream_impl.dart:647:11)
#6      _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:618:7)
#7      _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
#8      _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)
#9      _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:118:13)
#10     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:185:5)

Exited (255).

Focus on line 1 (not 0) where it tells me the file, function, line and offset of where the error was thrown.

If you use a tear off:

import 'dart:async';

void main() {
  var controller = StreamController<int>();
  var controller2 = StreamController<int>();
  controller2.stream.listen(controller.add);
  controller2.add(42);
  controller.close();
  controller2.add(42);
}

This is the output:

Connecting to VM Service at ws://127.0.0.1:35306/udzDEf1Qq0U=/ws
Connected to the VM Service.
Unhandled exception:
Bad state: Cannot add event after closing
#0      _StreamController.add (dart:async/stream_controller.dart:605:24)
#1      _RootZone.runUnaryGuarded (dart:async/zone.dart:1609:10)
#2      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:366:11)
#3      _DelayedData.perform (dart:async/stream_impl.dart:542:14)
#4      _PendingEvents.handleNext (dart:async/stream_impl.dart:647:11)
#5      _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:618:7)
#6      _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
#7      _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)
#8      _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:118:13)
#9      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:185:5)

Exited (255).

I have no more reference as to where this happened on my code. I think this should be better handled for better DX. I was struggling for more than half an hour on my Flutter project with many different files and pages when I realized where this could be happening. The uncaught exceptions also triggers inside the framework code and there is no way for you to go back to your own code.

@dart-github-bot
Copy link
Collaborator

Summary: The stack trace in Dart's uncaught exceptions does not include the user's code when using a tear-off function, making it difficult to pinpoint the source of the error. This issue hinders debugging, especially in larger projects with multiple files.

@dart-github-bot dart-github-bot added area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) labels Oct 18, 2024
@lrhn
Copy link
Member

lrhn commented Oct 18, 2024

That looks correct.

The controller2 is not a synchronous controller, so the call to the function argument to controller2.listen happens in a later microtask. At that point there is nothing on the stack except the internal Stream code that tries to call that function.

Then it calls the function, which is the tear-off controller1.add, which is exactly what shows up in the stack trace.
There is no code from main on the stack at this point, so the stack trace is correct.

In the first example, the argument function was (x) { controller1.add(x); }, not controller.add1. That adds one extra stack frame when called, which shows the location of controller.add(x) in main as that position that calls contoller1.add.

Errors that happen in asynchronous code do not have the original stack any more, that's just how event loops work.

@lrhn lrhn removed type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. labels Oct 18, 2024
@FMorschel
Copy link
Contributor Author

I know this is the current behaviour. Sorry if I was not clear on my request.

I'd like to ask if there is any way for the call stack to know about the call being a tear off (at least when developing). If this is possible, we could possibly show more information to the programmer in cases like the above so it's easier to find errors even in asynchronous code.

@lrhn
Copy link
Member

lrhn commented Oct 18, 2024

I guess could be technically possible for an instance member tear-off to record where in the code the tear-off occurred, at least in development mode. It will still be equal (==) to another tear-off of the same function from the same object that happens in another place, but could maybe look different in stack traces.

@lrhn lrhn added the type-enhancement A request for a change that isn't a bug label Oct 18, 2024
@FMorschel
Copy link
Contributor Author

Just to be clear, for the above case as an example, are you talking about controller1.add knowing where it got used or controller2.stream.listen knowing it received a tear off and where?

Just because both could help here but they would show slightly different things in the stack trace I believe.

@lrhn
Copy link
Member

lrhn commented Oct 19, 2024

I'm talking about controller.add knowing where it was torn off, so that can show up in stack traces.

I doubt the caller itself will know where the function came from, anything special had to happen in the method.

I don't know how the tear-off works.
If there is no code in it, and calling it directly invokes the torn off method, there is nothing on the stack to recognize that it was involved.
If it contains any code, even if just a jmp to the real method, it should be possible to do something else in development mode, like changing jmp to call+ret, and have a stack entry.

(But I'll stop guessing now and leave this to people who actually know what the VM is doing.)

@a-siva a-siva added triaged Issue has been triaged by sub team P4 closed-not-planned Closed as we don't intend to take action on the reported issue and removed P4 labels Oct 23, 2024
@a-siva
Copy link
Contributor

a-siva commented Oct 23, 2024

The VM does not record where a function was torn off from and we do not have plans to implement that.
There is possibly a workaround to use a closure instead of a tear off.

@FMorschel FMorschel closed this as not planned Won't fix, can't repro, duplicate, stale Oct 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. closed-not-planned Closed as we don't intend to take action on the reported issue triaged Issue has been triaged by sub team type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

4 participants