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和try/finally块在locking中的混合,ART正在标记为没有配平monitorenter/monitorexit字节码的路径。特别是,monitorenter和monitorexit本身可能会抛出异常(在null锁定对象上)。Java字节码执行某些复杂的异常表处理来覆盖这些情况,而这在实际上是不可能做到的,除非修改Clojure编译器。

方法:一种可能的方法是将锁定在Java同步块的情况下调用传入的主体。这通过将其交给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字节码。你对类转换成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 测试基础设施,我将乐意参与。

In javac 的 synchronized 生成中,它安装了一个具有“任何”异常类型的处理程序。链接的提交捕获 Exception。如果需要,我们可以通过调用 GeneratorAdapter.visitTryCatch 并将 null 作为目标类来生成 any 处理程序(我不知道这确切地意味着什么——Throwable + 将来的任何其他东西?)

0

评论者:adamclements

您尝试应用现有的clj-1472-2.patch来修复GraalVM了吗?我认为我们最初达到的境地是大家对该补丁表示满意(如果我说错了,请纠正我,Alex),但决定JVM实现在地位上是至高无上的,所以问题不是Clojure的,而是Android没有遵守这一点。如果同样的问题在其他地方再次出现,那个决定可能需要重新考虑(我仍然想亲自看到这个补丁)。

这是很久以前的事情了,但我记得尝试了所有可能的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上重新基座,没有语义变化,保留了归属权

...