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

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

このコミットは、Goコンパイラ(cmd/gc)が自動生成するラッパー関数の行番号の記録方法を改善するものです。以前は、自動生成されたコードの行番号が、最初のソースファイルの1行目として報告されていましたが、この変更により、<autogenerated>:1という形式で報告されるようになり、より明確な情報を提供するようになりました。これにより、デバッグ時などに自動生成コードの出所が分かりやすくなります。

コミット

commit 9b976f5f03689e65c8f58e9b3de94e0d7f7fe072
Author: Russ Cox <rsc@golang.org>
Date:   Mon May 12 11:59:55 2014 -0400

    cmd/gc: record line number for auto-generated wrappers as <autogenerated>:1
    
    Before we used line 1 of the first source file.
    This should be clearer.
    
    Fixes #4388.
    
    LGTM=iant
    R=golang-codereviews, iant
    CC=golang-codereviews
    https://golang.org/cl/92250044

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

https://github.com/golang/go/commit/9b976f5f03689e65c8f58e9b3de94e0d7f7fe072

元コミット内容

cmd/gc: record line number for auto-generated wrappers as <autogenerated>:1

Before we used line 1 of the first source file.
This should be clearer.

Fixes #4388.

変更の背景

Go言語のコンパイラは、特定の状況下で「ラッパー関数」と呼ばれるコードを自動的に生成します。例えば、インターフェースのメソッド呼び出しや、特定の最適化のために、コンパイラがユーザーが書いたコードとは別に、内部的に新しい関数を作成することがあります。

このコミット以前は、これらの自動生成されたラッパー関数が実行時にエラーを発生させたり、スタックトレースに現れたりした場合、その行番号は「最初のソースファイルの1行目」として報告されていました。これは、実際のソースコードのどこにも対応しないため、デバッグや問題の特定を非常に困難にしていました。開発者は、エラーがどこから発生したのか、それが自動生成されたコードによるものなのか、それとも自身のコードによるものなのかを区別することができませんでした。

この問題を解決し、自動生成コードの出所をより明確にするために、このコミットでは、自動生成されたラッパー関数の行番号を特別な識別子である<autogenerated>:1として記録するように変更されました。これにより、開発者はスタックトレースを見た際に、そのフレームがコンパイラによって自動生成されたものであることを一目で認識できるようになり、デバッグの効率が向上します。コミットメッセージにあるFixes #4388は、この問題がGoの内部バグトラッカーで追跡されていたことを示しています。

前提知識の解説

  • Goの自動生成ラッパー関数: Goコンパイラ(cmd/gc)は、特定の言語機能(例: インターフェースの実装、メソッド値、遅延呼び出しなど)を効率的に処理するために、実行時に内部的な「ラッパー関数」を生成することがあります。これらの関数は、開発者が直接記述するものではなく、コンパイラが生成する中間コードの一部です。
  • 行番号とデバッグ: プログラムの実行中にエラーが発生した場合、スタックトレースには通常、エラーが発生したファイル名と行番号が含まれます。これは、問題の場所を特定し、デバッグを行う上で非常に重要な情報です。
  • runtime.Caller: Goの標準ライブラリruntimeパッケージには、Callerという関数があります。これは、現在のゴルーチンのスタックトレースを調べ、指定された呼び出しスタックのフレームに対応するファイル名、行番号、関数名などの情報を取得するために使用されます。このコミットのテストコードでも、この関数が使用され、自動生成コードの行番号が正しく報告されているかを確認しています。
  • src/cmd/gc: Goコンパイラのソースコードがあるディレクトリです。Goのソースコードを機械語に変換する主要なツールであり、このコミットでは、コンパイラが自動生成コードの行番号情報をどのように埋め込むかを変更しています。
  • src/liblink: Goリンカのソースコードがあるディレクトリです。リンカは、コンパイラによって生成されたオブジェクトファイルやライブラリを結合し、実行可能なバイナリを作成する役割を担います。このコミットでは、リンカが<autogenerated>のような特殊なファイル名を認識できるように変更が加えられています。

