2

Я хочу добавить инструкции в код методов. Эти инструкции должны быть выполнены после достижения и до выхода из метода. Чтобы убедиться, что последние инструкции всегда выполняются перед отъездом, я хочу поместить их в блок finally. (я знаю класс AdviceAdapter, но он не обеспечивает выполнение экзит-кода, когда вызывается метод вызывает исключение.)Вставить существующий код метода в блок try-finally

Моя проблема заключается в том, что инструкции в результате в неправильном порядке.

Метод должен быть обработан:

@Test 
public void original() { 
    assertTrue(true); 
    assertTrue(!(false)); 
} 

желаемого результата: (. Вход X может также иметь место в первой строке блока TRY)

@Test 
public void desired() { 
    //some logging X 

    try { 
     assertTrue(true); 
     assertTrue(!(false)); 
    } 
    finally { 
     //some logging Y 
    } 
} 

(Байт-код желаемого результата равен байт-коду е следующий код Java :)

@Test 
public void desired() { 
    //some logging X 

    try { 
     assertTrue(true); 
     assertTrue(!(false)); 
     //some logging Y 
    } 
    catch (Throwable t) { 
     //some logging Y 
     throw t; 
    } 
} 

Мой код для обработки методы с использованием ASM:

@Override 
public void visitCode() { 
    before(); 

    super.visitCode(); 

    after(); 
} 

private void before() { 
    insertInstructionToSetMode(LoggingMode.TESTING); 

    this.l0 = new Label(); 
    this.l1 = new Label(); 
    visitLabel(l0); 
} 

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

    insertInstructionToSetMode(LoggingMode.FRAMING); 

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

    insertInstructionToSetMode(LoggingMode.FRAMING); 
} 

private void insertInstructionToSetMode(LoggingMode mode) { 
    String modeValue = (mode == LoggingMode.TESTING ? FIELD_NAME_TESTING : FIELD_NAME_FRAMING); 

    visitFieldInsn(Opcodes.GETSTATIC, CP_LOGGING_MODE, modeValue, FIELD_DESC_LOGGING_MODE); 
    visitMethodInsn(INVOKESTATIC, CP_INVOCATION_LOGGER, METHOD_NAME_SET_MODE, METHOD_DESC_SET_MODE); 
} 

Сформирован байткодом (с указанием в неправильном порядке):

// logging X 
01 getstatic instrumentation/LoggingMode/TESTING Linstrumentation/LoggingMode; 
02 invokestatic instrumentation/InvocationLogger/setMode(Linstrumentation/LoggingMode;)V 

// successfully passed the try block 
03 goto 9 

// catch block for the finally behaviour 
04 astore_1 
05 getstatic instrumentation/LoggingMode/FRAMING Linstrumentation/LoggingMode; 
06 invokestatic instrumentation/InvocationLogger/setMode(Linstrumentation/LoggingMode;)V 
07 aload_1 
08 athrow 

// logging Y 
09 getstatic instrumentation/LoggingMode/FRAMING Linstrumentation/LoggingMode; 
10 invokestatic instrumentation/InvocationLogger/setMode(Linstrumentation/LoggingMode;)V 

// original code 
11 iconst_1 
12 invokestatic org/junit/Assert/assertTrue(Z)V 
13 iconst_1 
14 invokestatic org/junit/Assert/assertTrue(Z)V 
15 return 

01-02 в порядке, однако 09-10 должно быть после исходного кода (14), но перед возвратом i nstruction. 11-14 должны быть перед 03.

+0

Обратите внимание, что для возврата также возможно исключение. – Antimony

+0

@ Антитела: само возвращение (строка 15) не может вызвать исключение, поскольку оно просто появляется и возвращает значение в стеке. Вычисление возвращаемого значения (которое может вызвать исключение) происходит в инструкциях перед возвратом, и это все равно должно быть в блоке try. (Хотя тестовые примеры обычно являются недействительными.) – nrainer

+0

, как правило, сама инструкция возврата может выдать исключение в случае, когда монитор находится в незаконном состоянии. Но это должно быть проблемой. – Antimony

ответ

0

Внимание: Это решение работает только если метод содержит ровно один возвратные инструкции (например, он не работает, если он выбрасывает только исключение). См: Embed the existing code of a method in a try-finally block (2)


Я нашел проблему: Существующий код не вставляется в методе visitCode при вызове super.visitCode. Этот метод пуст в суперклассе. Это дает понять, что существующий код добавлен в какой-то другой момент.

Решение: Я призываю мой метод before (который добавляет код для новых линий, которые должны быть в начале) в методе visitCode. Я вызываю after в visitVarInsn, если код операции является оператором return.

@Override 
public void visitCode() 
{ 
    before(); 
} 

@Override 
public void visitInsn(int opcode) 
{ 
    if (OpcodesUtil.isXRETURN(opcode)) 
    { 
     after(); 
    } 

    super.visitInsn(opcode); 
} 

(AdviceAdapter работал тоже, но были некоторые проблемы, гарантируя, что accept метод каждые ClassReader «S вызывается с EXPAND_FRAMES. Кроме того, он может посоветовать больше точек выхода и не работает при закрытии ровно одну попытку block.)

0

Вы можете просто поставить JUnit примечаниями @Before и @After на ваши методы, которые должны быть названы до и после тестового метода.

+0

Спасибо за ваш ответ. Я знаю это, но этого не хочу. Другие методы '@ Before' или' @ After' (или '@ BeforeClass',' @ AfterClass') могут уже существовать и выполняться до моего. – nrainer

2

Я не уверен, где ошибка в вашем подходе. Но я добился чего-то подобного после нескольких проб и ошибок с помощью AdviceAdapter.

См

http://code.google.com/p/pitestrunner/source/browse/pitest/src/main/java/org/pitest/coverage/codeassist/CoverageMethodVisitor.java

+0

Спасибо, я попытался сравнить ваше решение с моим. Соответствующие инструкции байт-кода были почти одинаковыми, но теперь я нашел проблему. – nrainer