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

(所需结果的字节码等于第E采用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)之后,但在返回之前我nstruction。 11-14需要是前03

+0

请注意,返回也可能引发异常。 – Antimony

+0

@Antimony:自身返回(第15行)不会导致异常,因为它只是弹出并返回堆栈中的值。返回值的计算(可能会引发异常)在返回前发生在指令中,并且应该仍在try块内。 (尽管测试用例通常是无效的方法。) – nrainer

+0

通常,在监视器处于非法状态的情况下,返回指令本身可能会引发异常。但这应该是一个问题。 – Antimony

回答

0

警告:如果一个方法只包含一个返回指令该解决方案唯一的工作(例如:它不工作,如果它只是抛出一个异常)。 参见:Embed the existing code of a method in a try-finally block (2)


我发现这个问题: 调用super.visitCode当现有的代码不是在visitCode方法插入。这个方法在超类中是空的。这清楚地表明现有的代码是在其他点添加的。

解决方案: 我调用我的方法before(添加的代码,这需要在开始新线)在visitCode方法。如果操作码是返回语句,我在visitVarInsn中调用after

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

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

    super.visitInsn(opcode); 
} 

(该AdviceAdapter工作过了,但是也有一些问题,确保每一个ClassReaderaccept方法被调用,EXPAND_FRAMES。此外,它可能会劝告多个退出点,收盘正好一次尝试时不工作块)。

0

您只需把JUnit标注@Before@After在你的方法应该和之前的测试方法后调用。

+0

感谢您的回答。我知道,但那不是我想要达到的。其他'@ Before'或'@ After'(或'@ BeforeClass','@ AfterClass')方法可能已经存在,然后在我之前执行。 – nrainer