Pregunta

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.

¿Fue útil?

Solución 3

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.)

Otros consejos

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

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top