[インデックス 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 (一般的な情報)