[インデックス 10309] ファイルの概要
このコミットは、Goコンパイラ(gc
)において、unsafe
パッケージの組み込み関数(unsafe.Sizeof
, unsafe.Offsetof
, unsafe.Alignof
)が呼び出し形式でなく、単なる変数として参照された場合に、より適切なエラーメッセージを出力するように改善するものです。これにより、開発者がunsafe
パッケージの誤用を早期に発見し、修正できるようになります。
コミット
commit 924ea515cf1d1a6f5e447c212e00e3e88c785c41
Author: Luuk van Dijk <lvd@golang.org>
Date: Wed Nov 9 18:30:54 2011 +0100
gc: better error for non-calling use of unsafe builtins.
Fixes #1951
R=rsc
CC=golang-dev
https://golang.org/cl/5372041
---
src/cmd/gc/go.h | 1 +
src/cmd/gc/typecheck.c | 4 ++++
src/cmd/gc/unsafe.c | 19 +++++++++++++++++--
test/fixedbugs/bug376.go | 11 +++++++++++
4 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/src/cmd/gc/go.h b/src/cmd/gc/go.h
index 52344e7563..faae7bd9ea 100644
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -1247,6 +1247,7 @@ void queuemethod(Node *n);\n /*
* unsafe.c
*/
+int isunsafebuiltin(Node *n);\n Node* unsafenmagic(Node *n);\n
/*
diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c
index f84f8440c4..ed5c35ae01 100644
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -210,6 +210,10 @@ reswitch:
}\n n->used = 1;\n }\n+\t\tif(!(top &Ecall) && isunsafebuiltin(n)) {\n+\t\t\tyyerror(\"%N is not an expression, must be called\", n);\n+\t\t\tgoto error;\n+\t\t}\n \t\tok |= Erv;\n \t\tgoto ret;\n
diff --git a/src/cmd/gc/unsafe.c b/src/cmd/gc/unsafe.c
index 7504b51c99..21496b08cc 100644
--- a/src/cmd/gc/unsafe.c
+++ b/src/cmd/gc/unsafe.c
@@ -10,6 +10,7 @@\n * look for\n *\tunsafe.Sizeof\n *\tunsafe.Offsetof\n+ *\tunsafe.Alignof\n * rewrite with a constant\n */\n Node*\n@@ -22,7 +23,7 @@ unsafenmagic(Node *nn)\n Val val;\n Node *fn;\n NodeList *args;\n-\t\n+\n fn = nn->left;\n args = nn->list;\n \n@@ -83,7 +84,7 @@ bad:\n \tyyerror(\"invalid expression %N\", nn);\n \tv = 0;\n \tgoto ret;\n-\t\n+\n yes:\n \tif(args->next != nil)\n \t\tyyerror(\"extra arguments for %S\", s);\n@@ -97,3 +98,17 @@ ret:\n \tn->type = types[TUINTPTR];\n \treturn n;\n }\n+\n+int\n+isunsafebuiltin(Node *n)\n+{\n+\tif(n == N || n->op != ONAME || n->sym == S || n->sym->pkg != unsafepkg)\n+\t\treturn 0;\n+\tif(strcmp(n->sym->name, \"Sizeof\") == 0)\n+\t\treturn 1;\n+\tif(strcmp(n->sym->name, \"Offsetof\") == 0)\n+\t\treturn 1;\n+\tif(strcmp(n->sym->name, \"Alignof\") == 0)\n+\t\treturn 1;\n+\treturn 0;\n+}\ndiff --git a/test/fixedbugs/bug376.go b/test/fixedbugs/bug376.go
new file mode 100644
index 0000000000..1efbeecf21
--- /dev/null
+++ b/test/fixedbugs/bug376.go
@@ -0,0 +1,11 @@\n+// errchk $G $D/$F.go\n+\n+// Copyright 2011 The Go Authors. All rights reserved.\n+// Use of this source code is governed by a BSD-style\n+// license that can be found in the LICENSE file.\n+\n+// issue 1951\n+package foo\n+import \"unsafe\"\n+var v = unsafe.Sizeof // ERROR \"must be called\"\n+\n```
## GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/924ea515cf1d1a6f5e447c212e00e3e88c785c41
## 元コミット内容
このコミットは、Goコンパイラ(`gc`)が`unsafe`パッケージの組み込み関数(`Sizeof`, `Offsetof`, `Alignof`)が呼び出し形式でなく、単なる変数として使用された場合に、より適切なエラーメッセージを生成するように修正するものです。具体的には、`unsafe.Sizeof`のように関数呼び出しの括弧を付けずに参照した場合に、「式ではありません、呼び出す必要があります」というエラーを出すように改善します。この変更は、内部の課題管理システムで追跡されていたIssue 1951を修正します。
## 変更の背景
Go言語の`unsafe`パッケージは、型安全性をバイパスして低レベルなメモリ操作を可能にする強力な機能を提供します。`unsafe.Sizeof`、`unsafe.Offsetof`、`unsafe.Alignof`は、それぞれ型や構造体フィールドのサイズ、オフセット、アライメントを取得するための組み込み関数です。これらは通常の関数と同様に、引数を渡して呼び出す必要があります。
しかし、Goの初期のコンパイラでは、これらの`unsafe`組み込み関数が関数呼び出しの形式でなく、単に変数として参照された場合(例: `var v = unsafe.Sizeof`)、コンパイラは一般的な「無効な式」のような曖昧なエラーを出すか、あるいは予期せぬ動作を引き起こす可能性がありました。これは、開発者が`unsafe`パッケージの誤用を特定し、デバッグするのを困難にしていました。
このコミットの目的は、このような`unsafe`組み込み関数の誤用に対して、コンパイラがより具体的で分かりやすいエラーメッセージを生成するようにすることです。これにより、開発者は問題の原因を迅速に理解し、コードを修正できるようになります。
## 前提知識の解説
* **Goコンパイラ (`gc`)**: Go言語の公式コンパイラです。ソースコードを解析し、実行可能なバイナリに変換します。このコミットで変更されている`src/cmd/gc`ディレクトリは、Goコンパイラのソースコードの一部です。
* **`unsafe`パッケージ**: Go言語の標準ライブラリの一つで、Goの型システムが提供する安全性を意図的にバイパスするための機能を提供します。ポインタ演算や、任意の型の値をバイト列として扱うなど、低レベルな操作が可能です。非常に強力ですが、誤用するとプログラムのクラッシュや未定義動作を引き起こす可能性があるため、慎重な使用が求められます。
* **`unsafe.Sizeof(x)`**: `x`が占めるメモリのバイト数を返します。
* **`unsafe.Offsetof(x.f)`**: 構造体`x`のフィールド`f`が、構造体の先頭からどれだけオフセットしているか(バイト単位)を返します。
* **`unsafe.Alignof(x)`**: `x`のアライメント(バイト単位)を返します。アライメントとは、メモリ上でデータが配置される際の特定の境界条件のことです。
* **抽象構文木 (AST)**: コンパイラがソースコードを解析する際に生成する、プログラムの構造を木構造で表現したものです。コンパイラはASTを走査して、型チェックやコード生成などの処理を行います。
* **型チェック (Type Checking)**: コンパイラの重要なフェーズの一つで、プログラムがGo言語の型規則に準拠しているかを確認します。例えば、関数が正しい型の引数で呼び出されているか、変数が正しい型で初期化されているかなどを検証します。
* **`Node`構造体**: Goコンパイラの内部で、ASTの各ノード(式、文、宣言など)を表すために使用されるデータ構造です。
* **`yyerror`**: Goコンパイラの内部で使用されるエラー報告関数です。コンパイル時にエラーが発生した場合に、エラーメッセージを出力するために使用されます。
* **`ONAME`**: ASTノードの操作タイプの一つで、名前(変数名、関数名など)を表します。
* **`Ecall`**: コンパイラの内部フラグで、現在のコンテキストが関数呼び出しの式であるかどうかを示します。
## 技術的詳細
このコミットの技術的な核心は、Goコンパイラの型チェックフェーズにおいて、`unsafe`パッケージの組み込み関数が「呼び出し」としてではなく「単なる参照」として使用された場合に、それを検出し、適切なエラーメッセージを生成することです。
変更は主に以下のファイルで行われています。
1. **`src/cmd/gc/go.h`**:
* `isunsafebuiltin`関数のプロトタイプ宣言が追加されました。この関数は、与えられた`Node`が`unsafe`パッケージの組み込み関数(`Sizeof`, `Offsetof`, `Alignof`)であるかどうかを判定します。
2. **`src/cmd/gc/unsafe.c`**:
* 新しい関数`isunsafebuiltin(Node *n)`が実装されました。
* この関数は、入力された`Node`が`ONAME`(名前)であり、かつ`unsafe`パッケージに属しているかを最初にチェックします。
* その後、ノードの名前が"Sizeof"、"Offsetof"、または"Alignof"のいずれかであるかを`strcmp`関数(文字列比較)で確認し、該当すれば1(真)を返します。それ以外の場合は0(偽)を返します。
* 既存の`unsafenmagic`関数内で、`unsafe.Alignof`が`unsafe`組み込み関数として認識されるようにコメントが追加されました。これはコードの動作には直接影響しませんが、ドキュメントとしての役割を果たします。
3. **`src/cmd/gc/typecheck.c`**:
* 型チェックの主要なロジックが含まれる`typecheck.c`の`typecheck`関数内に、新しいエラーチェックが追加されました。
* 追加された条件は以下の通りです: `if(!(top &Ecall) && isunsafebuiltin(n))`
* `!(top &Ecall)`: これは、現在のコンテキストが関数呼び出しの式ではないことを意味します。つまり、`unsafe.Sizeof(...)`のように呼び出されているのではなく、`unsafe.Sizeof`のように単独で参照されている状態を検出します。
* `isunsafebuiltin(n)`: これは、現在処理しているノード`n`が`unsafe`パッケージの組み込み関数(`Sizeof`, `Offsetof`, `Alignof`)のいずれかであるかをチェックします。
* 上記の2つの条件が同時に真である場合、つまり`unsafe`組み込み関数が呼び出し形式でなく参照されている場合に、`yyerror("%N is not an expression, must be called", n);`というエラーメッセージを出力します。`%N`はノードの名前(例: `unsafe.Sizeof`)に置換されます。
* エラーが出力された後、`goto error;`によってエラー処理ルーチンにジャンプし、コンパイルを停止します。
4. **`test/fixedbugs/bug376.go`**:
* このコミットによって修正される問題(Issue 1951)を再現し、新しいエラーメッセージが正しく出力されることを検証するためのテストケースが追加されました。
* テストコードは`var v = unsafe.Sizeof`という行を含み、その行のコメントに`// ERROR "must be called"`と記述することで、コンパイラが期待されるエラーメッセージを出すことを確認します。
これらの変更により、コンパイラは`unsafe`組み込み関数の誤用を正確に特定し、開発者にとってより分かりやすいエラーメッセージを提供できるようになりました。
## コアとなるコードの変更箇所
### `src/cmd/gc/go.h`
```diff
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -1247,6 +1247,7 @@ void queuemethod(Node *n);\n /*
* unsafe.c
*/
+int isunsafebuiltin(Node *n);\n Node* unsafenmagic(Node *n);\n
/*
src/cmd/gc/typecheck.c
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -210,6 +210,10 @@ reswitch:
}\n n->used = 1;\n }\n+\t\tif(!(top &Ecall) && isunsafebuiltin(n)) {\n+\t\t\tyyerror(\"%N is not an expression, must be called\", n);\n+\t\t\tgoto error;\n+\t\t}\n \t\tok |= Erv;\n \t\tgoto ret;\n
src/cmd/gc/unsafe.c
--- a/src/cmd/gc/unsafe.c
+++ b/src/cmd/gc/unsafe.c
@@ -10,6 +10,7 @@\n * look for\n *\tunsafe.Sizeof\n *\tunsafe.Offsetof\n+ *\tunsafe.Alignof\n * rewrite with a constant\n */\n Node*\n@@ -22,7 +23,7 @@ unsafenmagic(Node *nn)\n Val val;\n Node *fn;\n NodeList *args;\n-\t\n+\n fn = nn->left;\n args = nn->list;\n \n@@ -83,7 +84,7 @@ bad:\n \tyyerror(\"invalid expression %N\", nn);\n \tv = 0;\n \tgoto ret;\n-\t\n+\n yes:\n \tif(args->next != nil)\n \t\tyyerror(\"extra arguments for %S\", s);\n@@ -97,3 +98,17 @@ ret:\n \tn->type = types[TUINTPTR];\n \treturn n;\n }\n+\n+int\n+isunsafebuiltin(Node *n)\n+{\n+\tif(n == N || n->op != ONAME || n->sym == S || n->sym->pkg != unsafepkg)\n+\t\treturn 0;\n+\tif(strcmp(n->sym->name, \"Sizeof\") == 0)\n+\t\treturn 1;\n+\tif(strcmp(n->sym->name, \"Offsetof\") == 0)\n+\t\treturn 1;\n+\tif(strcmp(n->sym->name, \"Alignof\") == 0)\n+\t\treturn 1;\n+\treturn 0;\n+}\n```
### `test/fixedbugs/bug376.go`
```diff
--- /dev/null
+++ b/test/fixedbugs/bug376.go
@@ -0,0 +1,11 @@\n+// errchk $G $D/$F.go\n+\n+// Copyright 2011 The Go Authors. All rights reserved.\n+// Use of this source code is governed by a BSD-style\n+// license that can be found in the LICENSE file.\n+\n+// issue 1951\n+package foo\n+import \"unsafe\"\n+var v = unsafe.Sizeof // ERROR \"must be called\"\n+\n```
## コアとなるコードの解説
### `isunsafebuiltin` 関数の追加 (`src/cmd/gc/unsafe.c`)
この関数は、ASTノードが`unsafe`パッケージの組み込み関数(`Sizeof`, `Offsetof`, `Alignof`)のいずれかを表しているかを効率的に判定するために導入されました。
```c
int
isunsafebuiltin(Node *n)
{
// ノードがNULL、名前ノードでない、シンボルがない、またはunsafeパッケージに属していない場合は0を返す
if(n == N || n->op != ONAME || n->sym == S || n->sym->pkg != unsafepkg)
return 0;
// ノードの名前が"Sizeof"であれば1を返す
if(strcmp(n->sym->name, "Sizeof") == 0)
return 1;
// ノードの名前が"Offsetof"であれば1を返す
if(strcmp(n->sym->name, "Offsetof") == 0)
return 1;
// ノードの名前が"Alignof"であれば1を返す
if(strcmp(n->sym->name, "Alignof") == 0)
return 1;
// 上記のいずれにも該当しない場合は0を返す
return 0;
}
typecheck.c
におけるエラーチェックの追加
typecheck.c
のtypecheck
関数は、Goコンパイラの型チェックの中核を担っています。この関数内で、unsafe
組み込み関数の誤用を検出するための新しい条件が追加されました。
// ... 既存の型チェックロジック ...
// 現在のコンテキストが関数呼び出しの式ではなく (top & Ecall が偽)、
// かつ、現在のノードがunsafeパッケージの組み込み関数である場合
if(!(top &Ecall) && isunsafebuiltin(n)) {
// エラーメッセージを出力: 「%N は式ではありません、呼び出す必要があります」
// %N はノードの名前(例: unsafe.Sizeof)に置換される
yyerror("%N is not an expression, must be called", n);
// エラー処理ルーチンにジャンプ
goto error;
}
// ... 既存の型チェックロジック ...
この変更により、コンパイラはvar v = unsafe.Sizeof
のようなコードを検出した際に、以前の曖昧なエラーではなく、unsafe.Sizeof is not an expression, must be called
という明確なエラーメッセージを出力するようになります。これは、開発者がunsafe
パッケージの誤用をより迅速かつ正確に理解し、修正するのに役立ちます。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/924ea515cf1d1a6f5e447c212e00e3e88c785c41
- Go CL (Code Review): https://golang.org/cl/5372041
参考にした情報源リンク
- コミットデータ:
/home/orange/Project/comemo/commit_data/10309.txt
- Go言語の
unsafe
パッケージに関する公式ドキュメント (一般的な情報源として) - Goコンパイラのソースコード (一般的な情報源として)
- Go言語のIssueトラッカー (Issue 1951に関する情報が公開されている場合)
- (注: Issue 1951は、一般的なGitHub検索では直接見つかりませんでした。これは、内部の課題管理システムで管理されていたか、古いIssueである可能性があります。)