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

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

このコミットは、test/reorder.go ファイルに新しいテストケースを追加するものです。

コミット

commit 76490cffaf8c54f4e014cc8d74f77abde8cba416
Author: Ian Lance Taylor <iant@golang.org>
Date:   Tue Apr 24 10:17:26 2012 -0700

    test: add test for order of evaluation of map index on left of =
    
    Gccgo used to get this wrong.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/6121044

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

https://github.com/golang/go/commit/76490cffaf8c54f4e014cc8d74f77abde8cba416

元コミット内容

test: add test for order of evaluation of map index on left of =

Gccgo used to get this wrong.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6121044

変更の背景

このコミットの背景には、Go言語のコンパイラの一つであるGccgoが、マップのインデックス式が代入文の左辺にある場合の評価順序を誤って処理していたという問題があります。Go言語の仕様では、式が評価される順序が厳密に定義されており、特にマップのインデックス操作においては、インデックス式が評価されてから代入が行われる必要があります。Gccgoがこの順序を正しく守っていなかったため、予期せぬ動作やバグが発生する可能性がありました。

このコミットは、その誤った評価順序を特定し、将来的に同様の問題が再発しないようにするためのテストケースを追加することで、コンパイラの正確性を保証することを目的としています。

前提知識の解説

Go言語のマップ (map)

Go言語のマップは、キーと値のペアを格納するための組み込みのデータ構造です。他の言語のハッシュマップや辞書に相当します。マップはmake関数で初期化され、map[KeyType]ValueTypeのように宣言されます。キーは一意であり、値にアクセスしたり、値を設定したりするために使用されます。

例:

m := make(map[string]int) // stringをキー、intを値とするマップを作成
m["apple"] = 10           // キー"apple"に値10を代入
value := m["apple"]       // キー"apple"の値を取得

Go言語の式の評価順序

Go言語の仕様では、式の評価順序が厳密に定められています。これは、プログラムの動作が予測可能であることを保証するために非常に重要です。一般的に、Goの式は左から右に評価されますが、特定の演算子や構造(例えば、関数呼び出しの引数、代入文の左辺と右辺)には特定の評価規則が適用されます。

代入文 x = y の場合、まず右辺の y が評価され、その結果が左辺の x に代入されます。しかし、左辺が複雑な式(例えば、マップのインデックス式)である場合、その左辺の式自体も評価される必要があります。このコミットの文脈では、m[index_expression] = value_expression のような代入において、index_expressionvalue_expression よりも先に評価されることが保証される必要があります。

len() 関数

len()はGo言語の組み込み関数で、スライス、配列、文字列、マップ、チャネルの長さを返します。マップに対して使用した場合、マップに現在格納されているキーと値のペアの数を返します。

panic() 関数

panic()はGo言語の組み込み関数で、現在のゴルーチンを停止させ、パニック状態を引き起こします。これは通常、回復不可能なエラーが発生した場合に使用されます。パニックが発生すると、遅延関数(defer)が実行され、その後、呼び出しスタックをアンワインドしながら、各関数の遅延関数が実行されます。最終的に、プログラムがクラッシュするか、recover()関数によってパニックが捕捉されない限り、プログラムは終了します。

技術的詳細

このコミットが対処している技術的な問題は、Go言語の代入文におけるマップのインデックス式の評価順序です。具体的には、m[key_expression] = value_expression の形式の代入において、key_expressionvalue_expression よりも先に評価されるというGo言語の仕様に、Gccgoコンパイラが準拠していなかったという点です。

Go言語の仕様では、代入文の左辺にあるインデックス式(この場合はlen(m))は、右辺の式(この場合はlen(m)の結果)が評価される前に評価されるべきです。

もし key_expressionvalue_expression の後に評価された場合、value_expression の評価中にマップの状態が変更され、その変更が key_expression の評価に影響を与える可能性があります。これは、予測不能な動作や、Go言語の厳密な評価順序の保証に反する結果を招きます。

このテストケースでは、m[0] = len(m) という代入を行います。

  • 正しい評価順序:

    1. 左辺のインデックス式 0 が評価される。
    2. 右辺の len(m) が評価される。この時点で m は空なので 0 を返す。
    3. m[0]0 が代入される。
    4. 結果として m[0]0 になる。
  • Gccgoが誤っていた評価順序(推測):

    1. 右辺の len(m) が評価される。この時点で m は空なので 0 を返す。
    2. その後、左辺の m[0] が評価され、m[0]0 が代入される。

この特定のケースでは、len(m) が左辺のインデックス式に影響を与えることはありませんが、もしインデックス式が len(m) の結果に依存するような複雑な式であった場合、評価順序の誤りが致命的なバグにつながる可能性があります。このテストは、コンパイラがこの基本的な評価順序の保証を遵守していることを確認するためのものです。

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

--- a/test/reorder.go
+++ b/test/reorder.go
@@ -19,6 +19,7 @@ func main() {
 	p6()
 	p7()
 	p8()
+	p9()
 }
 
 var gx []int
@@ -119,3 +120,11 @@ func p8() {
 	i := 0
 	i, x[i], x[5] = 1, 100, 500
 }
+
+func p9() {
+	m := make(map[int]int)
+	m[0] = len(m)
+	if m[0] != 0 {
+		panic(m[0])
+	}
+}

コアとなるコードの解説

追加されたp9()関数は、マップのインデックスが代入文の左辺にある場合の評価順序をテストします。

func p9() {
	m := make(map[int]int) // intをキー、intを値とする新しい空のマップを作成
	m[0] = len(m)          // ここがテストの核心部分
	if m[0] != 0 {         // m[0]が0でなければパニック
		panic(m[0])
	}
}
  1. m := make(map[int]int): まず、空のintからintへのマップmが作成されます。この時点でのmの長さは0です。
  2. m[0] = len(m): この行がテストの肝です。
    • Go言語の仕様では、代入文の左辺の式(この場合はm[0]0)が、右辺の式(この場合はlen(m))よりも先に評価されます。
    • したがって、len(m)が評価される時点では、mはまだ空のマップであり、len(m)0を返します。
    • この0m[0]に代入されます。
  3. if m[0] != 0 { panic(m[0]) }: このif文は、m[0]が期待通り0になっているかを確認します。
    • もしGccgoが以前のように評価順序を誤っていた場合、例えばlen(m)が評価された後にm[0]への代入が行われ、その間にmの状態が何らかの形で変更される(この特定のケースでは起こりませんが、より複雑なシナリオではありえます)と、m[0]0以外の値になる可能性があります。
    • m[0]0以外であれば、panic()が呼び出され、テストが失敗します。これにより、コンパイラがマップのインデックス評価順序に関するGo言語の仕様に準拠していることが保証されます。

このシンプルなテストケースは、コンパイラがGo言語の厳密な評価順序規則を正しく実装していることを検証するための効果的な方法です。

関連リンク

参考にした情報源リンク

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

このコミットは、Go言語のテストスイートの一部である test/reorder.go ファイルに新しいテストケース p9() を追加するものです。このテストは、マップのインデックス式が代入文の左辺にある場合の評価順序がGo言語の仕様に準拠していることを検証するために設計されています。

コミット

commit 76490cffaf8c54f4e014cc8d74f77abde8cba416
Author: Ian Lance Taylor <iant@golang.org>
Date:   Tue Apr 24 10:17:26 2012 -0700

    test: add test for order of evaluation of map index on left of =
    
    Gccgo used to get this wrong.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/6121044

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

https://github.com/golang/go/commit/76490cffaf8c54f4e014cc8d74f77abde8cba416

元コミット内容

test: add test for order of evaluation of map index on left of =

Gccgo used to get this wrong.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6121044

変更の背景

