JDK-8309751 : Duplicate constant pool entries added during default method processing
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 22
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2023-06-09
  • Updated: 2024-04-16
  • Resolved: 2024-04-10
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 23
23 b18Fixed
Related Reports
Relates :  
Relates :  
Description
When using replay compilation I tried using the jar files generated by buildreplayjars command using clhsdb.
However, the replay compilation using those jar files failed with following error:

  Error while parsing line 13391 at position 49: constant pool length mismatch: wrong class files?

On debugging I found the runtime adds additional entries to the constant pool of a class during the handling of default methods.
These entries correspond to the overpass methods that throw AbstractMethodError [0]. New entries are created using BytecodeAssembler [1] which maintains its own copy of the newly created constant pool entries (in BytecodeConstantPool). But when it adds new entries it does not search the original constant pool for the presence of such entries. This has two implications:
1. It can potentially result in duplicate entries in the constant pool.
A simple test case for this:

public interface IWriter {
    public void write(final Object o);
    default public boolean canWrite() { return false; }
}
public interface IntWriter extends IWriter {
    public void writeInt(final Object o);
}
public class DefaultMethodTest {
  public static void main(String args[]) throws Exception {
    for (;;) {
      Thread.currentThread().sleep(1000);
    }
  }
  public void foo(IntWriter writer) {
    writer.writeInt(new Object());
  }
}

Note that IntWriter::writeInt has the same signature as IWriter::write.
After running DefaultMethodTest, attach to the process using jhsdb and dump the application class files using "buildreplayjars" command.
Disassembling the generated class file for IntWriter shows two Utf8 type entries corresponding to the same signature:

   #1 = Class              #2             // IntWriter
   #2 = Utf8               IntWriter
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Class              #6             // IWriter
   #6 = Utf8               IWriter
   #7 = Utf8               writeInt
   #8 = Utf8               (Ljava/lang/Object;)V
   #9 = Utf8               SourceFile
  #10 = Utf8               IntWriter.java
  #11 = Utf8               java/lang/AbstractMethodError
  #12 = Class              #11            // java/lang/AbstractMethodError
  #13 = Utf8               Method IntWriter.write(Ljava/lang/Object;)V is abstract
  #14 = String             #13            // Method IntWriter.write(Ljava/lang/Object;)V is abstract
  #15 = Utf8               (Ljava/lang/String;)V
  #16 = Utf8               <init>
  #17 = NameAndType        #16:#15        // "<init>":(Ljava/lang/String;)V
  #18 = Methodref          #12.#17        // java/lang/AbstractMethodError."<init>":(Ljava/lang/String;)V
  #19 = Utf8               write
  #20 = Utf8               (Ljava/lang/Object;)V

Note that #8 and #20 are the same. #20 gets added during default method processing.

2. Another consequence of this is using the jar files generated by buildreplayjars command for replay compilation results in mismatch in constant pool length.
This happens because the class file created by buildreplayjars already has the extra constant pool entries required for the overpass methods. And when the class is loaded during replay compilation, all such entries get added to the constant pool again, which makes constant pool size different than its recorded value in the replay file.
Currently this can be worked around by using -XX:+ReplayIgnoreInitErrors during replay compilation.

[0] https://github.com/openjdk/jdk/blob/84184f947342fd1adbe4e3f2230ce3de4ae6007e/src/hotspot/share/classfile/defaultMethods.cpp#L868
[1] https://github.com/openjdk/jdk/blob/84184f947342fd1adbe4e3f2230ce3de4ae6007e/src/hotspot/share/classfile/bytecodeAssembler.hpp#L173
Comments
Changeset: c5150c7b Author: Coleen Phillimore <coleenp@openjdk.org> Date: 2024-04-10 12:38:07 +0000 URL: https://git.openjdk.org/jdk/commit/c5150c7b81e2b7b8c9e13c228d3b7bcb9dfe5024
10-04-2024

It does cause a bug when dumping classfiles for replay compilation, and even though duplicate constant pool entries are technically legal, javac and other places have been fixed to not have duplicates see: JDK-6746955.
02-04-2024

Is this a bug or an enhancement? The PR indicates no spec rules are violated by having multiple CP entries.
02-04-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/18548 Date: 2024-03-29 13:34:01 +0000
29-03-2024

[~coleenp] Sorry Coleen I don't have a jtreg test case for this.
15-03-2024

[~asmehra] Your patch looks reasonable. I was originally thinking that we could repurpose some redefinition constant pool comparison code, which does the same thing but copying the relevant entries might be easier. The copying should be more lazy though. This code was designed to do more than create error methods to participate in overload resolution, and every time I look at it I wonder why it needs to be doing all the things it does. It creates an AME throwing method for java/lang/reflect/GenericDeclaration.getAnnotation(Ljava/lang/Class;)Ljava/lang/annotation/Annotation; because AnnotatedElement has a default method but this one isn't. So GenericDecclaration has to have an overpass method in case of ... not sure what. public interface GenericDeclaration extends AnnotatedElement { public TypeVariable<?>[] getTypeParameters(); } Anyway, your patch makes sense if it initializes the BytecodeConstantPool only in the overpass case (not if it finds a default method). I can make this change and credit you with author. If you happen to have an add-ible jtreg test case, that would be nice too. Otherwise, I'll try to cobble something up. Thanks. Also we've added some bounds checking to that code, which I'll integrate with your fix.
14-03-2024

[~coleenp] I have a patch for fixing this sitting in my repo for quite some time - https://github.com/ashu-mehra/jdk/commit/932af56bd042adb041ca4d15b6d0776cfae7635f I hope it has not gone stale. It basically does what I have mentioned in my previous comment, i.e. add the entries from the original constant pool into the BytecodeConstantPool before adding new entries, so that duplicates can be eliminated. As this involves doing an additional pass over the constant pool, it may impact the performance, but I never got around to measure its impact, which is why I didn't create a PR so far. Feel free to take this patch forward if you like the approach.
23-01-2024

A straight forward approach to fix this is to add the entries from the original constant pool into the BytecodeConstantPool before adding new entries. This would allow us to search for an existing entry before it gets add to BytecodeConstantPool. We don't need to add all the entries from the original constant pool; only the entries of those types that BytecodeConstantPool can hold ie. utf8, string, class, name_and_type and methodref.
09-06-2023