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

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

このコミットは、Go言語のコンパイラ(gc)の字句解析器(lexer)において、ソースコード中に含まれる不可視の制御文字(ホワイトスペースではないもの)を検出してエラーとして報告する機能を追加するものです。これにより、開発者が意図しない文字によって引き起こされる潜在的なバグや混乱を防ぎます。

コミット

commit 2538cf747bb5731702b801cc924daff2d12a43da
Author: Rob Pike <r@golang.org>
Date:   Sat Jan 31 16:42:10 2009 -0800

    Complain about control characters that are not white space.
    Bitten by invisible chars too many times.
    
    R=ken
    OCL=24024
    CL=24024

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

https://github.com/golang/go/commit/2538cf747bb5731702b801cc924daff2d12a43da

元コミット内容

「ホワイトスペースではない制御文字について苦情を言う。不可視の文字に何度も悩まされてきた。」

このコミットメッセージは、Go言語の初期開発者の一人であるRob Pike氏が、ソースコード中に紛れ込む不可視の制御文字によって引き起こされる問題に直面し、その対策としてコンパイラに検出機能を追加したことを示しています。

変更の背景

プログラミングにおいて、ソースコードは通常、可視文字と標準的なホワイトスペース(スペース、タブ、改行など)で構成されます。しかし、テキストエディタやコピー&ペーストの操作によっては、目に見えない制御文字が紛れ込むことがあります。これらの文字は、表示上は空白に見えたり、全く表示されなかったりするため、開発者がその存在に気づきにくいという問題があります。

不可視の制御文字がソースコードに存在すると、以下のような問題を引き起こす可能性があります。

  • コンパイルエラー: コンパイラが予期しない文字として認識し、エラーを発生させる。
  • 予期せぬ動作: 特定の環境やツールで異なる解釈をされ、プログラムの動作が変わる。
  • デバッグの困難さ: 目に見えないため、問題の原因特定が極めて困難になる。
  • コードの可読性低下: 意図しない文字が混入することで、コードの整合性が損なわれる。

このコミットは、Rob Pike氏が「Bitten by invisible chars too many times.(不可視の文字に何度も悩まされてきた)」と述べているように、Go言語のコンパイラがこれらの問題を早期に検出し、開発者に警告することで、より堅牢でデバッグしやすい開発環境を提供することを目的としています。

前提知識の解説

字句解析器(Lexer/Scanner/Tokenizer)

コンパイラの最初の段階であり、ソースコードを読み込み、意味のある最小単位(トークン)に分割する役割を担います。例えば、int x = 10;というコードは、int(キーワード)、x(識別子)、=(演算子)、10(リテラル)、;(区切り文字)といったトークンに分割されます。字句解析器は、これらのトークンを認識する際に、不要なホワイトスペースやコメントをスキップします。

制御文字(Control Characters)

ASCIIコード(0x00から0x1F、および0x7F)やUnicodeの特定の範囲に存在する、印字されない特殊な文字です。これらは通常、通信プロトコルや端末制御、テキストフォーマットなどに使用されます。一般的な例としては、改行(LF, 0x0A)、復帰(CR, 0x0D)、タブ(HT, 0x09)などがありますが、それ以外の多くの制御文字はソースコードには不適切です。

ホワイトスペース(Whitespace Characters)

ソースコードの可読性を高めるために使用される、プログラムの実行には影響しない文字です。スペース(0x20)、タブ(0x09)、改行(0x0A)、復帰(0x0D)、フォームフィード(0x0C)などがこれに該当します。コンパイラの字句解析器は、これらの文字をスキップするか、トークンの区切りとして利用します。

Go言語のgcコンパイラ

Go言語の公式コンパイラは、初期にはgc(Go Compiler)と呼ばれていました。src/cmd/gc/ディレクトリには、そのコンパイラのソースコードが含まれていました。lex.cファイルは、このコンパイラの字句解析器の実装の一部です。

RuneselfとUTF-8

Go言語は、ソースコードがUTF-8でエンコードされていることを前提としています。runeはGoにおけるUnicodeコードポイントを表す型です。Runeselfは、UTF-8エンコーディングにおいて1バイトで表現できる文字(ASCII文字)の最大値(0x7F)を指すことが多いです。fullruneは、与えられたバイト列が完全なUTF-8ルーンを構成しているかをチェックする関数です。Goの初期のコンパイラでは、マルチバイト文字(非ASCII文字)の処理において、これらの概念が重要でした。

技術的詳細

このコミットの核心は、isfrogという新しい関数の導入と、既存の字句解析ロジックへのその組み込みです。

isfrog関数のロジック

