[インデックス 12138] ファイルの概要
このコミットで変更されたファイルは src/cmd/godoc/mapping.go
です。このファイルは、Go言語のドキュメンテーションツールである godoc
の内部で、ファイルパスのマッピングと変換ロジックを扱う役割を担っています。具体的には、絶対パスと相対パスの変換や、パスの構成要素を分割する機能を提供しています。
コミット
commit d74680ea1cf3f1f52098eb293bd7198750f2193f
Author: Robert Griesemer <gri@golang.org>
Date: Tue Feb 21 22:50:00 2012 -0800
godoc: fix potential index out-of-bounds error
R=golang-dev, bradfitz, dsymonds
CC=golang-dev
https://golang.org/cl/5683072
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d74680ea1cf3f1f52098eb293bd7198750f2193f
元コミット内容
godoc: fix potential index out-of-bounds error
このコミットは、godoc
ツールにおける潜在的なインデックス範囲外エラーを修正することを目的としています。
変更の背景
このコミットの主な背景は、godoc
ツール内のパス変換ロジック、特に ToRelative
関数に存在する潜在的なバグを修正することでした。元のコードでは、fpath
(ファイルパス) が e.path
(プレフィックスパス) と完全に一致する場合、つまり fpath
の長さが e.path
の長さと同じである場合に、fpath[len(e.path)]
というアクセスがインデックス範囲外エラーを引き起こす可能性がありました。
Go言語の文字列はバイトのシーケンスであり、fpath[len(e.path)]
は fpath
の len(e.path)
番目のインデックスにあるバイトにアクセスしようとします。もし fpath
の長さが len(e.path)
と同じであれば、有効なインデックスは 0
から len(fpath) - 1
までとなるため、len(e.path)
は範囲外となります。
この問題は、strings.HasPrefix(fpath, e.path) && fpath[len(e.path)] == filepath.Separator
という条件式において顕在化します。この条件は、「fpath
が e.path
で始まり、かつ e.path
の直後の文字がパス区切り文字である」ことを意図しています。しかし、fpath
が e.path
と完全に一致する場合、fpath[len(e.path)]
の評価時にエラーが発生します。
このコミットは、この脆弱性を修正し、より堅牢なパス処理を実現するために行われました。
前提知識の解説
godoc
ツール:godoc
はGo言語の公式ドキュメンテーションツールです。Goのソースコードからコメントや宣言を解析し、HTML形式でドキュメントを生成したり、コマンドラインからパッケージのドキュメントを表示したりする機能を提供します。Go言語の標準ライブラリのドキュメントもgodoc
によって生成されています。filepath.Separator
: Go言語のpath/filepath
パッケージで定義されている定数で、現在のオペレーティングシステムにおけるパス区切り文字を表します。Windowsでは\
、Unix系システムでは/
となります。これにより、OSに依存しないパス操作が可能になります。strings.Index
: Go言語のstrings
パッケージで提供される関数で、文字列s
内で部分文字列substr
が最初に出現するインデックスを返します。見つからない場合は-1
を返します。strings.HasPrefix
: Go言語のstrings
パッケージで提供される関数で、文字列s
が指定されたプレフィックスprefix
で始まるかどうかを真偽値で返します。- インデックス範囲外エラー (Index out-of-bounds error): プログラミングにおいて、配列や文字列などのシーケンスデータ構造にアクセスする際に、存在しないインデックスを指定した場合に発生するエラーです。これはプログラムのクラッシュや予期せぬ動作につながるため、避けるべき重要なバグの一種です。Go言語では、このようなアクセスはランタイムパニックを引き起こします。
技術的詳細
このコミットの技術的な核心は、src/cmd/godoc/mapping.go
内の ToRelative
関数における条件式の変更です。
元のコードでは、以下の条件式が使用されていました。
if strings.HasPrefix(fpath, e.path) && fpath[len(e.path)] == filepath.Separator {
この条件式は、fpath
が e.path
で始まることを確認した後、e.path
の長さのインデックスにある文字がパス区切り文字であるかをチェックしています。しかし、もし fpath
が e.path
と完全に同じ文字列であった場合、len(e.path)
は fpath
の有効なインデックス範囲外となり、fpath[len(e.path)]
の評価時にパニック(インデックス範囲外エラー)が発生する可能性がありました。
このコミットでは、この問題を解決するために条件式を以下のように変更しました。
if strings.HasPrefix(fpath, e.path+sep) {
ここで sep
は const sep = string(filepath.Separator)
として定義された定数です。この変更により、fpath
が e.path
の後に直接パス区切り文字が続く文字列であるかを strings.HasPrefix
関数一つで効率的かつ安全にチェックできるようになりました。e.path+sep
という文字列をプレフィックスとしてチェックすることで、fpath
が e.path
よりも長く、かつその直後に区切り文字が続く場合にのみ条件が真となります。これにより、インデックス範囲外エラーの発生を防ぎます。
また、このコミットでは、filepath.Separator
を string
にキャストする処理が複数回行われていた箇所を、const sep = string(filepath.Separator)
という定数として定義し、それを再利用するように変更しています。これはコードの可読性を向上させるとともに、わずかながらパフォーマンスの最適化にも寄与します。
splitFirst
関数においても、同様に string(filepath.Separator)
の代わりに sep
定数を使用するように変更されています。これにより、コードの一貫性が保たれています。
コアとなるコードの変更箇所
--- a/src/cmd/godoc/mapping.go
+++ b/src/cmd/godoc/mapping.go
@@ -139,14 +139,16 @@ func (m *Mapping) Fprint(w io.Writer) {
}
}
+const sep = string(filepath.Separator)
+
// splitFirst splits a path at the first path separator and returns
// the path's head (the top-most directory specified by the path) and
// its tail (the rest of the path). If there is no path separator,
-// splitFirst returns path as head, and the the empty string as tail.
+// splitFirst returns path as head, and the empty string as tail.
// Specifically, splitFirst("foo") == splitFirst("foo/").
//
func splitFirst(path string) (head, tail string) {
- if i := strings.Index(path, string(filepath.Separator)); i > 0 {
+ if i := strings.Index(path, sep); i > 0 {
// 0 < i < len(path)
return path[0:i], path[i+1:]
}
@@ -179,7 +181,7 @@ func (m *Mapping) ToAbsolute(spath string) string {
func (m *Mapping) ToRelative(fpath string) string {
for _, e := range m.list {
// if fpath has prefix e.path, the next character must be a separator (was issue 3096)
- if strings.HasPrefix(fpath, e.path) && fpath[len(e.path)] == filepath.Separator {
+ if strings.HasPrefix(fpath, e.path+sep) {
spath := filepath.ToSlash(fpath)
// /absolute/prefix/foo -> prefix/foo
return path.Join(e.prefix, spath[len(e.path):]) // Join will remove a trailing '/'
コアとなるコードの解説
-
const sep = string(filepath.Separator)
の追加:filepath.Separator
はrune
型(文字)ですが、strings.Index
や文字列結合で使用するためにはstring
型に変換する必要があります。- この定数を導入することで、コード全体でパス区切り文字を文字列として扱う際の一貫性が保たれ、冗長な型変換が削減されます。
-
splitFirst
関数の変更:strings.Index(path, string(filepath.Separator))
がstrings.Index(path, sep)
に変更されました。これは、上記で定義されたsep
定数を使用するように更新されたものです。機能的な変更はありませんが、コードのクリーンアップと一貫性の向上に貢献しています。- コメント
// splitFirst returns path as head, and the the empty string as tail.
が// splitFirst returns path as head, and the empty string as tail.
に修正され、タイポが直されています。 - コメント
// Specifically, splitFirst("foo") == splitFirst("foo/").
が追加され、関数の挙動がより明確に説明されています。
-
ToRelative
関数の変更:- これがこのコミットの最も重要な変更点です。
- 元の条件式
if strings.HasPrefix(fpath, e.path) && fpath[len(e.path)] == filepath.Separator
が、if strings.HasPrefix(fpath, e.path+sep)
に変更されました。 - 前述の通り、この変更により、
fpath
がe.path
と完全に一致する場合に発生する可能性があったインデックス範囲外エラーが解消されます。 e.path+sep
という文字列をプレフィックスとしてチェックすることで、fpath
がe.path
の後にパス区切り文字が続く形式であるかを安全に確認できます。これにより、fpath
の長さがe.path
と同じであるケースを適切に除外できます。- コメント
(was issue 3096)
は、この変更がGoのIssue 3096に関連していることを示唆しています。
関連リンク
- Go Change List (CL):
https://golang.org/cl/5683072
Goプロジェクトでは、GitHubのプルリクエストに相当する変更提案を「Change List (CL)」と呼びます。これはGerritというコードレビューシステム上で管理されており、このリンクはそのCLへの直接リンクです。CLには、変更内容の詳細、レビューコメント、テスト結果などが含まれています。
参考にした情報源リンク
- Go言語公式ドキュメント:
path/filepath
パッケージ: https://pkg.go.dev/path/filepath - Go言語公式ドキュメント:
strings
パッケージ: https://pkg.go.dev/strings - Go言語公式ドキュメント:
godoc
コマンド: https://pkg.go.dev/cmd/godoc - Go Issue 3096 (関連する可能性のあるIssue): https://github.com/golang/go/issues/3096 (このコミットメッセージに記載されているIssue番号から推測)
- Gerrit Code Review: https://go-review.googlesource.com/ (Goプロジェクトが使用しているコードレビューシステム)
[インデックス 12138] ファイルの概要
このコミットで変更されたファイルは src/cmd/godoc/mapping.go
です。このファイルは、Go言語のドキュメンテーションツールである godoc
の内部で、ファイルパスのマッピングと変換ロジックを扱う役割を担っています。具体的には、絶対パスと相対パスの変換や、パスの構成要素を分割する機能を提供しています。
コミット
commit d74680ea1cf3f1f52098eb293bd7198750f2193f
Author: Robert Griesemer <gri@golang.org>
Date: Tue Feb 21 22:50:00 2012 -0800
godoc: fix potential index out-of-bounds error
R=golang-dev, bradfitz, dsymonds
CC=golang-dev
https://golang.org/cl/5683072
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d74680ea1cf3f1f52098eb293bd7198750f2193f
元コミット内容
godoc: fix potential index out-of-bounds error
このコミットは、godoc
ツールにおける潜在的なインデックス範囲外エラーを修正することを目的としています。
変更の背景
このコミットの主な背景は、godoc
ツール内のパス変換ロジック、特に ToRelative
関数に存在する潜在的なバグを修正することでした。元のコードでは、fpath
(ファイルパス) が e.path
(プレフィックスパス) と完全に一致する場合、つまり fpath
の長さが e.path
の長さと同じである場合に、fpath[len(e.path)]
というアクセスがインデックス範囲外エラーを引き起こす可能性がありました。
Go言語の文字列はバイトのシーケンスであり、fpath[len(e.path)]
は fpath
の len(e.path)
番目のインデックスにあるバイトにアクセスしようとします。もし fpath
の長さが len(e.path)
と同じであれば、有効なインデックスは 0
から len(fpath) - 1
までとなるため、len(e.path)
は範囲外となります。
この問題は、strings.HasPrefix(fpath, e.path) && fpath[len(e.path)] == filepath.Separator
という条件式において顕在化します。この条件は、「fpath
が e.path
で始まり、かつ e.path
の直後の文字がパス区切り文字である」ことを意図しています。しかし、fpath
が e.path
と完全に一致する場合、fpath[len(e.path)]
の評価時にエラーが発生します。
このコミットは、この脆弱性を修正し、より堅牢なパス処理を実現するために行われました。
前提知識の解説
godoc
ツール:godoc
はGo言語の公式ドキュメンテーションツールです。Goのソースコードからコメントや宣言を解析し、HTML形式でドキュメントを生成したり、コマンドラインからパッケージのドキュメントを表示したりする機能を提供します。Go言語の標準ライブラリのドキュメントもgodoc
によって生成されています。filepath.Separator
: Go言語のpath/filepath
パッケージで定義されている定数で、現在のオペレーティングシステムにおけるパス区切り文字を表します。Windowsでは\
、Unix系システムでは/
となります。これにより、OSに依存しないパス操作が可能になります。strings.Index
: Go言語のstrings
パッケージで提供される関数で、文字列s
内で部分文字列substr
が最初に出現するインデックスを返します。見つからない場合は-1
を返します。strings.HasPrefix
: Go言語のstrings
パッケージで提供される関数で、文字列s
が指定されたプレフィックスprefix
で始まるかどうかを真偽値で返します。- インデックス範囲外エラー (Index out-of-bounds error): プログラミングにおいて、配列や文字列などのシーケンスデータ構造にアクセスする際に、存在しないインデックスを指定した場合に発生するエラーです。これはプログラムのクラッシュや予期せぬ動作につながるため、避けるべき重要なバグの一種です。Go言語では、このようなアクセスはランタイムパニックを引き起こします。
技術的詳細
このコミットの技術的な核心は、src/cmd/godoc/mapping.go
内の ToRelative
関数における条件式の変更です。
元のコードでは、以下の条件式が使用されていました。
if strings.HasPrefix(fpath, e.path) && fpath[len(e.path)] == filepath.Separator {
この条件式は、fpath
が e.path
で始まることを確認した後、e.path
の長さのインデックスにある文字がパス区切り文字であるかをチェックしています。しかし、もし fpath
が e.path
と完全に同じ文字列であった場合、len(e.path)
は fpath
の有効なインデックス範囲外となり、fpath[len(e.path)]
の評価時にパニック(インデックス範囲外エラー)が発生する可能性がありました。
このコミットでは、この問題を解決するために条件式を以下のように変更しました。
if strings.HasPrefix(fpath, e.path+sep) {
ここで sep
は const sep = string(filepath.Separator)
として定義された定数です。この変更により、fpath
が e.path
の後に直接パス区切り文字が続く文字列であるかを strings.HasPrefix
関数一つで効率的かつ安全にチェックできるようになりました。e.path+sep
という文字列をプレフィックスとしてチェックすることで、fpath
が e.path
よりも長く、かつその直後に区切り文字が続く場合にのみ条件が真となります。これにより、インデックス範囲外エラーの発生を防ぎます。
また、このコミットでは、filepath.Separator
を string
にキャストする処理が複数回行われていた箇所を、const sep = string(filepath.Separator)
という定数として定義し、それを再利用するように変更しています。これはコードの可読性を向上させるとともに、わずかながらパフォーマンスの最適化にも寄与します。
splitFirst
関数においても、同様に string(filepath.Separator)
の代わりに sep
定数を使用するように変更されています。これにより、コードの一貫性が保たれています。
コアとなるコードの変更箇所
--- a/src/cmd/godoc/mapping.go
+++ b/src/cmd/godoc/mapping.go
@@ -139,14 +139,16 @@ func (m *Mapping) Fprint(w io.Writer) {
}
}
+const sep = string(filepath.Separator)
+
// splitFirst splits a path at the first path separator and returns
// the path's head (the top-most directory specified by the path) and
// its tail (the rest of the path). If there is no path separator,
-// splitFirst returns path as head, and the the empty string as tail.
+// splitFirst returns path as head, and the empty string as tail.
// Specifically, splitFirst("foo") == splitFirst("foo/").
//
func splitFirst(path string) (head, tail string) {
- if i := strings.Index(path, string(filepath.Separator)); i > 0 {
+ if i := strings.Index(path, sep); i > 0 {
// 0 < i < len(path)
return path[0:i], path[i+1:]
}
@@ -179,7 +181,7 @@ func (m *Mapping) ToAbsolute(spath string) string {
func (m *Mapping) ToRelative(fpath string) string {
for _, e := range m.list {
// if fpath has prefix e.path, the next character must be a separator (was issue 3096)
- if strings.HasPrefix(fpath, e.path) && fpath[len(e.path)] == filepath.Separator {
+ if strings.HasPrefix(fpath, e.path+sep) {
spath := filepath.ToSlash(fpath)
// /absolute/prefix/foo -> prefix/foo
return path.Join(e.prefix, spath[len(e.path):]) // Join will remove a trailing '/'
コアとなるコードの解説
-
const sep = string(filepath.Separator)
の追加:filepath.Separator
はrune
型(文字)ですが、strings.Index
や文字列結合で使用するためにはstring
型に変換する必要があります。- この定数を導入することで、コード全体でパス区切り文字を文字列として扱う際の一貫性が保たれ、冗長な型変換が削減されます。
-
splitFirst
関数の変更:strings.Index(path, string(filepath.Separator))
がstrings.Index(path, sep)
に変更されました。これは、上記で定義されたsep
定数を使用するように更新されたものです。機能的な変更はありませんが、コードのクリーンアップと一貫性の向上に貢献しています。- コメント
// splitFirst returns path as head, and the the empty string as tail.
が// splitFirst returns path as head, and the empty string as tail.
に修正され、タイポが直されています。 - コメント
// Specifically, splitFirst("foo") == splitFirst("foo/").
が追加され、関数の挙動がより明確に説明されています。
-
ToRelative
関数の変更:- これがこのコミットの最も重要な変更点です。
- 元の条件式
if strings.HasPrefix(fpath, e.path) && fpath[len(e.path)] == filepath.Separator
が、if strings.HasPrefix(fpath, e.path+sep)
に変更されました。 - 前述の通り、この変更により、
fpath
がe.path
と完全に一致する場合に発生する可能性があったインデックス範囲外エラーが解消されます。 e.path+sep
という文字列をプレフィックスとしてチェックすることで、fpath
がe.path
の後にパス区切り文字が続く形式であるかを安全に確認できます。これにより、fpath
の長さがe.path
と同じであるケースを適切に除外できます。- コメント
(was issue 3096)
は、この変更がGoのIssue 3096に関連していることを示唆しています。
関連リンク
- Go Change List (CL):
https://golang.org/cl/5683072
Goプロジェクトでは、GitHubのプルリクエストに相当する変更提案を「Change List (CL)」と呼びます。これはGerritというコードレビューシステム上で管理されており、このリンクはそのCLへの直接リンクです。CLには、変更内容の詳細、レビューコメント、テスト結果などが含まれています。
参考にした情報源リンク
- Go言語公式ドキュメント:
path/filepath
パッケージ: https://pkg.go.dev/path/filepath - Go言語公式ドキュメント:
strings
パッケージ: https://pkg.go.dev/strings - Go言語公式ドキュメント:
godoc
コマンド: https://pkg.go.dev/cmd/godoc - Go Issue 3096 (関連する可能性のあるIssue): https://github.com/golang/go/issues/3096 (このコミットメッセージに記載されているIssue番号から推測)
- Gerrit Code Review: https://go-review.googlesource.com/ (Goプロジェクトが使用しているコードレビューシステム)