[インデックス 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/printerpackage on GitHub. The issue, titled "go/printer crashes if it encounters a FuncType with nil Params field", describes a bug where thego/printerwould crash when encountering aFuncTypewith anilParamsfield, even though thego/astcomment indicated that this field could benilfor functions with no parameters. (Source: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEvIo0U7pQWK_uhTXd6J0rox9_7NnH3z3H1g8YgP-h5fdsh8JBv52TuE4SHcltR22Qy9lhckAnKhz6oXrPpAKwmfcTHcyqoeJv2Ee9D0fUqxuKFQpATDsCoy9YRKa98_eVel9s=)