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

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

このコミットは、Go言語のruntimeパッケージに含まれるCaller関数とCallers関数のドキュメントの修正に関するものです。具体的には、これらの関数がスタックトレースを辿る際に使用するskip引数の挙動に関する説明が誤っていたため、その記述を修正しています。以前のドキュメントではskip引数の意味が逆になっており、その混乱を解消することが目的です。

コミット

commit 11c1b1f96b9d2c99eef26cae2398961129985d75
Author: Rob Pike <r@golang.org>
Date:   Thu May 24 14:15:43 2012 -0700

    runtime: fix docs for Caller and Callers
    The previous attempt to explain this got it backwards (all the more reason to be
    sad we couldn't make the two functions behave the same).
    
    Fixes #3669.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6249051

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/11c1b1f96b9d2c99eef26cae2398961129985d75

元コミット内容

このコミットの元々の内容は、runtime.Callerおよびruntime.Callers関数のドキュメントが、skip引数の解釈について誤った説明をしていた点を修正することです。特に、Caller関数におけるskipの基準とCallers関数におけるskipの基準が異なっているにもかかわらず、その違いがドキュメントで正しく説明されていなかったことが問題でした。コミットメッセージには、「以前の説明は逆になっていた」と明記されており、両関数の挙動を統一できなかったことへの言及もあります。

変更の背景

Go言語のruntimeパッケージには、実行中のゴルーチンのスタックトレース情報を取得するためのCaller関数とCallers関数が存在します。これらの関数は、デバッグ、ロギング、あるいは特定のフレームワークにおけるメタプログラミングのような高度な用途で利用されます。

Caller関数は単一の呼び出し元の情報を取得し、Callers関数は複数の呼び出し元のプログラムカウンタ(PC)をスライスに格納します。どちらの関数もskipという整数引数を取り、これはスタックフレームをどれだけスキップするかを指定します。しかし、歴史的な理由により、CallerCallersではこのskip引数の解釈が異なっていました。

  • Caller(skip int): skip=0Caller関数自身の呼び出し元を指し、skip=1Callerを呼び出した関数の呼び出し元を指す、といったように、skipは「呼び出し元の呼び出し元」を数えるような挙動をしていました。
  • Callers(skip int, pc []uintptr): skip=0Callers関数自身のフレームを指し、skip=1Callersを呼び出した関数のフレームを指す、といったように、skipは「現在のフレームから数えてスキップする数」を指していました。

この違いがドキュメントで正確に説明されていなかったため、開発者がこれらの関数を誤って使用する可能性がありました。このコミットは、その誤解を解消し、ドキュメントを正確にすることで、開発者の混乱を防ぐことを目的としています。コミットメッセージにある「Fixes #3669」は、このドキュメントの不正確さがGoのIssueトラッカーで報告されていたことを示しています。

補足: 本コミットが参照しているIssue #3669は、golang/goリポジトリ内のruntime.Callerおよびruntime.Callersのドキュメントに関するものであり、Web検索でヒットしたgrpc/grpc-govscode-go、JetBrainsのIssue #3669とは異なります。GoのIssueトラッカーでは、同じ番号のIssueが複数のプロジェクトで存在することがありますが、この場合はGo本体のランタイムに関するIssueです。

前提知識の解説

スタックトレースとプログラムカウンタ (PC)

プログラムが実行される際、関数呼び出しは「コールスタック」と呼ばれるデータ構造に積まれていきます。各関数呼び出しは「スタックフレーム」を形成し、そのフレームには関数のローカル変数、引数、そして呼び出し元に戻るためのアドレス(リターンアドレス)などが含まれます。

「スタックトレース」とは、このコールスタックを逆順に辿り、現在実行中の関数から始まり、その関数を呼び出した関数、さらにその関数を呼び出した関数…というように、関数呼び出しの履歴を一覧表示したものです。これは、プログラムの実行フローを理解したり、エラー発生時の原因を特定したりする上で非常に重要です。

「プログラムカウンタ (PC)」とは、CPUが次に実行する命令のアドレスを保持するレジスタです。スタックトレースにおいては、各スタックフレームに対応するPCは、その関数が呼び出された時点での命令のアドレス、あるいはその関数が呼び出し元に戻るべきアドレスを指します。Goのruntime.Callerruntime.Callers関数は、このPC情報を提供することで、どのファイル、どの行で関数が呼び出されたかを特定できるようにします。

runtime.Callerruntime.Callers

Go言語のruntimeパッケージは、Goランタイムとの低レベルなインタラクションを提供します。その中で、CallerCallersはスタックトレース情報を取得するための関数です。

  • func Caller(skip int) (pc uintptr, file string, line int, ok bool): この関数は、呼び出し元のゴルーチンのスタック上の関数呼び出しに関するファイル名と行番号の情報を報告します。skip引数は、スタックフレームをどれだけスキップするかを指定します。返り値は、プログラムカウンタ(pc)、ファイル名(file)、行番号(line)、そして情報が取得できたかどうかを示す真偽値(ok)です。

  • func Callers(skip int, pc []uintptr) int: この関数は、呼び出し元のゴルーチンのスタック上の関数呼び出しのプログラムカウンタをpcスライスに格納します。skip引数は、pcに記録を開始する前にスキップするスタックフレームの数を指定します。返り値は、pcに書き込まれたエントリの数です。

これらの関数は、例えば以下のような場面で利用されます。

package main

import (
	"fmt"
	"runtime"
)

func logMessage(msg string) {
	pc, file, line, ok := runtime.Caller(1) // logMessageの呼び出し元を取得
	if !ok {
		file = "?"
		line = 0
	}
	fmt.Printf("[%s:%d] %s (PC: %x)\n", file, line, msg, pc)
}

func main() {
	logMessage("Hello from main!")
	anotherFunction()
}

func anotherFunction() {
	logMessage("Hello from anotherFunction!")
}

上記の例では、logMessage関数内でruntime.Caller(1)を呼び出すことで、logMessageを呼び出した関数(mainanotherFunction)のファイル名と行番号を取得しています。

技術的詳細

このコミットの技術的な詳細は、runtime.Callerruntime.Callersskip引数のドキュメントにおける「オフセット」の解釈の修正にあります。

修正前と修正後のドキュメントの記述を比較すると、その違いが明確になります。

runtime.Callerの変更点

修正前: The argument skip is the number of stack frames to ascend, with 1 identifying the caller of Caller. (引数skipは上昇するスタックフレームの数であり、1Callerの呼び出し元を識別します。)

修正後: The argument skip is the number of stack frames to ascend, with 0 identifying the caller of Caller. (引数skipは上昇するスタックフレームの数であり、0Callerの呼び出し元を識別します。)

この変更は、Caller関数においてskip=0Caller関数を呼び出した関数(つまり、Callerの直接の呼び出し元)の情報を返すことを明確にしています。修正前はskip=1がその役割を果たすと誤って記述されていました。これは、Caller関数が内部的にスタックを辿る際に、skip引数に指定された値に1を加算して実際のスキップ数を計算していたため、ドキュメントと実際の挙動が食い違っていた可能性があります。

runtime.Callersの変更点

修正前: The argument skip is the number of stack frames to skip before recording in pc, with 0 starting at the caller of Callers. (引数skippcに記録する前にスキップするスタックフレームの数であり、0Callersの呼び出し元から始まります。)

修正後: The argument skip is the number of stack frames to skip before recording in pc, with 0 identifying the frame for Callers itself and 1 identifying the caller of Callers. (引数skippcに記録する前にスキップするスタックフレームの数であり、0Callers関数自身のフレームを識別し、1Callersの呼び出し元を識別します。)

Callers関数では、修正前はskip=0Callersの呼び出し元から始まると記述されていましたが、実際にはskip=0Callers関数自身のスタックフレームを指し、skip=1Callersの呼び出し元を指します。この修正により、Callersskip引数の挙動がより正確に記述され、Callerとの違いが明確になりました。

このドキュメントの修正は、Goのランタイム内部のスタックフレームの扱いと、それらを外部に公開するAPIの間の整合性を高める上で重要です。特に、CallerCallersskipの基準が異なるという「歴史的な理由」による非対称性を、ドキュメントで正しく説明することが、開発者の混乱を避けるために不可欠でした。

コアとなるコードの変更箇所

このコミットによる変更は、src/pkg/runtime/extern.goファイル内のコメント(ドキュメンテーション)のみです。実際の関数の実装には変更はありません。

--- a/src/pkg/runtime/extern.go
+++ b/src/pkg/runtime/extern.go
@@ -20,7 +20,7 @@ func Goexit()
 
 // Caller reports file and line number information about function invocations on
 // the calling goroutine's stack.  The argument skip is the number of stack frames
-// to ascend, with 1 identifying the caller of Caller.  (For historical reasons the
+// to ascend, with 0 identifying the caller of Caller.  (For historical reasons the
 // meaning of skip differs between Caller and Callers.) The return values report the
 // program counter, file name, and line number within the file of the corresponding
 // call.  The boolean ok is false if it was not possible to recover the information.
@@ -28,7 +28,8 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool)
 
 // Callers fills the slice pc with the program counters of function invocations
 // on the calling goroutine's stack.  The argument skip is the number of stack frames
