Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 16162] ファイルの概要

このコミットは、Go言語のコンパイラ(gc)において、エクスポートされた文字列リテラルに含まれるUnicodeのバイトオーダーマーク(BOM: Byte Order Mark)が正しくエスケープされず、パッケージインポート時に問題を引き起こすバグ(Issue 5260)を修正するものです。具体的には、BOM文字(U+FEFF)が文字列リテラル内で検出された際に、\uFEFFとして適切にエスケープされるようにコンパイラのフォーマット処理が変更されました。

コミット

commit a9f1569e7bff932eada0c4c691123e3c1177b6f0
Author: Volker Dobler <dr.volker.dobler@gmail.com>
Date:   Thu Apr 11 11:45:18 2013 -0700

    gc: escape unicode BOM in exported string literals
    
    Fixes #5260.
    
    R=golang-dev, minux.ma, 0xjnml, r
    CC=golang-dev
    https://golang.org/cl/8658043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a9f1569e7bff932eada0c4c691123e3c1177b6f0

元コミット内容

gc: escape unicode BOM in exported string literals

Fixes #5260.

変更の背景

この変更は、Go言語のIssue 5260「Unicode BOM in exported string constant cannot be read back during package import.」を修正するために行われました。この問題は、Goのソースコード内でエクスポートされる文字列定数にUnicodeのバイトオーダーマーク(BOM、U+FEFF)が含まれている場合、その定数を別のパッケージがインポートしようとすると、コンパイラがBOMを正しく処理できず、エラーや予期せぬ動作を引き起こすというものでした。

BOMは、UTF-8などのUnicodeエンコーディングでファイルの先頭に付加されることがある特殊な文字で、バイトオーダー(バイトの並び順)を示すために使用されます。しかし、UTF-8においてはBOMは必須ではなく、むしろ多くのシステムやツールで問題を引き起こす可能性があるため、推奨されないことが多いです。特に、Goのコンパイラが文字列リテラルを処理する際に、このBOMが通常の文字として扱われてしまい、エクスポートされた定数のバイナリ表現が期待通りにならず、結果としてインポート側で正しく読み取れないというバグが発生していました。

このコミットは、コンパイラが文字列リテラルを処理する際にBOMを検出し、それをGoの文字列リテラル内で安全に表現できるUnicodeエスケープシーケンス(\uFEFF)に変換することで、この問題を解決することを目的としています。

前提知識の解説

Unicode BOM (Byte Order Mark)

Unicode BOM(バイトオーダーマーク)は、Unicodeテキストの先頭に挿入される特殊な文字(U+FEFF)です。主に、テキストファイルのエンコーディング(UTF-8, UTF-16, UTF-32など)と、UTF-16やUTF-32におけるバイトオーダー(ビッグエンディアンまたはリトルエンディアン)を示すために使用されます。

  • UTF-8におけるBOM: UTF-8ではバイトオーダーの概念がないため、BOMは厳密には不要です。しかし、一部のWindowsアプリケーションなどでは、UTF-8ファイルであることを明示するためにBOMを付加することがあります。このBOMは、EF BB BFという3バイトのシーケンスで表現されます。BOMが存在すると、それを正しく解釈できないパーサーやツールで問題が発生することがあります。Goのコンパイラも、このBOMを通常の文字として扱ってしまうことで、文字列リテラルの内部表現に予期せぬ影響を与えていました。

Go言語のコンパイラ (gc)

gcは、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイルプロセスには、字句解析、構文解析、意味解析、中間コード生成、最適化、コード生成などが含まれます。このコミットで変更されたfmt.cは、コンパイラのバックエンドの一部であり、Goの内部表現を最終的な出力形式(この場合は文字列リテラルのエスケープ処理)に変換する役割を担っています。

Go言語のパッケージエクスポート/インポート

Go言語では、コードはパッケージに分割されます。パッケージ内の識別子(変数、定数、関数、型など)は、先頭が大文字で始まることでエクスポートされ、他のパッケージから参照可能になります。他のパッケージの識別子を使用するには、import文を使ってそのパッケージをインポートします。

コンパイラは、エクスポートされた識別子の情報を、コンパイルされたパッケージのメタデータとして保存します。別のパッケージがそのパッケージをインポートする際、コンパイラはこのメタデータを読み込み、エクスポートされた識別子の型や値などを解決します。今回の問題は、エクスポートされた文字列定数にBOMが含まれている場合、このメタデータが正しく生成されず、インポート時に読み取りエラーが発生するというものでした。

