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

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

このコミットは、Goコンパイラのsrc/cmd/gc/walk.cファイルに対して行われた変更を記録しています。具体的には、unsafe.Pointer型とuintptr型間の変換に関する型チェックロジックが追加されています。これは、Go言語の初期段階において、低レベルのメモリ操作を可能にするための重要なステップでした。

コミット

commit bf6164719a20b5003ed6a4f49fb2d796801240af
Author: Ken Thompson <ken@golang.org>
Date:   Mon Dec 8 20:50:17 2008 -0800

    conversion to/from
    any pointer or uintptr
    and unsafe.pointer
    
    R=r
    OCL=20795
    CL=20795

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

https://github.com/golang/go/commit/bf6164719a20b5003ed6a4f49fb2d796801240af

元コミット内容

conversion to/from
any pointer or uintptr
and unsafe.pointer

変更の背景

Go言語は、メモリ安全性を重視し、ガベージコレクションによってメモリ管理を自動化しています。しかし、システムプログラミングや特定の最適化のシナリオでは、Goの型システムを迂回して直接メモリを操作する必要が生じることがあります。このために導入されたのがunsafeパッケージであり、その中でもunsafe.Pointerは任意のポインタ型と相互変換可能な特殊な型として機能します。また、uintptrはポインタを整数として扱うための型です。

このコミットが行われた2008年12月は、Go言語がまだ開発の初期段階にあり、コンパイラやランタイムの基本的な機能が構築されている時期でした。unsafe.Pointeruintptr間の変換は、Goのメモリモデルとガベージコレクタの整合性を保ちつつ、低レベル操作を可能にする上で非常にデリケートな部分です。コンパイラは、これらの「unsafe」な操作がGoのメモリ安全性の原則を完全に破壊しないように、厳密なルールを適用する必要があります。

この変更は、コンパイラの型チェックフェーズ(当時のwalk.cが担当していた機能)において、unsafe.Pointerと他のポインタ型またはuintptrとの間の変換が正しく処理されるようにするためのものです。これにより、開発者は必要に応じて低レベルの操作を行いつつも、コンパイラがその操作の妥当性をある程度検証できるようになります。

前提知識の解説

unsafe.Pointeruintptr

  • unsafe.Pointer: Go言語のunsafeパッケージで提供される特殊なポインタ型です。任意の型のポインタと相互に変換できます。これはC言語のvoid*に似ていますが、Goのガベージコレクタ(GC)がunsafe.Pointerの値を認識し、それが指すオブジェクトがGCによって移動された場合、unsafe.Pointerの値も更新されます。これにより、unsafe.Pointerが指すオブジェクトは、そのポインタが到達可能である限り「生きている」と見なされ、ガベージコレクションの対象から外れます。
  • uintptr: 符号なし整数型で、ポインタの値を保持するのに十分な大きさがあります。uintptrはポインタの値を整数として扱いますが、unsafe.Pointerとは異なり、ガベージコレクタはuintptrの値をポインタとして認識しません。したがって、uintptrが指すオブジェクトがGCによって移動されたり、回収されたりしても、uintptrの値は更新されず、古いアドレスを指したままになる可能性があります(ダングリングポインタ)。このため、uintptrを介したポインタ演算は非常に慎重に行う必要があり、通常はunsafe.Pointerを介して行われます。

Goコンパイラのwalk.c (当時の役割)

Goコンパイラは、ソースコードを機械語に変換する過程で複数のフェーズを経ます。当時のsrc/cmd/gc/walk.cは、コンパイラの「ウォーク」フェーズの一部を担っていました。このフェーズは、抽象構文木(AST)を走査し、型チェック、最適化、中間表現への変換など、様々なセマンティックな処理を行う役割を持っていました。現代のGoコンパイラでは、この機能はcmd/compile/internal/noderなどのパッケージに再構築されていますが、基本的な概念(ASTの走査と型チェック)は共通しています。

コンパイラ内部の型表現 (TANY, TUINTPTR, isptrto, isptr)

  • TANY: このコミットの文脈では、TANYはコンパイラ内部で「任意の型」や「ジェネリックなポインタ型」を表すために使用されていた可能性があります。特にunsafe.Pointerのような特殊な型を扱う際に、より一般的なポインタ型として扱われることがあります。現代のGoコンパイラでは、unsafe.Pointercmd/compile/internal/typesパッケージ内でTUNSAFEPTRとして明示的に表現されます。
  • TUINTPTR: コンパイラ内部でuintptr型を表すための定数または列挙型です。
  • isptrto(type1, type2): コンパイラ内部のヘルパー関数で、type1type2へのポインタ型であるかどうか、またはtype1type2に変換可能であるかどうかをチェックするような意味合いを持つと考えられます。このコミットの文脈では、isptrto(n->type, TANY)は、nの型がTANY(つまり、unsafe.Pointerまたは任意のポインタ型)へのポインタであるかどうかをチェックしていると解釈できます。
  • isptr[type_enum]: コンパイラ内部の配列またはマップで、特定の型列挙値(type_enum)がポインタ型であるかどうかを示すブール値を保持していると考えられます。例えば、isptr[l->type->etype]は、lの型がポインタ型であるかどうかをチェックしています。

これらの内部表現や関数は、コンパイラがGoの型システム、特にunsafeパッケージの厳密なルールを強制するために使用されます。

技術的詳細

Goコンパイラは、unsafe.Pointeruintptrの変換を非常に厳密に扱います。これは、これらの操作がGoのメモリ安全モデルを迂回するため、誤用すると深刻なバグ(クラッシュ、データ破損、セキュリティ脆弱性など)を引き起こす可能性があるからです。

このコミットが追加したロジックは、コンパイラの型チェックフェーズにおいて、以下の変換パターンを許可するためのものです。

  1. 任意のポインタ型からunsafe.Pointerへの変換: Goの仕様では、任意の型のポインタ(例: *int, *string)はunsafe.Pointerに変換できます。これは、unsafe.Pointerが汎用的なポインタとして機能するためです。
  2. uintptrからunsafe.Pointerへの変換: uintptrは整数型ですが、その値がメモリアドレスを表す場合、unsafe.Pointerに変換することで、そのアドレスを指すポインタとして扱うことができます。ただし、この変換はGCの追跡から外れるリスクがあるため、通常は一時的な操作として、かつGCがオブジェクトを移動する前に完了させる必要があります。
  3. unsafe.Pointerから任意のポインタ型への変換: unsafe.Pointerは汎用的なポインタであるため、任意の具体的なポインタ型(例: *int)に変換できます。
  4. unsafe.Pointerからuintptrへの変換: unsafe.Pointerが指すメモリアドレスを整数値として取得するためにuintptrに変換できます。この変換後、GCはそのuintptrの値を追跡しないため、注意が必要です。

コンパイラは、これらの変換がGoのunsafeパッケージのルールに準拠していることを確認するために、内部的な型情報(etypeなど)とヘルパー関数(isptrto, isptr)を使用します。特に、unsafe.Pointeruintptr間の変換は、ガベージコレクタとの相互作用を考慮する必要があるため、コンパイラはこれらの変換がプログラムのセマンティクスを損なわないように、特定のコンテキストでのみ許可します。

このコミットのコードは、コンパイラのASTウォーク中に、これらの変換ノードを検出した際に、それが有効な変換であるかどうかを判断するための条件分岐を追加しています。もし変換が有効であれば、コンパイラは処理を続行し、そうでなければエラーを報告します。

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

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -622,6 +622,22 @@ loop:
 		\t\tgoto ret;
 		\t}\
 
+\t\t// convert to unsafe.pointer
+\t\tif(isptrto(n->type, TANY)) {
+\t\t\tif(isptr[l->type->etype])
+\t\t\t\tgoto ret;
+\t\t\tif(l->type->etype == TUINTPTR)\
+\t\t\t\tgoto ret;
+\t\t}\
+
+\t\t// convert from unsafe.pointer
+\t\tif(isptrto(l->type, TANY)) {
+\t\t\tif(isptr[n->type->etype])
+\t\t\t\tgoto ret;
+\t\t\tif(n->type->etype == TUINTPTR)\
+\t\t\t\tgoto ret;
+\t\t}\
+
 \t\tif(l->type != T)\
 \t\t\tyyerror(\"cannot convert %T to %T\", l->type, t);\
 \t\tgoto ret;\

コアとなるコードの解説

このコミットでは、src/cmd/gc/walk.c内の既存の型変換ロジックに、unsafe.Pointeruintptrに関する新しいチェックが追加されています。

追加されたコードブロックは大きく2つの部分に分かれています。

1. unsafe.Pointerへの変換を許可するロジック (// convert to unsafe.pointer)

		// convert to unsafe.pointer
		if(isptrto(n->type, TANY)) {
			if(isptr[l->type->etype])
				goto ret;
			if(l->type->etype == TUINTPTR)
				goto ret;
		}
  • if(isptrto(n->type, TANY)): この条件は、変換先の型(n->type)がTANY、つまりunsafe.Pointerまたは任意のポインタ型であることをチェックしています。nは変換結果のノード(左辺)を表し、lは変換元のノード(右辺)を表すと推測されます。
  • if(isptr[l->type->etype]) goto ret;: もし変換元の型(l->type)がポインタ型(*int, *stringなど)であれば、その変換は許可され、処理を終了して呼び出し元に戻ります(goto ret)。これは、任意のポインタ型からunsafe.Pointerへの変換が有効であることを意味します。
  • if(l->type->etype == TUINTPTR) goto ret;: もし変換元の型(l->type)がuintptr型であれば、その変換も許可され、処理を終了します。これは、uintptrからunsafe.Pointerへの変換が有効であることを意味します。

このブロックは、任意のポインタ型またはuintptrからunsafe.Pointerへの変換をコンパイラが認識し、許可するためのロジックです。

2. unsafe.Pointerからの変換を許可するロジック (// convert from unsafe.pointer)

		// convert from unsafe.pointer
		if(isptrto(l->type, TANY)) {
			if(isptr[n->type->etype])
				goto ret;
			if(n->type->etype == TUINTPTR)
				goto ret;
		}
  • if(isptrto(l->type, TANY)): この条件は、変換元の型(l->type)がTANY、つまりunsafe.Pointerまたは任意のポインタ型であることをチェックしています。
  • if(isptr[n->type->etype]) goto ret;: もし変換先の型(n->type)がポインタ型であれば、その変換は許可され、処理を終了します。これは、unsafe.Pointerから任意のポインタ型への変換が有効であることを意味します。
  • if(n->type->etype == TUINTPTR) goto ret;: もし変換先の型(n->type)がuintptr型であれば、その変換も許可され、処理を終了します。これは、unsafe.Pointerからuintptrへの変換が有効であることを意味します。

このブロックは、unsafe.Pointerから任意のポインタ型またはuintptrへの変換をコンパイラが認識し、許可するためのロジックです。

これらの変更により、Goコンパイラはunsafe.Pointerと他のポインタ型やuintptrとの間の変換を、Go言語の仕様とunsafeパッケージの意図に従って正しく処理できるようになりました。これは、Go言語が低レベルのメモリ操作をサポートしつつ、その安全性を維持するための基盤となる重要なステップでした。

関連リンク

参考にした情報源リンク