understanding constant pools in Javassist - java

I am using Javassist to extend certain classes at runtime .
In a couple of places (in the generation code) I need to create instances of the Javassist ConstPool class.
For example, to mark a generated class as synthetic, I wrote something like this:
CtClass ctClassToExtend = ... //class to extend
CtClass newCtClass = extend(ctClassToExtend, ...); //method to create a new ctClass extending ctClassToExtend
SyntheticAttribute syntheticAttribute = new SyntheticAttribute(ctClassToExtend.getClassFile().getConstPool()); //creating a synthetic attribute using an instance of ConstPool
newCtClass.setAttribute(syntheticAttribute.getName(), syntheticAttribute.get()); //marking the generated class as synthetic
This is working as expected, but I have certain doubts about this being entirely correct. Concretely, my main question is:
Is the call to CtClass.getClassFile().getConstPool() the correct way to get a constant pool in this example?. If not, what is the general proper way to get the right instance of a constant pool when creating a new class at runtime using Javassist?
Also, I am a bit lost regarding what is happening behind the curtains here: Why do we need a constant pool to create a instance of a synthetic attribute ?, or in general, of any other kind of class attributes ?
Thanks for any clarification.

Don't know if you're still interested in the answer, but at least might help others that
find this question.
First of all, a small suggestion to everyone that starts creating/modifying bytecode
and needs more in-depth information on how the JVM internals works, the JVM's specification documentation might look bulky and scary at first but it's invaluable help!
Is the call to CtClass.getClassFile().getConstPool() the correct way to get a constant pool in this example?.
Yes, it is. Each Java Class has a single constant pool, so basicaly every time you need to access the constant
pool for a given class you can do ctClass.getClassFile().getConstPool(), although you must keep in mind the
following:
In javassist the constant pool field from CtClass is an instance field, that means that if you have two CtClass objects
representing the same Class you'll have two diferrent instances of constant pool (even though they represent
the constant pool in the actual class file). When modifying one of the CtClassinstances you must use the
associated constant pool instance in order to have the expected behaviour.
There are times where you might not have the CtClass but rather a CtMethod or a CtField which don't let you backtrace to the CtClass instance, in such cases you can use ctMethod.getMethodInfo().getConstPool() and ctField.getFieldInfo().getConstPool() to retrieve the correct constant pool.
Since I've mentioned CtMethod and CtField, keep in mind that if you want to add attributes to any of these, it can't be through the ClassFile Object, but through MethodInfo and FieldInfo respectively.
Why do we need a constant pool to create a instance of a synthetic attribute ?, or in general, of any other kind of class attributes ?
To answer this question, I'll start quoting section 4.4 regarding JVM 7 specs (like I said, this documentation is quite helpful):
Java virtual machine instructions do not rely on the runtime layout of classes, interfaces, class instances, or arrays. Instead, instructions refer to symbolic information in the constant_pool table.
With this in mind, I think the best way to shed some light on this subject is to look at a class file dump. We can achieve this by running the following command:
javap -s -c -p -v SomeClassFile.class
Javap comes with the java SDK and it's a good tool to analyse classes at this level, the explanation of each switch
-s : Prints internal type signature
-c : Prints byte code
-p : Prints all class members (methods and fields, including the private ones)
-v : Be verbose, will print tack information and class constant pool
Here's the output for test.Test1 class that I modified via javassist to have the synthetic attribute both in the class and in the injectedMethod
Classfile /C:/development/testProject/test/Test1.class
Last modified 29/Nov/2012; size 612 bytes
MD5 checksum 858c009090bfb57d704b2eaf91c2cb75
Compiled from "Test1.java"
public class test.Test1
SourceFile: "Test1.java"
Synthetic: true
minor version: 0
major version: 50
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // test/Test1
#2 = Utf8 test/Test1
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Methodref #3.#9 // java/lang/Object."<init>":()V
#9 = NameAndType #5:#6 // "<init>":()V
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Ltest/Test1;
#14 = Utf8 SourceFile
#15 = Utf8 Test1.java
#16 = Utf8 someInjectedMethod
#17 = Utf8 java/lang/System
#18 = Class #17 // java/lang/System
#19 = Utf8 out
#20 = Utf8 Ljava/io/PrintStream;
#21 = NameAndType #19:#20 // out:Ljava/io/PrintStream;
#22 = Fieldref #18.#21 // java/lang/System.out:Ljava/io/PrintStream;
#23 = Utf8 injection example
#24 = String #23 // injection example
#25 = Utf8 java/io/PrintStream
#26 = Class #25 // java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
#29 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#30 = Methodref #26.#29 // java/io/PrintStream.println:(Ljava/lang/String;)V
#31 = Utf8 RuntimeVisibleAnnotations
#32 = Utf8 Ltest/TestAnnotationToShowItInConstantTable;
#33 = Utf8 Synthetic
{
public com.qubit.augmentation.test.Test1();
Signature: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ltest/Test1;
protected void someInjectedMethod();
Signature: ()V
flags: ACC_PROTECTED
Code:
stack=2, locals=1, args_size=1
0: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #24 // String injection example
5: invokevirtual #30 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
RuntimeVisibleAnnotations:
0: #32()
Synthetic: true
}
Notice that both the class and the method have the attribute Synthetic: true which mean they are Synthetic but, you the synthetic symbol must also be present in the constant pool (check #33).
Another example regarding the use of constant pool and class/method attributes is the annotation added to someInjectedMethod with runtime retention policy. The method's bytecode only has a reference to the constant pool #32 symbol, and only there you learn that
the annotation is from the type test/TestAnnotationToShowItInConstantTable;
Hope things got a bit more clear for you now.

Related

Why does a Java string comparison behave different in Java 15 and Java 11?

Please consider the following class:
class Eq {
public static void main(String[] args) {
System.out.println("" == ".".substring(1));
}
}
The example is supposed to show that multiple copies of the empty string may exist in memory. I still have an old OpenJDK 11 where the program outputs false as expected. Under OpenJDK 15, the program outputs true. The generated bytecode for the class files looks similar (even though they differ in register values):
Java 11:
public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #13 // String
5: ldc #15 // String .
7: iconst_1
8: invokevirtual #17 // Method java/lang/String.substring:(I)Ljava/lang/String;
11: if_acmpne 18
14: iconst_1
15: goto 19
18: iconst_0
19: invokevirtual #23 // Method java/io/PrintStream.println:(Z)V
22: return
Java 15:
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String
5: ldc #4 // String .
7: iconst_1
8: invokevirtual #5 // Method java/lang/String.substring:(I)Ljava/lang/String;
11: if_acmpne 18
14: iconst_1
15: goto 19
18: iconst_0
19: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
22: return
I tried to exclude static compiler optimizations by reading "." from stdin but this does not change the outcome. I have tried to disable the JIT via -Djava.compiler=NONE and played around with adjusting the string table size via -XX:StringTableSize=100000. I now have the following questions:
Can someone reproduce the issue (i.e. did I do it correctly? I can provide the class files if that helps)
How do I find out the exact reason for the different behaviour?
What (in your opinion) is the source for the different behaviour?
I think just strategies to approach how to find the reason for the behaviour that don't answer the question might also be interesting.
This is mentioned in the JDK 15 Release Notes.
It was changed as requested by JDK-8240094:
JDK-8240094 : Optimize empty substring handling
String.substring return "" in some cases, but could be improved to do so in all cases when the substring length is zero.
Related:
JDK-8240225 : Optimize empty substring handling
Optimize String.substring and related operations like stripLeading, stripTrailing to avoid redundantly creating a new empty String.
Sub Task:
JDK-8251556 : Release Note: Optimized Empty Substring Handling
The implementation of String.substring and related methods stripLeading and stripTrailing have changed in this release to avoid redundantly creating a new empty String. This may impact code that depends on unspecified behaviour and the identity of empty sub-strings.

Generating java class file headers from jvm bytecode

Currently I am tinkering with the jvm bytecode instructions. I made a simple compiler that given source code (C like style) generates valid jvm bytecode representation. For example, the following code:
float x = 3;
float y = 4.5;
float z = x + y;
print z;
Compiles to:
ldc 3
i2f
fstore 1
ldc 4.5
fstore 2
fload 1
fload 2
fadd
fstore 3
getstatic java/lang/System/out Ljava/io/PrintStream;
fload 3
invokevirtual java/io/PrintStream/println(F)V
return
(I know the generated java code is not the most efficient as of now, but that is not the point).
Using a Java Bytecode Editor, I loaded a compiled main class and replaced the main method code with my code. After that, I was able to run the class file with my code perfectly fine. My question is, is there a tool/script without UI that can take java bytecode and generate the appropriate headers for the class file (in other words, take the bytecode and make a valid class file out of it). I guess I can write a script myself, but that would take some time that I might not have now.
The Krakatau assembler allows you to write bytecode in a textual format and assembles it into a classfile, handling all the binary encoding details for you.
It's similar to the older Jasmin assembler, but with minor syntax changes in order to remove ambiguity and to support classfile features that Jasmin can't handle. Unlike Jasmin, it fully supports the entire Java 8 classfile format and optionally allows full control over the binary representation of the classfile.
For example, here's a class using lambdas in Krakatau assembly format.
.version 52 0
.class public super LambdaTest1
.super java/lang/Object
.method public <init> : ()V
.code stack 1 locals 1
aload_0
invokespecial Method java/lang/Object <init> ()V
return
.end code
.end method
.method public static varargs main : ([Ljava/lang/String;)V
.code stack 4 locals 2
invokedynamic InvokeDynamic invokeStatic Method java/lang/invoke/LambdaMetafactory metafactory (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; MethodType (J)J MethodHandle invokeStatic Method LambdaTest1 lambda$main$0 (J)J MethodType (J)J : applyAsLong ()Ljava/util/function/LongUnaryOperator;
astore_1
getstatic Field java/lang/System out Ljava/io/PrintStream;
aload_1
ldc2_w 42L
invokeinterface InterfaceMethod java/util/function/LongUnaryOperator applyAsLong (J)J 3
invokevirtual Method java/io/PrintStream println (J)V
return
.end code
.end method
.method private static synthetic lambda$main$0 : (J)J
.code stack 4 locals 2
lload_0
lload_0
l2i
lshl
lreturn
.end code
.end method
.innerclasses
java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup public static final
.end innerclasses
.end class

JVM Verify Error 'Illegal type at constant pool'

I am currently writing my own compiler and I am trying to compile the following code:
List[String] list = List("a", "b", "c", "d")
list stream map((String s) => s.toUpperCase())
System out println list
The compiler has no problem parsing, linking or compiling the code, but when it comes to executing it, the JVM throws the following error:
java.lang.VerifyError: Illegal type at constant pool entry 40 in class dyvil.test.Main
Exception Details:
Location:
dyvil/test/Main.main([Ljava/lang/String;)V #29: invokevirtual
Reason:
Constant pool index 40 is invalid
Bytecode:
...
I tried to use javap to find the problem, and this is the instruction #29:
29: invokevirtual #40 // InterfaceMethod java/util/Collection.stream:()Ljava/util/stream/Stream;
And the entry in the Constant Pool (also using javap):
#37 = Utf8 stream
#38 = Utf8 ()Ljava/util/stream/Stream;
#39 = NameAndType #37:#38 // stream:()Ljava/util/stream/Stream;
#40 = InterfaceMethodref #36.#39 // java/util/Collection.stream:()Ljava/util/stream/Stream;
When opening the class with the Eclipse Class File Viewer, the line where #29 should be reads:
Class Format Exception
and all following instructions are not shown anymore (except for Locals, ...). However, the ASM Bytecode Plugin writes
INVOKEVIRTUAL java/util/Collection.stream ()Ljava/util/stream/Stream;
at that line, which appears to be valid. What am I doing wrong / missing here?
I figured out my mistake. The error lies here:
invokevirtual #40 // InterfaceMethod
^^^^^^^ ^^^^^^^^^
I am using invokevirtual on an interface method, which is generally not a good idea. However I think that the error thrown by the verifier should be a bit more clear about what is actually wrong.
With ASM you will see this error because of an incorrect isInterface flag. The argument isInterface of visitMethodInsn refers to the target/owner/destination and not the current context.
i.o.w when using INVOKEVIRTUAL, isInterface is false.
see issue here for more details.

What java command/binary uses for printing out the readable java bytecode?

I try to print out the readable java bytecode to see the monitorenter and monitorexit to study about the deadlock and synchronization instruction set but I don't know what the java command or binary that I should use to get the readable java bytecode.
Use the javap command, for example:
javap -v SomeClass.class
Example output:
19:23:56 (brettw) [dev] hikari$ javap -v HikariPool.class
Classfile /Users/brettw/Documents/dev/HikariCP/core/target/classes/com/zaxxer/hikari/HikariPool.class
Last modified Dec 19, 2013; size 11754 bytes
MD5 checksum 00e0441d0aad3bad1f4e7a67f6043b9c
Compiled from "HikariPool.java"
public final class com.zaxxer.hikari.HikariPool implements com.zaxxer.hikari.HikariPoolMBean
SourceFile: "HikariPool.java"
InnerClasses:
#384; //class com/zaxxer/hikari/HikariPool$1
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
Constant pool:
#1 = Class #2 // com/zaxxer/hikari/HikariPool
#2 = Utf8 com/zaxxer/hikari/HikariPool
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Class #6 // com/zaxxer/hikari/HikariPoolMBean
#6 = Utf8 com/zaxxer/hikari/HikariPoolMBean
...

jasmin hacking and verify error

I'm playing with jasmin and I try to launch my .class file, which is supposed to perform simple string concatenation. My jasmin source looks like this:
.class public default_class
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
.limit locals 1
.limit stack 1
invokestatic main_65428301()I
return
.end method
.method public static main_65428301()I
.limit locals 1
.limit stack 100
new java/lang/String
dup
ldc "foo"
invokestatic java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;
invokespecial java/lang/StringBuilder(Ljava/lang/String;)V
ldc "bar"
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invokevirtual java/lang/String.toString()V
astore_0
iconst_0
ireturn
.end method
Now I'm able to run java -jar jasmin.jar and I get default_class.class. However, when I try to launch it like java default_class I get an error:
Exception in thread "main" java.lang.VerifyError: (class: default_class, method: main_65428301 signature: ()I) Illegal use of nonvirtual function call
What should I change in my assembly to get this to work?
In JVM, to create the object you have to first use new instruction and then call <init> method (constructor). You do not create new StringBuilder and call the wrong constructor name (should be java/lang/StringBuilder/<init>(Ljava/lang/String;)V).
I also see no reason to do:
new java/lang/String
dup
or
invokestatic java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;
"The new instruction does not completely create a new instance; instance initialization is not completed until an instance initialization method has been invoked on the uninitialized instance."

Categories

Resources