前文指路:
在 JVM 中,if 语句对应的字节码指令有:
OP_IFEQ, OP_IFNE, OP_IFLE, OP_IFLT, OP_IFGT, OP_IFGE,
OP_IF_ACMPEQ, OP_IF_ACMPNE,
OP_IF_ICMPLT, OP_IF_ICMPGE, OP_IF_ICMPGT, OP_IF_ICMPNE,
OP_IF_ICMPEQ, OP_IF_ICMPLE,
OP_IFNONNULL, OP_IFNULL
这些指令与源码中的 if
语句一一对应。
源码:
void main() {
int a = 1;
if (a > 1) {
a = 2;
} else {
a = 3;
}
return;
}
对应字节码:
0: iconst_1
1: istore_1
2: iload_1
3: iconst_1
4: if_icmple 12
7: iconst_2
8: istore_1
9: goto 14
12: iconst_3
13: istore_1
14: return
if_icmple
表示:当栈顶两个元素满足 <=
时跳转。翻译后逻辑为:
if (a <= 1) {
a = 3;
} else {
a = 2;
}
可以看到,字节码中的 if
条件与源码相反,if
和 else
的 body 也被交换,但逻辑等价。
结论: 字节码中大多数 if 条件是反转的,因此在反编译时应对条件取反,同时互换 if 和 else 的 body。
流程图如下:
可以看出从 if 相关指令开始,出现两条分支,分别对应 if body 和 else body 的代码块。两个 body 最终在一个点汇合标志着if语句的结束。
流程图如下:
可以看出 if 指令指向两个节点,一个节点是 if body,另一个节点是 if 语句的结束。
一般来说,if 语句的 cfg 应该是一个树状的,不存在一个代码块同时属于多个 body的情况。
如图:
但逻辑运算表达式对应字节码的 cfg 会出现两个 if 语句 body 重叠的情况,如下:
源码:
if (a > 1 || a > 0){
a = 2;
}
问题:如果直接翻译会出现下面的情况,源码中的 if body 出现了两次。
if (a > 1) {
a = 2;
} else {
if (a > 0) {
a = 2; // 这里的body和if (a > 1)的body相同
}
}
为了解决这种问题,需要对指令中的 if 语句化简,将这种特殊情况的 if 语句嵌套转为逻辑运算。
break
和 continue
在字节码中表现为 goto
,会干扰 if
body 的范围识别。
如图:
可以看出,a = 1 是循环的入口点,end 为循环结束。图中的 goto 指向循环结束,说明 goto 其实是 break 语句。
可以将上面的图等价变为下面的图:
解决方案:在解析 if 语句前,先识别所有循环结构,并将 goto 恢复为 break / continue,补充 LoopEnd 节点。
在之前的案例中,if 语句的 body 从开始到结束,虚拟机栈深是不变的,也就是 body 内是完整的代码块。
但在三元表达式的情况下,body 可能是向栈 push 一个 constant。
源码:
int a = b > c ? 1 : 0;
字节码:
0: iload_1
1: iload_2
2: if_icmple 5
3: iconst_1
4: goto 6
5: iconst_0
6: istore_3
问题:
-
if
的两个分支都从开始到结束栈深发生变化 -
istore
才是真正的赋值语句 -
if body
/else body
无法单独构成完整语句
【解决方案】
方案一:引入中间变量
int tmp;if (b > c) { tmp = 1;} else { tmp = 0;}int a = tmp;
方案二:栈模拟
-
模拟所有指令的栈深
-
在汇合节点处栈深发生变动,说明是三元表达式,针对三元表达式进行化简
-
将
if
替换为?:
表达式
小结
实际情况更复杂,例如在 body 中执行函数、执行赋值、嵌套等操作:
int a = b > 1 ? getValue() : b < 2 ? c = b : c = 1;
int a = (b > 1 ? object1 : object2).getValue();
经过验证,栈模拟的方案才能解决这种复杂的情况。在实际反编译过程中需要将整个 if 语句替换为一个占位变量,在还原并化简 if 语句后,尝试将 if condition 和栈中的操作数替换到占位变量中。
在 java 中的比较运算也是通过 if 指令实现,例如:
bool a = b > 1;
上面的代码等价于:
bool a = b > 1 ? true : false
所以同理的,可以转为三元表达式的情况处理,最后再对三元表达式化简从而还原源码。
if 的结束点的必要条件
-
被
if
节点支配 -
被
if body
起始节点支配 -
被
else body
起始节点支配
条件表达式的简化
-
将前缀表达式转为后缀表达式
-
对逻辑表达式进行归约与优化
解析 if 语句
-
if
指令跳转地址可能是 body 开始,也可能是结束 -
汇合节点即为
if
的结束点(不一定被 if 指令支配) -
若两个
if
指向同一个 body,且汇合节点相同,可尝试合并为逻辑表达式 -
反编译时需注意
if
条件的反转 -
特殊情况如
break
/continue
、三元表达式需额外处理
本篇文章深入分析了 Java 字节码中 if
语句的结构、识别方式及反编译策略。通过流程图、控制流图、支配关系等方法,可以准确地识别 if
、else
、三元表达式等结构,并为后续的语法还原和优化打下基础。
——END——
【YAK官方资源】
Yak 语言官方教程:
https://yaklang.com/docs/intro/
Yakit 视频教程:
https://space.bilibili.com/437503777
Github下载地址:
https://github.com/yaklang/yakit
Yakit官网下载地址:
https://yaklang.com/
Yakit安装文档:
https://yaklang.com/products/download_and_install
Yakit使用文档:
https://yaklang.com/products/intro/
常见问题速查:
https://yaklang.com/products/FAQ