Skip to content

Inlining code causes an java.lang.IndexOutOfBoundsException for Scala 2.13 #13097

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

Closed
gergelyfabian opened this issue Mar 13, 2025 · 32 comments · Fixed by scala/scala#11016
Closed
Assignees
Milestone

Comments

@gergelyfabian
Copy link

gergelyfabian commented Mar 13, 2025

After upgrading my code from Scala 2.12 to 2.13 I found a bug.

If I inline some code that uses a certain protobuf version I get the following exception from the compiler:

error: java.lang.IndexOutOfBoundsException
	at scala.tools.asm.tree.InsnList.get(InsnList.java:94)
	at scala.tools.nsc.backend.jvm.analysis.BackendUtils$.computeMaxLocalsMaxStack(BackendUtils.scala:728)
	at scala.tools.nsc.backend.jvm.analysis.BackendUtils$.maxLocals(BackendUtils.scala:636)
	at scala.tools.nsc.backend.jvm.opt.Inliner.inlineCallsite(Inliner.scala:856)
	at scala.tools.nsc.backend.jvm.opt.Inliner.$anonfun$runInliner$10(Inliner.scala:352)
	at scala.collection.IterableOnceOps.foreach(IterableOnce.scala:619)
	at scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:617)
	at scala.collection.AbstractIterable.foreach(Iterable.scala:935)
	at scala.collection.IterableOps$WithFilter.foreach(Iterable.scala:905)
	at scala.tools.nsc.backend.jvm.opt.Inliner.runInliner(Inliner.scala:378)
	at scala.tools.nsc.backend.jvm.opt.Inliner.runInlinerAndClosureOptimizer(Inliner.scala:281)
	at scala.tools.nsc.backend.jvm.PostProcessor.runGlobalOptimizations(PostProcessor.scala:156)
	at scala.tools.nsc.backend.jvm.GeneratedClassHandler$GlobalOptimisingGeneratedClassHandler.complete(GeneratedClassHandler.scala:98)
	at scala.tools.nsc.backend.jvm.GenBCode$BCodePhase.$anonfun$run$1(GenBCode.scala:81)
	at scala.tools.nsc.backend.jvm.GenBCode$BCodePhase.run(GenBCode.scala:78)
	at scala.tools.nsc.Global$Run.compileUnitsInternal(Global.scala:1564)
	at scala.tools.nsc.Global$Run.compileUnits(Global.scala:1548)
	at scala.tools.nsc.Global$Run.compileSources(Global.scala:1540)
	at scala.tools.nsc.Global$Run.compile(Global.scala:1674)
	at scala.tools.nsc.Driver.doCompile(Driver.scala:48)
	at scala.tools.nsc.MainClass.doCompile(Main.scala:30)
	at scala.tools.nsc.Driver.process(Driver.scala:68)
	at io.bazel.rulesscala.scalac.ScalacInvoker.invokeCompiler(ScalacInvoker.java:24)
	at io.bazel.rulesscala.scalac.ScalacWorker.compileScalaSources(ScalacWorker.java:272)
	at io.bazel.rulesscala.scalac.ScalacWorker.work(ScalacWorker.java:88)
	at io.bazel.rulesscala.worker.Worker.persistentWorkerMain(Worker.java:96)
	at io.bazel.rulesscala.worker.Worker.workerMain(Worker.java:49)
	at io.bazel.rulesscala.scalac.ScalacWorker.main(ScalacWorker.java:48)
