2010-12-12 8 views
7

Я хочу написать MethodVisitor, который преобразует команды LDC, которые предназначены для умножения.ASM: Stateful Transformation

Пример байткод:

ldC#26 
imul 

В основном это толкает постоянное, а затем умножает его.

Это должно быть преобразование с состоянием, потому что я сначала должен проверить, что он предназначен для умножения, и, если это так, мне нужно вернуться к инструкции ldc и изменить константу. Я не совсем уверен, как это сделать, и я не знаю, как изменить константу (когда я попытался передать другое значение, старое значение все еще оставалось в постоянном пуле).

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); 
     } 
    } 
} 

Это то, что у меня есть, но это не снимает старое значение в постоянном пуле, и это может иметь ошибки.

ответ

1

Если вы заинтересованы в изменении байт-кода таким образом, вы можете ознакомиться с ASM tree API. Вы можете легко заменить LdcInsnNode.cst более удобным интерфейсом дерева в стиле DOM, в отличие от интерфейса посетителя в стиле SAX, который вы пытаетесь использовать.

+0

Я был заинтересован в поиске решения с использованием API-интерфейса посетителя, так как ASM довольно ясно дала понять, что это рекомендуется. Однако, если API дерева является лучшим выбором в этом случае, я рассмотрю его. Благодарю. – someguy

+0

Используя API-интерфейс посетителя, как вы сейчас, вы не можете просто заменить константу на месте; вам придется добавить дополнительный код к потоку, чтобы поместить старое значение и нажать новый. Возможно, вам следует, однако, изучить подклассификацию ClassWriter; существует несколько виртуальных методов, которые вы можете переопределить, имея дело с константами записи, хотя может быть немного сложно проверить, что вы изменяете только константу, которую вы собираетесь использовать. – oldrinb

1

То, что у вас есть, касается права, но не обслуживает другие типы кодов операций, вызываемых после ldc, поэтому вы можете повредить там, так как они будут искать что-то в стеке этого нет (так как вы не посещали ldc). Я не уверен, что об удалении существующей постоянной, но вы можете заменить константу следующим образом:

@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); 
}  

Другими словами, всегда посещают «LDC». Если вы затем увидите, как IMUL продолжит его, поместите стек, вставьте новую константу и затем перейдите к коду операции IMUL. Вам нужно будет сделать небольшую работу, чтобы сделать это полностью безопасным, в случае, если какой-либо другой метод будет посещен после посещения ldc и до IMUL. Чтобы быть параноидальным, вы можете переопределить все методы посетителей, и если это не посещениеInsn или нет IMUL, вы должны посетить ldc и установить replace = false.

Полностью заменить константу немного сложнее. Вам нужно будет запомнить, какие константы были замечены всеми методами, которые были посещены в классе до сих пор. Если вы еще не видели эту константу, вы можете просто заменить значение при посещении ldc.

+0

Спасибо, это немного помогло, но ваше решение кажется немного грязным. Он передает старое значение и новое, с инструкцией POP между ними. Вы уверены, что это единственный способ? «И о замене константы: я думал, что это то, что я делаю, но на самом деле не удаляет старое значение из постоянного пула. – someguy

+0

@someguy Вы можете сохранить операнд и отложить вызов visitLdcInsn. Просто убедитесь, что вы вызываете visitLdcInsn, если следующий visitInsn (код операции)! = IMUL, или следующий метод посещения! = VisitInsn. Константа будет удалена только в том случае, если в классе нет другого фрагмента кода. – axw