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

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

このコミットは、go/printer パッケージの堅牢性を向上させるための変更を含んでいます。具体的には、抽象構文木 (AST) に不正なノード(BadXXX ノード)が含まれている場合に、go/printer がクラッシュしないように修正されています。また、この変更を検証するためのテストケースが追加されています。

変更されたファイルは以下の通りです。

  • src/pkg/go/printer/printer.go: go/printer パッケージの主要なプリンタロジックが含まれるファイルです。不正なASTノードのハンドリングが改善されました。
  • src/pkg/go/printer/printer_test.go: go/printer パッケージのテストファイルです。BadXXX ノードを含むASTを処理する際のプリンタの挙動を検証する新しいテストが追加されました。

コミット

go/printer: don't crash if AST contains BadXXX nodes

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5535048

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

https://github.com/golang/go/commit/a30b172ca0cd34b8f06952e175ac6bc3d0f1f35b

元コミット内容

go/printer: don't crash if AST contains BadXXX nodes

このコミットは、go/printer パッケージが、Go言語のソースコードを解析して生成された抽象構文木 (AST) の中に、構文エラーなどによって生成される不正なノード(BadXXX ノード)が含まれている場合でも、プログラムが異常終了(クラッシュ)しないように修正することを目的としています。

変更の背景

Go言語のコンパイラツールチェーンにおいて、go/parser パッケージはGoのソースコードを解析してASTを生成し、go/printer パッケージはそのASTを元にGoのソースコードを整形して出力します。通常、go/parser は構文エラーを検出した場合、そのエラー箇所に対応するASTノードとして BadXXX ノード(例: BadExpr, BadStmt, BadDecl など)を挿入します。これは、パーサーがエラーを検出した後も、可能な限りASTの構築を続行し、後続の処理(例えば、エラーリカバリや部分的なコード解析)を可能にするための一般的な手法です。

しかし、このコミット以前の go/printer は、このような BadXXX ノードを適切に処理できない場合があり、結果としてプリンタがクラッシュする可能性がありました。これは、不正なGoコードを処理しようとした際に、ツールが予期せず終了してしまうという問題を引き起こします。開発ツールやIDEがGoコードを整形する際に go/printer を利用することを考えると、このようなクラッシュはユーザーエクスペリエンスを著しく損なうことになります。

このコミットの背景には、go/printer がより堅牢になり、不正な入力に対しても安定して動作することが求められたという経緯があります。特に、Goのツールエコシステムが成熟するにつれて、ユーザーが入力する可能性のあるあらゆる種類のコード(たとえそれが構文的に不正であっても)を適切に扱えるようにすることが重要になってきました。

前提知識の解説

抽象構文木 (Abstract Syntax Tree, AST)

抽象構文木 (AST) は、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタプリタがソースコードを解析する際に中間表現として生成されます。ASTは、ソースコードの具体的な構文(括弧やセミコロンなど)を抽象化し、プログラムの論理的な構造を表現します。

Go言語においては、go/ast パッケージがASTのデータ構造を定義しています。例えば、変数宣言、関数定義、式、文などがそれぞれASTノードとして表現されます。

go/parser パッケージ

go/parser パッケージは、Go言語のソースコードを解析し、そのソースコードに対応するASTを生成する役割を担っています。このパッケージは、字句解析(トークン化)と構文解析(パース)を行い、ソースコードがGo言語の文法規則に準拠しているかをチェックします。

構文エラーが検出された場合、go/parser はエラーを報告しつつ、ASTの該当箇所に BadXXX ノード(例: ast.BadExpr, ast.BadStmt, ast.BadDecl)を挿入することがあります。これにより、パーサーはエラーが発生した後も、可能な限り解析を続行し、部分的なASTを生成することができます。

go/printer パッケージ

go/printer パッケージは、go/parser によって生成されたASTを受け取り、それをGo言語のソースコードとして整形して出力する役割を担っています。このパッケージは、Goの公式なコードフォーマットルール(gofmt コマンドが使用するルール)に従ってコードを整形します。

go/printer はASTを走査し、各ノードの種類に応じて適切な文字列を生成し、インデントや改行を調整して読みやすいコードを出力します。

