M12i.

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

ミランダ・メソッドの災禍はいまも・・・

もう3年近く前になりますが、ProGuardについて調べる過程で、ミランダ・メソッド(Miranda Method)というものについて知る機会がありました。

これは古いJVMに存在した継承チェーン上のクラス・メンバー検索機能に関するバグに対する「カウンター・バグ」の呼び名です。そしてこの「カウンター・バグ」は、ProGuardのようなJVM上で実行される中間言語バイトコードを処理するツールに対して、特別な考慮を求めるものでした。

この間、たまたまこの話題を掘り返す機会があり、結果以前Google先生に質問した時よりもいくらか多くの情報が得られました。

例えばgccに含まれるgcjhというプログラムに関するバグ・リポート:

"Miranda methods" occur when an abstract class inherits a declaration from an
interface but does not redeclare or define that method.

When this occurs, gcj emits a vtable entry into the abstract class so that
virtual calls to the method, made on the abstract class, can work correctly.

gcjh is not aware of this, however, and the .h file it generates is inconsistent
with the vtable emitted by gcj.

ミランダ・メソッドは抽象クラスがあるインターフェースの宣言を継承しているのに、そのインターフェースのメソッドを再度宣言したり実装したりしていない場合に発生する〔コンパイラにより自動生成される〕。

このとき、gcjは当該メソッドへの仮想的な呼び出しができるようにvtableエントリを抽象クラスに挿入する。

gcjhはこのことを関知しないため、〔gcjhにより生成された〕.hファイルはgcjにより挿入されたvtableの情報と矛盾をきたしてしまう。

Bug 15411 - CNI: gcjh not aware of "miranda" methods / Bryce McKinlay 2004-05-13 01:42:20 UTC

OpenJDK関連のメーリングリストではJBossAOP製品における問題について:

The JBoss AOP team recently ran in to a problem when transforming an
abstract class offline (see https://jira.jboss.org/browse/JBAOP-731 for
details). The AOP transform operation loads the bytecode for a class,
call it X, manipulates it to insert, inter alia, a getAdvisor() method
then rewrites the classfile. Method getAdvisor is SYNTHETIC and has a
Code attribute. It is _NOT_ ABSTRACT.

When javac subsequently tries to compile the source for Y extends X it
complains that Y does not implement getAdvisor(). It turns out that
javac is assuming that the SYNTHETIC implementation found in the
transformed classfile for X is a Miranda method even though it is not
ABSTRACT....

最近、JBoss AOPチームは、抽象クラスのオフラインでのトランスフォーメーションの際に起こる問題について対応をした(詳細はこちら)。AOPトランフォーメーション処理はクラス──ここでは仮にXとする──のバイトコードを読み取り、とくにgetAdvisor()メソッドを挿入するためにこれを操作し、クラスファイルを書き換える。getAdvisor()メソッドはSYNTHETIC〔コンパイラにより自動生成されたものを示す〕であり、しかもCode属性を持つ。すなわち非ABSTRACTなメソッドである。

上記処理に引き続きJavacがXを継承したクラスYのソースコードコンパイルしようとすると、「YはgetAdvisor()を実装していない」とエラーになってしまう。このことから分かったのは右のようなことである: Javacは、Xのクラスファイル中で見つけたSYNTHETICなメソッドの実装を、それがABSTRACTでないのにミランダ・メソッドであると思い込んでしまうのである。〔後略〕

Miranda methods and Methods added by Code Generators / Andrew Dinn adinn at redhat.com Fri Aug 6 08:21:07 PDT 2010)

おまけに公式のJDKにまで塁が及んでいるらしく:

There is problem related to Miranda methods (target==1.1, still used by J2ME, AFAIK). To reproduce:
1. download and unpack the attachment.
2. compile the sources using:
javac -source 1.2 -target 1.1 miranda/*.java

When using a 1.6 javac, the sources compile fine. When using a recent 1.7 javac, the outcome is:
miranda/Impl.java:5: the symbol a() conflicts with a
compiler-synthesized symbol in Impl
public void a() {}
^
1 error

The problem seem to be in the (new in 1.7) Check.checkConflicts method, which ignores bridge methods, but does not ignore Miranda methods, although it probably should. Tomas did extended the checkConflicts method to ignore Miranda methods, see:
http://hg.netbeans.org/main/nb-javac/rev/e9b666cd96a3
We hope that this change is reasonable, and something like this should probably be added also into javac proper.

ミランダ・メソッドに関連する問題がある。再現手順は次の通り:
1. 添付ファイルをダウンロードし解凍する。
2. 下記のコマンドを実行してソースコードコンパイルする。
  javac -source 1.2 -target 1.1 miranda/*.java

〔中略〕

この問題は(バージョン1.7で追加された)Check.checkConflictsメソッドで起きているように思われる。このメソッドは、ブリッジ・メソッドを無視するがミランダ・メソッドを無視しない。本来的には無視するべきもののはずである。トーマスがこのcheckConflicts メソッドを拡張してミランダ・メソッドを無視するようにしている(詳細はこちら)。
われわれとしてはこの変更が妥当なものであると信じている。そしてこれと同様の修正が公式のJavacにも適用されてしかるべきと考えている。

JDK-6964669 : javac reports error on miranda methods