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的同步块上下文中通过锁定调用传入的表达式。这通过将问题交给Java避免了复杂的字节码问题,但可能对性能有未知的影响。

补丁: clj-1472-3.patch

另请参阅:与Java同步块相比的字节码检查显示了多项差异
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

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

已关闭,附带说明:已修复

41 答案

0

评论者:alexmiller

我添加了一个新的clj-1472.patch,修复了之前补丁中我不太满意的几个小细节。然而,这仍然是一个基本相同的修改,所以我保留了原始作者的归属。

0

评论者:gshayban

alexi,你上传的是正确的补丁吗?

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时,它安装了异常类型为“任何”的捕获处理器。链接的提交中捕获了Exception。如果需要,我们可以通过调用GeneratorAdapter.visitTryCatch并使用null作为目标类来发出“any”处理器。目前我还不知道这具体意味着什么——是Throwable加上未来的其他任何内容?

0
by

评论者:adamclements

您是否尝试应用现有的clj-1472-2.patch以查看它是否可以修复GraalVM?我认为我们最初达到了人们喜欢附加补丁的地方(如果亚历克斯您错了,请纠正我),但是决定JVM实现是王道,所以问题在于Android没有遵守它,而不是Clojure。如果相同的问题在其他地方再次出现,这个决定可能是可以重新考虑的(我仍然希望看到这个补丁)。

这已经是很久以前了,但我记得我尝试了所有不同的try/catch/finally/throw组合,但无法生成符合同步规范的字节码,而不改变Clojure的代码生成,因为深度太深而不切实际——这就是上面的补丁,该补丁依赖于javac生成同步的字节码,而不是使用monitor-enter和monitor-exit。我担心您的链接实现可能仍然会在某些情况下生成错误的字节码,就像现有实现一样,尽管它在Clojure代码级别看起来非常完美,并显然正确。

0
by

评论者:gshayban

我没有尝试,但我坚信从可行性角度来看,clj-1472-2.patch会有效。它有一个性能方面的缺点:它可能导致JVM的“性能分析污染”,并使锁优化产生混淆。(大多数锁是无争的)

如果这样可以适用于Android,我宁愿修复输出,使其更类似于javac。

0
by

评论者:eraserhd

我已经尝试了Ghadi提供的diff,但存在一些问题(包括丢弃被锁定的表达式值)。我已经修复了这个问题,但经过研究,我发现
1. 这未涵盖由monitor-exit本身抛出的异常,这似乎是必需的。我们不能在catch中递归,也不能捕获'任何',因此我们无法仅使用Clojure构造来生成此字节码。
Clojure的finally生成的字节码中包含处理异常时未发现的存储和加载操作,因此我们无法仅将异常处理添加到monitor-exit代码中。我相当肯定我们无法删除存储和加载操作。

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

0
by

评论者:adamclements

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

在2014年,我对生成的字节码进行了大量的测试和比较后,这是我找到的唯一可行的方案,该方案不需要教授一个新特形式,该特形式实现了文档糟糕的规范,以输出正确的字节码,这在我看来似乎很脆弱。

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

0

评论者:alexmiller

-3 修补程序重新基于主分支,没有语义更改,归因保留

...