isfrog関数は、与えられた文字(int cまたはrune)が「不正な」制御文字であるかどうかを判定します。

  1. 負の値のチェック:

    if(c < 0)
        return 1;
    

    c < 0は、ファイルの終端(EOF)や読み込みエラーなど、不正な文字コードを示す場合に1(不正)を返します。

  2. ASCII制御文字のチェック:

    if(c < ' ') {
        if(c == ' ' || c == '\n' || c== '\r' || c == '\t') // good white space
            return 0;
        return 1;
    }
    

    c < ' 'は、ASCIIコードでスペース(0x20)より小さい文字、つまりASCII制御文字(0x00-0x1F)を対象とします。 その中で、標準的なホワイトスペースであるスペース(0x20)、改行(\n, 0x0A)、復帰(\r, 0x0D)、タブ(\t, 0x09)は「良いホワイトスペース」として除外されます。これら以外のASCII制御文字は1(不正)を返します。

  3. Unicode制御ブロックのチェック:

    if(0x80 <= c && c <=0xa0) // unicode block including unbreakable space.
        return 1;
    

    この範囲(0x80から0xA0)は、Unicodeの特定の制御文字ブロックを含みます。例えば、0xA0はノーブレークスペース(No-Break Space)であり、見た目はスペースですが通常のスペースとは異なるため、意図しない挙動を引き起こす可能性があります。この範囲の文字も1(不正)と判定されます。

  4. その他の文字: 上記以外の文字は0(正常)を返します。

字句解析器への組み込み

isfrog関数は、yylex関数(字句解析のメインループ)と、マルチバイト文字(rune)を処理するtalphセクションの2箇所で呼び出されます。

  • yylex内での呼び出し: 単一の文字を読み込む際に、その文字がisfrogによって不正と判定された場合、yyerror関数を呼び出してエラーメッセージ「illegal character 0x%ux」を出力し、字句解析を継続します(goto l0)。

  • talph内での呼び出し: マルチバイト文字(UTF-8でエンコードされたrune)を読み込む際に、fullruneで完全なruneが形成された後、そのruneがisfrogによって不正と判定された場合も同様にyyerrorを呼び出してエラーを報告します。

この変更により、コンパイラはソースコード中の不可視の制御文字を積極的に検出し、開発者にその存在を警告できるようになりました。

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

src/cmd/gc/lex.cファイルに以下の変更が加えられました。

  1. isfrog関数の追加:

    +int
    +isfrog(int c) {
    +	// complain about possibly invisible control characters
    +	if(c < 0)
    +		return 1;
    +	if(c < ' ') {
    +		if(c == ' ' || c == '\n' || c== '\r' || c == '\t')	// good white space
    +			return 0;
    +		return 1;
    +	}
    +	if(0x80 <= c && c <=0xa0)	// unicode block including unbreakable space.
    +		return 1;
    +	return 0;
    +}
    
  2. yylex関数内でのisfrogの呼び出し:

    @@ -645,6 +660,10 @@ lx:
     	else
     		DBG("%L lex: TOKEN '%c'\n", lineno, c);
    +	if(isfrog(c)) {
    +		yyerror("illegal character 0x%ux", c);
    +		goto l0;
    +	}
     	return c;
    
  3. talph関数内でのisfrogの呼び出し(マルチバイト文字処理):

    @@ -661,8 +680,14 @@ talph:
     		if(c >= Runeself) {
     			for(c1=0;;) {
     				cp[c1++] = c;
    -				if(fullrune(cp, c1))
    +				if(fullrune(cp, c1)) {
    +					chartorune(&rune, cp);
    +					 if(isfrog(rune)) {
    +					 	yyerror("illegal character 0x%ux", rune);
    +					 	goto l0;
    +					 }
     					break;
    +				}
     				c = getc();
     			}
     			cp += c1;
    

コアとなるコードの解説

このコミットは、Goコンパイラの字句解析フェーズにおいて、ソースコードの文字を読み込むたびに、それが不正な制御文字でないかをチェックするメカニズムを導入しています。

isfrog関数は、文字が以下のいずれかの条件を満たす場合に「不正」と判断します。

  • 負の値(エラーまたはEOFを示す)
  • ASCII制御文字のうち、標準的なホワイトスペース(スペース、タブ、改行、復帰)ではないもの
  • Unicodeの0x80から0xA0の範囲にある文字(ノーブレークスペースなど、見た目は空白だが通常の空白とは異なる文字を含む)

yylexは、ソースコードから1文字ずつ読み込み、トークンを形成する主要な関数です。この関数内でisfrog(c)が呼び出されることで、読み込んだ単一文字が不正な制御文字である場合に即座にエラーが報告されます。

talphは、GoがUTF-8をサポートしているため、マルチバイト文字(rune)を処理する部分です。fullruneが完全なruneを構成したと判断した後、chartoruneでそのruneを取得し、isfrog(rune)を呼び出します。これにより、マルチバイト文字の中に不正な制御文字が隠れている場合でも、適切に検出してエラーを報告できるようになります。

これらの変更により、Goコンパイラは、開発者が気づきにくい不可視の文字による問題を、コンパイル時に早期に発見し、修正を促すことができるようになりました。これは、Go言語の堅牢性と開発体験の向上に貢献する重要な改善です。

関連リンク

参考にした情報源リンク