| Summary: | Debugger stops in uncheckedThrow when unchecked exception thrown in lambda while processing stream | ||||||
|---|---|---|---|---|---|---|---|
| Product: | [Eclipse Project] JDT | Reporter: | Luke Hutchison <luke.hutch> | ||||
| Component: | Debug | Assignee: | JDT-Core-Inbox <jdt-core-inbox> | ||||
| Status: | CLOSED NOT_ECLIPSE | QA Contact: | |||||
| Severity: | normal | ||||||
| Priority: | P3 | CC: | loskutov, register.eclipse, stephan.herrmann | ||||
| Version: | 4.8 | ||||||
| Target Milestone: | --- | ||||||
| Hardware: | PC | ||||||
| OS: | Linux | ||||||
| Whiteboard: | |||||||
| Attachments: |
|
||||||
|
Description
Luke Hutchison
Why did the debugger stop in the first place? Do you have an exception breakpoint on Throwable or similar? Just run the following code (I'm running in Oracle JDK 10.0.2)
---
import java.util.Arrays;
public class DebuggerFail {
public static void main(String[] args) {
Arrays.asList(1, 2, 3).parallelStream().forEach(n -> {
throw new RuntimeException();
});
}
}
---
The debugger stops at (*) shown below: (ForkJoinTask.java:669)
---
/**
* The sneaky part of sneaky throw, relying on generics
* limitations to evade compiler complaints about rethrowing
* unchecked exceptions.
*/
@SuppressWarnings("unchecked") static <T extends Throwable>
void uncheckedThrow(Throwable t) throws T {
if (t != null)
throw (T)t; // rely on vacuous cast // (*)
else
throw new Error("Unknown Exception");
}
---
No, I have no exception breakpoints set.
Created attachment 275770 [details]
Screenshot
A screenshot, showing there are no exception breakpoints set, and the debugger is stopped in ForkJoinTask with RuntimeException showing in the top left pane, but an entirely unhelpful stacktrace, and the debugger line indicator pointing to the vacuous cast.
The debugger stops without breakpoint because this exception is unhandled. BTW: Since 4.8, you can look at the exception being thrown in the variables view, but note that the stack trace seems to be empty in the debugger (this is something cause by the jvm). If you let the program continue, you'll see the stacktrace printed to the console. The only way to make eclipse stop at the original location is by setting an (exception) breakpoint for it (as you wrote in comment 0). Eclipse cannot do that automatically for you. I know I can hit continue to see the stack trace, but I want to examine the variable value inside the debugger. The important points: (1) The stack trace and the stopped position that are shown are both clearly wrong, and don't even make sense. (2) I am asking if Eclipse can add some special case handling for lambdas, which traps all uncaught exceptions when they are thrown, before the stack is unraveled. The debugger can do this when not running inside a lambda, so I have a hard time believing it is not possible within a lambda. (3) Maybe this will require different special case code for different JDK versions or different lambda implementations to fix the problem. However it would be worth whatever special case handling is needed, because right now buggy code is very hard to debug inside lambdas, and lambda usage is going to grow quickly, so this really needs to be fixed. This is a matter of the stream api catching and rethrowing exceptions. It has nothing to do with lambdas, you can replace the lambda by an anonymous class and you'll see the same behaviour. (In reply to Till Brychcy from comment #4) > The debugger stops without breakpoint because this exception is unhandled. Is that part intended? (In reply to Luke Hutchison from comment #5) > I know I can hit continue to see the stack trace, but I want to examine the > variable value inside the debugger. The important points: > > (1) The stack trace and the stopped position that are shown are both clearly > wrong, and don't even make sense. Wrong by what reasoning? The line is being executed, there an exception is being thrown. To me all this makes sense. I think you are asking Eclipse to stop not at the uncaught rethrow, but at the original throw? Is that it? To me it seems such RFE would amount to classifying the catch in java.util.concurrent.ForkJoinTask.doExec() as not happening, so the original exception will be classified as uncaught? (In reply to Stephan Herrmann from comment #7) > (In reply to Till Brychcy from comment #4) > > The debugger stops without breakpoint because this exception is unhandled. > > Is that part intended? Yes, there's even a setting for it: Preferences > Java > Debug > Suspend execution on uncaught exceptions Tim -- I see, thanks for patiently explaining. I see your point now. Let me refine my request: since the stream API makes use of the "sneaky rethrow" technique, could a new Eclipse option be added that specifically intercepted usage of "catch (Throwable t)", and stopped the debugger at the point where the exception is thrown? Basically this would be a new option, "Preferences > Java > Debug > Suspend execution on uncaught exceptions before catch-all in classes: [Class List]" Then you could add the stream API classes to that class list (or there could be a box to select all classes in system packages). It really is critical to fix this to improve the debugging experience with streams. Surely there is something that can be done about this? (In reply to Luke Hutchison from comment #9) > Tim -- I see, thanks for patiently explaining. I see your point now. I'm Till ;-) > > Let me refine my request: since the stream API makes use of the "sneaky > rethrow" technique, could a new Eclipse option be added that specifically > intercepted usage of "catch (Throwable t)", and stopped the debugger at the > point where the exception is thrown? Sorry, the JVM doesn't allow to specify such a breakpoint condition. (In reply to Till Brychcy from comment #10) > (In reply to Luke Hutchison from comment #9) > > Tim -- I see, thanks for patiently explaining. I see your point now. > > I'm Till ;-) > > > > > Let me refine my request: since the stream API makes use of the "sneaky > > rethrow" technique, could a new Eclipse option be added that specifically > > intercepted usage of "catch (Throwable t)", and stopped the debugger at the > > point where the exception is thrown? > > Sorry, the JVM doesn't allow to specify such a breakpoint condition. (Learned something new again, didn't know that this kind of breakpoint is natively handled by JDWP / JVM). From this, I conclude resolution: NOT_ECLIPSE, because: The stream API produces a situation that is not handled well by JDWP. Attempts to fix this in JDT would likely cause intolerable performance penalty: you would have to open up the exception breakpoint to ALL exceptions, caught and uncaught, then when debugger is about to suspend, walk the stack and look if one of the known sneaky things is in charge. If not, resume execution. Doing non-trivial work on every exception in a program sounds like a no-go to me. Workaround has been mentioned: set explicit exception breakpoint for the actual exception thrown. As a personal comment: I feel that stream API is not meant to be debugged at all. The sheer number of "uninteresting" stack frames in an unsuspecting programmer's face let's me wonder, if that library is written only for those functional programmers who supposedly don't need a debugger because they write 'correct programs' from the start? <shrug/> (In reply to Stephan Herrmann from comment #11) > let's me wonder, if that library is written only for those > functional programmers who supposedly don't need a debugger because they > write 'correct programs' from the start? <shrug/> Smile of the day :-) (Sorry for the typo on your name, Till -- I blame autocorrect!) If general uncaught exception interception cannot be implemented efficiently, then it's fair to close this. However, I'm curious as to how uncaught exceptions are trapped by the debugger when they are thrown, and what is missing from the JVM that would allow allow unchecked-but-caught exceptions to be efficiently intercepted by the debugger. > I feel that stream API is not meant to be debugged at all I don't buy that at all! :-) Come on, seriously?? One of the most important additions to the stream API is the ability to simply call .parallelStream().forEach(lambda) to add parallelism to your program. It's genius in its simplicity, given how hard it is to retrofit parallelism to Java in general. The more that pattern is used, the bigger the lambdas will get, and the more it will be impossible to say "lambdas in streams should never need debugging, because they're so simple". Personally I use this pattern all the time, and some of the lambdas in my parallel streams are large and non-trivial. > The sheer number of "uninteresting" stack frames in an unsuspecting > programmer's face This is really a big, big problem with the current implementation of both streams and lambdas. It's all unbelievably overcomplicated under the hood, and the complexity frequently rears its ugly head in both the debugger and in weird behaviors and corner cases. I hope it is all torn down and replaced with something simpler and cleaner in a future release! > if that library is written only for those functional programmers who > supposedly don't need a debugger because they write 'correct programs' > from the start? Now *that* I do buy :-) Unfortunately though the expectations of these functional programmers are inevitably shattered when they realize the Java type system is not strong enough to save them from shooting themselves in the foot like in a real functional programming language. (But I digress...) (In reply to Luke Hutchison from comment #13) > If general uncaught exception interception cannot be implemented > efficiently, then it's fair to close this. However, I'm curious as to how > uncaught exceptions are trapped by the debugger when they are thrown, and > what is missing from the JVM that would allow allow unchecked-but-caught > exceptions to be efficiently intercepted by the debugger. The documentation of JDWP [1] mentions "uncaught" as a field of Requests in the context of breakpoints. In my understanding, this indicates a kind of breakpoint filtering that can happen inside the JVM rather then by the debugger on top. There's even an interesting caveat: "Note that it is not always possible to determine whether an exception is caught or uncaught at the time it is thrown. See the exception event catch location under composite events for more information." But that seems to touch the opposite direction: exception is caught but JVM cannot see that. Maybe you can also skip the JDWP layer, and study all this in a nicer format via JDI [2]. [1] https://docs.oracle.com/javase/10/docs/specs/jdwp/jdwp-protocol.html#JDWP_EventRequest [2] https://docs.oracle.com/javase/10/docs/api/com/sun/jdi/request/ExceptionRequest.html Many thanks for the links. |