給IDEA道個歉,這不是它的BUG,而是反編譯外掛的BUG。

2022-05-25 15:01:08

你好呀,我是歪歪。

上週我不是發了《我懷疑這是IDEA的BUG,但是我翻遍全網沒找到證據!》這篇文章嗎。

主要描述了在 IDEA 裡面反編譯後的 class 檔案中有這樣的程式碼片段:

很明顯,這玩意就是一個語法錯誤。

但是當我用其他的編譯器開啟之後,顯示又是正常的。於是我當時就斷然的下了一個結論:這是 IDEA 的 BUG。

其實寫那篇文章的時候我就在想,這應該是 IDEA 使用的反編譯器的 BUG,但是 IDEA 使用的是什麼反編譯器,工作原理是什麼,這一塊東西就觸及到我知識的盲區了。

我實在是追不下去了。

反正我是在 IDEA 裡面才能復現這個 BUG,那就先把鍋甩給 IDEA 吧。

但是文章釋出之後,有這樣的一個評論:

我一看到這個評論,我就知道,排查問題的新方向來了。

FernFlower

直接衝向 github 對應的倉庫:

https://github.com/fesh0r/fernflower

在網上搜到的都是這個上面這個倉庫,但是你注意看這個倉庫的右邊的說明:

第一個單詞 unofficial,就是非官方的意思。

也就是說這個倉庫是「好事之人」為了更好的研究 IDEA 的反編譯器,搞出來的一個映象倉庫。

雖然說的是非官方,但是專案維護人員其實都是同一批人。所以這個映象和官方倉庫中的是一模一樣的,就直接看這個倉庫了。

首先進入這個倉庫,我就習慣性的想往 issue 裡面鑽,但是我發現它沒有 issue。

後來才發現它的 README.md 檔案第一段就寫了:

請將您的錯誤報告和改進建議傳送到這個連結:

https://youtrack.jetbrains.com/issues/IDEA

這個連結點過去,就是 jetbrains 產品的 issues 區域,

眾所周知,jetbrains 旗下的產品特別的多,所以我可以只選擇 IDEA 相關的:

那麼我應該搜尋什麼關鍵字呢?

我也不知道,於是我試著搜了一下這個,你別說還真有意外收穫:

早在 2 年前,就有人提出了這個問題:

而且除了我們說的型別不匹配的錯誤外,他還提出了另外一個值得優化的地方:

你看這個 result 是不是被宣告了兩次,是不是應該被優化一下?

我其實當時也發現這個問題了,但是本著又不是不能用的精神,也就沒去深究。沒想到歪打正著,在這裡又遇到了。那就順便看看到底啥情況。

所以我順著這裡的這個連結:

找到了這個 pr:

https://github.com/JetBrains/intellij-community/pull/1538

然後我看了一下這次 pr 對應的程式碼提交。

看了一眼我就直接關閉了。

因為我發現這玩意完全就是超了個大綱,根本看不懂:

既然程式碼看不懂個,換個思路,反正他說解決了,那麼我就看看是哪個版本解決的不就行了嗎?

回去一看發現是 2022.1 版本解決的,而我現在的版本是 2021.3:

剛好,那就趁著這個驗證的機會升級一波 IDEA。

果然,我升級到 2022.1 版本之後,再次檢視 class 檔案,變成了這樣:

這個算是意外收穫,也側面證明了這類問題確實應該是從 IDEA 的反編譯器的角度下手。

我們現在回到型別不匹配的這個錯誤中來:

官方在前面的連結中,並沒有說明這個問題解決了,但是又給我指了一條路。

我順著這條路,來到了這個連結:

https://youtrack.jetbrains.com/issue/IDEA-203794/Decompiling-bug

從上面的測試用例中可以看出來,當 int 的值比較大的時候,即使它沒有被使用,也不會被優化為 true。

那麼這個「比較大」的邊界是什麼呢,或者說分界點是什麼呢?

我也不知道,於是我採取了二分策略,找到了邊界值:

32767,就是這個邊界值。

那麼這個值難道有什麼特殊含義嗎?

我也不知道,於是搜了一下,才發現原來是 short 的邊界值:

好吧,short 這玩意著實用的不多,不眼熟也很正常的嘛。

但是它和編譯器把這個範圍內的、未使用的 int 型別的值變為 true 之間有什麼關係呢?

這個我真不知道,因為這個 BUG 官方也沒有給解決,也沒有給答覆。

這個問題最早可以追溯到 2018 年:

但是這個 BUG 下面並沒有任何人給任何回覆。

時隔四年,在這個問題下是一個程式設計師和另外一個程式設計師的一次擦肩而過和無功而返。

雖然我還是沒找到具體的觸發 BUG 的程式碼,但是目前足以說明, 下面這個圖片中的問題並不是 IDEA 的 BUG,而是 IDEA 的反編譯器外掛 Fernflower 的 BUG:

另外,其實我做了一次掙扎,我在 Fernflower 的 README.md 檔案中看到了這一句描述:

將 int 1 解釋為 boolean true,目的是為了解決編譯器的 BUG。

我並不知道這個和我的問題有沒有相關度,但是我還是試著去找了一下對應程式碼提交的地方。

我開始想的誰提交的相關的程式碼,可能在提交的 commit 資訊裡面會有蛛絲馬跡。

但是沒有。

我找到了程式碼對應的地方:

但是這行程式碼從 2014 年第一次提交的時候就是存在的。也就是說作者在最開始的時候就考慮到了 0 和 true 之間的關係。

只不過我在初始化提交的檔案頭中看到了一個網站,我以為這裡面會有一些線索什麼的:

於是我就那麼隨手一點。好傢伙,我是萬萬沒想到,變成不可描述的賭博網站了。

你也不用專門去找了,沒啥看的,特別簡陋的網站,一眼就是釣魚網站。

好了,看到這裡我想問你一個問題:你知道了這些有什麼卵用呢?

是的,沒有任何卵用。

那麼恭喜你...

真沒什麼用

我也不知道這是我寫的第幾個沒有什麼用處的知識點了。反正我記得應該不少了吧?

於是我決定要不盤點一下吧?

第一個

第一次出現用不上的知識點是在《面試時遇到『看門狗』脖子上掛著『時間輪』,我就問你怕不怕?》這篇文章中:

大概就是說用 Redisson 加鎖的時候,之前的 Lua 指令碼中用的是 hset 之後用的是 hincrby:

這是我兩年多前寫的文章了,那個時候經驗還不夠豐富。

比如現在讓我再次回到這個地方,我一看就覺得有故事:為什麼平白無故的會把這個地方從 hset 修改為 hincrby 呢?

巧了,剛好我手上有 Redisson 的原始碼,開啟看一眼:

果然是因為有 BUG 才從 hset 修改為 hincrby 的。

找到對應連結,淺看一眼:

https://github.com/redisson/redisson/issues/2551

從標題上看,是說解鎖的時候丟擲異常說 value 不是 integer 型別。

但是從他給的測試用例來說,和 integer 沒有任何關係。

這個 issues 裡面有很長的關於這個問題的討論,有興趣可以自己去看一下。

我只貼個結論,就是把 lock 的指令碼從 hset 修改為 hincrby:

還是怪自己太年輕,錯過了一個素材,沒有看到事物的本質。

第二個

第二個是出現在《這個Map你肯定不知道,畢竟存在感確實太低了。》這篇文章。

全片就是介紹了 IdentityHashMap 這個玩意,IdentityHashMap 的核心點在於 System.identityHashCode 方法。

然後我又畫了很多圖來說明 identityHashMap 的儲存套路: key 的下一個位置,就是這個 key 的 value 值。它的資料結構不是陣列加連結串列,就完完全全是一個陣列。

但是說真的,我在工作中還真沒有用過這個玩意。都是 HashMap 直接一把梭。

所以,目前為止,真沒什麼用。

第三個

第三個是出現在《這個Bug的排查之路,真的太有趣了。》這篇文章。

是從《深入理解Java虛擬機器器》書裡面的一段程式碼講起,引出了一個叫做 Monitor Ctrl-Break 的執行緒。

這個執行緒是 idea 特有的執行緒,通過 javaagent 的方式執行起來,對於我們來說完全無感知。

但是當 idea 直接 Run 起來的時候,它又會被作為活躍執行緒,所以下面的程式碼就是有問題的。

因為除了主執行緒之外,還有一個 Monitor Ctrl-Break 執行緒:

//等待所有累加執行緒都結束
while(Thread.activeCount()>1)
    Thread.yield();

當時我還費了老大的勁兒搞了這個動圖,不知道還有沒有人記得:

確實是一個非常冷的知識點,沒啥用處,但是還記得我探索這個問題的時候,從一頭霧水到逐漸清晰的過程,是一個很美妙的感受。

沒有查閱任何資料,就是通過自己平時的小積累和大膽的猜測最終找到了問題的答案。

