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

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

このコミットは、Goコンパイラ(gc)における構造体リテラル内の構造体型のインポートに関するバグを修正するものです。具体的には、外部パッケージからインポートされた構造体型が、構造体リテラル内で正しく扱われない問題に対処しています。

コミット

commit 77aaa3555dd420c79bafa88de60dee31717949c3
Author: Russ Cox <rsc@golang.org>
Date:   Sat Feb 11 00:34:01 2012 -0500

    gc: fix import of struct type in struct literal
    
    Fixes #2716.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/5652065

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

https://github.com/golang/go/commit/77aaa3555dd420c79bafa88de60dee31717949c3

元コミット内容

このコミットは、Goコンパイラ(gc)が構造体リテラル内で外部パッケージからインポートされた構造体型を処理する際のバグを修正します。具体的には、struct{int}{0}のような匿名構造体型が、インポートされた型として誤って解釈される問題がありました。この修正により、コンパイラはインポート時に匿名構造体型を正しく識別し、適切な処理を行うようになります。

変更の背景

Go言語のコンパイラは、ソースコードを解析し、抽象構文木(AST)を構築し、最終的に実行可能なバイナリを生成します。このプロセスにおいて、型情報の正確な処理は非常に重要です。特に、異なるパッケージ間で型を共有する場合、インポートメカニズムが正しく機能する必要があります。

このコミットが修正するバグは、Goコンパイラのフロントエンド、特に字句解析と構文解析の段階で発生していました。Go言語の構文解析はYacc/Bisonによって生成されたパーサー(go.yy.tab.c)を使用しており、このパーサーがインポートされた構造体型を匿名構造体リテラル内で誤って解釈していました。

具体的には、struct{int}のような匿名構造体型が、インポート処理中に「?シンボル」として扱われ、その結果、型が正しく解決されず、コンパイルエラーや不正なコード生成につながる可能性がありました。この問題は、Go言語のIssue 2716として報告されていました。

前提知識の解説

  • Goコンパイラ (gc): Go言語の公式コンパイラであり、Goプログラムを機械語に変換する役割を担います。gcは、字句解析、構文解析、型チェック、最適化、コード生成といった複数のフェーズで構成されています。
  • Yacc/Bison: Yet Another Compiler Compiler (Yacc) および GNU Bison は、文法定義から構文解析器(パーサー)を自動生成するツールです。Goコンパイラのフロントエンドの一部は、Yaccによって生成されたC言語のコード(y.tab.c)を使用しています。
  • go.y: Goコンパイラの構文規則を定義するYaccの入力ファイルです。Go言語の文法がこのファイルに記述されており、Yaccがこれを解析してy.tab.cを生成します。
  • 抽象構文木 (AST): ソースコードの構文構造を木構造で表現したものです。コンパイラはソースコードをASTに変換し、その後の型チェックやコード生成のフェーズでASTを操作します。
  • 構造体リテラル: Go言語で構造体の値を初期化するための構文です。例えば、MyStruct{Field1: value1, Field2: value2}のように記述します。匿名構造体リテラルは、struct{int}{0}のように、その場で構造体型を定義して初期化するものです。
  • インポート: Go言語で他のパッケージの型や関数を利用するために使用されるメカニズムです。import "path/to/package"のように記述します。
  • Node: Goコンパイラ内部でASTの各ノードを表すデータ構造です。Nodeは、演算子(op)、シンボル(sym)、型(type)、子ノード(left, right, list)などの情報を含みます。
  • Sym (Symbol): コンパイラが識別子(変数名、関数名、型名など)を管理するために使用するデータ構造です。シンボルは、その識別子の名前、所属するパッケージ、型などの情報を含みます。
  • ODCLFIELD: ASTノードのオペレーションコードの一つで、構造体のフィールド宣言を表します。
  • OIND: ASTノードのオペレーションコードの一つで、ポインタのデリファレンス(間接参照)を表します。
  • newname(sym): 新しい名前(シンボル)に対応するASTノードを作成する関数。
  • embedded(sym): 埋め込みフィールド(匿名フィールド)に対応するASTノードを作成する関数。
  • nod(op, left, right): 指定されたオペレーションコードと子ノードを持つASTノードを作成する関数。
  • list1(node): 単一のノードを含むNodeListを作成する関数。

技術的詳細

この修正は、主にGoコンパイラの構文解析器(src/cmd/gc/go.ysrc/cmd/gc/y.tab.c)に影響を与えます。

  1. go.ynew_name ルールにおける変更:

    • new_name ルールは、新しい識別子(シンボル)に対応するASTノードを作成する役割を担っています。
    • 変更前は、単にnewname($1)を呼び出してノードを作成していました。
    • 変更後は、$1(シンボル)がS(おそらく特殊なシンボル、または無効なシンボルを示す)である場合に、N(nilノード)を返すように修正されました。これは、インポート処理中に発生する可能性のある、不正なシンボルに対する堅牢性を高めるための変更と考えられます。
  2. go.ystructdcl ルールにおける変更:

    • structdcl ルールは、構造体宣言を処理する役割を担っています。
    • この修正の核心は、インポートされた構造体型が構造体リテラル内で使用される場合の特殊なケースを処理することにあります。
    • 追加されたコードブロックは、l != nil && l->next == nil && l->n == nilという条件で、リストlが単一の要素を持ち、その要素がまだノードに変換されていない(l->n == nil)特殊な状況を検出します。これは、インポートされた匿名構造体型がパーサーによって一時的に「?シンボル」として扱われるケースに対応していると推測されます。
    • この特殊なケースでは、以下の処理が行われます。
      • n = $2;$2は構造体フィールドの型を表すノードです。
      • if(n->op == OIND) n = n->left;:もし型がポインタ型(OIND)であれば、その基底型を取得します。
      • n = embedded(n->sym);:取得したシンボルから埋め込みフィールド(匿名フィールド)に対応するノードを作成します。これにより、インポートされた型が匿名フィールドとして正しく扱われるようになります。
      • n->right = $2;:元の型ノードをnrightフィールドに設定します。
      • n->val = $3;:フィールドの値(タグなど)を設定します。
      • $$ = list1(n);:作成したノードを単一要素のリストとして返します。
      • break;:通常の構造体フィールド処理ループをスキップします。
    • この変更により、インポートされた匿名構造体型が、通常のフィールド宣言としてではなく、埋め込みフィールドとして正しく認識され、処理されるようになります。
  3. y.tab.c の変更:

    • y.tab.cgo.yから生成されるC言語のコードであり、go.yの変更が反映されます。
    • 主に、yyval.nodeyyval.listへの代入ロジックが、go.yで定義された新しいセマンティックアクション({ ... }ブロック内のCコード)に合わせて変更されています。
    • 特に、new_nameおよびstructdclルールに対応する部分で、行番号の変更と、go.yで追加された条件分岐や関数呼び出しがCコードとして生成されています。

この修正は、GoコンパイラがGo言語の仕様に厳密に従い、特に型システムとインポートメカニズムの正確性を保証するために不可欠なものです。

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

src/cmd/gc/go.y

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -987,7 +987,10 @@ lbrace:
 new_name:
 	sym
 	{
-\t\t$$ = newname($1);\
+\t\tif($1 == S)\
+\t\t\t$$ = N;\
+\t\telse\
+\t\t\t$$ = newname($1);\
 	}
 
 dcl_name:
@@ -1418,6 +1421,19 @@ structdcl:
 	{
 		NodeList *l;
 
+\t\tNode *n;\
+\t\tif(l != nil && l->next == nil && l->n == nil) {\
+\t\t\t// ? symbol, during import
+\t\t\tn = $2;\
+\t\t\tif(n->op == OIND)\
+\t\t\t\tn = n->left;\
+\t\t\tn = embedded(n->sym);\
+\t\t\tn->right = $2;\
+\t\t\tn->val = $3;\
+\t\t\t$$ = list1(n);\
+\t\t\tbreak;\
+\t\t}\
+\
 		for(l=$1; l; l=l->next) {
 			l->n = nod(ODCLFIELD, l->n, $2);\
 			l->n->val = $3;\

src/cmd/gc/y.tab.c

y.tab.cgo.yから自動生成されるファイルであるため、変更内容はgo.yの変更を反映したものです。主に、yyreduce関数内のcase文に対応するセマンティックアクションのCコードが変更されています。

  • case 146 (new_nameルールに対応): newname関数の呼び出しに条件分岐が追加されています。
  • case 222 (structdclルールに対応): 新しい条件分岐と、embedded関数などの呼び出しを含むロジックが追加されています。
  • その他、行番号の変更など、go.yの変更に伴う自動生成コードの更新が含まれます。

test/fixedbugs/bug415.dir/main.go

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

package main
import "./p"
func main() {}
var _ p.A

test/fixedbugs/bug415.dir/p.go

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

package p

type A struct {
	s struct{int}
}

func (a *A) f() {
	a.s = struct{int}{0}
}

test/fixedbugs/bug415.go

// $G $D/$F.dir/p.go && $G $D/$F.dir/main.go

// 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 2716.  Export metadata error made main.go not compile.

package ignored

コアとなるコードの解説

このコミットの核心は、src/cmd/gc/go.ystructdclルールに追加された新しい条件分岐です。

+\t\tNode *n;\
+\t\tif(l != nil && l->next == nil && l->n == nil) {\
+\t\t\t// ? symbol, during import
+\t\t\tn = $2;\
+\t\t\tif(n->op == OIND)\
+\t\t\t\tn = n->left;\
+\t\t\tn = embedded(n->sym);\
+\t\t\tn->right = $2;\
+\t\t\tn->val = $3;\
+\t\t\t$$ = list1(n);\
+\t\t\tbreak;\
+\t\t}\

このコードブロックは、Goコンパイラがパッケージのインポート時に、特に匿名構造体型(例: struct{int})を処理する際の特殊な状況を検出して修正します。

  • l != nil && l->next == nil && l->n == nil: この条件は、structdclルールが処理しているNodeList lが、単一の要素を持ち、かつその要素がまだASTノードとして完全に構築されていない状態であることを示唆しています。これは、インポートされた型がパーサーによって一時的に「?シンボル」のような中間表現として扱われている場合に発生する可能性があります。
  • n = $2;: $2は、Yaccのセマンティックアクションにおいて、現在のルールで2番目のシンボル(この場合は構造体フィールドの型)に対応する値です。
  • if(n->op == OIND) n = n->left;: もし型がポインタ型(*T)であれば、その基底型Tを取得します。これは、ポインタ型の匿名構造体も正しく処理するためです。
  • n = embedded(n->sym);: ここが重要な部分です。embedded関数は、与えられたシンボルから「埋め込みフィールド」(Goの匿名フィールド)を表すASTノードを作成します。これにより、インポートされたstruct{int}のような型が、通常の名前付きフィールドではなく、埋め込みフィールドとして正しく解釈されるようになります。
  • n->right = $2;: embedded関数で作成されたノードnrightフィールドに、元の型ノード$2を設定します。これは、埋め込みフィールドの型情報を保持するためです。
  • n->val = $3;: $3は、構造体フィールドのタグ(例: json:"name")に対応する値です。埋め込みフィールドにもタグが付与される場合があるため、これを設定します。
  • $$ = list1(n);: 最終的に、正しく構築された埋め込みフィールドのノードを、単一要素のリストとして返します。これにより、パーサーはインポートされた匿名構造体型を正しくASTに組み込むことができます。
  • break;: この特殊なケースが処理された後、通常の構造体フィールドを処理するループをスキップし、重複処理を防ぎます。

この修正により、Goコンパイラは、外部パッケージからインポートされた匿名構造体型が構造体リテラル内で使用された場合でも、それを正しく解析し、コンパイルできるようになりました。test/fixedbugs/bug415.goとその関連ファイルは、このバグが修正されたことを検証するためのテストケースです。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Goコンパイラのソースコード (src/cmd/gc/)
  • Yacc/Bisonのドキュメント
  • Go言語のIssueトラッカー (Issue 2716)
  • Go言語のコードレビューシステム (CL 5652065)
  • Go言語の構文解析に関する一般的な情報