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

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

このコミットは、Go言語のツールチェインの一部である cmd/pack におけるログ出力のバグ修正です。具体的には、log.Fatal 関数がフォーマット文字列を正しく扱わないために発生する可能性のある問題を、log.Fatalf を使用することで修正しています。これにより、ログメッセージが意図した通りにフォーマットされ、プログラムの異常終了時に正確な情報が出力されるようになります。

コミット

cmd/pack: fix format string error in log message

Fixes #7693.

pack.go:347: possible formatting directive in Fatal call

LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/83310045

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

https://github.com/golang/go/commit/1aaea50c766a6bde8bebade9be745a371119082d

元コミット内容

cmd/pack: fix format string error in log message

このコミットは、cmd/pack ツール内のログメッセージにおけるフォーマット文字列の誤用を修正するものです。具体的には、pack.go の347行目で log.Fatal がフォーマットディレクティブを含む可能性のある引数と共に呼び出されており、これが潜在的な問題を引き起こす可能性がありました。この問題はGoのIssue #7693で報告されており、このコミットによって修正されました。

変更の背景

この変更の背景には、Go言語の標準ライブラリ log パッケージの Fatal 関数と Fatalf 関数の違いに関する理解があります。log.Fatal は引数をそのまま出力し、その後に os.Exit(1) を呼び出してプログラムを終了させます。一方、log.Fatalffmt.Printf と同様にフォーマット文字列とそれに続く引数を受け取り、フォーマットされた文字列を出力した後にプログラムを終了させます。

元のコードでは、log.Fatal("writing file: wrote %d bytes; file is size %d", n64, info.Size()) のように、log.Fatal にフォーマット文字列と可変引数を渡していました。しかし、log.Fatal はフォーマット文字列を解釈しないため、この呼び出しでは %d などのフォーマットディレクティブがそのまま文字列として出力されるか、あるいは予期せぬ動作を引き起こす可能性がありました。これは、開発者が意図したような整形されたエラーメッセージが表示されないという問題につながります。

この問題は、Goの静的解析ツールやリンターによって「possible formatting directive in Fatal call」として検出されることがあります。このコミットは、このような潜在的なバグを修正し、ログ出力の正確性と信頼性を向上させることを目的としています。

前提知識の解説

Go言語の log パッケージ

Go言語の標準ライブラリには、シンプルなロギング機能を提供する log パッケージが含まれています。このパッケージは、アプリケーションの実行中に情報を出力するために広く使用されます。

log パッケージの主な関数には以下のようなものがあります。

  • log.Print / log.Printf / log.Println: 標準エラー出力にメッセージを出力します。Printf はフォーマット文字列をサポートします。
  • log.Fatal / log.Fatalf / log.Fatalln: メッセージを出力し、その後に os.Exit(1) を呼び出してプログラムを終了させます。Fatalf はフォーマット文字列をサポートします。
  • log.Panic / log.Panicf / log.Panicln: メッセージを出力し、その後にパニックを発生させます。Panicf はフォーマット文字列をサポートします。

フォーマット文字列

Go言語では、fmt パッケージ(および log パッケージの *f 系関数)でC言語の printf に似たフォーマット文字列を使用できます。フォーマット文字列は、出力するデータの型や表示形式を指定するためのプレースホルダー(フォーマットディレクティブ)を含みます。

一般的なフォーマットディレクティブの例:

  • %d: 整数
  • %s: 文字列
  • %v: Goのデフォルトフォーマット(任意の型)
  • %T: 型名

cmd/pack ツール

cmd/pack は、Go言語の初期のツールチェインの一部であり、主にGoのアーカイブファイル(.a ファイル)を操作するために使用されていました。これは、コンパイルされたGoパッケージのオブジェクトファイルをまとめる役割を担っていました。現代のGoモジュールシステムでは、このツールの直接的な使用頻度は減っていますが、Goのビルドプロセスの一部として内部的に利用されることがあります。このツールは、Goのコンパイラやリンカと連携して、実行可能ファイルの生成に必要な中間ファイルを管理します。

技術的詳細

