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

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

このコミットは、Go言語の初期開発段階において、ポインタを介したスライス(配列)要素へのアクセスに関するコンパイラまたはランタイムのバグを特定し、その再現と検証を目的とした新しいテストケースを追加するものです。具体的には、*[]int 型のポインタからスライス要素にアクセスする際の挙動が誤っていた問題("wrong code for array access")を浮き彫りにしています。

コミット

commit 20b9bfb1365d85cafbaad2ff882572579feae620
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Nov 12 14:57:23 2008 -0800

    wrong code for array access
    
    R=r
    OCL=19107
    CL=19109
---
 test/bugs/bug119.go | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/test/bugs/bug119.go b/test/bugs/bug119.go
new file mode 100644
index 0000000000..0934a43070
--- /dev/null
+++ b/test/bugs/bug119.go
@@ -0,0 +1,32 @@
+// $G $D/$F.go && $L $F.$A && ./$A.out || echo BUG: should not fail
+//
+// Copyright 2009 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
+//
+func foo(a *[]int) int {
+//	return (*a)[0]  // this seesm to do the wrong thing
+}
+//
+func main() {
+//	a := &[]int{12};
+//	if x := a[0]   ; x != 12 { panicln(1, x) }
+//	if x := (*a)[0]; x != 12 { panicln(2, x) }
+//	if x := foo(a) ; x != 12 { panicln(3, x) }  // fails (x is incorrect)
+}
+//
+/*
+//uetli:~/Source/go1/test/bugs gri$ 6go bug119
+//3 70160
+//
+//panic on line 83 PC=0x14d6
+//0x14d6?zi
+//	main·main(23659, 0, 1, ...)\
+//	main·main(0x5c6b, 0x1, 0x7fff5fbff830, ...)\
+//0x52bb?zi
+//	mainstart(1, 0, 1606416432, ...)\
+//	mainstart(0x1, 0x7fff5fbff830, 0x0, ...)\
+//uetli:~/Source/go1/test/bugs gri$ 
+*/

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

https://github.com/golang/go/commit/20b9bfb1365d85cafbaad2ff882572579feae620

元コミット内容

    wrong code for array access
    
    R=r
    OCL=19107
    CL=19109

変更の背景

このコミットは、Go言語のコンパイラまたはランタイムにおける、スライス(Goでは動的な配列のようなもの)へのポインタを介した要素アクセスに関する深刻なバグを修正するためのものです。コミットメッセージ「wrong code for array access」が示す通り、特定の条件下でスライス要素にアクセスする際に、誤ったコードが生成されるか、あるいはランタイムが誤ったメモリ位置を参照してしまう問題が発生していました。

このバグは、*[]int のような「スライスへのポインタ」型に対して、(*a)[0] のようにポインタをデリファレンスしてから要素にアクセスする操作、または a[0] のように暗黙的にデリファレンスされると期待される操作が正しく機能しないことを意味します。この種のバグは、プログラムの予期せぬクラッシュや誤ったデータ処理を引き起こす可能性があり、言語の基本的な型システムとメモリ管理の信頼性に関わるため、早期の特定と修正が不可欠でした。

このコミット自体はバグの修正コードを含んでおらず、その代わりに、このバグを確実に再現し、将来の回帰を防ぐための新しいテストケース test/bugs/bug119.go を追加しています。これは、バグ修正のワークフローにおいて、まず問題の再現手順を確立し、その上で修正を行い、修正が正しく機能することを確認するための重要なステップです。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念と、当時のGo言語開発の状況について理解しておく必要があります。

Go言語の初期開発段階

このコミットは2008年11月12日に行われています。Go言語が一般に公開されたのは2009年11月であり、このコミットはそれよりも約1年前のものです。この時期のGo言語はまだ活発な開発段階にあり、言語仕様、コンパイラ、ランタイムの設計と実装が頻繁に変更・改善されていました。そのため、現在では安定していると見なされるような基本的な操作(例:ポインタとスライス)においても、バグや未定義の挙動が存在する可能性がありました。

Goにおけるポインタ

