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

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

このコミットは、Goコンパイラのcmd/gcパッケージにおけるwalk.cファイルに対する変更です。具体的には、構造体の比較(==演算子)がインライン化される際に、unsafe.Pointer型が安全モード(safemode)であっても参照できるようになる修正を導入しています。これにより、reflect.Valueのようなunsafe.Pointerを含む構造体の比較が正しく行われるようになります。

コミット

commit 0218dfe7eb4afded4614a51d03bfedfec995378d
Author: Russ Cox <rsc@golang.org>
Date:   Mon Sep 9 13:11:41 2013 -0400

    cmd/gc: allow inlined struct == to mention unsafe.Pointer even in safe mode
    
    Fixes #5578.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/13417044

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

https://github.com/golang/go/commit/0218dfe7eb4afded4614a51d03bfedfec995378d

元コミット内容

cmd/gc: インライン化された構造体の==演算子が、安全モードであってもunsafe.Pointerを参照することを許可する。 Issue #5578を修正。

変更の背景

Go言語では、構造体同士の比較(==演算子)は、その構造体が比較可能なフィールドのみで構成されている場合に可能です。しかし、reflect.Valueのような特定の型は、内部にエクスポートされていないunsafe.Pointer型のフィールドを持つことがあります。

Goコンパイラには、コード生成の過程で「安全モード(safemode)」という概念が存在しました。この安全モードは、通常、unsafeパッケージの使用を制限し、型安全性を強制するためのものです。しかし、構造体の比較がコンパイラによって内部的にインライン化される際、特にreflect.Valueのような型が含まれている場合、コンパイラが生成する比較コードが意図せずunsafe.Pointerを参照してしまうことがありました。

この状況下で安全モードが有効になっていると、コンパイラはunsafe.Pointerへの参照を不許可と判断し、コンパイルエラーを引き起こしていました。このコミットは、このような特定のケース(構造体の==演算子のインライン化)において、一時的に安全モードを無効にすることで、unsafe.Pointerへの参照を許可し、コンパイルエラーを回避することを目的としています。これにより、reflect.Valueを含む構造体の比較が正しく機能するようになります。

前提知識の解説

unsafe.Pointer

unsafe.PointerはGo言語のunsafeパッケージで提供される特殊なポインタ型です。これはC言語のvoid*に似ており、任意の型のポインタを保持できます。Goの厳格な型システムをバイパスし、低レベルなメモリ操作を可能にするために使用されます。

  • 型安全性からの逸脱: unsafe.Pointerを使用すると、Goの型安全性が損なわれます。これにより、メモリ破損や未定義の動作を引き起こす可能性があります。
  • 用途: 主にC言語との相互運用(cgo)、特定のデータ構造の最適化、またはリフレクションのような高度な機能の実装に使用されます。
  • 変換規則:
    • 任意のポインタ型*Tunsafe.Pointerに変換できます。
    • unsafe.Pointerは任意のポインタ型*Tに変換できます。
    • uintptrunsafe.Pointerに変換できます。
    • unsafe.Pointeruintptrに変換できます。 uintptrはポインタのアドレスを保持できる整数型であり、ポインタ演算を行う際に一時的に使用されますが、ガベージコレクタはuintptrを追跡しないため、注意が必要です。

reflect.Value

reflect.ValueはGo言語のreflectパッケージの一部であり、Goプログラムの実行時における値の動的な検査と操作を可能にします。

  • リフレクション: reflectパッケージは、プログラムが自身の構造を検査し、実行時にオブジェクトの型や値を操作する「リフレクション」機能を提供します。
  • 値の表現: reflect.Valueは、Goの変数や式の実行時データを表現します。これにより、型がコンパイル時に不明な場合でも、その値にアクセスしたり、場合によっては変更したりできます。
  • 内部構造: reflect.Valueの内部には、値の型情報やデータへのポインタが含まれています。このデータへのポインタが、エクスポートされていないunsafe.Pointer型である場合があります。

Goコンパイラのwalkフェーズとwalkexpr関数

Goコンパイラは、ソースコードを機械語に変換する過程で複数のフェーズを経ます。その一つが「walk(ウォーク)」フェーズです。

  • walkフェーズの目的: walkフェーズは、抽象構文木(AST)を、コンパイラのバックエンドが処理しやすい、より単純な形式に変換する役割を担います。複雑な式や操作を、より基本的な操作や内部関数呼び出しに分解します。
  • walkexpr関数: walkexprは、walkフェーズの中核をなす関数の一つで、AST内の式ノードを再帰的に走査し、変換を行います。例えば、特定の演算子を内部的な関数呼び出しに変換したり、定数式を簡略化したりします。このコミットの変更は、walkexpr関数内で構造体の比較(ONEケース)が処理される部分に影響を与えます。

Goコンパイラの「安全モード(safemode)」