java.lang.IndexOutOfBoundsException
	at scala.tools.asm.tree.InsnList.get(InsnList.java:94)
	at scala.tools.nsc.backend.jvm.analysis.BackendUtils$.computeMaxLocalsMaxStack(BackendUtils.scala:728)
	at scala.tools.nsc.backend.jvm.analysis.BackendUtils$.maxLocals(BackendUtils.scala:636)
	at scala.tools.nsc.backend.jvm.opt.Inliner.inlineCallsite(Inliner.scala:856)
	at scala.tools.nsc.backend.jvm.opt.Inliner.$anonfun$runInliner$10(Inliner.scala:352)
	at scala.collection.IterableOnceOps.foreach(IterableOnce.scala:619)
	at scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:617)
	at scala.collection.AbstractIterable.foreach(Iterable.scala:935)
	at scala.collection.IterableOps$WithFilter.foreach(Iterable.scala:905)
	at scala.tools.nsc.backend.jvm.opt.Inliner.runInliner(Inliner.scala:378)
	at scala.tools.nsc.backend.jvm.opt.Inliner.runInlinerAndClosureOptimizer(Inliner.scala:281)
	at scala.tools.nsc.backend.jvm.PostProcessor.runGlobalOptimizations(PostProcessor.scala:156)
	at scala.tools.nsc.backend.jvm.GeneratedClassHandler$GlobalOptimisingGeneratedClassHandler.complete(GeneratedClassHandler.scala:98)
	at scala.tools.nsc.backend.jvm.GenBCode$BCodePhase.$anonfun$run$1(GenBCode.scala:81)
	at scala.tools.nsc.backend.jvm.GenBCode$BCodePhase.run(GenBCode.scala:78)
	at scala.tools.nsc.Global$Run.compileUnitsInternal(Global.scala:1564)
	at scala.tools.nsc.Global$Run.compileUnits(Global.scala:1548)
	at scala.tools.nsc.Global$Run.compileSources(Global.scala:1540)
	at scala.tools.nsc.Global$Run.compile(Global.scala:1674)
	at scala.tools.nsc.Driver.doCompile(Driver.scala:48)
	at scala.tools.nsc.MainClass.doCompile(Main.scala:30)
	at scala.tools.nsc.Driver.process(Driver.scala:68)
	at io.bazel.rulesscala.scalac.ScalacInvoker.invokeCompiler(ScalacInvoker.java:24)
	at io.bazel.rulesscala.scalac.ScalacWorker.compileScalaSources(ScalacWorker.java:272)
	at io.bazel.rulesscala.scalac.ScalacWorker.work(ScalacWorker.java:88)
	at io.bazel.rulesscala.worker.Worker.persistentWorkerMain(Worker.java:96)
	at io.bazel.rulesscala.worker.Worker.workerMain(Worker.java:49)
	at io.bazel.rulesscala.scalac.ScalacWorker.main(ScalacWorker.java:48)

Scala version: 2.13.16 (could not reproduce with Scala 2.12.20).

Steps to reproduce:

git clone git@github.com:gergelyfabian/bazel-scala-example.git
cd bazel-scala-example
git checkout scala-compiler-bug
bazel build //example-maven:example-maven

Example code:

package mypackage

import com.google.protobuf.CodedOutputStream

object Maven {
  def repro(): Unit = {
    val result = new Array[Byte](0)
    CodedOutputStream.newInstance(result)
  }
}

I use the following scalacopt: -opt-inline-from:**.

Example Bazel config:

scala_library(
    name = "example-maven",
    srcs = glob(["src/main/scala/**/*.scala"]),
    scalacopts = [
        "-opt-inline-from:**",
    ],
    deps = [
        #"@maven//:com_google_protobuf_protobuf_java",
        "@com_google_protobuf//:protobuf_java",
    ],
)

This is a Bazel project, not an sbt one, as I wasn't able to reproduce this issue with sbt.
Related to that, in this example code, if I replace the dependency I use from @com_google_protobuf//:protobuf_java (de-facto built-in Bazel one) to @maven//:com_google_protobuf_protobuf_java (one generated by rules_jvm_external), then I cannot reproduce the bug any more, so it seems to be related to the protobuf version Bazel uses.
That would also explain why I cannot reproduce with sbt, as in sbt I suppose I get the same protobuf-java as with rules_jvm_external.

In any case, I wouldn't expect the Scala compiler to throw such an exception.

@gergelyfabian
Copy link
Author

gergelyfabian commented Mar 13, 2025

Managed to reproduce with sbt too.

Here is my project:

