【问题标题】:Embed the existing code of a method in a try-finally block [duplicate]在try-finally块中嵌入方法的现有代码[重复]
【发布时间】:2013-06-25 07:52:41
【问题描述】:

我想在方法代码中添加说明。这些指令应该在到达方法之后和离开方法之前执行。 为了确保在离开之前总是执行后面的指令,我想将它们放在 finally 块中。 (我知道 AdviceAdapter 类,但是当被调用的方法抛出异常时,它不能确保退出代码的执行。)

我的问题是结果中的指令顺序错误。

要处理的方法:

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

期望的结果:

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

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

(记录 X 也可以发生在 try 块的第一行。)

(期望结果的字节码等于下面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)之后,但在返回指令之前。 11-14 需要在 03 之前。

【问题讨论】:

  • 请注意,return 也有可能抛出异常。
  • @Antimony:返回本身(第 15 行)不会导致异常,因为它只是弹出并返回堆栈上的值。返回值的计算(可能引发异常)发生在返回之前的指令中,并且应该仍然在 try 块中。 (不过,测试用例通常是 void 方法。)
  • 一般来说,在监视器处于非法状态的情况下,返回指令本身会抛出异常。但这应该是一个问题。
  • 好的,没错。您知道为什么说明的顺序错误吗?

标签: java bytecode instrumentation java-bytecode-asm bytecode-manipulation


【解决方案1】:

我不确定您的方法中的错误在哪里。但是在使用 AdviceAdapter 进行了一些试验和错误之后,我实现了这样的目标。

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

【讨论】:

  • 谢谢,我尝试将您的解决方案与我的解决方案进行比较。相关的字节码指令几乎相同,但现在我发现了问题。
【解决方案2】:

您可以将 JUnit 注释 @Before@After 放在应该在测试方法之前和之后调用的方法上。

【讨论】:

  • 感谢您的回答。我知道,但这不是我想要达到的。其他@Before@After(或@BeforeClass@AfterClass)方法可能已经存在,然后在我之前执行。
【解决方案3】:

警告:这个解决方案只有在一个方法只包含一个返回指令时才有效(例如:如果它只抛出一个异常就不起作用)。 见:Embed the existing code of a method in a try-finally block (2)


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

解决方案: 我在visitCode 方法中调用我的方法before(它为需要在开头的新行添加代码)。如果操作码是返回语句,我会在 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 调用时存在一些问题。此外,它可能会建议更多退出点,并且在以下情况下不起作用正好关闭一个 try 块。)

【讨论】:

    猜你喜欢
    • 2021-09-09
    • 1970-01-01
    • 1970-01-01
    • 2013-12-16
    • 1970-01-01
    • 2013-09-01
    • 2018-11-07
    • 2018-07-18
    • 1970-01-01
    相关资源
    最近更新 更多