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

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

このコミットは、Go言語のコンパイラ (cmd/gc) における特定のバグ(Issue 3552)をテストするための新しいテストケースを追加するものです。主に、構造体における埋め込みフィールドのアクセスに関するコンパイラの挙動、特に型チェックとインライン化されたメソッドの処理に焦点を当てています。

コミット

commit 81d9621534ac726ff613e54e98844ee43398250c
Author: Luuk van Dijk <lvd@golang.org>
Date:   Wed May 2 16:56:26 2012 +0200

    cmd/gc: test for issue 3552
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6128051

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

https://github.com/golang/go/commit/81d9621534ac726ff613e54e98844ee43398250c

元コミット内容

このコミットは、Goコンパイラ (cmd/gc) のバグであるIssue 3552を検証するためのテストを追加します。具体的には、構造体の埋め込みフィールドのアクセスに関するコンパイラの挙動をテストし、期待されるエラーメッセージが正しく出力されることを確認します。

変更の背景

このコミットの背景には、Go言語のコンパイラが構造体の埋め込みフィールドを処理する際の特定のバグ、すなわちIssue 3552が存在します。このIssueは、構造体に同じ型のフィールドが複数埋め込まれている場合や、匿名フィールドが特定の形で定義されている場合に、コンパイラが誤った型チェックを行う可能性を指摘していました。

Go言語では、構造体に型を匿名で埋め込むことで、その埋め込まれた型のメソッドやフィールドを外側の構造体が直接利用できる「プロモーション」という機能があります。しかし、このプロモーションが複雑なケース(例えば、同じ型のフィールドが複数ある場合や、フィールド名と型名が衝突する場合)で正しく機能しない、あるいはコンパイラが予期せぬエラーを発生させるという問題がありました。

このコミットは、そのバグが修正されたことを検証するため、またはバグの存在を明確にするために、具体的なテストケースを追加しています。テストケースは、問題のあるコードパターンを再現し、コンパイラが期待通りのエラー(この場合は「unexported field or method」に関するエラー)を出すことを確認します。これにより、コンパイラの堅牢性と正確性を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念とコンパイラの挙動に関する知識が必要です。

  1. Go言語の構造体 (Structs):

    • Goの構造体は、異なる型のフィールドをまとめることができる複合データ型です。
    • 例: type Person struct { Name string; Age int }
  2. 埋め込みフィールド (Embedded Fields):

    • Goの構造体では、フィールド名を指定せずに型を直接埋め込むことができます。これを「匿名フィールド」または「埋め込みフィールド」と呼びます。
    • 埋め込まれた型のフィールドやメソッドは、外側の構造体から直接アクセスできるようになります(プロモーション)。
    • 例:
      type Engine struct { Horsepower int }
      func (e Engine) Start() { /* ... */ }
      
      type Car struct { Engine; Color string } // Engineを埋め込み
      // car := Car{Engine: Engine{Horsepower: 200}, Color: "Red"}
      // car.Start() // EngineのStartメソッドを直接呼び出せる
      // fmt.Println(car.Horsepower) // EngineのHorsepowerフィールドを直接アクセスできる
      
    • 名前の衝突: 埋め込みフィールドのフィールド名やメソッド名が、外側の構造体のフィールド名やメソッド名、あるいは他の埋め込みフィールドのフィールド名やメソッド名と衝突する場合、Goの言語仕様には解決ルールがあります。通常、より「近い」名前(外側の構造体で直接定義されたもの)が優先されます。複数の匿名フィールド間で名前が衝突する場合は、曖昧さエラーとなります。
  3. エクスポートされた識別子とエクスポートされていない識別子 (Exported vs. Unexported Identifiers):

    • Go言語では、識別子(変数名、関数名、型名、フィールド名など)の最初の文字が大文字である場合、その識別子はパッケージ外からアクセス可能です(エクスポートされている)。
    • 最初の文字が小文字である場合、その識別子は定義されたパッケージ内でのみアクセス可能です(エクスポートされていない)。
    • このコミットのテストケースでは、intという組み込み型が匿名フィールドとして埋め込まれており、これはエクスポートされていないフィールドとして扱われます。
  4. Goコンパイラ (cmd/gc):

    • Go言語の公式コンパイラです。ソースコードを機械語に変換する役割を担います。
    • コンパイル時には、構文解析、型チェック、最適化、コード生成などのフェーズがあります。
    • このコミットで問題となっているのは、主に型チェックのフェーズにおける埋め込みフィールドの解決とアクセス権限の検証です。
  5. インライン化 (Inlining):

    • コンパイラ最適化の一種で、関数呼び出しをその関数の本体のコードで置き換えることです。これにより、関数呼び出しのオーバーヘッドを削減し、パフォーマンスを向上させることができます。
    • このコミットのテストケースでは、one.goで定義されたメソッドがtwo.goで呼び出され、コンパイラがこれらのメソッドをインライン化しようとする際に、埋め込みフィールドのアクセスに関する型チェックがどのように行われるかが問題となります。インライン化されたコードでも、元のコードと同じ型チェックルールが適用されるべきです。

これらの概念を理解することで、コミットがなぜ特定の構造体定義とフィールドアクセスパターンをテストしているのか、そしてなぜそれがコンパイラのバグに関連しているのかが明確になります。

技術的詳細

このコミットは、Goコンパイラが構造体の埋め込みフィールドを処理する際の、特に曖昧なケースやエクスポートされていないフィールドのアクセスに関する挙動を検証しています。

テストケース test/bugs/bug434.dir/one.go では、以下のような構造体が定義されています。

  1. type T struct { int }

    • int型が匿名で埋め込まれています。Tのインスタンスからt.intとしてアクセスできます。
  2. type U struct { int int }

    • int型が2つ埋め込まれています。これはGoの言語仕様上、曖昧さ(ambiguity)を引き起こします。u.intとアクセスしようとすると、どちらのintを指すのかコンパイラは判断できません。
  3. type lint int

    • int型を基底とする新しい型lintが定義されています。
  4. type V struct { lint }

    • lint型が匿名で埋め込まれています。v.lintとしてアクセスできます。
  5. type W struct { lint lint }

    • lint型が2つ埋め込まれています。Uと同様に、w.lintとアクセスしようとすると曖昧さエラーとなります。

これらの構造体には、それぞれF()というメソッドが定義されており、埋め込まれたフィールドにアクセスしようとします。

  • func (t T) F() int { return t.int }
  • func (u U) F() int { return u.int }
  • func (v V) F() int { return int(v.lint) }
  • func (w W) F() int { return int(w.lint) }

test/bugs/bug434.dir/two.go では、one.goで定義されたこれらの型をインポートし、それぞれのF()メソッドを呼び出しています。コメントに「Use the functions in one.go so that the inlined forms get type-checked.」とあるように、これはコンパイラがこれらのメソッドをインライン化する際に、型チェックが正しく行われることを確認するためのものです。

test/bugs/bug434.go は、このテストを実行するためのスクリプトです。$G $D/$F.dir/one.go && $G $D/$F.dir/two.go || echo BUG:bug434 というコマンドは、one.gotwo.goをコンパイルし、もしコンパイルが成功しなかった場合(つまり、期待されるエラーが出なかった場合)にBUG:bug434というメッセージを出力します。

test/golden.out の変更は、このテストケースが期待するコンパイラのエラーメッセージを記録しています。

=========== bugs/bug434.go
bugs/bug434.dir/two.go:10: one.t.int undefined (cannot refer to unexported field or method one.int)
BUG:bug434

この出力は、two.goの10行目(_ = t.F()の行)で、one.t.intが未定義であるか、エクスポートされていないフィールドまたはメソッドを参照しているためアクセスできないというエラーが発生していることを示しています。これは、T構造体に埋め込まれたintフィールドが、パッケージoneの外からは直接アクセスできない(エクスポートされていない)ためです。

しかし、t.F()メソッド自体はoneパッケージ内で定義されており、その中ではt.intにアクセスできるはずです。このエラーメッセージは、コンパイラがt.F()の呼び出しをインライン化しようとした際に、two.goのコンテキストでt.intへのアクセスを再評価し、その際にアクセス権限のチェックが誤って適用された可能性を示唆しています。

本来、t.F()oneパッケージ内で定義されているため、t.intへのアクセスは許可されるべきです。このテストは、コンパイラがインライン化されたコードの型チェックを正しく行い、エクスポートルールを適切に適用できるかを検証しています。特に、UWのような曖昧な埋め込みフィールドを持つ構造体の場合、コンパイラが曖昧さエラーを正しく報告することも期待されます。

このコミットは、Goコンパイラの型システムと最適化(インライン化)の相互作用における微妙なバグを特定し、修正するための重要なステップです。

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

このコミットは、既存のコードの変更ではなく、主に新しいテストファイルを追加することで構成されています。

  1. test/bugs/bug434.dir/one.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 one
    
    // Issue 3552
    
    type T struct { int }
    
    func (t T) F() int { return t.int }
    
    type U struct { int int }
    
    func (u U) F() int { return u.int }
    
    type lint int
    
    type V struct { lint }
    
    func (v V) F() int { return int(v.lint) }
    
    type W struct { lint lint }
    
    func (w W) F() int { return int(w.lint) }
    
  2. test/bugs/bug434.dir/two.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.
    
    // Use the functions in one.go so that the inlined
    // forms get type-checked.
    
    package two
    
    import "./one"
    
    func use() {
        var t one.T
        var u one.U
        var v one.V
        var w one.W
    
        _ = t.F()
        _ = u.F()
        _ = v.F()
        _ = w.F()
    }
    
  3. test/bugs/bug434.go (新規追加):

    // $G $D/$F.dir/one.go && $G $D/$F.dir/two.go || echo BUG:bug434
    
    // Copyright 2011 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 ignored
    
  4. test/fixedbugs/bug396.dir/one.go (修正):

    • 既存のファイルに// Issue 2687というコメントが追加されています。これはIssue 3552とは直接関係なく、別のIssue番号が誤って追加されたか、関連する修正の一部である可能性があります。このコミットの主要な目的とは異なります。
  5. test/golden.out (修正):

    • 新しいテストケースbug434.goの期待される出力が追加されています。
    --- a/test/golden.out
    +++ b/test/golden.out
    @@ -22,3 +22,7 @@ throw: all goroutines are asleep - deadlock!
     
     =========== bugs/bug395.go
     bug395 is broken
    ++
    ++=========== bugs/bug434.go
    ++bugs/bug434.dir/two.go:10: one.t.int undefined (cannot refer to unexported field or method one.int)
    ++BUG:bug434
    

