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 |
[Java Tutorials] |
|
[Seacord 2015] |
翻訳元
これは以下のページを翻訳したものです。
STR01-J. Do not assume that a Java char fully represents a Unicode code point (revision 123)