.
├── build.sbt
├── lib
│   ├── libcore-hjar.jar
│   └── liblite_runtime_only-hjar.jar
├── project
│   └── build.properties
└── src
    └── main
        └── scala
            └── example
                └── Example.scala

build.sbt:

name := "scala-compiler-inline-bug"

version := "0.1"

scalaVersion := "2.13.16"

unmanagedJars in Compile += file("lib/libcore-hjar.jar")

javacOptions := Seq("--release", "21")

scalacOptions += "-target:jvm-21"
scalacOptions += "-opt-inline-from:**"

Example.scala:

package example

import com.google.protobuf.CodedOutputStream

object Example {
  def repro(): Unit = {
    val result = new Array[Byte](0)
    CodedOutputStream.newInstance(result)
  }
}

The two files in lib are added from my example Bazel project with:

# This will fail, but will generate the dependency jars.
( cd ~/opt/bazel-scala-example; bazel build //example-maven:example-maven )
cp ~/opt/bazel-scala-example/bazel-out/k8-fastbuild/bin/external/com_google_protobuf/java/core/libcore-hjar.jar lib/
cp ~/opt/bazel-scala-example/bazel-out/k8-fastbuild/bin/external/com_google_protobuf/java/core/liblite_runtime_only-hjar.jar lib/

Also uploaded these files to Google Drive.

Repro (using sbt only):

git clone git@github.com:gergelyfabian/scala-compiler-inline-bug.git
cd scala-compiler-inline-bug
mkdir -p lib
curl -L -o lib/libcore-hjar.jar 'https://drive.google.com/uc?export=download&id=1NbKjL5vwqoLUb-sbR3vMpKjKBqvF8pio'
curl -L -o lib/liblite_runtime_only-hjar.jar 'https://drive.google.com/uc?export=download&id=12pSp3bTaMZxqkzQP3Ah7SMuv_Co0vxfg'
sbt compile

Produces:

$ sbt compile
[info] welcome to sbt 1.10.2 (Ubuntu Java 21.0.6)
[info] loading global plugins from /home/user/.sbt/1.0/plugins
[info] loading project definition from /home/user/Work/scala-compiler-inline-bug/project
[info] loading settings for project scala-compiler-inline-bug from build.sbt ...
[info] set current project to scala-compiler-inline-bug (in build file:/home/user/Work/scala-compiler-inline-bug/)
[info] Executing in batch mode. For better performance use sbt's shell
[info] compiling 1 Scala source to /home/user/Work/scala-compiler-inline-bug/target/scala-2.13/classes ...
[error] ## Exception when compiling 1 sources to /home/user/Work/scala-compiler-inline-bug/target/scala-2.13/classes
[error] java.lang.IndexOutOfBoundsException
[error] scala.tools.asm.tree.InsnList.get(InsnList.java:94)
[error] scala.tools.nsc.backend.jvm.analysis.BackendUtils$.computeMaxLocalsMaxStack(BackendUtils.scala:728)
[error] scala.tools.nsc.backend.jvm.analysis.BackendUtils$.maxLocals(BackendUtils.scala:636)
[error] scala.tools.nsc.backend.jvm.opt.Inliner.inlineCallsite(Inliner.scala:856)
[error] scala.tools.nsc.backend.jvm.opt.Inliner.$anonfun$runInliner$10(Inliner.scala:352)
[error] scala.collection.IterableOnceOps.foreach(IterableOnce.scala:619)
[error] scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:617)
[error] scala.collection.AbstractIterable.foreach(Iterable.scala:935)
[error] scala.collection.IterableOps$WithFilter.foreach(Iterable.scala:905)
...

@gergelyfabian
Copy link
Author

If I change my sbt repro to use Scala 2.12.20, it does not fail compiling:

-scalaVersion := "2.13.16"
+scalaVersion := "2.12.20"

@SethTisue
Copy link
Member

SethTisue commented Mar 13, 2025

I wonder if it's related to #12612 — wdyt @lrytz ?

@gergelyfabian do those JARs exist on the internet, can I grab them with curl? That's the main remaining thing that would make this easier to reproduce.

