请分享您的想法,参与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自身可以抛出异常(对null锁定对象)。Java字节码对此类情况进行了一些复杂的异常表处理,但这似乎需要在修改Clojure编译器的情况下才能做到。

方法:一种可能的方法是在Java的同步块中调用传递的body。这通过将问题交给Java来避免复杂的字节码,但会有未知的影响。

补丁:clj-1472-3.patch

另请参阅:将字节码与java synchronized块进行比较,会发现许多不同之处
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

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

已关闭并备注: 已修复

41 个回答

0

评论者:alexmiller

我添加了一个新的clj-1472.patch,解决了之前补丁中一些我不喜欢的细节。然而,这基本上仍然是同样的更改,因此我保留了原始作者的归属。

0

评论者:gshayban

alexprompt 你上传了对的补丁吗?

0

评论者:alexmiller

啊,搞错了。我会修复的。

0

评论者:bronsa

Alex,你可能值得将其改为^:once fn**

0

评论者:gshayban

来自Android
{quote}
Android不运行Java字节码,它运行Dex字节码。你的类转换成的dexdump输出很有意思。

对JVMS也没有什么兴趣。Android不是Java虚拟机。我们遵循JLS,但不遵循JVMS(我们怎么可能运行Java字节码)。因此,所有对它的上诉都是无关的。我们努力在Dex字节码方面与JVMS的精神兼容,但如果你的源代码不是Java,则没有保障。

现在,验证器已经(可能还是)坏了,甚至不符合我们(相当糟糕)的规范,不幸的是,我们并不十分一致。例如,在Marshmallow中,许多我们无法正确验证的结构化锁定代码被拒绝为VerifyError,这与JVMS的精神不符。然而,在下一个版本中,这将得到缓解,并且将推迟到代码运行时进行实际检查。

很遗憾,关于旧版本,我们无法进行任何操作,您必须自己解决遇到的问题。:-( 我会尽量在找到时间时查看您的类。
{quote}

听起来在Clojure中创建一个解决方案是所有方法中最不坏的选择。

0

评论者:alexmiller

已添加带有 ^:once 的 -2 补丁。

0

评论者:alexmiller

我们目前认为问题是出在Android上,因此目前将这个问题暂时归档。

0

评论者:gshayban

GraalVM/native-image 也对 monitorenter/exit 平衡表示不满。我通过模仿 javac 的方法解决了它:[链接](https://github.com/ghadishayban/clojure/commit/8acb995853761bc48b62190fe7005b70da692510)

0

评论者:alexmiller

Ghadi,如果这是一个可行的解决方案,我对修正它的补丁感兴趣。

0

评论者:gshayban

如果有人能帮助我进行 Android 测试基础设施,我将非常乐意。

javac 输出 synchronized 时,它会安装一个异常类型为 "any" 的 catch 处理器。链接的提交会捕获 Exception。如果需要,我们可以通过调用 GeneratorAdapter.visitTryCatch 并将 null 作为目标类来输出 any 处理器(我还不知道这究竟代表什么 -- Throwable 加未来可能的其他任何东西?)。

0

评论者:adamclements

您尝试过应用现有的clj-1472-2.patch来查看是否修复了GraalVM吗?我认为我们原本已经到达了一个人们对附加补丁表示满意的状态(如果我有误,请指正,阿里克斯),但是最终决定JVM实现是国王,所以问题出在Android没有遵守这一点,而不是Clojure。这或许意味着如果同样的问题在其他地方再次出现,这个决定可能需要重新考虑(我仍然希望亲自看到这个补丁被应用)。

这是一段很早以前的事情了,但我记得尝试过所有可能想到的clojure try/catch/finally/throw组合,但无法使生成的字节码符合同步规范,而不改变clojure的代码生成,这离实用太远了——因此才有上面的补丁,该补丁依赖于javac在同步周围生成字节码,而不是使用monitor-enter和monitor-exit。 我对你的链接实现有点担心,它可能在某些情况下仍然生成错误的字节码,尽管它在Clojure代码层面看起来完美无瑕且显然正确。

0

评论者:gshayban

我没有尝试过,但我相信从可行性角度来看,clj-1472-2.patch会有效。但它的性能有缺点:它可能从JVM的角度导致“性能分析污染”,并混乱锁优化。(大多数锁都不是竞争的)

如果这可能适用于Android,我更愿意将输出修复得更像javac。

0

评论者:eraserhd

我尝试了Ghadi链接的差异,但有问题(包括丢弃被锁定表达式的值)。我修复了这个问题,但经过研究,我意识到
1. 这不包括由monitor-exit本身抛出的异常,这似乎是必需的。我们无法在catch中递归,也无法捕获‘任何’,因此我们不能仅使用Clojure结构来生成这种字节码。
Clojure的finally中生成的字节码包括受异常处理影响的存储和加载操作,因此我们不能只在monitor-exit代码中添加异常处理。我非常确信我们不能删除存储和加载操作。

所以,除非我漏掉了什么,否则通过结构化锁定检查的唯一方法是将锁定做成特殊形式。

0

评论者:adamclements

已经附加的clj-1472-2.patch通过了结构化锁定测试。

在我于2014年进行大量测试和比较生成的字节码之后,这是我找到的唯一可行的方案,它不需要教导新的特殊形式来重新实现详细说明不充分的规范,以生成准确地正确的字节码,这在我看来很脆弱。

我很有兴趣查看一些性能分析数字,看看这个声明的性能影响有多大,尤其是考虑到在Clojure中锁定宏的用法实际上非常罕见(我在测试整个生态系统中只能找到几个例子。大多数人使用其他的并发原语或Java互操作)

0

评论者:alexmiller

-3补丁在master上重新基座,没有语义变更,保留属权

...