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

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

このコミットは、Goコンパイラ(cmd/gc)における定数評価の厳密性を向上させるものです。具体的には、固定長配列のlen(長さ)を定数として評価する際に、組み込み関数呼び出しが誤って定数として扱われる問題を修正します。これにより、コンパイル時に決定できない値が定数として扱われることを防ぎ、Go言語の定数規則への準拠を強化します。

コミット

  • コミットハッシュ: f3ecb298ad3187a6c47f43916480d396ec8c35c3
  • 作者: Russ Cox rsc@golang.org
  • 日付: 2014年4月3日 木曜日 19:04:33 -0400
  • 件名: cmd/gc: reject builtin function calls in len(fixed array) constants

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

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

元コミット内容

cmd/gc: reject builtin function calls in len(fixed array) constants

Fixes #7385.

LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/84010044

変更の背景

Go言語において、定数はコンパイル時にその値が完全に決定される必要があります。しかし、このコミット以前のGoコンパイラには、特定の状況下でこの原則が破られる脆弱性がありました。具体的には、固定長配列のlen(長さ)を定数として宣言する際に、その配列の要素に組み込み関数(例: real(), len()など)の呼び出しが含まれている場合、コンパイラがその組み込み関数呼び出しの結果を誤って定数として扱ってしまう可能性がありました。

例えば、len([4]float64{real(z)})のような式において、zが変数である場合、real(z)の結果はコンパイル時には決定できません。しかし、コンパイラがこれを定数として評価しようとすると、不正な動作や予期せぬ結果を招く可能性がありました。

この問題は、Go issue #7385として報告されており、このコミットはその問題を修正するために導入されました。Gerritのレビュープロセス中に、この変更がlinux-386-387ビルドを一時的に壊したという報告もあり、定数評価の厳密化がコンパイラの他の部分に影響を与える可能性を示唆しています。この修正は、Goプログラムの健全性と予測可能性を保証するために不可欠でした。

前提知識の解説

Go言語の定数

Go言語における定数(constキーワードで宣言される)は、コンパイル時にその値が確定している不変のエンティティです。数値、真偽値、文字列、またはこれらの組み合わせからなる式でなければなりません。定数式は、関数呼び出し(組み込み関数であっても)、チャネル操作、マップ操作、スライス操作など、実行時に評価される必要のある操作を含むことはできません。

Goコンパイラ (cmd/gc)

cmd/gcは、Go言語の公式コンパイラです。ソースコードを解析し、抽象構文木(AST)を構築し、型チェック、最適化、コード生成などのフェーズを経て実行可能なバイナリを生成します。定数評価は、コンパイルフェーズの早い段階で行われ、定数式が実行時に評価されることなく、その場で計算されるようにします。

抽象構文木 (AST) と Node

コンパイラは、ソースコードを解析してプログラムの構造を表現する抽象構文木(AST)を構築します。ASTの各ノードは、式、ステートメント、宣言などのプログラム要素を表します。Goコンパイラの内部では、これらのノードはNode構造体で表現され、opフィールドによってそのノードがどのような種類の操作(例: OCALLは関数呼び出し、OLENlen組み込み関数)を表すかが識別されます。

hascallchan 関数

src/cmd/gc/const.cファイルに存在するhascallchan関数は、Goコンパイラの定数評価ロジックの一部です。この関数は、与えられたASTノードが、関数呼び出しやチャネル操作など、コンパイル時に評価できない(つまり、定数ではない)操作を含んでいるかどうかを再帰的にチェックします。もしそのような操作が含まれていれば、その式全体は定数として扱われるべきではないと判断されます。

技術的詳細

このコミットの核心は、src/cmd/gc/const.cファイル内のhascallchan関数の拡張にあります。この関数は、ある式が定数として評価可能かどうかを判断する際に、その式が実行時評価を必要とする操作(関数呼び出しやチャネル操作など)を含んでいるかをチェックします。

以前のhascallchan関数は、OCALLOCALLFUNCOCALLMETHOCALLINTERORECVといった比較的少数の操作のみをチェックしていました。しかし、Go言語には他にも多くの組み込み関数や操作があり、これらが非定数値を引数に取る場合、その結果は定数にはなり得ません。

このコミットでは、hascallchan関数に以下のNode.opタイプが追加されました。

  • OAPPEND
  • OCAP
  • OCLOSE
  • OCOMPLEX
  • OCOPY
  • ODELETE
  • OIMAG
  • OLEN
  • OMAKE
  • ONEW
  • OPANIC
  • OPRINT
  • OPRINTN
  • OREAL
  • ORECOVER

