1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        報(bào)告,書(shū)里有個(gè)BUG!

        共 5073字,需瀏覽 11分鐘

         ·

        2021-08-24 09:07

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

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

        給大家匯報(bào)一下。

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

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

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

        其實(shí)還有個(gè) 8.4.5 小節(jié),由于排版問(wèn)題,我不好拍下來(lái)。

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

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

        接下來(lái),我們就看看到底是哪里出 Bug 了。

        另外,需要提前說(shuō)明的是,我沒(méi)有做背景知識(shí)的鋪墊,默認(rèn)你是了解關(guān)于 Java 虛擬機(jī)層面對(duì)于動(dòng)態(tài)類型語(yǔ)言的支持的。

        其實(shí)說(shuō)白了就是那幾個(gè)指令:

        • invokestatic
        • invokespecial
        • invokevirtual
        • invokeinterface
        • invokedynameic


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

        • findStatic
        • findSpecial
        • findVirtual

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

        啥 BUG

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

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

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

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

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

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

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

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

        先公布答案,為了你方便運(yùn)行,我直接把整個(gè)代碼放這里,你粘過(guò)去就能跑:

        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();
         }
        }

        上面這個(gè)答案就是來(lái)自書(shū)中的答案。

        但是當(dāng)你粘出來(lái)運(yùn)行的時(shí)候,有趣的事情發(fā)生了:

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

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

        為啥是這樣的?

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

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

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

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

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

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

        首先這個(gè)神一樣的男人,直接就說(shuō)書(shū)上的結(jié)論是錯(cuò)誤的。

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

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

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

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

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

        它的直接父類又是什么?

        來(lái),我給你 Debug 一下:

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

        它的直接父類又是什么?

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

        Son->Father->GrandFather

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

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

        同時(shí),R大還說(shuō)了:

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

        來(lái),試驗(yàn)一把嘛。

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

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

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

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

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

        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.

        簡(jiǎn)單翻譯一下就是這樣的。

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

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

        比如在書(shū)中的例中,在 Son 類中調(diào)用 MethodHandles.lookup,Son 是調(diào)用者,因?yàn)檎{(diào)用者是敏感,所以只能訪問(wèn)到 Father 類的 thinking。

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

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

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

        翻譯過(guò)來(lái)就是“這是一個(gè)調(diào)用者敏感的方法”。

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

        能突破嗎?

        知道問(wèn)題被修復(fù)了,那么問(wèn)題又來(lái)了。

        這個(gè)需求還能實(shí)現(xiàn)嗎?

        現(xiàn)在這個(gè)需求按照前面的思路走不通的原因,是因?yàn)檫@個(gè)地方的校驗(yàn)繞不過(guò)去:

        java.lang.invoke.MethodHandles.Lookup#checkSpecialCaller

        那我們繞過(guò)這個(gè)限制就好了。

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

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

        怎么繞開(kāi)呢?

        直接上代碼:

        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();
           }
          }
         }

        來(lái)看運(yùn)行結(jié)果:

        這個(gè)方案也是周大大書(shū)上寫(xiě)的方案:

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

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

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

        那你看完了,我問(wèn)你一個(gè)問(wèn)題:

        你覺(jué)得你知道了這個(gè)點(diǎn),有什么卵用嗎?

        是的,沒(méi)有。

        那么恭喜你,又在我這里學(xué)到了一個(gè)沒(méi)有任何卵用的知識(shí)點(diǎn)。

        如果一定要說(shuō)有用的地方,那么就是看書(shū)的時(shí)候別只看,得動(dòng)手。

        比如本文的例子,如果不動(dòng)手,你自己大概率是不會(huì)踩到這個(gè)“彩蛋的。


        END -

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


        瀏覽 55
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美一级特黄A片 | 伦片丰满丰满午夜电影 | 娇妻被两根一起进3p | 豆花视频一区二区在线播放 | 香蕉毛片 | chinese叫床videos | 欧美亚洲及日本黄色电影 | 国产一级大片 | 韩国三伦理片免费 | 女美草逼 |