Why doesn't proguard obfuscate the method body?
Because it is impossible.
The names of the method arguments and local variables are simply not saved during compilation.
The names you see are generated by your decompiler.
For compiled code, there are two ways to store data locally (i.e. inside the method):
- On the operand stack
- In local variables
The operand stack is just a stack.
See Table 7.2 of the Java VM specification for stack statements.
You can call values ββ( pop ), duplicate the upper value ( dup ), change the upper two values ββ( swap ) and the same with slightly changed behavior ( pop2 , dup_x1 , dup_x2 , dup2 , dup2_x1 , dup2_x2 ).
And most, if not all, of the instructions that produce the return value throw the specified value onto the stack.
The important point for this question is how things are mentioned on the stack, which is like any other stack:
Regarding the upper position and based on the instructions used.
There are no assigned numbers or names; this is just what currently exists.
Now for the so-called "local variables":
Think of them more as an ArrayList than variables in Java.
Because exactly how you refer to them: by index.
For variables from 0 to 3, there are special instructions (i.e., One byte), because they are used so often, all other variables can be accessed only through a two-byte instruction, where the second byte is the index.
See Table 7.2 , βLoadsβ and βStoresβ.
The first five entries in both tables are broad (double-byte) storage / load instructions for each data type (note that for single values boolean , char , byte and short all are converted to int , leaving only int , float and Object as values one slot and long and double as two-slot ones), the next twenty instructions are instructions for direct access to registers 0 to 3, and the last eight instructions are for accessing array indices (note that inside arrays boolean , byte , char and short not It converted to int , and not to an empty space, POE th there are three instructions (not four, as the byte and char are the same size)).
The maximum stack size and the number of local variables are limited and must be indicated in the header of the Code attribute for each method, as defined in Section 4.7.3 ( max_stack and max_locals ).
The interesting thing about local variables, however, is that they double as arguments to the method, which means that the number of local variables can never be less than the number of arguments to the method. Please note that when calculating values ββfor the Java virtual machine, variables of type long and double considered as two values ββand need two "slots", respectively.
Also note that for non-static methods, the argument 0 will be this , which requires another "slot" for itself.
Saying, let's look at some code!
Example:
class Test { public static void main(String[] myArgs) throws NumberFormatException { String myString = "42"; int myInt = Integer.parseInt(myString); double myDouble = (double)myInt * 42.0d; System.out.println(myDouble); } }
Here we have three local variables myString , myInt and myDouble , plus one argument, myArgs .
In addition, we have two constants "42" and 42.0d and many external links:
java.lang.String[] - classjava.lang.NumberFormatException - classjava.lang.String - classjava.lang.Integer.parseInt - methodjava.lang.System.out - fieldjava.io.PrintStream.println - method
And some export: Test and main , plus the default constructor that the compiler will generate for us.
All constants, references, and export will be exported to the Constant Pool - the names of local variables and arguments will not be.
Compiling and disassembling a class (using javap -c Test ) gives:
Compiled from "Test.java" class Test { Test(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]) throws java.lang.NumberFormatException; Code: 0: ldc #2 // String 42 2: astore_1 3: aload_1 4: invokestatic #3 // Method java/lang/Integer.parseInt:(Ljava/lang/String;)I 7: istore_2 8: iload_2 9: i2d 10: ldc2_w #4 // double 42.0d 13: dmul 14: dstore_3 15: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 18: dload_3 19: invokevirtual #7 // Method java/io/PrintStream.println:(D)V 22: return }
Besides the default constructor, we can see our main method step by step.
Note that astore_1 and aload_1 , myInt with istore_2 and iload_2 and myDouble with iload_2 and myDouble with dload_3 .
myArgs does not have access anywhere, so it does not have bytecode, but at the beginning of the method, the reference to the String array will be in local variable 1, which will soon be overwritten with the reference to "42" .
javap will also show you the constant pool if you pass the -v flag to it, but in fact it does not add any value to the output, since all the relevant information from the constant pool is displayed in the comments anyway.
But now let's see what decompilers produce!
JD-GUI 0.3.5 (JD-Core 0.6.2):
import java.io.PrintStream; class Test { public static void main(String[] paramArrayOfString) throws NumberFormatException { String str = "42"; int i = Integer.parseInt(str); double d = i * 42.0D; System.out.println(d); } }
Procyon 0.5.28:
class Test { public static void main(final String[] array) throws NumberFormatException { System.out.println(Integer.parseInt("42") * 42.0); } }
Note that everything that was exported to the Constant Pool is preserved, while the JD-GUI just selects some names for local variables, and Procyon optimizes them completely.
The argument name - paramArrayOfString vs array (vs original myArgs ) is a great example, however, to show that there is no longer a "right" name, and decompilers just have to rely on some name picker pattern.
I donβt know where the "true" names come from in your decompiled code, but I am sure that they are not contained in the jar file.
Perhaps the function of your IDE?