@gergelyfabian
Copy link
Author

gergelyfabian commented Mar 13, 2025 via email

@SethTisue
Copy link
Member

SethTisue commented Mar 13, 2025

I am not sure whether you can grab them from somewhere, but it's easy to
generate them. You just need bazelisk installed (
https://github.com/bazelbuild/bazelisk), and then run the bazel build
command as I wrote in the repro steps. That will generate the jars, and I
added the path to their location.

To be honest, this is a level of extra effort that compiler maintainers aren't typically willing to go through. You might get lucky with someone else, but I'm not personally willing.

The ideal actionable compiler bug report doesn't involve any external dependencies whatsoever. Having an external dependency of any kind greatly decreases the likelihood of an investigation occurring.

But if a report must involve an external dependency, that dependency ought to be readily available.

Aren't the JARs on Maven Central somewhere...?

@gergelyfabian
Copy link
Author

In the Bazel world everything is generated on the user's side (that's the reason why protobuf is also rebuilt - to make things reproducible). But, I'll soon send a link to the jar files uploaded to an url (from what I built locally).

@SethTisue
Copy link
Member

SethTisue commented Mar 13, 2025

protobuf is also rebuilt

And can we be confident that the resulting JARs aren't somehow corrupt...?

We seek to reduce or eliminate external dependencies in bug reports not only to reduce the effort level for maintainers, but also to narrow down the possible root causes of whatever is going wrong.

@SethTisue
Copy link
Member

In the Bazel world everything is generated on the user's side

Is that process intended to produce something that's equivalent to something on Maven Central?

@gergelyfabian
Copy link
Author

In the Bazel world everything is generated on the user's side

Is that process intended to produce something that's equivalent to something on Maven Central?

