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

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

このコミットは、Goコンパイラ(cmd/gc)におけるパッケージ定義の「safe」アノテーションの取り扱いに関するバグ修正です。具体的には、パッケージが2回目以降に読み込まれる際に、その「safe」アノテーションの情報が失われてしまう問題を解決します。これにより、-uフラグ(Goの古いバージョンでは、パッケージの更新や再ビルドに関連するフラグ)を使用する際に発生する可能性のある、誤ったパッケージの安全性判断を防ぎます。

コミット

commit 389093feec0f71d3cd0cfe44e31600187ad24191
Author: David Symonds <dsymonds@golang.org>
Date:   Wed Apr 3 08:26:08 2013 +1100

    cmd/gc: preserve safe annotation of package def.
    
    A package file may begin as either "package foo" or
    "package foo safe". The latter is relevant when using -u.
    https://golang.org/cl/6903059 resulted in the distinction
    being dropped when a package was read for the second or later time.
    This CL records whether that "safe" tag was present,
    and includes it in the dummy statement generated for the lexer.
    
    R=golang-dev, r, minux.ma, daniel.morsing, iant
    CC=golang-dev
    https://golang.org/cl/8255044

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

https://github.com/golang/go/commit/389093feec0f71d3cd0cfe44e31600187ad24191

元コミット内容