BadXXX ノード

BadXXX ノードは、go/parser がソースコードの解析中に構文エラーを検出した際に、AST内に挿入される特殊なノードです。例えば、ast.BadDecl は不正な宣言、ast.BadExpr は不正な式、ast.BadStmt は不正な文を表します。これらのノードは、エラーが発生した箇所をAST上でマークし、パーサーがエラーから回復して残りのコードの解析を試みることを可能にします。

BadXXX ノードは、それ自体が有効なGoの構文要素ではないため、go/printer がこれらのノードを処理する際には特別な注意が必要です。適切に処理されない場合、プリンタが予期しない状態に陥り、クラッシュする可能性があります。

技術的詳細

このコミットの技術的な核心は、go/printer パッケージ内の print メソッドが、ASTノードの型を処理する switch ステートメントにおいて、新たに string 型のケースを追加した点にあります。

従来の go/printer の実装では、print メソッドがASTノードを処理する際に、予期しない型(特に BadXXX ノードが内部的にどのように表現されるかによる)の引数を受け取ると、default ケースにフォールバックし、そこでパニック(プログラムの異常終了)を引き起こしていました。これは、BadXXX ノードが、プリンタが期待するASTノードの型とは異なる、例えば単なるエラーメッセージを表す string として内部的に扱われる場合に問題となります。

この変更では、print メソッドの switch ステートメントに case string: が追加されました。これにより、print メソッドが string 型の引数を受け取った場合、それを不正なASTの一部として扱い、その文字列データをそのまま出力するように変更されました。具体的には、data = x で文字列を data 変数に格納し、isLit = truetok = token.STRING を設定することで、リテラル文字列として扱われ、プリンタがクラッシュすることなく処理を続行できるようになります。

また、default ケースでのエラーメッセージも改善され、単に型を表示するだけでなく、引数の値も表示されるようになりました。これにより、デバッグ時の情報がより豊富になります。

この修正により、go/parser が生成したASTに BadXXX ノードが含まれており、それが go/printerstring 型として渡された場合でも、プリンタはパニックを起こすことなく、その不正な部分を適切に(エラーメッセージとして)出力し、処理を継続できるようになります。これは、Goのツールチェーン全体の堅牢性を高める上で重要な改善です。

追加されたテストケース TestBadNodes は、この修正が正しく機能することを検証します。このテストは、意図的に構文エラーを含むGoのソースコードを go/parser で解析し、生成されたASTを go/printer に渡します。そして、プリンタがクラッシュせずに、期待されるエラー出力(BadDecl という文字列を含む)を生成することを確認します。

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

src/pkg/go/printer/printer.go

--- a/src/pkg/go/printer/printer.go
+++ b/src/pkg/go/printer/printer.go
@@ -773,8 +773,13 @@ func (p *printer) print(args ...interface{}) {
 			next = p.fset.Position(x) // accurate position of next item
 		}
 		tok = p.lastTok
+		case string:
+			// incorrect AST - print error message
+			data = x
+			isLit = true
+			tok = token.STRING
 		default:
-			fmt.Fprintf(os.Stderr, "print: unsupported argument type %T\n", f)
+			fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", f, f)\n 			panic("go/printer type")
 		}
 		p.lastTok = tok

src/pkg/go/printer/printer_test.go

--- a/src/pkg/go/printer/printer_test.go
+++ b/src/pkg/go/printer/printer_test.go
@@ -204,3 +204,18 @@ func init() {
 		panic("got " + s + ", want " + name)
 	}
 }
+
+// Verify that the printer doesn't crash if the AST contains BadXXX nodes.
+func TestBadNodes(t *testing.T) {
+	const src = "package p\n(\"\n"
+	const res = "package p\nBadDecl\n"
+	f, err := parser.ParseFile(fset, "", src, parser.ParseComments)
+	if err == nil {
+		t.Errorf("expected illegal program")
+	}
+	var buf bytes.Buffer
+	Fprint(&buf, fset, f)
+	if buf.String() != res {
+		t.Errorf("got %q, expected %q", buf.String(), res)
+	}
+}

