用於改變在方法體表達的共同的圖案使用ExprEditor並且其包括以下類型的表達式(在寫入時)的Expr特定子類:
- 型投
- 構造請致電
- 字段訪問
- catch語句
- instanceof expression
- 方法調用
- 新的數組表達
- 新表達
然而沒有這些包括比較表達式。你可以通過查看ExprEditor::loopBody
來源證實了這一點:
if (c < Opcode.GETSTATIC) // c < 178
/* skip */;
比較操作碼,如if_icmple
= 164被跳過。
Javassist由於其高級工具API而經常使用,但它也具有javassist.bytecode
包下的字節碼級API。這意味着您可以在方法字節碼中檢查操作碼並交換它們。
首先我們需要確定foo
方法和表達,我們希望改變(例如,使用的javap)的字節碼:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmple 18
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #3 // String I love apples
12: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: goto 26
18: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
21: ldc #5 // String I hate apples
23: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
26: return
正如我們所看到的,a > 5
被編譯成if_icmple
(< =)比較並分支到else塊,這是處理表達式的編譯器的常見模式。 要翻轉你的例子中的表達式,我們只需要將if_icmple
換成if_icmpgt
即可。
下面的代碼演示瞭如何使用Javassist進行字節碼的API來做到這一點:
CtClass cc = ClassPool.getDefault().get("TryClass");
CtMethod fooMethod = cc.getDeclaredMethod("foo");
CodeIterator codeIterator = fooMethod.getMethodInfo().getCodeAttribute().iterator();
while (codeIterator.hasNext()) {
int pos = codeIterator.next();
int opcode = codeIterator.byteAt(pos);
if(opcode == Opcode.IF_ICMPLE) {
codeIterator.writeByte(Opcode.IF_ICMPGT, pos);
break;
}
}
TryClass test = (TryClass) cc.toClass().newInstance();
test.foo();
但是這個代碼不進行任何額外的檢查,以確保預期的表達被改變。一些建議可能是在比較過程中檢查哪些操作數在堆棧中。如果其中一個從本地變量插槽加載,則可以使用LocalVariableTable(如果可用)CodeAttribute中的信息來匹配變量名稱。