[インデックス 1693] ファイルの概要
このコミットは、Goコンパイラの型宣言と型チェックに関連する部分、具体的にはsrc/cmd/gc/dcl.c
ファイルに対する変更です。このファイルは、Go言語のコンパイラ(gc
)の一部であり、宣言(declarations)の処理、特に型の解決や式の評価において重要な役割を担っています。
コミット
fix unsafe.Sizeof("abc")
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3c0fc400fb1f8f43b855cd663d87e5f091d90bbf
元コミット内容
fix unsafe.Sizeof("abc")
R=rsc
OCL=25105
CL=25105
変更の背景
このコミットは、Go言語のunsafe
パッケージに含まれるSizeof
関数が、文字列リテラルに対して正しく動作しないというバグを修正するために行われました。
Go言語において、unsafe.Sizeof
は任意の型の変数が占めるメモリサイズをバイト単位で返します。しかし、初期の実装では、unsafe.Sizeof("abc")
のように文字列リテラルを引数として渡した場合、コンパイラがその文字列リテラルの型を正しく認識せず、誤ったサイズを返す可能性がありました。
具体的には、文字列リテラルはコンパイル時に定数として扱われますが、Sizeof
が期待するのは特定の型の情報です。この不整合により、コンパイラが文字列リテラルを一般的なリテラルとして処理し、その結果、Sizeof
が文字列型(string
)の実際のサイズ(ポインタと長さの2ワード)ではなく、リテラル自体の内部表現のサイズを誤って計算してしまう問題が発生していました。
この修正は、unsafe.Sizeof
、unsafe.Offsetof
、unsafe.Alignof
といったunsafe
パッケージの関数が、文字列リテラルを引数として受け取った際に、それがGoの組み込み型であるstring
型として扱われるように、コンパイラの型推論ロジックを調整することを目的としています。これにより、これらの関数が文字列リテラルに対しても期待通りの結果を返すようになります。
前提知識の解説
unsafe
パッケージ
Go言語のunsafe
パッケージは、Goの型安全性とメモリ安全性の制約を意図的に回避するための機能を提供します。これにより、低レベルのメモリ操作や、Goの型システムでは表現できないような操作が可能になります。しかし、その名の通り「unsafe(安全でない)」であり、誤用するとプログラムのクラッシュや未定義の動作を引き起こす可能性があります。主に、C言語との連携、特定のパフォーマンス最適化、またはGoのランタイム内部の操作など、特殊なケースでのみ使用されます。
unsafe
パッケージには以下の主要な関数があります。
unsafe.Sizeof(x interface{}) uintptr
: 引数x
が占めるメモリのバイト数を返します。これは、x
の静的な型に基づいて計算され、実行時の値には依存しません。unsafe.Offsetof(x interface{}) uintptr
: 構造体のフィールドx
が、その構造体の先頭からどれだけオフセットしているか(バイト単位)を返します。unsafe.Alignof(x interface{}) uintptr
: 引数x
の型が要求するアライメント(メモリ配置の制約)をバイト単位で返します。
Goの型システムと文字列の内部表現
Go言語は静的型付け言語であり、すべての変数にはコンパイル時に型が決定されます。string
型はGoの組み込み型であり、不変のバイトシーケンスを表します。Goの文字列は内部的には、データへのポインタと文字列の長さ(バイト数)の2つの要素で構成される構造体として表現されます。したがって、64ビットシステムでは通常16バイト(ポインタ8バイト + 長さ8バイト)を占めます。
Goコンパイラ (gc
) と dcl.c
Go言語の公式コンパイラはgc
と呼ばれます。gc
は、Goのソースコードを機械語に変換するプロセスにおいて、字句解析、構文解析、型チェック、最適化、コード生成などの複数のフェーズを実行します。
src/cmd/gc/dcl.c
ファイルは、gc
コンパイラの「宣言(declarations)」フェーズに関連するコードを含んでいます。このフェーズでは、変数、関数、型などの宣言が処理され、それらのスコープ、型、およびその他の属性が解決されます。特に、unsafe
パッケージの関数呼び出しのように、コンパイラが特殊な処理を行う必要がある組み込み関数やマジック関数(コンパイラが特別に扱う関数)の処理ロジックもこのファイルに含まれることがあります。
技術的詳細
このコミットの技術的な核心は、src/cmd/gc/dcl.c
ファイル内のunsafenmagic
関数にあります。この関数は、unsafe.Sizeof
、unsafe.Offsetof
、unsafe.Alignof
といったunsafe
パッケージの「マジック関数」の呼び出しをコンパイル時に処理します。
変更前は、unsafe.Sizeof
やunsafe.Alignof
に文字列リテラル(例: "abc"
)が渡された場合、コンパイラは引数r
の型(r->type
)を直接参照していました。しかし、文字列リテラルはコンパイル時にはOLITERAL
(リテラルオペレーション)として扱われ、そのval.ctype
(定数型)はCTSTR
(文字列定数)となります。この時点でのr->type
は、必ずしもstring
型を指しているとは限りませんでした。そのため、Sizeof
やAlignof
が文字列リテラルの「実際の型」であるstring
のサイズやアライメントではなく、リテラル自体の内部的なコンパイラ表現のサイズを誤って計算してしまう問題がありました。
この修正では、tr
という新しいType
ポインタ変数を導入し、引数r
の型を決定するロジックを改善しています。
- まず、
tr = r->type;
として、引数r
の現在の型をtr
に代入します。 - 次に、
if(r->op == OLITERAL && r->val.ctype == CTSTR)
という条件で、r
が文字列リテラルであるかどうかをチェックします。 - もし
r
が文字列リテラルであれば、tr = types[TSTRING];
として、tr
を明示的にGoの組み込みstring
型(types[TSTRING]
はコンパイラ内部でstring
型を表す定数)に設定します。 - その後の処理(
v = tr->width;
など)では、このtr
変数を使用することで、文字列リテラルが渡された場合でも、常にstring
型の正しいサイズやアライメントが計算されるようになります。
これにより、unsafe.Sizeof("abc")
は、文字列リテラルの内容に関わらず、Goのstring
型が占めるメモリサイズ(通常16バイト)を正しく返すようになります。同様の修正がAlignof
にも適用され、文字列リテラルのアライメントも正しく計算されるようになりました。
コアとなるコードの変更箇所
diff --git a/src/cmd/gc/dcl.c b/src/cmd/gc/dcl.c
index 1f053b6114..fc977eba20 100644
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -1525,7 +1525,7 @@ unsafenmagic(Node *l, Node *r)
{
Node *n;
Sym *s;
- Type *t;\n+ Type *t, *tr;
long v;
Val val;
@@ -1541,9 +1541,12 @@ unsafenmagic(Node *l, Node *r)
if(strcmp(s->name, "Sizeof") == 0) {
walktype(r, Erv);
- if(r->type == T)
+ tr = r->type;
+ if(r->op == OLITERAL && r->val.ctype == CTSTR)
+ tr = types[TSTRING];
+ if(tr == T)
goto no;
- v = r->type->width;
+ v = tr->width;
goto yes;
}
if(strcmp(s->name, "Offsetof") == 0) {
@@ -1555,16 +1558,21 @@ unsafenmagic(Node *l, Node *r)
}
if(strcmp(s->name, "Alignof") == 0) {
walktype(r, Erv);
- if (r->type == T)
+ tr = r->type;
+ if(r->op == OLITERAL && r->val.ctype == CTSTR)
+ tr = types[TSTRING];
+ if(tr == T)
goto no;
+\n // make struct { byte; T; }
t = typ(TSTRUCT);
t->type = typ(TFIELD);
t->type->type = types[TUINT8];
t->type->down = typ(TFIELD);
- t->type->down->type = r->type;
+ t->type->down->type = tr;
// compute struct widths
dowidth(t);
+\n // the offset of T is its required alignment
v = t->type->down->width;
goto yes;
}
コアとなるコードの解説
変更の中心は、unsafenmagic
関数内でSizeof
とAlignof
の処理を行う部分です。
Type *t, *tr;
:tr
という新しいType
ポインタ変数が追加されました。これは、引数の型を一時的に保持し、必要に応じて調整するために使用されます。tr = r->type;
:Sizeof
とAlignof
の処理の冒頭で、まず引数r
の現在の型をtr
に代入します。if(r->op == OLITERAL && r->val.ctype == CTSTR)
: ここが重要な変更点です。r->op == OLITERAL
:r
がリテラル(定数)であることをチェックします。r->val.ctype == CTSTR
: そのリテラルが文字列定数であることをチェックします。- この条件が真の場合、つまり引数が文字列リテラルである場合、
tr = types[TSTRING];
が実行されます。これは、tr
をGoの組み込みstring
型に強制的に設定することを意味します。これにより、文字列リテラルがunsafe.Sizeof
やunsafe.Alignof
に渡された場合でも、コンパイラはそれをstring
型として正しく扱います。
if(tr == T)
: 型が不明な場合(T
はコンパイラ内部で「不明な型」を表すことが多い)はエラー処理(goto no;
)に移行します。このチェックは、tr
が正しく設定された後に行われます。v = tr->width;
:Sizeof
の場合、tr
のwidth
(サイズ)が計算に使用されます。t->type->down->type = tr;
:Alignof
の場合、アライメント計算のために一時的に作成される構造体のフィールド型としてtr
が使用されます。
この修正により、文字列リテラルがunsafe.Sizeof
やunsafe.Alignof
に渡された際に、コンパイラがそのリテラルをstring
型として正しく解釈し、期待されるサイズやアライメントを返すようになりました。
関連リンク
- Go言語
unsafe
パッケージのドキュメント: https://pkg.go.dev/unsafe - Go言語の文字列に関する公式ブログ記事 (内部表現について触れている可能性): https://go.dev/blog/strings (一般的な情報源として)
- Goコンパイラのソースコード (GoのGitHubリポジトリ): https://github.com/golang/go
参考にした情報源リンク
- Go言語
unsafe
パッケージのドキュメント: https://pkg.go.dev/unsafe - Go言語の文字列に関する公式ブログ記事: https://go.dev/blog/strings
- Goコンパイラのソースコード (GoのGitHubリポジトリ): https://github.com/golang/go
- Go言語のコンパイラに関する一般的な情報 (必要に応じて検索): "Go compiler internals", "Go gc dcl.c" など
- Go言語の型システムに関する一般的な情報 (必要に応じて検索): "Go type system"```markdown
[インデックス 1693] ファイルの概要
このコミットは、Goコンパイラの型宣言と型チェックに関連する部分、具体的にはsrc/cmd/gc/dcl.c
ファイルに対する変更です。このファイルは、Go言語のコンパイラ(gc
)の一部であり、宣言(declarations)の処理、特に型の解決や式の評価において重要な役割を担っています。
コミット
fix unsafe.Sizeof("abc")
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3c0fc400fb1f8f43b855cd663d87e5f091d90bbf
元コミット内容
fix unsafe.Sizeof("abc")
R=rsc
OCL=25105
CL=25105
変更の背景
このコミットは、Go言語のunsafe
パッケージに含まれるSizeof
関数が、文字列リテラルに対して正しく動作しないというバグを修正するために行われました。
Go言語において、unsafe.Sizeof
は任意の型の変数が占めるメモリサイズをバイト単位で返します。しかし、初期の実装では、unsafe.Sizeof("abc")
のように文字列リテラルを引数として渡した場合、コンパイラがその文字列リテラルの型を正しく認識せず、誤ったサイズを返す可能性がありました。
具体的には、文字列リテラルはコンパイル時に定数として扱われますが、Sizeof
が期待するのは特定の型の情報です。この不整合により、コンパイラが文字列リテラルを一般的なリテラルとして処理し、その結果、Sizeof
が文字列型(string
)の実際のサイズ(ポインタと長さの2ワード)ではなく、リテラル自体の内部表現のサイズを誤って計算してしまう問題が発生していました。
この修正は、unsafe.Sizeof
、unsafe.Offsetof
、unsafe.Alignof
といったunsafe
パッケージの関数が、文字列リテラルを引数として受け取った際に、それがGoの組み込み型であるstring
型として扱われるように、コンパイラの型推論ロジックを調整することを目的としています。これにより、これらの関数が文字列リテラルに対しても期待通りの結果を返すようになります。
前提知識の解説
unsafe
パッケージ
Go言語のunsafe
パッケージは、Goの型安全性とメモリ安全性の制約を意図的に回避するための機能を提供します。これにより、低レベルのメモリ操作や、Goの型システムでは表現できないような操作が可能になります。しかし、その名の通り「unsafe(安全でない)」であり、誤用するとプログラムのクラッシュや未定義の動作を引き起こす可能性があります。主に、C言語との連携、特定のパフォーマンス最適化、またはGoのランタイム内部の操作など、特殊なケースでのみ使用されます。
unsafe
パッケージには以下の主要な関数があります。
unsafe.Sizeof(x interface{}) uintptr
: 引数x
が占めるメモリのバイト数を返します。これは、x
の静的な型に基づいて計算され、実行時の値には依存しません。unsafe.Offsetof(x interface{}) uintptr
: 構造体のフィールドx
が、その構造体の先頭からどれだけオフセットしているか(バイト単位)を返します。unsafe.Alignof(x interface{}) uintptr
: 引数x
の型が要求するアライメント(メモリ配置の制約)をバイト単位で返します。
Goの型システムと文字列の内部表現
Go言語は静的型付け言語であり、すべての変数にはコンパイル時に型が決定されます。string
型はGoの組み込み型であり、不変のバイトシーケンスを表します。Goの文字列は内部的には、データへのポインタと文字列の長さ(バイト数)の2つの要素で構成される構造体として表現されます。したがって、64ビットシステムでは通常16バイト(ポインタ8バイト + 長さ8バイト)を占めます。
Goコンパイラ (gc
) と dcl.c
Go言語の公式コンパイラはgc
と呼ばれます。gc
は、Goのソースコードを機械語に変換するプロセスにおいて、字句解析、構文解析、型チェック、最適化、コード生成などの複数のフェーズを実行します。
src/cmd/gc/dcl.c
ファイルは、gc
コンパイラの「宣言(declarations)」フェーズに関連するコードを含んでいます。このフェーズでは、変数、関数、型などの宣言が処理され、それらのスコープ、型、およびその他の属性が解決されます。特に、unsafe
パッケージの関数呼び出しのように、コンパイラが特殊な処理を行う必要がある組み込み関数やマジック関数(コンパイラが特別に扱う関数)の処理ロジックもこのファイルに含まれることがあります。
技術的詳細
このコミットの技術的な核心は、src/cmd/gc/dcl.c
ファイル内のunsafenmagic
関数にあります。この関数は、unsafe.Sizeof
、unsafe.Offsetof
、unsafe.Alignof
といったunsafe
パッケージの「マジック関数」の呼び出しをコンパイル時に処理します。
変更前は、unsafe.Sizeof
やunsafe.Alignof
に文字列リテラル(例: "abc"
)が渡された場合、コンパイラは引数r
の型(r->type
)を直接参照していました。しかし、文字列リテラルはコンパイル時にはOLITERAL
(リテラルオペレーション)として扱われ、そのval.ctype
(定数型)はCTSTR
(文字列定数)となります。この時点でのr->type
は、必ずしもstring
型を指しているとは限りませんでした。そのため、Sizeof
やAlignof
が文字列リテラルの「実際の型」であるstring
のサイズやアライメントではなく、リテラル自体の内部的なコンパイラ表現のサイズを誤って計算してしまう問題がありました。
この修正では、tr
という新しいType
ポインタ変数を導入し、引数r
の型を決定するロジックを改善しています。
- まず、
tr = r->type;
として、引数r
の現在の型をtr
に代入します。 - 次に、
if(r->op == OLITERAL && r->val.ctype == CTSTR)
という条件で、r
が文字列リテラルであるかどうかをチェックします。 - もし
r
が文字列リテラルであれば、tr = types[TSTRING];
として、tr
を明示的にGoの組み込みstring
型(types[TSTRING]
はコンパイラ内部でstring
型を表す定数)に設定します。 - その後の処理(
v = tr->width;
など)では、このtr
変数を使用することで、文字列リテラルが渡された場合でも、常にstring
型の正しいサイズやアライメントが計算されるようになります。
これにより、unsafe.Sizeof("abc")
は、文字列リテラルの内容に関わらず、Goのstring
型が占めるメモリサイズ(通常16バイト)を正しく返すようになります。同様の修正がAlignof
にも適用され、文字列リテラルのアライメントも正しく計算されるようになりました。
コアとなるコードの変更箇所
diff --git a/src/cmd/gc/dcl.c b/src/cmd/gc/dcl.c
index 1f053b6114..fc977eba20 100644
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -1525,7 +1525,7 @@ unsafenmagic(Node *l, Node *r)
{
Node *n;
Sym *s;
- Type *t;\n+ Type *t, *tr;
long v;
Val val;
@@ -1541,9 +1541,12 @@ unsafenmagic(Node *l, Node *r)
if(strcmp(s->name, "Sizeof") == 0) {
walktype(r, Erv);
- if(r->type == T)
+ tr = r->type;
+ if(r->op == OLITERAL && r->val.ctype == CTSTR)
+ tr = types[TSTRING];
+ if(tr == T)
goto no;
- v = r->type->width;
+ v = tr->width;
goto yes;
}
if(strcmp(s->name, "Offsetof") == 0) {
@@ -1555,16 +1558,21 @@ unsafenmagic(Node *l, Node *r)
}
if(strcmp(s->name, "Alignof") == 0) {
walktype(r, Erv);
- if (r->type == T)
+ tr = r->type;
+ if(r->op == OLITERAL && r->val.ctype == CTSTR)
+ tr = types[TSTRING];
+ if(tr == T)
goto no;
+\n // make struct { byte; T; }
t = typ(TSTRUCT);
t->type = typ(TFIELD);
t->type->type = types[TUINT8];
t->type->down = typ(TFIELD);
- t->type->down->type = r->type;
+ t->type->down->type = tr;
// compute struct widths
dowidth(t);
+\n // the offset of T is its required alignment
v = t->type->down->width;
goto yes;
}
コアとなるコードの解説
変更の中心は、unsafenmagic
関数内でSizeof
とAlignof
の処理を行う部分です。
Type *t, *tr;
:tr
という新しいType
ポインタ変数が追加されました。これは、引数の型を一時的に保持し、必要に応じて調整するために使用されます。tr = r->type;
:Sizeof
とAlignof
の処理の冒頭で、まず引数r
の現在の型をtr
に代入します。if(r->op == OLITERAL && r->val.ctype == CTSTR)
: ここが重要な変更点です。r->op == OLITERAL
:r
がリテラル(定数)であることをチェックします。r->val.ctype == CTSTR
: そのリテラルが文字列定数であることをチェックします。- この条件が真の場合、つまり引数が文字列リテラルである場合、
tr = types[TSTRING];
が実行されます。これは、tr
をGoの組み込みstring
型に強制的に設定することを意味します。これにより、文字列リテラルがunsafe.Sizeof
やunsafe.Alignof
に渡された場合でも、コンパイラはそれをstring
型として正しく扱います。
if(tr == T)
: 型が不明な場合(T
はコンパイラ内部で「不明な型」を表すことが多い)はエラー処理(goto no;
)に移行します。このチェックは、tr
が正しく設定された後に行われます。v = tr->width;
:Sizeof
の場合、tr
のwidth
(サイズ)が計算に使用されます。t->type->down->type = tr;
:Alignof
の場合、アライメント計算のために一時的に作成される構造体のフィールド型としてtr
が使用されます。
この修正により、文字列リテラルがunsafe.Sizeof
やunsafe.Alignof
に渡された際に、コンパイラがそのリテラルをstring
型として正しく解釈し、期待されるサイズやアライメントを返すようになりました。
関連リンク
- Go言語
unsafe
パッケージのドキュメント: https://pkg.go.dev/unsafe - Go言語の文字列に関する公式ブログ記事 (内部表現について触れている可能性): https://go.dev/blog/strings (一般的な情報源として)
- Goコンパイラのソースコード (GoのGitHubリポジトリ): https://github.com/golang/go
参考にした情報源リンク
- Go言語
unsafe
パッケージのドキュメント: https://pkg.go.dev/unsafe - Go言語の文字列に関する公式ブログ記事: https://go.dev/blog/strings
- Goコンパイラのソースコード (GoのGitHubリポジトリ): https://github.com/golang/go
- Go言語のコンパイラに関する一般的な情報 (必要に応じて検索): "Go compiler internals", "Go gc dcl.c" など
- Go言語の型システムに関する一般的な情報 (必要に応じて検索): "Go type system"