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

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

このコミットは、Goコンパイラ(cmd/gc)におけるポインタ複合リテラルがif文の条件式内で使用された場合のバグ修正に関するものです。具体的には、エクスポートされたif文の条件式内でポインタ複合リテラルが正しく処理されない問題に対処しています。

コミット

commit 20ebee2c31688e6b67c1c5c235616d67cdd4ac09
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Sun Jun 23 18:39:07 2013 +0200

    cmd/gc: fix pointer composite literals in exported if statements.
    
    Fixes #4230 (again).
    
    R=rsc, golang-dev, r
    CC=golang-dev
    https://golang.org/cl/10470043

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

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

元コミット内容

このコミットは、Goコンパイラ(cmd/gc)がポインタ複合リテラルをif文の条件式内で処理する際の不具合を修正するものです。特に、if文の初期化ステートメントでポインタ複合リテラルが使用された場合に、コンパイラが生成するコードのフォーマットが誤っていた問題に対応しています。コミットメッセージには「Fixes #4230 (again)」とあり、これは以前にも同様のバグが報告され、修正が試みられたものの、完全に解決されていなかったことを示唆しています。

変更の背景

Go言語では、複合リテラル(Composite Literal)を使用して構造体、配列、スライス、マップなどの複合型の値を簡潔に初期化できます。例えば、&T{field1: value1, field2: value2}のように記述することで、T型の新しいインスタンスを生成し、そのポインタを返すことができます。

このコミットの背景にある問題は、Goコンパイラ(cmd/gc)が、if文の初期化ステートメント内でこのようなポインタ複合リテラルが使用された際に、内部的なコード表現(ASTノードのフォーマット)を誤って処理していたことに起因します。具体的には、fmt.c内のexprfmt関数が、ポインタ複合リテラルを表現するASTノード(OPTRLIT)を整形する際に、余分な括弧を追加していなかったため、生成されるコードが構文的に不正になる可能性がありました。

コミットメッセージにある「Fixes #4230 (again)」は、この問題がGo issue #4230として追跡されており、過去にも修正が試みられたが、特定のケース(特にif文の初期化ステートメント内での使用)で再発していたことを示しています。この再発は、コンパイラのコード生成ロジックにおけるエッジケースの取り扱いが不完全であったことを示唆しています。

前提知識の解説

  • Goコンパイラ (cmd/gc): Go言語の公式コンパイラの一つで、Goソースコードを機械語に変換する役割を担います。コンパイラは、字句解析、構文解析、意味解析、中間コード生成、最適化、コード生成といった複数のフェーズを経て処理を行います。
  • 抽象構文木 (AST): ソースコードの構文構造を木構造で表現したものです。コンパイラの構文解析フェーズで生成され、その後の意味解析やコード生成の基盤となります。Goコンパイラ内部では、各ノードが特定のGo言語の構文要素(変数、関数呼び出し、リテラルなど)に対応します。
  • 複合リテラル (Composite Literal): Go言語で構造体、配列、スライス、マップなどの複合型の値を初期化するための構文です。例えば、MyStruct{Field: value}のように記述します。
  • ポインタ複合リテラル: 複合リテラルの前に&演算子を付けることで、その複合リテラルのアドレス(ポインタ)を生成します。例: &MyStruct{Field: value}。これは、新しいMyStructのインスタンスをヒープに割り当て、そのポインタを返すのと同等です。
  • if文の初期化ステートメント: Go言語のif文は、条件式の前に短いステートメントを記述できます。例: if x := f(); x > 0 { ... }。この機能は、条件式で使用する変数をスコープを限定して宣言するのに便利です。
  • fmt.c: Goコンパイラのソースコードの一部で、ASTノードをフォーマット(整形)して出力する役割を持つファイルです。デバッグ出力や、コンパイラが生成する中間コードの可読性を高めるために使用されることがあります。
  • OPTRLIT: Goコンパイラの内部で、ポインタ複合リテラルを表すASTノードの種類(オペレーションコード)です。
  • FExp: fmt.c内で使用されるフォーマットモードの一つで、式(Expression)を整形する際に適用されるモードです。

技術的詳細

この修正は、Goコンパイラのsrc/cmd/gc/fmt.cファイル内のexprfmt関数に焦点を当てています。exprfmt関数は、ASTノードを文字列としてフォーマットする役割を担っています。

問題は、exprfmt関数がOPTRLIT(ポインタ複合リテラル)ノードを処理する際に発生していました。元のコードでは、fmtmode == FExp && ptrlitの条件が真の場合、つまり式としてポインタ複合リテラルをフォーマットする際に、&%T{ %,H }というフォーマット文字列を使用していました。ここで%Tは型を、%,Hは複合リテラルの要素を意味します。

このフォーマットは、単純なポインタ複合リテラル(例: &T{1, 2})の場合には問題ありませんでしたが、これがif文の初期化ステートメントのような文脈で使用されると、コンパイラが生成する内部表現が曖昧になり、構文エラーを引き起こす可能性がありました。

修正は、このフォーマット文字列を(&%T{ %,H })に変更することです。つまり、ポインタ複合リテラル全体を明示的に括弧で囲むようにしました。これにより、生成されるコードの構文的な曖昧さが解消され、コンパイラが正しくパースできるようになります。

