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

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

このコミットは、Go言語の標準ライブラリ os パッケージ内の error_plan9.go ファイルに対する変更です。このファイルは、Plan 9オペレーティングシステムにおけるファイルシステム関連のエラー(ファイルが存在するか、存在しないか、パーミッションがあるかなど)を判定するためのヘルパー関数を提供しています。具体的には、isExistisNotExistisPermission といった関数が含まれており、これらは 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 パッケージが提供する isExistisNotExistisPermission といったエラー判定関数が、入力として nil (エラーがない状態) を受け取った際にパニック(プログラムの異常終了)を引き起こす可能性があったため、これを回避することが目的です。

Go言語では、関数がエラーを返さない場合、慣習的に nil を返します。しかし、これらの is* 関数は、エラーオブジェクトが nil でないことを前提として err.(*PathError) のような型アサーションを行っていました。もし errnil の場合、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)
}

PathErrorerror インターフェースを実装しているため、通常の 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言語のインターフェースと型アサーションの挙動、およびエラーハンドリングのベストプラクティスに関連しています。

変更前の isExistisNotExistisPermission 関数は、以下のようなロジックを持っていました(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 を返します。この nilisExist に渡されると、上記の型アサーションが実行され、パニックを引き起こす可能性がありました。

このコミットは、この問題を解決するために、各関数の冒頭にシンプルな nil チェックを追加しました。

func isExist(err error) bool {
    if err == nil { // 追加されたnilチェック
        return false
    }
    if pe, ok := err.(*PathError); ok {
        err = pe.Err
    }
    // ... 既存のエラー判定ロジック ...
}

この変更により、errnil の場合は即座に false が返され、その後の型アサーションが実行されることはありません。これにより、nil エラーが渡された場合のパニックが回避され、関数の堅牢性が向上しました。isExistisNotExistisPermission のいずれの関数も、エラーが存在しない(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 ファイル内の isExistisNotExistisPermission の各関数に、それぞれ以下の3行が追加されています。

	if err == nil {
		return false
	}

このコードブロックは、関数に渡された err 引数が nil であるかどうかをチェックします。

  • もし errnil であれば、それはエラーが発生していないことを意味します。
  • したがって、そのエラーが「存在するエラー」「存在しないエラー」「パーミッションエラー」のいずれかであるはずがないため、関数は直ちに false を返します。

このシンプルな nil チェックの追加により、errnil の場合にその後の if pe, ok := err.(*PathError); ok という型アサーションが実行されることを防ぎます。これにより、nil インターフェース値に対する型アサーションによるランタイムパニックが効果的に回避され、これらの関数の堅牢性が向上しました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に src/pkg/os/error_plan9.go の変更履歴)
  • Plan 9 from Bell Labs (一般的な情報)