[インデックス 12124] ファイルの概要
このコミットは、godoc ツールにおける絶対パスから相対パスへのマッピング処理のバグを修正するものです。具体的には、strings.HasPrefix の誤用によって発生していた、意図しないパスのマッチングを防ぐための改善が行われています。
コミット
commit 7b22e4628296518691a8ce6e4e4065ff4aeb69d8
Author: Robert Griesemer <gri@golang.org>
Date: Tue Feb 21 18:12:37 2012 -0800
godoc: fix absolute->relative mapping
Fixes #3096.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5690063
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7b22e4628296518691a8ce6e4e4065ff4aeb69d8
元コミット内容
src/cmd/godoc/mapping.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/cmd/godoc/mapping.go b/src/cmd/godoc/mapping.go
index 89e531e2f3..1a0da15bfc 100644
--- a/src/cmd/godoc/mapping.go
+++ b/cmd/godoc/mapping.go
@@ -178,7 +178,8 @@ func (m *Mapping) ToAbsolute(spath string) string {
//
func (m *Mapping) ToRelative(fpath string) string {
for _, e := range m.list {
- if strings.HasPrefix(fpath, e.path) {
+ // 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 {
spath := filepath.ToSlash(fpath)
// /absolute/prefix/foo -> prefix/foo
return path.Join(e.prefix, spath[len(e.path):]) // Join will remove a trailing '/'
変更の背景
このコミットは、Go言語のドキュメンテーションツールである godoc における、パスのマッピングに関するバグ(Issue 3096)を修正するために行われました。
元のコードでは、絶対ファイルパスを相対パスに変換する ToRelative 関数内で、strings.HasPrefix(fpath, e.path) という条件を使用していました。この条件は、fpath が e.path で始まるかどうかを単純にチェックします。しかし、このチェックだけでは不十分なケースがありました。
例えば、e.path が /usr/local/go/src/pkg/net で、fpath が /usr/local/go/src/pkg/net/http のような場合、strings.HasPrefix は正しく true を返します。しかし、問題となるのは、e.path が /usr/local/go/src/pkg/net で、fpath が /usr/local/go/src/pkg/network のようなケースです。この場合も strings.HasPrefix は true を返してしまいますが、これらは異なるディレクトリであり、network が net のサブディレクトリであるかのように誤って扱われてしまう可能性がありました。
この誤ったマッピングは、godoc がドキュメントを生成する際に、間違ったパス解決を行い、結果としてリンク切れや不正確なドキュメント表示を引き起こす原因となっていました。Issue 3096 はこの問題点を指摘しており、このコミットはその根本原因を解決することを目的としています。
前提知識の解説
godoc
godoc は、Go言語のソースコードからドキュメンテーションを生成し、表示するためのツールです。Goのコードには、関数、型、変数、パッケージなどに対してコメント形式でドキュメントを記述する慣習があり、godoc はこれらのコメントを解析して、Webブラウザで閲覧可能な形式で表示したり、プレーンテキストで出力したりします。開発者がGoの標準ライブラリやサードパーティのパッケージのドキュメントを参照する際に広く利用されます。
パスとファイルシステム
- 絶対パス (Absolute Path): ファイルシステム上のルートディレクトリから始まる完全なパスです。例えば、Linux/macOSでは
/home/user/documents/file.txt、WindowsではC:\Users\user\Documents\file.txtのようになります。 - 相対パス (Relative Path): 現在の作業ディレクトリを基準としたパスです。例えば、現在のディレクトリが
/home/userであれば、documents/file.txtは/home/user/documents/file.txtを指します。 - パスセパレータ (Path Separator): ディレクトリ名を区切る文字です。Linux/macOSでは
/(スラッシュ)、Windowsでは\(バックスラッシュ) が一般的です。Go言語のfilepathパッケージは、OSに依存しないパス操作を提供し、filepath.Separatorは現在のOSのパスセパレータを表す定数です。
strings.HasPrefix
Go言語の標準ライブラリ strings パッケージに含まれる関数で、strings.HasPrefix(s, prefix string) bool の形式で使用します。これは、文字列 s が指定された prefix で始まる場合に true を返し、そうでない場合に false を返します。
filepath.Separator
Go言語の標準ライブラリ path/filepath パッケージに含まれる定数で、現在のオペレーティングシステムで使用されるパスセパレータ文字を表します。例えば、Unix系システムでは /、Windowsでは \ となります。この定数を使用することで、OSに依存しないパス操作が可能になります。
技術的詳細
このコミットの技術的な核心は、godoc の Mapping 構造体の ToRelative メソッドにおけるパスの比較ロジックの改善です。
元のコードでは、for _, e := range m.list ループ内で、各マッピングエントリ e の e.path (絶対パスのプレフィックス) と入力ファイルパス fpath を比較する際に、strings.HasPrefix(fpath, e.path) のみを使用していました。
この単純なプレフィックスチェックの問題点は、例えば以下のようなケースで顕在化します。
e.path=/a/bfpath=/a/bc
この場合、strings.HasPrefix("/a/bc", "/a/b") は true を返します。しかし、/a/bc は /a/b ディレクトリのサブパスではなく、/a/b とは異なる /a/bc というファイルまたはディレクトリを指しています。godoc のパスマッピングにおいては、/a/b が /a/bc の親ディレクトリであるかのように誤って解釈されてしまう可能性がありました。
このコミットでは、この問題を解決するために、strings.HasPrefix の結果に加えて、さらに厳密な条件を追加しています。
if strings.HasPrefix(fpath, e.path) && fpath[len(e.path)] == filepath.Separator {
この変更により、以下の2つの条件が同時に満たされる場合にのみ、パスがマッチしたと判断されます。
fpathがe.pathで始まる (strings.HasPrefix(fpath, e.path))。e.pathの直後のfpathの文字が、パスセパレータ (filepath.Separator) である。
この追加された fpath[len(e.path)] == filepath.Separator という条件が非常に重要です。
len(e.path)はe.pathの文字列長です。fpath[len(e.path)]は、fpathの中でe.pathの直後に続く文字を指します。
例えば、
e.path=/usr/local/go/src/pkg/net(長さ: 26)fpath=/usr/local/go/src/pkg/net/http
この場合、fpath[26] は / となり、filepath.Separator と一致するため、条件は true となります。これは正しいマッピングです。
一方、
e.path=/usr/local/go/src/pkg/net(長さ: 26)fpath=/usr/local/go/src/pkg/network
この場合、fpath[26] は w となり、filepath.Separator (/) と一致しません。したがって、この条件は false となり、誤ったマッピングが回避されます。
この修正により、godoc はパスをより正確に識別し、絶対パスから相対パスへの変換が意図した通りに行われるようになります。これは、特にGoの標準ライブラリのように、多くのパッケージが階層的に配置されている場合に、ドキュメントの正確性を保つ上で不可欠な改善です。
コアとなるコードの変更箇所
--- a/src/cmd/godoc/mapping.go
+++ b/src/cmd/godoc/mapping.go
@@ -178,7 +178,8 @@ func (m *Mapping) ToAbsolute(spath string) string {
//
func (m *Mapping) ToRelative(fpath string) string {
for _, e := range m.list {
- if strings.HasPrefix(fpath, e.path) {
+ // 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 {
spath := filepath.ToSlash(fpath)
// /absolute/prefix/foo -> prefix/foo
return path.Join(e.prefix, spath[len(e.path):]) // Join will remove a trailing '/'
コアとなるコードの解説
変更は src/cmd/godoc/mapping.go ファイルの ToRelative 関数内で行われています。
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 {
spath := filepath.ToSlash(fpath)
// /absolute/prefix/foo -> prefix/foo
return path.Join(e.prefix, spath[len(e.path):]) // Join will remove a trailing '/'
}
}
// ... (以降のコードは変更なし)
}
for _, e := range m.list:Mapping構造体が持つマッピングエントリのリストをイテレートしています。各eは、絶対パスのプレフィックス (e.path) とそれに対応する相対パスのプレフィックス (e.prefix) を持っています。- if strings.HasPrefix(fpath, e.path) {: 変更前のコードです。fpathがe.pathで始まるかどうかだけをチェックしていました。+ // if fpath has prefix e.path, the next character must be a separator (was issue 3096): 追加されたコメントです。Issue 3096 で指摘された問題の解決策として、次の文字がセパレータである必要があることを示しています。+ if strings.HasPrefix(fpath, e.path) && fpath[len(e.path)] == filepath.Separator {: 変更後のコードです。strings.HasPrefix(fpath, e.path): これは以前と同じく、fpathがe.pathで始まることを確認します。&&: 論理AND演算子です。両方の条件がtrueの場合にのみ、全体の条件がtrueとなります。fpath[len(e.path)] == filepath.Separator: この部分が追加された重要な条件です。len(e.path):e.pathの文字列長を取得します。fpath[len(e.path)]:fpath文字列のlen(e.path)インデックスにある文字(つまり、e.pathの直後の文字)を取得します。== filepath.Separator: 取得した文字が、現在のOSのパスセパレータ(例:/または\)と等しいかどうかをチェックします。
この追加された条件により、fpath が e.path の「真の」サブパスである場合にのみマッチングが成功するようになり、"/a/b" と "/a/bc" のような誤ったマッチングが排除されます。
spath := filepath.ToSlash(fpath):fpathをスラッシュ区切りのパスに変換します。これは、Windows環境など、バックスラッシュがパスセパレータとして使われる場合でも、内部的に統一された形式でパスを扱うためです。return path.Join(e.prefix, spath[len(e.path):]):spath[len(e.path):]:spathの中でe.pathの部分を除いた残りの部分(相対パスの残りの部分)を抽出します。path.Join(e.prefix, ...):e.prefix(マッピングされた相対パスのプレフィックス) と抽出した相対パスの残りの部分を結合して、最終的な相対パスを生成します。path.Joinは、必要に応じて余分なスラッシュを削除するなどの正規化を行います。
この修正は、godoc のパス解決の堅牢性を高め、より正確なドキュメント生成に貢献しています。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/7b22e4628296518691a8ce6e4e4065ff4aeb69d8
- Go CL (Change List): https://golang.org/cl/5690063
- Go Issue 3096: https://code.google.com/p/go/issues/detail?id=3096 (古いGoogle Codeのリンクですが、当時のIssueトラッカーです)
参考にした情報源リンク
- Go Issue 3096 (Web検索で確認した情報源): https://code.google.com/p/go/issues/detail?id=3096
- Go言語
stringsパッケージドキュメント: https://pkg.go.dev/strings - Go言語
path/filepathパッケージドキュメント: https://pkg.go.dev/path/filepath - Go言語
pathパッケージドキュメント: https://pkg.go.dev/path godocツールに関する一般的な情報 (Go公式ドキュメントなど)