bug
bug copied to clipboard
Compiler crashes with code involving `return` inside `try`
Scala 2.12 (both .0 and .1) compiler crashes when trying to compile my project. Specifically, it throws the following exception:
scala.tools.asm.tree.analysis.AnalyzerException: While processing switchwalker/Inspector.inspect
....
Caused by: scala.tools.asm.tree.analysis.AnalyzerException: Error at instruction 350: Incompatible stack heights
....
Caused by: scala.tools.asm.tree.analysis.AnalyzerException: Incompatible stack heights
....
[error] Error while emitting switchwalker/Inspector
[error] While processing switchwalker/Inspector.inspect
The construct that hurts the compiler seems to look something like this:
def inspect(): Result
{
var switch: Switch = null
....
try {
/*** 1 ***/ switch = Switch(...) match {
case Right(sw) => sw
case Left(result) => return result
}//match
...
} catch {
....
/*** 2 ***/ } finally {
if (switch != null)
switch.cleanup()
}//finally
result
}//inspect
Commenting out the assignment in the point marked /*** 1 / (without touching the following "match" clause) or removing the "finally" clause in the point / 2 ***/ stops the compiler from crashing. But no other changes in the same file (those I've tried) help.
The project compiles without any problems with Scala 2.11. I could not reproduce the crash with the smaller example project, containing the similar constructs. So I supply the full stack trace and the source file that causes the compiler to crash in the attachment. The points in the source file where the changes can help the compiler to survive are also marked /*** 1 / and / 2 ***/.
I also can supply the entire project if required (since it is not secret nor very big), so the crash could be reproduced.
Imported From: https://issues.scala-lang.org/browse/SI-10183?orig=1 Reporter: Dmitriy Stepanenko (mpolk) Affected Versions: 2.12.0, 2.12.1 Attachments:
- Inspector.scala (created on Feb 13, 2017 12:17:58 PM UTC, 13077 bytes)
- stacktrace.txt (created on Feb 13, 2017 12:17:58 PM UTC, 6433 bytes)
@lrytz said:
Hi, thanks for the report. I'm trying turn your file into a self-contained example that triggers the crash. In the meantime, you can probably use -opt:l:none as a workaround.
@lrytz said (edited on Feb 13, 2017 2:16:21 PM UTC): [~mpolk] I managed to make a self-contained example, a minimized version: https://gist.github.com/lrytz/fb9c38d0454f05ddb34907e4b4cc169c
Dmitriy Stepanenko (mpolk) said: Thanks, Lukas. Probably you are right about the minimum self-contained program causing this compiler crash. I've tried a similar program, but without a "foreach" cycle in the middle, which seems to be essential to crash the compiler.
@lrytz said (edited on Feb 14, 2017 11:31:46 AM UTC): Minimized:
class C {
def fin(): Unit = ()
def m(x: Int) = ()
def inspect: Unit = {
try {
m(if (this == null) return else 1) // jump to finally block, `this` on stack
return // jump to finally block, empty stack
} finally {
fin()
}
}
}
@lrytz said: This looks not easy to fix unfortunately.
For a try-finally, the finally block is emitted in 3 copies
- once as a handler, if an exception is thrown in the
tryblock (or anycatchblock) - once for ordinary control flow
- once for
returnstatements within thetryblock
return statements within the try are replaced by a store (if there's a return value) and a jump to the 3rd finally copy (called "cleanup"). This is handled in genReturn.
The classfile spec requires the stack to be the same (height and types) for all branches to a location. In the example, the at the first return, there's a value on the stack (the receiver of method m), at the second return there isn't. What we should do is insert drop operations until the stack is clear, but we don't keep track of the stack height in our code generator.
In 2.11.x, returns were handled differently: the finally block was inlined into the try block above every return.
Ideas welcome, cc @retronym.
@lrytz said: https://github.com/lrytz/scala/tree/t10183
For the record, javac duplicates the finalizer like we did in 2.11.
as of 2.13.10, the error message on Lukas's reproduction is:
error: Error while emitting C
Index 0 out of bounds for length 0
Scala 3.2.2 gives:
java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0 while running genBCode on rs$line$1
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
at scala.tools.asm.Frame.merge(Frame.java:1268)
Just wanted to drop here a line in the hope it would help anyone. In my case the code was as follows
❯ scalac -version Scala compiler version 2.13.12 -- Copyright 2002-2023, LAMP/EPFL and Lightbend, Inc.
❯ cat Repro.scala
class Process() {
def exit() = println("exit")
}
class Actor extends Process {
override def exit() = {
try {
println("try")
} catch {
case e: Throwable => 'ignored
}
}
}
❯ scalac -d /tmp Repro.scala
error: Error while emitting Actor
Index 0 out of bounds for length 0
1 error
Notice that the return type of the exit in superclass is Unit. While in override the return type from the catch branch is Symbol. The fix for me was to return () from the function.
The original reproducer in https://github.com/scala/bug/issues/10183#issuecomment-292443914 also return incorrect type (Integer instead of Unit).
Just realized lrytz's minimization was also on Valentine's Day. SethTisue's followup last year was a week before because he's always busy on Valentine's Day. :heart: