bazel
bazel copied to clipboard
Java Header Compilation does not respect access modifiers when using star imports with classes of the same name
Description of the bug:
Whilst migrating existing code from Maven to Bazel, I came across a weird compilation discrepancy. TL;DR, a class with the same name is present in two libraries. A downstream consumer imports both libraries via star-imports. When building a target with bazel, the header compilation of a dependency references the private class in Library1, when it should reference the public class in Library2. This causes a compilation failure. Maven compiles successfully.
Context:
There are two java libraries: Library1 and Library2. They both declare a class "Pair", however only Library2 declares it as public. Relevant snippets included below, but the full code can be seen in the repro code.
Example snippet
package com.example.library1;
class Pair<T1, T2>
{
private T1 theFirst;
private T2 theSecond;
public Pair(T1 aFirst, T2 aSecond)
{
theFirst = aFirst;
theSecond = aSecond;
}
public T1 first()
{
return theFirst;
}
public T2 second()
{
return theSecond;
}
}
package com.example.library2;
public class Pair<T1, T2>
{
private T1 theFirst;
private T2 theSecond;
public Pair(T1 aFirst, T2 aSecond)
{
theFirst = aFirst;
theSecond = aSecond;
}
public T1 first()
{
return theFirst;
}
public T2 second()
{
return theSecond;
}
}
We have a third library, lets call it "extension". It has a dependency on both Library1 and Library2, and uses star-imports to import everything from the packages.
Inside of an abstract class definition in "extension", we reference Pair
(not fully qualified). It should resolve to Pair
from Library2, since that is the only one that is public.
package com.example.extension;
import com.example.library1.*;
import com.example.library2.*;
public abstract class User {
public Pair<String, String> getUser() {
return new Pair<String, String>("a", "b");
}
}
Finally, there is a fourth library "consumer". It only depends on Extension and Library2 - both via star imports. This extends the abstract class defined in "extension", and overrides the method returning Pair.
package com.example.consumer;
import com.example.extension.*;
import com.example.library2.*;
class ActualUser extends User {
@Override
public Pair<String, String> getUser() {
return new Pair<String, String>("c", "d");
}
}
Maven compiles this all file, but when running bazel build //example/consumer
, we find the following compilation error:
example/consumer/src/main/java/com/example/consumer/Consumer.java:8: error: getUser() in ActualUser cannot override getUser() in User
public Pair<String, String> getUser() {
^
return type com.example.library2.Pair<String,String> is not compatible with com.example.library1.Pair<String,String>
This seems odd at first. If we run bazel build //example/extension
, we find the following decompiled .class definition in the produced libextension.jar
. Everything looks correct here.
package com.example.extension;
import com.example.library2.Pair;
public abstract class User {
public Pair<String, String> getUser() {
return new Pair("a", "b");
}
}
But then, we can see when running bazel build //example/consumer --sandbox_debug
, we actually use libextension-hjar.jar
when compiling. Decompiling the class inside of here, we find the problem:
package com.example.extension;
import com.example.library1.Pair;
public abstract class User {
public User() {
}
public Pair<String, String> getUser() {
}
}
The Pair
resolved during hjar generation comes from Library1, when it should come from Library2. Interestingly, if the hjar compiler could see both the Pair's, I would've thought that at least it would raise some sort of error if it could not discern which Pair to use.
If we run bazel build //example/consumer --nojava_header_compilation
, the build succeeds.
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.
Checkout the repo code at https://github.com/JohnnyMorganz/bazel-java-header-compilation-bug
Run the command bazel run //example/consumer
and see the compilation failure.
Run bazel run //example/consumer --nojava_header_compilation
and see compilation success
Which operating system are you running Bazel on?
Rocky Linux 9.3
What is the output of bazel info release
?
release 6.4.0, but also tested on release 7.0.2
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
Is this a regression? If yes, please try to identify the Bazel commit where the bug was introduced.
No response
Have you found anything relevant by searching the web?
Poking around the bazel code, it looks like header compilation comes from a tool "turbine": https://github.com/google/turbine/tree/main. I am unsure whether this issue should actually live in the turbine repo.
Any other information, logs, or outputs that you want to share?
No response
Cc @cushon
Presumably this has been fixed by https://github.com/google/turbine/pull/313 so all we need now is a turbine release and updating that in Bazel.
This will be resolved once there's a new java_tools and rules_java release (https://github.com/bazelbuild/bazel/pull/22310)