このコミットの技術的な核心は、log.Fatallog.Fatalf のセマンティクスの違いにあります。

  • log.Fatal(v ...interface{}): この関数は、可変引数 v を受け取り、それらをスペースで区切って標準エラー出力に書き込み、その後に os.Exit(1) を呼び出してプログラムを終了します。fmt.Print と同様の動作をします。つまり、引数として渡された文字列リテラルや変数の値をそのまま出力します。もし引数の中に %d のようなフォーマットディレクティブが含まれていても、それは単なる文字列の一部として扱われ、特別な意味を持ちません。

  • log.Fatalf(format string, v ...interface{}): この関数は、最初の引数としてフォーマット文字列 format を受け取り、それに続く可変引数 v をそのフォーマット文字列に従って整形し、標準エラー出力に書き込みます。その後、os.Exit(1) を呼び出してプログラムを終了します。これは fmt.Printf と同様の動作をします。つまり、%d などのフォーマットディレクティブは、対応する引数の値に置き換えられて出力されます。

元のコードの log.Fatal("writing file: wrote %d bytes; file is size %d", n64, info.Size()) という行では、開発者は n64info.Size() の値を %d の位置に挿入して出力することを意図していました。しかし、log.Fatal はフォーマット文字列を解釈しないため、この意図は達成されません。結果として、出力されるログメッセージは writing file: wrote %d bytes; file is size %d のように、プレースホルダーがそのまま表示されるか、あるいは引数の型とフォーマットディレクティブが一致しない場合に予期せぬエラーやパニックを引き起こす可能性がありました。

このコミットでは、この行を log.Fatalf("writing file: wrote %d bytes; file is size %d", n64, info.Size()) に変更することで、この問題を解決しています。log.Fatalf を使用することで、%d フォーマットディレクティブが正しく n64info.Size() の値に置き換えられ、期待通りの整形されたエラーメッセージがログに出力されるようになります。これにより、デバッグや問題の特定が容易になります。

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

--- a/src/cmd/pack/pack.go
+++ b/src/cmd/pack/pack.go
@@ -344,7 +344,7 @@ func (ar *Archive) addFile(fd FileLike) {
 		log.Fatal("writing file: ", err)
 	}\n \tif n64 != info.Size() {\n-\t\tlog.Fatal(\"writing file: wrote %d bytes; file is size %d\", n64, info.Size())\n+\t\tlog.Fatalf(\"writing file: wrote %d bytes; file is size %d\", n64, info.Size())\n \t}\n \tar.endFile()\
 }\n

コアとなるコードの解説

変更された行は src/cmd/pack/pack.go の347行目です。

元のコード:

log.Fatal("writing file: wrote %d bytes; file is size %d", n64, info.Size())

修正後のコード:

log.Fatalf("writing file: wrote %d bytes; file is size %d", n64, info.Size())

この変更は非常にシンプルですが、その影響は重要です。 log.Fatal から log.Fatalf への変更により、Goのロギングシステムがこのエラーメッセージを正しく解釈し、%d プレースホルダーを n64info.Size() の実際の数値に置き換えるようになります。これにより、例えば writing file: wrote 1024 bytes; file is size 2048 のような、人間が読みやすく、デバッグに役立つ正確なエラーメッセージが出力されるようになります。

この修正は、Goのコードベース全体で、ログ出力のベストプラクティスに従うことの重要性を示しています。フォーマット文字列を使用する場合は、必ず PrintfFatalf のようなフォーマットをサポートする関数を使用する必要があります。

関連リンク

参考にした情報源リンク

  • Go言語 log パッケージのドキュメント: https://pkg.go.dev/log
  • Go言語 fmt パッケージのドキュメント: https://pkg.go.dev/fmt
  • Go Code Review Comments - Error messages: https://go.dev/doc/effective_go#error-messages Goにおけるエラーメッセージの書き方に関する一般的なガイドライン。
  • Go CL 83310045: https://golang.org/cl/83310045 このコミットのGo Code Reviewシステムにおける変更リスト。
  • Dave Cheney's Blog: https://dave.cheney.net/ コミットの作者であるDave Cheney氏のブログ。Goに関する多くの洞察が含まれています。 (直接このコミットに関する記事があるわけではありませんが、Goのベストプラクティスを理解する上で参考になります。)
  • Goのツールチェインに関する情報: cmd/pack のようなGoの内部ツールに関する詳細な公式ドキュメントは少ないですが、Goのソースコードや関連するIssue、メーリングリストの議論から情報を得ることができます。