2024年Clojure状态调查!中分享您的想法。

欢迎!有关如何操作的更多信息,请参阅关于页面。

0
编译器

发现当Java代理(通过ClassFileTransformer修改字节码)试图在Clojure函数退出时访问“this”引用时,“this”不再可用。

这可能是由于代码在以下位置发生变化而触发的:[ GitHub链接](https://github.com/clojure/clojure/blame/b19b781b1f0f3f46aee5e951f415e0456a39cbcb/src/jvm/clojure/lang/Compiler.java#L5946),在该方法退出之前删除了生成的字节码中的“this”引用。

这对Java代理的仪器来说是个问题,因为我们理论上可以在返回语句(通常作为finally块)之后注入代码,并且由于它在方法调用的作用域内,因此“this”引用缺失是意外的。

非常感谢您提前的关注!

1 答案

0

清除引用(可能为长的序列链)很重要,因此这不是一个不太可能改变的事情。

我不明白清除this引用如何防止您注入一个finally块。

by
感谢您的快速回复!

或许我可以通过我们的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。
by
清除这些内容有很好的理由,这已在票据中说明(在某种情况下可防止OOME),因此正如我所说,这不是Clojure会停止做的事情。可能的一件事是添加一个编译器标志(如https://clojure.org/reference/compilation#_compiler_options中的那些标志)以禁用此清除。
by
多谢,正如在https://stackoverflow.com/a/22175055中讨论的那样,似乎没有防止字节码在方法体内覆盖局部变量 0 的内容,所以我认为目前Clojure并没有做错什么。

可以说,编译器标志很不错,因为它确实为字节码修改库在某些情况下与Clojure一起工作提供了一个选项。

至于我们,如果我们需要在函数退出时访问它,我们总是可以使用在函数入口处存储this引用的旁路方法。
...