読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり、所属会社の見解ではありません。 GitHub: takahashikzn

closure-compiler-20140407でVMBridgeのエラー

Java

僕は強力にタイプチェックのかかる環境で開発するのが好きです。


Javaではできるだけキャストを排除するのは当然として、JSを書くときもclosure-compilerのアノテーションでタイプセーフに書くようにしています。
詳しくはこちら。
http://developers.google.com/closure/compiler/docs/js-for-compiler#types


(余談)型システムを後付けするということ

本来、Javascriptはダックタイピングな言語ですから、そこへ型システムを後付しても完璧に事が進むわけではなく、いろいろと残念な箇所も出てきます。


例えば、jQueryのpropメソッドなんかまさにそれ。戻り値の型が不確定。

$(":input").prop("checked")          // → true/falseを返す
$(":input").prop("checked", true)  // → jQueryを返す


この時、closureのアノテーションでは

/**
  * (関数名は手抜き)
  *
  * @param {!string} sel
  * @param {!boolean=} value
  * @return {!(jQuery|boolean)}
  */
var doCheck = function(sel, value) { return $(sel).prop("checked", value); }

と書くわけで、doCheckの戻り値の型は(jQuery|boolean)
すなわち『「jQueryとbooleanのどちらか」という型』になります。


ココ、大事なことなので二回言います。
jQueryとbooleanのどちらかの型になる、ではありません。
jQueryとbooleanのどちらかの型になりうる」ということを表現する、ひとつの型になるということです。


一度こういう型になってしまうと、その後の型チェックの信頼度はそれほど無いと言ってよいでしょう。
セマンティクス上はboolean型になるようなdoCheckの呼び出しを行い、その戻り値に対してjQueryメソッドを呼び出してもコンパイルエラーになりません。


こんなこと書くと、まるで役に立たないツールのように見えてしまいますが、そんな事は無い。
増える面倒事(主にアノテーションを書く手間)を補って余りあるメリットがあるから使っているわけです。


ま、原理主義的には、「そんなことしないで、とっととTypeScript使えよ」になるんでしょうけど。

今回のお題

閑話休題


さて、先月あたりに新しいバージョンがリリースされたので使ってみたところ、意味不明なエラーが出てまともに動かなかった。
その時は、ちゃんと調べるのが面倒なので前のバージョンに戻してしまっていました。


ですが、ずっとこのまま放置というわけにもいかないので、よっこらしょっと腰を上げることに。
あーメンドクサイ。

原因調査

closure-compiler-20140407では、こんなエラーが出てまともに動きません。

Caused by: java.lang.IllegalStateException: Failed to create VMBridge instance
	at com.google.javascript.rhino.head.VMBridge.makeInstance(VMBridge.java:38)
	at com.google.javascript.rhino.head.VMBridge.<clinit>(VMBridge.java:18)
	... 38 more

一体なんだこれは、ということでVMBridgeのソースを見ようとしたら、Mavenリポジトリにはソースが登録されていないようです。

ただしスタックトレースを見れば分かる通り、closure-compilerは内部的に、JSのパーサーとしてRhinoを用いています。例によって行儀の悪いことに、パッケージ名だけ変えてソースを丸ごと取り込むという方法です。

次の画像は、closure-compiler-20140407の内容一覧です。

f:id:takahashikzn:20140508175338p:plain



今回はclosure-compilerのソースを拾ってくるのが面倒なので、Rhinoのソースを参照することにしました。パッケージ名はclosureのものと読み替えて下さい。

public abstract class VMBridge
{

    static final VMBridge instance = makeInstance();

    private static VMBridge makeInstance()
    {
        String[] classNames = {
            "org.mozilla.javascript.VMBridge_custom",
            "org.mozilla.javascript.jdk15.VMBridge_jdk15",
            "org.mozilla.javascript.jdk13.VMBridge_jdk13",
            "org.mozilla.javascript.jdk11.VMBridge_jdk11",
        };
        for (int i = 0; i != classNames.length; ++i) {
            String className = classNames[i];
            Class<?> cl = Kit.classOrNull(className);
            if (cl != null) {
                VMBridge bridge = (VMBridge)Kit.newInstanceOrNull(cl);
                if (bridge != null) {
                    return bridge;
                }
            }
        }
        throw new IllegalStateException("Failed to create VMBridge instance");
    }

    ...

}


はあ、どうやらVMBridge_jdk15のクラスに対して

(VMBridge) Kit.newInstanceOrNull(cl)

がnullを返すようです。なぜ?


もう一度closure-compilerのJarの内容を調べてみます。

f:id:takahashikzn:20140508175401p:plain


( ゚д゚)ポカーン


クラスファイルが、無いんだけど。そもそもパッケージごと無い。


一つ前のバージョン(closure-compiler-20131014)を見てみます。

f:id:takahashikzn:20140508175420p:plain

こちらにはちゃんと、com.google.javascript.rhino.head.jdk15が含まれています。
どうやらclosure-compiler-20140407は、必要なクラスが含まれていないという単純ミスが、今回の原因の模様です。何でこうなったのかは知らん。


「そうじゃなくて、一部の依存関係を別のJarへ切り出したのでは?」
いいえ、違います。たぶん。
closure-compiler-20140407のpom.xmlを見る限り、そのような変更は入っていません。

「closure-compiler-rhino.jarを追加すればええがな」
いいえ、違います。確かに、このJarはVMBridge_jdk15というクラスを含みますが。
パッケージ名をよーく見てみて下さい。

無理やり動かす

原因は判明しました。
動くようにするためには、com.google.javascript.rhino.head.jdk15.VMBridge_jdk15というクラスを用意すれば良いということです。


一番簡単なのは、Rhinoから必要なソースを持ってきて、パッケージ名だけ変えてソースディレクトリへ放り込んでおくこと。これで動きます。

2014-06-28追記

closure-compiler-20140625.jarでは解消されていました。
パーサーを自前で書き直したようで、rhinoへの依存がほぼ無い形に改良されています。