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

欢迎!请查看关于页面以获取更多关于其工作方式的信息。

0
编译器

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

这可能是由于代码更改引起的,具体是https://github.com/clojure/clojure/blame/b19b781b1f0f3f46aee5e951f415e0456a39cbcb/src/jvm/clojure/lang/Compiler.java#L5946,该代码在方法退出之前从生成的字节码中删除了“this”引用。

这对Java代理的仪器化来说是个问题,因为我们可以在返回语句之后(通常作为finally块)注入代码,但“this”引用缺失是在方法调用范围内的,因此这令人意外

非常感谢您的提前关注!

1 答案

0

在清除引用(可能是长序列链)时,这是很重要的,因此这不会轻易发生变化。

我不明白清除“this”引用是如何阻止你注入一个finally块的。

谢谢您的快速回复!

或许我可以从我们关于 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                // 字段 const__37:Lclojure/lang/Var; #'clout.core/assoc-keys-with-groups
        83: invokevirtual #120                // 方法 clojure/lang/Var.getRawRoot:()Ljava/lang/Object;
        86: checkcast     #122                       // 类 clojure/lang/IFn
        89: aload_3
        90: aload_0
        91: getfield     #44                           // 字段 keys:Ljava/lang/Object;
        94: aconst_null
        95: astore_0
        96: invokeinterface #133,  3                       // 接口方法 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                // 字段 const__37:Lclojure/lang/Var; #'clout.core/assoc-keys-with-groups
        83: invokevirtual #120                // 方法 clojure/lang/Var.getRawRoot:()Ljava/lang/Object;
        86: checkcast     #122                       // 类 clojure/lang/IFn
        89: aload_3
        90: aload_0
        91: getfield     #44                           // 字段 keys:Ljava/lang/Object;
        94: aconst_null
        95: astore_0
        96: invokeinterface #133,  3                       // 接口方法 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                          // 字段 java/lang/System.out:Ljava/io/PrintStream;
>> inst 114 有问题,因为 aload_0 不是 `this` 而是null
       114: aload_0
       115: invokevirtual #491                                                                                    ( [?
       118: invokevirtual #496                   ( [?@
       121: aload         5
>> 完成代码注入
       123: areturn

我们可以看到,在指令 114 处失败,因为 aload_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 作为 call frame 的 `this` 是可取的?

就像这个情况,当我们使用 bytecode 注入库时,在方法体末尾注入代码将导致由于将局部变量 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` 引用的方法作为解决方案。
...