美文网首页jvm
虚拟机字节码执行引擎【动态类型语言支持(四)】

虚拟机字节码执行引擎【动态类型语言支持(四)】

作者: 云芈山人 | 来源:发表于2021-08-18 23:52 被阅读0次

    实战:掌控方法分派规则

    invokedynamic指令与此前4条传统的“invoke*”指令的最大区别就是它的分派逻辑不是虚拟机决定的,而是由程序员决定的。

    方法调用问题,举个例子:

    package com.jvm.test;
    
    public class invoketest {
    
        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(){
                //填入适当的代码(不能修改其他地方的代码)
                //实现调用祖父类的thinking()方法,打印“ i am grandfather”
            }
        }
    }
    

    在Java程序中,可通过“super”关键字很方便地调用父类中的方法,但如果要访问祖类的方法呢?

    在拥有invokedynamic和java.lang.invoke包之前,使用纯粹的Java语言很难处理这个问题(使用ASM等字节码工具直接生成字节码可以处理,但这已经是在字节码而不是Java语言层面),原因是在Son类的thinking()方法中根本无法获取到一个实际类型是GrandFather的对象引用,而invokevirtual指令的分派逻辑是固定的,只能按照方法接收者的实际类型进行分派,这个逻辑完全固话在虚拟机中,程序员无法改变。

    如果是JDK7 Update9之前,可使用以下代码来解决该问题。

    package com.jvm.test;
    
    import java.lang.invoke.MethodHandle;
    import java.lang.invoke.MethodHandles;
    import java.lang.invoke.MethodType;
    
    public class invoketest {
    
        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 = MethodHandles.lookup().findSpecial(
                            GrandFather.class, 
                            "thinking", 
                            mt, 
                            getClass());
                    mh.invoke(this);
                }catch(Throwable e){
                }
            }
        }
    }
    

    运行结果:

    i am grandfather
    

    但是这个逻辑在JDK7 Update9之后被视作一个潜在的安全性缺陷修正了,原因是必须保证findSpecial()查找方法版本时受到的访问约束(如对访问控制的限制、对参数类型的限制)应与使用invokespecial指令一样,两者必须保持精确对等,包括在上面的场景中它只能访问到其父类中的方法版本。所以在JDK7 Update10修正之后,运行以上代码得到的结果如下:

    i am father
    

    查看MethodHandles.Lookup类的代码,在该API实现时预留了后门。访问保护是通过一个allowedModes的参数控制,而且这个参数可以被设置成“TRUSTED”来绕开所有 的保护措施。尽管这个参数只是在Java类库本身是引用,没有开放给外部设置,但我们通过反射可轻易打破这种限制。由此,我们可把代码修改成如下来解决问题:

    package com.jvm.test;
    
    import java.lang.invoke.MethodHandle;
    import java.lang.invoke.MethodHandles;
    import java.lang.invoke.MethodType;
    import java.lang.reflect.Field;
    
    public class invoketest {
    
        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);
                    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){
                }
            }
        }
    }
    

    《深入理解Java虚拟机》第三版 学习

    相关文章

      网友评论

        本文标题:虚拟机字节码执行引擎【动态类型语言支持(四)】

        本文链接:https://www.haomeiwen.com/subject/cgpwbltx.html