技術的詳細

Goのコンパイラgcは、ソースコード中の文字列リテラルを処理する際に、その内容を内部的に表現し、必要に応じてエスケープ処理を行います。このコミットの対象となったsrc/cmd/gc/fmt.cファイルは、コンパイラが文字列やその他のデータをフォーマットして出力する際のロジックを含んでいます。

問題の核心は、GoのコンパイラがUnicodeのBOM文字(U+FEFF)を、通常の表示可能な文字と同様に扱ってしまっていた点にあります。Goの文字列リテラルはUTF-8でエンコードされますが、BOMは通常、文字列の内容の一部としてではなく、エンコーディングのメタデータとして扱われるべきものです。しかし、コンパイラがBOMを通常の文字として解釈し、それをそのまま文字列リテラルの内部表現に含めてしまうと、以下のような問題が発生します。

  1. バイナリ表現の不一致: エクスポートされた文字列定数がBOMを含む場合、そのバイナリ表現が、BOMを含まない同じ文字列とは異なるものになります。
  2. インポート時の問題: 別のパッケージがこの定数をインポートしようとすると、コンパイラはBOMを含む文字列のバイナリ表現を正しく解釈できず、予期せぬエラーや、文字列の内容が破損したと見なされる可能性がありました。これは、Goのコンパイラがパッケージ間の依存関係を解決し、エクスポートされたシンボルを読み込む際の内部的な整合性チェックに影響を与えていたと考えられます。

この修正では、fmt.c内の文字列フォーマット関数において、文字を処理するループ内でBOM文字(0xFEFF)が検出された場合に、それを直接出力するのではなく、Goの文字列リテラルでUnicode文字を表現するためのエスケープシーケンスである\uFEFFに変換するように変更しました。これにより、BOMが文字列の内容としてではなく、その文字コードを明示的に示すエスケープシーケンスとして扱われるようになり、エクスポートされた文字列定数のバイナリ表現が標準的かつ予測可能なものとなり、インポート時の問題が解消されます。

コアとなるコードの変更箇所

src/cmd/gc/fmt.cZconv 関数に以下の変更が加えられました。

--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -443,6 +443,9 @@ Zconv(Fmt *fp)
 		case 0xFEFF: // BOM, basically disallowed in source code
 		    fmtstrcpy(fp, "\\uFEFF");
 		    break;
 		}
 	}
 	return 0;

また、この修正を検証するためのテストケースが追加されています。

  • test/fixedbugs/issue5260.dir/a.go
  • test/fixedbugs/issue5260.dir/b.go
  • test/fixedbugs/issue5260.go

コアとなるコードの解説

変更はsrc/cmd/gc/fmt.cファイルのZconv関数内で行われています。この関数は、Goコンパイラが文字列リテラルなどの値をフォーマットする際に使用されるものです。

追加されたコードは以下の通りです。

		case 0xFEFF: // BOM, basically disallowed in source code
			fmtstrcpy(fp, "\\uFEFF");
			break;
  • case 0xFEFF:: これは、現在処理しているUnicode文字がBOM(U+FEFF)であるかどうかをチェックしています。
  • // BOM, basically disallowed in source code: コメントは、BOMがソースコード内で基本的に許可されていないことを示唆しています。これは、BOMが多くのツールで問題を引き起こす可能性があるため、Goのソースコードでは使用を避けるべきであるという一般的な慣習を反映しています。
  • fmtstrcpy(fp, "\\uFEFF");: BOM文字が検出された場合、コンパイラはそれを直接出力する代わりに、文字列\uFEFFをフォーマット出力ストリーム(fp)に書き込みます。\uFEFFはGoの文字列リテラルにおけるUnicodeエスケープシーケンスであり、U+FEFF文字を明示的に表現します。

この変更により、Goのコンパイラは、ソースコード内の文字列リテラルにBOMが含まれていても、それを安全なエスケープシーケンスに変換して処理するようになります。これにより、エクスポートされた文字列定数がBOMを含んでいても、他のパッケージがそれを正しくインポートし、利用できるようになります。

関連リンク

参考にした情報源リンク

  • Go issue 5260: Unicode BOM in exported string constant cannot be read back during package import. (Google Search Result)
  • Unicode Byte Order Mark (BOM) - Wikipedia (一般的なBOMの知識)
  • Go言語のパッケージとエクスポート/インポート (一般的なGoのパッケージに関する知識)