JPCERT コーディネーションセンター

STR01-J. Java の char 型が Unicode のコードポイントを完全に表現できると想定しない

STR01-J. Java の char 型が Unicode のコードポイントを完全に表現できると想定しない

char 型は、文字を固定幅16ビットと定義した初期の Unicode 仕様に基づいている。その後、Unicode 標準は16ビット以上の表現を必要とする文字を許容するように変更された。現在の「Unicode コードポイント」の範囲は U+0000 から U+10FFFF である。U+0000 から U+FFFF の文字集合は「基本多言語面 (BMP)」と呼ばれ、コードポイントが U+FFFF より大きい文字は「補助文字」と呼ばれる。補助文字の多くはあまり使われないが、日本や中国の人名などに使われる文字もある。char プリミティブデータ型の変更や既存の Java プログラムとの非互換を避けながら扱うため、補助文字は「サロゲート」と呼ばれる「Unicode コードユニット」のペアとして定義されている。Java API [API 2014] の Character クラスの説明には以下のように書かれている。

Java プラットフォームは、char 配列、String クラスおよび StringBuffer クラスで UTF-16 表現を使う。UTF-16 表現では、補助文字は char 値のペアとして表現され、「上位サロゲート」範囲内 (\uD800-\uDBFF) の前半部分と、「下位サロゲート」範囲内 (\uDC00-\uDFFF) の後半部分から構成される。

したがって、char 値は、サロゲートコードポイントを含む基本多言語面のコードポイント、つまり UTF-16 エンコーディングのコードユニットを表現する。int 値は、補助コードポイントを含むすべての Unicode コードポイントを表現する。int の下位21ビットは Unicode コードポイントを表現するために使われ、上位11ビットは0にする必要がある。UTF-8 (STR00-J. Don't form strings containing partial characters from variable-width encodings参照) と同様、UTF-16 は可変幅エンコーディングである。UTF-16 表現は char 配列、String クラスおよび StringBuffer クラスでも使われているため、Java で文字列データを扱う際には注意が必要だ。特に、char プリミティブ型 (または Character オブジェクト) が Unicode コードポイントを完全に表現できると想定するコードを書いてはいけない。この要求に適合するには、典型的には、補助文字に対応できないため Unicode コードユニットを char 値として受け取るメソッドを避け、Unicode コードポイントを int 値として受け取るメソッドを使う必要がある。

違反コード

以下のコード例では、string の先頭何文字かを削除しようとしている。

public static String trim(String string) {
  char ch;
  int i;
  for (i = 0; i < string.length(); i += 1) {
    ch = string.charAt(i);
    if (!Character.isLetter(ch)) {
      break;
    }
  }
  return string.substring(i);
}

残念ながら、文字を引数とする Character.isLetter() メソッドを使っているため、この trim() メソッドは失敗する可能性がある。char 値のみを受け取るメソッドは、補助文字に対応できない。Java API [API 2014] の Character クラスの説明には、以下のように書かれている。

これらのメソッドはサロゲート範囲の char 値を未定義の文字として扱う。たとえば、「\uD840」という値は、下位サロゲート値が続く場合に文字を表現するかもしれないが、Character.isLetter('\uD840')false を返す。

適合コード

以下の適合コードは、Unicode コードポイントを int 引数として受け取る Character.isLetter() の整数版を使うことで、補助文字の問題を解決している。int 値を受け取る Java ライブラリのメソッドは、補助文字を含むすべての Unicode 文字に対応している。

public static String trim(String string) {
  int ch;
  int i;
  for (i = 0; i < string.length(); i += Character.charCount(ch)) {
    ch = string.codePointAt(i);
    if (!Character.isLetter(ch)) {
      break;
    }
  } 
  return string.substring(i);
}
リスク評価

部分文字からなる文字列を生成すると、予期せぬ動作を引き起こす可能性がある。

ルール

深刻度

可能性

修正コスト

優先度

レベル

STR01-J

P2

L3

自動検出
ツール バージョン チェッカー 説明
The Checker Framework

2.1.3

Tainting Checker Trust and security errors (see Chapter 8)
Parasoft Jtest

2024.1

CERT.STR01.NCUCP Do not assume that a Java char fully represents a Unicode code point
参考文献

[API 2014]

Classes Character and BreakIterator

[Java Tutorials]

Character Boundaries

[Seacord 2015]
翻訳元

これは以下のページを翻訳したものです。

STR01-J. Do not assume that a Java char fully represents a Unicode code point (revision 123)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter