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

这对于清除引用(可能到长的seq链)很重要,所以这不太可能改变。

我不明白清除this引用是如何阻止您注入finally块。

谢谢您的快速回复!

也许我可以通过一个来自我们实验仪器对Clojure cloout的真实示例来解释

特别是我们试图向这个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                   // 方法 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基本上只重复注入相同字节数据两次(在instr 105之后,异常表的指针指向105之后的语句)。但是由于null赋值,`this`在这个点上就已经丢失。
by
清除这些内容有充分的理由,这在票据中已经解释过(在某种情况下可以防止JVM内存溢出)。所以我刚才说过,Clojure不会停止做这件事。可能的一项事情是增加一个编译器标志(就像https://clojure.org/reference/compilation#_compiler_options中的其他标志一样),以禁用这种清除。
by
谢谢,就像在https://stackoverflow.com/a/22175055中讨论的那样,似乎没有阻止字节数据覆盖方法体内局部变量0的可能性,因此我认为clojure目前没有做错什么。

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

至于我们,如果我们需要在函数退出时访问它,我们总是可以在函数进入时将`this`引用存储为工作区。
...