技術的詳細

この変更は主にGoコンパイラ(src/cmd/gc)とリンカ(src/liblink)の2つの部分に影響を与えます。

  1. コンパイラ側 (src/cmd/gc/subr.c): genwrapper関数は、Goコンパイラ内でラッパーコードを生成する役割を担っています。この関数内で、自動生成されるコードの行番号情報が設定されます。 変更前は、lineno = 1;という行があり、これは単に現在の行番号を1に設定していました。これは、多くの場合、最初のソースファイルの1行目として解釈されていました。 変更後、以下の3行が追加されました。

    lexlineno++;
    lineno = lexlineno;
    linehist("<autogenerated>", 0, 0);
    
    • lexlineno++: lexlinenoは、コンパイラが現在処理しているソースコードの論理的な行番号を追跡するために使用される変数です。これをインクリメントすることで、自動生成コードに新しいユニークな行番号を割り当てます。
    • lineno = lexlineno;: 現在の行番号をlexlinenoの値に設定します。
    • linehist("<autogenerated>", 0, 0);: この関数は、コンパイラが生成するデバッグ情報に行番号とファイル名のマッピングを記録するために使用されます。ここでファイル名として<autogenerated>という特殊な文字列を渡すことで、このコードが自動生成されたものであることを明示的に示します。
  2. リンカ側 (src/liblink/obj.c): linkgetline関数は、リンカがデバッグ情報からファイル名と行番号を取得する際に使用されます。この関数は、ファイルパスが絶対パスであるかどうかを判断するために、パスの最初の文字をチェックしていました。 変更前は、Unix系システムでは/、Windowsではドライブレター(例: C:)で始まるかどうかをチェックしていました。 変更後、以下の条件が追加されました。

    || file[0] == '<'
    

    これにより、ファイルパスの最初の文字が<である場合(つまり、<autogenerated>のような特殊なファイル名である場合)も、絶対パスとして扱われるようになりました。これは、リンカがコンパイラによって埋め込まれた<autogenerated>という特殊なファイル名を正しく解釈し、デバッグ情報として利用できるようにするために必要です。

  3. テストコード (test/fixedbugs/issue4388.go): このコミットには、issue4388.goという新しいテストファイルが追加されています。このテストは、変更が正しく機能していることを検証するために設計されています。 テストコードでは、io.Closerインターフェースを埋め込んだ構造体Tを定義し、そのCloseメソッドを呼び出す際にパニックを発生させます。そして、checkLine関数内でruntime.Caller(n)を使用してスタックトレースの情報を取得し、ファイル名が<autogenerated>であり、行番号が1であることを検証しています。これは、自動生成されたラッパー関数が期待通りに<autogenerated>:1として報告されることを確認するためのものです。

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

src/cmd/gc/subr.c

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -2498,7 +2498,9 @@ genwrapper(Type *rcvr, Type *method, Sym *newnam, int iface)\n 		print("genwrapper rcvrtype=%T method=%T newnam=%S\n",\n 			rcvr, method, newnam);\n 
-	lineno = 1;	// less confusing than end of input
+	lexlineno++;
+	lineno = lexlineno;
+	linehist("<autogenerated>", 0, 0);
 
 	dclcontext = PEXTERN;
 	markdcl();

src/liblink/obj.c

--- a/src/liblink/obj.c
+++ b/src/liblink/obj.c
@@ -183,7 +183,7 @@ linkgetline(Link *ctxt, int32 line, LSym **f, int32 *l)\n 		file = a[n].incl->name;\n 		dlno = a[n].idel-1;\n 	}\n-	if((!ctxt->windows && file[0] == '/') || (ctxt->windows && file[1] == ':'))\n+	if((!ctxt->windows && file[0] == '/') || (ctxt->windows && file[1] == ':') || file[0] == '<')\n 		snprint(buf, sizeof buf, "%s", file);\n 	else\n 		snprint(buf, sizeof buf, "%s/%s", ctxt->pathname, file);\

