bazelbuild / bazel

a fast, scalable, multi-language and extensible build system
https://bazel.build
Apache License 2.0
23.21k stars 4.06k forks source link

ijar / hjar producing incorrect Java 21 exhaustive switch statement errors #24138

Closed fzakaria closed 1 week ago

fzakaria commented 1 week ago

Description of the bug:

I have a very weird bug which I've created a very small reproduction at this repo

In essence, I have a library with a single class:

public class Sealed {
    public sealed interface Error {
        record UserNotFound(String userName) implements Error { }
        record OtherError(Throwable cause) implements Error { }
    }
}

I have another library which uses it.

public class Test {
    public static void handleError(Sealed.Error error) {
        switch (error) {
            case Sealed.Error.UserNotFound userNotFound ->
                    System.out.println("User not found: " + userNotFound.userName());
            case Sealed.Error.OtherError otherError ->
                    System.out.println("An error occurred: " + otherError.cause().getMessage());
        }
    }
}

Here is the minimal BUILD file

load("@rules_java//java:defs.bzl", "java_library")

java_library(
    name = "sealed",
    srcs = [
        "src/Sealed.java",
    ],
)

java_library(
    name = "test",
    srcs = [
        "src/Test.java",
    ],
    deps = [
        ":sealed",
    ],
)

This produces the following error:

> bazel build //:test
INFO: Invocation ID: 0ae940e9-a6ef-431e-b626-c57b3be9a722
INFO: Analyzed target //:test (0 packages loaded, 0 targets configured).
ERROR: /Users/fzakaria/code/playground/bazel/java-21-switch/BUILD.bazel:24:13: Building libtest.jar (1 source file) failed: (Exit 1): java failed: error executing Javac command (from target //:test) external/rules_java~~toolchains~remotejdk21_macos_aarch64/bin/java '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' '--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED' ... (remaining 19 arguments skipped)
src/Test.java:4: error: the switch statement does not cover all possible input values
        switch (error) {

If however I change //:sealed to be a java_export from contrib_rules_jvm

load("@contrib_rules_jvm//java:defs.bzl", "java_export")

java_export(
    name = "sealed",
    srcs = [
        "src/Sealed.java",
    ],
    maven_coordinates = "abc:abc:1.0.0",
    tags = [
        "no-javadocs",
    ],
)

java_library(
    name = "test",
    srcs = [
        "src/Test.java",
    ],
    deps = [
        ":sealed",
    ],
)

The build completes successfully

> bazel build //:test
INFO: Invocation ID: deb880ca-396e-414f-98f9-fbdee97a0712
INFO: Analyzed target //:test (0 packages loaded, 6 targets configured).
INFO: Found 1 target...
Target //:test up-to-date:
  bazel-bin/libtest.jar
INFO: Elapsed time: 0.116s, Critical Path: 0.01s
INFO: 2 processes: 1 disk cache hit, 1 internal.
INFO: Build completed successfully, 2 total actions

We noticed that compiling ourselves against the ijar or hjar (unsure the distinction between the two causes the failure) This leads me to believe that important information is being stripped out.

# succeeds
> bazel-java-21-switch/external/rules_java~~toolchains~remotejdk21_macos_aarch64/bin/javac src/Test.java -cp bazel-bin/libsealed.jar

# fails
> bazel-java-21-switch/external/rules_java~~toolchains~remotejdk21_macos_aarch64/bin/javac src/Test.java -cp bazel-bin/libsealed-hjar.jar
src/Test.java:4: error: the switch statement does not cover all possible input values
        switch (error) {
        ^
1 error

Which category does this issue belong to?

Java Rules

What's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.

Please see this repo

Which operating system are you running Bazel on?

MacOS

What is the output of bazel info release?

release 7.4.0

If bazel info release returns development version or (@non-git), tell us how you built Bazel.

No response

What's the output of git remote get-url origin; git rev-parse HEAD ?

No response

If this is a regression, please try to identify the Bazel commit where the bug was introduced with bazelisk --bisect.

No response

Have you found anything relevant by searching the web?

No.

Any other information, logs, or outputs that you want to share?

No response

fmeum commented 1 week ago

@cushon

cushon commented 1 week ago

This was fixed in https://github.com/google/turbine/commit/0c6eb49b7401fb3b542a734d0c571f91b7fb7f7e, I'll do a release

fzakaria commented 1 week ago

@cushon or @fmeum looks like java_export ends up creating an ijar instead which does work.

> bazel-java-21-switch/external/rules_java~~toolchains~remotejdk21_macos_aarch64/bin/javac src/Test.java -cp bazel-bin/sealed-project-ijar.jar

This is interesting since my understanding was ijar doesn't support "newer Java runtime bytecode". Is there something I can read about this; and should contrib_rules_jvm use hjar instead? (CC @shs96c )

cushon commented 1 week ago

This is interesting since my understanding was ijar doesn't support "newer Java runtime bytecode"

ijar should support the latest bytecode features, do you remember where you saw that?

should contrib_rules_jvm use hjar instead? (CC @shs96c )

It's probably worth considering, it should be faster.

fzakaria commented 1 week ago

@cushon admittedly I asked ChatGPT because I didn't know about hjar and how to find it. I couldn't find any rationale on the differences; happy to read more if you have links.

Is there a flag to pick one or the other for now to avoid the failure?

In Bazel, ijar (interface jar) and hjar (header jar) are two different approaches to handling Java dependencies to speed up incremental builds and reduce memory usage.

  1. ijar (Interface JAR) ijar is an earlier tool for Bazel's Java builds, creating a minimal JAR containing only the class signatures (public APIs) without method bodies or implementation details. This approach allows Bazel to compile dependent targets without needing the full JAR, saving resources. However, ijar has limitations in supporting newer Java language features, particularly those related to Java 9 and above, due to missing implementation details that some features rely on.
  2. hjar (Header JAR) hjar, or "header JAR," is an evolution of ijar and is generated using the turbine tool. hjar preserves all public API information, including more modern Java features, allowing for better compatibility and support for advanced Java features (e.g., modules). Unlike ijar, hjar includes certain additional details needed for accurate compilation without including the full implementation, making it more reliable for newer Java versions. Bazel uses hjar by default as it provides a more complete API surface compared to ijar.
cushon commented 1 week ago

This is fixed by 3ed6f3bf3a9fd8c4b07e53c43aaf2d68c3cbf1f4, but that needs to make it into a rules_java release.

@hvadehra do you want to leave this bug open to track that, or should I close it out?

hvadehra commented 1 week ago

rules_java v8.3.0 has been released that includes this. I'm updating it in Bazel in https://github.com/bazelbuild/bazel/pull/24143.

fzakaria commented 1 week ago

Does that mean if we pin the newest rules_java we don't need to bump bazel ?

hvadehra commented 1 week ago

Does that mean if we pin the newest rules_java we don't need to bump bazel ?

Yes