このコミットの主な背景は、Go言語のコンパイラの一つである Gccgo が、マップのインデックス式が代入文の左辺に現れる際の評価順序に関して、Go言語の仕様に準拠していない挙動を示していたという問題にあります。Go言語の仕様では、代入文 LHS = RHS において、まず左辺 (LHS) と右辺 (RHS) の全てのオペランドが評価され、その後、実際の代入が実行されるという厳密な評価順序が定められています。特に、マップのインデックス式 m[key_expression] の場合、key_expressionRHS の評価よりも前に評価される必要があります。

Gccgo がこの評価順序を誤っていたため、Goプログラムの予測可能性と移植性に影響を与える可能性がありました。このようなコンパイラのバグは、特定のコードパターンで予期せぬ動作を引き起こす原因となります。このコミットは、この特定の評価順序の誤りを検出し、将来的に同様の問題が再発しないようにするための堅牢なテストケースをGoの標準テストスイートに追加することで、コンパイラの正確性とGo言語の仕様への準拠を保証することを目的としています。

前提知識の解説

Go言語のマップ (map)

Go言語におけるマップは、キーと値のペアを格納するための組み込みのデータ構造であり、他のプログラミング言語におけるハッシュマップ、辞書、または連想配列に相当します。マップは make 関数を使用して初期化され、map[KeyType]ValueType の形式で宣言されます。キーは一意であり、値への高速なアクセスを可能にします。

例:

m := make(map[string]int) // string型のキーとint型の値を持つマップを作成
m["apple"] = 10           // キー"apple"に値10を代入
value := m["apple"]       // キー"apple"の値を取得

Go言語の式の評価順序

Go言語のプログラムの動作が予測可能であるためには、式の評価順序が明確に定義されていることが不可欠です。Go言語の仕様では、式の評価は一般的に左から右へと進みますが、代入文や関数呼び出しの引数など、特定の構文にはより詳細な規則が適用されます。

代入文 x = y の場合、Goの仕様では以下の2段階の評価プロセスが定義されています。

  1. 評価フェーズ: 代入文の左辺 (LHS) と右辺 (RHS) の全てのオペランドが評価されます。この評価は、コードの記述順(左から右)に行われます。これには、マップのキー式や代入される値の式も含まれます。
  2. 実行フェーズ: 評価フェーズで得られた結果に基づいて、実際の代入が左から右へと実行されます。このフェーズで実行される代入は、評価フェーズの結果に影響を与えません。

この規則により、myMap[keyExpression] = valueExpression のような代入では、keyExpressionvalueExpression よりも先に評価されることが保証されます。この厳密な順序は、特に keyExpressionvalueExpression が副作用を持つ関数呼び出しや複雑な式である場合に重要となります。

len() 関数

len() はGo言語の組み込み関数で、スライス、配列、文字列、マップ、チャネルなどの組み込み型の長さを返します。マップに対して使用された場合、マップに現在格納されているキーと値のペアの数を返します。

panic() 関数

panic() はGo言語の組み込み関数で、現在のゴルーチンを即座に停止させ、パニック状態を引き起こします。これは通常、プログラムが回復できないエラー状態に陥った場合に使用されます。パニックが発生すると、defer ステートメントで登録された遅延関数が実行され、呼び出しスタックを遡りながら各関数の遅延関数が実行されます。recover() 関数によってパニックが捕捉されない限り、プログラムは最終的に終了します。テストにおいては、予期せぬ動作やエラーを検出するために panic() を使用することがあります。

技術的詳細

このコミットが修正しようとしている技術的な問題は、Go言語の代入文におけるマップのインデックス式の評価順序の誤りです。具体的には、m[key_expr] = value_expr の形式の代入において、Gccgo コンパイラが key_exprvalue_expr よりも後に評価してしまう可能性があったという点です。

Go言語の仕様では、代入文の評価フェーズにおいて、左辺の式(この場合はマップのインデックス式)が右辺の式よりも先に評価されることが保証されています。この順序が守られないと、特に key_exprvalue_expr が副作用を持つ場合、プログラムの動作が非決定論的になったり、予期せぬ結果を引き起こしたりする可能性があります。