test/fixedbugs/issue4388.go (新規追加)

// run

// Copyright 2014 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"io"
	"runtime"
)

type T struct {
	io.Closer
}

func f1() {
	// The 4 here and below depends on the number of internal runtime frames
	// that sit between a deferred function called during panic and
	// the original frame. If that changes, this test will start failing and
	// the number here will need to be updated.
	defer checkLine(4)
	var t *T
	var c io.Closer = t
	c.Close()
}

func f2() {
	defer checkLine(4)
	var t T
	var c io.Closer = t
	c.Close()
}

func main() {
	f1()
	f2()
}

func checkLine(n int) {
	if err := recover(); err == nil {
		panic("did not panic")
	}
	_, file, line, _ := runtime.Caller(n)
	if file != "<autogenerated>" || line != 1 {
		panic(fmt.Sprintf("expected <autogenerated>:1 have %s:%d", file, line))
	}
}

コアとなるコードの解説

  • src/cmd/gc/subr.c の変更: genwrapper関数は、コンパイラがラッパーコードを生成する際に呼び出されます。 lexlineno++;lineno = lexlineno;は、自動生成されるコードに新しい行番号を割り当て、コンパイラの内部的な行番号カウンタを更新します。 最も重要な変更はlinehist("<autogenerated>", 0, 0);です。linehist関数は、コンパイラが生成するオブジェクトファイルにデバッグ情報(ファイル名と行番号のマッピング)を記録するために使用されます。ここでファイル名として<autogenerated>という特殊な文字列を渡すことで、このコードがユーザーが書いたソースファイルではなく、コンパイラによって自動生成されたものであることを明示的に示します。これにより、デバッグツールやスタックトレースがこの情報を利用できるようになります。

  • src/liblink/obj.c の変更: linkgetline関数は、リンカがデバッグ情報からファイル名と行番号を読み取る際に使用されます。 file[0] == '<'という条件が追加されたことで、リンカはファイルパスの最初の文字が<である場合(例: <autogenerated>)を特殊なケースとして認識し、それを絶対パスとして扱います。これにより、リンカはコンパイラが埋め込んだ<autogenerated>というファイル名を正しく解釈し、実行可能なバイナリのデバッグ情報に含めることができます。

  • test/fixedbugs/issue4388.go の解説: このテストは、runtime.Caller関数を使用して、パニック発生時のスタックトレースからファイル名と行番号を取得し、それが期待通りに<autogenerated>:1となっていることを検証します。 f1()f2()関数は、それぞれポインタ型と値型のT構造体をio.Closerインターフェースに代入し、そのClose()メソッドを呼び出します。T構造体はio.Closerを埋め込んでいるため、Close()メソッドはインターフェースのメソッドとして呼び出され、この際にコンパイラによってラッパー関数が生成される可能性があります。 checkLine(n int)関数は、defer文によってパニック発生時に呼び出されます。この関数内でruntime.Caller(n)を呼び出し、スタックトレースのn番目のフレームのファイル名と行番号を取得します。そして、取得したファイル名が<autogenerated>であり、行番号が1であることをアサートします。これにより、コンパイラとリンカの変更が正しく機能し、自動生成コードの行番号が意図した通りに報告されていることが確認されます。

関連リンク

  • Go Gerrit Change: https://golang.org/cl/92250044
  • Go Issue #4388: このコミットが修正したとされるGoの内部バグトラッカーの課題番号です。公開されているGitHubリポジトリのIssueとは異なる可能性がありますが、コミットメッセージに明記されています。

参考にした情報源リンク

  • Go言語のソースコード (特にsrc/cmd/gcsrc/liblinkの関連ファイル)
  • Go言語のruntimeパッケージのドキュメント (特にruntime.Callerについて)
  • Go言語のコンパイラとリンカに関する一般的な知識
  • Go issue #51774 (関連する可能性のあるGoのIssue。ただし、このコミットが直接修正したIssue #4388とは異なる可能性があります。)# [インデックス 19329] ファイルの概要

