3

I want to add instructions to the code of methods. These instructions should be executed after reaching and before leaving the method. In order to make sure that the latter instructions are always executed before leaving I want to put these in a finally block. (I know the class AdviceAdapter but it does not ensure the execution of exit-code when an invoked method throws an exception.)

My problem is that the instructions in the result are in the wrong order.

Method to be processed:

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

Desired result:

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

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

(logging X can also take place in the first line of the try block.)

(The bytecode of the desired result is equal to the bytecode of the following Java code:)

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

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

My code to process the methods using 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);
}

Generated bytecode (with instructions in wrong order):

// 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 is fine, however 09-10 need to be after the original code (14), but before the return instruction. 11-14 need to be before 03.

nrainer
  • 2,542
  • 2
  • 23
  • 35
  • Note that it's possible for return to throw an exception as well. – Antimony Jun 25 '13 at 13:56
  • @Antimony: Return itself (line 15) can't cause an exception since it just pops and returns the value on the stack. The calculation of the return value (which could throw an exception) happens in the instructions before return and that should still be within the try block. (Though, test cases usually are void methods.) – nrainer Jun 25 '13 at 16:34
  • in general the return instruction itself can throw an exception in the case where the monitor is in an illegal state. But that should be an issue here. – Antimony Jun 26 '13 at 02:56
  • Ok, that's right. Have you got a clue why the instructions are in the wrong order? – nrainer Jun 26 '13 at 09:47

3 Answers3

2

I'm not sure where the error is in your approach. But I achieved something like this after a bit of trial and error using AdviceAdapter.

See

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

henry
  • 5,923
  • 29
  • 46
  • Thanks, I tried to compare your solution with mine. The relevant bytecode instructions were nearly the same, but now I've found the problem. – nrainer Jun 27 '13 at 08:20
0

You can just put the JUnit annotations @Before and @After on your methods that should be called before and after your test method.

Lukas Eichler
  • 5,689
  • 1
  • 24
  • 43
  • Thanks for your answer. I know that but that's not what I want to achieve. Other `@Before` or `@After` (or `@BeforeClass`, `@AfterClass`) methods might already exist and then be executed before mine. – nrainer Jun 25 '13 at 08:02
0

Warning: This solution is only working if a method contains exactly one return instruction (eg: it is not working if it only throws an exception). See: Embed the existing code of a method in a try-finally block (2)


I found the problem: The existing code is not inserted in the visitCode method when invoking super.visitCode. This method is empty in the super class. That makes clear the existing code is added at some other point.

Solution: I invoke my method before (which adds the code for the new lines which need to be at the beginning) in the visitCode method. I invoke after in visitVarInsn if the opcode is an return statement.

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

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

    super.visitInsn(opcode);
}

(The AdviceAdapter had worked too, but there were some problems ensuring that every ClassReader's accept method is invoked with EXPAND_FRAMES. Furthermore, it might advice more exit points and that does not work when closing exactly one try block.)

Community
  • 1
  • 1
nrainer
  • 2,542
  • 2
  • 23
  • 35