[インデックス 11694] ファイルの概要
コミット
commit 00134fe8ef917f17fc87076badbc54c086f74589
Author: Russ Cox <rsc@golang.org>
Date: Tue Feb 7 23:37:05 2012 -0500
fmt: diagnose invalid verb applied to pointer
Fixes #2851.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5644048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/00134fe8ef917f17fc87076badbc54c086f74589
元コミット内容
fmt: diagnose invalid verb applied to pointer
Fixes #2851.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5644048
変更の背景
このコミットは、Go言語の標準ライブラリであるfmt
パッケージにおいて、ポインタ型に対して不適切な書式指定動詞(verb)が使用された場合に、適切な診断メッセージを出力するように改善することを目的としています。具体的には、Go issue #2851で報告された問題に対応しています。
Go issue #2851は、「fmt
: accepts non strings/chars with verb %q
」というタイトルで、ポインタ型のような文字列や文字ではない値に対して、文字列をクォートして出力するための%q
動詞が適用された際に、予期せぬ出力やエラーが発生しないという問題点を指摘していました。本来、%q
は文字列やバイトスライスに適用されるべきであり、ポインタのような型に適用された場合は、開発者にその誤用を知らせるべきです。
この変更以前は、fmt
パッケージはポインタに対して不適切な動詞が使用されても、エラーを報告せずにデフォルトの書式で出力しようとするか、あるいは予期せぬ結果を招く可能性がありました。このコミットは、このような誤用を早期に検出し、開発者に明確なフィードバックを提供することで、デバッグの労力を削減し、より堅牢なコードの記述を促進します。
前提知識の解説
Go言語のfmt
パッケージ
fmt
パッケージは、Go言語における書式付きI/O(入出力)を実装するためのパッケージです。C言語のprintf
やscanf
に似た機能を提供し、様々なデータ型を整形して文字列として出力したり、文字列からデータを読み取ったりすることができます。
主な関数には以下のようなものがあります。
fmt.Print
,fmt.Println
,fmt.Printf
: 標準出力への出力fmt.Sprint
,fmt.Sprintln
,fmt.Sprintf
: 文字列への出力fmt.Fprint
,fmt.Fprintln
,fmt.Fprintf
: 指定されたio.Writer
への出力
書式指定動詞(Verbs)
fmt.Printf
などの書式付き関数では、出力する値の型や表示形式を指定するために「書式指定動詞(verb)」と呼ばれるプレースホルダーを使用します。動詞は%
で始まり、その後に文字が続きます。
本コミットに関連する主な動詞は以下の通りです。
%p
: ポインタのアドレスを16進数で表示します。0x
プレフィックスが付きます。%v
: 値のデフォルトの書式で表示します。構造体や配列など、様々な型に対応しています。%q
: Goの構文でクォートされた文字列リテラルとして表示します。非表示文字はエスケープされます。主に文字列型に適用されます。%s
: 文字列またはバイトスライスをそのまま表示します。%d
: 整数値を10進数で表示します。%x
,%X
: 整数値を16進数で表示します(%x
は小文字、%X
は大文字)。%b
: 整数値を2進数で表示します。%o
: 整数値を8進数で表示します。
reflect
パッケージ
reflect
パッケージは、Goプログラムの実行時に、変数や関数の型情報を動的に検査したり、値を操作したりするための機能を提供します。リフレクションは、ジェネリックなコードや、型が事前にわからないデータを扱う場合に特に有用です。
reflect.Value
: Goのあらゆる値のランタイム表現です。reflect.ValueOf(i interface{})
関数を使って、任意のインターフェース値からreflect.Value
を取得できます。reflect.Kind
:reflect.Value
が表す値の基本的な種類(例:Int
,String
,Struct
,Ptr
,Chan
,Func
,Map
,Slice
,UnsafePointer
など)を返します。reflect.Type
: Goの型を表します。reflect.TypeOf(i interface{})
関数を使って、任意のインターフェース値からreflect.Type
を取得できます。
fmt
パッケージは、内部でreflect
パッケージを使用して、引数として渡された値の型情報を動的に取得し、それに基づいて適切な書式設定ロジックを選択します。
Stringer
インターフェース
fmt
パッケージは、Stringer
インターフェースを実装する型に対して特別な扱いをします。Stringer
インターフェースは以下のように定義されています。
type Stringer interface {
String() string
}
ある型がString() string
メソッドを実装している場合、fmt
パッケージはその型の値を%v
や%s
などの動詞で出力する際に、そのString()
メソッドの戻り値を使用します。これにより、カスタムの文字列表現を提供できます。
技術的詳細
このコミットの主要な変更は、src/pkg/fmt/print.go
ファイル内のpp
構造体のfmtPointer
メソッドにあります。fmtPointer
メソッドは、reflect.Chan
, reflect.Func
, reflect.Map
, reflect.Ptr
, reflect.Slice
, reflect.UnsafePointer
といったポインタに関連する型を整形する役割を担っています。
変更前は、fmtPointer
メソッドは渡されたverb
(書式指定動詞)がポインタに対して適切かどうかを明示的にチェックしていませんでした。そのため、例えばポインタに対して%q
のような不適切な動詞が指定された場合でも、エラーを報告せずに処理を続行しようとしていました。これは、開発者が意図しない出力を受け取ったり、デバッグが困難になったりする原因となっていました。
このコミットでは、fmtPointer
メソッドの冒頭にswitch
文が追加されました。このswitch
文は、verb
が'p'
, 'v'
, 'b'
, 'd'
, 'o'
, 'x'
, 'X'
のいずれかであるかをチェックします。これらの動詞はポインタに対して意味のある書式設定を提供するため、「ok」と判断されます。もしverb
がこれらの許可された動詞のいずれでもない場合、p.badVerb(verb)
が呼び出され、不適切な動詞が使用されたことを示すエラーメッセージが生成されます。これにより、コンパイル時ではなく実行時に、ポインタに対する書式指定の誤りを診断できるようになります。
また、このコミットでは、%v
動詞でnil
ポインタが整形される際の挙動も改善されています。変更前は、nil
ポインタを%v
で出力すると、0x0
のようなアドレス値が表示されることがありました。変更後は、%v
でnil
ポインタを整形した場合に、<nil>
という文字列が出力されるようになります。これは、Go言語の他の部分でのnil
の表現と一貫性を持たせ、より直感的な出力にするための変更です。
さらに、src/pkg/fmt/fmt_test.go
には、これらの変更を検証するための新しいテストケースが追加されています。
{"p3=%p", (*int)(nil), "p3=0x0"}
:nil
ポインタを%p
で整形した場合の出力が0x0
になることを確認。{"%q", (*int)(nil), "%!q(*int=<nil>)"}
:nil
ポインタを%q
で整形した場合に、%!q(*int=<nil>)
というエラー診断メッセージが出力されることを確認。これは、%q
がポインタに対して不適切な動詞であることを示しています。{"%q", new(int), "%!q(*int=0xPTR)"}
:new(int)
で作成されたポインタを%q
で整形した場合に、同様のエラー診断メッセージが出力されることを確認。{"%v", (*int)(nil), "<nil>"}
:nil
ポインタを%v
で整形した場合に、<nil>
が出力されることを確認。{"%v", new(int), "0xPTR"}
:new(int)
で作成されたポインタを%v
で整形した場合に、アドレス値が出力されることを確認。
これらのテストケースは、fmtPointer
メソッドの新しい診断ロジックと、%v
によるnil
ポインタの整形挙動が期待通りに機能することを保証します。
コアとなるコードの変更箇所
src/pkg/fmt/fmt_test.go
--- a/src/pkg/fmt/fmt_test.go
+++ b/src/pkg/fmt/fmt_test.go
@@ -423,6 +423,7 @@ var fmttests = []struct {
{"p0=%p", new(int), "p0=0xPTR"},
{"p1=%s", &pValue, "p1=String(p)"}, // String method...
{"p2=%p", &pValue, "p2=0xPTR"}, // ... not called with %p
+ {"p3=%p", (*int)(nil), "p3=0x0"},
{"p4=%#p", new(int), "p4=PTR"},
// %p on non-pointers
@@ -431,6 +432,14 @@ var fmttests = []struct {
{"%p", make([]int, 1), "0xPTR"},
{"%p", 27, "%!p(int=27)"}, // not a pointer at all
+ // %q on pointers
+ {"%q", (*int)(nil), "%!q(*int=<nil>)"},
+ {"%q", new(int), "%!q(*int=0xPTR)"},
+
+ // %v on pointers formats 0 as <nil>
+ {"%v", (*int)(nil), "<nil>"},
+ {"%v", new(int), "0xPTR"},
+
// %d on Stringer should give integer if possible
{"%s", time.Time{}.Month(), "January"},
{"%d", time.Time{}.Month(), "1"},
src/pkg/fmt/print.go
--- a/src/pkg/fmt/print.go
+++ b/src/pkg/fmt/print.go
@@ -553,6 +553,14 @@ func (p *pp) fmtBytes(v []byte, verb rune, goSyntax bool, depth int) {
}
func (p *pp) fmtPointer(value reflect.Value, verb rune, goSyntax bool) {
+ switch verb {
+ case 'p', 'v', 'b', 'd', 'o', 'x', 'X':
+ // ok
+ default:
+ p.badVerb(verb)
+ return
+ }
+
var u uintptr
switch value.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
@@ -561,6 +569,7 @@ func (p *pp) fmtPointer(value reflect.Value, verb rune, goSyntax bool) {
p.badVerb(verb)
return
}
+
if goSyntax {
p.add('(')
p.buf.WriteString(value.Type().String())
@@ -572,6 +581,8 @@ func (p *pp) fmt0x64(uint64(u), true)
p.fmt0x64(uint64(u), true)
}
p.add(')')
+ } else if verb == 'v' && u == 0 {
+ p.buf.Write(nilAngleBytes)
} else {
p.fmt0x64(uint64(u), !p.fmt.sharp)
}
@@ -929,24 +940,7 @@ BigSwitch:
break BigSwitch
}
}
- if goSyntax {
- p.buf.WriteByte('(')
- p.buf.WriteString(value.Type().String())
- p.buf.WriteByte(')')
- p.buf.WriteByte('(')
- if v == 0 {
- p.buf.Write(nilBytes)
- } else {
- p.fmt0x64(uint64(v), true)
- }
- p.buf.WriteByte(')')
- break
- }
- if v == 0 {
- p.buf.Write(nilAngleBytes)
- break
- }
- p.fmt0x64(uint64(v), true)
+ fallthrough
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
p.fmtPointer(value, verb, goSyntax)
default:
コアとなるコードの解説
src/pkg/fmt/fmt_test.go
の変更
このファイルでは、fmttests
というテストケースのスライスに新しいエントリが追加されています。
{"p3=%p", (*int)(nil), "p3=0x0"}
:(*int)(nil)
はnil
の*int
型ポインタを表します。%p
動詞で整形した場合、その出力が0x0
となることを期待しています。これは、nil
ポインタのアドレスが0
であることを示します。
// %q on pointers
以下の2行:{"%q", (*int)(nil), "%!q(*int=<nil>)"}
:nil
の*int
型ポインタを%q
で整形しようとした場合、fmt
パッケージが「%q
は*int
型には不適切であり、値は<nil>
である」という診断メッセージを出力することを期待しています。{"%q", new(int), "%!q(*int=0xPTR)"}
:new(int)
で作成された*int
型ポインタ(非nil
)を%q
で整形しようとした場合、同様に「%q
は*int
型には不適切であり、値は0xPTR
(実際のアドレス)である」という診断メッセージが出力されることを期待しています。0xPTR
はテストフレームワークが実際のアドレス値に置き換えるプレースホルダーです。
// %v on pointers formats 0 as <nil>
以下の2行:{"%v", (*int)(nil), "<nil>"}
:nil
の*int
型ポインタを%v
で整形した場合、その出力が<nil>
となることを期待しています。これは、nil
ポインタのより直感的な表現です。{"%v", new(int), "0xPTR"}
:new(int)
で作成された*int
型ポインタを%v
で整形した場合、その出力が0xPTR
(実際のアドレス)となることを期待しています。非nil
ポインタはアドレス値として表示されます。
これらのテストケースは、fmt
パッケージがポインタに対する不適切な動詞を正しく診断し、また%v
動詞でnil
ポインタを適切に整形できるようになったことを検証します。
src/pkg/fmt/print.go
の変更
このファイルでは、pp
構造体のfmtPointer
メソッドが変更されています。
-
func (p *pp) fmtPointer(value reflect.Value, verb rune, goSyntax bool)
の冒頭に追加されたswitch
文:switch verb { case 'p', 'v', 'b', 'd', 'o', 'x', 'X': // ok default: p.badVerb(verb) return }
この
switch
文は、fmtPointer
メソッドが処理するポインタ型に対して、どの書式指定動詞が許可されているかを明示的に定義しています。'p'
(ポインタアドレス),'v'
(デフォルト),'b'
(2進数),'d'
(10進数),'o'
(8進数),'x'
,'X'
(16進数) はポインタに対して有効な動詞と見なされます。- これらのいずれでもない動詞(例:
'q'
,'s'
など)が指定された場合、p.badVerb(verb)
が呼び出されます。badVerb
メソッドは、%!verb(type=value)
のような形式のエラーメッセージを生成し、出力バッファに追加します。これにより、開発者は不適切な動詞の使用を即座に認識できます。 return
文により、不適切な動詞が検出された場合はそれ以上の処理を行わずにメソッドを終了します。
-
else if verb == 'v' && u == 0 { p.buf.Write(nilAngleBytes) }
の追加:} else if verb == 'v' && u == 0 { p.buf.Write(nilAngleBytes) } else { p.fmt0x64(uint64(u), !p.fmt.sharp) }
この変更は、
%v
動詞でポインタを整形する際の挙動を改善します。u
はポインタのアドレスを表すuintptr
型の変数です。u == 0
は、ポインタがnil
であることを意味します。verb == 'v'
かつu == 0
の場合、p.buf.Write(nilAngleBytes)
が実行されます。nilAngleBytes
は<nil>
というバイトスライスを保持しており、これによりnil
ポインタが<nil>
と出力されるようになります。- この変更により、
nil
ポインタの%v
出力がより明確で一貫性のあるものになります。
-
BigSwitch
ラベル内のポインタ整形ロジックの削除とfallthrough
の追加:- if goSyntax { - p.buf.WriteByte('(') - p.buf.WriteString(value.Type().String()) - p.buf.WriteByte(')') - p.buf.WriteByte('(') - if v == 0 { - p.buf.Write(nilBytes) - } else { - p.fmt0x64(uint64(v), true) - } - p.buf.WriteByte(')') - break - } - if v == 0 { - p.buf.Write(nilAngleBytes) - break - } - p.fmt0x64(uint64(v), true) + fallthrough case reflect.Chan, reflect.Func, reflect.UnsafePointer: p.fmtPointer(value, verb, goSyntax)
この部分の変更は、
reflect.Ptr
(一般的なポインタ)のケースから、reflect.Chan
,reflect.Func
,reflect.UnsafePointer
のケースへの処理の流れを変更しています。- 以前は
reflect.Ptr
のケースで独自のポインタ整形ロジックを持っていましたが、これが削除されました。 - 代わりに
fallthrough
キーワードが追加され、reflect.Ptr
のケースの処理が、その次のcase reflect.Chan, reflect.Func, reflect.UnsafePointer:
のブロックに引き継がれるようになりました。 - このブロックでは、
p.fmtPointer(value, verb, goSyntax)
が呼び出されます。これにより、すべてのポインタ関連の型(reflect.Ptr
を含む)の整形が、新しく診断ロジックが追加されたfmtPointer
メソッドに一元化されることになります。これはコードの重複を排除し、保守性を向上させるためのリファクタリングです。
- 以前は
これらの変更により、fmt
パッケージはポインタに対する書式指定の誤用をより効果的に検出し、開発者により有用なフィードバックを提供できるようになりました。また、nil
ポインタの%v
出力も改善され、全体的な使いやすさが向上しています。
関連リンク
- Go issue #2851: https://github.com/golang/go/issues/2851
- Go CL 5644048: https://golang.org/cl/5644048
参考にした情報源リンク
- GitHubのコミットページ: https://github.com/golang/go/commit/00134fe8ef917f17fc87076badbc54c086f74589
- Go issue #2851: https://github.com/golang/go/issues/2851
- Go言語の
fmt
パッケージ公式ドキュメント: https://pkg.go.dev/fmt - Go言語の
reflect
パッケージ公式ドキュメント: https://pkg.go.dev/reflect - Go言語の
Stringer
インターフェースに関する情報 (例: https://pkg.go.dev/fmt#Stringer)