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

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

このコミットは、Go言語のコンパイラ(gc)におけるシンボル解決のバグ修正に関するものです。具体的には、同じスコープ内で変数と型が同じ名前で宣言された場合にコンパイラがクラッシュする問題を解決しています。

コミット

commit c1e7e270f11feb9adb834f973ab07c0090dcef08
Author: Russ Cox <rsc@golang.org>
Date:   Thu Jan 15 17:32:31 2009 -0800

    don't crash on:

    package main
    var x int
    type x struct { a int }

    R=ken
    OCL=22903
    CL=22903

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

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

元コミット内容

このコミットは、Goコンパイラが以下のコードパターンでクラッシュする問題を修正します。

package main
var x int
type x struct { a int }

このコードは、xという名前が変数と型の両方で宣言されているため、Go言語の仕様上は不正なコードです。しかし、コンパイラはこのような不正なコードに対しても、クラッシュするのではなく、適切なエラーメッセージを出力して処理を停止すべきです。このコミットは、そのクラッシュを防ぐための修正です。

変更の背景

Go言語は、その設計初期段階から、コンパイラの堅牢性とエラーハンドリングの品質を重視していました。コンパイラが不正な入力に対してクラッシュすることは、開発体験を著しく損なうため、このような問題は早期に特定され、修正される必要がありました。

この特定のバグは、シンボルテーブルの管理と型宣言の処理におけるエッジケースに起因していました。var x intxが変数として宣言された後、type x struct { ... }xが型として再宣言される際に、コンパイラが既存のシンボル情報を誤って解釈し、内部的な不整合を引き起こしてクラッシュしていたと考えられます。

Go言語のコンパイラは、シンボル(変数名、型名、関数名など)を管理するためにシンボルテーブルを使用します。シンボルテーブルは、各シンボルの名前、スコープ、型などの情報を格納します。このバグは、dodcltype関数(型宣言を処理する関数)が、既に変数として宣言されているシンボルと同じ名前の型を宣言しようとした際に、シンボルテーブル内の既存のエントリを適切に処理できなかったために発生しました。

前提知識の解説

このコミットを理解するためには、以下の知識が役立ちます。

  1. コンパイラの基本構造:

    • 字句解析 (Lexical Analysis): ソースコードをトークン(単語)に分解します。
    • 構文解析 (Syntax Analysis): トークンの並びが文法的に正しいかチェックし、抽象構文木 (AST) を構築します。
    • 意味解析 (Semantic Analysis): 型チェック、シンボル解決などを行い、プログラムの意味的な正しさを検証します。この段階でシンボルテーブルが活用されます。
    • コード生成 (Code Generation): 実行可能なコードを生成します。 このコミットは主に意味解析の段階、特にシンボル解決と型チェックに関連しています。
  2. シンボルテーブル (Symbol Table): コンパイラがプログラム内の識別子(変数名、関数名、型名など)に関する情報を格納するために使用するデータ構造です。シンボルテーブルには、識別子の名前、その型、スコープ(有効範囲)、メモリ上のアドレスなどの情報が記録されます。コンパイラは、識別子が出現するたびにシンボルテーブルを参照し、その識別子が正しく宣言され、使用されているかを確認します。

  3. Go言語のコンパイラ gc: gcは、Go言語の公式コンパイラであり、Goのソースコードを機械語に変換します。初期のGoコンパイラはC言語で書かれており、src/cmd/gcディレクトリにそのソースコードが格納されていました。このコミットが作成された2009年当時、Go言語はまだ開発の初期段階にあり、コンパイラも活発に開発・改善されていました。

  4. Go言語の型システムと宣言: Go言語では、変数と型は異なる名前空間を持ちません。つまり、同じスコープ内で変数と型に同じ名前を付けることはできません。例えば、var x inttype x struct {}を同じパッケージ内で宣言することはコンパイルエラーとなります。コンパイラは、このような状況を検出し、適切なエラーを報告する必要があります。

  5. Type構造体とSym構造体: Goコンパイラの内部では、Type構造体が型情報を表現し、Sym構造体がシンボル(識別子)情報を表現します。Sym構造体には、そのシンボルが参照する型情報(otypeフィールドなど)が含まれることがあります。

技術的詳細

