-
Bug
-
Resolution: Unresolved
-
P4
-
None
-
22, 23, 24
-
generic
-
generic
A DESCRIPTION OF THE PROBLEM :
When comparing two `PoolEntry` or `BootstrapMethodEntry` objects made by distinct `ConstantPoolBuilder`s, they return `true` from `equals`, but can have different `hashCode`s, as the `hashCode`s depend on the index of the referenced `PoolEntry` fields in the `ConstantPool`.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached Java program.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The program completes successfully.
ACTUAL -
The program fails with:
```
Exception in thread "main" java.lang.AssertionError: [
interface java.lang.classfile.constantpool.ClassEntry,
interface java.lang.classfile.constantpool.StringEntry,
interface java.lang.classfile.constantpool.FieldRefEntry,
interface java.lang.classfile.constantpool.MethodRefEntry,
interface java.lang.classfile.constantpool.InterfaceMethodRefEntry,
interface java.lang.classfile.constantpool.NameAndTypeEntry,
interface java.lang.classfile.constantpool.MethodHandleEntry,
interface java.lang.classfile.constantpool.MethodTypeEntry,
interface java.lang.classfile.constantpool.ConstantDynamicEntry,
interface java.lang.classfile.constantpool.InvokeDynamicEntry,
interface java.lang.classfile.constantpool.ModuleEntry,
interface java.lang.classfile.constantpool.PackageEntry,
interface java.lang.classfile.BootstrapMethodEntry
]
at PoolEntryEqHashBug.main(PoolEntryEqHashBug.java:85)
```
---------- BEGIN SOURCE ----------
/*
* Any copyright is dedicated to the Public Domain.
* https://creativecommons.org/publicdomain/zero/1.0/
*/
import java.lang.classfile.BootstrapMethodEntry;
import java.lang.classfile.constantpool.*;
import java.lang.constant.*;
import java.lang.constant.DirectMethodHandleDesc.Kind;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.StringConcatFactory;
import java.lang.invoke.TypeDescriptor;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.SequencedSet;
import java.util.function.Function;
import static java.lang.constant.ConstantDescs.*;
public final class PoolEntryEqHashBug {
/**
* @param args the command line arguments
*/
public static final void main(final String... args) {
var pool1 = ConstantPoolBuilder.of();
var pool2 = ConstantPoolBuilder.of();
// ensure that pool1 and pool2 have different indicies:
prefillWithGarbage(pool1);
SequencedSet<Class<?>> badPoolEntries
= new LinkedHashSet<>();
// Root entries
testPoolEntry(badPoolEntries, Utf8Entry.class, pool1, pool2, pool -> pool.utf8Entry("Test Utf8Entry"));
testPoolEntry(badPoolEntries, IntegerEntry.class, pool1, pool2, pool -> pool.intEntry(12345));
testPoolEntry(badPoolEntries, FloatEntry.class, pool1, pool2, pool -> pool.floatEntry(12345f));
testPoolEntry(badPoolEntries, LongEntry.class, pool1, pool2, pool -> pool.longEntry(12345L));
testPoolEntry(badPoolEntries, DoubleEntry.class, pool1, pool2, pool -> pool.doubleEntry(12345d));
// Ref entries
testPoolEntry(badPoolEntries, ClassEntry.class, pool1, pool2, pool -> pool.classEntry(CD_Object));
testPoolEntry(badPoolEntries, StringEntry.class, pool1, pool2, pool -> pool.stringEntry("Test String"));
testPoolEntry(badPoolEntries, FieldRefEntry.class, pool1, pool2, pool -> pool.fieldRefEntry(CD_Boolean, "TRUE", CD_Boolean));
testPoolEntry(badPoolEntries, MethodRefEntry.class, pool1, pool2, pool -> pool.methodRefEntry(CD_Exception, INIT_NAME, MTD_void));
testPoolEntry(badPoolEntries, InterfaceMethodRefEntry.class, pool1, pool2, pool -> pool.interfaceMethodRefEntry(CD_Collection, "isEmpty", MethodTypeDesc.of(CD_boolean)));
testPoolEntry(badPoolEntries, NameAndTypeEntry.class, pool1, pool2, pool -> pool.nameAndTypeEntry("foo", MethodTypeDesc.of(CD_Object)));
testPoolEntry(badPoolEntries, MethodHandleEntry.class, pool1, pool2, pool -> pool.methodHandleEntry(BSM_INVOKE));
testPoolEntry(badPoolEntries, MethodTypeEntry.class, pool1, pool2, pool -> pool.methodTypeEntry(MethodTypeDesc.of(CD_String)));
testPoolEntry(badPoolEntries, ConstantDynamicEntry.class, pool1, pool2, pool -> pool.constantDynamicEntry(FALSE));
testPoolEntry(badPoolEntries, InvokeDynamicEntry.class, pool1, pool2, pool -> pool.invokeDynamicEntry(
DynamicCallSiteDesc.of(
ConstantDescs.ofCallsiteBootstrap(
ClassDesc.of(StringConcatFactory.class.getName()),
"makeConcat",
CD_CallSite
),
MethodTypeDesc.of(CD_String, CD_Object, CD_Object, CD_Object)
)
));
testPoolEntry(badPoolEntries, ModuleEntry.class, pool1, pool2, pool -> pool.moduleEntry(ModuleDesc.of("java.base")));
testPoolEntry(badPoolEntries, PackageEntry.class, pool1, pool2, pool -> pool.packageEntry(PackageDesc.ofInternalName("java/lang")));
testPoolEntry(badPoolEntries, BootstrapMethodEntry.class, pool1, pool2, pool -> pool.bsmEntry(
ConstantDescs.ofCallsiteBootstrap(
ClassDesc.of(LambdaMetafactory.class.getName()),
"metafactory",
CD_CallSite,
CD_MethodType,
CD_MethodHandle,
CD_MethodType
),
List.of(
MethodTypeDesc.of(CD_Object, CD_Object),
MethodHandleDesc.ofMethod(Kind.VIRTUAL, CD_Object, "hashCode", MethodTypeDesc.of(CD_int)),
MethodTypeDesc.of(CD_int, CD_Object)
)
));
if (!badPoolEntries.isEmpty()) {
throw new AssertionError(badPoolEntries);
}
}
private static final <T> void testPoolEntry(
final SequencedSet<Class<?>> badPoolEntries,
final Class<T> type,
final ConstantPoolBuilder pool1,
final ConstantPoolBuilder pool2,
final Function<? super ConstantPoolBuilder, ? extends T> factory
) {
final var entry1 = type.cast(factory.apply(pool1));
final var entry2 = type.cast(factory.apply(pool2));
if (entry1.equals(entry2) && entry1.hashCode() != entry2.hashCode()) {
badPoolEntries.add(type);
}
}
private static final void prefillWithGarbage(final ConstantPoolBuilder pool) {
for (int i = 0; i < 10; i += 1) {
pool.utf8Entry("ignore: " + i);
}
pool.bsmEntry(
MethodHandleDesc.ofMethod(
Kind.STATIC,
ClassDesc.of(DEFAULT_NAME),
DEFAULT_NAME,
MethodTypeDesc.of(CD_Object, CD_MethodHandles_Lookup, CD_String, ClassDesc.of(TypeDescriptor.class.getName()))
),
List.of()
);
}
}
---------- END SOURCE ----------
FREQUENCY : always
When comparing two `PoolEntry` or `BootstrapMethodEntry` objects made by distinct `ConstantPoolBuilder`s, they return `true` from `equals`, but can have different `hashCode`s, as the `hashCode`s depend on the index of the referenced `PoolEntry` fields in the `ConstantPool`.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached Java program.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The program completes successfully.
ACTUAL -
The program fails with:
```
Exception in thread "main" java.lang.AssertionError: [
interface java.lang.classfile.constantpool.ClassEntry,
interface java.lang.classfile.constantpool.StringEntry,
interface java.lang.classfile.constantpool.FieldRefEntry,
interface java.lang.classfile.constantpool.MethodRefEntry,
interface java.lang.classfile.constantpool.InterfaceMethodRefEntry,
interface java.lang.classfile.constantpool.NameAndTypeEntry,
interface java.lang.classfile.constantpool.MethodHandleEntry,
interface java.lang.classfile.constantpool.MethodTypeEntry,
interface java.lang.classfile.constantpool.ConstantDynamicEntry,
interface java.lang.classfile.constantpool.InvokeDynamicEntry,
interface java.lang.classfile.constantpool.ModuleEntry,
interface java.lang.classfile.constantpool.PackageEntry,
interface java.lang.classfile.BootstrapMethodEntry
]
at PoolEntryEqHashBug.main(PoolEntryEqHashBug.java:85)
```
---------- BEGIN SOURCE ----------
/*
* Any copyright is dedicated to the Public Domain.
* https://creativecommons.org/publicdomain/zero/1.0/
*/
import java.lang.classfile.BootstrapMethodEntry;
import java.lang.classfile.constantpool.*;
import java.lang.constant.*;
import java.lang.constant.DirectMethodHandleDesc.Kind;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.StringConcatFactory;
import java.lang.invoke.TypeDescriptor;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.SequencedSet;
import java.util.function.Function;
import static java.lang.constant.ConstantDescs.*;
public final class PoolEntryEqHashBug {
/**
* @param args the command line arguments
*/
public static final void main(final String... args) {
var pool1 = ConstantPoolBuilder.of();
var pool2 = ConstantPoolBuilder.of();
// ensure that pool1 and pool2 have different indicies:
prefillWithGarbage(pool1);
SequencedSet<Class<?>> badPoolEntries
= new LinkedHashSet<>();
// Root entries
testPoolEntry(badPoolEntries, Utf8Entry.class, pool1, pool2, pool -> pool.utf8Entry("Test Utf8Entry"));
testPoolEntry(badPoolEntries, IntegerEntry.class, pool1, pool2, pool -> pool.intEntry(12345));
testPoolEntry(badPoolEntries, FloatEntry.class, pool1, pool2, pool -> pool.floatEntry(12345f));
testPoolEntry(badPoolEntries, LongEntry.class, pool1, pool2, pool -> pool.longEntry(12345L));
testPoolEntry(badPoolEntries, DoubleEntry.class, pool1, pool2, pool -> pool.doubleEntry(12345d));
// Ref entries
testPoolEntry(badPoolEntries, ClassEntry.class, pool1, pool2, pool -> pool.classEntry(CD_Object));
testPoolEntry(badPoolEntries, StringEntry.class, pool1, pool2, pool -> pool.stringEntry("Test String"));
testPoolEntry(badPoolEntries, FieldRefEntry.class, pool1, pool2, pool -> pool.fieldRefEntry(CD_Boolean, "TRUE", CD_Boolean));
testPoolEntry(badPoolEntries, MethodRefEntry.class, pool1, pool2, pool -> pool.methodRefEntry(CD_Exception, INIT_NAME, MTD_void));
testPoolEntry(badPoolEntries, InterfaceMethodRefEntry.class, pool1, pool2, pool -> pool.interfaceMethodRefEntry(CD_Collection, "isEmpty", MethodTypeDesc.of(CD_boolean)));
testPoolEntry(badPoolEntries, NameAndTypeEntry.class, pool1, pool2, pool -> pool.nameAndTypeEntry("foo", MethodTypeDesc.of(CD_Object)));
testPoolEntry(badPoolEntries, MethodHandleEntry.class, pool1, pool2, pool -> pool.methodHandleEntry(BSM_INVOKE));
testPoolEntry(badPoolEntries, MethodTypeEntry.class, pool1, pool2, pool -> pool.methodTypeEntry(MethodTypeDesc.of(CD_String)));
testPoolEntry(badPoolEntries, ConstantDynamicEntry.class, pool1, pool2, pool -> pool.constantDynamicEntry(FALSE));
testPoolEntry(badPoolEntries, InvokeDynamicEntry.class, pool1, pool2, pool -> pool.invokeDynamicEntry(
DynamicCallSiteDesc.of(
ConstantDescs.ofCallsiteBootstrap(
ClassDesc.of(StringConcatFactory.class.getName()),
"makeConcat",
CD_CallSite
),
MethodTypeDesc.of(CD_String, CD_Object, CD_Object, CD_Object)
)
));
testPoolEntry(badPoolEntries, ModuleEntry.class, pool1, pool2, pool -> pool.moduleEntry(ModuleDesc.of("java.base")));
testPoolEntry(badPoolEntries, PackageEntry.class, pool1, pool2, pool -> pool.packageEntry(PackageDesc.ofInternalName("java/lang")));
testPoolEntry(badPoolEntries, BootstrapMethodEntry.class, pool1, pool2, pool -> pool.bsmEntry(
ConstantDescs.ofCallsiteBootstrap(
ClassDesc.of(LambdaMetafactory.class.getName()),
"metafactory",
CD_CallSite,
CD_MethodType,
CD_MethodHandle,
CD_MethodType
),
List.of(
MethodTypeDesc.of(CD_Object, CD_Object),
MethodHandleDesc.ofMethod(Kind.VIRTUAL, CD_Object, "hashCode", MethodTypeDesc.of(CD_int)),
MethodTypeDesc.of(CD_int, CD_Object)
)
));
if (!badPoolEntries.isEmpty()) {
throw new AssertionError(badPoolEntries);
}
}
private static final <T> void testPoolEntry(
final SequencedSet<Class<?>> badPoolEntries,
final Class<T> type,
final ConstantPoolBuilder pool1,
final ConstantPoolBuilder pool2,
final Function<? super ConstantPoolBuilder, ? extends T> factory
) {
final var entry1 = type.cast(factory.apply(pool1));
final var entry2 = type.cast(factory.apply(pool2));
if (entry1.equals(entry2) && entry1.hashCode() != entry2.hashCode()) {
badPoolEntries.add(type);
}
}
private static final void prefillWithGarbage(final ConstantPoolBuilder pool) {
for (int i = 0; i < 10; i += 1) {
pool.utf8Entry("ignore: " + i);
}
pool.bsmEntry(
MethodHandleDesc.ofMethod(
Kind.STATIC,
ClassDesc.of(DEFAULT_NAME),
DEFAULT_NAME,
MethodTypeDesc.of(CD_Object, CD_MethodHandles_Lookup, CD_String, ClassDesc.of(TypeDescriptor.class.getName()))
),
List.of()
);
}
}
---------- END SOURCE ----------
FREQUENCY : always