Perché proguard non offusca il corpo del metodo?
Perché non può.
I nomi degli argomenti del metodo e le variabili locali non vengono semplicemente memorizzati durante la compilazione.
I nomi che stai vedendo sono generati dal tuo decompilatore.
Per il codice compilato, ci sono due modi per memorizzare dati a livello locale (ad esempio all'interno di un metodo):
- Sulla pila operando
- In variabili locali
Lo stack operando è davvero solo una pila.
Vedere Table 7.2 dalla specifica VM Java per gli operatori di stack.
È possibile pop valori (pop
), duplicare il valore superiore (dup
), scambiare le prime due valori (swap
) e la stessa cosa con un comportamento leggermente diverso (pop2
, dup_x1
, dup_x2
, dup2
, dup2_x1
, dup2_x2
).
E la maggior parte, se non tutte le istruzioni che producono un valore di ritorno, lasceranno cadere il valore in pila.
La cosa importante per questa domanda è come le cose nello stack sono indicati, che è come con qualsiasi altro stack:
rispetto alla posizione superiore, e sulla base delle istruzioni utilizzate.
Non ci sono numeri o nomi assegnati, è solo quello che c'è attualmente.
Ora, per le cosiddette "variabili locali":
pensare a loro più come un ArrayList
di variabili in Java.
Perché è esattamente come accedervi: per indice.
Per le variabili da 0 a 3, ci sono istruzioni speciali (ad es.byte singolo) perché vengono utilizzati così spesso, tutte le altre variabili sono accessibili solo tramite un'istruzione a due byte, in cui il secondo byte è l'indice.
Vedere Table 7.2 di nuovo, "Carichi" e "Negozi".
I primi cinque voci di entrambe le tabelle sono ampie (due byte) istruzioni memorizzano/carico per ogni tipo di dati (si noti che, per valori singoli, boolean
, char
, byte
e short
sono tutti convertiti in int
, lasciando solo int
, float
e come valori a slot singolo e long
e double
come quelli a doppio slot, le venti istruzioni successive sono le istruzioni per l'accesso diretto ai registri da 0 a 3 e le ultime otto istruzioni sono per accedere agli indici di array (notare che all'interno degli array , boolean
, byte
, char
e short
sono non convertito in int
, per non sprecare spazio, ecco perché ci sono altre tre istruzioni (non quattro, dal byte
e dallo char
hanno le stesse dimensioni)).
Sia la dimensione massima dello stack e il numero di variabili locali sono limitate, e deve essere data nell'intestazione dell'attributo Code
di ciascun metodo, come definito nella Section 4.7.3 (max_stack
e max_locals
).
La cosa interessante di variabili locali, però, è che raddoppiano come argomenti di metodo, il che significa che il numero di variabili locali non può mai essere inferiore al numero di argomenti di metodo.
noti che quando il conteggio valori per il Java VM, le variabili del tipo long
e double
sono considerate due valori, e necessitano di due "slot" di conseguenza.
noti inoltre che per i metodi non statici, l'argomento sarà 0 this
, che richiede un altro "slot" per sé.
Detto questo, diamo un'occhiata ad un codice!
Esempio:
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);
}
}
Qui abbiamo tre variabili locali myString
, myInt
e myDouble
, oltre a un argomento myArgs
.
Inoltre, abbiamo due costanti "42"
e 42.0d
, e un sacco di riferimenti esterni:
java.lang.String[]
- classe
java.lang.NumberFormatException
- classe
java.lang.String
- classe
java.lang.Integer.parseInt
- metodo
java.lang.System.out
- campo
java.io.PrintStream.println
- Metodo
E alcune esportazioni: Test
e main
, più il costruttore di default che il compilatore genera per noi.
Tutte le costanti, i riferimenti e le esportazioni saranno esportati al Constant Pool - le variabili locali e nomi degli argomenti non.
Compilazione e smontaggio della classe (utilizzando javap -c Test
) rendimenti:
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
}
Oltre il costruttore di default, possiamo vedere la nostra main
metodo, passo dopo passo.
Si noti come myString
si accede con astore_1
e aload_1
, myInt
con istore_2
e iload_2
, e myDouble
con dstore_3
e dload_3
.
non è accessibile da nessuna parte, quindi non esiste alcun bytecode che lo tratti, ma all'inizio del metodo, un riferimento all'array di stringhe sarà nella variabile locale 1, che verrà presto sovrascritta da un riferimento a "42"
.
javap
visualizzerà anche il Pool costante se si passa il flag -v
, ma in realtà non aggiunge alcun valore all'output, poiché tutte le informazioni rilevanti del Pool costante vengono comunque visualizzate nei commenti.
Ma ora, diamo un'occhiata a ciò che producono i decompilatori!
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);
}
}
Nota come tutto ciò che è stato esportato al Pool Constant persiste, mentre JD-GUI seleziona semplicemente alcuni nomi per le variabili locali e Procyon li ottimizza completamente.
Il nome dell'argomento - paramArrayOfString
vs array
(rispetto all'originale myArgs
) - è un esempio perfetto, tuttavia, per dimostrare che non esiste più un nome "corretto" e che i decompilatori devono semplicemente fare affidamento su alcuni schemi di selezione di un nome.
Non so da dove vengano i nomi "veri" nel codice decompilato, ma sono abbastanza certo che non siano contenuti nel file jar.
Caratteristica del tuo IDE forse?
Il codice "offuscato" codice Java reale o codice Java che è stato decompilato? La mia comprensione è che un file bytecode semplicemente non registra i nomi dei parametri del metodo e delle variabili locali. (Se questo è il codice sorgente emesso da ProGuard prova a compilarlo, quindi decompila il file .class ... o guardandolo usando javap.) –
@StephenC È il codice Java decompilato. Ho offuscato il file .jar (con proguard, dopo aver compilato il .jar), poi ho decompilato il .jar in precedenza offuscato. I file .jar (il bytecode) memorizzano quasi tutti i dati dal codice sorgente originale (tranne i commenti e la formattazione della sintassi). – Victor2748