[Solved] IndexOutOfBoundsException in Scala 2.13.8 inliner

I have ~30 Scala 2.13.8 work projects (so no source immediately available) that are getting updated to compile and run on JDK 17. We have an SBT plugin that sets identical scalacOptions for all of them. Most work just fine, but 3 have triggered the same IndexOutOfBoundsException while inlining. The same code with the same options compiles just fine using JDK 11.

[error] java.lang.IndexOutOfBoundsException
[error] 	at scala.tools.asm.tree.InsnList.get(InsnList.java:94)
[error] 	at scala.tools.nsc.backend.jvm.analysis.BackendUtils$.computeMaxLocalsMaxStack(BackendUtils.scala:727)
[error] 	at scala.tools.nsc.backend.jvm.analysis.BackendUtils$.maxLocals(BackendUtils.scala:635)
[error] 	at scala.tools.nsc.backend.jvm.opt.Inliner.inlineCallsite(Inliner.scala:856)
[error] 	at scala.tools.nsc.backend.jvm.opt.Inliner.$anonfun$runInliner$10(Inliner.scala:352)
[info] * -deprecation
[info] * -encoding
[info] * utf-8
[info] * -feature
[info] * -unchecked
[info] * -explaintypes
[info] * -language:existentials
[info] * -language:experimental.macros
[info] * -language:higherKinds
[info] * -Xcheckinit
[info] * -Xlint:_,-missing-interpolator
[info] * -Xverify
[info] * -Yrangepos
[info] * -release
[info] * 11
[info] * -target:11
[info] * -Wdead-code
[info] * -Wextra-implicit
[info] * -Wnumeric-widen
[info] * -Woctal-literal
[info] * -Wunused:imports
[info] * -Wunused:patvars
[info] * -Wunused:privates
[info] * -Wunused:locals
[info] * -Wunused:explicits
[info] * -Wunused:implicits
[info] * -Wunused:synthetics
[info] * -Wunused:-nowarn
[info] * -Wvalue-discard
[info] * -Xasync
[info] * -Xsource:3
[info] * -Ytasty-reader
[info] * -opt:l:inline
[info] * -opt-inline-from:**,!java.**
[info] * -opt-warnings:none
[info] * -Werror

I have not found anything similar on the scala/bug repo. Has anyone seen this before or have idea what would trigger it? I’m working to create a minimal repro but since I have no clue what would cause it I’m basically doing a bunch of guess-and-check work.

Turns out all three projects were inlining something from a javax package while the rest do not. Excluding that package as part of -opt-inline-from just like the java package solves the issue.

Changing the scalacOptions to -target:17 -release 17 separately solves the issue.

So this appears to occur when the compiler is running with a newer JDK while targeting an older JDK.

I was under the impression that choosing the -release flag would set the JDK classpath such that it matched the target version and inlining from those packages should be fine for applications, but I think this was misguided.

Inline settings need to exclude all the JVM-included packages in order to target older bytecode, or target settings need to match the compiler/runtime JVM version if inlining from them is to be viable.

@jgulotta I’d be keen to be able to reproduce (and fix) this issue. Maybe running the compiler with -Vinline _ could give some useful information… Though the inline log probably doesn’t show when the compiler crashes.

If you can’t minimize or share your code, an option would be to enable the debugger agent in the JVM running the compiler, connect from IntelliJ, set a breakpoint in InsnList.java:94, and get more details from there (what is being inlined, what the produced bytecode is that crashes in computeMaxLocalsMaxStack).

I haven’t checked, but it’s possible that using the -release flag implicitly disables inlining from the java because the classpath then doesn’t contain the actual JDK jars with classfiles, but only the ct.sym file matching the specified Java version.

an option would be to enable the debugger agent in the JVM running the compiler, connect from IntelliJ, set a breakpoint in InsnList.java:94

Attaching the debugger is exactly how I figured out that it was inlining from the javax package. I should have had that package excluded but didn’t. That turned out to be more difficult that I wanted just because IntelliJ only seems to know about the 2.12 scala-compiler.jar that SBT itself uses so I can actually navigate through the 2.13 source, instead relying on an exception breakpoint and the locals window.

what is being inlined

In my case one error was inlining from javax.crypto.Mac.doFinal and the other was inlining from javax.xml.datatype.DatatypeFactory.newInstance. I suspect inlining from any javax package will trigger it.

what the produced bytecode is that crashes in computeMaxLocalsMaxStack

I’m not 100% sure what you’re looking for here, but in both cases the instructions list is empty at the time of the crash, and the index it’s using is 0, essentially trying to access the head of the empty list.

If you can’t minimize or share your code

Once I found that it’s inlining from javax which was problematic, minimization became trivial.

Compile this with scalac -release 11 -opt:l:inline -opt-inline-from:'**' where scalac is 2.13.8 and JAVA_HOME points to a Java 17 location (or equivalent setup, of course)

object crash {
  def main(args: Array[String]): Unit = {
    val algo = "HmacSHA1"
    val charset = java.nio.charset.StandardCharsets.UTF_8
    val key = "totally secret key".getBytes(charset)
    val value = "totally real value".getBytes(charset)

    val mac = javax.crypto.Mac.getInstance(algo)
    mac.init(new javax.crypto.spec.SecretKeySpec(key, algo))
    val macced = mac.doFinal(value)

    println(new String(macced, charset))
  }
}
1 Like

Perfect, thanks for the reproducer! I logged it at Inliner crash on Java 17 · Issue #12612 · scala/bug · GitHub, next time feel free to create a ticket there if you have a good way to reproduce an issue.