Go言語のポインタは、変数のメモリアドレスを指し示す型です。

  • T 型の変数のアドレスは &T で取得します。
  • *TT 型の値を指すポインタの型です。
  • ポインタが指す値にアクセスするには、* 演算子を使ってデリファレンス(間接参照)します。例: *p はポインタ p が指す値です。

Goにおけるスライス

スライスはGo言語の強力な機能の一つで、可変長シーケンスを表します。内部的には、基盤となる配列へのポインタ、長さ(len)、容量(cap)の3つの要素から構成されます。スライスは配列とは異なり、そのサイズを動的に変更できます。

ポインタとスライスの組み合わせ

このコミットの核心は、*[]int のように「スライスへのポインタ」を扱う際の挙動です。これは、スライスそのものではなく、スライスを指すポインタを意味します。

  • []int: int 型のスライス。
  • *[]int: int 型のスライスへのポインタ。

通常、スライス s の要素にアクセスするには s[index] を使用します。しかし、p*[]int 型の場合、p はスライスそのものではなく、スライスへのポインタです。したがって、要素にアクセスするには (*p)[index] のように明示的にデリファレンスしてからインデックスアクセスを行うのが一般的です。ただし、Go言語のコンパイラは、特定の文脈でポインタを自動的にデリファレンスする「ポインタの自動デリファレンス」のような挙動を示すことがあります。このバグは、その自動デリファレンスまたは明示的なデリファレンスとインデックスアクセスの組み合わせが正しく処理されていなかったことを示唆しています。

技術的詳細

このバグは、Goコンパイラが *[]int 型の変数に対して (*a)[0] のような式を処理する際に、生成されるアセンブリコードが誤っていたことに起因すると考えられます。具体的には、以下のいずれか、または複数の問題が発生していた可能性があります。

  1. アドレス計算の誤り: ポインタ a が指すスライスの基盤となる配列の先頭アドレスを正しく特定できていなかった。スライスは内部的にデータポインタ、長さ、容量を持つ構造体であるため、*a はこの構造体全体を指し、その中のデータポインタを抽出してオフセットを計算する必要がありました。この計算ロジックに誤りがあった可能性があります。
  2. 型情報の誤解釈: コンパイラが *[]int 型を正しく解釈せず、スライスへのポインタではなく、単なる配列へのポインタ、あるいは全く異なる型として扱ってしまった。これにより、要素アクセスに必要なオフセット計算や境界チェックが正しく行われなかった可能性があります。
  3. レジスタ割り当てまたはスタックフレームの問題: 生成されたコードが、スライス要素の値を格納すべきレジスタやスタック上の位置を誤って指定していた。これにより、期待される値とは異なる、メモリ上のゴミデータや別の変数の値が読み込まれてしまっていた。

bug119.go のコメントにある panic on line 83 PC=0x14d63 70160 という出力は、foo(a) の呼び出し(テストケース3)がパニックを引き起こし、期待値 12 の代わりに 70160 という誤った値が返されたことを明確に示しています。70160 という値は、多くの場合、初期化されていないメモリ領域や、意図しないメモリ位置から読み込まれたデータに見られるような、意味のない大きな整数値です。これは、コンパイラが (*a)[0] の評価において、正しいメモリアドレスを計算できず、結果として無効なデータを読み込んでしまったことを強く示唆しています。

このバグは、Go言語のコンパイラが、ポインタのデリファレンスとスライス(または配列)のインデックスアクセスという、基本的な操作の組み合わせを正しく処理するための、より堅牢なコード生成ロジックを必要としていたことを浮き彫りにしました。

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

このコミット自体は、バグを修正するGo言語のコンパイラやランタイムのコード変更を含んでいません。代わりに、このコミットは、問題の「配列アクセスにおける誤ったコード」を再現するための新しいテストファイル test/bugs/bug119.go を追加しています。

追加されたファイル:

  • test/bugs/bug119.go (32行追加)

このファイルは、Go言語のテストスイートの一部として、将来的にこのバグが再発しないことを保証するための回帰テストとして機能します。

コアとなるコードの解説

追加された test/bugs/bug119.go ファイルは、Go言語のテストフレームワークの一部として、特定のバグの再現と検証を行うためのものです。

// $G $D/$F.go && $L $F.$A && ./$A.out || echo BUG: should not fail

