[インデックス 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/b
fpath
=/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公式ドキュメントなど)