JDK-8275509: (jlink) SystemModulesPlugin generates a jdk.internal.module.SystemModules$all.class which isn't reproducible
Jaikiran Pai
jai.forums2013 at gmail.com
Thu Oct 21 09:49:58 UTC 2021
Hello Alan,
On 19/10/21 7:40 pm, Alan Bateman wrote:
> On 19/10/2021 14:49, Jaikiran Pai wrote:
>>
>> Ah! So this exact same investigation had already happened a few weeks
>> back then. I haven't subscribed to that list, so missed it. I see in
>> one of those messages this part:
>>
>> "Off hand I can't think of any issues with the ModuleDescriptor
>> hashCode. It is computed at link time and should be deterministic. If
>> I were to guess then then this may be something to do with the module
>> version recorded at compile-time at that is one of the components
>> that the hash is based on."
>>
>> To be clear, is the ModuleDescriptor#hashCode() expected to return
>> reproducible (same) hashCode across multiple runs? What currently
>> changes the hashCode() across multiple runs is various components
>> within ModuleDescriptor's hashCode() implementation using the
>> hashCode() of the enums (specifically the various Modifier enums).
>
> The discussion on jigsaw-dev didn't get to the bottom of the issue at
> the time, mostly because it wasn't easy to reproduce.
>
> Now that the issue is clearer then we should fix it. Aside from
> reproducible builds then I expect it is possible to use a
> ModuleDescriptor.Builder to create a ModuleDescriptor that is equal to
> a ModuleDescriptor in boot layer configuration but with a different
> hashCode.
>
Based on this input, one of the tests I have included for verifying this
proposed hashCode fix, involves dealing with a boot layer
ModuleDescriptor and then verifying it's hashCode against a
ModuleDescriptor that is built for the same module using the
ModuleDescriptor.Builder. It does reproduce the hashCode issue. However,
that test seems to have exposed a different bug with CDS and equality
checks against enums (which impact ModuleDescriptor#equals()).
More precisely, consider this trivial Java code:
import java.lang.module.*;
import java.io.*;
import java.util.*;
public class EnumEquality {
public static void main(final String[] args) throws Exception {
String moduleName = "java.sql";
// load the "java.sql" module from boot layer
Optional<Module> bootModule =
ModuleLayer.boot().findModule(moduleName);
if (bootModule.isEmpty()) {
throw new RuntimeException(moduleName + " module is missing
in boot layer");
}
ModuleDescriptor m1 = bootModule.get().getDescriptor();
// now recreate the same module using the ModuleDescriptor.read
on the module's module-info.class
ModuleDescriptor m2;
try (InputStream moduleInfo =
bootModule.get().getResourceAsStream("module-info.class")) {
if (moduleInfo == null) {
throw new RuntimeException("Could not locate
module-info.class in " + moduleName + " module");
}
// this will internally use a ModuleDescriptor.Builder to
construct the ModuleDescriptor
m2 = ModuleDescriptor.read(moduleInfo);
}
if (!m1.equals(m2)) {
// root cause - the enums aren't "equal"
for (ModuleDescriptor.Requires r1 : m1.requires()) {
if (r1.name().equals("java.base")) {
for (ModuleDescriptor.Requires r2 : m2.requires()) {
if (r2.name().equals("java.base")) {
System.out.println("Modifiers r1 " +
r1.modifiers() + " r2 " + r2.modifiers()
+ " --> equals? " +
r1.modifiers().equals(r2.modifiers()));
}
}
}
}
throw new RuntimeException("ModuleDescriptor(s) aren't
equal: \n" + m1 + "\n" + m2);
}
System.out.println("Success");
}
}
This program uses "java.sql" as the module under test. This "java.sql"
is a boot layer module and among other things has:
requires transitive java.logging;
requires transitive java.transaction.xa;
requires transitive java.xml;
in its module definition[1]. The program first loads this module's
ModuleDescriptor into an instance m1, using the boot() module layer. It
then "reconstructs" this same module by reading the module-info.class of
this module, using the ModuleDescriptor.read() API (which internally
calls ModuleDescriptor.Builder). This is stored into m2. m1 and m2 are
then checked for equality (using a call to equals() method). This
equality check keeps failing consistently.
Digging into it, it appears that since the ModuleDescriptor#equals()
calls equals() on enum types (in this specific case on
ModuleDescriptor.Requires.Modifier) and since enum type equality is
implemented as identity checks, those identity checks are surprisingly
failing. More specifically ModuleDescriptor.Requires.Modifier.MANDATED
== ModuleDescriptor.Requires.Modifier.MANDATED is equating to false
because at runtime I see that two different instances of
ModuleDescriptor.Requires.Modifier.MANDATED have been loaded (by the
same boot module classloader). Although I use
ModuleDescriptor.Requires.Modifier.MANDATED as an example, the same is
reproducible with other enum values like
ModuleDescriptor.Requires.Modifier.TRANSITIVE.
This appears to be specific to CDS since running the above program with:
java -Xshare:off EnumEquality
succeeds and the ModuleDescriptor equality check passes.
In short, it looks like there is some general issue with CDS and
equality checks with enums and perhaps deserves a separate JBS issue?
[1]
https://github.com/openjdk/jdk/blob/master/src/java.sql/share/classes/module-info.java#L34
-Jaikiran
More information about the core-libs-dev
mailing list