雖然好像對於實際工作中的幫助不大,所以我把它歸屬於沒有什麼卵用的知識點。

但是,這種偏門的事情,你知道了之後,以後萬一用上了,那一定是一個非常裝逼的場面。

第四個

第四個是出現在《真是絕了!這段被JVM動了手腳的程式碼!》這篇文章。

這是一個關於安全點機制帶來的長時間睡眠問題。

看到這個案例的時候我想到了之前在書上也看到過相關的例子,當時想的是:這玩意學它幹啥啊,我寫業務程式碼而已,肯定是遇不到這樣的問題呀。

實際情況是,我確實沒有遇到。但是我也確實看到了,我們的監控機制中,有專門針對 safepoint 相關的監控,以及我看到的很多問題排查文章,都會提到這個排查的方向。

以前覺得沒啥用的知識點,以後說不定就用上了。

第五個

第五個是出現在《414天前,我以為這是程式設計玄學...》這篇文章。

主要是關於使用 volatile ,又使用了輸出語句的時候,導致實驗結論和預期的不符合,從而引出的各種奇奇怪怪的問題。

比如這個文章中說到的 final 關鍵字在其中起到的作用。

額,這個知識點吧,確實用處不大。

但是,我在各個技術群裡面經常看到大家驗證 volatile 特效的時候會踩到加入輸出語句的坑。

我都會先問一句:是不是初學者?

如果是的話就別折騰了,也別深究了,這玩意,水很深,你把握不住。

第六個

第六個是出現在《就這?一個沒啥卵用的知識點。》這篇文章。

額,這個你看題目就知道...

文章很短,主要是回答了下面這個問題:

結論就是:一回事。

第七個

第七個是出現在《我承認,看過億點點。》這篇文章。

關於 hashCode 的值和物件記憶體地址之間的關係,據說這是一道面試題,我去翻了一下原始碼。

hashCode 的值和物件記憶體地址之間的關係就是:沒有關係。

第八個

第八個是出現在《這題答案不在原始碼裡...》這篇文章。

這篇文章是在回答讀者的一個問題: Java 的異常是如何丟擲來的?

答案藏在位元組碼中,所以隨著這條路找到了 jvm 規範。

而在規範中意外發現一個方法的程式碼長度,從位元組碼的層面來說是有限制的。

冷知識,用處不大。

第九個

第九個是出現在《報告,書裡有個BUG!》這篇文章。

就是看《深入理解 JVM 虛擬機器器》(第三版)這本書的時候發現其中一段描述有點意思:

於是我去挖了一下。

額...

當時挖的津津有味,但是現在其實我已經忘記了文章裡面寫的是什麼了。

仔細看了一遍,發現,嗯,確實沒有騙你,確實是用處不大的知識點

第十個

第十個是出現在《幾行爛程式碼,我賠了16萬。》這篇文章。

在並行加事務的雙重 buff 下,修改事務的隔離級別為序列化。

不是臥龍鳳雛,想不出這麼絕的方案。

確實屬於沒有用的範圍。

第十一個

第十一個是出現在《填個坑!再談執行緒池動態調整那點事。》這篇文章。

執行緒池動態調整的時候需要一個自定義佇列。

而這個佇列,一般來說就是把 LinkedBlockingQueue 簡單的修改一下,但是 put 方法也需要進行對應的修改。

但但是執行緒池裡面並沒有用佇列的 put 方法,而是用的 offer 方法。

這是我之前寫的時候完全沒有考慮的到的問題,是和一個大佬探討的時候,大佬給我分享的。

如果只是把場景限定為執行緒池使用,那麼不會有什麼問題。但是既然都自定義了,就應該把各個方法都維護好。

用處不是特別大,畢竟用動態調整的方案的人並不多,主要是拿捏一波細節。

第十二個

第十二個是出現在《我被這個瀏覽了 746000 次的問題驚住了!》這篇文章。

反正以後只要碰到時間不對勁的問題,不管它是有多奇葩,多不可思議,想都不用想,十有八九都是時區問題。

朝這個方向找方案就對了。

第十三個

第十三個是出現在《算是我看原始碼時的一個小技巧吧~》這篇文章。

這篇文章裡面有一段描述我寫了很長,在那段描述裡面,你知道了看門狗的生日是 7 月 4 日,也知道了它有好幾個曾用名...

盤點的差不多就是這些了,我記得應該是還有的,但是我實在是想不起來了。

想起來了又怎麼樣呢?

是的,想起來了也沒有什麼用。