[インデックス 12709] ファイルの概要
このコミットは、Go言語のCgoツールにおいて、gccgo
コンパイラを使用する際のerrno
(エラー番号)の取り扱いに関するバグを修正するものです。具体的には、C関数呼び出し後にerrno
が正しく取得されない、または以前のerrno
値が残ってしまう問題を解決し、syscall.GetErrno()
の挙動を改善します。
コミット
commit 3a3c5aad4e6ccef38a1e6d56652523c3258da6a8
Author: Ian Lance Taylor <iant@golang.org>
Date: Wed Mar 21 10:38:58 2012 -0700
cmd/cgo: fix handling of errno for gccgo
Fixes #3332.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5868047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3a3c5aad4e6ccef38a1e6d56652523c3258da6a8
元コミット内容
cmd/cgo
: gccgo
におけるerrno
の取り扱いを修正。
Issue #3332を修正。
変更の背景
この変更は、Go言語のCgoツールがC言語の関数を呼び出す際に、C言語の標準ライブラリが設定するerrno
(エラー番号)の取り扱いに関する問題、特にgccgo
コンパイラを使用した場合に発生するバグを修正するために行われました。
C言語の関数は、エラーが発生した場合にグローバル変数errno
にエラーコードを設定することが一般的です。Goのsyscall
パッケージは、このerrno
の値をGoのエラーとして取得するためのsyscall.GetErrno()
関数を提供しています。しかし、gccgo
で生成されたCgoコードでは、C関数が成功した場合でもerrno
がクリアされず、以前のエラーコードが残ってしまう可能性がありました。これにより、C関数が実際には成功しているにもかかわらず、Go側で誤ってエラーが報告されるという問題が発生していました(Issue #3332)。
このコミットは、C関数呼び出しの前にerrno
を明示的にゼロにリセットし、C関数の戻り値とerrno
の値を適切にチェックすることで、この問題を解決し、gccgo
環境下でのCgoの信頼性を向上させることを目的としています。
前提知識の解説
Cgo
Cgoは、GoプログラムからC言語のコードを呼び出すためのGoツールです。これにより、既存のCライブラリをGoアプリケーションで利用したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。Cgoは、Goのソースコード内にCのコードを埋め込む特殊なコメント構文(import "C"
)を使用し、ビルド時にGoとCの間のブリッジコードを生成します。
errno
errno
は、C言語の標準ライブラリ関数がエラーを報告するために使用するグローバル変数です。多くのシステムコールやライブラリ関数は、失敗した場合に特定の値を返し(例: -1
やNULL
)、同時にerrno
に具体的なエラーコード(例: EACCES
、ENOENT
など)を設定します。errno
の値は、perror()
関数やstrerror()
関数を使って人間が読めるエラーメッセージに変換できます。
syscall.GetErrno()
とsyscall.SetErrno()
Go言語のsyscall
パッケージは、オペレーティングシステムのプリミティブな機能への低レベルなインターフェースを提供します。
syscall.GetErrno()
: 現在のスレッドのerrno
の値をGoのsyscall.Errno
型として取得します。Cgoを通じてC関数を呼び出した後、C関数が設定したerrno
の値を取得するために使用されます。syscall.SetErrno(int)
: 現在のスレッドのerrno
の値を設定します。このコミットでは、C関数呼び出しの前にerrno
を0
にリセットするために使用されています。これは、C関数が成功した場合にerrno
がクリアされないというC言語の慣習に対応するための重要なステップです。
gccgo
gccgo
は、GCC(GNU Compiler Collection)のフロントエンドとして実装されたGoコンパイラです。標準のGoコンパイラ(gc
)とは異なる実装であり、Cgoのコード生成やランタイムの挙動に微妙な違いがある場合があります。このコミットで修正された問題は、特にgccgo
環境で顕在化していました。
技術的詳細
このコミットの技術的な詳細は、Cgoが生成するGoコードにおけるerrno
の取り扱いロジックの改善にあります。
従来のCgoのコード生成では、C関数を呼び出した後、無条件にsyscall.GetErrno()
を呼び出してその値を返していました。しかし、C言語の慣習として、関数が成功した場合にerrno
を0
にリセットすることは保証されていません。そのため、以前のC関数呼び出しで設定されたerrno
の値がそのまま残ってしまい、Go側で誤ってエラーとして解釈される可能性がありました。
この修正では、以下の2つの主要な変更が導入されています。
errno
の事前リセット: C関数を呼び出す直前に、syscall.SetErrno(0)
を呼び出してerrno
を明示的に0
に設定します。これにより、C関数が成功した場合にerrno
が0
のままであることが保証され、以前の呼び出しによる「古い」errno
値が誤って取得されることを防ぎます。errno
の条件付き取得: C関数の呼び出し結果を受け取った後、syscall.GetErrno()
でerrno
の値を取得します。しかし、このerrno
の値が0
でない場合にのみ、それをエラーとして返します。errno
が0
の場合は、エラーがないことを示すnil
を返します。
このロジックにより、C関数が成功した場合にはnil
エラーが返され、C関数がエラーを報告するためにerrno
を設定した場合にのみ、そのerrno
値がGoのエラーとして適切に伝播されるようになります。これは、C言語のerrno
のセマンティクスとGoのエラーハンドリングモデルをより正確に整合させるための重要な改善です。
コアとなるコードの変更箇所
--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -284,8 +284,13 @@ func (p *Package) writeDefsFunc(fc, fgo2 *os.File, n *Name) {
}
conf.Fprint(fgo2, fset, d)
fmt.Fprintf(fgo2, "{\\n")
+ fmt.Fprintf(fgo2, "\\tsyscall.SetErrno(0)\\n")
fmt.Fprintf(fgo2, "\\tr := %s(%s)\\n", cname, strings.Join(paramnames, ", "))
-\t\t\tfmt.Fprintf(fgo2, "\\treturn r, syscall.GetErrno()\\n")
+\t\t\tfmt.Fprintf(fgo2, "\\te := syscall.GetErrno()\\n")
+\t\t\tfmt.Fprintf(fgo2, "\\tif e != 0 {\\n")
+\t\t\tfmt.Fprintf(fgo2, "\\t\\treturn r, e\\n")
+\t\t\tfmt.Fprintf(fgo2, "\\t}\\n")
+\t\t\tfmt.Fprintf(fgo2, "\\treturn r, nil\\n")
fmt.Fprintf(fgo2, "}\\n")
// declare the C function.
fmt.Fprintf(fgo2, "//extern %s\\n", n.C)
コアとなるコードの解説
この変更は、src/cmd/cgo/out.go
ファイル内のwriteDefsFunc
関数にあります。この関数は、CgoがGoのラッパー関数を生成する際に使用されます。
変更前は、C関数呼び出し後のerrno
の取り扱いが以下のようになっていました。
// 変更前 (擬似コード)
r := C.c_function(params)
return r, syscall.GetErrno() // 無条件にerrnoを返す
このコードでは、C関数が成功した場合でもsyscall.GetErrno()
が非ゼロの値を返す可能性があり、Go側で誤ったエラーが報告される原因となっていました。
変更後は、以下のようになっています。
// 変更後 (擬似コード)
syscall.SetErrno(0) // C関数呼び出し前にerrnoを0にリセット
r := C.c_function(params)
e := syscall.GetErrno() // errnoの値を取得
if e != 0 { // errnoが0でない場合のみエラーとして返す
return r, e
}
return r, nil // errnoが0の場合はnilエラーを返す
各行の変更点を詳しく見ていきます。
-
fmt.Fprintf(fgo2, "\\tsyscall.SetErrno(0)\\n")
- C関数を呼び出す直前に、
syscall.SetErrno(0)
を挿入します。これにより、現在のスレッドのerrno
が明示的に0
にリセットされます。これは、C関数が成功した場合にerrno
をクリアしないというC言語の慣習に対応するための重要なステップです。
- C関数を呼び出す直前に、
-
fmt.Fprintf(fgo2, "\\tr := %s(%s)\\n", cname, strings.Join(paramnames, ", "))
- これは変更されていません。C関数を呼び出し、その戻り値を
r
に格納します。
- これは変更されていません。C関数を呼び出し、その戻り値を
-
fmt.Fprintf(fgo2, "\\te := syscall.GetErrno()\\n")
- 変更前は
return r, syscall.GetErrno()
と直接返していましたが、変更後はsyscall.GetErrno()
の結果を一時変数e
に格納します。
- 変更前は
-
fmt.Fprintf(fgo2, "\\tif e != 0 {\\n")
- 取得した
errno
の値e
が0
でない場合にのみ、エラーとして処理するための条件分岐を追加します。
- 取得した
-
fmt.Fprintf(fgo2, "\\t\\treturn r, e\\n")
e
が0
でない場合(つまり、C関数がエラーを報告したと判断できる場合)は、C関数の戻り値r
とerrno
の値e
をGoの関数から返します。
-
fmt.Fprintf(fgo2, "\\t}\\n")
if
ブロックの閉じ括弧です。
-
fmt.Fprintf(fgo2, "\\treturn r, nil\\n")
e
が0
の場合(つまり、C関数が成功したと判断できる場合)は、C関数の戻り値r
とnil
(エラーなし)をGoの関数から返します。
この一連の変更により、Cgoが生成するGoのラッパー関数は、C言語のerrno
のセマンティクスをより正確に反映し、gccgo
環境下でのerrno
の誤った報告を防ぐことができます。
関連リンク
- Go Issue #3332: https://github.com/golang/go/issues/3332
- Go CL 5868047: https://golang.org/cl/5868047
参考にした情報源リンク
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHWvQ5ydR0saYsFG13PNrvNIvcp6UQ_JdK6wPFewmG6Crub378K7_canm6bB4-o6E8qgMqs8EMYZCGhovevac3Yq07EAe2DlwvP_DLAVyobIXXBcxJZTHuejc0I3m84KFxM71Q=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG3ECpJlXZJyp44Xj1MEQA2DJnBp7nORjx2YeRSvv4o6Wg8LpkoopaucNDRNwbA0PDShQpPwp9_chpyLQNbUGKXV8Xvszh-ieylE8bP-RgQB60U0L7JSkgL_58QzMF8keC-iBdvdOZlQDtyA8qV3I7G_uCbJ2qXGXidg1H5SjuMUS8TSVB-bWO6GxMaQk3zXz8u654=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHErrpT4W8cyMY1TrVTweznDsdtO1g_ROKTDF3A39GMi9St8NUL6BLcvvwe7E5SupzYfsA7h2G8zUek6zcbIJSmScd0tUlCNOyYGR_8rZ6GR8iGqZo0oRzzRXmKEIBJlKfhabGKzWM51juxqcSgeJwNcNHLmDsiwr8_S-DJ3-g62Js=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF4WEc0-5sLZyXZX_afy55f1XqVeS4XxLJQmuBVPrPPSE9lD74i7mWuCoe2zUAGO6gLmBSezOaKZIimiHze8G8Hd0GKI7Ezd--EE2arVuW0TjaW1b5x8R_MmQjojjSyT9zMFdgfkvakdPBuKHYjWZK
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFxeRsFvjTUsABcbEwpVUZ4hMhpoAA2wfQnu9cyftCxCYyJxuDdOzjvl03Pfzrac3hsXCa-IPnUjy4HnU9Kb6UoMLmy3Ef4DV_PvYCsvKyvYDrORmChcxRQdggmNDiq2ILbKBcpXj3iEiGmayCDVcJRsYwlsG-6LkxfyEi4lUUGB5mWuPHRXGsqoG8=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFMMy1Ig2gu2EpHuZw5fe-KEgasQMevyvrebuZMsNb5ZGvjTWIVC2lq-6dV3AGtxQZwD9ySAkTrBnrzPjuTN95OWmsVfgwIYeUHn9Mj8bporjcimfFtiQwOLKWXzHOpS6TOXA-ZQQmQxOqhsclv-iY3EMgp2924FHjhKqTYODn9TEM1
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE_rnFIb-vstcDwvwLe_93g-lAP_J1eBxUrccasObcv48WLq91lL_kWTtSwTvoiNXXziCSJ4u6U9BVh63MI_6c7wRr_ywi5ef8lUi39ePDVt7q8OXlHvQ0P-n-1aRlXZk3R_TnYW-dqAx517WesjTWoXF9blGZG2edvjC7m-Oqz