<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          報告,書里有個BUG!

          共 5073字,需瀏覽 11分鐘

           ·

          2021-08-24 09:07

          你好呀,我是 Kirito。今天分享一篇我的好友 Why 近期寫的一篇原創(chuàng)文章。

          最近在看《深入理解 JVM 虛擬機》(第三版)的時候發(fā)現(xiàn)一個有意思的 BUG。

          給大家匯報一下。

          這段話位于第三版的 326 頁,屬于書中的第八章虛擬機字節(jié)碼執(zhí)行引擎這一部分的內(nèi)容。

          整個第八章主要分析了虛擬機在執(zhí)行代碼時,如何找到正確的方法、如何執(zhí)行方法內(nèi)的字節(jié)碼,以及執(zhí)行代碼時涉及的內(nèi)存結(jié)構(gòu)。

          而其中的 8.4 小節(jié)是這樣的:

          其實還有個 8.4.5 小節(jié),由于排版問題,我不好拍下來。

          而出 Bug 的地方,就是對應(yīng)書中的 8.4.5 小節(jié),標(biāo)題是:

          實戰(zhàn):掌控方法分派規(guī)則

          接下來,我們就看看到底是哪里出 Bug 了。

          另外,需要提前說明的是,我沒有做背景知識的鋪墊,默認(rèn)你是了解關(guān)于 Java 虛擬機層面對于動態(tài)類型語言的支持的。

          其實說白了就是那幾個指令:

          • invokestatic
          • invokespecial
          • invokevirtual
          • invokeinterface
          • invokedynameic


          同時也了解 MethodHandle 類中下面幾個方法和上述幾個指令的關(guān)系的:

          • findStatic
          • findSpecial
          • findVirtual

          不知道也沒關(guān)系,就看一樂呵。面試不考,放心。

          啥 BUG

          先直接給大家上個代碼,也是書上的示例代碼,你思考一下,能不能實現(xiàn)這個需求:

          絕大部分人的第一反應(yīng)就是 super 關(guān)鍵字。

          但是可惜的是 super 調(diào)用的是父類的 thinking 方法,而當(dāng)前類 son 的父類是 Father 類。

          再接著想,可能有的同學(xué)能想到操作字節(jié)碼,比如用 ASM、Javassist 等字節(jié)碼操作工具,去搞一些騷操作。

          這個思路是可以的,但是屬于作弊行為。

          題目是要求在字節(jié)碼之上的 Java 層面解決。

          有的同學(xué)還能想到反射。

          誒,想到反射的同學(xué)很不錯,可以給自己鼓個掌。

          先公布答案,為了你方便運行,我直接把整個代碼放這里,你粘過去就能跑:

          public class MethodHandleTest {

           class GrandFather{
            void thinking(){
             System.out.println("i am grandfather");
            }
           }
           class Father extends GrandFather{
            void thinking(){
             System.out.println("i am father");
            }
           }
           class Son extends Father {
            void thinking() {
             try {
              MethodType mt = MethodType.methodType(void.class);
              MethodHandle mh = lookup().findSpecial(GrandFather.class,
                "thinking", mt, getClass());
              mh.invoke(this);
             } catch (Throwable e) {
             }
            }
           }

           public static void main(String[] args) {
            (new MethodHandleTest().new Son()).thinking();
           }
          }

          上面這個答案就是來自書中的答案。

          但是當(dāng)你粘出來運行的時候,有趣的事情發(fā)生了:

          什么情況,為什么書上的運行結(jié)果是這樣的?

          誒,這就是 BUG 的體現(xiàn)了。

          為啥是這樣的?

          同樣的程序,在第三版里面是這樣描述的:

          很明顯了,在 JDK 7 Update 9 之前的運行結(jié)果是這樣的,說明后續(xù)更的時候修復(fù)了什么問題。

          如果你的運行結(jié)果還是 i am grandfather,那么兄弟,你的 JDK 版本該升級一下了。

          那么到底修復(fù)了什么問題呢?

          我在知乎上找到了關(guān)于這個問題的R大的回答:

          https://www.zhihu.com/question/40427344

          首先這個神一樣的男人,直接就說書上的結(jié)論是錯誤的。

          他說:因為 MethodHandle 用于模擬 invokespecial 時,必須遵守跟 Java 字節(jié)碼里的 invokespecial 指令相同的限制,只能調(diào)用到傳給 findSpecial() 方法的最后一個參數(shù)(“specialCaller”)的直接父類的版本。

          啥意思,直接就是看著頭大。

          不慌,根據(jù)我們深厚的語文功底,大家都知道,重點在后半句:

          只能調(diào)用到傳給 findSpecial() 方法的最后一個參數(shù)(“specialCaller”)的直接父類的版本。

          那么最后一個參數(shù)是什么?

          它的直接父類又是什么?

          來,我給你 Debug 一下:

          通過截圖我們知道最后一個參數(shù)其實就是當(dāng)前類,即 son。

          它的直接父類又是什么?

          在周大大書里的例子里,類之間的基礎(chǔ)關(guān)系是這樣的:

          Son->Father->GrandFather

          所以 son 的直接父類,就是 father 類:

          從這里可以清楚的看到,這里的 method 其實是 father 類的 thinking 方法。

          同時,R大還說了:

          findSpecial()還特別限制如果Lookup發(fā)現(xiàn)傳入的最后一個參數(shù)(“specialCaller”)跟當(dāng)前類不一致的話默認(rèn)會馬上拋異常

          來,試驗一把嘛。

          當(dāng)我們把最后一個參數(shù)傳 Father.class,再次運行發(fā)現(xiàn)拋出了異常。

          最后,R大也指出,曾經(jīng)有這樣的 bug 存在,所以也有可能是存在示例代碼中的結(jié)果的:

          可能是因為findSpecial()得到的MethodHandle的具體語義在JSR 292的設(shè)計過程中有被調(diào)整過。有一段時間findSpecial()得到的MethodHandle確實可以超越invokespecial的限制去調(diào)用到任意版本的虛方法,但這種行為很快就被認(rèn)為是bug而修正了。

          所以,周大大在第三版中也更新了這部分的內(nèi)容:

          我也去看了 JDK 8 關(guān)于 findSpecial 方法的規(guī)范說明 :

          https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandles.Lookup.html#findSpecial-java.lang.Class-java.lang.String-java.lang.invoke.MethodType-java.lang.Class

          其中有這樣的一句話:

          The function MethodHandles.lookup is caller sensitive so that there can be a secure foundation for lookups. Nearly all other methods in the JSR 292 API rely on lookup objects to check access requests.

          簡單翻譯一下就是這樣的。

          MethodHandles.lookup這個函數(shù)對調(diào)用者是敏感的,這樣就可以有一個安全查找基礎(chǔ)。JSR 292 API 中的幾乎所有其他方法都依賴查找對象來檢查訪問請求。

          調(diào)用者敏感,我是這樣理解的:不同調(diào)用者,訪問權(quán)限不同,其結(jié)果也不同。

          比如在書中的例中,在 Son 類中調(diào)用 MethodHandles.lookup,Son 是調(diào)用者,因為調(diào)用者是敏感,所以只能訪問到 Father 類的 thinking。

          另外,文檔中提到的 JSR 292 也和 R 大的回答呼應(yīng)上了。

          我對比了一下 JDK 7 和 8 之間描述的差異:

          發(fā)現(xiàn) JDK 8 的描述多了整整一個 Caller sensitive methods 小節(jié)。

          翻譯過來就是“這是一個調(diào)用者敏感的方法”。

          這一小節(jié)里面的這一句話,就是我剛剛說的那句。

          能突破嗎?

          知道問題被修復(fù)了,那么問題又來了。

          這個需求還能實現(xiàn)嗎?

          現(xiàn)在這個需求按照前面的思路走不通的原因,是因為這個地方的校驗繞不過去:

          java.lang.invoke.MethodHandles.Lookup#checkSpecialCaller

          那我們繞過這個限制就好了。

          這個方法看起來也不復(fù)雜,而且有這樣的一個判斷,如果成立則直接返回,不做校驗:

          allowedModes,這個值如果我們可以設(shè)置為 “TRUSTED”,那么就能直接返回,從而避開下面的這些校驗。

          怎么繞開呢?

          直接上代碼:

          class Son extends Father {
            void thinking() {
             try {
              MethodType mt = MethodType.methodType(void.class);
              Field lookupImpl = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
              lookupImpl.setAccessible(true);
              MethodHandle mh = ((MethodHandles.Lookup) lookupImpl.get(null)).findSpecial(GrandFather.class, "thinking", mt, GrandFather.class);
              mh.invoke(this);
             } catch (Throwable e) {
              e.printStackTrace();
             }
            }
           }

          來看運行結(jié)果:

          這個方案也是周大大書上寫的方案:

          結(jié)合著這個看,基本上就能看懂了:

          不得不說,反射真的是太“流氓”了。

          好了,本文就這些內(nèi)容了。

          那你看完了,我問你一個問題:

          你覺得你知道了這個點,有什么卵用嗎?

          是的,沒有。

          那么恭喜你,又在我這里學(xué)到了一個沒有任何卵用的知識點。

          如果一定要說有用的地方,那么就是看書的時候別只看,得動手。

          比如本文的例子,如果不動手,你自己大概率是不會踩到這個“彩蛋的。


          END -

          「技術(shù)分享」某種程度上,是讓作者和讀者,不那么孤獨的東西。歡迎關(guān)注我的微信公眾號:「Kirito的技術(shù)分享」


          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大香蕉伊人视频在线观看 | 欧美系列-熊猫成人网 | 40岁无码视频看看 | 色屄影院 | 亚洲欧美性爱 |