2

Некоторое время назад я спросил в Embed the existing code of a method in a try-finally block, как обернуть тело метода в блок try-finally, используя ASM. Решение заключалось в том, чтобы посетить метку блока try в начале тела метода в visitCode() и завершить блок try-finally при посещении инструкции с кодом возврата с номером visitInsn(). Я знал, что решение не будет работать, если метод не имеет инструкции возврата, которая применяется, если метод всегда оставляет исключение.Вставить существующий код метода в блок try-finally (2)

Хотя, я обнаружил, что прежнее решение иногда не подходит для методов с инструкциями возврата. Он не будет работать, если метод имеет несколько команд возврата. Причина в том, что он генерирует недействительный байт-код, потому что в начале метода добавляется один блок try-finally, но завершается более одного блока try-finally.

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

public boolean isEven(int x) { 
    return x % 2 == 0; 
} 

байт, собранных с Eclipse:

0: iload_1 
    1: iconst_2 
    2: irem 
    3: ifne   8 
    6: iconst_1 
    7: ireturn  // javac compilation: goto 9 
    8: iconst_0 
    9: ireturn 

Таким образом, я задаюсь , что такое правильный способ для обертывания всего кода кода метода.

ответ

2

Вы должны повторить то, что компилятор Java делает при компиляции try … finally … которая подразумевает копирование вашего finally действие в каждой точке, где будет слева защищенный (источник) блок кода (т.е. возврат инструкции) и установить несколько защищенных (полученный байт-код) (поскольку они не должны покрывать ваше действие finally), но все они могут указывать на один и тот же обработчик исключений. В качестве альтернативы вы можете преобразовать код, заменив всю команду возврата веткой на один экземпляр вашего действия «после», за которым следует единственная команда возврата.

Это не тривиально. Поэтому, если вам не нужен Hot Code Replace, который обычно не поддерживает добавление методов к загруженному классу, самый простой способ избежать этого - переименовать исходный метод в имя, не связанное с другими (вы можете использовать символы, которые не разрешены в обычном исходном коде) и создать новый метод с использованием старого имени и подписи, который состоит из простой конструкции try … finally …, содержащей вызов переименованного метода.

E.g. изменить public void desired() к private void desired$instrumented() и добавить новый

public void desired() { 
    //some logging X 

    try { 
     desired$instrumented(); 
    } 
    finally { 
     //some logging Y 
    } 
} 

Обратите внимание, что поскольку отладочная информация остается на переименованном методе трассировки стека будет продолжать сообщать правильные номера строк, если исключение в переименованном метода. Если вы переименуете его, просто добавив невидимый персонаж (помните, что у вас больше свободы на уровне байтового кода), он будет выглядеть довольно гладко.

+0

Обратите внимание, что перенаправлять все возвращается к одной точке также означает, что вы должны убедиться, что высоты стека равны. Если вы только преобразуете обычный скомпилированный Java-код, они, вероятно, уже будут, но это еще одна проблема, о которой нужно помнить. – Antimony

+0

@Antimony: правильно, и если вы не используете встроенную функцию вычисления структуры ASM, есть еще больше работы. Вот почему я рекомендую решение для замены и замены. – Holger

+0

Благодарим вас за ответ. Я не уверен, что это хорошая идея пойти на простое решение делегирования, потому что я касаюсь каждого метода кода, и я буду выполнять дальнейшие шаги анализа байт-кода, которые должны были бы обнаружить и пропустить методы делегирования. Думаю, я попытаюсь ввести несколько блоков (в примере от 0 до 7 и 8-9). Идея о замене всех возвратов прыжком на один возврат тоже звучит хорошо, но он не охватывает явно заброшенные исключения ('' ATHROW''). – nrainer

1

Благодаря ответу Хольгера и комментариям сурьмы я разработал следующее решение, которое удовлетворяет мои потребности. Позже я обнаружил, что аналогичный подход описан также в Using ASM framework to implement common bytecode transformation patterns, E. Kuleshov, AOSD.07, март 2007 г., Ванкувер, Канада.

Это решение не работает для методов, которые не содержат исключающих возврат (методы, которые генерируют исключение в каждом пути выполнения, например throw new NotSupportedOperationException();)!

