2010-12-12 19 views
7

Voglio scrivere un MethodVisitor che trasforma le istruzioni LDC che sono per la moltiplicazione.ASM: Stateful Transformation

Esempio bytecode:

ldC#26 
imul 

Questo spinge sostanzialmente costante e quindi moltiplica.

Deve essere una trasformazione stateful perché prima devo verificare che sia per moltiplicare e, se lo è, ho bisogno di tornare all'istruzione ldc e modificare la costante. Non sono del tutto sicuro di come andrei su questo, e non so come modificare la costante (quando ho provato a passare un valore diverso, il vecchio valore rimaneva ancora nel pool costante).

Edit:

public class AdditionTransformer extends MethodAdapter { 
    boolean replace = false; 
    int operand = 0; 

    AdditionTransformer(MethodVisitor mv) { 
     super(mv); 
    } 

    @Override 
    public void visitInsn(int opcode) { 
     if (opcode == IMUL && replace) { 
      operand *= 2; 
      visitLdcInsn(operand); 
      replace = false; 
     } 
     mv.visitInsn(opcode); 
    } 

    @Override 
    public void visitLdcInsn(Object cst) { 
     if (cst instanceof Integer && !replace) { 
      operand = (Integer) cst; 
      replace = true; 
     } else { 
      mv.visitLdcInsn(cst); 
     } 
    } 
} 

Questo è quello che ho, ma non rimuovere il vecchio valore nel pool di costante, e può avere bug.

risposta

1

Se si desidera modificare bytecode in questo modo, è possibile esaminare lo ASM tree API. È possibile sostituire facilmente LdcInsnNode.cst tramite un'interfaccia ad albero in stile DOM più comoda rispetto all'interfaccia utente in stile SAX che si sta tentando di utilizzare.

+0

Ero appassionato di cercare una soluzione utilizzando l'API del visitatore, poiché ASM ha reso abbastanza chiaro che è raccomandato. Tuttavia, se l'API tree è la scelta migliore in questo caso, cercherò di esaminarla. Grazie. – someguy

+0

Utilizzando l'API visitatore come sei, non puoi semplicemente sostituire la costante sul posto; dovresti aggiungere altro codice al flusso per inserire il vecchio valore e spingerne uno nuovo. Forse dovresti, comunque, guardare in ClassWriter per sottoclassi; ci sono alcuni metodi virtuali che puoi sovrascrivere che riguardano le costanti di scrittura, anche se potrebbe essere un po 'complesso verificare che stai modificando solo la costante che intendi. – oldrinb

1

Quello che hai è giusto, ma non soddisfa gli altri tipi di opcode che vengono chiamati dopo l'ldc, quindi causerai qualche rottura lì, dato che cercheranno qualcosa sullo stack non è lì (dato che non hai visitato l'ICD). Io non sono così sicuro sulla rimozione costante l'esistente, ma è possibile sostituire la costante in questo modo:

@Override 
public void visitInsn(int opcode) { 
    if (opcode == IMUL && replace) { 
     operand *= 2; 
     mv.visitInsn(POP); 
     mv.visitLdcInsn(operand); 
     replace = false; 
    } 
    mv.visitInsn(opcode); 
} 

@Override 
public void visitLdcInsn(Object cst) { 
    if (cst instanceof Integer && !replace) { 
     operand = (Integer) cst; 
     replace = true; 
    } 
    mv.visitLdcInsn(cst); 
}  

In altre parole, visitare sempre il "LDC". Se poi vedi un IMUL che lo procede, fai uno stack, inserisci una nuova costante e poi visita l'opcode IMUL. Avresti bisogno di fare un po 'di lavoro per renderlo completamente sicuro, nel caso in cui qualche altro metodo venga visitato dopo aver visitato ldc e prima di IMUL. Per essere paranoico, è possibile sovrascrivere tutti i metodi dei visitatori e, se non si visita VisitInsn o non IMUL, si visita il file ldc e si imposta replace = false.

Sostituire completamente la costante è un po 'più complicato. Dovresti ricordare quali costanti sono state viste finora da tutti i metodi visitati nella classe. Se non hai visto quella costante finora, puoi semplicemente sostituire il valore quando visiti il ​​ldc.

+0

Grazie, questo ha aiutato un po ', ma la tua soluzione sembra un po' confusa. Sta passando il vecchio valore e il nuovo, con una istruzione POP in mezzo. Sei sicuro che questo sia l'unico modo? "E per sostituire la costante: ho pensato che fosse quello che stavo facendo, ma in realtà non sta rimuovendo il vecchio valore dal pool costante. – someguy

+0

@someguy È possibile memorizzare l'operando e rinviare la chiamata visitLdcInsn. Assicurati di chiamare visitLdcInsn se la prossima visitaInsn (opcode)! = IMUL, o il prossimo metodo visitato! = VisitInsn. La costante verrà rimossa solo se non c'è un altro pezzo di codice nella classe che si riferisce ad esso. – axw