Goコンパイラ(cmd/gc)において、パッケージ定義の「safe」アノテーションが正しく保持されない問題を修正します。以前の変更(https://golang.org/cl/6903059)により、パッケージが2回目以降に読み込まれる際に、「package foo safe」という形式で指定された「safe」タグの情報が失われていました。このコミットでは、その「safe」タグが存在したかどうかを記録し、レキサー(字句解析器)のために生成されるダミーステートメントにその情報を含めるように変更します。これにより、-uフラグを使用する際のパッケージの安全性に関する区別が正しく維持されるようになります。

変更の背景

Go言語のパッケージは、package foo または package foo safe の形式で宣言されることがあります。ここで safe キーワードは、そのパッケージが特定の条件下で安全であるとマークされることを示唆しています。特に、Goのビルドシステムにおいて、-u フラグ(古いGoのバージョンでは、依存関係の更新や再ビルドを強制する際に使用されることがありました)が使用される場合、この safe アノテーションは重要な意味を持ちます。

以前のコミット https://golang.org/cl/6903059 の変更により、Goコンパイラが一度読み込んだパッケージを再度読み込む際に、この safe アノテーションの情報が失われるという問題が発生していました。これは、コンパイラがパッケージの安全性を誤って判断し、結果として不適切なビルド動作やエラーを引き起こす可能性がありました。

このコミットは、この情報の欠落を防ぎ、パッケージの「safe」アノテーションがコンパイルプロセス全体で正確に保持されるようにすることで、コンパイラの堅牢性と正確性を向上させることを目的としています。

前提知識の解説

  • Goコンパイラ (cmd/gc): Go言語の公式コンパイラの一つで、Goソースコードを機械語に変換する役割を担います。gcは「Go compiler」の略です。
  • パッケージ定義 (package foo / package foo safe): Goのソースファイルは必ず package 宣言で始まります。通常は package <パッケージ名> ですが、特定の文脈(特に古いGoのバージョンや内部的なコンパイラの挙動)では safe キーワードが付加されることがありました。この safe キーワードは、そのパッケージが特定の条件下で安全である、あるいは特定の制約を満たしていることをコンパイラに伝えるためのメタデータとして機能します。
  • -u フラグ: Goのコマンドラインツール(go build, go get など)で使用されるフラグの一つです。このコミットの時期(2013年)においては、-u フラグは主に依存関係の更新や、既にビルドされたパッケージを強制的に再ビルドする際に使用されていました。このフラグが関与する状況では、パッケージの「safe」アノテーションが正しく伝播されることが重要でした。
  • レキサー (Lexer / 字句解析器): コンパイラの最初の段階で、ソースコードをトークン(意味を持つ最小単位、例: キーワード、識別子、演算子)の並びに変換する部分です。このコミットでは、レキサーに渡される「ダミーステートメント」に「safe」情報を含めることで、レキサーがその情報を認識できるようにしています。
  • Yacc/Bison (.y ファイルと y.tab.c): Yacc (Yet Another Compiler Compiler) や Bison は、文法定義からパーサー(構文解析器)を自動生成するツールです。Goコンパイラの初期のバージョンでは、go.y のようなファイルでGo言語の文法が定義されており、これをYacc/Bisonで処理することで y.tab.c のようなC言語のパーサーコードが生成されていました。
  • Pkg 構造体: Goコンパイラの内部でパッケージの情報を保持するためのデータ構造です。このコミットでは、この構造体に safe フィールドが追加されています。
  • importpkg: Goコンパイラがパッケージをインポートする際に使用する変数や構造体で、インポートされるパッケージの情報を一時的に保持します。

技術的詳細

このコミットの主要な変更点は、Goコンパイラがパッケージの「safe」アノテーションを、パッケージが複数回読み込まれる場合でも正しく保持するようにすることです。

  1. Pkg 構造体への safe フィールドの追加: src/cmd/gc/go.h にある Pkg 構造体に char safe; というフィールドが追加されました。このフィールドは、そのパッケージが「safe」アノテーションを持っているかどうかを示すブール値(またはそれに準ずる値)を格納します。これにより、パッケージのメタデータとして「safe」情報が明示的に保持されるようになります。

  2. go.y における safe 情報の伝播: src/cmd/gc/go.y はGo言語の文法を定義するYacc/Bisonの入力ファイルです。このファイルでは、パッケージのインポート処理 (import_package ルール) において、importpkg->safe = curio.importsafe; という行が追加されています。これは、現在のI/Oコンテキスト (curio) から取得した importsafe の値を、インポートされるパッケージの Pkg 構造体の safe フィールドにコピーすることを意味します。これにより、パーサーがパッケージを処理する際に「safe」情報が正しく設定されるようになります。

  3. lex.c におけるダミーステートメントの生成: src/cmd/gc/lex.c はレキサーの実装を含んでいます。importfile 関数は、既にインポートされたパッケージを再度読み込む際に、レキサーに渡すためのダミーステートメントを生成します。以前は package %s\n$$\n のようにパッケージ名のみを含んでいましたが、このコミットでは package %s %s\n$$\n のように変更され、safe タグが存在する場合はそのタグもダミーステートメントに含めるようになりました。 具体的には、importpkg->safe が真の場合に tag = "safe"; と設定され、この tag がダミーステートメントに挿入されます。これにより、レキサーがこのダミーステートメントを処理する際に、「safe」アノテーションの存在を認識できるようになります。

これらの変更により、Goコンパイラはパッケージの「safe」アノテーションを、初期の解析段階から内部のデータ構造、そして再読み込みのプロセスに至るまで、一貫して保持できるようになります。これは、特に -u フラグのような、パッケージの再評価を伴うビルドシナリオにおいて、コンパイラの正確な挙動を保証するために不可欠です。

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

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -394,6 +394,7 @@ struct	Pkg
 	uchar	imported;	// export data of this package was parsed
 	char	exported;	// import line written in export data
 	char	direct;	// imported directly
+	char	safe;	// whether the package is marked as safe
 };
 
 typedef	struct	Iter	Iter;

src/cmd/gc/go.y

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -251,7 +251,8 @@ import_package:
 		} else if(strcmp(importpkg->name, $2->name) != 0)
 			yyerror("conflicting names %s and %s for package \"%Z\"", importpkg->name, $2->name, importpkg->path);
 		importpkg->direct = 1;
