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

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

このコミットは、Goコンパイラのcmd/gcにおけるisgoconst関数で発生していたセグメンテーション違反(segfault)を修正するものです。具体的には、varキーワードで宣言された変数がsym->defを持たない場合に、isgoconst関数が不正なメモリアクセスを行う問題を解決します。

コミット

commit 32dffef0980eb810b97f48eb9dfabb33602a0472
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed Apr 16 23:12:06 2014 -0400

    cmd/gc: fix segfault in isgoconst.
    
    Variables declared with 'var' have no sym->def.
    
    Fixes #7794.
    
    LGTM=rsc
    R=golang-codereviews, bradfitz, rsc
    CC=golang-codereviews
    https://golang.org/cl/88360043
---
 src/cmd/gc/const.c          |  2 +-\n test/fixedbugs/issue7794.go | 12 ++++++++++++\n 2 files changed, 13 insertions(+), 1 deletion(-)\n
diff --git a/src/cmd/gc/const.c b/src/cmd/gc/const.c
index 28d0725d33..f356c4f59a 100644
--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -1594,7 +1594,7 @@ isgoconst(Node *n)\n \n 	case ONAME:\n 		l = n->sym->def;\n-\t\tif(l->op == OLITERAL && n->val.ctype != CTNIL)\n+\t\tif(l && l->op == OLITERAL && n->val.ctype != CTNIL)\n \t\t\treturn 1;\n \t\tbreak;\n \t\ndiff --git a/test/fixedbugs/issue7794.go b/test/fixedbugs/issue7794.go
new file mode 100644
index 0000000000..1e303bd4f2
--- /dev/null
+++ b/test/fixedbugs/issue7794.go
@@ -0,0 +1,12 @@
+// compile
+\n+// Copyright 2014 The Go Authors.  All rights reserved.\n+// Use of this source code is governed by a BSD-style\n+// license that can be found in the LICENSE file.\n+\n+package main\n+\n+func main() {\n+\tvar a [10]int\n+\tconst ca = len(a)\n+}\n

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

https://github.com/golang/go/commit/32dffef0980eb810b97f48eb9dfabb33602a0472

元コミット内容

Goコンパイラのcmd/gcにおいて、isgoconst関数で発生するセグメンテーション違反を修正します。この問題は、varキーワードで宣言された変数がsym->def(シンボルの定義)を持たないことに起因していました。この修正は、GoのIssue #7794を解決します。

変更の背景

この変更は、Goコンパイラのcmd/gc(当時のGoの主要なコンパイラ)が、特定のコードパターンでクラッシュする(セグメンテーション違反を起こす)バグを修正するために行われました。具体的には、isgoconstという関数が、コンパイル時に定数であるかどうかを判断する際に、varで宣言された変数に対して誤った処理を行っていました。

Go言語では、constで宣言された値はコンパイル時に定数として扱われ、その値はコードに直接埋め込まれるか、コンパイル時に評価されます。一方、varで宣言された変数は実行時に値が割り当てられる可能性があり、その定義はコンパイル時の定数とは異なる方法で扱われます。

問題は、isgoconst関数が、ONAME(名前付きエンティティ、つまり変数など)を処理する際に、そのシンボル定義(n->sym->def)にアクセスしようとしたことにありました。しかし、varで宣言された変数、特に配列のlenのような組み込み関数を適用した場合など、そのsym->defNULLになるケースが存在しました。元のコードでは、このNULLチェックが行われていなかったため、NULLポインタをデリファレンスしようとしてセグメンテーション違反が発生していました。

このバグはGoのIssue #7794として報告され、このコミットによって修正されました。

前提知識の解説

Goコンパイラ (cmd/gc)

cmd/gcは、Go言語のソースコードを機械語に変換する主要なコンパイラの歴史的な名称です。現在のGoのソースツリーではsrc/cmd/compileに位置しています。コンパイラは、ソースコードを解析し、抽象構文木(AST)を構築し、中間表現(IR)に変換し、最終的に実行可能なバイナリを生成します。

isgoconst関数

isgoconstは、Goコンパイラの内部関数の一つで、与えられた式や値がGoのコンパイル時定数であるかどうかを判断する役割を担っています。コンパイラは、定数値を事前に評価することで、コードの最適化(例: コンパイル時にif条件を評価して不要なコードを削除するなど)を行います。

AST/IRとノードタイプ (ONAME, OLITERAL)