このコミットは、Goコンパイラ(cmd/gc)が自動生成するラッパー関数の行番号の記録方法を改善するものです。以前は、自動生成されたコードの行番号が、最初のソースファイルの1行目として報告されていましたが、この変更により、<autogenerated>:1という形式で報告されるようになり、より明確な情報を提供するようになりました。これにより、デバッグ時などに自動生成コードの出所が分かりやすくなります。

コミット

commit 9b976f5f03689e65c8f58e9b3de94e0d7f7fe072
Author: Russ Cox <rsc@golang.org>
Date:   Mon May 12 11:59:55 2014 -0400

    cmd/gc: record line number for auto-generated wrappers as <autogenerated>:1
    
    Before we used line 1 of the first source file.
    This should be clearer.
    
    Fixes #4388.
    
    LGTM=iant
    R=golang-codereviews, iant
    CC=golang-codereviews
    https://golang.org/cl/92250044

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

https://github.com/golang/go/commit/9b976f5f03689e65c8f58e9b3de94e0d7f7fe072

元コミット内容

cmd/gc: record line number for auto-generated wrappers as <autogenerated>:1

Before we used line 1 of the first source file.
This should be clearer.

Fixes #4388.

変更の背景

Go言語のコンパイラは、特定の状況下で「ラッパー関数」と呼ばれるコードを自動的に生成します。例えば、インターフェースのメソッド呼び出しや、特定の最適化のために、コンパイラがユーザーが書いたコードとは別に、内部的に新しい関数を作成することがあります。

このコミット以前は、これらの自動生成されたラッパー関数が実行時にエラーを発生させたり、スタックトレースに現れたりした場合、その行番号は「最初のソースファイルの1行目」として報告されていました。これは、実際のソースコードのどこにも対応しないため、デバッグや問題の特定を非常に困難にしていました。開発者は、エラーがどこから発生したのか、それが自動生成されたコードによるものなのか、それとも自身のコードによるものなのかを区別することができませんでした。

この問題を解決し、自動生成コードの出所をより明確にするために、このコミットでは、自動生成されたラッパー関数の行番号を特別な識別子である<autogenerated>:1として記録するように変更されました。これにより、開発者はスタックトレースを見た際に、そのフレームがコンパイラによって自動生成されたものであることを一目で認識できるようになり、デバッグの効率が向上します。コミットメッセージにあるFixes #4388は、この問題がGoの内部バグトラッカーで追跡されていたことを示しています。

前提知識の解説

  • Goの自動生成ラッパー関数: Goコンパイラ(cmd/gc)は、特定の言語機能(例: インターフェースの実装、メソッド値、遅延呼び出しなど)を効率的に処理するために、実行時に内部的な「ラッパー関数」を生成することがあります。これらの関数は、開発者が直接記述するものではなく、コンパイラが生成する中間コードの一部です。
  • 行番号とデバッグ: プログラムの実行中にエラーが発生した場合、スタックトレースには通常、エラーが発生したファイル名と行番号が含まれます。これは、問題の場所を特定し、デバッグを行う上で非常に重要な情報です。
  • runtime.Caller: Goの標準ライブラリruntimeパッケージには、Callerという関数があります。これは、現在のゴルーチンのスタックトレースを調べ、指定された呼び出しスタックのフレームに対応するファイル名、行番号、関数名などの情報を取得するために使用されます。このコミットのテストコードでも、この関数が使用され、自動生成コードの行番号が正しく報告されているかを確認しています。
  • src/cmd/gc: Goコンパイラのソースコードがあるディレクトリです。Goのソースコードを機械語に変換する主要なツールであり、このコミットでは、コンパイラが自動生成コードの行番号情報をどのように埋め込むかを変更しています。
  • src/liblink: Goリンカのソースコードがあるディレクトリです。リンカは、コンパイラによって生成されたオブジェクトファイルやライブラリを結合し、実行可能なバイナリを作成する役割を担います。このコミットでは、リンカが<autogenerated>のような特殊なファイル名を認識できるように変更が加えられています。

