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

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

このコミットは、Go言語のツールチェインの一部である cmd/pack において、pkgdef (パッケージ定義) ファイル内の非常に長い行を適切に処理できるようにするための修正です。具体的には、bufio.Scanner の代わりに bufio.Reader を使用することで、行の長さに起因する問題を解決し、堅牢性を向上させています。

コミット

commit 6524310770649b6aa9786711edd2f6eeab4ba61a
Author: Ian Lance Taylor <iant@golang.org>
Date:   Thu Apr 17 11:47:12 2014 -0700

    cmd/pack: handle very long lines in pkgdef
    
    LGTM=rsc, bradfitz
    R=golang-codereviews, rsc, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/88170049
---
 src/cmd/pack/pack.go      |  20 +++++----\n src/cmd/pack/pack_test.go | 102 +++++++++++++++++++++++++++++++++++++++-------\n 2 files changed, 98 insertions(+), 24 deletions(-)\n

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

https://github.com/golang/go/commit/6524310770649b6aa9786711edd2f6eeab4ba61a

元コミット内容

cmd/pack: handle very long lines in pkgdef

このコミットは、cmd/pack ツールが pkgdef ファイル内の非常に長い行を処理できるようにするためのものです。

変更の背景

Go言語のコンパイルプロセスにおいて、パッケージのメタデータは pkgdef と呼ばれる形式で格納されます。この pkgdef ファイルは、Goのオブジェクトファイル(.a ファイルなど)内に埋め込まれることがあります。特に、構造体のフィールドに付与されるタグ(例: json:"field")が非常に多くなると、その定義行が極端に長くなる可能性があります。

従来の cmd/pack の実装では、pkgdef ファイルのヘッダー部分を読み込む際に bufio.Scanner を使用していました。bufio.Scanner は、デフォルトで読み込む行の長さに制限(bufio.MaxScanTokenSize、通常は64KB)があります。この制限を超える非常に長い行が出現した場合、bufio.Scannerbufio.ErrTooLong エラーを返し、それ以上の読み込みを停止してしまいます。

この問題は、特に大規模な構造体定義や、多数のタグを持つ構造体を使用するGoプログラムをコンパイルする際に顕在化し、コンパイルエラーや予期せぬ動作を引き起こす可能性がありました。このコミットは、このような長い行が存在しても cmd/pack が正しく pkgdef を読み込めるようにするために導入されました。

前提知識の解説

  • cmd/pack: Go言語のツールチェインの一部であり、主にGoのアーカイブファイル(.a ファイル)を操作するために使用される内部ツールです。Goのコンパイルプロセスにおいて、コンパイルされたパッケージのオブジェクトファイルをアーカイブにまとめる役割などを担います。
  • pkgdef: Goのパッケージ定義の内部形式です。コンパイルされたGoのパッケージに関するメタデータ(型情報、関数シグネチャなど)がこの形式で記述され、Goのオブジェクトファイル内に埋め込まれます。__.PKGDEF というマジック文字列で始まるセクションとして識別されます。
  • bufio.Scanner: Goの標準ライブラリ bufio パッケージが提供する型で、入力ストリームをトークン(デフォルトでは行)に分割して読み込むための便利なインターフェースを提供します。しかし、前述の通り、デフォルトのトークンサイズに制限があります。
  • bufio.Reader: bufio パッケージが提供する別の型で、バッファリングされたI/O操作を提供します。Scanner と異なり、行の長さに明示的な制限はなく、ReadBytesReadString などのメソッドを使用して、指定されたデリミタ(例: 改行文字)までデータを読み込むことができます。これにより、非常に長い行でも柔軟に処理することが可能です。
  • Goのコンパイルプロセス: Goのソースコードは、まずコンパイラ(go tool compile)によってオブジェクトファイルにコンパイルされます。これらのオブジェクトファイルは、cmd/pack のようなツールによってアーカイブファイル(.a)にまとめられ、最終的にリンカ(go tool link)によって実行可能ファイルが生成されます。pkgdef はこのプロセスの中で重要な役割を果たします。

技術的詳細

このコミットの核心は、src/cmd/pack/pack.go 内の readPkgdef 関数におけるファイル読み込み方法の変更です。