Goコンパイラは、ソースコードを解析する際に、その構造を内部的なデータ構造である抽象構文木(AST)や中間表現(IR)として表現します。これらの木構造の各ノードは、ソースコードの異なる要素を表す「オペレーションコード(opcode)」を持っています。

  • ONAME: ソースコード内の名前付きエンティティ(変数、関数、型、パッケージなど)を表すノードタイプです。
  • OLITERAL: ソースコードに直接記述されたリテラル値(例: 整数10、文字列"hello"、真偽値true、浮動小数点数など)を表すノードタイプです。

シンボルとsym->def

Goコンパイラは、ソースコード内の識別子(変数名、関数名など)に関する情報を「シンボルテーブル」に格納します。このシンボルテーブル内の各エントリは「シンボル(Sym)」オブジェクトとして表現されます。

  • Sym (Symbol): コンパイラのシンボルテーブルにおける識別子に関する情報を保持するオブジェクトです。
  • sym->def: このフィールドは、シンボルの「定義」を指すポインタです。歴史的に、このフィールドはシンボルがどのように定義されたか(例: リテラルとして定義されたか、別の変数として定義されたかなど)を示すために使用されていました。しかし、varで宣言された変数など、特定のケースではこのdefフィールドがNULLになるか、OLITERALノードを指さないことがあります。現在のGoコンパイラでは、このsym->defフィールドは非推奨(deprecated)とされており、より高レベルの抽象化が推奨されていますが、このコミットが作成された時点ではまだ使用されていました。

varconstの違い

  • const: コンパイル時定数を宣言します。その値はコンパイル時に完全に決定され、実行時に変更されることはありません。コンパイラはこれらの値を直接コードに埋め込むことができます。
  • var: 変数を宣言します。その値は実行時に変更される可能性があり、メモリ上に割り当てられます。varで宣言された変数は、constのようにコンパイル時にリテラルとして直接扱われるわけではありません。

技術的詳細

問題は、src/cmd/gc/const.c内のisgoconst関数がONAMEノードを処理するロジックにありました。

	case ONAME:
		l = n->sym->def;
		if(l->op == OLITERAL && n->val.ctype != CTNIL)
			return 1;
		break;

このコードスニペットでは、ONAMEノード(n)が与えられた場合、まずそのシンボル定義(n->sym->def)をlに代入しています。その後、if(l->op == OLITERAL ...)という条件で、lOLITERALノードであるかどうかをチェックしています。

ここで問題となるのは、varで宣言された変数(特に、test/fixedbugs/issue7794.goのテストケースのように、配列のlenconstに代入しようとするような場合)のONAMEノードでは、n->sym->defNULLになる可能性があることです。

元のコードでは、lNULLであるにもかかわらず、l->opという形でNULLポインタをデリファレンスしようとしていました。これは未定義動作を引き起こし、結果としてセグメンテーション違反(プログラムのクラッシュ)につながっていました。

このコミットの修正は、このNULLポインタデリファレンスを防ぐために、lNULLでないことを事前にチェックする条件を追加することです。

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

--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -1594,7 +1594,7 @@ isgoconst(Node *n)\n \n 	case ONAME:\n 		l = n->sym->def;\n-\t\tif(l->op == OLITERAL && n->val.ctype != CTNIL)\n+\t\tif(l && l->op == OLITERAL && n->val.ctype != CTNIL)\n \t\t\treturn 1;\n \t\tbreak;\n \t\n```

## コアとなるコードの解説

変更された行は以下の通りです。

```c
if(l && l->op == OLITERAL && n->val.ctype != CTNIL)

この修正の核心は、条件式の先頭にl &&を追加したことです。

  • ln->sym->defの値です。
  • l &&という条件は、C言語(およびGoコンパイラのCコードベース)において、lNULLでない場合にのみ真となります。
  • 論理AND演算子(&&)はショートサーキット評価を行うため、もしlNULLであれば、l &&の時点で条件全体が偽と判断され、l->opの評価は行われません。

これにより、lNULLである場合にNULLポインタをデリファレンスしようとする試みが回避され、セグメンテーション違反が防止されます。varで宣言された変数は、その定義がOLITERALノードではないため、lNULLであるか、あるいはl->opOLITERALではないため、このifブロックの条件を満たさず、isgoconst関数は正しく0(定数ではない)を返します。

追加されたテストケースtest/fixedbugs/issue7794.goは、このバグが再現する最小限のコードを示しています。

package main

func main() {
	var a [10]int
	const ca = len(a)
}

このコードでは、varで宣言された配列aの長さ(len(a))をconst定数caに代入しようとしています。len(a)はコンパイル時に評価可能ですが、a自体はvar変数であるため、isgoconstaのシンボル定義を処理する際にsym->defNULLとなり、修正前のコンパイラではクラッシュしていました。修正後は、このコードが正しくコンパイルされるようになります。

関連リンク

参考にした情報源リンク