-// to skip before recording in pc, with 0 starting at the caller of Callers.
+// to skip before recording in pc, with 0 identifying the frame for Callers itself and
+// 1 identifying the caller of Callers.
 // It returns the number of entries written to pc.
 func Callers(skip int, pc []uintptr) int
 

コアとなるコードの解説

変更されたのは、Goのソースコード内のコメント、特にGoDocとして機能する部分です。

  1. runtime.Callerのドキュメント修正: // to ascend, with 1 identifying the caller of Caller.// to ascend, with 0 identifying the caller of Caller. に変更されました。 これにより、Caller(0)Caller関数を呼び出した関数の情報を返すことが明確になりました。以前のドキュメントではCaller(1)がその役割を果たすと誤解される可能性がありました。

  2. runtime.Callersのドキュメント修正: // to skip before recording in pc, with 0 starting at the caller of Callers.// to skip before recording in pc, with 0 identifying the frame for Callers itself and // 1 identifying the caller of Callers. に変更されました。 これにより、Callers(0, ...)Callers関数自身のスタックフレームから情報を取得し始めること、そしてCallers(1, ...)Callers関数を呼び出した関数のスタックフレームから情報を取得し始めることが明確になりました。以前のドキュメントではCallers(0, ...)が呼び出し元から始まると誤解される可能性がありました。

これらの変更は、関数の挙動そのものを変えるものではなく、その挙動を正確に記述することで、開発者がAPIを正しく理解し、利用できるようにするための重要なドキュメント改善です。特に、CallerCallersの間でskip引数の解釈が異なるという「歴史的な理由」による非対称性が存在するため、この違いをドキュメントで明確にすることは、混乱を避ける上で不可欠でした。

関連リンク

参考にした情報源リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/11c1b1f96b9d2c99eef26cae2398961129985d75
  • コミットデータファイル: /home/orange/Project/comemo/commit_data/13159.txt
  • Go言語のruntimeパッケージのドキュメント(一般的な情報源として、このコミットが修正した内容を理解するために参照されるべきものです。ただし、この解説の生成においては、直接的な参照は行っていません。)
  • Go言語のスタックトレースに関する一般的な知識。
  • Issue #3669に関するWeb検索を行いましたが、本コミットが修正したGo本体のruntimeパッケージのドキュメントに関するIssue #3669の直接的な情報は見つかりませんでした。検索結果は主に他のプロジェクトの同一番号のIssueに関するものでした。