Yes, and if you check the protobuf tar.gz, it includes a Bazel BUILD file that is used to generate the jar file, and is also used to push jars in further targets (by protobuf's CI I suppose) to Maven Central. However, there are different versions of those jars, and I'm not sure it will be exactly the same jar that would end up as a dependency when I use it in my Bazel project.

@gergelyfabian
Copy link
Author

protobuf is also rebuilt

And can we be confident that the resulting JARs aren't somehow corrupt...?

We seek to reduce or eliminate external dependencies in bug reports not only to reduce the effort level for maintainers, but also to narrow down the possible root causes of whatever is going wrong.

I can never be sure, as this is a bug that happens in the interaction of Bazel with Scala (and there is rules_scala in the middle too). Obviously there can be an issue on Bazel or rules_scala side, but that would mean that either Bazel builds a wrong jar (for protobuf) or that rules_scala provides a wrong jar to the scalac compiler. I'd doubt that though as for Scala 2.12 there is no issue, only with Scala 2.13 the problem appears. And the same thing is with my sbt repro.

@gergelyfabian
Copy link
Author

@gergelyfabian
Copy link
Author

gergelyfabian commented Mar 13, 2025

With curl:

curl -L -o lib/libcore-hjar.jar 'https://drive.google.com/uc?export=download&id=1NbKjL5vwqoLUb-sbR3vMpKjKBqvF8pio'
curl -L -o lib/liblite_runtime_only-hjar.jar 'https://drive.google.com/uc?export=download&id=12pSp3bTaMZxqkzQP3Ah7SMuv_Co0vxfg'

@gergelyfabian
Copy link
Author

Here is the updated repro:

git clone git@github.com:gergelyfabian/scala-compiler-inline-bug.git
cd scala-compiler-inline-bug
mkdir -p lib
curl -L -o lib/libcore-hjar.jar 'https://drive.google.com/uc?export=download&id=1NbKjL5vwqoLUb-sbR3vMpKjKBqvF8pio'
curl -L -o lib/liblite_runtime_only-hjar.jar 'https://drive.google.com/uc?export=download&id=12pSp3bTaMZxqkzQP3Ah7SMuv_Co0vxfg'
sbt compile

@som-snytt
Copy link

There was another issue with ASM where @lrytz asked them to take a fix; I don't have a link. That's to say, it's not inconceivable that a strange build product interacts badly with ASM facilities.

@SethTisue
Copy link
Member

SethTisue commented Mar 13, 2025

I can confirm this is reproducible for me on JDK 21 with:

Example.scala

//> using scala 2.13.16
//> using jar libcore-hjar.jar liblite_runtime_only-hjar.jar
//> using options -opt-inline-from:**

import com.google.protobuf.CodedOutputStream

object Example {
  def repro(): Unit = {
    val result = new Array[Byte](0)
    CodedOutputStream.newInstance(result)
  }
}

Justfile

default:
  curl -L -o libcore-hjar.jar \
    'https://drive.google.com/uc?export=download&id=1NbKjL5vwqoLUb-sbR3vMpKjKBqvF8pio'
  curl -L -o liblite_runtime_only-hjar.jar \
    'https://drive.google.com/uc?export=download&id=12pSp3bTaMZxqkzQP3Ah7SMuv_Co0vxfg'
  scala compile Example.scala

@som-snytt
Copy link

som-snytt commented Mar 13, 2025

The modern option is -opt-inline:**. (Maybe -from works because it's just an alias; it used to need -opt.)

I have tried to get this change in the project build:

    //scalacOptions ++= Seq("-feature", "-opt:inline:scala/**", "-Wopt"),
    scalacOptions ++= Seq("-feature", "-opt:l:inline", "-opt-inline-from:scala/**", "-opt-warnings"),

Maybe that just needed a re-starr at some point?

@SethTisue
Copy link
Member

I thought I might see if this had regressed in some 2.13.x version, but it was broken as early as 2.13.11 and I can't test 2.13.10 or earlier because the JARs are JDK 21+ only.

@gergelyfabian
Copy link
Author

gergelyfabian commented Mar 13, 2025

I thought I might see if this had regressed in some 2.13.x version, but it was broken as early as 2.13.11 and I can't test 2.13.10 or earlier because the JARs are JDK 21+ only.

I could bake the jars for some earlier JDK version for you. What would you exactly need?

@SethTisue
Copy link
Member

Ideally 8, but I'm not sure at the moment if it's a good use of your time.

note that -opt:inline:com.google.** is sufficient to reproduce

@SethTisue
Copy link
Member

SethTisue commented Mar 13, 2025

@gergelyfabian liblite_runtime_only-hjar.jar seems sufficient to reproduce, I don't think libcore-hjar.jar matters here — is there some reason you included both JARs?

I haven't given up on wanting a reproduction that doesn't involve any Bazel-built JARs.

you wrote:

it seems to be related to the protobuf version Bazel uses

and what version is that? is the problem reproducible if we swap in the Maven Central JAR for that version?

@gergelyfabian
Copy link
Author

Files for Java 8 (hopefully):

  curl -L -o libcore-hjar.jar \
    'https://drive.google.com/uc?export=download&id=1e4JeVx9fLK8f7yZW0ro9kNrctuC6wIx9'
  curl -L -o liblite_runtime_only-hjar.jar \
    'https://drive.google.com/uc?export=download&id=19VgPcE5LkJmazhDUaUEvt6D6Jcq8zlYc'

Generated from a scala-compiler-bug-java-8 branch of my bazel-scala-example repo.

@gergelyfabian
Copy link
Author

There is no reason I included both. I'm quite new to debugging Scala compiler issues, so I was just trying to create a better repro.

The protobuf version is https://github.com/protocolbuffers/protobuf/archive/refs/tags/v21.7.tar.gz, but I tried with e.g. v3.21.10, and it also failed.

If I swap in the Maven Central JAR for e.g. 3.21.10 or 3.25.5, I could not reproduce it (I tried at least once for the exact same protobuf version, Maven Central JAR vs. Bazel JAR).

The issue seems to be only reproducible if I use the protobuf JAR built in Bazel, and I try my example Scala code with Scala 2.13.16 (not Scala 2.12.20).

Please note, that these repro jars seem to be "hjars", according to AI that would be:

A Bazel HJAR (or Hjar, which stands for "Header-only Java ARchive") is a concept in Bazel used to represent a Java archive that is primarily composed of Java header files (such as interfaces or API declarations) without implementation classes. This archive is intended to be used as a "stub" or "interface-only" JAR for Java code that will be consumed by other projects, which might not need the full implementation, just the API.

@SethTisue
Copy link
Member

SethTisue commented Mar 13, 2025

Thanks, the Java 8 JAR enabled me to determine that the problem is reproducible for me in Scala 2.13.9 but not in 2.13.8.

The issue seems to be only reproducible if I use the protobuf JAR built in Bazel

Okay, that marks the endpoint past which I'm not willing to investigate. (It's possible someone else will be more willing...)

A Bazel HJAR (or Hjar, which stands for "Header-only Java ARchive") is a concept in Bazel used to represent a Java archive that is primarily composed of Java header files (such as interfaces or API declarations) without implementation classes

Seems like inlining from such a JAR cannot possibly be expected to work...?

In which case the bug, if any, might simply be that we don't give a nicer error message?

@SethTisue
Copy link
Member

Seems like inlining from such a JAR cannot possibly be expected to work

(Though that still leaves one wondering why it didn't crash and burn in earlier Scala versions...)

@gergelyfabian
Copy link
Author

Another thing that makes me wonder is that originally we were inlining
other classes, not google protobuf ones, and it was failing when the
protobuf jar got to the classpath. Maybe we could add another dependency,
try inlining that, but keep protobuf on the classpath and it would still
reproduce?

I simply inlined everything for the simplest repro.

@SethTisue
Copy link
Member

Maybe we could add another dependency,
try inlining that, but keep protobuf on the classpath and it would still
reproduce?

That does sound like a worthwhile experiment.

@lrytz
Copy link
Member

lrytz commented Mar 14, 2025

Thanks all for the details. This is definitely due to the hjars, the inliner implementation expects non-abstract methods to have instructions.

We can add a better error message and skip inlining in this case, that should be easy to add.

@gergelyfabian
Copy link
Author

Did some further investigation.

I use a -opt-inline-from:com.mycompany.**,scalaz.**,scala.** inlining instruction, where some of the com.mycompany classes are real Scala dependencies that Bazel adds as normal jars (and there is no issue, if I have only those), but in some cases I have additional classes, that Bazel adds as hjars. The hjar I had an issue with was a Java library generated from protobuf. Also other Java dependencies, built right in Bazel, end up as hjars in the classpath.
All external dependencies (pulled down with rules_jvm_external) are added as normal (aka. Maven Central) jars, and there seem to be no issue with them (as we discussed earlier).

It would be great, if the Scala compiler would just ignore classes in hjars (even if the pattern matches them), but still inline those classes that match the pattern and are in normal jars.

In the meantime I'm still trying to find out what's up with these Java dependencies and hjars.

@lrytz
Copy link
Member

lrytz commented Mar 14, 2025

It would be great, if the Scala compiler would just ignore classes in hjars (even if the pattern matches them), but still inline those classes that match the pattern and are in normal jars.

That's what should happen after scala/scala#11016

@gergelyfabian
Copy link
Author

In the meantime I'm still trying to find out what's up with these Java dependencies and hjars.

https://bazelbuild.slack.com/archives/CA31HN1T3/p1741940032376929

@gergelyfabian
Copy link
Author

Related to bazelbuild/bazel#3528 as well.

@gergelyfabian
Copy link
Author

gergelyfabian commented Mar 14, 2025

Opened an issue in rules_scala (bazel-contrib/rules_scala#1717), where we can discuss the Bazel side of things (including possible workarounds), especially why and how hjars are used as dependencies, and how that relates to inlining.
The Scala bug with scala/scala#11016 will be solved, the rest of it is on the Bazel side.

Thanks all for your help!

@lrytz lrytz added this to the 2.13.17 milestone Mar 17, 2025
@lrytz lrytz self-assigned this Mar 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants