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

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

本コミットは、Goコンパイラ(cmd/gc)におけるマップリテラルの重複キーチェックのバグ修正に関するものです。具体的には、interface{}型のマップキーにおいてniltrueのような異なる型の定数を比較しようとした際に発生していた誤った重複キー検出とコンパイルエラーを修正します。

コミット

commit ec38c6f5e324bd550a5d034d36d48f479a8b9b47
Author: Russ Cox <rsc@golang.org>
Date:   Thu May 15 15:34:37 2014 -0400

    cmd/gc: fix duplicate map key check
    
    Do not compare nil and true.
    
    Fixes #7996.
    
    LGTM=r
    R=golang-codereviews, r
    CC=golang-codereviews
    https://golang.org/cl/91470043

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

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

元コミット内容

このコミットは、Goコンパイラ(cmd/gc)がマップリテラル内の重複キーをチェックする際のロジックを修正します。特に、niltrueのような異なる型の定数を誤って比較しようとする問題を解決します。これにより、Fixes #7996で報告されたバグが修正されます。

変更の背景

Go言語では、マップリテラルを定義する際に、同じキーを複数回指定することはできません。コンパイラは、この重複キーを検出してコンパイルエラーを発生させる必要があります。しかし、特定の条件下、特にマップのキー型がinterface{}である場合に、コンパイラがniltrueのような本質的に異なる型の定数を、あたかも比較可能であるかのように扱おうとするバグが存在しました。

このバグは、issue #7996として報告されました。報告された問題は、以下のようなコードがコンパイルエラーになるというものでした。

package p

var m = map[interface{}]struct{}{
	nil:  {},
	true: {},
}

このコードは、niltrueが異なる型であるため、本来であれば重複キーとは見なされず、正しくコンパイルされるべきです。しかし、当時のコンパイラは「illegal constant expression: bool == interface {}」のようなエラーを出力し、コンパイルに失敗していました。これは、コンパイラが重複キーチェックの過程で、bool型のtrueinterface{}型のnilを直接比較しようとしたために発生したものです。

このコミットは、このような誤った比較を防ぎ、コンパイラがマップリテラルの重複キーを正しく識別できるようにすることを目的としています。

前提知識の解説

本コミットの理解には、以下のGoコンパイラの内部構造とGo言語の概念に関する知識が役立ちます。

  • Goコンパイラ (cmd/gc): Go言語の公式コンパイラです。ソースコードを解析し、中間表現を生成し、最終的に実行可能なバイナリを生成します。型チェックはコンパイルプロセスの重要なフェーズの一つです。
  • マップリテラル: Go言語でマップ(ハッシュマップ、連想配列)を初期化するための構文です。map[KeyType]ValueType{key1: value1, key2: value2, ...}のように記述します。
  • 重複キーチェック: マップリテラル内で同じキーが複数回出現していないかをコンパイラが検証するプロセスです。
  • interface{}: Go言語における空のインターフェース型です。任意の型の値を保持できるため、Goにおけるポリモーフィズムの基本的なメカニズムとして機能します。nilはインターフェース型のゼロ値であり、truebool型の値です。interface{}型の変数には、niltrueのような異なる型の値を代入できます。
  • 定数式評価 (evconst): コンパイル時に評価可能な定数式を評価するコンパイラの機能です。例えば、1 + 2のような式はコンパイル時に3として評価されます。
  • 型比較 (eqtype): コンパイラが2つの型が等しいかどうかを判断するために使用する関数です。Goの型システムは厳格であり、型が異なる値は通常、直接比較できません。
  • OCONVIFACE: コンパイラの内部ノードタイプの一つで、値をインターフェース型に変換する操作を表します。
  • Node構造体: コンパイラの抽象構文木(AST)における各要素を表す内部構造体です。n->typeはノードの型情報を、n->opはノードの操作タイプ(例: OCONVIFACE)を表します。
  • cmp.op = OEQ: 比較操作が等価性(==)であることを示します。
  • cmp.val.u.bval: 定数式評価の結果がブーリアン値である場合に、その値(trueまたはfalse)を保持するフィールドです。

技術的詳細

この修正は、src/cmd/gc/typecheck.cファイル内のkeydup関数に焦点を当てています。keydup関数は、マップリテラル内のキーが重複しているかどうかをチェックする役割を担っています。

