今天调试一个 bug
的过程非常有趣,起承转合很像央视《走近科学》的风格。故障代码类似于这样:
if (mgr.getSomeObj().getSomeField() != null) {
hostnum = mgr.getSomeObj().getSomeField();
} else {
// ...
}
断点成功断在第一行,单步进入 getSomeField
后确定返回的是 null
。然后神奇的事情发生了:再次单步就发生了 NullPointerException
。既没有进入 if
块,也没有进入 else
块。简直不敢相信自己的眼睛 - 不相信就对了,因为远程执行的代码和本地 IntelliJ
打开的代码不一样。远程的代码应该类似这样:
foo = mgr.getSomeObj().getSomeField();
从 JVM 的角度去理解,本地和远程在出错前跑的代码都是一样的(也许这也是IntelliJ
一直没有报错说代码不匹配的原因):
325: getfield #546
328: invokeinterface #1389, 1
333: invokevirtual #1421 // getSomeField()
336: invokevirtual #288 // java/lang/Integer.intValue:()I
NullPointerException
是 336
导致的。因为远程代码中,foo
被声明为int
,而 getSomeField()
返回的是 Integer
。由于编译器自动 unbox
,
对 null
值进行了 intValue()
计算,发生了异常。
其实这个bug
半个月前同事就修复了,而测试团队用的是老的代码。又恰好336
前所有 JVM 指令也都是一样的,IntelliJ
也没有报告代码不匹配,造成了这个神秘的假象。