So I am trying to add information about locals I use. The current way I am trying to achieve it - is:
methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "something", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitInsn(ICONST_1);
Label label1 = new Label();
methodVisitor.visitJumpInsn(IFEQ, label1);
methodVisitor.visitLdcInsn("number");
methodVisitor.visitVarInsn(ASTORE, 0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESTATIC, "org/bezsahara/minikotlin/NamingTests", "accept", "(Ljava/lang/Object;)V", false);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitLabel(label1);
methodVisitor.visitIntInsn(BIPUSH, 21);
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
methodVisitor.visitVarInsn(ASTORE, 0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESTATIC, "org/bezsahara/minikotlin/NamingTests", "accept", "(Ljava/lang/Object;)V", false);
methodVisitor.visitInsn(RETURN);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLocalVariable("hello", "Ljava/lang/String;", null, label0, label1, 0);
methodVisitor.visitLocalVariable("goodbye", "Ljava/lang/Integer;", null, label1, label2, 0);
methodVisitor.visitMaxs(0, 0);
methodVisitor.visitEnd();
However, when using Intellij decompiler (I think it uses FernFlower). Variable names are incorrect:
public static void something() {
if (true) {
String goodbye = "number";
NamingTests.accept(goodbye);
} else {
Integer hello = 21;
NamingTests.accept(hello);
}
}
After examining compiler outputs. It seems they indicate only LOAD kind of operations on variables. But it seems unreasonable. I mean if I just assign a variable to something it will loose all naming information then.
So, I would appreciate it if you can help me understand:
I don't know enough about the decompiler's implementation details to tell you exactly why it produced that output, but the bytecode you generated is quite odd in the first place.
As you may know, visitLocalVariable
corresponds to adding entries to the LocalVariableTable
attribute of the method. The two Label
arguments are used to compute the start_pc
and length
items in each entry. The specification says (emphasis mine):
The
start_pc
andlength
items indicate that the given local variable has a value at indices into the code array in the interval[start_pc, start_pc + length)
, that is, betweenstart_pc
inclusive andstart_pc + length
exclusive.
Based on this, the placement of the labels in your code is quite odd. You are saying that hello
"has a value" even before the astore
instruction. Similarly, you are saying that goodbye
"has a value" as soon as the else
branch begins. This obviously doesn't make sense, and it is not surprising that a decompiler would behave unexpectedly.
This is not about "marking" load/store instructions. This is about the range of code where that variable has a value. The first Label
should be after the first instruction that assigns the variable a value, and the second Label
should be before the variable "goes out of scope".
Here I have added 4 labels to indicate the ranges for the 2 variables:
var methodVisitor = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "something", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitInsn(ICONST_1);
Label label1 = new Label();
methodVisitor.visitJumpInsn(IFEQ, label1);
methodVisitor.visitLdcInsn("number");
methodVisitor.visitVarInsn(ASTORE, 0);
var helloStart = new Label();
methodVisitor.visitLabel(helloStart);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESTATIC, "org/bezsahara/minikotlin/NamingTests", "accept", "(Ljava/lang/Object;)V", false);
var helloEnd = new Label();
methodVisitor.visitLabel(helloEnd);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitLabel(label1);
methodVisitor.visitIntInsn(BIPUSH, 21);
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
methodVisitor.visitVarInsn(ASTORE, 0);
var goodbyeStart = new Label();
methodVisitor.visitLabel(goodbyeStart);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESTATIC, "org/bezsahara/minikotlin/NamingTests", "accept", "(Ljava/lang/Object;)V", false);
var goodbyeEnd = new Label();
methodVisitor.visitLabel(goodbyeEnd);
methodVisitor.visitInsn(RETURN);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLocalVariable("hello", "Ljava/lang/String;", null, helloStart, helloEnd, 0);
methodVisitor.visitLocalVariable("goodbye", "Ljava/lang/Integer;", null, goodbyeStart, goodbyeEnd, 0);
methodVisitor.visitMaxs(0, 0);
methodVisitor.visitEnd();
The IntelliJ decompiler is now able to give the variables their expected names.