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

[インデックス 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は、字句解析器の一部であり、ソースコードの文字を読み込み、トークンに変換する初期段階の処理を担当しています。

yyerrorlyyerror

これらは、Goコンパイラの字句解析器や構文解析器で使用されるエラー報告関数です。

  • yyerrorl(lineno, message): 指定された行番号(lineno)でエラーメッセージ(message)を報告します。
  • yyerror(message): 現在の行番号でエラーメッセージを報告します。

これらの関数は、コンパイル中に問題が検出された際に、ユーザーにエラーの内容と発生箇所を伝えるために使用されます。

技術的詳細

このコミットは、Goコンパイラの字句解析器の一部であるsrc/cmd/gc/lex.cファイル内のgetc関数を変更しています。getc関数は、ソースコードファイルから1文字ずつ読み込む役割を担っています。

変更の核心は、getc関数がファイルからバイトを読み込む際に、UTF-8 BOMのシーケンス(0xef 0xbb 0xbf)を検出した場合に、それをスキップし、エラーメッセージを報告するロジックを追加した点です。

具体的な変更点は以下の通りです。

  1. 変数の追加: getc関数内に、BOMの検出に使用する一時変数c1, c2が追加されました。

    -	int c;
    +	int c, c1, c2;
    
  2. 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というバイトであった場合)、読み込んだc1c2Bungetc(curio.bin)でストリームに戻します。これは、BOMではないバイトを誤って読み飛ばさないようにするためです。
  3. 既存の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関数です。この関数は、コンパイラがソースコードファイルを読み込む際の最も基本的な操作、すなわち「次のバイトを取得する」ことを担当しています。

  1. 新しい変数の導入: int c, c1, c2; cは現在読み込んでいるバイト、c1c2はBOMの残りのバイトを一時的に保持するために導入されました。

  2. 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);:もしc0xefであれば、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);:もしc0xefであったが、続くバイトがBOMのシーケンスではなかった場合、読み込んだc1c2をファイルストリームに戻します。これは、0xefで始まるがBOMではない正当なUTF-8シーケンスを誤ってスキップしないようにするための重要な処理です。
  3. 古いBOMチェックの削除:

    -	if(rune == BOM) {
    -		lineno = lexlineno;
    -		yyerror("Unicode (UTF-8) BOM in middle of file");
    -	}
    

    以前は、chartorune関数でバイト列をルーン(Unicodeコードポイント)に変換した後、そのルーンがBOM定数と一致するかどうかをチェックしていました。このチェックは、新しいバイトレベルでのBOM検出ロジックによって冗長になるため削除されました。新しいアプローチは、より低レベルで効率的にBOMを処理します。

この変更により、GoコンパイラはBOMを含むソースファイルをより堅牢に処理できるようになり、開発者が遭遇する可能性のあるコンパイルエラーを減らすことができます。

関連リンク

参考にした情報源リンク