[インデックス 14070] ファイルの概要
このコミットは、Go言語のコンパイラであるcmd/gc
において、ソースコードファイル内に存在するBOM(Byte Order Mark)を適切にスキップする機能を追加するものです。これにより、BOMが原因で発生する可能性のあるコンパイルエラーを防ぎ、より堅牢なコンパイラ動作を実現します。
コミット
commit d749783f70843741d6469f6c7edc77bfd820c1a6
Author: Russ Cox <rsc@golang.org>
Date: Sun Oct 7 16:35:45 2012 -0400
cmd/gc: skip over reported BOMs
This keeps the BOM runes from causing other errors.
R=golang-dev, remyoudompheng
CC=golang-dev
https://golang.org/cl/6625062
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d749783f70843741d6469f6c7edc77bfd820c1a6
元コミット内容
cmd/gc: skip over reported BOMs
This keeps the BOM runes from causing other errors.
変更の背景
Go言語のソースコードは通常UTF-8エンコーディングで記述されます。しかし、一部のテキストエディタやIDEは、UTF-8ファイルにBOM(Byte Order Mark)を付加して保存する場合があります。BOMは、ファイルのエンコーディングやバイト順を示すための特殊なバイト列ですが、UTF-8においては必須ではなく、むしろ問題を引き起こすことがあります。
Goコンパイラgc
は、ソースファイルを読み込む際に、このBOMを通常の文字として解釈してしまう可能性がありました。BOMがファイルの先頭に存在する場合、それは通常無視されるか、特別な処理が施されますが、ファイルの途中にBOMが挿入された場合(例えば、ファイルを結合した際など)、コンパイラがこれを予期しない文字として扱い、構文エラーやその他の予期せぬエラーを引き起こす可能性がありました。
このコミットは、このようなBOMが原因で発生するコンパイルエラーを回避するために、gc
コンパイラがソースコードを読み込む際にBOMを検出し、それをスキップするように修正することを目的としています。これにより、BOMを含むファイルでもGoコンパイラが正しく動作し、開発者の利便性が向上します。
前提知識の解説
BOM (Byte Order Mark)
BOM(Byte Order Mark)は、Unicodeテキストファイルの先頭に付加される特殊なバイト列で、そのファイルのエンコーディング(UTF-8, UTF-16, UTF-32など)と、UTF-16やUTF-32におけるバイト順(ビッグエンディアンまたはリトルエンディアン)を示すために使用されます。
- UTF-8におけるBOM: UTF-8はバイト順の概念を持たないため、BOMは必須ではありません。UTF-8のBOMは
EF BB BF
という3バイトのシーケンスで表現されます。多くのシステムやアプリケーションはUTF-8 BOMを正しく処理できますが、一部のパーサーやコンパイラはこれを通常の文字として解釈してしまい、問題を引き起こすことがあります。特に、スクリプト言語のインタープリタやコンパイラでは、BOMが予期しない構文エラーや実行時エラーの原因となることがあります。
UTF-8
UTF-8(Unicode Transformation Format - 8-bit)は、Unicode文字を可変長でエンコードする文字エンコーディング方式です。ASCII文字は1バイトで表現され、それ以外の文字は2バイトから4バイトで表現されます。UTF-8はウェブ上で最も広く使用されている文字エンコーディングであり、Go言語のソースコードも通常UTF-8で記述されます。
Goコンパイラ (gc
)
gc
は、Go言語の公式コンパイラです。Goのソースコード(.go
ファイル)を機械語に変換し、実行可能なバイナリを生成する役割を担っています。gc
は、字句解析(lexing)、構文解析(parsing)、型チェック、最適化、コード生成などの複数のフェーズを経てコンパイルを行います。このコミットで変更されるsrc/cmd/gc/lex.c
は、字句解析器の一部であり、ソースコードの文字を読み込み、トークンに変換する初期段階の処理を担当しています。
yyerrorl
と yyerror
これらは、Goコンパイラの字句解析器や構文解析器で使用されるエラー報告関数です。
yyerrorl(lineno, message)
: 指定された行番号(lineno
)でエラーメッセージ(message
)を報告します。yyerror(message)
: 現在の行番号でエラーメッセージを報告します。
これらの関数は、コンパイル中に問題が検出された際に、ユーザーにエラーの内容と発生箇所を伝えるために使用されます。
技術的詳細
このコミットは、Goコンパイラの字句解析器の一部であるsrc/cmd/gc/lex.c
ファイル内のgetc
関数を変更しています。getc
関数は、ソースコードファイルから1文字ずつ読み込む役割を担っています。
変更の核心は、getc
関数がファイルからバイトを読み込む際に、UTF-8 BOMのシーケンス(0xef 0xbb 0xbf
)を検出した場合に、それをスキップし、エラーメッセージを報告するロジックを追加した点です。
具体的な変更点は以下の通りです。
-
変数の追加:
getc
関数内に、BOMの検出に使用する一時変数c1
,c2
が追加されました。- int c; + int c, c1, c2;
-
BOM検出ロジックの追加:
Bgetc(curio.bin)
で文字を読み込んだ後、その文字が0xef
(UTF-8 BOMの最初のバイト)であるかをチェックします。+ } else { + loop: + c = Bgetc(curio.bin); + if(c == 0xef) { + c1 = Bgetc(curio.bin); + c2 = Bgetc(curio.bin); + if(c1 == 0xbb && c2 == 0xbf) { + yyerrorl(lexlineno, "Unicode (UTF-8) BOM in middle of file"); + goto loop; + } + Bungetc(curio.bin); + Bungetc(curio.bin); + } + }
c == 0xef
の場合、さらに次の2バイト(c1
,c2
)を読み込みます。- もし
c1 == 0xbb
かつc2 == 0xbf
であれば、それはUTF-8 BOMの完全なシーケンス(0xef 0xbb 0xbf
)であると判断されます。 - この場合、
yyerrorl(lexlineno, "Unicode (UTF-8) BOM in middle of file")
を呼び出して、ファイルの途中にBOMが存在するという警告(またはエラー)を報告します。 - その後、
goto loop;
によってgetc
関数の先頭に戻り、BOMの3バイトをスキップして次の文字の読み込みを続行します。 - もし
c == 0xef
であったが、続く2バイトがBOMのシーケンスでなかった場合(つまり、単なる0xef
というバイトであった場合)、読み込んだc1
とc2
をBungetc(curio.bin)
でストリームに戻します。これは、BOMではないバイトを誤って読み飛ばさないようにするためです。
-
既存のBOMチェックの削除: 以前は、
chartorune
でルーンに変換した後にrune == BOM
というチェックがあり、BOMが検出された場合にyyerror("Unicode (UTF-8) BOM in middle of file")
を呼び出していました。このチェックは、新しいBOM検出ロジックによって置き換えられるため、削除されました。- if(rune == BOM) { - lineno = lexlineno; - yyerror("Unicode (UTF-8) BOM in middle of file"); - }
この変更により、BOMの検出とスキップが、より低レベルのバイト読み込み段階で行われるようになり、字句解析の早い段階でBOMの問題に対処できるようになります。
コアとなるコードの変更箇所
diff --git a/src/cmd/gc/lex.c b/src/cmd/gc/lex.c
index 601f182997..703bb127df 100644
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1532,7 +1532,7 @@ yylex(void)
static int
getc(void)
{
- int c;
+ int c, c1, c2;
c = curio.peekc;
if(c != 0) {
@@ -1545,8 +1545,20 @@ getc(void)
c = *curio.cp & 0xff;
if(c != 0)
curio.cp++;
- } else
+ } else {
+ loop:
c = Bgetc(curio.bin);
+ if(c == 0xef) {
+ c1 = Bgetc(curio.bin);
+ c2 = Bgetc(curio.bin);
+ if(c1 == 0xbb && c2 == 0xbf) {
+ yyerrorl(lexlineno, "Unicode (UTF-8) BOM in middle of file");
+ goto loop;
+ }
+ Bungetc(curio.bin);
+ Bungetc(curio.bin);
+ }
+ }
check:
switch(c) {
@@ -1597,10 +1609,6 @@ loop:
if(!fullrune(str, i))
goto loop;
c = chartorune(&rune, str);
- if(rune == BOM) {
- lineno = lexlineno;
- yyerror("Unicode (UTF-8) BOM in middle of file");
- }
if(rune == Runeerror && c == 1) {
lineno = lexlineno;
yyerror("illegal UTF-8 sequence");
コアとなるコードの解説
変更の中心はsrc/cmd/gc/lex.c
ファイル内のgetc
関数です。この関数は、コンパイラがソースコードファイルを読み込む際の最も基本的な操作、すなわち「次のバイトを取得する」ことを担当しています。
-
新しい変数の導入:
int c, c1, c2;
c
は現在読み込んでいるバイト、c1
とc2
はBOMの残りのバイトを一時的に保持するために導入されました。 -
BOM検出とスキップのロジック:
+ } else { + loop: + c = Bgetc(curio.bin); + if(c == 0xef) { + c1 = Bgetc(curio.bin); + c2 = Bgetc(curio.bin); + if(c1 == 0xbb && c2 == 0xbf) { + yyerrorl(lexlineno, "Unicode (UTF-8) BOM in middle of file"); + goto loop; + } + Bungetc(curio.bin); + Bungetc(curio.bin); + } + }
c = Bgetc(curio.bin);
:ファイルから次のバイトを読み込みます。if(c == 0xef)
:読み込んだバイトがUTF-8 BOMの最初のバイト(0xef
)であるかをチェックします。c1 = Bgetc(curio.bin); c2 = Bgetc(curio.bin);
:もしc
が0xef
であれば、BOMの残りの2バイトを読み込みます。if(c1 == 0xbb && c2 == 0xbf)
:読み込んだ3バイトが完全なUTF-8 BOMシーケンス(0xef 0xbb 0xbf
)であるかを検証します。yyerrorl(lexlineno, "Unicode (UTF-8) BOM in middle of file");
:もしBOMが検出された場合、現在の行番号(lexlineno
)で「ファイルの途中にUnicode (UTF-8) BOMがあります」というエラーメッセージを報告します。これは、BOMがファイルの先頭以外に存在する場合に特に問題となるため、警告としてユーザーに通知します。goto loop;
:BOMの3バイトを読み飛ばし、loop
ラベルに戻って次の有効な文字の読み込みを再開します。これにより、BOMがコンパイラの字句解析に影響を与えるのを防ぎます。Bungetc(curio.bin); Bungetc(curio.bin);
:もしc
が0xef
であったが、続くバイトがBOMのシーケンスではなかった場合、読み込んだc1
とc2
をファイルストリームに戻します。これは、0xef
で始まるがBOMではない正当なUTF-8シーケンスを誤ってスキップしないようにするための重要な処理です。
-
古いBOMチェックの削除:
- if(rune == BOM) { - lineno = lexlineno; - yyerror("Unicode (UTF-8) BOM in middle of file"); - }
以前は、
chartorune
関数でバイト列をルーン(Unicodeコードポイント)に変換した後、そのルーンがBOM
定数と一致するかどうかをチェックしていました。このチェックは、新しいバイトレベルでのBOM検出ロジックによって冗長になるため削除されました。新しいアプローチは、より低レベルで効率的にBOMを処理します。
この変更により、GoコンパイラはBOMを含むソースファイルをより堅牢に処理できるようになり、開発者が遭遇する可能性のあるコンパイルエラーを減らすことができます。
関連リンク
- Go CL 6625062: https://golang.org/cl/6625062
参考にした情報源リンク
- Byte Order Mark (BOM) - Wikipedia: https://en.wikipedia.org/wiki/Byte_order_mark
- UTF-8 - Wikipedia: https://en.wikipedia.org/wiki/UTF-8
- Go Programming Language: https://golang.org/