技術的詳細

この変更は主にGoコンパイラ(src/cmd/gc)とリンカ(src/liblink)の2つの部分に影響を与えます。

  1. コンパイラ側 (src/cmd/gc/subr.c): genwrapper関数は、Goコンパイラ内でラッパーコードを生成する役割を担っています。この関数内で、自動生成されるコードの行番号情報が設定されます。 変更前は、lineno = 1;という行があり、これは単に現在の行番号を1に設定していました。これは、多くの場合、最初のソースファイルの1行目として解釈されていました。 変更後、以下の3行が追加されました。

    lexlineno++;
    lineno = lexlineno;
    linehist("<autogenerated>", 0, 0);
    
    • lexlineno++: lexlinenoは、コンパイラが現在処理しているソースコードの論理的な行番号を追跡するために使用される変数です。これをインクリメントすることで、自動生成コードに新しいユニークな行番号を割り当てます。
    • lineno = lexlineno;: 現在の行番号をlexlinenoの値に設定します。
    • linehist("<autogenerated>", 0, 0);: この関数は、コンパイラが生成するデバッグ情報に行番号とファイル名のマッピングを記録するために使用されます。ここでファイル名として<autogenerated>という特殊な文字列を渡すことで、このコードが自動生成されたものであることを明示的に示します。
  2. リンカ側 (src/liblink/obj.c): linkgetline関数は、リンカがデバッグ情報からファイル名と行番号を取得する際に使用されます。この関数は、ファイルパスが絶対パスであるかどうかを判断するために、パスの最初の文字をチェックしていました。 変更前は、Unix系システムでは/、Windowsではドライブレター(例: C:)で始まるかどうかをチェックしていました。 変更後、以下の条件が追加されました。

    || file[0] == '<'
    

    これにより、ファイルパスの最初の文字が<である場合(つまり、<autogenerated>のような特殊なファイル名である場合)も、絶対パスとして扱われるようになりました。これは、リンカがコンパイラによって埋め込まれた<autogenerated>という特殊なファイル名を正しく解釈し、デバッグ情報として利用できるようにするために必要です。

  3. テストコード (test/fixedbugs/issue4388.go): このコミットには、issue4388.goという新しいテストファイルが追加されています。このテストは、変更が正しく機能していることを検証するために設計されています。 テストコードでは、io.Closerインターフェースを埋め込んだ構造体Tを定義し、そのCloseメソッドを呼び出す際にパニックを発生させます。そして、checkLine関数内でruntime.Caller(n)を使用してスタックトレースの情報を取得し、ファイル名が<autogenerated>であり、行番号が1であることを検証しています。これは、自動生成されたラッパー関数が期待通りに<autogenerated>:1として報告されることを確認するためのものです。

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

src/cmd/gc/subr.c

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -2498,7 +2498,9 @@ genwrapper(Type *rcvr, Type *method, Sym *newnam, int iface)\n 		print("genwrapper rcvrtype=%T method=%T newnam=%S\n",\n 			rcvr, method, newnam);\n 
-	lineno = 1;	// less confusing than end of input
+	lexlineno++;
+	lineno = lexlineno;
+	linehist("<autogenerated>", 0, 0);
 
 	dclcontext = PEXTERN;
 	markdcl();

src/liblink/obj.c

--- a/src/liblink/obj.c
+++ b/src/liblink/obj.c
@@ -183,7 +183,7 @@ linkgetline(Link *ctxt, int32 line, LSym **f, int32 *l)\n 		file = a[n].incl->name;\n 		dlno = a[n].idel-1;\n 	}\n-	if((!ctxt->windows && file[0] == '/') || (ctxt->windows && file[1] == ':'))\n+	if((!ctxt->windows && file[0] == '/') || (ctxt->windows && file[1] == ':') || file[0] == '<')\n 		snprint(buf, sizeof buf, "%s", file);\n 	else\n 		snprint(buf, sizeof buf, "%s/%s", ctxt->pathname, file);\

