感谢您的快速回复!
或许我可以通过我们的clojure clout实验性工具的实际例子来解释。
特别是,我们试图向这个route-matches函数注入代码
```
(route-matches [_ request]
(let [path-info (if absolute?
(request-url request)
(path-info request))]
(let [groups (re-match-groups re path-info)]
(when groups
(assoc-keys-with-groups groups keys)))))
```
为route-matches生成的字节码如下(仅提取最后几行 - 从` (assoc-keys-with-groups groups keys)`调用开始)
80: getstatic #136 / * Field const__37:Lclojure/lang/Var; #'clout.core/assoc-keys-with-groups * /
83: invokevirtual #120 / * Method clojure/lang/Var.getRawRoot:()Ljava/lang/Object; * /
86: checkcast #122 / * class clojure/lang/IFn * /
89: aload_3
90: aload_0
91: getfield #44 * / * Field keys:Ljava/lang/Object; * /
94: aconst_null
95: astore_0
96: invokeinterface #133, 3 * / * InterfaceMethod clojure/lang/IFn.invoke:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; * /
101: goto 106
104: pop
105: aconst_null
106: areturn
现在我们尝试使用字节码库(javassist)在方法体末尾注入字节码(一个`System.out.println(this.toString())` Java调用),如下所示
80: getstatic #136 / * Field const__37:Lclojure/lang/Var; #'clout.core/assoc-keys-with-groups * /
83: invokevirtual #120 / * Method clojure/lang/Var.getRawRoot:()Ljava/lang/Object; * /
86: checkcast #122 / * class clojure/lang/IFn * /
89: aload_3
90: aload_0
91: getfield #44 * / * Field keys:Ljava/lang/Object; * /
94: aconst_null
95: astore_0
96: invokeinterface #133, 3 * / * InterfaceMethod clojure/lang/IFn.invoke:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; * /
101: goto 106
104: pop
105: aconst_null
> 开始代码注入
106: goto 109
109: astore 5
111: getstatic #489 / * Field java/lang/System.out:Ljava/io/PrintStream; * /
> 指令114有问题,因为alload_0不是`this`而是null
114: aload_0
115: invokevirtual #491 / * Method toString:()Ljava/lang/String; * /
118: invokevirtual #496 / * Method java/io/PrintStream.println:(Ljava/lang/String;)V * /
121: aload 5
> 结束代码注入
123: areturn
我们可以看到,在第114条指令处失败,因为alload_0不再是预期的this指针。
我对JVM规范和Clojure的字节码输出不是很熟悉,因为我对这个语言的熟悉程度有限 :)。但根据
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.6.1:
> 当调用实例方法时,局部变量 0 总是用于传递将要调用实例方法的对象的引用(在Java编程语言中称为this)
我认为这并不限制将局部变量 0 改变为 null?但我假设将变量 0 保持为调用帧中的this是有益的?
正如这种情况,当我们使用字节码注入库时,将局部变量 0 改变为 null 会在方法体末尾注入代码时引发问题。
像finally一样注入代码没有任何区别,因为javasisst基本上只是在instr 105(带有异常表指针指向105之后的语句)后注入相同的字节码两次。但在这个点,由于null分配,已经丢失了this。