ConstantDynamic instances should not be canonicalized as they define distinct constants
I would like to construct the class file shown at the bottom of this issue. However, that is not possible with ASM. The issue arises from the two ldc
instructions using a dynamic constant using the exact same BSM and arguments to that BSM seen in the two methods getConstant1
and getConstant2
:
public static java.lang.Object getConstant1();
descriptor: ()Ljava/lang/Object;
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #27 // Dynamic #0:constantName:Ljava/lang/Object;
2: areturn
LineNumberTable:
line 135: 0
public static java.lang.Object getConstant2();
descriptor: ()Ljava/lang/Object;
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #29 // Dynamic #1:constantName:Ljava/lang/Object;
2: areturn
LineNumberTable:
line 139: 0
When trying to generate these methods using ASM:
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "getConstant1", "()Ljava/lang/Object;", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(135, label0);
methodVisitor.visitLdcInsn(new ConstantDynamic("constantName", "Ljava/lang/Object;", new Handle(Opcodes.H_INVOKESTATIC, "A", "myConstant", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", false), new Object[] {}));
methodVisitor.visitInsn(ARETURN);
methodVisitor.visitMaxs(1, 0);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "getConstant2", "()Ljava/lang/Object;", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(139, label0);
methodVisitor.visitLdcInsn(new ConstantDynamic("constantName", "Ljava/lang/Object;", new Handle(Opcodes.H_INVOKESTATIC, "A", "myConstant", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", false), new Object[] {}));
methodVisitor.visitInsn(ARETURN);
methodVisitor.visitMaxs(1, 0);
methodVisitor.visitEnd();
}
the two new ConstantDynamic
taking the exact same arguments will be canonicalized during writing and the generated class file will not have two dynamic constants but only one.
To actually generate the class file below I did a crude patch on ASM (see here), which is not a general solution.
Full javap output of the class file (the bytes for it is here):
Classfile A.class
Last modified Mar 17, 2023; size 1364 bytes
SHA-256 checksum 4379e359d727521479cee1aa5b6d711090b2777553454d9b667fe502a1b0daa9
Compiled from "A.java"
public class A
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #2 // A
super_class: #4 // java/lang/Object
interfaces: 0, fields: 0, methods: 5, attributes: 3
Constant pool:
#1 = Utf8 A
#2 = Class #1 // A
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 A.java
#6 = Utf8 java/lang/invoke/MethodHandles$Lookup
#7 = Class #6 // java/lang/invoke/MethodHandles$Lookup
#8 = Utf8 java/lang/invoke/MethodHandles
#9 = Class #8 // java/lang/invoke/MethodHandles
#10 = Utf8 Lookup
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = NameAndType #11:#12 // "<init>":()V
#14 = Methodref #4.#13 // java/lang/Object."<init>":()V
#15 = Utf8 this
#16 = Utf8 LA;
#17 = Utf8 getConstant1
#18 = Utf8 ()Ljava/lang/Object;
#19 = Utf8 myConstant
#20 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
#21 = NameAndType #19:#20 // myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
#22 = Methodref #2.#21 // A.myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
#23 = MethodHandle 6:#22 // REF_invokeStatic A.myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
#24 = Utf8 constantName
#25 = Utf8 Ljava/lang/Object;
#26 = NameAndType #24:#25 // constantName:Ljava/lang/Object;
#27 = Dynamic #0:#26 // #0:constantName:Ljava/lang/Object;
#28 = Utf8 getConstant2
#29 = Dynamic #1:#26 // #1:constantName:Ljava/lang/Object;
#30 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class<*>;)Ljava/lang/Object;
#31 = Utf8 lookup
#32 = Utf8 Ljava/lang/invoke/MethodHandles$Lookup;
#33 = Utf8 name
#34 = Utf8 Ljava/lang/String;
#35 = Utf8 type
#36 = Utf8 Ljava/lang/Class<*>;
#37 = Utf8 Ljava/lang/Class;
#38 = Utf8 main
#39 = Utf8 ([Ljava/lang/String;)V
#40 = Utf8 java/lang/System
#41 = Class #40 // java/lang/System
#42 = Utf8 out
#43 = Utf8 Ljava/io/PrintStream;
#44 = NameAndType #42:#43 // out:Ljava/io/PrintStream;
#45 = Fieldref #41.#44 // java/lang/System.out:Ljava/io/PrintStream;
#46 = NameAndType #17:#18 // getConstant1:()Ljava/lang/Object;
#47 = Methodref #2.#46 // A.getConstant1:()Ljava/lang/Object;
#48 = NameAndType #28:#18 // getConstant2:()Ljava/lang/Object;
#49 = Methodref #2.#48 // A.getConstant2:()Ljava/lang/Object;
#50 = Utf8 java/io/PrintStream
#51 = Class #50 // java/io/PrintStream
#52 = Utf8 [Ljava/lang/String;
#53 = Class #52 // "[Ljava/lang/String;"
#54 = Utf8 println
#55 = Utf8 (Z)V
#56 = NameAndType #54:#55 // println:(Z)V
#57 = Methodref #51.#56 // java/io/PrintStream.println:(Z)V
#58 = Utf8 Code
#59 = Utf8 LineNumberTable
#60 = Utf8 LocalVariableTable
#61 = Utf8 LocalVariableTypeTable
#62 = Utf8 Signature
#63 = Utf8 StackMapTable
#64 = Utf8 InnerClasses
#65 = Utf8 SourceFile
#66 = Utf8 BootstrapMethods
{
public A();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #14 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 132: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LA;
public static java.lang.Object getConstant1();
descriptor: ()Ljava/lang/Object;
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #27 // Dynamic #0:constantName:Ljava/lang/Object;
2: areturn
LineNumberTable:
line 135: 0
public static java.lang.Object getConstant2();
descriptor: ()Ljava/lang/Object;
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #29 // Dynamic #1:constantName:Ljava/lang/Object;
2: areturn
LineNumberTable:
line 139: 0
private static java.lang.Object myConstant(java.lang.invoke.MethodHandles$Lookup, java.lang.String, java.lang.Class<?>);
descriptor: (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
Code:
stack=2, locals=3, args_size=3
0: new #4 // class java/lang/Object
3: dup
4: invokespecial #14 // Method java/lang/Object."<init>":()V
7: areturn
LineNumberTable:
line 143: 0
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 lookup Ljava/lang/invoke/MethodHandles$Lookup;
0 8 1 name Ljava/lang/String;
0 8 2 type Ljava/lang/Class;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 8 2 type Ljava/lang/Class<*>;
Signature: #30 // (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class<*>;)Ljava/lang/Object;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #45 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #47 // Method getConstant1:()Ljava/lang/Object;
6: invokestatic #49 // Method getConstant2:()Ljava/lang/Object;
9: if_acmpne 16
12: iconst_1
13: goto 17
16: iconst_0
17: invokevirtual #57 // Method java/io/PrintStream.println:(Z)V
20: return
StackMapTable: number_of_entries = 2
frame_type = 80 // same_locals_1_stack_item
stack = [ class java/io/PrintStream ]
frame_type = 255 // full_frame
offset_delta = 0
locals = [ class "[Ljava/lang/String;" ]
stack = [ class java/io/PrintStream, int ]
LineNumberTable:
line 12: 0
line 13: 20
}
InnerClasses:
public static final #10= #7 of #9; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
SourceFile: "A.java"
BootstrapMethods:
0: #23 REF_invokeStatic A.myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
Method arguments:
1: #23 REF_invokeStatic A.myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
Method arguments: