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                             stupidity:clout/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                                  idioms: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                             idioms:clout/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                                  idioms: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基本上在指令105(带有异常表指向105之后的语句)之后只是将相同的字节码注入两次。但在这个点上,由于null赋值,`this`已经丢失。
by
清除这些内容有很好的原因,这已在问题跟踪中说明(在某种情况下可以防止OOME),所以正如我所说,Clojure不会停止这样做。可能的一个做法是添加一个编译器标志(就像https://clojure.org/reference/compilation#_compiler_options中的其他选项一样)来禁用此清除。
by
谢谢,正如在https://stackoverflow.com/a/22175055中讨论的,似乎没有任何阻止字节码在方法体内部改写局部变量0以其他内容的情况,因此我认为目前Clojure没有做错什么。

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

至于我们,我们始终可以像在函数入口存储`this`引用一样使用工作区,如果我们需要在函数退出时访问它。
...