この修正は、Goコンパイラのsrc/cmd/gc/dcl.cファイル内のdodcltype関数にあります。dodcltype関数は、型宣言(type T struct { ... }type T intなど)を処理する役割を担っています。

問題の箇所は、型宣言の際に、その名前が既にシンボルテーブルに存在するかどうかを確認するロジックです。既存のコードでは、s->block == blockという条件で、現在のスコープ(block)内に同じ名前のシンボル(s)が存在するかどうかをチェックしていました。

しかし、このチェックだけでは不十分でした。var x intのように変数が先に宣言されている場合、xというシンボルは既にシンボルテーブルに登録されており、s->block == blockの条件は真となります。このとき、コンパイラはxが既に「前方宣言された型」であるかのように誤って処理を進めてしまい、結果として内部的な不整合やクラッシュを引き起こしていました。

修正は、この条件に&& s->otype != Tを追加することです。

  • s->block == block: 現在のスコープにシンボルsが存在するかどうか。
  • s->otype != T: シンボルsotype(元の型)がT(不明な型、または初期値)ではないことを確認します。

s->otype != Tという条件が追加されたことで、dodcltype関数は、シンボルsが既に何らかの具体的な型(変数型など)を持っている場合には、それを「前方宣言された型」として扱わないようになります。これにより、var x intで宣言されたxが、type x struct { ... }で再宣言されようとした際に、コンパイラはxが既に変数として存在することを正しく認識し、クラッシュすることなく適切なエラー処理を行うことができるようになりました。

この修正は、コンパイラのシンボル解決ロジックの堅牢性を高め、不正なプログラム入力に対する耐性を向上させるものです。

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

変更はsrc/cmd/gc/dcl.cファイルのdodcltype関数内の一行です。

--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -63,7 +63,7 @@ dodcltype(Type *n)
 	// if n has been forward declared,
 	// use the Type* created then
 	s = n->sym;
-	if(s->block == block) {
+	if(s->block == block && s->otype != T) {
 		switch(s->otype->etype) {
 		case TFORWSTRUCT:
 		case TFORWINTER:

コアとなるコードの解説

dodcltype関数は、Go言語の型宣言を処理するコンパイラの内部関数です。

  • s = n->sym;: nは宣言される型を表すノードで、n->symはその型の名前(シンボル)を取得します。
  • if(s->block == block && s->otype != T) { ... }: このif文は、宣言しようとしている型名sが、現在のスコープ(block)内で既に宣言されているかどうか、そしてそれが「前方宣言された型」であるかどうかをチェックします。
    • s->block == block: シンボルsが現在のスコープblockに属しているかを確認します。
    • s->otype != T: ここが追加された条件です。s->otypeはシンボルsが持つ型情報を指します。Tはコンパイラ内部で「不明な型」や「初期状態の型」を示す定数です。この条件が追加されたことで、シンボルsが既に具体的な型(例えば、var x intで宣言されたint型)を持っている場合は、このifブロック内の処理(前方宣言された型として扱うロジック)には進まないようになります。

この修正により、var x intxが変数として宣言された後、type x struct { a int }xが型として再宣言されようとした場合、s->otypeint型を指すため、s->otype != Tの条件が真となり、ifブロック内の前方宣言処理はスキップされます。これにより、コンパイラはxが既に変数として存在することを正しく認識し、クラッシュすることなく、適切なエラー(例: "x redeclared in this block")を報告できるようになります。

関連リンク

  • Go言語の初期のコミット履歴は、Go言語の進化を理解する上で貴重な情報源です。
  • Go言語のコンパイラに関するドキュメントや論文(例: "The Go Programming Language Specification" や "Go's Declaration Syntax" など)は、より深い理解に役立ちます。

参考にした情報源リンク

  • Go言語の公式GitHubリポジトリ: https://github.com/golang/go
  • Go言語のコンパイラソースコード(特にsrc/cmd/gcディレクトリ)
  • コンパイラ設計に関する一般的な書籍やオンラインリソース
  • Go言語の仕様書: https://go.dev/ref/spec
  • Go言語のブログやメーリングリストのアーカイブ(当時の議論を追うことで、より詳細な背景がわかる可能性があります)
  • Russ Cox氏のブログや発表資料(Go言語の初期開発に関する洞察が得られることがあります)