[インデックス 10352] ファイルの概要
このコミットは、Go言語のコードフォーマッタであるgofmt
が、抽象構文木(AST)内のnil
ノードを処理する際にパニックを起こす問題を修正するものです。具体的には、item.field
のような式に対して同一性変換を適用しようとした際に発生するパニックを防ぐための変更が加えられました。この修正により、gofmt
の堅牢性が向上し、特定の不正なAST構造を持つコードに対しても安定して動作するようになります。
コミット
commit 2ba079868204837891e531eecb4215eecfee8ff7
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Fri Nov 11 14:11:30 2011 -0800
gofmt: leave nil nodes of the AST unchanged.
Without this check, gofmt panics when trying to apply
the identity transformation on "item.field" expressions.
Fixes #2410.
R=rsc, gri
CC=golang-dev, remy
https://golang.org/cl/5376061
---
src/cmd/gofmt/gofmt_test.go | 1 +
src/cmd/gofmt/rewrite.go | 4 ++--
src/cmd/gofmt/testdata/rewrite3.golden | 12 ++++++++++++\n src/cmd/gofmt/testdata/rewrite3.input | 12 ++++++++++++\n 4 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/src/cmd/gofmt/gofmt_test.go b/src/cmd/gofmt/gofmt_test.go
index 4432a178bc..303c4f1e1c 100644
--- a/src/cmd/gofmt/gofmt_test.go
+++ b/src/cmd/gofmt/gofmt_test.go
@@ -76,6 +76,7 @@ var tests = []struct {
{"testdata/old.input\", \"\"},\n \t{\"testdata/rewrite1.input\", \"-r=Foo->Bar\"},\n \t{\"testdata/rewrite2.input\", \"-r=int->bool\"},\n+\t{\"testdata/rewrite3.input\", \"-r=x->x\"},\n \t{\"testdata/stdin*.input\", \"-stdin\"},\n \t{\"testdata/comments.input\", \"\"},\n \t{\"testdata/import.input\", \"\"},\ndiff --git a/src/cmd/gofmt/rewrite.go b/src/cmd/gofmt/rewrite.go
index 25049f8f8c..60a4a7b49f 100644
--- a/src/cmd/gofmt/rewrite.go
+++ b/src/cmd/gofmt/rewrite.go
@@ -159,8 +159,8 @@ func match(m map[string]reflect.Value, pattern, val reflect.Value) bool {
\tif m != nil && pattern.IsValid() && pattern.Type() == identType {\n \t\tname := pattern.Interface().(*ast.Ident).Name\n \t\tif isWildcard(name) && val.IsValid() {\n-\t\t\t// wildcards only match expressions\n-\t\t\tif _, ok := val.Interface().(ast.Expr); ok {\n+\t\t\t// wildcards only match valid (non-nil) expressions.\n+\t\t\tif _, ok := val.Interface().(ast.Expr); ok && !val.IsNil() {\n \t\t\t\tif old, ok := m[name]; ok {\n \t\t\t\t\treturn match(nil, old, val)\n \t\t\t\t}\ndiff --git a/src/cmd/gofmt/testdata/rewrite3.golden b/src/cmd/gofmt/testdata/rewrite3.golden
new file mode 100644
index 0000000000..0d16d16011
--- /dev/null
+++ b/src/cmd/gofmt/testdata/rewrite3.golden
@@ -0,0 +1,12 @@
+// Copyright 2011 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
+
+// Field tags are *ast.BasicLit nodes that are nil when the tag is
+// absent. These nil nodes must not be mistaken for expressions,
+// the rewriter should not try to dereference them. Was issue 2410.
+type Foo struct {
+ Field int
+}
diff --git a/src/cmd/gofmt/testdata/rewrite3.input b/src/cmd/gofmt/testdata/rewrite3.input
new file mode 100644
index 0000000000..0d16d16011
--- /dev/null
+++ b/src/cmd/gofmt/testdata/rewrite3.input
@@ -0,0 +1,12 @@
+// Copyright 2011 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
+
+// Field tags are *ast.BasicLit nodes that are nil when the tag is
+// absent. These nil nodes must not be mistaken for expressions,
+// the rewriter should not try to dereference them. Was issue 2410.
+type Foo struct {
+ Field int
+}
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2ba079868204837891e531eecb4215eecfee8ff7
元コミット内容
gofmt: leave nil nodes of the AST unchanged.
Without this check, gofmt panics when trying to apply
the identity transformation on "item.field" expressions.
Fixes #2410.
R=rsc, gri
CC=golang-dev, remy
https://golang.org/cl/5376061
変更の背景
このコミットは、Go言語の公式フォーマッタであるgofmt
が特定の状況下でパニック(プログラムの異常終了)を起こすバグを修正するために行われました。具体的には、gofmt
がコードの抽象構文木(AST)を処理する際に、nil
(ヌル)であるべきノードを誤って有効な式として扱おうとすると、パニックが発生していました。
問題は、item.field
のような式、特に構造体のフィールドタグなど、AST上ではnil
になりうる部分に対して、gofmt
が「同一性変換」(つまり、何も変更しない変換)を適用しようとした際に顕在化しました。gofmt
の内部処理では、ASTノードが有効な式であるかどうかをチェックしますが、このチェックが不十分であったため、nil
のノードが式として認識され、その後の処理でnil
ポインタ参照が発生し、パニックに至っていました。
このバグは、Goの内部イシュートラッカーで「#2410」として報告されており、gofmt
の安定性と信頼性を確保するために修正が必要でした。
前提知識の解説
gofmt
gofmt
は、Go言語のソースコードを自動的にフォーマットするためのツールです。Go言語には厳格なコーディングスタイルガイドラインがあり、gofmt
はそのガイドラインに沿ってコードを整形することで、Goコミュニティ全体で一貫したコードスタイルを維持するのに役立っています。開発者はgofmt
を使用することで、インデント、スペース、改行などのスタイルに関する議論を避け、コードの可読性を高めることができます。gofmt
はGoのツールチェインの一部として提供されており、通常はgo fmt
コマンドを通じて利用されます。
抽象構文木 (AST: Abstract Syntax Tree)
抽象構文木(AST)は、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタプリタは、ソースコードを直接処理するのではなく、まずソースコードを解析(パース)してASTを生成します。ASTは、コードの論理的な構造を反映しており、コメントや空白などの構文上の詳細を省略しています。
Go言語では、go/ast
パッケージがASTの構造を定義しており、go/parser
パッケージがソースコードからASTを構築する機能を提供します。gofmt
のようなツールは、このASTを操作してコードの整形やリファクタリングを行います。ASTの各ノードは、変数、関数、式、ステートメントなどのコード要素を表します。
Goにおけるnil
Go言語におけるnil
は、ポインタ、インターフェース、マップ、スライス、チャネルなどの参照型変数が「ゼロ値」または「何も指していない」状態であることを示します。nil
は他の言語のnull
に似ていますが、Goでは型によってnil
が意味するものが異なります。
例えば、ポインタがnil
である場合、それは有効なメモリアドレスを指していません。nil
ポインタをデリファレンス(参照解除)しようとすると、ランタイムパニックが発生し、プログラムがクラッシュします。このコミットで修正された問題は、まさにこのnil
ポインタデリファレンスに起因するものでした。
reflect
パッケージ
Goのreflect
パッケージは、実行時にプログラムの構造を検査(リフレクション)するための機能を提供します。これにより、変数の型、値、メソッドなどを動的に調べたり、変更したりすることが可能になります。
このコミットのコードでは、reflect.Value
型が使用されています。reflect.Value
は、Goの任意の型の値を表すことができます。val.IsValid()
はreflect.Value
が有効な値を表しているか(ゼロ値でないか)をチェックし、val.IsNil()
はreflect.Value
がnil
であるかどうかをチェックします。gofmt
は、ASTノードをreflect.Value
として扱い、その型や値を動的に検査することで、リライト処理を行っています。
技術的詳細
gofmt
のrewrite.go
ファイルは、コードのリライト(書き換え)ロジックを担っています。このファイル内のmatch
関数は、パターンマッチングを通じてASTノードを比較し、必要に応じて変換を適用します。
問題の核心は、match
関数内でワイルドカード(_
やx
などのプレースホルダー)がASTノードにマッチするかどうかを判断する部分にありました。ワイルドカードは通常、式(ast.Expr
)にマッチすることを意図しています。しかし、GoのASTでは、特定の要素(例えば、構造体のフィールドタグが省略された場合など)がnil
のast.BasicLit
ノードとして表現されることがあります。
以前のコードでは、val.IsValid()
のチェックは行われていましたが、val.IsNil()
のチェックが欠けていました。そのため、val
が有効なreflect.Value
であり、かつast.Expr
型にキャスト可能であると判断された場合でも、その実体がnil
である可能性がありました。
// 変更前
if _, ok := val.Interface().(ast.Expr); ok {
// ...
}
// 変更後
if _, ok := val.Interface().(ast.Expr); ok && !val.IsNil() {
// ...
}
val.Interface().(ast.Expr)
は、reflect.Value
がラップしている実際の値をast.Expr
インターフェース型に変換しようとします。この変換は、val
がnil
であっても、その基底の型がast.Expr
インターフェースを満たすポインタ型であれば成功する可能性があります(例: (*ast.BasicLit)(nil)
)。しかし、その後にval
が指す値(この場合はnil
)をデリファレンスしようとすると、パニックが発生します。
このコミットでは、!val.IsNil()
という追加のチェックが導入されました。これにより、val
がast.Expr
型にキャスト可能であるだけでなく、実際にnil
ではない有効な式である場合にのみ、ワイルドカードのマッチングとそれに続く処理が行われるようになりました。これにより、nil
ポインタデリファレンスによるパニックが回避されます。
テストケースとして追加されたtestdata/rewrite3.input
は、フィールドタグが省略された構造体type Foo struct { Field int }
を含んでいます。このような構造体は、AST上ではnil
の*ast.BasicLit
ノードを持つことがあり、これが以前のgofmt
のパニックを引き起こす原因となっていました。このテストケースは、修正が正しく機能することを確認するために使用されます。
コアとなるコードの変更箇所
変更はsrc/cmd/gofmt/rewrite.go
ファイル内のmatch
関数にあります。
--- a/src/cmd/gofmt/rewrite.go
+++ b/src/cmd/gofmt/rewrite.go
@@ -159,8 +159,8 @@ func match(m map[string]reflect.Value, pattern, val reflect.Value) bool {
if m != nil && pattern.IsValid() && pattern.Type() == identType {
name := pattern.Interface().(*ast.Ident).Name
if isWildcard(name) && val.IsValid() {
- // wildcards only match expressions
- if _, ok := val.Interface().(ast.Expr); ok {
+ // wildcards only match valid (non-nil) expressions.
+ if _, ok := val.Interface().(ast.Expr); ok && !val.IsNil() {
if old, ok := m[name]; ok {
return match(nil, old, val)
}
コアとなるコードの解説
変更の核心は、if _, ok := val.Interface().(ast.Expr); ok
の条件に && !val.IsNil()
が追加された点です。
-
変更前:
if _, ok := val.Interface().(ast.Expr); ok
この行は、val
が表す値がast.Expr
インターフェース型に変換可能かどうかをチェックしていました。val
がnil
ポインタであっても、その基底の型がast.Expr
インターフェースを満たすポインタ型であれば、このチェックはtrue
を返してしまう可能性がありました。例えば、(*ast.BasicLit)(nil)
のような値はast.Expr
インターフェースを満たしますが、実体はnil
です。 -
変更後:
if _, ok := val.Interface().(ast.Expr); ok && !val.IsNil()
この変更により、val
がast.Expr
インターフェース型に変換可能であることに加えて、val
がnil
ではない(つまり、有効な値を指している)ことも同時にチェックされるようになりました。val.IsNil()
メソッドは、reflect.Value
がラップしている値がnil
であるかどうかを正確に判断します。
この追加された!val.IsNil()
チェックによって、gofmt
はnil
のASTノードを有効な式として誤って処理しようとすることがなくなり、結果としてnil
ポインタデリファレンスによるパニックが回避されるようになりました。これは、gofmt
がASTをより堅牢に、かつ安全に処理するための重要な修正です。
関連リンク
- Go CL (Code Review) リンク: https://golang.org/cl/5376061
- Go Issue #2410: このコミットメッセージで参照されている
#2410
は、Goの内部イシュートラッカーの番号である可能性が高いです。公開されているGitHubリポジトリのIssue #2410は、golang/vscode-go
に関するものであり、このgofmt
のパニックとは直接関係ありません。
参考にした情報源リンク
- Web search results for "gofmt panic nil AST nodes issue 2410" (Medium.com, Joeshaw.org articles)
- Go言語の公式ドキュメント(
go/ast
,go/parser
,reflect
パッケージに関する情報)