-		
+		importpkg->safe = curio.importsafe;
+
 		if(safemode && !curio.importsafe)
 			yyerror("cannot import unsafe package \"%Z\"", importpkg->path);
 	}

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -602,7 +602,7 @@ void
 importfile(Val *f, int line)
 {
 	Biobuf *imp;
-	char *file, *p, *q;
+	char *file, *p, *q, *tag;
 	int32 c;
 	int len;
 	Strlit *path;
@@ -610,8 +610,6 @@ importfile(Val *f, int line)
 	USED(line);
 
-	// TODO(rsc): don't bother reloading imports more than once?
-
 	if(f->ctype != CTSTR) {
 		yyerror("import statement not a string");
 		fakeimport();
@@ -686,7 +684,11 @@ importfile(Val *f, int line)
 	// to the lexer to avoid parsing export data twice.
 	if(importpkg->imported) {
 		file = strdup(namebuf);
-		p = smprint("package %s\n$$\n", importpkg->name);
+		tag = "";
+		if(importpkg->safe) {
+			tag = "safe";
+		}
+		p = smprint("package %s %s\n$$\n", importpkg->name, tag);
 		cannedimports(file, p);
 		return;
 	}

src/cmd/gc/y.tab.c

src/cmd/gc/y.tab.cgo.y から自動生成されるファイルであるため、直接的な手動変更は行われません。go.y の変更が反映され、行番号の調整などが行われています。

コアとなるコードの解説

  1. src/cmd/gc/go.h の変更: Pkg 構造体は、Goコンパイラがパッケージに関するメタデータを管理するために使用されます。char safe; フィールドの追加により、各パッケージが「safe」アノテーションを持っているかどうかの情報が、コンパイラの内部データモデルに永続的に保存されるようになりました。これは、パッケージのライフサイクル全体でこの重要な属性が失われないようにするための基盤となります。

  2. src/cmd/gc/go.y の変更: go.y はGo言語の文法規則を定義するファイルです。import_package ルール内の importpkg->safe = curio.importsafe; という行は、パッケージがインポートされる際に、現在の字句解析コンテキスト (curio.importsafe) から「safe」フラグの値を Pkg 構造体に伝播させる役割を担います。これにより、パーサーがパッケージ宣言を処理する時点で、そのパッケージが「safe」であるかどうかの情報が正確にキャプチャされます。

  3. src/cmd/gc/lex.c の変更: lex.cimportfile 関数は、既に読み込まれたパッケージを再利用する際に、レキサーに「ダミーステートメント」を供給します。このダミーステートメントは、レキサーが再度パッケージのソースを解析する手間を省き、既に解析済みの情報を効率的に再利用するためのものです。 変更前は、このダミーステートメントは単に package <パッケージ名> という形式でした。しかし、このコミットでは、importpkg->safe の値に基づいて safe キーワードが追加されるようになりました。 tag = ""; if(importpkg->safe) { tag = "safe"; } のロジックにより、importpkg->safe が真であれば tag"safe" となり、最終的に smprint("package %s %s\n$$\n", importpkg->name, tag); によって package <パッケージ名> safe のような形式のダミーステートメントが生成されます。 この変更の重要性は、レキサーがパッケージを再処理する際に、そのパッケージが元々「safe」アノテーションを持っていたことを正しく認識できるようになる点にあります。これにより、コンパイラの異なるフェーズ間で「safe」情報の整合性が保たれ、特に -u フラグのような、パッケージの再評価を伴うシナリオでの正確な動作が保証されます。

これらの変更は、Goコンパイラの内部的なパッケージ管理と字句解析のメカニズムを改善し、パッケージのメタデータ、特に安全性に関する情報が、コンパイルプロセス全体を通じて正確に維持されるようにするために不可欠です。

関連リンク

参考にした情報源リンク

  • Go Code Review 6903059: https://golang.org/cl/6903059 (このコミットで言及されている、問題を引き起こした可能性のある以前の変更)
  • Go Code Review 8255044: https://golang.org/cl/8255044 (このコミット自体のGo Code Reviewリンク)
  • Go言語のパッケージ宣言に関する議論 (古い情報を含む可能性あり):
    • "Go package safe annotation" での検索結果 (当時の情報を見つけるのは困難ですが、Goの歴史的な文脈を理解するのに役立つかもしれません)
    • "Go compiler -u flag" での検索結果 (当時の -u フラグの挙動に関する情報)
  • Go言語のコンパイラ内部に関する一般的な情報源 (例: "Go compiler internals" での検索)