このテストケース p9() では、m[0] = len(m) という代入が行われます。

  • Go言語の正しい評価順序:

    1. まず、左辺のマップインデックス 0 が評価されます。
    2. 次に、右辺の len(m) が評価されます。この時点で m は空のマップであるため、len(m)0 を返します。
    3. 最後に、評価された値 0m[0] に代入されます。
    4. 結果として、m[0] の値は 0 になります。
  • Gccgo が誤っていた可能性のある評価順序(推測): もし Gccgo がこの評価順序を誤っていた場合、例えば len(m) が先に評価され、その結果が代入される前にマップの状態が変更されるようなシナリオ(この特定のテストケースでは発生しませんが、より複雑な式では起こりえます)では、m[0] に期待される値が代入されない可能性があります。

このテストは、m[0] が最終的に 0 になることを if m[0] != 0 { panic(m[0]) } で検証しています。もし m[0]0 以外の値になった場合、それはコンパイラがGo言語の評価順序の仕様に違反していることを意味し、テストは panic を発生させて失敗します。これにより、コンパイラがマップのインデックス評価順序に関するGo言語の仕様を正しく実装していることを確認できます。

過去には、gccgo が配列やマップを含む特定の代入において、Goの仕様に反する評価順序の誤り(Issue #23188など)を抱えていたことが報告されています。このコミットは、そのような既知の、または潜在的な評価順序のバグを捕捉し、Goコンパイラの堅牢性を高めるためのものです。

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

--- a/test/reorder.go
+++ b/test/reorder.go
@@ -19,6 +19,7 @@ func main() {
 	p6()
 	p7()
 	p8()
+	p9()
 }
 
 var gx []int
@@ -119,3 +120,11 @@ func p8() {
 	i := 0
 	i, x[i], x[5] = 1, 100, 500
 }
+
+func p9() {
+	m := make(map[int]int)
+	m[0] = len(m)
+	if m[0] != 0 {
+		panic(m[0])
+	}
+}

コアとなるコードの解説

追加された p9() 関数は、マップのインデックスが代入文の左辺にある場合の評価順序を検証するためのものです。

func p9() {
	m := make(map[int]int) // intをキー、intを値とする新しい空のマップを作成
	m[0] = len(m)          // この行がテストの核心部分
	if m[0] != 0 {         // m[0]が0でなければパニック
		panic(m[0])
	}
}
  1. m := make(map[int]int): この行では、キーが int 型、値が int 型の新しい空のマップ m を作成します。この時点では、マップ m には要素が一つも含まれていないため、len(m)0 を返します。
  2. m[0] = len(m): この代入文がテストの主要な部分です。
    • Go言語の仕様に従うと、代入文の左辺にあるマップのインデックス式(この場合は m[0]0)は、右辺の式(この場合は len(m))よりも先に評価されます。
    • したがって、len(m) が評価される時点では、マップ m はまだ空であり、len(m)0 を返します。
    • この返された値 0 が、マップ m のキー 0 に対応する値として代入されます。
  3. if m[0] != 0 { panic(m[0]) }: この if 文は、m[0] の値が期待通り 0 であることを検証します。
    • もしコンパイラが評価順序を誤り、例えば len(m) の評価が先に実行され、その結果が代入される前にマップの状態が何らかの形で変更されるような状況(この特定のケースでは発生しませんが、より複雑なシナリオではありえます)が発生した場合、m[0]0 以外の値になる可能性があります。
    • m[0]0 でない場合、panic() が呼び出され、テストは失敗します。これにより、コンパイラがGo言語のマップインデックス評価順序の仕様に厳密に準拠していることが保証されます。

この簡潔なテストケースは、Goコンパイラが言語仕様で定められた厳密な評価順序規則を正しく実装していることを検証するための効果的な手段となります。

関連リンク

参考にした情報源リンク