記一次 inline 使用不當導致編譯期Null指針的排查過程

Petterp 2021-08-15 18:16:37 阅读数:540

本文一共[544]字,预计阅读时长:1分钟~
一次 inline 使用 用不 null

起因

周五的一個下午,我哼著小曲和往常一樣合完代碼。准備運行試試看,結果build時發現了這樣一個异常。

InlineParameterChecker NullPointerException

image-20210815115927974

一般對於這種編譯期間的异常,原因往往並不是很容易能快速定比特,因為往往都是業務代碼出現的問題,如果某次合並更改很多,比如我這一次,重構了底層的某個組件,所以直接當場裂開。於是接下來整個任務都變成了如何找到 錯誤的 代碼處。

先說結論

當方法添加了 inline 修飾後,即也就是內聯之後,如果方法參數是一個函數對象(lambda),那麼不可為 null

一般情况下 IDE 會主動提示你,如下所示:

image-20210815121248484

但是特殊情况下,如下錯誤示例:

inline-ide錯誤示例

某一天,程序員小P 突然發現一段代碼,善用Kotlin的他,覺得這裏可以使用 inline 可以優化,於是下意識就加了一個 inline ,但是沒有仔細看方法參數,反正IDE沒報錯,即也就是自己還沒意識這個更改帶來的嚴重性。

但是一旦改完之後,沒有 build ,那麼這就是一個隱藏的坑,嚴重一點可能會導致你好幾個小時找不到原因。

如何定比特錯誤代碼

如果直接對著代碼找,那麼可能就需要對比所有相關 inline 相關的代碼,如果使用之處不多,那麼也能很快定比特。

但是最關鍵的問題在於: 我怎麼知道那段代碼有問題呢,雖然我知道是inline的問題,但是具體是什麼呢,我現在不知道啊,所以這種方法暫時也只能放弃。

google三連

身為一個開發經驗小成的老鳥,這肯定難不倒我,直接google三連

image-20210815122659073

什麼玩意都是,我是傻狗。

難道不應該直接搜索如何打印完整的 build 日志嗎,然後通過日志查看到底在哪一步失敗了,於是剛好想起了前幾天同學也發現過這樣的問題,直接去問他。

image-20210815123116551

打印build詳細日志

波瀾不驚,這樣就可以了,嘿嘿嘿,好簡單。

於是,改完,開始build ....等待 loading

image-20210815131524974

內存不足,那就再改 gradle 內存,這下應該可以了吧,重啟as,開始build...等待loading:

image-20210815130912878

這...,還是看不到具體日志啊,難道是真的看不了嗎,花了10幾分鐘就這?

gradlew assembleDebug

繼續嘗試別的方案,又是一頓搜索,這時候看到了 StackOverFlow 上有人用這個命令也可以,於是死馬當活馬醫,繼續嘗試。

執行 ./gradlew clean assembleDebug 開始嘗試。結果如下:

image-20210815151300208

我裂開了,於是繼續找其他方案,來來回回折騰了快1個小時,還是這樣,難不成我只能去對代碼了嗎?

太痛苦了,這時候只能尋求坐在我對面的開發組大佬幫助,希望能解决問題,阿門。

讓大佬來看了一下,大佬的回複很簡單:

這應該已經是gradle能給出的最大提示了,你想要的錯誤具體比特置,應該是無法打印出的,這種情况,你只能通過合並的diff對比下,看看是哪裏導致的。

是啊,我忙活了這麼久,居然忘記了最簡單的,我直接去看git合並的diff啊,因為這個是 inline 的報錯,那麼範圍也就只有 改動過的inline 啊,瞬間感覺自己走錯方向了。

通過git-合並diff對比

直接 github 找到 pr ,點擊 Files changed , command+F 搜索 inline ,瞬間流下來開心的淚水.

image-20210815152000980

果然範圍小多了,那麼接下來只需要找 inline 具體更改比特置。

image-20210815152305983

於是乎,就發現上述代碼,似乎不太對勁,乍一眼看上去沒啥,但整個 inline 相關的更改裏,只有這段新增了一個 inline 修飾。

於是試著去掉 inline ,發現居然好了,臥槽......

APEX新賽季快被玩家玩壞了,靠錶情包就能躲攻擊,屏幕還能當外掛?

反思

到了這裏,我似乎不敢相信自己,在之前的開發中,我從不知道 inline 修飾的方法,參數是函數對象時不可為 null ,於是我趕緊去搜了下相關文章:

google

image-20210815152642317

度娘

image-20210815152702463

結果並沒有發現相關有價值的文章,可能很少人像我那樣操作,的確一般情况下,IDE 都會准確提示錯誤信息。

對比轉換後的java代碼,結果也是報錯,也沒有什麼可奇怪的。

image-20210815153106034

於是接連測試了下:

結果也很簡單。對於 inline 修飾的方法而言,如果方法參數是基本對象,那麼可以為 null ,如果是 函數對象(lambda) ,則不可為 null ,否則將引發編譯錯誤。

image-20210815154254706

那到底為什麼呢?

難道網上沒有資料,這個問題就要爛在這裏了嗎,我不太甘心,既然沒有現成,那我們就從 inline 的本質出發,尋找原因:

我們都知道,inline 的本質是在編譯器將相關代碼直接拷貝到了調用的地方,也就是說,比如我們上述截圖中的 testObj 方法中的 obj 函數對象,其具體實現,會最終變成如下例子。

假設如下:

public final void testObj2(@Nullable Function0 obj) {
int $i$f$testObj2 = 0;
}
複制代碼

裏面的Function是具體可量化的接口,其是Kotlin提前定義好的。

但是現在,obj函數對象 可能為 null,即編譯器沒法確定了,編譯器不知道這裏到底應該複制什麼玩意,如果不複制,那還怎麼優化,但怎麼複制,你都是 null 的,我怎麼知道呢,所以直接 null 指針了。

當然上述過程我是猜測的,因為我沒辦法知道其內部原理,但是我相信實際和我推測的應該差別也不是很大。

碎碎談

這雖然只是一個開發中不怎麼常見的問題,但從整個過程而言,我的做法或許並不是很好,如果我一開始選擇最直接的方式,效率會更高點,但也許我也會失去對一些東西的探索,之所以寫本篇,很大程度上也是因為我在這個問題上所花費的時間讓我必須寫下來記錄一下,希望這個過程可以幫助到大家。

大家加油,周末愉快!

版权声明:本文为[Petterp]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/08/20210815181418992L.html