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: 拒绝类 clojure.core.server$stop_server 因为它在编译时验证失败('clojure.core.server$stop_server' 的声明出现在 /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字节码对某些异常表处理进行了一些技巧性的调整来覆盖这些情况,而(据知)这些调整在没有修改Clojure编译器的情况下是无法实现的。

方法:一个可能的方法是在Java的同步块中执行锁定传递的body。这通过将问题交给Java而不是字节码来解决,但具有不可知的影响。

补丁:clj-1472-3.patch

另请参阅:将字节码与Java同步块进行比较揭示了几个差异
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

审核者: Alex Miller - 我认为这是一个可行的方案,可以解决这个问题,并且由于使用频率不高,我并不担心它会导致性能问题。我将标记为已审核。我认为另一种处理方式是将锁定作为一种特殊形式,并得到编译器的支持,但我不确定这是否值得做,所以我会留由Rich来决定。

已关闭,备注:已修复

41 个答案

0

评论人:alexmiller

我添加了一个新的clj-1472.patch,修复了我对之前补丁中的一些小细节不满意的地方。然而,它基本上仍然是同样的更改,因此我保留了原始作者归属。

0

评论人:gshayban

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

0

评论人:alexmiller

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

0

评论人:bronsa

Alex,将这变成一个^:once fn**可能更有价值。

0

评论人:gshayban

来自Android
{quote}
Android不运行Java字节码,它运行Dex字节码。你的类翻译成的是什么,dexdump输出很有趣。

jagیموس(Java虚拟机规范)也并不有趣。Android不是Java虚拟机。我们遵循JLS(Java语言规范),但不遵循JVMS(我们根本不运行Java字节码,我们怎么可能呢)。因此,所有针对它的上诉都是无关紧要的。我们 try 在Dex字节码方面与 JVMS 的精神保持兼容,但如果你不是Java源码,那 没有保证

现在,验证器(也可能是现在)是有缺陷的,甚至是针对我们(相当糟糕)的规范,遗憾的是,我们并不非常一致。例如,在Marshmallow中,大量我们无法正确验证关于结构化锁定代码被拒绝为VerifyError(这不符合 JVMS 的精神),这是一个不可接受的事实。然而,在下一个版本中,这将得到放宽,并推迟到代码执行时进行检查。

遗憾的是,对于旧版本,我们无力回天,你必须自己解决任何问题。 :-( 我会尽量在有时间的条件下看看你的类。
{quote}

听起来在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 产生的字节码包含一个 store 和一个 load,在异常处理中未被揭示,因此不能 simply 在 monitor-exit 代码中添加异常处理。我相当肯定我们不能删除 store 和 load。

因此,除非我漏掉了什么,否则通过结构化锁定检查的唯一方法是将锁定转换为特殊形式。

0

评论者:adamclements

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

在2014年进行了大量的测试和生成的字节码比较后,这是我发现的唯一可行的方案,该方案不需要创建一个新的特殊形式来重新实现文档记录不佳的规范,以精确地生成正确的字节码,这在我看来似乎很脆弱。

我想看看一些性能分析数据,以了解业务影响有多大,尤其是在clojure中实际上很少使用锁定宏的情况下(我在整个生态系统中仅找到了几个示例。大多数人都使用其他并发原语或Java互操作性)

0

评论人:alexmiller

-3 patch рефакторинг на master без семантических изменений, сохранена атрибуция

...