問題の核心は、interface{}型のキーを持つマップにおいて、コンパイラが異なる型の定数(例: niltrue)を比較しようとした際に、その比較が常にfalseになるべきであるにもかかわらず、誤って「重複」と判断したり、無効な比較を試みたりしていた点にあります。

修正前のコードでは、OCONVIFACE(インターフェースへの変換)ノードを処理する際に、a->left->type == n->typeという直接的な型比較を行っていました。これは、インターフェース型に変換された後の基底型が完全に一致する場合にのみtrueを返します。しかし、interface{}型の場合、nilは特定の基底型を持たず、truebool型を持つため、この比較は常にfalseとなり、結果としてelseブロックにフォールバックしていました。

elseブロックでは、cmp.right = a; evconst(&cmp); b = cmp.val.u.bval;という処理が行われていました。ここで、evconstは定数式を評価しようとしますが、niltrueのような互換性のない型を比較しようとすると、コンパイル時に「illegal constant expression」エラーが発生していました。これは、Goの型システムでは異なる型の値を直接比較できないためです。

このコミットによる修正は、このロジックを改善し、より堅牢な型比較を行うことで、誤った重複キー検出を防ぎます。

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

src/cmd/gc/typecheck.cファイルのkeydup関数において、以下の変更が行われました。

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2415,23 +2415,19 @@ keydup(Node *n, Node *hash[], ulong nhash)
 	for(a=hash[h]; a!=N; a=a->ntest) {
 		cmp.op = OEQ;
 		cmp.left = n;
+		b = 0; // <-- 追加
 		if(a->op == OCONVIFACE && orign->op == OCONVIFACE) {
-			if(a->left->type == n->type) { // <-- 変更前
+			if(eqtype(a->left->type, n->type)) { // <-- 変更後
 				cmp.right = a->left;
 				evconst(&cmp);
 				b = cmp.val.u.bval;
 			}
-			else { // <-- 削除
-				b = 0; // <-- 削除
-			} // <-- 削除
-		}
-		else { // <-- 変更前
+		} else if(eqtype(a->type, n->type)) { // <-- 変更後
 			cmp.right = a;
 			evconst(&cmp);
 			b = cmp.val.u.bval;
 		}
 		if(b) {
-			// too lazy to print the literal // <-- 削除
 			yyerror("duplicate key %N in map literal", n);
 			return;
 		}

また、この修正を検証するための新しいテストケースが追加されました。

test/fixedbugs/issue7996.go

// compile

// Copyright 2014 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.

// /tmp/x.go:5: illegal constant expression: bool == interface {}

package p

var m = map[interface{}]struct{}{
	nil:  {},
	true: {},
}

コアとなるコードの解説

修正のポイントは以下の通りです。

  1. b = 0; の初期化: forループの冒頭でb(比較結果のブーリアン値)が0(false)に初期化されるようになりました。これにより、以降の条件分岐でbが明示的にtrueに設定されない限り、重複キーとは見なされないようになります。これは、以前のコードでelseブロックにフォールバックした際にbが未定義または意図しない値を持つ可能性があった問題を解消します。

  2. OCONVIFACE ブロック内の型比較の改善: 変更前: if(a->left->type == n->type) 変更後: if(eqtype(a->left->type, n->type)) ==演算子による直接的な型ポインタの比較から、eqtype関数によるより厳密な型比較に変更されました。eqtypeは、Goの型システムにおける型の等価性を正確に判断するための関数です。interface{}型の場合、その基底型が異なる場合でも、eqtypeは適切にfalseを返すため、niltrueのような異なる型の値が誤って同じ型として扱われることを防ぎます。

  3. else if への変更とロジックの整理: 変更前はOCONVIFACEifブロックの後に独立したelseブロックがありました。 変更後: } else if(eqtype(a->type, n->type)) { これにより、OCONVIFACEの条件が満たされない場合でも、anの直接の型がeqtypeによって比較されるようになりました。この構造により、コンパイラはまずインターフェース変換のケースを考慮し、次に一般的な型比較を行うという、より論理的なフローで重複キーをチェックできるようになります。

これらの変更により、コンパイラはniltrueのような異なる型の定数をinterface{}型のマップキーとして使用した場合に、それらが本質的に異なる型であることを正しく認識し、無効な比較を試みることなく、重複キーではないと判断できるようになりました。結果として、issue #7996で報告されたコンパイルエラーが解消され、Go言語のマップリテラルの型チェックがより堅牢になりました。

関連リンク

参考にした情報源リンク