[インデックス 13430] ファイルの概要
本コミットは、Goコンパイラのcmd/gc
におけるdefaultlit()
関数に、OCOM
(ビット反転演算子 ^
)のケースが欠落していたバグを修正するものです。この修正により、特定のビット反転演算を含む定数リテラルの型推論が正しく行われるようになります。
コミット
commit a732cbb593c832a72e87c0ab19b6b06369cb7073
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Mon Jul 2 09:33:22 2012 +0800
cmd/gc: add missing case for OCOM in defaultlit()
Fixes #3765.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6349064
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a732cbb593c832a72e87c0ab19b6b06369cb7073
元コミット内容
diff --git a/src/cmd/gc/const.c b/src/cmd/gc/const.c
index e27c883387..2f323c77f9 100644
--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -1012,12 +1012,13 @@ defaultlit(Node **np, Type *t)
}
n->type = t;
return;
+ case OCOM:
case ONOT:
defaultlit(&n->left, t);
n->type = n->left->type;
return;
default:
- if(n->left == N) {
+ if(n->left == N || n->right == N) {
dump("defaultlit", n);
fatal("defaultlit");
}
diff --git a/test/fixedbugs/bug445.go b/test/fixedbugs/bug445.go
new file mode 100644
index 0000000000..497ecd3aba
--- /dev/null
+++ b/test/fixedbugs/bug445.go
@@ -0,0 +1,14 @@
+// compile
+
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Issue 3765
+
+package main
+
+func f(x uint) uint {
+ m := ^(1 << x)
+ return uint(m)
+}
変更の背景
このコミットは、Goコンパイラ(cmd/gc
)におけるバグ、具体的にはIssue 3765を修正するために行われました。このバグは、defaultlit()
関数がビット反転演算子であるOCOM
(^
)を適切に処理していなかったことに起因します。
Go言語では、型が明示的に指定されていない数値リテラルや定数式は「型なし定数」として扱われます。これらの型なし定数は、使用される文脈によって適切な型に推論されます。この型推論のプロセスはコンパイラの重要な機能であり、defaultlit()
関数がその役割を担っています。
しかし、defaultlit()
関数は、ビット反転演算子^
が適用された定数式(例: ^(1 << x)
)に対して、その結果の型を正しく推論できていませんでした。これは、defaultlit()
関数内のswitch
文にOCOM
のケースが欠落していたためです。結果として、このような式を含むコードがコンパイルエラーになったり、予期せぬ動作を引き起こしたりする可能性がありました。
この修正は、コンパイラの堅牢性を高め、Go言語の定数式のセマンティクスを正しく実装するために不可欠でした。
前提知識の解説
Goコンパイラ (cmd/gc
)
Go言語の公式コンパイラは、gc
(Go Compiler)と呼ばれ、Goのソースコードを機械語に変換する役割を担っています。cmd/gc
は、Goのツールチェインの一部であり、字句解析、構文解析、型チェック、中間コード生成、最適化、最終的な機械語コード生成といったコンパイルの各段階を実行します。
抽象構文木 (AST)
コンパイラは、ソースコードを直接扱うのではなく、まずその構造を抽象構文木(Abstract Syntax Tree, AST)というツリー構造で表現します。ASTの各ノードは、変数、演算子、関数呼び出しなどのプログラムの要素を表します。例えば、a + b
という式は、+
演算子を表すノードをルートとし、その子ノードとしてa
とb
を表すノードを持つASTとして表現されます。
Node
構造体
Goコンパイラ内部では、ASTの各ノードはNode
という構造体で表現されます。このNode
構造体には、ノードの種類(演算子、変数、リテラルなど)を示すOp
フィールドや、子ノードへのポインタ(left
, right
など)、そしてそのノードが表す式の型を示すtype
フィールドなどが含まれます。
Op
コード
Op
コードは、Node
構造体のOp
フィールドに格納される列挙型で、ASTノードの種類を識別するために使用されます。例えば、OADD
は加算演算子、ONOT
は論理否定演算子、そして本コミットで問題となっているOCOM
はビット反転演算子(補数演算子)を表します。
型なし定数と型推論
Go言語には「型なし定数(untyped constants)」という概念があります。これは、数値リテラル(例: 100
, 3.14
)や、それらを含む定数式(例: 1 + 2
, 1 << 5
)が、特定の型を持たない状態で表現されることを指します。これらの型なし定数は、変数への代入、関数の引数、演算子のオペランドなど、特定の文脈で使用される際に、その文脈に基づいて適切な型(例: int
, float64
, uint
)に「推論」されます。この型推論は、Go言語の柔軟性と表現力を高める重要な機能です。
defaultlit()
関数
defaultlit()
関数は、Goコンパイラのsrc/cmd/gc/const.c
ファイルに定義されており、型なし定数式の型推論を行う主要な関数の一つです。この関数は、ASTノードを受け取り、そのノードが表す定数式が使用されるべき型(ターゲット型)に基づいて、ノードの型を決定します。例えば、var i int = 100
のような場合、100
は型なし定数ですが、int
型に推論されます。
defaultlit()
関数は、様々な種類のASTノード(リテラル、単項演算子、二項演算子など)を処理するために、ノードのOp
フィールドに基づいて異なるロジックを実行します。
技術的詳細
本コミットの技術的詳細は、src/cmd/gc/const.c
ファイル内のdefaultlit()
関数の変更に集約されます。
defaultlit()
関数は、引数としてNode **np
(処理対象のASTノードへのポインタのポインタ)とType *t
(ターゲット型)を受け取ります。関数内部では、n = *np
として現在のノードを取得し、そのOp
フィールドに基づいてswitch
文で処理を分岐します。
修正前のコードでは、ONOT
(論理否定演算子 !
)のケースは存在しましたが、OCOM
(ビット反転演算子 ^
)のケースが欠落していました。
// 修正前の一部
case ONOT:
defaultlit(&n->left, t);
n->type = n->left->type;
return;
ONOT
の場合と同様に、OCOM
も単項演算子であり、そのオペランド(n->left
)の型推論が完了すれば、OCOM
ノード自体の型もオペランドの型と同じになるべきです。しかし、OCOM
のケースがなかったため、OCOM
ノードはdefault
ケースにフォールバックしていました。
default
ケースは、通常、二項演算子やその他の複雑なノードを処理するために設計されており、n->left == N
またはn->right == N
(オペランドが欠落している場合)のチェックが含まれています。ビット反転演算子^
は単項演算子であるため、n->right
はN
(nil)になります。このため、default
ケースのif(n->left == N || n->right == N)
という条件が真となり、dump("defaultlit", n); fatal("defaultlit");
が実行され、コンパイラが異常終了していました。
本コミットの修正は、この問題を解決するために以下の変更を導入しました。
-
OCOM
ケースの追加:ONOT
ケースの直前にcase OCOM:
が追加されました。これにより、OCOM
ノードがONOT
と同じロジックで処理されるようになります。+ case OCOM: case ONOT: defaultlit(&n->left, t); n->type = n->left->type; return;
この変更により、
OCOM
ノードの左の子(オペランド)に対して再帰的にdefaultlit()
が呼び出され、その子の型が推論されます。その後、OCOM
ノード自体の型がその子の型(n->left->type
)に設定されます。これは、ビット反転演算がオペランドの型を保持するというGoのセマンティクスに合致しています。 -
default
ケースの条件修正:default
ケースのif
条件がif(n->left == N)
からif(n->left == N || n->right == N)
に変更されました。- if(n->left == N) { + if(n->left == N || n->right == N) {
この変更は、
OCOM
ケースが追加されたことで、OCOM
ノードがdefault
ケースに到達しなくなるため、直接的な影響は少なくなります。しかし、将来的に他の単項演算子がdefault
ケースにフォールバックした場合に、より正確なエラーチェックを行うための防御的な変更と考えられます。単項演算子の場合、n->right
は通常N
であるため、この条件は単項演算子に対しても適切に機能します。
テストケース (test/fixedbugs/bug445.go
)
この修正を検証するために、test/fixedbugs/bug445.go
という新しいテストファイルが追加されました。このテストケースは、問題となっていたビット反転演算子^
を含む定数式を使用しています。
package main
func f(x uint) uint {
m := ^(1 << x)
return uint(m)
}
このコードスニペットでは、1 << x
というシフト演算の結果に対してビット反転演算子^
が適用されています。1 << x
は型なし定数式であり、その結果も型なし定数として扱われます。この型なし定数^(1 << x)
が変数m
に代入される際に、defaultlit()
関数によって型推論が行われます。修正前はここでコンパイラが異常終了していましたが、修正後は正しく型推論が行われ、コンパイルが成功するようになります。
このテストケースは、// compile
ディレクティブを含んでおり、コンパイルが成功することを確認するものです。
コアとなるコードの変更箇所
src/cmd/gc/const.c
ファイルのdefaultlit()
関数内。
--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -1012,12 +1012,13 @@ defaultlit(Node **np, Type *t)
}
n->type = t;
return;
+ case OCOM: // この行が追加された
case ONOT:
defaultlit(&n->left, t);
n->type = n->left->type;
return;
default:
- if(n->left == N) { // この行が変更された
+ if(n->left == N || n->right == N) { // この行が変更された
dump("defaultlit", n);
fatal("defaultlit");
}
コアとなるコードの解説
case OCOM:
の追加
この変更の核心は、defaultlit()
関数のswitch
文にOCOM
(ビット反転演算子 ^
)のケースを追加したことです。
OCOM
は単項演算子であり、そのオペランドはn->left
に格納されます。ONOT
(論理否定演算子 !
)も同様に単項演算子であり、既存のONOT
ケースは既に正しい処理ロジックを持っていました。
追加されたOCOM
ケースは、ONOT
ケースと全く同じ処理を行います。
defaultlit(&n->left, t);
:OCOM
ノードの左の子(オペランド)に対して、再帰的にdefaultlit()
を呼び出します。これにより、オペランドの型がターゲット型t
に基づいて推論されます。n->type = n->left->type;
: オペランドの型推論が完了した後、OCOM
ノード自体の型を、推論されたオペランドの型(n->left->type
)に設定します。Go言語では、ビット反転演算はオペランドの型を保持するため、この動作は正しいです。
この追加により、^(1 << x)
のような式がdefaultlit()
によって正しく処理され、コンパイラの異常終了が回避されるようになりました。
default
ケースの条件変更
default
ケースのif
条件がif(n->left == N)
からif(n->left == N || n->right == N)
に変更されました。
修正前は、n->left
がN
(nil)の場合にのみエラーとしていましたが、これは主にオペランドが必須の二項演算子などで、左オペランドが欠落している場合を想定していました。
しかし、OCOM
のような単項演算子が誤ってdefault
ケースに到達した場合、n->left
は存在するがn->right
がN
であるため、修正前の条件ではエラーとして捕捉されませんでした。
この変更により、n->left
またはn->right
のいずれかがN
である場合にエラーとして扱うようになり、より一般的な「オペランドが不完全なノード」を捕捉できるようになりました。OCOM
ケースが追加されたため、この変更が直接的にOCOM
ノードに影響を与えることはなくなりましたが、コンパイラの他の部分で同様の問題が発生した場合に備えた、より堅牢なエラーチェックを提供します。
関連リンク
- Go言語のIssueトラッカー: https://github.com/golang/go/issues
- Go言語のコンパイラに関するドキュメント (Goのソースコード内):
src/cmd/gc/
ディレクトリ内のドキュメントやコードコメント
参考にした情報源リンク
- Go言語のソースコード:
src/cmd/gc/const.c
- Go言語のIssue 3765 (コミットメッセージに記載されているが、具体的なIssueページは特定できなかったため、一般的なGoのIssueトラッカーへのリンクを記載)
- Go言語の型なし定数に関する公式ドキュメントや仕様 (Go言語の公式ウェブサイトやGoの仕様書)