test/fixedbugs/issue4388.go (新規追加)

// run

// Copyright 2014 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"io"
	"runtime"
)

type T struct {
	io.Closer
}

func f1() {
	// The 4 here and below depends on the number of internal runtime frames
	// that sit between a deferred function called during panic and
	// the original frame. If that changes, this test will start failing and
	// the number here will need to be updated.
	defer checkLine(4)
	var t *T
	var c io.Closer = t
	c.Close()
}

func f2() {
	defer checkLine(4)
	var t T
	var c io.Closer = t
	c.Close()
}

func main() {
	f1()
	f2()
}

func checkLine(n int) {
	if err := recover(); err == nil {
		panic("did not panic")
	}
	_, file, line, _ := runtime.Caller(n)
	if file != "<autogenerated>" || line != 1 {
		panic(fmt.Sprintf("expected <autogenerated>:1 have %s:%d", file, line))
	}
}

コアとなるコードの解説

  • src/cmd/gc/subr.c の変更: genwrapper関数は、コンパイラがラッパーコードを生成する際に呼び出されます。 lexlineno++;lineno = lexlineno;は、自動生成されるコードに新しい行番号を割り当て、コンパイラの内部的な行番号カウンタを更新します。 最も重要な変更はlinehist("<autogenerated>", 0, 0);です。linehist関数は、コンパイラが生成するオブジェクトファイルにデバッグ情報(ファイル名と行番号のマッピング)を記録するために使用されます。ここでファイル名として<autogenerated>という特殊な文字列を渡すことで、このコードがユーザーが書いたソースファイルではなく、コンパイラによって自動生成されたものであることを明示的に示します。これにより、デバッグツールやスタックトレースがこの情報を利用できるようになります。

  • src/liblink/obj.c の変更: linkgetline関数は、リンカがデバッグ情報からファイル名と行番号を読み取る際に使用されます。 file[0] == '<'という条件が追加されたことで、リンカはファイルパスの最初の文字が<である場合(例: <autogenerated>)を特殊なケースとして認識し、それを絶対パスとして扱います。これにより、リンカはコンパイラが埋め込んだ<autogenerated>というファイル名を正しく解釈し、実行可能なバイナリのデバッグ情報に含めることができます。

  • test/fixedbugs/issue4388.go の解説: このテストは、runtime.Caller関数を使用して、パニック発生時のスタックトレースからファイル名と行番号を取得し、それが期待通りに<autogenerated>:1となっていることを検証します。 f1()f2()関数は、それぞれポインタ型と値型のT構造体をio.Closerインターフェースに代入し、そのClose()メソッドを呼び出します。T構造体はio.Closerを埋め込んでいるため、Close()メソッドはインターフェースのメソッドとして呼び出され、この際にコンパイラによってラッパー関数が生成される可能性があります。 checkLine(n int)関数は、defer文によってパニック発生時に呼び出されます。この関数内でruntime.Caller(n)を呼び出し、スタックトレースのn番目のフレームのファイル名と行番号を取得します。そして、取得したファイル名が<autogenerated>であり、行番号が1であることをアサートします。これにより、コンパイラとリンカの変更が正しく機能し、自動生成コードの行番号が意図した通りに報告されていることが確認されます。

関連リンク

  • Go Gerrit Change: https://golang.org/cl/92250044
  • Go Issue #4388: このコミットが修正したとされるGoの内部バグトラッカーの課題番号です。公開されているGitHubリポジトリのIssueとは異なる可能性がありますが、コミットメッセージに明記されています。

参考にした情報源リンク

  • Go言語のソースコード (特にsrc/cmd/gcsrc/liblinkの関連ファイル)
  • Go言語のruntimeパッケージのドキュメント (特にruntime.Callerについて)
  • Go言語のコンパイラとリンカに関する一般的な知識
  • Go issue #51774 (関連する可能性のあるGoのIssue。ただし、このコミットが直接修正したIssue #4388とは異なる可能性があります。)