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)。

似乎monitor-enter、monitor-exit和locking中的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 - 我认为这是一个可行的方案,可以解决这个问题,由于使用频率不高,我并不担心这会是一个性能问题。我将会标记这个问题,我认为另一种处理方法是让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环绕监视器退出是可选的(有或没有验证都通过),但在Java版本会无限期重试监视器退出并显示死锁的正确时间,不捕获监视器退出错误,未来的某个不确定的监视器进入可能会失败,不会显示实际的错误。

这不美观,但如果没有更精细地控制生成的字节码,这就是我能做得最好。

0

评论由:adamclements 发表

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

正在联系ART团队,看看他们是否能提供更多帮助并验证它是否能在AOSP当前主分支上运行。

0

评论由:adamclements 发表

已在AOSP项目中提交bug,希望他们能说明这是否是我们的问题,如果是,我们该如何修复它。

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,只是如果用户不越界,它看起来像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 发表

是的,第一个补丁确实是错误的,我留下它以供讨论,但为了清晰起见,最好是直接删除它。

对于那些想跟踪此次讨论却不想反编译和观察字节码的人来说,这里有关于 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 解决,这个 bug 过于专注于 Java 实现,而不是 JVM 规范,或者他们可能会发现 Clojure 实现中的某个错误。我已经上传了原始的 Clojure 行为,并要求他们进一步解释为什么它失败了。

0

评论由:adamclements 发表

ART 团队关于我们违反了什么的回复是:

"结构化加锁"部分的如下内容:

"(链接: ...) 实现 (链接: ...) 被允许但不要求执行以下两个保证结构化加锁的规则。 (链接: ...)"
ART 目前在验证时间强制执行这两个规则,包括:

"在方法调用过程中,方法调用者 T 在 M 上的监视器退出次数不能超过自方法调用以来在 M 上执行的监视器进入次数。"

"方法调用期间,T 在 M 上执行的监视器退出次数不能超过自方法调用以来在 M 上执行的监视器进入次数。"
如果例如指令 https://gist.github.com/AdamClements/2ae6c4919964b71eb470#file-test_locks-class-L24 或下一行的 monitor-enter 本身失败,它不是会导致最终块中尝试释放锁,即使它从未被捕获?
我认为这违反了您链接的 JVM 规范中的结构化加锁规则。

0

评论由:adamclements 发表

我实际上没有在 JVM 规范中找到对应“any thread status”的部分。这可能意味着规范中并没有涵盖所有此类情况。

我不确定这种情况是否应该被视为“未捕获”的退出,或者我们是否应该考虑它为某种类型的异常路径。

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构建等都已移除,并且使用标准的Clojure jar获取Javap转储以确定哪些内容无法验证,然后再将其提交给Android问题追踪器并问“嘿,这个验证失败,为什么?”会澄清很多。

0
by

评论由:adamclements 发表

是的,我不应该将它与修补过的版本混淆。现在gist和最近的版本都使用标准的Clojure版本中的锁定宏。

我认为问题来自异常表和覆盖的指令。如果第24行可以抛出异常,那么在运行时,你将面临一个monitor-exit,但你从未遇到过monitor-enter。

0
by

评论者:gshayban

根据Marcus Lagergren JRockit和Hotspot都认为锁释放位于不同的方法中。也许Android有不同的(错误的)解释?

...