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

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

このコミットは、Go言語の標準ライブラリであるgo/astパッケージ内のast.Walk関数における潜在的なクラッシュバグを修正するものです。具体的には、FuncTypeノードのParamsフィールドがnilである場合にast.Walkがパニックを起こす問題を、Walk関数内でParamsフィールドがnilでないことを確認するチェックを追加することで解決しています。

コミット

commit a42e8a80864281807384a6e5a45bebf3327a53fe
Author: Robert Griesemer <gri@golang.org>
Date:   Fri Nov 16 11:53:26 2012 -0800

    go/ast: FuncType.Params may be nil (per AST documentation)
    
    ast.Walk needs to check for it or it will crash.
    
    R=r
    CC=golang-dev
    https://golang.org/cl/6852062

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

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

元コミット内容

go/ast: FuncType.Paramsは(ASTドキュメントに従い)nilである可能性がある。 ast.Walkはそれをチェックする必要がある。さもなければクラッシュするだろう。

変更の背景

Go言語のコンパイラやツールは、ソースコードを抽象構文木(AST: Abstract Syntax Tree)として内部的に表現します。go/astパッケージは、このASTを操作するためのデータ構造と関数を提供します。ast.Walk関数は、ASTを再帰的に走査するためのユーティリティであり、ASTノードを訪問する際に特定の処理を実行するために使用されます。

このコミットが行われる前、go/astパッケージのドキュメントでは、関数型(FuncType)のパラメータリスト(Paramsフィールド)がnilである可能性があると明記されていました。これは、例えば引数を全く取らない関数(func())の場合に発生し得ます。しかし、ast.Walk関数内のFuncTypeを処理するロジックでは、このParamsフィールドがnilである可能性を考慮していませんでした。

その結果、ast.WalkParamsフィールドがnilであるFuncTypeノードに遭遇すると、nilポインタのデリファレンスが発生し、プログラムがクラッシュするというバグが存在していました。このコミットは、この潜在的なクラッシュを防ぐために、ast.Walk関数にnilチェックを追加することを目的としています。

前提知識の解説

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

ASTは、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやリンター、コード分析ツールなどは、ソースコードを直接扱うのではなく、まずASTに変換してから処理を行います。各ノードは、変数宣言、関数呼び出し、演算子などの言語構造を表します。

go/astパッケージ

Go言語の標準ライブラリの一部であり、GoのソースコードのAST表現を提供します。このパッケージには、ASTノードを表す様々な型(例: ast.File, ast.FuncDecl, ast.Exprなど)と、ASTを操作するためのユーティリティ関数が含まれています。

ast.Walk関数

ast.Walkは、go/astパッケージで提供される重要な関数の一つで、ASTを深さ優先で走査(トラバース)するために使用されます。この関数はast.Visitorインターフェースを実装したオブジェクトとASTノードを受け取り、ノードとその子ノードを再帰的に訪問します。VisitorインターフェースのVisitメソッドは、各ノードが訪問されるたびに呼び出され、開発者はこのメソッド内で特定のノードタイプに応じた処理を記述できます。

ast.FuncType構造体

ast.FuncTypeは、GoのASTにおいて関数型を表す構造体です。例えば、func(int, string) (bool, error)のような関数シグネチャを表現します。この構造体には、以下の主要なフィールドが含まれます。

  • Func: token.Pos型で、funcキーワードの位置を示します。
  • Params: *FieldList型で、関数の引数(パラメータ)のリストを表します。
  • Results: *FieldList型で、関数の戻り値のリストを表します。

ここで重要なのは、ParamsResults*FieldList型であることです。FieldListは、パラメータや戻り値のリストを表現するための構造体ですが、関数が引数を全く取らない場合や戻り値を全く持たない場合、これらのフィールドはnilになることがあります。

ast.FieldList構造体

ast.FieldListは、関数パラメータや構造体フィールド、インターフェースメソッドの引数など、名前と型を持つ要素のリストを表現するために使用される構造体です。Listというフィールドを持ち、これは*ast.Fieldのスライスです。

