请在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的synchronized块中调用传递的body,以此在锁定上下文中调用。这种方法避免了困难字节码的问题,但不知道其性能影响。

补丁:clj-1472-3.patch

另请参阅:将字节码与Java synchronized块的比较显示出许多不同的地方
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

筛选:由Alex Miller筛选 - 我认为这是一个可行的解决方案,可以修复问题。由于其使用频率不高,我认为这不太可能成为性能问题。我将标记我认为另一种处理方法是使locking成为一个特殊形式并有编译器支持,但我不确定这是否值得去做,所以我将把它留给Rich来决定。

已关闭并标记为: 已修复

41 条回答

0
 
最佳回答

此问题的修复已于1.10.2-alpha1版本中发布。

0

评论者:adamclements

使用了一段时间后,我发现将这个(代码/逻辑)移出try块会导致nREPL崩溃。

查看字节码,clojure.tools.nrepl.middleware.session/session-out中的锁的monitorenter和一些其他地方最终在完全不同的方法定义中结束,我们现在得到了JVM IllegalMonitorStateException以及ART验证错误。

0

评论者:jafingerhut

Adam,我无法评论你的补丁是否有价值,但确实如此,如果作者没有签署贡献协议,Clojure就不会提交补丁,现在可以在https://clojure.org/contributing上在线完成

0

评论者:adamclements

上传了一个新的补丁(并签署了贡献协议)。这个补丁通过了JVM和ART的字节码验证,额外的try/catch在monitor退出周围是可选项的(验证在有或没有它的情况下都通过),但是java版本会在monitor-exit处无限期重试monitor-exit,在正确的时间显示死锁,而没有捕获monitor-exit的错误可能会在未来的某个时刻导致未确定的monitor-enter失败,没有显示出实际的错误。

这不太好,但如果没有更细粒度的控制生成的字节码,这是我能做到的最好办法。

0

评论者:adamclements

刚测试了Lollipop,这个补丁可能不再足够了。

联系ART团队,看看他们是否可以提供更多见解,以验证是否能在AOSP当前主分支上工作

0
by

评论者:adamclements

已在AOSP项目中提交了错误,希望他们可以揭示这是我们的问题,如果是,我们将如何修复它。

https://code.google.com/p/android/issues/detail?id=80823

0
by

评论者:adamclements

我上传了锁定宏的替代实现(0001-CLJ-1472-Locking-macro-without-explicit-monitor-ente.patch),稍微有些作弊——实际上同步块是用Java实现的,这样可以保证兼容性。这以额外的间接调用为代价,命名/位置可能可以更好。

但这确实解决了bug,并且在所有版本的android、android + art和jvm上都可以正常工作。这种方案能让人接受吗?

0
by

评论者:hiredman

我还没有看到任何证据表明,clojure生成的字节码以某种方式违反了jvm规范,所以我怀疑clojure需要运行在jvm上,而android并没有提供jvm,只是如果你不远离既定路径,它看起来像是一个jvm。

0
by

评论者:hiredman

根据https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.11.10中关于结构化锁定的规范用语,(locking nil)可能生成违反结构化锁定的运行时行为的字节码。此问题的第一个补丁可能导致编译器在不同方法中发出monitorenter/exit指令,这肯定违反了结构化锁定

0
by

评论者:adamclements

是的,第一个补丁确实是有问题的,我留它在这里是为了给对话提供一些上下文,但它最好还是为了清晰起见移除。

对于关注这次对话但不希望反汇编和观察字节的任何人,这里有关于 Java 同步块和 Clojure 锁定的差异的 gist:https://gist.github.com/AdamClements/2ae6c4919964b71eb470

虽然我可以看到 Java 版本的差异,但我仍然难以找出与规范偏差的地方,尽管 Clojure 版本看起来比 Java 版本更接近规范中的描述!

如果有人比我更了解这个主题,并且能够参与到 AOSP 漏洞 https://code.google.com/p/android/issues/detail?id=80823 中,那么也许我们可以将其作为一个 Android 漏洞来解决,它太过关注 Java 实现,而不是 JVM 规范,也许他们会找到 Clojure 实现中的错误。我已经上传了原始的 Clojure 行为,并要求他们解释为什么它失败了。

0

评论者:adamclements

ART 团队关于他们认为我们违反的内容的响应是

“结构化锁定”部分包含以下内容

“(链接:...)实现(链接:...)允许但不是必须强制执行以下两个规则以保证结构化锁定。(链接:...)”
ART 目前在验证时执行这两个规则,包括

“在方法调用过程中,T 在 M 上执行的监控退出数不能超过自方法调用以来 T 在 M 上执行的监控进入数。”

...
...
...

0

评论者:adamclements

例如,如果指令 https://gist.github.com/AdamClements/2ae6c4919964b71eb470#file-test_locks-class-L24 或下一行的 monitor-enter 本身失败,会不会最终导致在 finally 子句中尝试释放锁,尽管它从未被捕获呢?

我认为这违反了您链接的 JVM 规范中的结构化锁定规则。

0

评论者:hiredman

关于结构化锁的一个有趣问题:该规范是参照静态字节码,还是字节码的运行时行为。给定的字节码链接(《https://gist.github.com/AdamClements/2ae6c4919964b71eb470#file-test_locks-class-L24》)中,静态字节码具有相同数量的入口和出口,但动态行为可能不同。我想知道艺术团队在验证时宣称的是什么(似乎应该是静态字节码,而不是动态属性,但如果这样,他们就不应该验证失败)。查看Google代码问题,评论(《https://code.google.com/p/android/issues/detail?id=80823#c6》)是由与(《https://code.google.com/p/android/issues/detail?id=80823#c3》)相同的开发者所写,因此我怀疑存在一些误解。由于在先前的评论中提到了在方法之间拆分monitor-enter和exit,因此不清楚开发者是在哪个语境中回复的。如果删除所有补丁、专门化的Clojure Android构建等,事情将会更加清晰,然后在纯Clojure jar中获取失败的验证的javap转储,然后将它带到Android问题跟踪器,询问“嘿,这失败了验证,为什么?”

0

评论者:adamclements

是的,我不应该把它与补丁版本混淆。现在gist和当前上传的版本都使用纯Clojure的锁定宏。

我认为问题出在异常表和覆盖指令上。如果第24行可以抛出异常,那么在运行时将会有一个monitor-exit,而从未遇到monitor-enter。

0

评论者:gshayban

根据Marcus Lagergren的说法,JRockit和Hotspot都考虑了锁释放在不同方法中。也许Android有一个不同的(错误的)解释?

...