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

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

このコミットは、Goコンパイラの字句解析器(lexer)におけるエスケープ文字の処理に関する変更です。具体的には、escchar関数のシグネチャが変更され、エスケープされた文字がバイトとして扱われるべきかどうかのフラグが追加されました。これにより、文字列リテラルや文字リテラル内のエスケープシーケンスの解釈がより正確になります。

コミット

commit f9c58c25e0aabf8420aadf12dafd507f97245de6
Author: Ken Thompson <ken@golang.org>
Date:   Sun Jun 8 19:02:27 2008 -0700

    more nihan
    
    SVN=121622

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

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

元コミット内容

    more nihan
    
    SVN=121622

変更の背景

コミットメッセージの「more nihan」は、Go言語の共同開発者の一人であるKen Thompson氏によるものです。Web検索の結果によると、「nihan」はKen Thompson氏が提供した「nih.a」というファイルに由来し、「not invented here」(ここで発明されたものではない)の略であるとされています。これは、Thompson氏の有名なチューリング賞受賞講演「Reflections on Trusting Trust」で示された、コンパイラにバックドアを仕込む可能性に関する概念と関連しています。

この文脈から推測すると、このコミットはGoコンパイラの初期開発段階において、字句解析器の堅牢性や正確性を向上させるための継続的な作業の一部であると考えられます。特に、エスケープ文字の処理は、文字列や文字リテラルの正確な解釈に不可欠であり、セキュリティや言語仕様の厳密な遵守において重要な側面です。escchar関数の変更は、エスケープシーケンスが単なる数値としてではなく、バイトとして扱われるべきかどうかのセマンティクスを明確にする必要があったことを示唆しています。これは、Go言語がUnicodeをネイティブにサポートし、UTF-8エンコーディングを前提としているため、文字とバイトの区別が重要になる背景があります。