コアとなるコードの解説

src/pkg/go/printer/printer.go の変更

printer.go の変更は、printer 構造体の print メソッド内で行われています。このメソッドは、ASTノードやその他の要素を処理して出力するための中心的なロジックを含んでいます。

  • case string: の追加: switch x := f.(type) のブロックに case string: が追加されました。これは、print メソッドに渡された引数 fstring 型であった場合の処理を定義しています。

    • // incorrect AST - print error message: コメントが示すように、これは不正なASTの一部として文字列が渡された場合の処理です。
    • data = x: 渡された文字列 xdata 変数に格納します。この data は後続の処理で出力される内容となります。
    • isLit = true: data がリテラル(そのまま出力されるべき値)であることを示します。
    • tok = token.STRING: 処理中のトークンタイプを token.STRING に設定します。これにより、プリンタはこれを文字列として適切に扱います。 この変更により、go/parser が構文エラーを検出した際に、BadXXX ノードの代わりに(またはその内部で)エラーメッセージを表す文字列をASTに挿入した場合でも、go/printer はそれを認識し、クラッシュすることなくその文字列を出力できるようになります。
  • default ケースの変更: default ケースでのエラーメッセージが fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\\n", f, f) に変更されました。

    • 以前は fmt.Fprintf(os.Stderr, "print: unsupported argument type %T\\n", f) で、引数の型のみが表示されていました。
    • 新しいメッセージでは、%v を使用して引数 f の値も表示されるようになりました。これにより、print メソッドが予期しない引数を受け取ってパニックを起こした場合に、デバッグ情報がより詳細になり、問題の原因特定が容易になります。

src/pkg/go/printer/printer_test.go の変更

printer_test.go には、TestBadNodes という新しいテスト関数が追加されました。

  • テストの目的: コメント // Verify that the printer doesn't crash if the AST contains BadXXX nodes. が示すように、このテストは go/printerBadXXX ノードを含むASTを処理する際にクラッシュしないことを検証します。

  • テストシナリオ:

    1. const src = "package p\n(\"\n": 意図的に構文エラーを含むGoのソースコードを定義します。このコードは、パッケージ宣言の後に不正な括弧と引用符の組み合わせを含んでいます。
    2. f, err := parser.ParseFile(fset, "", src, parser.ParseComments): 定義した不正なソースコードを go/parser で解析します。この際、parser.ParseComments オプションを指定しています。
    3. if err == nil { t.Errorf("expected illegal program") }: 不正なプログラムなので、parser.ParseFile はエラーを返すはずです。エラーが返されない場合はテストを失敗させます。
    4. var buf bytes.Buffer: go/printer の出力をキャプチャするための bytes.Buffer を用意します。
    5. Fprint(&buf, fset, f): go/printer.Fprint 関数を使用して、go/parser が生成したAST fbuf に出力します。このASTには、構文エラーによって挿入された BadDecl ノードが含まれていると想定されます。
    6. if buf.String() != res { t.Errorf("got %q, expected %q", buf.String(), res) }: go/printer の出力 (buf.String()) が期待される結果 (const res = "package p\nBadDecl\n") と一致するかを検証します。期待される結果は、パッケージ宣言と、不正な宣言に対応する BadDecl という文字列です。これにより、プリンタがクラッシュせず、かつエラー箇所を適切に表現していることが確認されます。

このテストは、go/printer が不正なAST入力に対しても堅牢であり、予期せぬクラッシュを防ぐことができることを保証します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコードリポジトリ
  • Go言語のコードレビューシステム (Gerrit) の変更リスト
  • 抽象構文木 (AST) に関する一般的なプログラミング言語の概念
  • コンパイラの設計と実装に関する一般的な知識The user's request has been fully addressed. I have provided a detailed technical explanation of the commit in Markdown format, following all the specified sections and including relevant background and technical details. I also included the core code changes and their explanations.
# [インデックス 11107] ファイルの概要

このコミットは、`go/printer` パッケージの堅牢性を向上させるための変更を含んでいます。具体的には、抽象構文木 (AST) に不正なノード(`BadXXX` ノード)が含まれている場合に、`go/printer` がクラッシュしないように修正されています。また、この変更を検証するためのテストケースが追加されています。

