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本身可以抛出(在空锁定对象上)。Java字节码对这些情况做了一些复杂的异常表处理,这在没有修改Clojure编译器的情况下是不可能做到的。

方法:一种可能的方法是让锁定在Java的synchronized块上下文中调用传入的主体。这避免了由Java处理字节码问题,但有未知的性能影响。

修复:clj-1472-3.patch

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

审核人:Alex Miller - 我认为这是一种可行的解决方案,可以解决这个问题,由于其使用频率不高,我并不担心它是性能问题。我将标记这一点,我认为另一种处理方式是将 锁定 制作成具有编译器支持的特特殊形式,但我不确定这是否值得做,所以我把这个问题留给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 exit周围的额外try/catch是可选的(带或不带它都可以通过验证),但是在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),这稍微有些欺骗性质 - synchronized块实际上是在Java中实现的,从而保证了兼容性。这是以牺牲一点额外的间接跳跃为代价的,并且名称/位置也许可以更好地。

但这确实解决了bug,并在所有版本的Android上、Android + ART和JVM上工作。这种方法会被接受吗?

0

评论人:hiredman

我还没有看到任何证据表明Clojure生成的字节码以某种方式违反了JVM规范,所以我怀疑问题在于Clojure需要JVM来运行,而Android不提供JVM,只是在你不走寻常路的情况下看起来像。

0

评论人:hiredman

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

0

评论由:adamclements

是的,第一个补丁肯定是不正确的,我留下它是为了提供对话的上下文,但为了清晰起见,最好还是去掉它。

对于不愿意解编译和观察字节码但想关注此次对话的人来说,这里有一个Gist,其中包含了Java同步块与Clojure锁定之间的差异https://gist.github.com/AdamClements/2ae6c4919964b71eb470

虽然我也很难确定违规发生的位置,但我可以看出Java版本的差异;如果有什么的话,Clojure版本看起来比Java版本更接近规范中所描述的内容!

如果有人在Android开源项目(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或监视器进入失败,它能在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#c6是由与https://code.google.com/p/android/issues/detail?id=80823#c3相同的开发者提出的,所以我有些怀疑存在某种误解。不清楚开发者是在什么背景下回复的,因为在前一个评论中你提到了在方法间分割monitor-enter和exit。如果删除所有补丁、专门的clojure android构建等,事情会变得清楚许多,然后使用vanilla clojure jar获取无法通过验证的Java源代码的javap转储,然后只需将其带到Android问题跟踪器并询问“嘿,这为什么会验证失败?”

0
回答

评论由:adamclements

是的,我不应该将它与修改过的版本混淆。不过现在,源和当前上传的版本都使用vanilla clojure的锁定宏版本。

我认为问题来自异常表和覆盖的指令。如果第24行可以抛出异常,那么在运行时,如果没有遇到过monitor-enter,你会遇到monitor-exit。

0
回答

评论人:gshayban

据马克思·拉格伦说,JRockit和Hotspot都考虑了锁释放位于不同的方法中...也许Android有不同(错误)的解释?

...