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块。

谢谢您的快速回复!

也许我可以通过从我们的实验性仪器中提取的实际示例来解释这个示例

具体来说,我们试图向这个 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;
>> 指令 114 存在问题,因为 aload_0 不是 `this` 而是null
       114: aload_0
       115: invokevirtual #491                 // 方法 toString:()Ljava/lang/String;
       118: invokevirtual #496                 // 方法 java/io/PrintStream.println:(Ljava/lang/String;)V
       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 对调用帧的 `this` 引用。

正如这个案例,当我们使用字节码注入库时,将局部变量 0 更改为 null 会导致在方法体末尾注入代码时出现问题。

用finally块注入代码与不注入没有区别,因为javasisst基本只在指令105之后(异常表指向105之后的语句)重复注入相同的字节码两次。但由于null赋值,`this`在这个点上已经丢失。
by
清除这些内容有充分的理由,这已经在工单中说明(可以防止某些情况下的OOME),所以,正如我所说的,Clojure不会停止这样做。可能的一个做法是添加一个编译器标志(类似于https://clojure.org/reference/compilation#_compiler_options中的其他标志)来禁用此清除。
by
谢谢,正如在https://stackoverflow.com/a/22175055 中讨论的那样,似乎没有任何东西阻止字节码在方法体内部覆盖局部变量0。所以我认为现在Clojure没有做错什么。

我认为编译器标志很好,因为它确实为字节码修改库在某些情况下正确与Clojure一起工作提供了选项。

对于我们来说,如果我们需要在函数退出时访问它,我们总是可以像在函数入口处存储`this`引用这样的做法。
...