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

M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

ProGuardマニュアル (1) はじめに/利用方法

Java

ScalaAndroidについて調べるなかで、ProGuardというキーワードを目にする機会がありました。

意識して頻繁に使用する機会はなさそうですが、概略くらいはかるーく知っておきたいなー、ということで、例によって公式サイトのドキュメントを見てみました。

原文は、ProGuard Manual”(Eric Lafortune)です(2011年3月29日取得)。

******************************

はじめに

ProGuardは、Javaクラスファイルのダウンサイジング*1、最適化、難読化そして事前検証を行うツールです。ダウンサイジングのステップでは、使用されていないクラス、フィールド、メソッドと属性を検出し削除します。最適化のステップでは、メソッドのバイトコードを解析し、最適化していきます。難読化ステップでは、残っているクラスとそのフィールドとメソッドを、短い無意味な(訳者:意味をとることができない)名前に変更していきます。最後の事前検証ステップでは、Java Micro Editionでの実行やJava 6での実行速度を向上させるための事前検証情報をクラスに対して付加します。

それぞれのステップを実施するかは任意です。例えば、ProGuardは既存のアプリケーションに含まれる使用されなくなったコードのリストアップや、Java 6での実行効率について事前検証するといった用途に使用できます。

通常、ProGuardは入力jarファイル(もしくはwar、ear、zip、ディレクトリ)を読み込み、ダウンサイズ、最適化、難読化した上で、できあがったコードを事前検証します。必要に応じて、複数の最適化──典型的には別のダウンサイズ処理が続く──を実施することもできます。処理結果は1つかそれ以上の出力jarファイル(もしくはwar、ear、zip、ディレクトリ)として保存されます。入力にはリソースファイルを含めることができます。オプションで、これらのファイルの名前や内容には、難読化されたクラス名を反映させることもできます。

ProGuardは処理対象のコードが依存するライブラリjarファイル(もしくはwar、ear、zip、ディレクトリ)を必要とします。処理対象のコードをコンパイルする際に必要となったライブラリです。ProGuardはこれらのファイルを処理対象クラスの依存性の再構築に使用します。ライブラリ自体はいかなる場合にも変更されません。ライブラリは、最終的な成果物となるアプリケーションのクラスパスに配備してください。

エントリーポイント

どのコードァイルが変更せずにおかれるべきか、またどのコードが削除したり難読化したりすべきかを決定するため、1つかそれ以上のエントリーポイントを指定する必要があります。通常、これらのエントリーポイントは、mainメソッドを持つクラスやアプレット、ミッドレット*2、その他のファイルです。

  • ダウンサイズ処理ステップでは、ProGuardはどのクラスとクラス・メンバーが使用されているか再帰的に判断していく作業を、これらのエントリーポイントから開始します。使用されていないすべてのクラスは廃棄されます。
  • 最適化ステップでは、一層のコードの最適化が進められます。最適化のなかでは、エントリーポイントでないクラスとメソッドがprivateや、static、finalにされたり、使用されていない引数が削除されたり、いくつかのメソッドがインライン化されたりします。
  • 難読化のステップでは、ProGuardはエントリーポイントでないクラスとクラス・メンバーの名前が変更されます。このステップを通じて、エントリーポイントのクラスとクラス・メンバーがもとの名前で利用可能であり続けることが約束されています。
  • 事前検証ステップだけは、エントリーポイントについて関知しません。

このマニュアルのUsage 節では、エントリーポイントの指定に必要な-keepオプションを解説しています。またExamples節では、多くの実行例が提供されています。

リフレクション

リフレクション*3とイントロスペクション*4は、Proguardによるコード処理過程において独特の問題を引き起こします。ProGuardでは、あるプログラムの中で動的に作成・実行されるクラスもしくはクラスメンバーは、エントリーポイントと同様にあらかじめ指定されている必要があります。例えば、Class.forName()メソッドは、実行時に任意のクラスへの参照を返します。例えばコンフィギュレーションファイルからクラス名が読み取らたあとで、(訳者:その一覧になかったクラスや動的に生成されるクラスのうち)どのクラスがProGuardによる処理を免れるか(そして元の名前のままであるか)ということを予測するのは、一般的にいって不可能です。このためProGuardの処理の対象としたくないクラスやクラスメンバーは、-keepオプションにより明確に設定する必要があります。

