Error: Comparison method violates its general contract! の対処法
FlashDevelopでFlexを用いてFlashやAIRアプリを開発する場合、以下のエラーが出て再ビルド(差分コンパイル)が出来ない問題があります。
Error: Comparison method violates its general contract! Build halted with errors (fcsh).
FlashDevelopの実際の画面
1回目のビルドやクリーンビルドでは出ないのでそのまま開発することも可能ですが、不便なので対処法を調べました。
そもそもの原因
「Comparison method violates its general contract!」というエラーメッセージはJavaから出ており、比較関数が規則に違反しているという内容を表しています。
比較関数とは2つの引数の大小関係を返す関数のことで、
A < B ならば compare(A, B) < 0
A == B ならば compare(A, B) == 0
A > B ならば compare(A, B) > 0
という規則を満たす必要があります。
もしも compare(A, B) < 0 と compare(B, A) < 0 が同時に成り立てば A < B と A > B が同時に成り立つことになり矛盾します。
このような矛盾が起きた時に出る例外メッセージが「Comparison method violates its general contract!」です。
Java6ではこのエラーは出なかったのですが、Java7へバージョンアップした際にソートアルゴリズムが変わりこのエラーが出るようになりました。
新しいソートアルゴリズムでは高速なソートが可能になった代わりに、比較関数に矛盾が無いことが前提になったそうです。
一見するとJavaの仕様変更のせいでエラーが出るようになったようにも見えますが、そもそも比較関数に矛盾があったら正しくソート出来ません。
ソートに失敗してもエラーが出なかった今までの仕様の方が問題があったようにも思えます。
そして比較関数を正しく実装すればエラーは出なくなるのでそうすべきです。
しかし、FlexではエラーをJavaのせいにして、長いことJava6を使うよう推奨していました。
また動作環境にJava7を含めるようになっても、このエラーは修正せずにJava7のオプションで古いソートアルゴリズムを使うようにしただけでした。
ところでFlexはオープンソースなのでソースコードが公開されているのですが、私が読んでみたところバグを特定できました。
flex2.compiler.swc.SwcGroup の 309行目以降
// Favor SwcScript's with a cached CompilationUnit CompilationUnit swcScript1CompilationUnit = swcScript1.getCompilationUnit(); CompilationUnit swcScript2CompilationUnit = swcScript2.getCompilationUnit(); if (swcScript1CompilationUnit != null) { if (swcScript1CompilationUnit.hasTypeInfo) { return -1; } } else if (swcScript2CompilationUnit != null) { if (swcScript2CompilationUnit.hasTypeInfo) { return 1; } } return 0;
A.getCompilationUnit().hasTypeInfo == true かつ B.getCompilationUnit().hasTypeInfo == true のとき、compare(A, B) < 0 と compare(B, A) < 0 で矛盾します。
適当に修正してコンパイルしてみたところエラーが出なくなったのでここで間違いなさそうです。
一週間ほど前にこのバグの修正パッチをApacheのフォーラムに投稿しておきました。
このパッチがいつ採用されるのか分からないのでとりあえずの対処法も下に書いておきます。
対処法
対処法1 Java6を使う
Java6は既にサポートが終わっていますが、Flexでは推奨されています。
対処法2 Java7で古いソートアルゴリズムを使う
Flex SDKの \bin\jvm.config もしくは \build.properties を開き次のように書き換えます。
( \bin\jvm.config があればそちらが優先されます。)
jvm.args = ${local.d32} -Xms64m -Xmx384m -ea -Dapple.awt.UIElement=true
↓
jvm.args = ${local.d32} -Xms64m -Xmx384m -ea -Dapple.awt.UIElement=true -Djava.util.Arrays.useLegacyMergeSort=true
対処法3 Javaを複数インストールする
Flex SDKの \bin\jvm.config もしくは \build.properties を開き、Flexで使いたいJavaのパスを適当なところに次のように書き加えます。
( \bin\jvm.config があればそちらが優先されます。)
java.home=C:\Program Files\Java\jre6
対処法4 筆者の作ったパッチを当てる
Javaでは比較関数がメソッド1つのクラスで実装されるのと、1クラス1ファイルでコンパイルされるという仕様のおかげでうまくパッチが作れました。
パッチはこちら。
Flex SDKの \lib\mxmlc.jar の名前をmxmlc_original.jarに変更し、筆者の作ったmxmlc.jarを代わりに置いてみてください。
mxmlc_original.jarを読み込んでパッチを当てた状態で実行してくれます。
Apache Flex 4.13以前で動くことは確認済みです。大きな変更がなければ新しいバージョンでも使えます。
最後に
Apache Flexの開発コミュニティを見ていて思ったのですが、あまり活動的ではないようです。
パッチを投稿しても一週間音沙汰がないくらいですし、コンパイラの保守もあまり行われていません。
最近の活動を見てもActionScriptやMXMLのコードがメインで、Javaコードを保守できる人が少ないのかもしれません。