Goコンパイラには、かつて「安全モード(safemode)」という概念が存在しました。これは、コンパイル時に特定の「安全でない」操作やパッケージの使用を制限するためのフラグのようなものでした。例えば、unsafeパッケージの直接的な使用を禁止したり、ローカルインポートを制限したりする目的で使われていた可能性があります。

このコミットの時点ではまだ存在していましたが、後のGoのバージョンでこの明示的な「安全モード」は削除されています。しかし、このコミットは、その当時のコンパイラの挙動と、unsafe.Pointerが関わる特定の状況下での型安全性の強制がどのように問題を引き起こしていたかを示しています。

技術的詳細

Goコンパイラは、構造体同士の比較(==演算子)を処理する際に、その構造体のフィールドを一つずつ比較するコードを生成します。この処理は、多くの場合、コンパイラによってインライン化されます。

問題は、reflect.Valueのような型が、その内部にエクスポートされていないunsafe.Pointer型のフィールドを持っている点にありました。Goの型システムでは、通常、unsafe.Pointerは明示的なunsafeパッケージの使用なしには扱えません。しかし、コンパイラが構造体の比較のために内部的に生成するコードは、reflect.Valueの内部フィールドにアクセスする必要があり、結果としてunsafe.Pointerを参照してしまうことがありました。

コンパイラの「安全モード」が有効な場合、このようなunsafe.Pointerへの内部的な参照であっても、安全でない操作と見なされ、コンパイルエラーが発生していました。これは、ユーザーが直接unsafeパッケージを使用していなくても、reflect.Valueを含む構造体を比較しようとすると問題になることを意味します。

このコミットの解決策は、walkexpr関数内で構造体の比較(ONEケース)を処理する直前に、一時的にsafemodeフラグを0(無効)に設定することです。比較コードの生成が完了した後、元のsafemodeの値に戻します。これにより、コンパイラが内部的にunsafe.Pointerを参照するコードを生成しても、安全モードによる制約を受けずにコンパイルが成功するようになります。

これは、コンパイラが生成する内部コードの特殊性を考慮し、ユーザーが意図しないunsafeな操作として扱われないようにするための、非常に具体的な修正です。

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

diff --git a/src/cmd/gc/walk.c b/src/cmd/gc/walk.c
index e539d25d32..b170d6e387 100644
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -340,7 +340,7 @@ walkexpr(Node **np, NodeList **init)
 	Node *r, *l, *var, *a;
 	NodeList *ll, *lr, *lpost;
 	Type *t;
-	int et;
+	int et, old_safemode;
 	int64 v;
 	int32 lno;
 	Node *n, *fn, *n1, *n2;
@@ -488,7 +488,15 @@ walkexpr(Node **np, NodeList **init)
 	case ONE:
 		walkexpr(&n->left, init);
 		walkexpr(&n->right, init);
+		// Disable safemode while compiling this code: the code we
+		// generate internally can refer to unsafe.Pointer.
+		// In this case it can happen if we need to generate an ==
+		// for a struct containing a reflect.Value, which itself has
+		// an unexported field of type unsafe.Pointer.
+		old_safemode = safemode;
+		safemode = 0;
 		walkcompare(&n, init);
+		safemode = old_safemode;
 		goto ret;
 
 	case OANDAND:

コアとなるコードの解説

変更はsrc/cmd/gc/walk.cファイルのwalkexpr関数内、case ONE:(比較演算子==または!=を処理する部分)にあります。

  1. int et, old_safemode;: walkexpr関数のローカル変数として、old_safemodeという新しい整数型変数が追加されました。これは、safemodeの現在の値を一時的に保存するために使用されます。

  2. // Disable safemode while compiling this code: the code we ...: このコメントは、なぜsafemodeを無効にする必要があるのかを説明しています。コンパイラが内部的に生成するコードがunsafe.Pointerを参照する可能性があるためです。特に、reflect.Valueを含む構造体の比較において、reflect.Valueがエクスポートされていないunsafe.Pointerフィールドを持っている場合にこの問題が発生します。

  3. old_safemode = safemode;: walkcompare関数(実際の比較コードを生成する関数)を呼び出す直前に、現在のsafemodeの値をold_safemodeに保存します。

  4. safemode = 0;: safemode0に設定します。これは、安全モードを一時的に無効にすることを意味します。この状態でwalkcompareが呼び出され、unsafe.Pointerへの参照を含む可能性のある比較コードが生成されます。

  5. walkcompare(&n, init);: 構造体の比較を行うためのコードを生成するwalkcompare関数が呼び出されます。この時点ではsafemodeが無効になっているため、unsafe.Pointerへの内部的な参照があってもエラーになりません。

  6. safemode = old_safemode;: walkcompareの呼び出しが完了した後、safemodeの値をold_safemodeに保存しておいた元の値に戻します。これにより、この特定の比較処理が終了した後は、コンパイラは再び元の安全モードの制約下で動作します。

この変更により、Goコンパイラは、reflect.Valueのような特殊な型を含む構造体の比較を、型安全性の原則を維持しつつ、正しく処理できるようになりました。

関連リンク

参考にした情報源リンク