とはいえ次のようなケースではProGuardはいつでも状況を察知し適切に処理します:

    Class.forName("SomeClass")
    SomeClass.class
    SomeClass.class.getField("someField")
    SomeClass.class.getDeclaredField("someField")
    SomeClass.class.getMethod("someMethod", new Class[] {})
    SomeClass.class.getMethod("someMethod", new Class[] { A.class })
    SomeClass.class.getMethod("someMethod", new Class[] { A.class, B.class })
    SomeClass.class.getDeclaredMethod("someMethod", new Class[] {})
    SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class })
    SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class, B.class })
    AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")
    AtomicLongFieldUpdater.newUpdater(SomeClass.class, "someField")
    AtomicReferenceFieldUpdater.newUpdater(SomeClass.class, SomeType.class, "someField")

もちろん、クラスとクラス・メンバーの名前は異なる概念です。しかしProGuardがそれらを理解する上では同じことなのです。参照されているクラスとクラスメンバーは、ダウンサイジング過程では保護され、(訳者:クラス名やクラス・メンバー名をあらわす)文字列引数は難読化過程で適切なものに変更されます。

その上Proguardはそれが必要があると思われるクラスとクラス・メンバーが存在した場合、それらの保護を提案をすることもあります。例えばProguardは、“(SomeClass)Class.forName(variable).newInstance()”といった記述があった場合にこれを指摘します。これらはクラスもしくはインターフェースであるところのSomeClassもしくはその実装が保護されるべきであることを示すものである可能性があります。これらの提案にしたがって設定を変更することができます。

正しい結果を得るためには、Proguardの使用者は、自身が処理しようとしているコードの実装について、少なくとも幾分か理解している必要があります。リフレクションを多用しているコードの難読化処理は──とくにコードの内部的な実装方法についての情報がない状態でのそれには──試行錯誤が必要でしょう。

利用方法

ProGuardの実行には次のようにタイプします:

java -jar proguard.jar options ...

ProGuard jarは、ProGuardディストリビューション(配布ファイル)のlibディレクトリ内にあります。オプションは、1つかそれ以上のコンフィギュレーションファイル書き出しておくことができます。通常、利用者はほとんどのオプションを、設定ファイルに記述しておきます(ここではmyconfig.proという名前にしましょう)。そして次のようにして実行します:

java -jar proguard.jar @myconfig.pro

コマンドラインオプションと、コンフィギュレーションファイルのオプションとを組み合わせて使用することもできます。例えば次のように:

java -jar proguard.jar @myconfig.pro -verbose

コンフィギュレーションファイル内では、#記号とそれに続く残りのすべての文字は無視され、コメントとして扱われます。

キーワードと区切り文字の間にある余分な空白文字は無視されます。空白文字や特殊文字をともなうファイル名を指定する場合、シングルクオテーションやダブルクオテーションにより囲う必要があります。コマンドラインオプションの中でクオテーションを使用するときは、シェルによって引用符が取り払われてしまわないよう、エスケープする必要があるでしょう。コマンドラインコンフィギュレーションファイルでは、オプションを適宜グループ化することができます。これは例えば、複数のコマンドライン・オプションを、シェルによる特殊文字の展開から守るために、適宜引用符で囲うことができるということです。

一般的にはオプションの順序は重要ではありません。オプションは頭文字によって省略形で使用することもできます。

つづく節ではより詳細な情報が得られます:

******************************

ProGuardマニュアル (2)につづく。

*1:訳者:原文の“shrink”という動詞には、訳文では“ダウンサイズ”などの語をあてています。

*2:訳者: MIDletは、Java MEで定義されている携帯小型端末向けアプリケーション形式。

*3:訳者:そのクラスのメンバー情報を取得すること。

*4:訳者:そのクラスがどのようなプロパティを持っているかという情報を取得すること。