技術的詳細

このコミットの核心は、ast.Walk関数がFuncTypeノードを処理する際のロバスト性の向上にあります。

ast.Walk関数は、内部でswitch n := node.(type)という型アサーションを使用して、現在走査しているASTノードの具体的な型を判別し、それぞれの型に応じた処理を実行します。*FuncTypeの場合、以前のコードではWalk(v, n.Params)と直接n.Paramsに対して再帰的にWalkを呼び出していました。

問題は、Goの関数定義において、パラメータが全くない関数(例: func() {})の場合、ast.FuncType構造体のParamsフィールドがnilになるという仕様でした。ast.Walknilノードを安全に処理するように設計されていますが、n.Paramsnilであるにもかかわらず、そのnil値に対してWalkを呼び出す前にnilチェックが行われていなかったため、Walk関数がnilポインタをデリファレンスしようとしてパニック(ランタイムエラー)を引き起こしていました。

このコミットでは、この問題を解決するために、*FuncTypeケースの処理にシンプルなnilチェックを追加しています。具体的には、Walk(v, n.Params)の呼び出しの前にif n.Params != nilという条件を追加し、Paramsフィールドが実際にnilでない場合にのみ、その子ノード(パラメータリスト)の走査を行うように変更しました。同様に、Resultsフィールドについても、以前からnilチェックが存在していましたが、このコミットの変更はParamsに焦点を当てています。

この修正により、ast.Walkは、パラメータを持たない関数型を表現するASTノードに遭遇しても、安全に処理を続行できるようになり、GoコンパイラやASTを扱うツールチェーン全体の安定性が向上しました。これは、Go言語のASTの仕様(FuncType.Paramsnilになり得るという点)と、AST走査ユーティリティの堅牢性との間の不整合を解消する、重要なバグ修正と言えます。

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

--- a/src/pkg/go/ast/walk.go
+++ b/src/pkg/go/ast/walk.go
@@ -158,7 +158,9 @@ func Walk(v Visitor, node Node) {
 		Walk(v, n.Fields)
 
 	case *FuncType:
-		Walk(v, n.Params)
+		if n.Params != nil {
+			Walk(v, n.Params)
+		}
 		if n.Results != nil {
 			Walk(v, n.Results)
 		}

コアとなるコードの解説

変更はsrc/pkg/go/ast/walk.goファイル内のWalk関数のswitch文の*FuncTypeケースにあります。

元のコード:

	case *FuncType:
		Walk(v, n.Params)
		if n.Results != nil {
			Walk(v, n.Results)
		}

修正後のコード:

	case *FuncType:
		if n.Params != nil { // 追加されたnilチェック
			Walk(v, n.Params)
		}
		if n.Results != nil {
			Walk(v, n.Results)
		}

この変更は非常にシンプルですが、その影響は大きいです。

  • case *FuncType:: 現在走査しているノードがFuncType型であることを示します。
  • if n.Params != nil { ... }: ここが追加されたnilチェックです。n.Params*ast.FieldList型であり、関数がパラメータを持たない場合、このポインタはnilになります。
  • Walk(v, n.Params): n.Paramsnilでない場合にのみ、パラメータリストの子ノードに対して再帰的にWalk関数が呼び出されます。これにより、nilポインタのデリファレンスが回避されます。
  • if n.Results != nil { ... }: 戻り値のリストn.Resultsに対するnilチェックは以前から存在しており、このコミットでは変更されていません。これは、戻り値がない関数(例: func() {})の場合にn.Resultsnilになる可能性があるため、同様の理由で必要です。

この修正により、ast.Walk関数は、Go言語のASTの仕様に完全に準拠し、より堅牢になりました。

関連リンク

参考にした情報源リンク

  • Go言語のソースコードとドキュメント(go/astパッケージ)
  • Go言語の抽象構文木に関する一般的な知識
  • コミットメッセージと差分情報