コアとなるコードの解説

このコミットの「コアとなるコード」は、test/bugs/bug434.dir/one.gotest/bugs/bug434.dir/two.go で定義されているテストケースそのものです。これらは、Goコンパイラが構造体の埋め込みフィールド、特にエクスポートされていないフィールドや曖昧なフィールドをどのように扱うかを検証するために設計されています。

one.go の解説

one.go は、テスト対象となる様々な構造体とそれらのメソッドを定義しています。

  • type T struct { int }func (t T) F() int { return t.int }:

    • int型が匿名で埋め込まれています。Goの言語仕様では、匿名フィールドは外側の構造体のフィールドとしてプロモートされます。このintフィールドは小文字で始まるため、oneパッケージ内でのみアクセス可能です(エクスポートされていない)。
    • F()メソッドはTのレシーバを持ち、t.intにアクセスしてその値を返します。このメソッドはoneパッケージ内で定義されているため、t.intへのアクセスは合法です。
  • type U struct { int int }func (u U) F() int { return u.int }:

    • int型が2つ匿名で埋め込まれています。これはGoの言語仕様において、u.intというアクセスがどちらのintを指すのか曖昧であるため、コンパイルエラーとなるべきケースです。
  • type lint int:

    • intを基底とする新しい型lintを定義しています。これは、組み込み型だけでなく、ユーザー定義型が埋め込まれた場合も同様の問題が発生するかをテストするためのものです。
  • type V struct { lint }func (v V) F() int { return int(v.lint) }:

    • Tと同様に、lint型が匿名で埋め込まれています。v.lintへのアクセスはoneパッケージ内では合法です。
  • type W struct { lint lint }func (w W) F() int { return int(w.lint) }:

    • Uと同様に、lint型が2つ匿名で埋め込まれています。これも曖昧さエラーとなるべきケースです。

two.go の解説

two.go は、one.go で定義された型をインポートし、それらのメソッドを呼び出すことで、コンパイラの挙動をトリガーします。

  • import "./one":

    • oneパッケージをインポートしています。これにより、oneパッケージで定義されたエクスポートされた型や関数にアクセスできるようになります。
  • func use() { ... }:

    • この関数内で、one.T, one.U, one.V, one.W のインスタンスを作成し、それぞれのF()メソッドを呼び出しています。
    • _ = t.F() などの行は、メソッドの戻り値を破棄していますが、重要なのはメソッド呼び出し自体がコンパイラによって型チェックされることです。
    • コメントにある「Use the functions in one.go so that the inlined forms get type-checked.」は、このテストの核心を示しています。Goコンパイラは、小さな関数を呼び出し元に直接展開する「インライン化」という最適化を行うことがあります。このテストは、one.goで定義されたF()メソッドがtwo.goにインライン化された場合でも、Goのアクセスルール(エクスポートされているか否か)が正しく適用されるかを検証しています。

golden.out の解説

golden.out に追加された出力は、このテストが期待するコンパイルエラーメッセージです。 bugs/bug434.dir/two.go:10: one.t.int undefined (cannot refer to unexported field or method one.int)

このエラーメッセージは、two.goの10行目(_ = t.F()の呼び出し箇所)で、one.T型のtintフィールドにアクセスしようとした際に、「エクスポートされていないフィールドまたはメソッドを参照できない」というエラーが発生したことを示しています。

なぜこれが問題なのか? t.F()メソッド自体はoneパッケージ内で定義されており、その中ではt.int(エクスポートされていないフィールド)にアクセスすることは合法です。しかし、two.gooneパッケージとは別のパッケージです。もしコンパイラがt.F()two.goにインライン化した場合、インライン化されたコードはtwoパッケージのコンテキストで型チェックされることになります。この時、t.intへのアクセスがtwoパッケージから直接行われていると誤解され、エクスポートルールに違反すると判断されてしまうバグが存在した可能性があります。

このテストは、コンパイラがインライン化されたコードに対しても、元の定義パッケージのアクセスルールを正しく適用できることを保証するために追加されました。つまり、t.F()two.goにインライン化されても、その内部のt.intへのアクセスはoneパッケージのルールに従って許可されるべきであり、このエラーメッセージはバグの存在を示唆しています。このコミットは、このバグが修正されたことを確認するためのものです。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (特に構造体、埋め込み、エクスポートルールに関するセクション)
  • Goコンパイラの内部構造に関する資料 (インライン化、型チェックのフェーズなど)
  • Go言語のIssueトラッカー (GitHub Issues)
  • Go言語のコードレビューシステム (Gerrit)
  • Go言語のソースコード (特にcmd/gcディレクトリ)