6 ms·
JVM exceptions are weird: a decompiler perspective
- pron 10mo agoNice post! A minor point: > monitors are incompatible with coroutines If by coroutines the author meant virtual threads, then monitors have always been compatible with virtual threads (which have always needed to adhere to the Thread specification). Monitors could, for a short while, degrade the scalability of virtual threads (and in some situations even lead to deadlocks), but that has since been resolved in JDK 24 (https://openjdk.org/jeps/491 https://openjdk.org/jeps/491).
- PhilipRoman 10mo agoI think it's coroutines as in other JVM languages like Kotlin, where yielding may be implemented internally as return (due to lack of native coroutine support in JVM). Holding a lock/monitor across a yield is a bad idea for other reasons, so it shouldn't be a big deal in practice.
- purplesyringa 10mo agoI meant polyfilled coroutines used by other JVM languages, like Kotlin. When you compile a coroutine to a state machine, yielding has to return from the machine; but JVM does not support unbalanced monitors, although it obviously does support unbalanced locking operations with normal mutexes.
- deleted 10mo ago[deleted]
- marginalia_nu 10mo agoOn the subject void foo() { for (;;) { try { return; } finally { continue; } } } is my favorite cursed Java exceptions construct.
- buggymcbugfix 10mo agoThe cursed thing here is `continue`!
- cerved 10mo agoTo anyone wondering, I believe it's cursed because the finally continue blocks hijacks the try return, so the for loop never returns
- deleted 10mo ago[deleted]
- chii 10mo agosee, if you only had GOTO's, this would be obvious what is going on!
- taneq 10mo agoSo the function returns, and then during its tidyup, the 'continue' basically comefrom()s the VM back into the loop? That is, indeed, cursed.
- friendzis 10mo agoI would not call this snippet particularly "cursed". There is no "aktshchually this happens, therefore this is activated" hidden magic going on. The try-catch-finally construct is doing exactly what it is designed and documented to do: finally block is executed regardless of the outcome within try. The purpose of finally block is to fire regardless of exceptionality in control flow. Surprising at first? Maybe. Cursed? Wouldn't say so. It is merely unconventional use of the construct.
- Joker_vD 10mo agoIt messes with the semantics of "return" statement. The conventional intuition is that right after a "return" statement completes, the current method's invocation ends and the control returns to the caller. Unfortunately, the actual semantics has to be that is "attempts to return control": The preceding descriptions say "attempts to transfer control" rather than just "transfers control" because if there are any try statements (§14.20) within the method or constructor whose try blocks or catch clauses contain the return statement, then any finally clauses of those try statements will be executed, in order, innermost to outermost, before control is transferred to the invoker of the method or constructor. Abrupt completion of a finally clause can disrupt the transfer of control initiated by a return statement. This, I believe, is the only way for "return E", after the evaluation of E completes normally, to not return the E's value to the caller. Thanks for a needless corner-case complication, I guess.
- Joker_vD 10mo agoDoesn't JRE has some limited form of decompilation in its JIT, as a pre-pass? IIRC, it reconstructs the basic blocks and CFG from the bytecode and does some minor optimizations before going on to regalloc and codegen.
- monocasa 10mo agoIt's hard to call it decompilation as opposed to just regular compilation though.
- immibis 10mo agoOlder versions of Java did try to have only one copy of the finally block code. To implement this, there were "jsr" and "ret" instructions, which allowed a method (a subroutine) to contain subroutines inside it. This even curseder implementation of finally is prohibited starting from version 51 class files (Java 7).