Java とか Android (DEX) の MUTF-8 (Modified UTF-8) って何者よ?っていう話

Android の APK の DEX をゴニョゴニョしてたら、DEX の string 領域に格納されている文字列は実は UTF-8 じゃなくてちょっと modify された MUTF-8 (Modified UTF-8) だということが判明して軽くショックを受けています。

Wikipedia の UTF-8 の項によりますと、標準の UTF-8 から以下の 2 つの変更点があるようです。

後者に関しては端的にいうと CESU-8 (Compatibility Encoding Scheme for UTF-16: 8-Bit) というエンコード方法に則っているということのようです。

Wikipedia のページにも例がありますが、U+10400 というコードポイントを普通に UTF-8 でエンコーディングしたら 0xF0, 0x90, 0x90, 0x80 という 4 バイトのバイト列になるのですが、CESU-8 だと 0xED, 0xA0, 0x81, 0xED, 0xB0, 0x80 という 6 バイトのバイト列になります。

これは U+10400 を UTF-16 でエンコーディングすると 0xD801, 0xDC00 というサロゲートペアになるのですが、それぞれ、0xD801 を UTF-8 のようにエンコーディングすると 0xED, 0xA0, 0x81 となり、0xDC00 を UTF-8 のようにエンコーディングすると 0xED, 0xB0, 0x80 となるためなんですね。

これら 2 つのルールを整理しますと、以下の表のようになります。

Unicode コードポイント U+0000 U+0001 〜 U+007F U+0080 〜 U+07FF U+0800 〜 U+FFFF U+10000 〜 U+10FFFF
通常の UTF-8 0x00 0x01 〜 0x7F 0xC4,0x80 〜 0xDF,0xBF 0xE0,0xA0,0x80 〜 0xEF,0xBF,0xBF 0xF0,0x90,0x80,0x80 〜 0xF0,0x90,0xBF,0xBF
UTF-16F 0x0000 0x0001 〜 0x007F 0x0080 〜 0x7FFF 0x0800 〜 0xFFFF 0xD800,0xDC00 〜 0xDBFF,0xDFFF
Modified UTF-8 0xC0,0x80 0x01 〜 0x7F 0xC4,0x80 〜 0xDF,0xBF 0xE0,0xA0,0x80 〜 0xEF,0xBF,0xBF 0xED,0xA0,0x80,0xED,0xB0,0x80 〜 0xED,0xAF,0xBF,0xED,0xBF,0xBF


ちなみに、MUTF-8 を UTF-16 に戻したり、逆に UTF-16 を MUTF-8 に変換したりするコードは Android のソースの
dalvik/dx/src/com/android/dx/rop/cst/CstString.java
あたりにあります。
(関数名 utf8BytesToString() や stringToUtf8Bytes() が該当します)


その他、参考 URL
http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html#wp16542