请在 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,因为它未通过编译时验证('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).

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

方法: 一种可行的方法是使锁定将传递的正文在 Java 的 synchronized 块上下文中调用。这通过将其交给 Java 来避免复杂的字节码问题,但可能对性能产生未知的影响。

补丁: clj-1472-3.patch

另见: 将字节码与 java synchronized 块的比较显示出了许多差异
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

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

fixed 的备注关闭

41 条回答

0

评论由:alexmiller发布

我添加了一个新的clj-1472.patch文件,修复了上一个补丁中我不太满意的一些细节。然而,它仍然是实质性的相同更改,所以我保留了原始作者的归属。

0

评论由:gshayban发布

alexs,你上传的补丁文件正确吗?

0

评论由:alexmiller发布

哦,对了,我搞砸了。马上去修复。

0

评论由:bronsa发布

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

0

评论由:gshayban发布

从Android得知
{quote}
Android不运行Java字节码,它运行Dex字节码。你的类转换成的dexdump输出很有趣。

JVMS也不有趣。Android不是一个Java虚拟机。我们遵循JLS,但不遵循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中,它安装了一个异常类型的“任何”的捕获处理程序。链接的commit捕获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链接的diff,但存在一些问题(包括丢弃了被锁的表达式值)。我修复了这个问题,但经过研究,我发现
1. 这没有涵盖由monitor-exit本身抛出的异常,这似乎是必需的。我们不能在catch中递归,也不能捕获'任何',因此我们不能仅使用Clojure结构生成这种字节码。
Clojure的finally生成的字节码包含被异常处理未覆盖的store和load,因此我们不能仅在monitor-exit代码中添加异常处理。我相当肯定我们不能去除store和load。

因此,除非我错过了什么,否则通过结构锁定检查的唯一方法是使锁定成为一个特殊形式。

0

评论者:adamclements

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

在2014年进行了大量彻底的测试和生成的字节码对比后,这是我找到的唯一可行的解决方案,该方案不需要一个新特殊形式的指令来重新实现唯一糟糕的 документированной спецификации,以生成确切正确的字节码,这在我看来很脆弱。

我很想查看一下这种声称的性能影响的具体数值,尤其是在clojure(我在测试整个生态系统时只能找到几个例子,大多数人使用其他并发原语或Java互操作)中锁宏的实际使用非常罕见的情况下。

0

评论由:alexmiller发布

-3补丁回退到master,没有语义变化,保留了归属

...