请分享您的想法,请参与 2024 年 Clojure 国家调查!

欢迎!请参阅 关于 页面以获取有关此功能的更多信息。

+52
Clojure
关闭

Android ART 在字节码上运行编译时验证,并在锁定宏的任何使用中失败。错误看起来与 CLJ-1829 中的类似(在这种情况下,clojure.core.server/stop-server 调用了锁定宏)

10-16 14:49:26.801 2008-2008/? E/AndroidRuntime: java.lang.VerifyError: Rejecting class clojure.core.server$stop_server because it failed compile-time verification (declaration of 'clojure.core.server$stop_server' appears in /data/app/com.clojure_on_android-1/base.apk)

原因: 根据对 Android 问题讨论的讨论(https://code.google.com/p/android/issues/detail?id=80823),这似乎是由于更严格地执行 JVM 规范中的“结构锁定”规定(https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.11.10)。

似乎在“锁定”(locking)中使用 monitor-enter、monitor-exit 和 try/finally 块的组合会产生 ART 标记为不平衡 monitorenter/monitorexit 字节码的路径。特别是,monitorenter 和 monitorexit 本身可以抛出(在 null 锁定对象上)。Java 字节码对这些情况做了一些技巧的な异常表处理,但这些(afaict)不可能在不修改 Clojure 编译器的情况下完成。

方法: 一种可能的方法是在 Java 的同步块中调用传入的体。这避免了调料字码的问题,但是性能影响未知。

补丁: clj-1472-3.patch

另请参阅: 将字节码与 java 同步块比较显示了一些差异
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

审查者: Alex Miller - 我将此问题标记为“已审查”,因为我认为这是一种可行的解决方案,并且由于它很少使用,我并不真的关心它是性能问题。我认为另一种处理方法是将 locking 检查为特殊形式并用编译器支持,但我不确定是否值得这么做,所以我会把它留给 Rich 决定。

解决方案已定,请参阅备注: fixed

41 个回答

0

评论由:alexmiller 添加

我添加了一个新的clj-1472.patch来修复一些关于先前补丁我不喜欢的细节。然而,它仍然基本上是同样的更改,所以我保留了原始作者的归属。

0

评论由:gshayban 添加

alexs,你上传了正确的补丁吗?

0

评论由:alexmiller 添加

啊,我搞错了。我会修复的。

0

评论由:bronsa 添加

Alex,可能值得将它改为^:once fn**

0

评论由:gshayban 添加

来自Android
{引用}
Android不运行Java字节码,它运行Dex字节码。您的类转换成的dexdump输出很有趣。

JVMS也不有趣。Android不是Java虚拟机。我们遵循JLS,但不是JVMS(我们怎么可能运行Java字节码)。因此,所有针对它的上诉都是无关的。我们尝试与Dex字节码的JVMS精神兼容,但如果您的源代码不是Java,则没有任何保证。

现在,验证器之前(可能现在仍然是)是错误的,甚至违背了我们的(相当差的)规范,并且遗憾的是,我们的执行并不一致。例如,在Marshmallow中,大量我们不能正确验证关于结构锁定的问题代码被拒绝为VerifyError,这与JVMS的精神不符。然而,在下一次发布中,这将得到缓解,并将其推迟到实际执行代码时进行检查。

很遗憾,我们无能为力解决旧版本的问题,您将不得不解决任何问题。:-(\n当我找到时间时,我会试着看看您的类。
{引用}

听起来在Clojure中制作一个变通方案是所有方案中最不糟糕的。

0

评论由:alexmiller 添加

使用了带有^:once的-2补丁。

0

评论由:alexmiller 添加

我们目前认为这是Android的过错,所以现在暂时将这个问题归档。

0

评论由:gshayban 添加

GraalVM/native-image也抱怨关于monitorenter/exit平衡的问题。通过模仿javac的做法来解决:https://github.com/ghadishayban/clojure/commit/8acb995853761bc48b62190fe7005b70da692510

0

评论由:alexmiller 添加

Ghadi,如果这是一个可行的解决方案,我对此补丁感兴趣。

0

评论由:gshayban 添加

如果有人可以帮助我测试Android测试基础设施,我会很高兴。

在javac发出synchronized时,它安装了一个捕获处理器,异常类型为"任何"。相关的提交捕获Exception。如果需要,我们可以通过调用GeneratorAdapter.visitTryCatch,并将null作为目标类来发出any处理器(我不知道这究竟意味着什么--Throwable加上未来的其他任何东西?)

0

评论者:adamclements

您是否试过应用现有的clj-1472-2.patch来查看是否能修复GraalVM?我认为我们最初达成的共识是人们对于附加的补丁表示满意(亚历克斯,如果我说错了请纠正我),但是后来决定JVM实现是王者之言,因此问题出在Android没有符合该规范,而不是Clojure本身。如果同样的问题在其他地方再次出现(我仍然想看到这个补丁被应用),那么这个决定可能会重新考虑。

这已经是很久以前的事情了,但我记得我尝试了所有我能想到的不同组合的clojure try/catch/finally/throw,但无法使生成的字节码符合同步的规范,而无需更改clojure的代码生成,而这太过深入,难以实际应用——因此有了上面的补丁,它依赖于javac生成围绕同步的字节码,而不是使用monitor-enter和monitor-exit。我对您链接的实现表示担忧,它可能会在某种情况下生成错误的字节码,尽管它在Clojure代码级别看起来完美无缺,并且显然是正确的。

0

评论由:gshayban 添加

我没有尝试过,但我坚信从可行性角度来看,clj-1472-2.patch是可行的。但有一个性能劣势:它可能会从JVM的角度造成“性能分析污染”,并混淆锁优化。(大多数锁是无竞争的)

如果对Android有效,我更希望修复输出,使其更像javac。

0

评论者:eraserhd

我尝试了Ghadi提供的diff,但存在一些问题(包括丢弃受保护表达式的值)。我修复了这个问题,但在研究后,我意识到
1. 这不涵盖由monitor-exit本身抛出的异常,这似乎是必需的。我们无法在catch中递归,也无法捕获'any',因此仅使用Clojure构造来生成这种字节码是不够的。
Clojure的finally生成的字节码包含存储和加载操作,这些操作在异常处理中未被发现,因此我们无法仅在monitor-exit代码中添加异常处理。我相信我们无法移除存储和加载操作。

所以,除非我漏掉了什么,否则通过结构化锁定检查的唯一方法是使锁定成为一个特殊形式。

0

评论者:adamclements

已附加的clj-1472-2.patch通过了结构化锁定测试。

在2014年进行大量测试和比较生成的字节码后,这是我找到的唯一可行的方案,该方案不需要指令新特殊形式,该特殊形式重新实现了不记名的规范,以精确地发出正确的字节码,这对我觉得是脆弱的。

我很好奇能看一些性能分析数据,以便了解这项声明性能影响的大小,尤其是在Clojure中锁宏的实际使用极为罕见的情况下(我在整个生态系统中仅找到几个实例。大多数人使用其他并发原语或Java互操作)

0

评论由:alexmiller 添加

-3 修补程序合并到主分支,没有语义更改,保留了归属

...