// Copyright 2009 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

func foo(a *[]int) int {
	return (*a)[0]  // this seesm to do the wrong thing
}

func main() {
	a := &[]int{12};
	if x := a[0]   ; x != 12 { panicln(1, x) }
	if x := (*a)[0]; x != 12 { panicln(2, x) }
	if x := foo(a) ; x != 12 { panicln(3, x) }  // fails (x is incorrect)
}

/*
uetli:~/Source/go1/test/bugs gri$ 6go bug119
3 70160

panic on line 83 PC=0x14d6
0x14d6?zi
	main·main(23659, 0, 1, ...)\
	main·main(0x5c6b, 0x1, 0x7fff5fbff830, ...)\
0x52bb?zi
	mainstart(1, 0, 1606416432, ...)\
	mainstart(0x1, 0x7fff5fbff830, 0x0, ...)\
uetli:~/Source/go1/test/bugs gri$ 
*/

このテストコードの主要な部分は以下の通りです。

  1. func foo(a *[]int) int { return (*a)[0] }:

    • この関数は、int 型のスライスへのポインタ a を引数として受け取ります。
    • 関数内部では、(*a)[0] という式を使って、ポインタ a が指すスライスの最初の要素にアクセスし、その値を返します。
    • コメント // this seesm to do the wrong thing は、この特定の操作がバグの原因であることを示唆しています。
  2. func main():

    • a := &[]int{12};: int 型のスライス {12} を作成し、そのスライスへのポインタを変数 a に代入しています。これで a*[]int 型となります。
    • if x := a[0] ; x != 12 { panicln(1, x) }:
      • これは、ポインタ a を直接インデックスアクセスしようとするケースです。Go言語のコンパイラは、このような文脈でポインタを自動的にデリファレンスしてスライス要素にアクセスできる場合があります。この行は、その挙動が正しいかをテストしています。
    • if x := (*a)[0]; x != 12 { panicln(2, x) }:
      • これは、ポインタ a を明示的にデリファレンス (*a) してから、スライス要素にインデックスアクセス [0] するケースです。これは、ポインタを介したスライス要素アクセスにおける最も直接的な方法であり、このバグの核心部分をテストしています。
    • if x := foo(a) ; x != 12 { panicln(3, x) }:
      • これは、foo 関数を呼び出し、その中で行われる (*a)[0] の操作が正しいかをテストしています。コメント // fails (x is incorrect) と、その後のコメントブロックにある実行結果 3 70160 が示す通り、このテストケースが実際に失敗し、foo 関数が期待される 12 ではなく 70160 という誤った値を返していたことが確認できます。
  3. コメントブロックの実行結果:

    • コードの下にあるコメントブロックは、このテストファイルが当時のGoコンパイラ(6go)でどのように実行され、どのような結果になったかを示しています。
    • 3 70160 という出力は、panicln(3, x) が実行されたことを意味し、x の値が 70160 であったことを示しています。これは、foo(a)12 を返すべきところで 70160 を返したため、テストが失敗したことを明確に証明しています。
    • panic on line 83 PC=0x14d6 は、パニックが発生した場所を示しており、バグが実際に存在したことを裏付けています。

このテストファイルは、Go言語のコンパイラ開発者が、ポインタとスライスの組み合わせにおける特定のコード生成の誤りを特定し、修正するための具体的な再現手順を提供しています。このテストが追加されたことで、将来的に同様のバグが導入された場合に、自動テストスイートによって検出されるようになります。

関連リンク

  • OCL=19107 および CL=19109: これらはGo言語の内部的な変更リスト(Change List)番号である可能性が高いです。当時のGoプロジェクトはGoogleの内部バージョン管理システム(Perforceなど)を使用しており、これらの番号は公開されているGitHubリポジトリからは直接参照できない場合があります。これらは、このコミットに関連する他の内部的な変更やレビュープロセスを示唆している可能性があります。

参考にした情報源リンク

  • コミットメッセージおよび test/bugs/bug119.go ファイルの内容。
  • Go言語のポインタとスライスの基本的な概念に関する一般的な知識。
  • Go言語の初期開発段階におけるコンパイラとランタイムの挙動に関する一般的な知識。