[インデックス 12901] ファイルの概要
このコミットは、Go言語の標準ライブラリ os パッケージ内の error_plan9.go ファイルに対する変更です。このファイルは、Plan 9オペレーティングシステムにおけるファイルシステム関連のエラー(ファイルが存在するか、存在しないか、パーミッションがあるかなど)を判定するためのヘルパー関数を提供しています。具体的には、isExist、isNotExist、isPermission といった関数が含まれており、これらは os.PathError 型のエラーを適切に処理し、基となるエラーの種類を判別します。
コミット
- コミットハッシュ:
74607d18c5355aed38b3354c64b307af96780e68 - 作者: Anthony Martin ality@pbrane.org
- コミット日時: Mon Apr 16 17:36:02 2012 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/74607d18c5355aed38b3354c64b307af96780e68
元コミット内容
os: avoid panic when testing errors on Plan 9
R=golang-dev, bradfitz, akumar
CC=golang-dev
https://golang.org/cl/6017043
変更の背景
この変更の背景には、Go言語の os パッケージにおけるエラーハンドリングの堅牢性向上が挙げられます。特に、Plan 9環境下で os パッケージが提供する isExist、isNotExist、isPermission といったエラー判定関数が、入力として nil (エラーがない状態) を受け取った際にパニック(プログラムの異常終了)を引き起こす可能性があったため、これを回避することが目的です。
Go言語では、関数がエラーを返さない場合、慣習的に nil を返します。しかし、これらの is* 関数は、エラーオブジェクトが nil でないことを前提として err.(*PathError) のような型アサーションを行っていました。もし err が nil の場合、nil インターフェース値に対する型アサーションはランタイムパニック interface conversion: interface {} is nil, not *os.PathError を引き起こします。
このコミットは、このような潜在的なパニックを防ぎ、nil エラーが渡された場合でも安全に false を返すようにすることで、これらの関数の堅牢性と予測可能性を高めています。
前提知識の解説
Go言語のエラーハンドリング
Go言語では、エラーは組み込みの error インターフェースによって表現されます。関数は通常、最後の戻り値として error 型の値を返します。エラーが発生しなかった場合は nil を返し、エラーが発生した場合は nil ではない error 値を返します。
func doSomething() (result string, err error) {
// ... 処理 ...
if someCondition {
return "", errors.New("something went wrong") // エラーを返す
}
return "success", nil // エラーがない場合はnilを返す
}
os.PathError
os パッケージでは、ファイルシステム操作中に発生するエラーをラップするために PathError という構造体が定義されています。これは、エラーが発生した操作(Op)、関連するパス(Path)、そして元のエラー(Err)という情報を含みます。
type PathError struct {
Op string // 操作 (e.g., "open", "unlink")
Path string // パス (e.g., "/tmp/foo")
Err error // 元のエラー (e.g., syscall.ENOENT)
}
PathError は error インターフェースを実装しているため、通常の error 型として扱うことができます。
os パッケージの isExist, isNotExist, isPermission 関数
これらの関数は、ファイルシステム操作で返された error が、それぞれ「ファイルが存在するエラー」、「ファイルが存在しないエラー」、「パーミッションエラー」であるかどうかを判定するために使用されます。これらは内部的に PathError をアンラップし、その中の Err フィールドを調べて具体的なエラーコード(例: syscall.ENOENT for "no such entry")と比較することで判定を行います。
Plan 9
Plan 9 from Bell Labsは、ベル研究所で開発された分散オペレーティングシステムです。Go言語の開発者の一部はPlan 9の設計思想に影響を受けており、Goの標準ライブラリにはPlan 9に特化したコードが含まれることがあります。error_plan9.go は、GoがPlan 9環境で動作する際のエラー処理ロジックを定義しています。
技術的詳細
このコミットの技術的詳細は、Go言語のインターフェースと型アサーションの挙動、およびエラーハンドリングのベストプラクティスに関連しています。
変更前の isExist、isNotExist、isPermission 関数は、以下のようなロジックを持っていました(isExist を例に取ります)。
func isExist(err error) bool {
if pe, ok := err.(*PathError); ok { // ここで型アサーションが行われる
err = pe.Err
}
// ... err を使ったエラー判定ロジック ...
}
ここで問題となるのは if pe, ok := err.(*PathError); ok の部分です。Go言語では、インターフェース変数が nil である場合、そのインターフェース変数は「型も値も nil」の状態です。この状態で err.(*PathError) のような型アサーションを行うと、ランタイムパニックが発生します。
例えば、os.Stat のような関数がエラーなく成功した場合、nil を返します。この nil が isExist に渡されると、上記の型アサーションが実行され、パニックを引き起こす可能性がありました。
このコミットは、この問題を解決するために、各関数の冒頭にシンプルな nil チェックを追加しました。
func isExist(err error) bool {
if err == nil { // 追加されたnilチェック
return false
}
if pe, ok := err.(*PathError); ok {
err = pe.Err
}
// ... 既存のエラー判定ロジック ...
}
この変更により、err が nil の場合は即座に false が返され、その後の型アサーションが実行されることはありません。これにより、nil エラーが渡された場合のパニックが回避され、関数の堅牢性が向上しました。isExist、isNotExist、isPermission のいずれの関数も、エラーが存在しない(nil である)場合は、当然ながら「存在するエラー」「存在しないエラー」「パーミッションエラー」のいずれでもないため、false を返すのが論理的にも正しい挙動です。
コアとなるコードの変更箇所
diff --git a/src/pkg/os/error_plan9.go b/src/pkg/os/error_plan9.go
index 3c9dfb0b15..f083a2d1de 100644
--- a/src/pkg/os/error_plan9.go
+++ b/src/pkg/os/error_plan9.go
@@ -5,6 +5,9 @@
package os
func isExist(err error) bool {
+ if err == nil {
+ return false
+ }
if pe, ok := err.(*PathError); ok {
err = pe.Err
}
@@ -12,6 +15,9 @@ func isExist(err error) bool {
}
func isNotExist(err error) bool {
+ if err == nil {
+ return false
+ }
if pe, ok := err.(*PathError); ok {
err = pe.Err
}
@@ -19,6 +25,9 @@ func isNotExist(err error) bool {
}
func isPermission(err error) bool {
+ if err == nil {
+ return false
+ }
if pe, ok := err.(*PathError); ok {
err = pe.Err
}
コアとなるコードの解説
上記の差分が示すように、src/pkg/os/error_plan9.go ファイル内の isExist、isNotExist、isPermission の各関数に、それぞれ以下の3行が追加されています。
if err == nil {
return false
}
このコードブロックは、関数に渡された err 引数が nil であるかどうかをチェックします。
- もし
errがnilであれば、それはエラーが発生していないことを意味します。 - したがって、そのエラーが「存在するエラー」「存在しないエラー」「パーミッションエラー」のいずれかであるはずがないため、関数は直ちに
falseを返します。
このシンプルな nil チェックの追加により、err が nil の場合にその後の if pe, ok := err.(*PathError); ok という型アサーションが実行されることを防ぎます。これにより、nil インターフェース値に対する型アサーションによるランタイムパニックが効果的に回避され、これらの関数の堅牢性が向上しました。
関連リンク
- Go CL 6017043: https://golang.org/cl/6017043
- Go言語の
osパッケージドキュメント: https://pkg.go.dev/os - Go言語のエラーハンドリングに関する公式ブログ記事 (A Tour of Go - Errors): https://go.dev/tour/basics/16
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード (特に
src/pkg/os/error_plan9.goの変更履歴) - Plan 9 from Bell Labs (一般的な情報)