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,因为它在编译时验证失败(在 /data/app/com.clojure_on_android-1/base.apk 中声明了 'clojure.core.server$stop_server')

原因: 从 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》)。

似乎monitor-enter、monitor-exit和lock代码中的try/finally块的组合创建了ART标记为不平衡monitorenter/monitorexit字节码的路径。特别是,monitorenter和monitorexit本身可以抛出(在null锁定对象上)。Java字节码执行一些非常技巧性的异常表处理来覆盖这些情况,这些情况(afaict)如果不修改Clojure编译器,就不可能做到。

方法:一个可能的方法是将锁定让在Java同步块中调用传入的主体。这通过将问题交给Java来避免复杂的字节码,但具有未知的影响。

补丁:clj-1472-3.patch

另请参阅:将字节码与java同步块进行比较,可以发现许多不同之处
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-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,只是如果你不偏离正道,它看起来像是JVM。

0

评论者:hiredman

根据JVM规范中关于结构化锁 menstruation 的内容(《java/se7/html/jvms-2.html#jvms-2.11.10》)(locking nil)可能生成违反结构化锁行为的字节码。这个问题上的第一个补丁会使得编译器在方法中发出monitorenter/exit指令,这将肯定违反结构化锁。

0

由:adamclements发表的评论

是的,第一个补丁绝对是错误的,我把它留下是为了与对话的上下文相对应,但为了清晰起见,最好还是把它去掉。

对于想跟踪这笔谈话但又不想反编译和观察字节码的人来说,这里有一个包含 Java synchronized 块和 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 执行的监视器进入次数。"

"T 在方法调用上对 M 执行的监视器退出次数不得超过在该方法调用上由 T 对 M 执行的监视器进入次数。"
如果例如指令 https://gist.github.com/AdamClements/2ae6c4919964b71eb470#file-test_locks-class-L24 或下一行的 monitor-enter 本身失败,会不会它最终会出现在 finally 子句中并尝试释放锁,即使它从未捕获过?
我认为这违反了你链接的 JVM 规范中的结构化锁定规则。

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),静态字节码拥有相同数量的进入和退出,但动态行为可能不同。我想知道艺术家人物在验证时声称要执行哪一个(似乎应该是静态字节码,而不是动态属性,但是这样他们通常不应该验证失败)。查看谷歌代码问题,评论 https://code.google.com/p/android/issues/detail?id=80823#c6https://code.google.com/p/android/issues/detail?id=80823#c3 都是由同一个开发者做出的,所以我怀疑有些沟通出了问题。由于在之前的评论中提到了将monitor-enter和退出分跨在不同方法上,不清楚开发者是在什么背景下回复的。如果所有的补丁、特定的Clojure Android构建等等都去掉了,那么在获取普通的Clojure jar后,得到的是验证失败的字节码转储,然后只需将其提交到Android问题跟踪器,问“嘿,这个验证失败,为什么?”

0

由:adamclements发表的评论

是的,我不应该把它与修补版本混淆。现在gist和目前上传的版本使用的是普通Clojure的锁定宏。

我想这个问题源于异常表以及覆盖指令。如果第24行可以抛出异常,那么运行时,你可能会在一个没有遇到monitor-enter的地方遇到monitor-exit。

0

评论人:gshayban

据Marcus Lagergren所说,JRockit和Hotspot都考虑了锁定释放在同一方法中进行的情况……那么Android可能有一个不同的(错误的)解释?

...