[インデックス 13505] ファイルの概要
このコミットは、Go言語のgo/printer
パッケージにおけるバグ修正です。具体的には、ast.FuncType
構造体のParams
フィールドがnil
である場合にgo/printer
がクラッシュする問題を解決します。go/ast
パッケージの仕様ではFuncType.Params
がnil
である可能性が許容されているにもかかわらず、go/printer
がそのケースを適切に処理していなかったため、この修正が必要となりました。
コミット
commit a7c74d52dc49a6b6e448e301165a76bf3fb0a3c7
Author: Robert Griesemer <gri@golang.org>
Date: Thu Jul 26 17:09:11 2012 -0700
go/printer: don't crash if ast.FuncType.Params is nil
The go/ast comment for FuncType.Params says that the field may be nil.
Make sure the printer accepts such a value. The go/parser always sets
the field (to provide parenthesis position information), but a program
creating a Go AST from scatch may not.
Added corresponding test case.
Fixes #3870.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6448060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a7c74d52dc49a6b6e448e301165a76bf3fb0a3c7
元コミット内容
このコミットは、go/printer
パッケージがast.FuncType.Params
フィールドがnil
の場合にクラッシュしないように修正します。go/ast
のコメントではFuncType.Params
フィールドがnil
である可能性があると明記されており、go/parser
は常にこのフィールドを設定するものの(括弧の位置情報を提供するため)、スクラッチからGoのASTを生成するプログラムではnil
になる可能性があるため、go/printer
がそのような値を許容するように変更されました。関連するテストケースも追加され、Issue #3870を修正します。
変更の背景
この変更の背景には、Go言語の抽象構文木(AST)を扱う際の、go/ast
パッケージとgo/printer
パッケージ間の整合性の問題がありました。
go/ast
の仕様:go/ast
パッケージはGoプログラムのASTを定義しており、ast.FuncType
構造体には関数のパラメータリストを表すParams
フィールドがあります。このフィールドに関するコメントでは、パラメータがない関数型の場合にParams
がnil
になる可能性があると記述されていました。go/parser
の挙動: 一方、go/parser
パッケージはGoのソースコードを解析してASTを生成しますが、このパーサーはパラメータがない関数であっても、Params
フィールドをnil
ではなく、空のast.FieldList
(つまり、List
が空のスライス)として設定していました。これは、関数の括弧()
の位置情報などを保持するためです。- 問題の発生:
go/printer
パッケージはASTをGoのソースコードに整形して出力する役割を担っています。しかし、go/parser
が生成するASTとは異なり、手動でASTを構築するプログラムや、他のツールが生成したASTでは、ast.FuncType.Params
が実際にnil
になる可能性がありました。go/printer
はParams
がnil
であるケースを想定しておらず、その結果、nil
ポインタ参照によるクラッシュが発生していました。 - Issue #3870: この問題はGitHubのGoリポジトリでIssue #3870として報告されました。報告された問題は、「
go/printer
がFuncType
のParams
フィールドがnil
の場合にクラッシュする」というものでした。
このコミットは、go/ast
の仕様に準拠し、go/printer
がnil
のParams
フィールドを安全に処理できるようにすることで、ASTを扱うツールの堅牢性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のパッケージと概念に関する知識が必要です。
-
抽象構文木 (Abstract Syntax Tree - AST):
- ASTは、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやリンター、コード整形ツールなどがソースコードを解析する際に内部的に使用します。
- Go言語では、
go/ast
パッケージがGoプログラムのASTを定義しています。
-
go/ast
パッケージ:- Go言語のソースコードの抽象構文木(AST)を表現するための型と関数を提供します。
ast.Node
: ASTのすべてのノードが実装するインターフェース。ast.FuncType
: 関数型(例:func(int, string) (bool, error)
)を表す構造体。Params
: 関数の引数リストを表す*ast.FieldList
型のフィールド。Results
: 関数の戻り値リストを表す*ast.FieldList
型のフィールド。
ast.FieldList
: 識別子と型を持つフィールドのリスト(例: 関数の引数や戻り値、構造体のフィールドなど)を表す構造体。List
:*ast.Field
のポインタのスライス。各*ast.Field
は単一のフィールド(変数名と型)を表します。
-
go/parser
パッケージ:- Go言語のソースコードを解析し、
go/ast
パッケージで定義されたASTを生成するパーサーを提供します。 go/parser
は、たとえパラメータがなくても、ast.FuncType.Params
フィールドをnil
ではなく、空のast.FieldList
(List
が空のスライス)として初期化する傾向があります。これは、ソースコード上の括弧()
の位置情報などを保持するためです。
- Go言語のソースコードを解析し、
-
go/printer
パッケージ:go/ast
パッケージで定義されたASTを受け取り、それをGoのソースコードとして整形して出力する機能を提供します。- このパッケージは、ASTを人間が読める形式のGoコードに変換する役割を担っています。
-
token
パッケージ:- Go言語の字句解析(lexical analysis)で使用されるトークン(キーワード、識別子、演算子など)を定義します。
token.LPAREN
は左括弧(
を、token.RPAREN
は右括弧)
を表します。
これらのパッケージが連携して、Goのソースコードの解析、操作、そして再生成を行います。このコミットは、特にgo/printer
がgo/ast
の仕様のすべてのケース(特にnil
のParams
フィールド)を適切に処理できるようにするためのものです。
技術的詳細
このコミットの技術的な詳細は、go/printer
パッケージ内のsignature
メソッドの挙動の変更と、それに対応するテストケースの追加に集約されます。
問題点:
従来のgo/printer
のsignature
メソッドは、関数のパラメータリストを処理する際に、params
(*ast.FieldList
型)が常に非nil
であると仮定していました。そのため、もしparams
がnil
であった場合、p.parameters(params)
の呼び出し内でnil
ポインタ参照が発生し、プログラムがクラッシュしていました。これは、go/ast
のドキュメントがFuncType.Params
がnil
である可能性を示唆しているにもかかわらず、go/printer
がそのケースを適切に扱っていなかったためです。go/parser
はParams
をnil
にしないため、この問題は手動でASTを構築するシナリオでのみ顕在化しました。
解決策:
コミットは、signature
メソッド内でparams
がnil
であるかどうかを明示的にチェックするロジックを追加することでこの問題を解決します。
params
のnil
チェック:if params != nil { ... }
という条件分岐が追加されました。- もし
params
が非nil
であれば、これまで通りp.parameters(params)
を呼び出して通常のパラメータリストを整形します。
params
がnil
の場合の処理:- もし
params
がnil
であれば、これはパラメータが全くない関数型を意味します。Goの構文では、パラメータがない関数は()
と表記されます。 - この場合、
p.print(token.LPAREN, token.RPAREN)
を呼び出して、明示的に空の括弧()
を出力するように変更されました。これにより、nil
ポインタ参照を回避しつつ、正しいGoの構文を生成できます。
- もし
テストケースの追加:
この修正の正しさを検証するために、src/pkg/go/printer/printer_test.go
にTestFuncType
という新しいテスト関数が追加されました。
- このテストでは、
ast.FuncType{}
という形で、Params
フィールドが明示的に設定されていない(つまりnil
である)ast.FuncType
を持つast.FuncDecl
(関数宣言)を含むASTをプログラム的に構築します。 - 構築されたASTを
go/printer.Fprint
関数で整形し、その出力が期待される文字列func f()
と一致するかどうかを検証します。 - このテストケースは、
go/printer
がnil
のParams
フィールドを適切に処理し、クラッシュせずに正しい出力を生成することを確認します。
この変更により、go/printer
はgo/ast
の仕様に完全に準拠し、より堅牢になりました。
コアとなるコードの変更箇所
--- a/src/pkg/go/printer/nodes.go
+++ b/src/pkg/go/printer/nodes.go
@@ -325,9 +325,14 @@ func (p *printer) parameters(fields *ast.FieldList) {
}
func (p *printer) signature(params, result *ast.FieldList) {
- p.parameters(params)
+ if params != nil {
+ p.parameters(params)
+ } else {
+ p.print(token.LPAREN, token.RPAREN)
+ }
n := result.NumFields()
if n > 0 {
+ // result != nil
p.print(blank)
if n == 1 && result.List[0].Names == nil {
// single anonymous result; no ()'s
--- a/src/pkg/go/printer/printer_test.go
+++ b/src/pkg/go/printer/printer_test.go
@@ -385,6 +385,35 @@ func (t *t) foo(a, b, c int) int {\n }\n }\n \n+// TestFuncType tests that an ast.FuncType with a nil Params field\n+// can be printed (per go/ast specification). Test case for issue 3870.\n+func TestFuncType(t *testing.T) {\n+\tsrc := &ast.File{\n+\t\tName: &ast.Ident{Name: \"p\"},\n+\t\tDecls: []ast.Decl{\n+\t\t\t&ast.FuncDecl{\n+\t\t\t\tName: &ast.Ident{Name: \"f\"},\n+\t\t\t\tType: &ast.FuncType{},\n+\t\t\t},\n+\t\t},\n+\t}\n+\n+\tvar buf bytes.Buffer\n+\tif err := Fprint(&buf, fset, src); err != nil {\n+\t\tt.Fatal(err)\n+\t}\n+\tgot := buf.String()\n+\n+\tconst want = `package p\n+\n+func f()\n+`\n+\n+\tif got != want {\n+\t\tt.Fatalf(\"got:\\n%s\\nwant:\\n%s\\n\", got, want)\n+\t}\n+}\n+\n // TextX is a skeleton test that can be filled in for debugging one-off cases.\n // Do not remove.\n func TestX(t *testing.T) {\n```
## コアとなるコードの解説
### `src/pkg/go/printer/nodes.go` の変更
`signature`関数は、関数のシグネチャ(パラメータと戻り値)を整形して出力する役割を担っています。
変更前:
```go
func (p *printer) signature(params, result *ast.FieldList) {
p.parameters(params)
n := result.NumFields()
if n > 0 {
p.print(blank)
if n == 1 && result.List[0].Names == nil {
// single anonymous result; no ()'s
変更前は、params
がnil
であるかどうかのチェックなしに直接p.parameters(params)
を呼び出していました。p.parameters
内部でparams
がnil
の場合にパニック(クラッシュ)する可能性がありました。
変更後:
func (p *printer) signature(params, result *ast.FieldList) {
if params != nil {
p.parameters(params)
} else {
p.print(token.LPAREN, token.RPAREN)
}
n := result.NumFields()
if n > 0 {
// result != nil
p.print(blank)
if n == 1 && result.List[0].Names == nil {
// single anonymous result; no ()'s
この変更では、params
がnil
であるかどうかを最初にチェックするif
文が追加されました。
if params != nil
:params
がnil
でない場合(つまり、パラメータリストが存在する場合)は、これまで通りp.parameters(params)
を呼び出してパラメータを整形します。else
:params
がnil
の場合(つまり、パラメータが全くない関数型の場合)は、p.print(token.LPAREN, token.RPAREN)
を呼び出して、Goの構文でパラメータなしを示す空の括弧()
を出力します。これにより、nil
ポインタ参照を防ぎつつ、正しい出力を保証します。// result != nil
というコメントが追加されていますが、これはn > 0
の条件がresult
がnil
でないことを暗に示しているため、その後の処理が安全であることを補足しています。
src/pkg/go/printer/printer_test.go
の変更
TestFuncType
という新しいテスト関数が追加されました。
// TestFuncType tests that an ast.FuncType with a nil Params field
// can be printed (per go/ast specification). Test case for issue 3870.
func TestFuncType(t *testing.T) {
src := &ast.File{
Name: &ast.Ident{Name: "p"},
Decls: []ast.Decl{
&ast.FuncDecl{
Name: &ast.Ident{Name: "f"},
Type: &ast.FuncType{},
},
},
}
var buf bytes.Buffer
if err := Fprint(&buf, fset, src); err != nil {
t.Fatal(err)
}
got := buf.String()
const want = `package p
func f()
`
if got != want {
t.Fatalf("got:\n%s\nwant:\n%s\n", got, want)
}
}
このテストは、ast.FuncType
のParams
フィールドがnil
であるケースを明示的にテストします。
src
という*ast.File
(Goのソースファイル全体を表すASTノード)を構築します。- その中に、
Name
が"f"
で、Type
が&ast.FuncType{}
である*ast.FuncDecl
(関数宣言)を追加します。ここで重要なのは、ast.FuncType{}
と初期化することで、そのParams
フィールドがGoのデフォルト値であるnil
になる点です。 bytes.Buffer
を使ってgo/printer.Fprint
の出力をキャプチャします。Fprint
がエラーなく実行され、クラッシュしないことを確認します。- 生成された文字列
got
が、期待される出力func f()
(want
)と完全に一致するかを検証します。これにより、nil
のParams
フィールドが正しく空の括弧として整形されることを確認します。
このテストは、Issue #3870で報告されたクラッシュを再現し、修正が正しく機能することを確認するための重要なものです。
関連リンク
- Go Issue #3870: https://github.com/golang/go/issues/3870
- Gerrit Change List (CL) 6448060: https://golang.org/cl/6448060
参考にした情報源リンク
- Go issue 3870 refers to an issue in the Go programming language's
go/printer
package on GitHub. The issue, titled "go/printer crashes if it encounters a FuncType with nil Params field", describes a bug where thego/printer
would crash when encountering aFuncType
with anil
Params
field, even though thego/ast
comment indicated that this field could benil
for functions with no parameters. (Source: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEvIo0U7pQWK_uhTXd6J0rox9_7NnH3z3H1g8YgP-h5fdsh8JBv52TuE4SHcltR22Qy9lhckAnKhz6oXrPpAKwmfcTHcyqoeJv2Ee9D0fUqxuKFQpATDsCoy9YRKa98_eVel9s=)