Если вам необходимо также поддерживать эти методы, вы должны следовать рекомендациям Хольгера, чтобы переименовать исходный метод, а затем добавить новый метод со старым именем. Добавьте вызов делегата в добавленном методе к переименованному методу и вставьте вызов в блок try-finally.


Я использую простой MethodVisitor посетить код. В методе visitCode() я добавляю инструкции для выполнения при вводе метода. Затем добавьте начало блока try, посетив новый Label. Когда я нахожусь в обратном коде операции в visitInsn(), я завершу блок try и добавлю блок finally. Moveover, я добавляю новый Label, чтобы начать новый блок try, если метод содержит дополнительные инструкции возврата. (Если нет инструкции по возврату не следует метка визита не будет иметь никакого эффекта.)

Упрощенный код выглядит следующим образом:

public abstract class AbstractTryFinallyMethodVisitor extends MethodVisitor { 

    private Label m_currentBeginLabel; 
    private boolean m_isInOriginalCode = true; 

    protected void execBeforeMethodCode() { 
    // Code at the beginning of the method and not in a try block 
    } 

    protected void execVisitTryBlockBegin() { 
    // Code at the beginning of each try block 
    } 

    protected void execVisitFinallyBlock() { 
    // Code in each finally block 
    } 

    @Override 
    public void visitCode() { 
    try { 
     m_isInOriginalCode = false; 
     execBeforeMethodCode(); 
     beginTryFinallyBlock(); 
    } 
    finally { 
     m_isInOriginalCode = true; 
    } 
    } 

    protected void beginTryFinallyBlock() { 
    m_currentBeginLabel = new Label(); 
    visitLabel(m_currentBeginLabel); 
    execVisitTryBlockBegin(); 
    } 

    @Override 
    public void visitInsn(int opcode) { 
    if (m_inOriginalCode && isReturnOpcode(opcode) { 
     try { 
     m_isInOriginalCode = false; 
     completeTryFinallyBlock(); 

     super.visitInsn(opcode); 

     beginTryBlock(); 
     } 
     finally { 
     m_isInOriginalCode = true; 
     } 
    } 
    else { 
     super.visitInsn(opcode); 
    } 
    } 

    protected void completeTryFinallyBlock() { 
    Label l1 = new Label(); 
    visitTryCatchBlock(m_currentBeginLabel, l1, l1, null); 
    Label l2 = new Label(); 
    visitJumpInsn(GOTO, l2); 
    visitLabel(l1); 
    // visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] { "java/lang/Throwable" }); 
    visitVarInsn(ASTORE, 1); 

    execVisitFinallyBlock(); 

    visitVarInsn(ALOAD, 1); 
    super.visitInsn(ATHROW); 
    visitLabel(l2); 
    // visitFrame(Opcodes.F_SAME, 0, null, 0, null); 

    execVisitFinallyBlock(); 
    } 

    protected static boolean isReturnOpcode(int opcode) { 
    return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN; 
    } 
} 

Примечание:

  • Вы не должны invoke visitFrame, если вы создаете экземпляр ClassWriter с флагом COMPUTE_FRAMES.
  • Также возможно (и, возможно, предпочтительно) использовать AdviceAdapter и выполнить манипуляцию байт-кодом в своих методах onMethodEnter() и onMethodExit().
  • Как упоминалось ранее, блок try-finally будет добавлен только в том случае, если байт-код содержит хотя бы одну команду возврата.

Преобразованный выходной байт-код для isEven() метода вопрос будет:

public boolean isEven(int); 
Code: 
0: ldc   #22     //isEven(int) 
2: invokestatic #28     //log/Logger.push:(Ljava/lang/String;)V 
5: iload_1     *1* 
6: iconst_2     *1* 
7: irem      *1* 
8: ifne   25   *1* 
11: iconst_1     *1* 
12: goto   21   *1* 
15: astore_1 
16: invokestatic #31     //log/Logger.pop:()V 
19: aload_1    
20: athrow 
21: invokestatic #31     //log/Logger.pop:()V 
24: ireturn 
25: iconst_0     *2* 
26: goto   35   *2* 
29: astore_1 
30: invokestatic #31     //log/Logger.pop:()V 
33: aload_1 
34: athrow 
35: invokestatic #31     //log/Logger.pop:()V 
38: ireturn 

Exception table: 
from to target type 
    5 15 15 any  *1* 
    25 29 29 any  *2* 
} 
Смежные вопросы