これらの操作がhascallchanのチェック対象に追加されたことで、コンパイラはより多くの種類の組み込み関数呼び出しや操作が定数式内で使用された場合に、それが非定数であることを正しく識別できるようになりました。これにより、len(fixed array)のような定数式の中に、real(z)zが変数の場合)のような非定数部分が含まれていても、コンパイラがそれを定数として扱ってしまう誤りを防ぎます。

この変更は、Go言語の定数規則の厳密な適用を保証し、コンパイル時のエラー検出能力を向上させます。

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

src/cmd/gc/const.c

--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -1629,10 +1629,25 @@ hascallchan(Node *n)
 	if(n == N)
 		return 0;
 	switch(n->op) {
+	case OAPPEND:
 	case OCALL:
 	case OCALLFUNC:
-\tcase OCALLMETH:
 	case OCALLINTER:
+	case OCALLMETH:
+	case OCAP:
+	case OCLOSE:
+	case OCOMPLEX:
+	case OCOPY:
+	case ODELETE:
+	case OIMAG:
+	case OLEN:
+	case OMAKE:
+	case ONEW:
+	case OPANIC:
+	case OPRINT:
+	case OPRINTN:
+	case OREAL:
+	case ORECOVER:
 	case ORECV:
 		return 1;
 	}

test/const5.go

--- a/test/const5.go
+++ b/test/const5.go
@@ -18,6 +18,7 @@ var s [][30]int
 
 func f() *[40]int
 var c chan *[50]int
+var z complex128
 
 const (
 	n1 = len(b.a)
@@ -29,5 +30,8 @@ const (
 
 	n6 = cap(f())  // ERROR "is not a constant|is not constant"
 	n7 = cap(<-c) // ERROR "is not a constant|is not constant"
+\tn8 = real(z) // ERROR "is not a constant|is not constant"
+\tn9 = len([4]float64{real(z)}) // ERROR "is not a constant|is not constant"
+\
 )
 

コアとなるコードの解説

src/cmd/gc/const.c の変更

hascallchan関数は、ASTノードnが関数呼び出しやチャネル操作を含むかどうかをチェックします。switch(n->op)文は、ノードの操作タイプに基づいて処理を分岐させます。

変更前は、OCALL(一般的な関数呼び出し)、OCALLFUNC(関数ポインタによる呼び出し)、OCALLMETH(メソッド呼び出し)、OCALLINTER(インターフェースメソッド呼び出し)、ORECV(チャネルからの受信)のみがreturn 1(呼び出しまたはチャネル操作を含む)を返していました。

変更後は、OAPPEND, OCAP, OCLOSE, OCOMPLEX, OCOPY, ODELETE, OIMAG, OLEN, OMAKE, ONEW, OPANIC, OPRINT, OPRINTN, OREAL, ORECOVERといった、Goの組み込み関数や特定の操作を表すNode.opタイプが追加されました。これらの操作は、引数が定数でない場合や、実行時に副作用を持つ可能性があるため、定数式内での使用は許可されません。これらの追加により、コンパイラはより広範な非定数式を正しく識別し、コンパイルエラーを発生させることができるようになります。

test/const5.go の変更

このテストファイルは、定数評価に関するコンパイラの挙動を検証するために使用されます。

  • var z complex128complex128型の変数zが宣言されています。変数はコンパイル時には値が確定しないため、定数ではありません。
  • n8 = real(z)real()は組み込み関数ですが、引数zが変数であるため、real(z)の結果は定数にはなり得ません。この行には// ERRORコメントが付加されており、コンパイラがこの式を定数として受け入れず、エラーを出すことを期待しています。
  • n9 = len([4]float64{real(z)}):この式は、要素にreal(z)を含む固定長配列のlenを計算しようとしています。real(z)が非定数であるため、配列リテラル全体も定数ではなく、したがってそのlenも定数にはなり得ません。この行にも// ERRORコメントが付加されており、コンパイラがこの不正な定数宣言を検出することを期待しています。

これらのテストケースは、hascallchan関数の拡張によって、コンパイラがこれらの非定数式を正しく拒否するようになったことを確認するために追加されました。

関連リンク

  • Gerrit Change-ID: Ie27d60af04a2f3187a6c47f43916480d396ec8c35c3
  • Gerrit Code Review: https://golang.org/cl/84010044
  • Go Issue #7385 (関連する問題): このコミットは、Go issue #7385を修正します。Gerritのコミットメッセージに記載されていますが、GitHub上での直接のリンクは見つかりませんでした。しかし、コミットの目的と変更内容から、定数評価の厳密化に関する問題であったことが推測されます。

参考にした情報源リンク