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

[インデックス 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という式は、+演算子を表すノードをルートとし、その子ノードとしてabを表すノードを持つ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->rightN(nil)になります。このため、defaultケースのif(n->left == N || n->right == N)という条件が真となり、dump("defaultlit", n); fatal("defaultlit");が実行され、コンパイラが異常終了していました。

本コミットの修正は、この問題を解決するために以下の変更を導入しました。

  1. 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のセマンティクスに合致しています。

  2. 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ケースと全く同じ処理を行います。

  1. defaultlit(&n->left, t);: OCOMノードの左の子(オペランド)に対して、再帰的にdefaultlit()を呼び出します。これにより、オペランドの型がターゲット型tに基づいて推論されます。
  2. 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->leftN(nil)の場合にのみエラーとしていましたが、これは主にオペランドが必須の二項演算子などで、左オペランドが欠落している場合を想定していました。

しかし、OCOMのような単項演算子が誤ってdefaultケースに到達した場合、n->leftは存在するがn->rightNであるため、修正前の条件ではエラーとして捕捉されませんでした。

この変更により、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の仕様書)