変更されたファイルは以下の通りです。

*   `src/pkg/go/printer/printer.go`: `go/printer` パッケージの主要なプリンタロジックが含まれるファイルです。不正なASTノードのハンドリングが改善されました。
*   `src/pkg/go/printer/printer_test.go`: `go/printer` パッケージのテストファイルです。`BadXXX` ノードを含むASTを処理する際のプリンタの挙動を検証する新しいテストが追加されました。

## コミット

go/printer: don't crash if AST contains BadXXX nodes

R=golang-dev, rsc CC=golang-dev https://golang.org/cl/5535048


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

[https://github.com/golang/go/commit/a30b172ca0cd34b8f06952e175ac6bc3d0f1f35b](https://github.com/golang/go/commit/a30b172ca0cd34b8f06952e175ac6bc3d0f1f35b)

## 元コミット内容

`go/printer: don't crash if AST contains BadXXX nodes`

このコミットは、`go/printer` パッケージが、Go言語のソースコードを解析して生成された抽象構文木 (AST) の中に、構文エラーなどによって生成される不正なノード(`BadXXX` ノード)が含まれている場合でも、プログラムが異常終了(クラッシュ)しないように修正することを目的としています。

## 変更の背景

Go言語のコンパイラツールチェーンにおいて、`go/parser` パッケージはGoのソースコードを解析してASTを生成し、`go/printer` パッケージはそのASTを元にGoのソースコードを整形して出力します。通常、`go/parser` は構文エラーを検出した場合、そのエラー箇所に対応するASTノードとして `BadXXX` ノード(例: `BadExpr`, `BadStmt`, `BadDecl` など)を挿入します。これは、パーサーがエラーを検出した後も、可能な限りASTの構築を続行し、後続の処理(例えば、エラーリカバリや部分的なコード解析)を可能にするための一般的な手法です。

しかし、このコミット以前の `go/printer` は、このような `BadXXX` ノードを適切に処理できない場合があり、結果としてプリンタがクラッシュする可能性がありました。これは、不正なGoコードを処理しようとした際に、ツールが予期せず終了してしまうという問題を引き起こします。開発ツールやIDEがGoコードを整形する際に `go/printer` を利用することを考えると、このようなクラッシュはユーザーエクスペリエンスを著しく損なうことになります。

このコミットの背景には、`go/printer` がより堅牢になり、不正な入力に対しても安定して動作することが求められたという経緯があります。特に、Goのツールエコシステムが成熟するにつれて、ユーザーが入力する可能性のあるあらゆる種類のコード(たとえそれが構文的に不正であっても)を適切に扱えるようにすることが重要になってきました。

## 前提知識の解説

### 抽象構文木 (Abstract Syntax Tree, AST)

抽象構文木 (AST) は、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタプリタがソースコードを解析する際に中間表現として生成されます。ASTは、ソースコードの具体的な構文(括弧やセミコロンなど)を抽象化し、プログラムの論理的な構造を表現します。

Go言語においては、`go/ast` パッケージがASTのデータ構造を定義しています。例えば、変数宣言、関数定義、式、文などがそれぞれASTノードとして表現されます。

### `go/parser` パッケージ

`go/parser` パッケージは、Go言語のソースコードを解析し、そのソースコードに対応するASTを生成する役割を担っています。このパッケージは、字句解析(トークン化)と構文解析(パース)を行い、ソースコードがGo言語の文法規則に準拠しているかをチェックします。

構文エラーが検出された場合、`go/parser` はエラーを報告しつつ、ASTの該当箇所に `BadXXX` ノード(例: `ast.BadExpr`, `ast.BadStmt`, `ast.BadDecl`)を挿入することがあります。これにより、パーサーはエラーが発生した後も、可能な限り解析を続行し、部分的なASTを生成することができます。

### `go/printer` パッケージ

`go/printer` パッケージは、`go/parser` によって生成されたASTを受け取り、それをGo言語のソースコードとして整形して出力する役割を担っています。このパッケージは、Goの公式なコードフォーマットルール(`gofmt` コマンドが使用するルール)に従ってコードを整形します。

`go/printer` はASTを走査し、各ノードの種類に応じて適切な文字列を生成し、インデントや改行を調整して読みやすいコードを出力します。

### `BadXXX` ノード

`BadXXX` ノードは、`go/parser` がソースコードの解析中に構文エラーを検出した際に、AST内に挿入される特殊なノードです。例えば、`ast.BadDecl` は不正な宣言、`ast.BadExpr` は不正な式、`ast.BadStmt` は不正な文を表します。これらのノードは、エラーが発生した箇所をAST上でマークし、パーサーがエラーから回復して残りのコードの解析を試みることを可能にします。

`BadXXX` ノードは、それ自体が有効なGoの構文要素ではないため、`go/printer` がこれらのノードを処理する際には特別な注意が必要です。適切に処理されない場合、プリンタが予期しない状態に陥り、クラッシュする可能性があります。

## 技術的詳細

このコミットの技術的な核心は、`go/printer` パッケージ内の `print` メソッドが、ASTノードの型を処理する `switch` ステートメントにおいて、新たに `string` 型のケースを追加した点にあります。

従来の `go/printer` の実装では、`print` メソッドがASTノードを処理する際に、予期しない型(特に `BadXXX` ノードが内部的にどのように表現されるかによる)の引数を受け取ると、`default` ケースにフォールバックし、そこでパニック(プログラムの異常終了)を引き起こしていました。これは、`BadXXX` ノードが、プリンタが期待するASTノードの型とは異なる、例えば単なるエラーメッセージを表す `string` として内部的に扱われる場合に問題となります。

この変更では、`print` メソッドの `switch` ステートメントに `case string:` が追加されました。これにより、`print` メソッドが `string` 型の引数を受け取った場合、それを不正なASTの一部として扱い、その文字列データをそのまま出力するように変更されました。具体的には、`data = x` で文字列を `data` 変数に格納し、`isLit = true` と `tok = token.STRING` を設定することで、リテラル文字列として扱われ、プリンタがクラッシュすることなく処理を続行できるようになります。

また、`default` ケースでのエラーメッセージも改善され、単に型を表示するだけでなく、引数の値も表示されるようになりました。これにより、デバッグ時の情報がより豊富になります。

この修正により、`go/parser` が生成したASTに `BadXXX` ノードが含まれており、それが `go/printer` に `string` 型として渡された場合でも、プリンタはパニックを起こすことなく、その不正な部分を適切に(エラーメッセージとして)出力し、処理を継続できるようになります。これは、Goのツールチェーン全体の堅牢性を高める上で重要な改善です。

追加されたテストケース `TestBadNodes` は、この修正が正しく機能することを検証します。このテストは、意図的に構文エラーを含むGoのソースコードを `go/parser` で解析し、生成されたASTを `go/printer` に渡します。そして、プリンタがクラッシュせずに、期待されるエラー出力(`BadDecl` という文字列を含む)を生成することを確認します。

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

### `src/pkg/go/printer/printer.go`

```diff
--- a/src/pkg/go/printer/printer.go
+++ b/src/pkg/go/printer/printer.go
@@ -773,8 +773,13 @@ func (p *printer) print(args ...interface{}) {
 			next = p.fset.Position(x) // accurate position of next item
 		}
 		tok = p.lastTok
+		case string:
+			// incorrect AST - print error message
+			data = x
+			isLit = true
+			tok = token.STRING
 		default:
-			fmt.Fprintf(os.Stderr, "print: unsupported argument type %T\n", f)
+			fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", f, f)\n 			panic("go/printer type")
 		}
 		p.lastTok = tok

src/pkg/go/printer/printer_test.go

--- a/src/pkg/go/printer/printer_test.go
+++ b/src/pkg/go/printer/printer_test.go
@@ -204,3 +204,18 @@ func init() {
 		panic("got " + s + ", want " + name)
 	}
 }
+
+// Verify that the printer doesn't crash if the AST contains BadXXX nodes.
+func TestBadNodes(t *testing.T) {
+	const src = "package p\n(\"\n"
+	const res = "package p\nBadDecl\n"
+	f, err := parser.ParseFile(fset, "", src, parser.ParseComments)
+	if err == nil {
+		t.Errorf("expected illegal program")
+	}
+	var buf bytes.Buffer
+	Fprint(&buf, fset, f)
+	if buf.String() != res {
+		t.Errorf("got %q, expected %q", buf.String(), res)
+	}
+}

コアとなるコードの解説

src/pkg/go/printer/printer.go の変更

printer.go の変更は、printer 構造体の print メソッド内で行われています。このメソッドは、ASTノードやその他の要素を処理して出力するための中心的なロジックを含んでいます。

  • case string: の追加: switch x := f.(type) のブロックに case string: が追加されました。これは、print メソッドに渡された引数 fstring 型であった場合の処理を定義しています。

    • // incorrect AST - print error message: コメントが示すように、これは不正なASTの一部として文字列が渡された場合の処理です。
    • data = x: 渡された文字列 xdata 変数に格納します。この data は後続の処理で出力される内容となります。
    • isLit = true: data がリテラル(そのまま出力されるべき値)であることを示します。
    • tok = token.STRING: 処理中のトークンタイプを token.STRING に設定します。これにより、プリンタはこれを文字列として適切に扱います。 この変更により、go/parser が構文エラーを検出した際に、BadXXX ノードの代わりに(またはその内部で)エラーメッセージを表す文字列をASTに挿入した場合でも、go/printer はそれを認識し、クラッシュすることなくその文字列を出力できるようになります。
  • default ケースの変更: default ケースでのエラーメッセージが fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\\n", f, f) に変更されました。

    • 以前は fmt.Fprintf(os.Stderr, "print: unsupported argument type %T\\n", f) で、引数の型のみが表示されていました。
    • 新しいメッセージでは、%v を使用して引数 f の値も表示されるようになりました。これにより、print メソッドが予期しない引数を受け取ってパニックを起こした場合に、デバッグ情報がより詳細になり、問題の原因特定が容易になります。

src/pkg/go/printer/printer_test.go の変更

printer_test.go には、TestBadNodes という新しいテスト関数が追加されました。

  • テストの目的: コメント // Verify that the printer doesn't crash if the AST contains BadXXX nodes. が示すように、このテストは go/printerBadXXX ノードを含むASTを処理する際にクラッシュしないことを検証します。

  • テストシナリオ:

    1. const src = "package p\n(\"\n": 意図的に構文エラーを含むGoのソースコードを定義します。このコードは、パッケージ宣言の後に不正な括弧と引用符の組み合わせを含んでいます。
    2. f, err := parser.ParseFile(fset, "", src, parser.ParseComments): 定義した不正なソースコードを go/parser で解析します。この際、parser.ParseComments オプションを指定しています。
    3. if err == nil { t.Errorf("expected illegal program") }: 不正なプログラムなので、parser.ParseFile はエラーを返すはずです。エラーが返されない場合はテストを失敗させます。
    4. var buf bytes.Buffer: go/printer の出力をキャプチャするための bytes.Buffer を用意します。
    5. Fprint(&buf, fset, f): go/printer.Fprint 関数を使用して、go/parser が生成したAST fbuf に出力します。このASTには、構文エラーによって挿入された BadDecl ノードが含まれていると想定されます。
    6. if buf.String() != res { t.Errorf("got %q, expected %q", buf.String(), res) }: go/printer の出力 (buf.String()) が期待される結果 (const res = "package p\nBadDecl\n") と一致するかを検証します。期待される結果は、パッケージ宣言と、不正な宣言に対応する BadDecl という文字列です。これにより、プリンタがクラッシュせず、かつエラー箇所を適切に表現していることが確認されます。

このテストは、go/printer が不正なAST入力に対しても堅牢であり、予期せぬクラッシュを防ぐことができることを保証します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコードリポジトリ
  • Go言語のコードレビューシステム (Gerrit) の変更リスト
  • 抽象構文木 (AST) に関する一般的なプログラミング言語の概念
  • コンパイラの設計と実装に関する一般的な知識