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

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

このコミットは、Go言語のランタイムにおけるprintf形式文字列のチェック機能を導入するものです。これにより、printf系の関数呼び出しにおいて、フォーマット文字列と引数の型が一致しているかをコンパイル時に検証できるようになり、実行時エラーやセキュリティ脆弱性のリスクを低減します。

コミット

  • コミットハッシュ: 5bb0c4f88bbfad8173c32dcda304867a22e09add
  • Author: Russ Cox rsc@golang.org
  • Date: Mon Dec 15 10:50:33 2008 -0800

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

https://github.com/golang/go/commit/5bb0c4f88bbfad8173c32dcda304867a22e09add

元コミット内容

    check printf format strings

    R=r
    DELTA=18  (16 added, 0 deleted, 2 changed)
    OCL=21177
    CL=21185

変更の背景

Go言語は、その初期段階から安全性と堅牢性を重視して設計されていました。printfのような可変引数関数は非常に強力ですが、フォーマット文字列と引数の型が一致しない場合に、未定義動作やクラッシュ、さらにはセキュリティ上の脆弱性(例: フォーマット文字列攻撃)を引き起こす可能性があります。

このコミットが行われた2008年12月は、Go言語がまだ一般に公開される前の開発初期段階にあたります。この時期にprintf形式文字列のチェック機能を導入することは、Go言語のランタイムが最初から型安全性を意識し、開発者がより安全なコードを書けるようにするための基盤を築くという、設計思想の一環であったと考えられます。コンパイル時にこれらの問題を検出することで、デバッグの手間を省き、より信頼性の高いシステムを構築することを目指していました。

前提知識の解説

printf形式文字列と可変引数関数

printfはC言語の標準ライブラリ関数であり、フォーマット文字列に基づいて出力を整形する可変引数関数です。Go言語のfmtパッケージにも同様の機能が提供されています。フォーマット文字列には、%d(整数)、%s(文字列)、%p(ポインタ)などの変換指定子が含まれ、これらが後続の引数に対応します。

例: printf("Hello, %s! Your age is %d.\n", name, age);

ここで、%sには文字列型の引数nameが、%dには整数型の引数ageが期待されます。もし、%sの代わりに整数を渡したり、%dの代わりに文字列を渡したりすると、プログラムのクラッシュや予期せぬ動作につながる可能性があります。

varargckプラグマ

