请在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 的同步块上下文中调用传递的体。这种方法通过交给 Java 来避免复杂字节码的问题,但性能影响是未知的。

补丁: 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的字节码验证,围绕monitor退出额外添加的try/catch是可选的(验证无论是带或不带它都通过),因为在java版本中,retry monitor-exit会无限期重试并在正确的时间显示死锁,如果没有在monitor-exit捕获到错误,则在未来某一时刻可能发生的未确定的monitor-enter可能会失败,无法显示真正的错误。

这并不美观,但没有更细粒度控制生成的字节码,这已经是我能做到的最好的解决方案。

0

评论者:adamclements

刚在Lollipop上进行测试,这个补丁可能不再足够。

将联系ART团队,看看他们是否能提供更多信息,并验证它是否能在AOSP当前主分支上工作

0

评论者:adamclements

已在AOSP项目中提交了错误,希望他们能提供有关这是否是我们的问题的线索,如果是,我们将如何修复它。

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

0

评论者:adamclements

我已经上传了一个锁定宏的替代实现(0001-CLJ-1472-Locking-macro-without-explicit-monitor-ente.patch)——有点欺诈——实际上同步块是在Java中实现的,这样保证了兼容性。这是以牺牲一小部分额外间接表示为代价的,而且命名/位置也许可以更好。

但这确实解决了错误,并在所有版本的Android,Android + Art和JVM上工作。这种方法可以接受吗?

0

评论者:hiredman

我还没有看到任何证据表明Clojure生成的字节码以某种方式违反了JVM规范,所以我怀疑的问题是Clojure需要JVM来运行,而Android只是提供了类似JVM的东西,只要你不走出通常的路径。

0

评论者:hiredman

鉴于《Java虚拟机规范》中关于结构化锁言语的说明(《https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.11.10》),(locking nil)可能生成会违反结构化锁的运行时行为的字节码。这个问题的第一个补丁会导致编译器在不同的方法中发出monitorenter/exit指令,这显然违反了结构化锁。

0

评论者:adamclements

是的,第一个补丁肯定是不对的,我留了一些上下文给对话,但为了清晰起见,最好还是直接删除它。

对于任何不希望反编译和观察字节码的跟帖者,这里有一个关于 Java 同步块与 Clojure 锁定区别的 gist:[链接](https://gist.github.com/AdamClements/2ae6c4919964b71eb470)

我也很难确定与规范偏差在哪里出现,尽管我可以看到与 Java 版本的差异,但 Clojure 版本似乎比 Java 版本更接近规范中所描述的!

如果有人在这方面比我更了解,并且能在 AOSP bug [链接](https://code.google.com/p/android/issues/detail?id=80823) 上进行讨论,那么也许我们可以将这个问题作为一个 Android bug 解决,它更多的是关注 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) 或.nextLine上的monitor-enter本身失败,它会不会最终进入finally子句并尝试释放锁,尽管它从未被捕获过?

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

0

评论者:hiredman

0

评论者:adamclements

0

评论者:gshayban

...