Clojure 2024调查问卷中分享您的想法!

欢迎!请参阅关于页面了解有关此内容的一些更多信息。

+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).

似乎在locking中与monitor-enter、monitor-exit和try/finally块混合创建了一些路径,ART将这些路径标记为未平衡的monitorenter/monitorexit字节码。特别是,monitorenter和monitorexit本身可能会抛出异常(在null锁定对象上)。Java字节码对这些情况进行了 一些复杂的异常表处理,这在afaict中是无法在不修改Clojure编译器的情况下完成的。

方法:一种可能的方法是在Java的synchronized块中调用传入的体以锁定调用。这避免了这个问题,将困难的字节码交给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

alex,你上传的正确补丁吗?

0

评论者:alexmiller

啊,搞错了。会修正。

0

评论者:bronsa

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

0

评论者:gshayban

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

JVMS(Java虚拟机规范)也不是很有趣。Android 不是一个 Java 虚拟机。我们遵循 JLS(Java语言规范),但不遵循 JVMS(因为我们不运行 Java 字节码)。因此,所有针对它的上诉都是不相关的。我们 尽力 与 JVMS 关于 Dex 字节码的精神保持兼容,但如果你的源代码不是 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时,它会安装一个异常类型的catch处理器为"任何类型"。链接的提交中捕获了Exception。如果需要,我们可以通过调用带有null作为目标类的GeneratorAdapter.visitTryCatch来生成any处理器(我不知道这究竟意味着什么——Throwable+未来可能的一切?)。

0

评论者:adamclements

您尝试应用现有的 clj-1472-2.patch 来查看它是否可以修复 GraalVM 吗?我认为我们最初达到了人们对于附件中补丁感到满意的状态(如果我说错了,Alex 请纠正我),但是决定 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 提供的 diff,但存在问题(包括丢弃被锁表达式的值)。我已经修复了这个问题,但经过研究,我发现
1. 这没有涵盖由 monitor-exit 本身抛出的异常,这似乎很必要。我们不能在 catch 中递归,也不能捕获“任何”,所以我们不能仅使用 Clojure 构造来生成这种字节码。
Clojure 的 finally 生成的字节码包括异常处理中未覆盖的存储和加载,所以我们不能仅仅在 monitor-exit 代码中添加异常处理。我相当肯定我们不能删除存储和加载。

因此,除非我遗漏了什么,否则唯一通过结构化锁定检查的方法是将锁定作为特殊形式。

0

评论者:adamclements

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

在我于 2014 年进行的广泛测试和比较生成的字节码之后,这是唯一可行的解决方案,它不需要指令新特殊形式来实现对文档不充分规范的精确重写,以生成正确的字节码,这在我看来很脆弱。

我想看看一些性能分析数据,以便了解这个所说的性能影响有多大,尤其是考虑到在Clojure中使用锁宏的情况非常罕见(我在整个生态系统测试中只找到了几个例子。大多数人使用其他并发原语或Java互操作)。

0

评论者:alexmiller

-3 在master上重新合并补丁,没有语义变化,保留了归属

...