変更前は bufio.NewScanner(f) を使用し、scan.Scan()scan.Text() で行を読み込んでいました。この方法では、bufio.Scanner の内部バッファサイズ(デフォルト64KB)を超える行が出現すると、scan.Scan()false を返し、エラー(bufio.ErrTooLong)が発生していました。

変更後は、bufio.NewReader(f) を使用し、rbuf.ReadBytes('\n') で改行文字までをバイトスライスとして読み込むように変更されました。bufio.Readerbufio.Scanner のような行の長さ制限を持たないため、非常に長い行でも問題なく読み込むことができます。

また、読み込んだ行が ! だけで構成されているかをチェックする部分も、line == "!" から bytes.Equal(line, []byte("!\\n")) に変更されています。これは ReadBytes が改行文字を含むバイトスライスを返すため、比較対象も改行文字を含むバイトスライスにする必要があるためです。

この変更により、pkgdef 内に非常に長い行(特に、多数のフィールドタグを持つ構造体定義など)が含まれていても、cmd/pack がそのファイルを正常に解析できるようになり、Goのコンパイルプロセスの堅牢性が向上しました。

新しいテストケース TestLargeDefs は、この問題が実際に発生しうるシナリオを再現しています。このテストでは、10000個のフィールドを持ち、それぞれのフィールドタグが100個のキー-バリューペアを含む非常に大きな構造体を定義したGoファイルを作成し、cmd/pack がこれを正しく処理できることを検証しています。

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

src/cmd/pack/pack.goreadPkgdef 関数:

--- a/src/cmd/pack/pack.go
+++ b/src/cmd/pack/pack.go
@@ -406,20 +406,22 @@ func readPkgdef(file string) (data []byte, err error) {
 	// Read from file, collecting header for __.PKGDEF.
 	// The header is from the beginning of the file until a line
 	// containing just "!". The first line must begin with "go object ".
-	var buf bytes.Buffer
-	scan := bufio.NewScanner(f)
-	for scan.Scan() {
-		line := scan.Text()
-		if buf.Len() == 0 && !strings.HasPrefix(line, "go object ") {
+	rbuf := bufio.NewReader(f)
+	var wbuf bytes.Buffer
+	for {
+		line, err := rbuf.ReadBytes('\n')
+		if err != nil {
+			return nil, err
+		}
+		if wbuf.Len() == 0 && !bytes.HasPrefix(line, []byte("go object ")) {
 			return nil, errors.New("not a Go object file")
 		}
-		if line == "!" {
+		if bytes.Equal(line, []byte("!\\n")) {
 			break
 		}
-		buf.WriteString(line)
-		buf.WriteString("\n")
+		wbuf.Write(line)
 	}
-	return buf.Bytes(), nil
+	return wbuf.Bytes(), nil
 }

src/cmd/pack/pack_test.go に追加された TestLargeDefs 関数:

--- a/src/cmd/pack/pack_test.go
+++ b/src/cmd/pack/pack_test.go
@@ -198,17 +198,97 @@ func TestHello(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	char := findChar(t, dir)
+
 	run := func(args ...string) string {
-		cmd := exec.Command(args[0], args[1:]...)
-		cmd.Dir = dir
-		out, err := cmd.CombinedOutput()
+		return doRun(t, dir, args...)
+	}
+
+	run("go", "build", "cmd/pack") // writes pack binary to dir
+	run("go", "tool", char+"g", "hello.go")
+	run("./pack", "grc", "hello.a", "hello."+char)
+	run("go", "tool", char+"l", "-o", "a.out", "hello.a")
+	out := run("./a.out")
+	if out != "hello world\\n" {
+		t.Fatal("incorrect output: %q, want %q", out, "hello world\\n")
+	}
+}
+
+// Test that pack works with very long lines in PKGDEF.
+func TestLargeDefs(t *testing.T) {
+	dir := tmpDir(t)
+	defer os.RemoveAll(dir)
+	large := filepath.Join(dir, "large.go")
+	f, err := os.Create(large)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	printf := func(format string, args ...interface{}) {
+		_, err := fmt.Fprintf(f, format, args...)
 	if err != nil {
-		t.Fatalf("%v: %v\\n%s", args, err, string(out))
+		t.Fatalf("Writing to %s: %v", large, err)
 		}
-		return string(out)
 	}
 
-	out := run("go", "env")
+	printf("package large\\n\\ntype T struct {\\n")
+	for i := 0; i < 10000; i++ {
+		printf("f%d int `tag:\\\"\", i)
+		for j := 0; j < 100; j++ {
+			printf("t%d=%d,\", j, j)
+		}
+		printf("\\\"`\\n")
+	}
+	printf("}\\n")
+	if err = f.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+	main := filepath.Join(dir, "main.go")
+	prog := `
+		package main
+		import "./large"
+		var V large.T
+		func main() {
+			println("ok")
+		}
+	`
+	err = ioutil.WriteFile(main, []byte(prog), 0666)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	char := findChar(t, dir)
+
+	run := func(args ...string) string {
+		return doRun(t, dir, args...)
+	}
+
+	run("go", "build", "cmd/pack") // writes pack binary to dir
+	run("go", "tool", char+"g", "large.go")
+	run("./pack", "grc", "large.a", "large."+char)
+	run("go", "tool", char+"g", "main.go")
+	run("go", "tool", char+"l", "-o", "a.out", "main."+char)
+	out := run("./a.out")
+	if out != "ok\\n" {
+		t.Fatal("incorrect output: %q, want %q", out, "ok\\n")
+	}
+}
+
+// doRun runs a program in a directory and returns the output.
+func doRun(t *testing.T, dir string, args ...string) string {
+	cmd := exec.Command(args[0], args[1:]...)
+	cmd.Dir = dir
+	out, err := cmd.CombinedOutput()
+	if err != nil {
+		t.Fatalf("%v: %v\\n%s", args, err, string(out))
+	}
+	return string(out)
+}
+
+// findChar returns the architecture character for the go command.
+func findChar(t *testing.T, dir string) string {
+	out := doRun(t, dir, "go", "env")
 	re, err := regexp.Compile(`\s*GOCHAR=['"]?(\w)['"]?`)
 	if err != nil {
 		t.Fatal(err)
@@ -217,15 +297,7 @@ func TestHello(t *testing.T) {
 	if fields == nil {
 		t.Fatal("cannot find GOCHAR in 'go env' output:\\n", out)
 	}\n-	char := fields[1]\n-\trun("go", "build", "cmd/pack") // writes pack binary to dir\n-\trun("go", "tool", char+"g", "hello.go")\n-\trun("./pack", "grc", "hello.a", "hello."+char)\n-\trun("go", "tool", char+"l", "-o", "a.out", "hello.a")\n-\tout = run("./a.out")\n-\tif out != "hello world\\n" {\n-\t\tt.Fatal("incorrect output: %q, want %q", out, "hello world\\n")\n-\t}\n+	return fields[1]\n }\n```

## コアとなるコードの解説

`readPkgdef` 関数は、Goのオブジェクトファイルから `__.PKGDEF` ヘッダーを読み取る役割を担っています。

1.  **`bufio.NewReader(f)` の導入**:
    *   以前は `bufio.NewScanner(f)` を使用していましたが、これは行の長さに制限があるため、非常に長い行を処理できませんでした。
    *   `bufio.NewReader(f)` を使用することで、バッファリングされた読み込みが可能になり、行の長さに依存しない柔軟な読み込みが可能になります。

2.  **`rbuf.ReadBytes('\n')` による行読み込み**:
    *   `bufio.Reader` の `ReadBytes('\n')` メソッドは、指定されたデリミタ(ここでは改行文字 `\n`)が出現するまでバイトを読み込み、そのバイトスライスを返します。これにより、行の長さに関わらず、完全な行を読み込むことができます。
    *   `ReadBytes` はデリミタ自体も返されるため、読み込んだ `line` バイトスライスには改行文字が含まれます。

3.  **`bytes.HasPrefix` と `bytes.Equal` による比較**:
    *   `ReadBytes` がバイトスライスを返すため、文字列比較 (`strings.HasPrefix`, `==`) の代わりにバイトスライス比較 (`bytes.HasPrefix`, `bytes.Equal`) を使用するように変更されました。
    *   特に、ヘッダーの終了を示す `!` 行のチェックは、`bytes.Equal(line, []byte("!\\n"))` となり、読み込んだバイトスライスが `!` と改行文字で構成されているかを正確に比較します。

4.  **`wbuf bytes.Buffer` への書き込み**:
    *   読み込んだ行は `wbuf bytes.Buffer` に書き込まれ、最終的に `wbuf.Bytes()` として返されます。これにより、`pkgdef` ヘッダーのデータが正しく収集されます。

これらの変更により、`readPkgdef` 関数は `pkgdef` ファイル内の非常に長い行を堅牢に処理できるようになり、Goのコンパイルプロセスにおける潜在的な問題を解消しました。

## 関連リンク

*   Go CL 88170049: [https://golang.org/cl/88170049](https://golang.org/cl/88170049)

## 参考にした情報源リンク

*   `bufio.Scanner` のドキュメント: [https://pkg.go.dev/bufio#Scanner](https://pkg.go.dev/bufio#Scanner)
*   `bufio.Reader` のドキュメント: [https://pkg.go.dev/bufio#Reader](https://pkg.go.dev/bufio#Reader)
*   `bufio.Scanner` の行長制限に関する情報:
    *   [https://stackademic.com/blog/golang-bufio-scanner-line-length-limit](https://stackademic.com/blog/golang-bufio-scanner-line-length-limit)
    *   [https://stackoverflow.com/questions/8757389/reading-a-file-line-by-line-in-go](https://stackoverflow.com/questions/8757389/reading-a-file-line-by-line-in-go)
    *   [https://go.dev/src/bufio/scan.go](https://go.dev/src/bufio/scan.go) (Go source code for `bufio.Scanner`)
*   Go言語のパッケージとツールチェインに関する一般的な情報 (Web検索結果より)
    *   [https://go.dev/doc/](https://go.dev/doc/)
    *   [https://shichao.io/2012/10/27/go-package-management.html](https://shichao.io/2012/10/27/go-package-management.html)```markdown
# [インデックス 19198] ファイルの概要

このコミットは、Go言語のツールチェインの一部である `cmd/pack` において、`pkgdef` (パッケージ定義) ファイル内の非常に長い行を適切に処理できるようにするための修正です。具体的には、`bufio.Scanner` の代わりに `bufio.Reader` を使用することで、行の長さに起因する問題を解決し、堅牢性を向上させています。

## コミット

commit 6524310770649b6aa9786711edd2f6eeab4ba61a Author: Ian Lance Taylor iant@golang.org Date: Thu Apr 17 11:47:12 2014 -0700

cmd/pack: handle very long lines in pkgdef

LGTM=rsc, bradfitz
R=golang-codereviews, rsc, bradfitz
CC=golang-codereviews
https://golang.org/cl/88170049

src/cmd/pack/pack.go | 20 +++++----\n src/cmd/pack/pack_test.go | 102 +++++++++++++++++++++++++++++++++++++++-------\n 2 files changed, 98 insertions(+), 24 deletions(-)\n


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

[https://github.com/golang/go/commit/6524310770649b6aa9786711edd2f6eeab4ba61a](https://github.com/golang/go/commit/6524310770649b6aa9786711edd2f6eeab4ba61a)

## 元コミット内容

`cmd/pack: handle very long lines in pkgdef`

このコミットは、`cmd/pack` ツールが `pkgdef` ファイル内の非常に長い行を処理できるようにするためのものです。

## 変更の背景

Go言語のコンパイルプロセスにおいて、パッケージのメタデータは `pkgdef` と呼ばれる形式で格納されます。この `pkgdef` ファイルは、Goのオブジェクトファイル(`.a` ファイルなど)内に埋め込まれることがあります。特に、構造体のフィールドに付与されるタグ(例: `json:"field"`)が非常に多くなると、その定義行が極端に長くなる可能性があります。

従来の `cmd/pack` の実装では、`pkgdef` ファイルのヘッダー部分を読み込む際に `bufio.Scanner` を使用していました。`bufio.Scanner` は、デフォルトで読み込む行の長さに制限(`bufio.MaxScanTokenSize`、通常は64KB)があります。この制限を超える非常に長い行が出現した場合、`bufio.Scanner` は `bufio.ErrTooLong` エラーを返し、それ以上の読み込みを停止してしまいます。

この問題は、特に大規模な構造体定義や、多数のタグを持つ構造体を使用するGoプログラムをコンパイルする際に顕在化し、コンパイルエラーや予期せぬ動作を引き起こす可能性がありました。このコミットは、このような長い行が存在しても `cmd/pack` が正しく `pkgdef` を読み込めるようにするために導入されました。

## 前提知識の解説

*   **`cmd/pack`**: Go言語のツールチェインの一部であり、主にGoのアーカイブファイル(`.a` ファイル)を操作するために使用される内部ツールです。Goのコンパイルプロセスにおいて、コンパイルされたパッケージのオブジェクトファイルをアーカイブにまとめる役割などを担います。
*   **`pkgdef`**: Goのパッケージ定義の内部形式です。コンパイルされたGoのパッケージに関するメタデータ(型情報、関数シグネチャなど)がこの形式で記述され、Goのオブジェクトファイル内に埋め込まれます。`__.PKGDEF` というマジック文字列で始まるセクションとして識別されます。
*   **`bufio.Scanner`**: Goの標準ライブラリ `bufio` パッケージが提供する型で、入力ストリームをトークン(デフォルトでは行)に分割して読み込むための便利なインターフェースを提供します。しかし、前述の通り、デフォルトのトークンサイズに制限があります。
*   **`bufio.Reader`**: `bufio` パッケージが提供する別の型で、バッファリングされたI/O操作を提供します。`Scanner` と異なり、行の長さに明示的な制限はなく、`ReadBytes` や `ReadString` などのメソッドを使用して、指定されたデリミタ(例: 改行文字)までデータを読み込むことができます。これにより、非常に長い行でも柔軟に処理することが可能です。
*   **Goのコンパイルプロセス**: Goのソースコードは、まずコンパイラ(`go tool compile`)によってオブジェクトファイルにコンパイルされます。これらのオブジェクトファイルは、`cmd/pack` のようなツールによってアーカイブファイル(`.a`)にまとめられ、最終的にリンカ(`go tool link`)によって実行可能ファイルが生成されます。`pkgdef` はこのプロセスの中で重要な役割を果たします。

## 技術的詳細

このコミットの核心は、`src/cmd/pack/pack.go` 内の `readPkgdef` 関数におけるファイル読み込み方法の変更です。

変更前は `bufio.NewScanner(f)` を使用し、`scan.Scan()` と `scan.Text()` で行を読み込んでいました。この方法では、`bufio.Scanner` の内部バッファサイズ(デフォルト64KB)を超える行が出現すると、`scan.Scan()` が `false` を返し、エラー(`bufio.ErrTooLong`)が発生していました。

変更後は、`bufio.NewReader(f)` を使用し、`rbuf.ReadBytes('\n')` で改行文字までをバイトスライスとして読み込むように変更されました。`bufio.Reader` は `bufio.Scanner` のような行の長さ制限を持たないため、非常に長い行でも問題なく読み込むことができます。

また、読み込んだ行が `!` だけで構成されているかをチェックする部分も、`line == "!"` から `bytes.Equal(line, []byte("!\\n"))` に変更されています。これは `ReadBytes` が改行文字を含むバイトスライスを返すため、比較対象も改行文字を含むバイトスライスにする必要があるためです。

この変更により、`pkgdef` 内に非常に長い行(特に、多数のフィールドタグを持つ構造体定義など)が含まれていても、`cmd/pack` がそのファイルを正常に解析できるようになり、Goのコンパイルプロセスの堅牢性が向上しました。

新しいテストケース `TestLargeDefs` は、この問題が実際に発生しうるシナリオを再現しています。このテストでは、10000個のフィールドを持ち、それぞれのフィールドタグが100個のキー-バリューペアを含む非常に大きな構造体を定義したGoファイルを作成し、`cmd/pack` がこれを正しく処理できることを検証しています。

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

`src/cmd/pack/pack.go` の `readPkgdef` 関数:

```diff
--- a/src/cmd/pack/pack.go
+++ b/src/cmd/pack/pack.go
@@ -406,20 +406,22 @@ func readPkgdef(file string) (data []byte, err error) {
 	// Read from file, collecting header for __.PKGDEF.
 	// The header is from the beginning of the file until a line
 	// containing just "!". The first line must begin with "go object ".
-	var buf bytes.Buffer
-	scan := bufio.NewScanner(f)
-	for scan.Scan() {
-		line := scan.Text()
-		if buf.Len() == 0 && !strings.HasPrefix(line, "go object ") {
+	rbuf := bufio.NewReader(f)
+	var wbuf bytes.Buffer
+	for {
+		line, err := rbuf.ReadBytes('\n')
+		if err != nil {
+			return nil, err
+		}
+		if wbuf.Len() == 0 && !bytes.HasPrefix(line, []byte("go object ")) {
 			return nil, errors.New("not a Go object file")
 		}
-		if line == "!" {
+		if bytes.Equal(line, []byte("!\\n")) {
 			break
 		}
-		buf.WriteString(line)
-		buf.WriteString("\n")
+		wbuf.Write(line)
 	}
-	return buf.Bytes(), nil
+	return wbuf.Bytes(), nil
 }

src/cmd/pack/pack_test.go に追加された TestLargeDefs 関数:

--- a/src/cmd/pack/pack_test.go
+++ b/src/cmd/pack/pack_test.go
@@ -198,17 +198,97 @@ func TestHello(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	char := findChar(t, dir)
+
 	run := func(args ...string) string {
-		cmd := exec.Command(args[0], args[1:]...)
-		cmd.Dir = dir
-		out, err := cmd.CombinedOutput()
+		return doRun(t, dir, args...)
+	}
+
+	run("go", "build", "cmd/pack") // writes pack binary to dir
+	run("go", "tool", char+"g", "hello.go")
+	run("./pack", "grc", "hello.a", "hello."+char)
+	run("go", "tool", char+"l", "-o", "a.out", "hello.a")
+	out := run("./a.out")
+	if out != "hello world\\n" {
+		t.Fatal("incorrect output: %q, want %q", out, "hello world\\n")
+	}
+}
+
+// Test that pack works with very long lines in PKGDEF.
+func TestLargeDefs(t *testing.T) {
+	dir := tmpDir(t)
+	defer os.RemoveAll(dir)
+	large := filepath.Join(dir, "large.go")
+	f, err := os.Create(large)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	printf := func(format string, args ...interface{}) {
+		_, err := fmt.Fprintf(f, format, args...)
 	if err != nil {
-		t.Fatalf("%v: %v\\n%s", args, err, string(out))
+		t.Fatalf("Writing to %s: %v", large, err)
 		}
-		return string(out)
 	}
 
-	out := run("go", "env")
+	printf("package large\\n\\ntype T struct {\\n")
+	for i := 0; i < 10000; i++ {
+		printf("f%d int `tag:\\\"\", i)
+		for j := 0; j < 100; j++ {
+			printf("t%d=%d,\", j, j)
+		}
+		printf("\\\"`\\n")
+	}
+	printf("}\\n")
+	if err = f.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+	main := filepath.Join(dir, "main.go")
+	prog := `
+		package main
+		import "./large"
+		var V large.T
+		func main() {
+			println("ok")
+		}
+	`
+	err = ioutil.WriteFile(main, []byte(prog), 0666)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	char := findChar(t, dir)
+
+	run := func(args ...string) string {
+		return doRun(t, dir, args...)
+	}
+
+	run("go", "build", "cmd/pack") // writes pack binary to dir
+	run("go", "tool", char+"g", "large.go")
+	run("./pack", "grc", "large.a", "large."+char)
+	run("go", "tool", char+"g", "main.go")
+	run("go", "tool", char+"l", "-o", "a.out", "main."+char)
+	out := run("./a.out")
+	if out != "ok\\n" {
+		t.Fatal("incorrect output: %q, want %q", out, "ok\\n")
+	}
+}
+
+// doRun runs a program in a directory and returns the output.
+func doRun(t *testing.T, dir string, args ...string) string {
+	cmd := exec.Command(args[0], args[1:]...)
+	cmd.Dir = dir
+	out, err := cmd.CombinedOutput()
+	if err != nil {
+		t.Fatalf("%v: %v\\n%s", args, err, string(out))
+	}
+	return string(out)
+}
+
+// findChar returns the architecture character for the go command.
+func findChar(t *testing.T, dir string) string {
+	out := doRun(t, dir, "go", "env")
 	re, err := regexp.Compile(`\s*GOCHAR=['"]?(\w)['"]?`)
 	if err != nil {
 		t.Fatal(err)
@@ -217,15 +297,7 @@ func TestHello(t *testing.T) {
 	if fields == nil {
 		t.Fatal("cannot find GOCHAR in 'go env' output:\\n", out)
 	}\n-	char := fields[1]\n-\trun("go", "build", "cmd/pack") // writes pack binary to dir\n-\trun("go", "tool", char+"g", "hello.go")\n-\trun("./pack", "grc", "hello.a", "hello."+char)\n-\trun("go", "tool", char+"l", "-o", "a.out", "hello.a")\n-\tout = run("./a.out")\n-\tif out != "hello world\\n" {\n-\t\tt.Fatal("incorrect output: %q, want %q", out, "hello world\\n")
-\t}\n+	return fields[1]\n }\n```

## コアとなるコードの解説

`readPkgdef` 関数は、Goのオブジェクトファイルから `__.PKGDEF` ヘッダーを読み取る役割を担っています。

1.  **`bufio.NewReader(f)` の導入**:
    *   以前は `bufio.NewScanner(f)` を使用していましたが、これは行の長さに制限があるため、非常に長い行を処理できませんでした。
    *   `bufio.NewReader(f)` を使用することで、バッファリングされた読み込みが可能になり、行の長さに依存しない柔軟な読み込みが可能になります。

2.  **`rbuf.ReadBytes('\n')` による行読み込み**:
    *   `bufio.Reader` の `ReadBytes('\n')` メソッドは、指定されたデリミタ(ここでは改行文字 `\n`)が出現するまでバイトを読み込み、そのバイトスライスを返します。これにより、行の長さに関わらず、完全な行を読み込むことができます。
    *   `ReadBytes` はデリミタ自体も返されるため、読み込んだ `line` バイトスライスには改行文字が含まれます。

3.  **`bytes.HasPrefix` と `bytes.Equal` による比較**:
    *   `ReadBytes` がバイトスライスを返すため、文字列比較 (`strings.HasPrefix`, `==`) の代わりにバイトスライス比較 (`bytes.HasPrefix`, `bytes.Equal`) を使用するように変更されました。
    *   特に、ヘッダーの終了を示す `!` 行のチェックは、`bytes.Equal(line, []byte("!\\n"))` となり、読み込んだバイトスライスが `!` と改行文字で構成されているかを正確に比較します。

4.  **`wbuf bytes.Buffer` への書き込み**:
    *   読み込んだ行は `wbuf bytes.Buffer` に書き込まれ、最終的に `wbuf.Bytes()` として返されます。これにより、`pkgdef` ヘッダーのデータが正しく収集されます。

これらの変更により、`readPkgdef` 関数は `pkgdef` ファイル内の非常に長い行を堅牢に処理できるようになり、Goのコンパイルプロセスにおける潜在的な問題を解消しました。

## 関連リンク

*   Go CL 88170049: [https://golang.org/cl/88170049](https://golang.org/cl/88170049)

## 参考にした情報源リンク

*   `bufio.Scanner` のドキュメント: [https://pkg.go.dev/bufio#Scanner](https://pkg.go.dev/bufio#Scanner)
*   `bufio.Reader` のドキュメント: [https://pkg.go.dev/bufio#Reader](https://pkg.go.dev/bufio#Reader)
*   `bufio.Scanner` の行長制限に関する情報:
    *   [https://stackademic.com/blog/golang-bufio-scanner-line-length-limit](https://stackademic.com/blog/golang-bufio-scanner-line-length-limit)
    *   [https://stackoverflow.com/questions/8757389/reading-a-file-line-by-line-in-go](https://stackoverflow.com/questions/8757389/reading-a-file-line-by-line-in-go)
    *   [https://go.dev/src/bufio/scan.go](https://go.dev/src/bufio/scan.go) (Go source code for `bufio.Scanner`)
*   Go言語のパッケージとツールチェインに関する一般的な情報 (Web検索結果より)
    *   [https://go.dev/doc/](https://go.dev/doc/)
    *   [https://shichao.io/2012/10/27/go-package-management.html](https://shichao.io/2012/10/27/go-package-management.html)