Javaのデータはビッグエンディアン形式(ネットワークオーダとも呼ばれる)で保存されている。つまり、すべてのデータは最上位ビットから最下位ビットに向けてシーケンシャルに並んでいる。JDK 1.4 以前のJavaでは、リトルエンディアン形式のシステムとの互換性を確保するためには、バイトオーダを反転させる自前のメソッドを定義しなくてはならなかった。特に、ビッグエンディアン形式のマシンとリトルエンディアン形式のマシンが混在するようなネットワーク環境でデータのやり取りを行う場合や、JNIを通じて他のプログラミング言語とやりとりする場合には、バイトオーダに関連した問題を正しく処理することが重要になる。バイトオーダに関する問題を適切に処理しないと、予期せぬプログラムの動作を引き起こしかねない。
違反コード
クラス java.io.DataInputStream が定義する読み取りメソッド(readByte, readShort, readInt, readLong, readFloat, readDouble)および対応する書き出しメソッドは、ビッグエンディアン形式のデータのみに対応している。これらのメソッドを使って C や C++など他のプログラミング言語とやりとりするのは危険である。なぜなら、これらの言語ではデータのエンディアンネスが決まっていないからだ。以下の違反コードにこのようなエンディアンネスの不一致を示す。
try { DataInputStream dis = null; try { dis = new DataInputStream(new FileInputStream("data")); // リトルエンディアン形式のデータをビッグエンディアン形式として読み取ってしまうかもしれない int serialNumber = dis.readInt(); } catch (IOException x) { // エラー処理 } finally { if (dis != null) { try { dis.close(); } catch (IOException e) { // エラー処理 } } } }
適合コード (ByteBufferの使用)
以下の解決法では、クラス ByteBuffer が提供するメソッドを使用し([API 2006] の ByteBuffer を参照)、入力値をint型に正しく抽出している。このメソッドは、入力バイトの配列をByteBufferを使ってラップし、バイトオーダーをリトルエンディアン形式に指定し、intデータを抽出している。抽出した結果はint型のserialNumberに格納される。
try { DataInputStream dis = null; try { dis = new DataInputStream( new FileInputStream("data")); byte[] buffer = new byte[4]; int bytesRead = dis.read(buffer); // バッファにバイトデータを読み取る if (bytesRead != 4) { throw new IOException("Unexpected End of Stream"); } int serialNumber = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt(); } finally { if (dis != null) { try { dis.close(); } catch (IOException x) { // handle error } } } } catch (IOException x) { // エラー処理 }
適合コード (専用のメソッドを定義する)
別の解決法としては、ファイルの読み書き時に必要なバイトオーダ変換を行う専用のメソッドを定義するというやり方がある。以下の例において、readLittleEndianInteger() メソッドは、バイトバッファに4バイト読み取り、正しい順序に並べて整数に変換している。writeLittleEndianInteger() メソッドは、右シフトした結果をキャストすることで下位バイトを抽出し、書き出すバイトを取得している。Long 値の場合も、8バイトのバイトバッファを用意することで対応できる。
// 読み取りメソッド public static int readLittleEndianInteger(InputStream ips) throws IOException { byte[] buffer = new byte[4]; int check = ips.read(buffer); if (check != 4) { throw new IOException("Unexpected End of Stream"); } int result = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0]; return result; } // 書き出しメソッド public static void writeLittleEndianInteger(int i, OutputStream ops) throws IOException { byte[] buffer = new byte[4]; buffer[0] = (byte) i; buffer[1] = (byte) (i >> 8); buffer[2] = (byte) (i >> 16); buffer[3] = (byte) (i >> 24); ops.write(buffer); }
適合コード (reverseBytes() の使用)
JDK 1.5以降を使用している場合、Character、Short、Integer、Long クラスに定義されている reverseBytes() メソッドを使用して整数値のバイトオーダを変更することができる。Float クラスと Doubleクラスにはこのメソッドは定義されていないことに注意。
public static int reverse(int i) { return Integer.reverseBytes(i); }
リスク評価
エンディアンを考慮せずにデータの読み書きを行うと、データの符号と絶対値の解釈を誤る恐れがある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
FIO12-J | 低 | 低 | 低 | P3 | L3 |
自動検出
このルールの自動検出は一般には難しい。
関連ガイドライン
MITRE CWE | CWE-198. Use of incorrect byte ordering |
参考文献
[API 2006] | Class ByteBuffer: Methods wrap and order. Class Integer: method reverseBytes |
[Cohen 1981] | On Holy Wars and a Plea for Peace |
[Harold 1997] | Chapter 2, Primitive Data Types, Cross-Platform Issues |
翻訳元
これは以下のページを翻訳したものです。
FIO12-J. Provide methods to read and write little-endian data (revision 56)