varargck(variable argument check)は、主にPlan 9 Cコンパイラ(Go言語の初期コンパイラはPlan 9 Cコンパイラをベースにしていました)や、一部のGCC拡張で利用されるプラグマ(#pragmaディレクティブ)です。これは、可変引数関数(特にprintfのようなフォーマット文字列を持つ関数)の呼び出しにおいて、フォーマット文字列と引数の型が一致しているかをコンパイル時にチェックするために使用されます。

#pragma varargck argpos funcname argnum これは、funcnameという関数のargnum番目の引数がフォーマット文字列であることをコンパイラに伝えます。

#pragma varargck type "format_specifier" type_name これは、format_specifier(例: "d""s")がtype_name(例: int32char*)の引数に対応することを示します。

これらのプラグマを使用することで、コンパイラはprintfのような関数呼び出しを静的に解析し、誤った型が渡された場合に警告やエラーを生成できるようになります。これにより、実行時エラーを未然に防ぎ、コードの品質と安全性を向上させることができます。

技術的詳細

このコミットの技術的な核心は、Go言語のランタイムコード(C言語で書かれている部分)において、printf形式の関数呼び出しに対するコンパイル時型チェックを有効にすることです。これは、主に以下の2つの変更によって実現されています。

  1. Makefileの変更: src/runtime/Makefileにおいて、Cコンパイラ($(CC))の呼び出しに-wFフラグが追加されています。

    • -wFフラグは、Plan 9 Cコンパイラ(またはその互換コンパイラ)において、#pragma varargckディレクティブを有効にするためのものです。このフラグが指定されることで、コンパイラはソースコード内のvarargckプラグマを解釈し、それに基づいて可変引数関数の型チェックを実行します。
    • これにより、ランタイムコード内でprintf系の関数が誤った引数で呼び出された場合、コンパイル時に警告またはエラーが発生するようになります。
  2. src/runtime/runtime.hへの#pragma varargckディレクティブの追加: src/runtime/runtime.hファイルに、printf関数に対する#pragma varargckディレクティブが追加されています。

    • #pragma varargck argpos printf 1: これは、printf関数の1番目の引数(0-indexedではないため、実際には2番目の引数)がフォーマット文字列であることをコンパイラに指示します。
    • #pragma varargck type "d" int32#pragma varargck type "D" int64など、多数の#pragma varargck typeディレクティブが追加されています。これらは、各フォーマット指定子(例: "d""x""s""p")が期待する引数の型(例: int32uint32void*int8*string)をコンパイラにマッピングします。

これらの変更により、GoランタイムのCコード内でprintf関数が使用される際、コンパイラはフォーマット文字列と可変引数の型の一貫性を厳密にチェックするようになります。これにより、開発者はより安全で堅牢なランタイムコードを記述できるようになり、Go言語全体の信頼性向上に貢献しています。

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

src/runtime/Makefile

--- a/src/runtime/Makefile
+++ b/src/runtime/Makefile
@@ -54,10 +54,10 @@ clean:
 	rm -f *.$(O) *.a runtime.acid
 
 %.$O:	%.c
--	$(CC) -w $<\
++	$(CC) -wF $<\
 
 sys_file.$O:	sys_file.c sys_types.h $(OS_H)
--	$(CC) -w -D$(GOARCH)_$(GOOS) $<\
++	$(CC) -wF -D$(GOARCH)_$(GOOS) $<\
 
 %.$O:	%.s
 	$(AS) $<\

src/runtime/runtime.h

--- a/src/runtime/runtime.h
+++ b/src/runtime/runtime.h
@@ -281,6 +281,22 @@ int32	funcline(Func*, uint64);
 void*	stackalloc(uint32);
 void	stackfree(void*);
 
+#pragma	varargck	argpos	printf	1
+#pragma	varargck	type	"d"	int32
+#pragma	varargck	type	"d"	uint32
+#pragma	varargck	type	"D"	int64
+#pragma	varargck	type	"D"	uint64
+#pragma	varargck	type	"x"	int32
+#pragma	varargck	type	"x"	uint32
+#pragma	varargck	type	"X"	int64
+#pragma	varargck	type	"X"	uint64
+#pragma	varargck	type	"p"	void*
+#pragma	varargck	type	"p"	uint64
+#pragma	varargck	type	"s"	int8*
+#pragma	varargck	type	"s"	uint8*
+#pragma	varargck	type	"S"	string
+
 // TODO(rsc): Remove. These are only temporary,
 // for the mark and sweep collector.
 void	stoptheworld(void);

コアとなるコードの解説

src/runtime/Makefileの変更

Makefileの変更は非常にシンプルで、Cコンパイラ($(CC))の呼び出しに-wFフラグを追加しています。

  • $(CC) -w $<\ から $(CC) -wF $<\ へ変更。
  • $(CC) -w -D$(GOARCH)_$(GOOS) $<\ から $(CC) -wF -D$(GOARCH)_$(GOOS) $<\ へ変更。

この-wFフラグは、Plan 9 Cコンパイラにおいて#pragma varargckディレクティブを有効にするための重要なオプションです。このフラグがないと、コンパイラは#pragma varargckディレクティブを無視し、printf形式文字列の型チェックは行われません。この変更により、コンパイルプロセスに静的解析のステップが組み込まれ、ランタイムコードの堅牢性が向上します。

src/runtime/runtime.hの変更

このファイルには、printf関数に対する一連の#pragma varargckディレクティブが追加されています。

  • #pragma varargck argpos printf 1: これは、printf関数の引数リストにおいて、フォーマット文字列がどの位置にあるかをコンパイラに伝えます。1は、引数のインデックスが1であることを意味します(多くのCコンパイラでは0から始まるインデックスですが、Plan 9 Cコンパイラでは1から始まる場合があります。または、printfの最初の引数がフォーマット文字列であるため、その位置を示しています)。これにより、コンパイラはフォーマット文字列を特定し、その後の可変引数をチェックする準備ができます。

  • #pragma varargck type "d" int32

  • #pragma varargck type "d" uint32

  • #pragma varargck type "D" int64

  • #pragma varargck type "D" uint64

  • #pragma varargck type "x" int32

  • #pragma varargck type "x" uint32

  • #pragma varargck type "X" int64

  • #pragma varargck type "X" uint64

  • #pragma varargck type "p" void*

  • #pragma varargck type "p" uint64

  • #pragma varargck type "s" int8*

  • #pragma varargck type "s" uint8*

  • #pragma varargck type "S" string

これらのディレクティブは、各printfフォーマット指定子(例: "%d""%x""%s""%p")が期待するC言語の型をコンパイラに明示的に定義します。例えば、#pragma varargck type "d" int32は、%dフォーマット指定子にはint32型の引数が対応することを意味します。

これらのプラグマを組み合わせることで、コンパイラはprintfの呼び出しを解析し、フォーマット文字列内の変換指定子と、それに対応する可変引数の実際の型が一致しているかを厳密に検証します。もし不一致があれば、コンパイル時に警告やエラーとして報告され、開発者は実行時エラーに遭遇する前に問題を修正できるようになります。これは、Go言語のランタイムの安定性と信頼性を高める上で非常に重要な変更です。

関連リンク

  • Go言語の初期開発に関する情報: Go言語の公式ブログや初期の設計ドキュメントには、このような低レベルのランタイムの決定に関する背景が記述されている可能性があります。
  • Plan 9 Cコンパイラのドキュメント: varargckプラグマの詳細な仕様は、Plan 9のCコンパイラのドキュメントに記載されています。

参考にした情報源リンク