前提知識の解説

  • Goコンパイラ: Go言語のソースコードを機械語に変換するプログラムです。初期のGoコンパイラはKen Thompson氏によって開発され、C言語で書かれていました。
  • 字句解析器(Lexer/Scanner): コンパイラの最初の段階であり、ソースコードをトークン(意味のある最小単位)のストリームに変換する役割を担います。例えば、"hello"という文字列は、STRINGトークンとhelloという値に変換されます。
  • エスケープ文字/シーケンス: プログラミング言語において、特殊な意味を持つ文字(例: 改行、タブ、引用符など)を文字列リテラルや文字リテラル内で表現するために使用される特殊な文字の組み合わせです。通常、バックスラッシュ(\)で始まります(例: \n\t\"\xNN\uNNNN)。
  • Rune: Go言語におけるUnicodeコードポイントを表す型です。Goの文字列はUTF-8でエンコードされたバイトのシーケンスですが、runeは単一のUnicode文字を表します。Runeselfは、UTF-8で1バイトで表現できるUnicodeコードポイントの最大値(U+007F、ASCII範囲)を指す定数であると推測されます。
  • vlong: 64ビット整数型(long longに相当)であると推測されます。エスケープシーケンスによって表現される数値(文字コードなど)を格納するために使用されます。
  • remal: reallocのようなメモリ再割り当て関数であると推測されます。字句解析中に文字列バッファのサイズを動的に調整するために使用されます。

技術的詳細

このコミットの主要な変更点は、escchar関数のシグネチャと、それに関連するlex.c内のロジックの修正です。

  1. escchar関数のシグネチャ変更: src/cmd/gc/go.hにおいて、escchar関数のプロトタイプが以下のように変更されました。 変更前: int escchar(int, vlong*); 変更後: int escchar(int, int*, vlong*); 新しい引数int *escflgが追加されました。このescflgは、エスケープされた文字が「バイト」として扱われるべきか(例: \xNN\NNNのような16進数/8進数エスケープ)、それとも通常のUnicodeコードポイントとして扱われるべきかを示すフラグとして機能します。

  2. lex.cにおけるesccharの呼び出しとロジックの変更:

    • yylex関数内で、文字列リテラル(caseq)と文字リテラル(case '\'')の解析において、esccharの呼び出しが新しいシグネチャに合わせて変更されました。 例: escchar('"', &v) から escchar('"', &escflag, &v) へ。
    • escchar関数内で、escflgポインタが初期化され、\x(16進数エスケープ)や8進数エスケープ(\0\7で始まるもの)が検出された場合に*escflg = 1;が設定されるようになりました。これは、これらのエスケープシーケンスがバイト値を表すことを示します。
    • yylexcaseq(文字列リテラル解析)ブロック内で、escflagの導入により、エスケープされた文字の処理ロジックが変更されました。 変更前は、v >= Runeself(Unicode文字として扱われるべき場合)とそれ以外で処理が分かれていました。 変更後は、escflag || v < Runeselfという条件が導入されました。
      • escflagが真の場合(つまり、\xNN\NNNのようなバイトエスケープの場合)、その値vは直接バイトとして文字列バッファに追加されます。
      • escflagが偽で、かつv < Runeselfの場合(つまり、ASCII範囲内の通常の文字の場合)、その値vも直接バイトとして文字列バッファに追加されます。
      • escflagが偽で、かつv >= Runeselfの場合(つまり、ASCII範囲外のUnicode文字の場合)、vruneとして扱われ、runelenrunetocharを使ってUTF-8エンコードされたバイトシーケンスに変換されてから文字列バッファに追加されます。

この変更により、字句解析器はエスケープシーケンスのセマンティクスをより正確に区別できるようになりました。特に、\xNN\NNNのようなエスケープが、Unicodeコードポイントではなく、単一のバイト値として扱われるべきであることを明示的に示すことができるようになりました。これは、Go言語が文字列をUTF-8バイトのシーケンスとして扱うという設計思想と整合しています。

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

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -387,7 +387,7 @@ void	lexinit(void);
 char*	lexname(int);
 long	getr(void);
 int	getnsc(void);
-int	escchar(int, vlong*);
+int	escchar(int, int*, vlong*);
 int	getc(void);
 void	ungetc(int);
 void	mkpackage(char*);

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -175,6 +175,7 @@ yylex(void)
 	vlong v;
 	char *cp;
 	Rune rune;
+	int escflag;
 	Sym *s;
 
 l0:
@@ -224,19 +225,19 @@ l0:
 
 	caseq:
 		for(;;) {
-\t\t\tif(escchar('"', &v))
+\t\t\tif(escchar('"', &escflag, &v))
 				break;
-\t\t\tif(v >= Runeself) {
+\t\t\tif(escflag || v < Runeself) {
+\t\t\t\tcp = remal(cp, c1, 1);
+\t\t\t\tcp[c1++] = v;
+\t\t\t} else {
 				// botch - this limits size of runes
 				rune = v;
 				c = runelen(rune);
 				cp = remal(cp, c1, c);
 				runetochar(cp+c1, &rune);
 				c1 += c;
-\t\t\t\tcontinue;
 \t\t\t}
-\t\t\tcp = remal(cp, c1, 1);
-\t\t\tcp[c1++] = v;
 		}
 	catem:
 
@@ -281,9 +282,9 @@ l0:
 
 	case '\'':
 		/* '.' */
-\t\tif(escchar('\'', &v))
+\t\tif(escchar('\'', &escflag, &v))
 			v = '\'';	// allow '''
-\t\tif(!escchar('\'', &v)) {
+\t\tif(!escchar('\'', &escflag, &v)) {
 			yyerror("missing '");
 			ungetc(v);
 		}
@@ -695,12 +696,13 @@ getnsc(void)
 
 
 int
-escchar(int e, vlong *val)
+escchar(int e, int *escflg, vlong *val)
 {
-\tint i;
-\tlong c;
+\tint i, c;
 	vlong l;
 
+\t*escflg = 0;
+
 loop:
 	c = getr();
 	if(c == '\n') {
@@ -720,6 +722,7 @@ loop:
 		goto loop;
 
 	case 'x':
+\t\t*escflg = 1;\t// it's a byte
 		i = 2;
 		goto hex;
 
@@ -739,6 +743,7 @@ loop:
 	case '5':
 	case '6':
 	case '7':
+\t\t*escflg = 1;\t// it's a byte
 		goto oct;
 
 	case 'a': c = '\a'; break;
@@ -793,6 +798,7 @@ oct:
 	}\n\
 	if(l > 255)\n\
 		warn("oct escape value > 255: %d", l);\n\
+\n\
 	*val = l;\n\
 	return 0;\n\
 }\n

コアとなるコードの解説

このコミットの核心は、Go言語の字句解析器が文字列リテラルや文字リテラル内のエスケープシーケンスをどのように解釈するかをより厳密に制御することにあります。

  1. escchar関数の役割の拡張: 以前のescchar関数は、エスケープシーケンスを解析し、その結果の数値(vlong)を返すだけでした。しかし、Go言語では文字列がUTF-8バイトのシーケンスであり、\xNN\NNNのようなエスケープは単一のバイト値を表すのに対し、\uNNNN\UHHHHHHHHはUnicodeコードポイントを表します。この区別は、文字列の内部表現において重要です。 新しいescchar関数は、escflgという新しい出力パラメータを受け取ります。このフラグは、解析されたエスケープシーケンスが「バイト」として扱われるべきか(escflg = 1)、それとも「Unicodeコードポイント」として扱われるべきか(escflg = 0)を示します。具体的には、\xエスケープや8進数エスケープが検出された場合にescflgが1に設定されます。

  2. yylexにおけるエスケープ処理の分岐: yylex関数内の文字列リテラル解析(caseq)では、esccharから返されたescflagの値に基づいて、文字の処理方法が分岐するようになりました。

    • escflagが真(1)の場合、またはvRuneself未満の場合(ASCII文字の場合): これは、エスケープされた値が単一のバイトとして文字列バッファに直接追加されるべきであることを意味します。\xNN\NNNのようなエスケープは、Goの文字列がバイトのシーケンスであるため、直接バイトとして扱われます。ASCII文字も同様に1バイトとして扱われます。
    • escflagが偽(0)で、かつvRuneself以上の場合: これは、エスケープされた値がUnicodeコードポイント(rune)として扱われるべきであることを意味します。この場合、vrune型に変換され、runelen(UTF-8エンコード後のバイト数を計算)とrunetocharruneをUTF-8バイトシーケンスに変換)を使って、UTF-8エンコードされたバイトシーケンスとして文字列バッファに追加されます。

この変更により、Goコンパイラの字句解析器は、エスケープシーケンスのセマンティクスをより正確に区別し、Go言語の文字列とruneの厳密な定義に沿った形で、ソースコード内のリテラルを正しく内部表現に変換できるようになりました。これは、コンパイラの正確性と堅牢性を高める上で重要な改善です。

関連リンク

参考にした情報源リンク