この変更は、コンパイラの内部的なASTノードの表現と、それを文字列として出力する際の整合性を保つためのものです。特に、if文の初期化ステートメントのように、式が文脈によって異なる解釈をされる可能性がある場所では、明示的な括弧が構文の明確性を保証するために重要となります。

テストケースの追加もこの修正の重要な部分です。test/fixedbugs/bug465.dir/a.goF8F9という新しい関数が追加されています。これらの関数は、if文の初期化ステートメント内でポインタ複合リテラルを使用するシナリオを具体的にテストしています。

  • F8: if a := (&T{1, 1}); a != nil { ... } の形式で、:= を用いた短い変数宣言とポインタ複合リテラルを組み合わせています。
  • F9: if a = (&T{1, 1}); a != nil { ... } の形式で、既存の変数への代入とポインタ複合リテラルを組み合わせています。

これらのテストケースは、修正が正しく機能し、以前のバグが再発しないことを保証するために不可欠です。

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

このコミットにおけるコアとなるコードの変更は、主にsrc/cmd/gc/fmt.cファイルにあります。

--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -1222,7 +1222,7 @@ exprfmt(Fmt *f, Node *n, int prec)\n 		}\n 		if(fmtmode == FExp && ptrlit)\n 			// typecheck has overwritten OIND by OTYPE with pointer type.\n-			return fmtprint(f, "&%T{ %,H }", n->right->type->type, n->list);\n+			return fmtprint(f, "(&%T{ %,H })", n->right->type->type, n->list);\n 		return fmtprint(f, "(%N{ %,H })", n->right, n->list);\n 
 	case OPTRLIT:

また、この修正を検証するためのテストケースがtest/fixedbugs/bug465.dir/に追加されています。

test/fixedbugs/bug465.dir/a.goの変更:

--- a/test/fixedbugs/bug465.dir/a.go
+++ b/test/fixedbugs/bug465.dir/a.go
@@ -59,3 +59,18 @@ func F7() int {
 	}\n 	return 0\n }\n+\n+func F8() int {\n+\tif a := (&T{1, 1}); a != nil {\n+\t\treturn 1\n+\t}\n+\treturn 0\n+}\n+\n+func F9() int {\n+\tvar a *T\n+\tif a = (&T{1, 1}); a != nil {\n+\t\treturn 1\n+\t}\n+\treturn 0\n+}\n```

`test/fixedbugs/bug465.dir/b.go`の変更:
```diff
--- a/test/fixedbugs/bug465.dir/b.go
+++ b/test/fixedbugs/bug465.dir/b.go
@@ -9,7 +9,7 @@ import "./a"\n func main() {\n 	for _, f := range []func() int{\n 		a.F1, a.F2, a.F3, a.F4,\n-\t\ta.F5, a.F6, a.F7} {\n+\t\ta.F5, a.F6, a.F7, a.F8, a.F9} {\n 		if f() > 1 {\n 			panic("f() > 1")\n 		}\n```

## コアとなるコードの解説

`src/cmd/gc/fmt.c`の変更は、`exprfmt`関数内の特定の条件分岐にあります。

```c
if(fmtmode == FExp && ptrlit)
    // typecheck has overwritten OIND by OTYPE with pointer type.
    return fmtprint(f, "(&%T{ %,H })", n->right->type->type, n->list);
  • fmtmode == FExp: これは、現在フォーマットしているASTノードが式(Expression)として扱われていることを示します。
  • ptrlit: これは、現在のノードがポインタ複合リテラル(OPTRLIT)であることを示すフラグです。
  • fmtprint(f, "(&%T{ %,H })", ...): ここが変更された行です。以前は"&%T{ %,H }"でしたが、"(&%T{ %,H })"に変更されました。
    • &%T{ %,H }: これは、Go言語のポインタ複合リテラルの構文&Type{field: value}に対応する内部表現を文字列として出力するためのフォーマットです。
    • 変更点である外側の括弧 () は、生成されるコードの構文的な明確性を高めるために追加されました。これにより、if文の初期化ステートメントのような文脈で、ポインタ複合リテラルが正しくグループ化され、コンパイラが曖昧さなくパースできるようになります。

この修正は、コンパイラのコード生成における微細なバグを修正し、特定の構文パターン(if文の初期化ステートメント内のポインタ複合リテラル)が正しく処理されるようにするために重要です。

テストファイルtest/fixedbugs/bug465.dir/a.goに追加されたF8F9関数は、この修正が意図通りに機能することを確認するためのものです。

  • F8: if a := (&T{1, 1}); a != nil { ... }
    • このケースでは、if文の初期化ステートメントで新しい変数aが宣言され、ポインタ複合リテラル(&T{1, 1})がその値として割り当てられます。この構文が以前のバグの影響を受けていました。
  • F9: if a = (&T{1, 1}); a != nil { ... }
    • このケースでは、既存の変数aにポインタ複合リテラル(&T{1, 1})が代入されます。これも同様に、if文の初期化ステートメント内でのポインタ複合リテラルの使用をテストしています。

test/fixedbugs/bug465.dir/b.goでは、main関数内のテスト関数のリストにa.F8a.F9が追加され、これらの新しいテストケースが実行されるようにしています。これにより、コンパイラの修正が正しく適用され、これらの特定のシナリオでコンパイルエラーやランタイムエラーが発生しないことが保証されます。

関連リンク

参考にした情報源リンク