JavaクラスファイルのCONSTANT_Long_infoがコンスタントプールのエントリを2つ使う件
JavaのコンスタントプールのLong_info構造体について、わからんーとTwitterで言っていたところ、finalfusionさんに教えてもらって疑問が解けたので、メモしておきます。
クラスファイルのおさらい
Javaクラスファイルのバイナリの仕様は、次のようになっています。
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
constant_pool配列には、その上の (constant_pool_countの数−1) 個の要素があります。仕様書ではエントリと呼ばれています。
constant_pool配列の要素
constant_pool配列の各要素は、内部にそれぞれ1バイトのtagを持っています。
各要素が、tag以外に、どれくらいのサイズのどんな情報を格納するかは、tagによって示される「要素の種類」(構造体の種類)ごとに異なります。
たとえば、tagが「7」であれば、その配列要素はCONSTANT_Class_info構造体であり、1バイトのtagと、2バイトのname_indexを持つことになります。
その定義は、仕様でこんなふうに書かれています。
CONSTANT_Class_info { u1 tag; u2 name_index; }
tagの種類は、現時点で14種類のようですね。
# このあたりの詳しい読み方は、バイナリ勉強会で教えてもらいました。ご存じない方は、よろしければ、まとめをどうぞ。
- Javaのクラスファイルの読み方 http://d.hatena.ne.jp/torazuka/20120820/cafebabe
(疑問 その1)high_bytesとlow_bytesがある意味
分からなかったのは、CONSTANT_Long_infoについてでした。
CONSTANT_Long_infoの仕様は、次のようになっています。
CONSTANT_Long_info { u1 tag; u4 high_bytes; u4 low_bytes; }
そもそもの疑問は、high_bytesとlow_bytesに(以下の式で得られた)"the long constant bits"が格納されるという文章を読んで、high_bytesとlow_bytesの2つの項目があるのに、両方に同じ値を入れるの?と思ったことでした。
The high_bytes and low_bytes items are converted into the long constant bits, which is equal to
((long) high_bytes << 32) + low_bytes
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.5
これについては、「8バイトのlong値を一度に表現できないから、上位4バイトと下位4バイトに分けて表現する」と教えていただきました。
上位4バイトのhigh_bytesと、下位4バイトのlow_bytesを合わせて、CONSTANT_Long_info構造体1つで、1つのlong値を表現するんですね。
(疑問 その2)2エントリを使う、とはどういう意味か
上の話には聞き覚えがあるので、以前どこかで聴いたのに、忘れてしまったのに違いありません。
確かに仕様にもそう書いてあります。……あります?
All 8-byte constants take up two entries in the constant_pool table of the class file. If a CONSTANT_Long_info or CONSTANT_Double_info structure is the item in the constant_pool table at index n, then the next usable item in the pool is located at index n+2.
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.5
この「take up two entries」の意味が分からない、というのが2つ目の疑問でした。
最初は、Longを表現するために、constant_pool配列の要素であるCONSTANT_Long_info構造体を、2つ必要とするのかと思いました。しかし、8バイトのlong値もhigh_bytwsとlow_bytesで表現できてしまうのですから、2つ並べるには及びません。
というところで、finalfusionさんから、次のページを教えていただいて、疑問が解けました。
- 【Java クラス (2)】 ConstantPool (1)http://blogs.wankuma.com/yamada/archive/2010/07/31/191839.aspx
Longを表現するのに必要なバイナリは、あくまでもCONSTANT_Long_info構造体1つ分です。しかし、constant_pool配列要素のカウンタを2つ分消費するという意味だったんですね。
勉強になりました。ありがとうございました。
(おまけ)CONSTANT_Long_infoで2つのカウンタが消費されることを確認する
longを使ったJavaのコードを用意し、コンパイルします。
public class PreIncrement2 { public long increment() { long n = 10000000L; ++n; return n; } }
(ひどすぎる、というなかれ。下手に複雑にすると目視で読むのに一苦労です)(それでもクラス名がひどいか…)
これをバイナリエディタのBZで開いて、ダンプファイルに落としたものが、次のとおりです。
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F 0123456789ABCDEF 000000 CA FE BA BE 00 00 00 33-00 11 0A 00 05 00 0E 05 .......3........ 000010 00 00 00 00 00 98 96 80-07 00 0F 07 00 10 01 00 ................ 000020 06 3C 69 6E 69 74 3E 01-00 03 28 29 56 01 00 04 .<init>...()V... 000030 43 6F 64 65 01 00 0F 4C-69 6E 65 4E 75 6D 62 65 Code...LineNumbe 000040 72 54 61 62 6C 65 01 00-09 69 6E 63 72 65 6D 65 rTable...increme 000050 6E 74 01 00 03 28 29 4A-01 00 0A 53 6F 75 72 63 nt...()J...Sourc 000060 65 46 69 6C 65 01 00 12-50 72 65 49 6E 63 72 65 eFile...PreIncre 000070 6D 65 6E 74 32 2E 6A 61-76 61 0C 00 06 00 07 01 ment2.java...... 000080 00 0D 50 72 65 49 6E 63-72 65 6D 65 6E 74 32 01 ..PreIncrement2. 000090 00 10 6A 61 76 61 2F 6C-61 6E 67 2F 4F 62 6A 65 ..java/lang/Obje 0000A0 63 74 00 21 00 04 00 05-00 00 00 00 00 02 00 01 ct.!............ 0000B0 00 06 00 07 00 01 00 08-00 00 00 1D 00 01 00 01 ................ 0000C0 00 00 00 05 2A B7 00 01-B1 00 00 00 01 00 09 00 ....*........... 0000D0 00 00 06 00 01 00 00 00-01 00 01 00 0A 00 0B 00 ................ 0000E0 01 00 08 00 00 00 2A 00-04 00 03 00 00 00 0A 14 ......*......... 0000F0 00 02 40 1F 0A 61 40 1F-AD 00 00 00 01 00 09 00 ..@..a@......... 000100 00 00 0E 00 03 00 00 00-03 00 04 00 04 00 08 00 ................ 000110 05 00 01 00 0C 00 00 00-02 00 0D ...........
最初は、次のように間違った読み方をしていました。
※注意※ 次の読み方は誤りです。正しい読み方は、この記事の後ろの方にあります。
u4 magic CAFEBABE u2 minor_version 00 00 u2 major_version 00 33 u2 constant_pool_count 00 11 # (0x11==17) - 1個 #1 u1 tag 0A # CONSTANT_Methodref_info u2 class_index 00 05 u2 name_and_type_ index 00 0E #2 u1 tag 05 # CONSTANT_Long_info u4 high_bytes 00 00 00 00 u4 low_bytes 00 98 96 80 # ★この次のカウントが誤り。Longの後なので、本当は4にしなければならない。 #3 u1 tag 07 # CONSTANT_Class_info u2 name_index 00 0F #4 u1 tag 07 # CONSTANT_Class_info u2 name_index 00 10 #5 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 06 u1 bytes[length] 3C 69 6E 69 74 3E # <init> #6 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 03 u1 bytes[length] 28 29 56 # ()V #7 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 04 u1 bytes[length] 43 6F 64 65 # Code #8 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 0F u1 bytes[length] 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 #9 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 09 u1 bytes[length] 69 6E 63 72 65 6D 65 6E 74 #10 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 03 u1 bytes[length] 28 29 4A #11 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 0A u1 bytes[length] 53 6F 75 72 63 65 46 69 6C 65 #12 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 12 u1 bytes[length] 50 72 65 49 6E 63 72 65 6D 65 6E 74 32 2E 6A 61 76 61 #13 u1 tag 0C # CONSTANT_NameAndType_info u2 name_index 00 06 u2 descriptor_index 00 07 (以下略)
これで何がまずいかというと、constant_pool配列の13番目の要素(と上では示されているモノ)であるCONSTANT_NameAndType_info構造体のname_indexとdescriptor_indexが、明らかに正しくない点です。
CONSTANT_NameAndType_infoのname_index項目は、いわゆるJavaの識別子を格納したconstant_poolのエントリを参照していなければなりません。「
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.6
- name_index
- The value of the name_index item must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure representing either the special method name
(§2.9) or a valid unqualified name (§4.2.2) denoting a field or method.
しかし、上のように誤って順序づけられたダンプを見ると、name_indexが示す6番目のエントリは、文字列「()V」です。これはフィールド名でもメソッド名でもありません。
さらに、CONSTANT_NameAndType_infoのdescriptor_index項目は、フィールドディスクリプタやメソッドディスクリプタでなければなりません。詳細は割愛しますが、これにも違反しています。
で、おかしいなーーと悩んでいた時、ちょうど上のページを教えてもらい、間違いに気づきました :)
正しくは、こう読むんですね。
(正しい読み方バージョン)
u4 magic CAFEBABE u2 minor_version 00 00 u2 major_version 00 33 u2 constant_pool_count 00 11 # (0x11==17) - 1個 #1 u1 tag 0A # CONSTANT_Methodref_info u2 class_index 00 05 u2 name_and_type_ index 00 0E #2 u1 tag 05 # CONSTANT_Long_info u4 high_bytes 00 00 00 00 u4 low_bytes 00 98 96 80 # ☆Longの後なので、カウンタを1つ進める。 #4 u1 tag 07 # CONSTANT_Class_info u2 name_index 00 0F #5 u1 tag 07 # CONSTANT_Class_info u2 name_index 00 10 #6 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 06 u1 bytes[length] 3C 69 6E 69 74 3E # <init> #7 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 03 u1 bytes[length] 28 29 56 # ()V #8 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 04 u1 bytes[length] 43 6F 64 65 # Code #9 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 0F u1 bytes[length] 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 #10 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 09 u1 bytes[length] 69 6E 63 72 65 6D 65 6E 74 #11 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 03 u1 bytes[length] 28 29 4A #12 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 0A u1 bytes[length] 53 6F 75 72 63 65 46 69 6C 65 #13 u1 tag 01 # CONSTANT_Utf8_info u2 length 00 12 u1 bytes[length] 50 72 65 49 6E 63 72 65 6D 65 6E 74 32 2E 6A 61 76 61 #14 u1 tag 0C # CONSTANT_NameAndType_info u2 name_index 00 06 u2 descriptor_index 00 07 (以下略)
2番目のエントリがCONSTANT_Long_infoなので、3番目のエントリが使用不能になり、次のエントリは4番目になります。
この場合、name_indexが示す先の「正しい」6番目のエントリは、「
descriptor_indexが示す先の7番目のエントリも、「V()」ですから、メソッドディスクリプタです。
javapの出力結果
javapの出力を注意深く見ていれば、上のようなことはすぐ分かりそうなものですが、まったく気がつきませんでした^^;;
>javap -v PreIncrement2.class # (略) minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #5.#14 // java/lang/Object."<init>":()V #2 = Long 10000000l #4 = Class #15 // PreIncrement2 #5 = Class #16 // java/lang/Object #6 = Utf8 <init> #7 = Utf8 ()V #8 = Utf8 Code #9 = Utf8 LineNumberTable #10 = Utf8 increment #11 = Utf8 ()J #12 = Utf8 SourceFile #13 = Utf8 PreIncrement2.java #14 = NameAndType #6:#7 // "<init>":()V #15 = Utf8 PreIncrement2 #16 = Utf8 java/lang/Object
ほら、#3がない! (>_<)
参考
ほかにもせっかく教えていただいたので書き留めておきます。こんなページ知りませんでした。
あとは、OpenJDKのjava_rw_demo.cのadd_new_cpool_entry関数ですね。
ひどいフォールダウンをかましているswitch文があるのですが、その中のLongとDoubleのところで、たしかにカウンタが1つ余分に進められています。
switch (tag) { case JVM_CONSTANT_Class: writeU2(ci, index1); break; case JVM_CONSTANT_String: writeU2(ci, index1); break; case JVM_CONSTANT_Fieldref: case JVM_CONSTANT_Methodref: case JVM_CONSTANT_InterfaceMethodref: case JVM_CONSTANT_Integer: case JVM_CONSTANT_Float: case JVM_CONSTANT_NameAndType: writeU2(ci, index1); writeU2(ci, index2); break; case JVM_CONSTANT_Long: case JVM_CONSTANT_Double: writeU4(ci, index1); writeU4(ci, index2); ci->cpool_count_plus_one++; CRW_ASSERT(ci, ci->cpool_count_plus_one < ci->cpool_max_elements ); break; case JVM_CONSTANT_Utf8: CRW_ASSERT(ci, len==(len & 0xFFFF)); writeU2(ci, len); write_bytes(ci, (void*)str, len); utf8 = (char*)duplicate(ci, str, len); break; default: CRW_FATAL(ci, "Unknown constant"); break; }
(悩んだ時にこーいうのをバシッと見つけられるように、自分もなりたいものです)