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

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

このコミットは、Goコンパイラ(gc)における型チェックの挙動を修正し、appendおよびcopy操作において非エクスポートフィールドへの暗黙的な代入を禁止するものです。具体的には、src/cmd/gc/typecheck.cに型チェックロジックが追加され、この変更を検証するための新しいテストケースtest/fixedbugs/bug378.goが追加されています。

コミット

  • コミットハッシュ: 151b2f15094168946993448f7f4a5e2a8441bd76
  • Author: Luuk van Dijk lvd@golang.org
  • Date: Wed Nov 9 11:17:06 2011 +0100

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

https://github.com/golang/go/commit/151b2f15094168946993448f7f4a5e2a8441bd76

元コミット内容

gc: Fail on implicit assigment to non-exported fields in copy and append.

Fixes #1387.

R=rsc
CC=golang-dev
https://golang.org/cl/5348046

変更の背景

Go言語では、パッケージ外部からアクセス可能な識別子(変数、関数、型、フィールドなど)は「エクスポートされた」ものと呼ばれ、大文字で始まります。一方、小文字で始まる識別子は「非エクスポート(または内部)フィールド」と呼ばれ、そのパッケージ内でのみアクセス可能です。このルールは、カプセル化を強制し、APIの安定性を保つために重要です。

このコミット以前のGoコンパイラには、appendcopyといった組み込み関数を使用する際に、このエクスポートルールが適切に適用されないというバグが存在していました。具体的には、非エクスポートフィールドを持つ構造体のスライスに対してappendcopyを行うと、本来許されないはずの非エクスポートフィールドへの暗黙的な代入が発生してしまう可能性がありました。これは、Go言語の設計思想に反する挙動であり、予期せぬデータ変更やセキュリティ上の問題を引き起こす可能性がありました。

この問題は、GoのIssue #1387として報告されており、このコミットはその問題を修正することを目的としています。

前提知識の解説

Go言語のエクスポートルール

Go言語では、識別子(変数名、関数名、型名、構造体のフィールド名など)の最初の文字が大文字である場合、その識別子はパッケージ外に「エクスポート」されます。つまり、他のパッケージからその識別子にアクセスできます。一方、最初の文字が小文字である場合、その識別子はパッケージ内に限定され、パッケージ外からはアクセスできません。これは、オブジェクト指向プログラミングにおけるカプセル化の概念をGo言語で実現する方法の一つです。

例:

package mypackage

type MyStruct struct {
    ExportedField   int // エクスポートされる
    unexportedField int // エクスポートされない
}

func ExportedFunction() { // エクスポートされる
    // ...
}

func unexportedFunction() { // エクスポートされない
    // ...
}

append関数

appendはGoの組み込み関数で、スライスに要素を追加するために使用されます。 newSlice = append(slice, elements...) の形式で使われ、sliceelementsを追加した新しいスライスを返します。必要に応じて、基盤となる配列の容量を増やします。

copy関数

copyもGoの組み込み関数で、ソーススライスの要素をデスティネーションスライスにコピーするために使用されます。 copiedCount = copy(dst, src) の形式で使われ、コピーされた要素の数を返します。コピーは、デスティネーションスライスの長さとソーススライスの長さの小さい方まで行われます。

Goコンパイラ (gc) と型チェック

gcはGo言語の公式コンパイラです。コンパイルプロセスの一部として、ソースコードがGo言語の仕様に準拠しているかを確認する「型チェック」フェーズがあります。このフェーズでは、変数の型が正しく使用されているか、関数の引数と戻り値の型が一致しているか、エクスポートルールが守られているかなどが検証されます。このコミットは、この型チェックフェーズに新しい制約を追加するものです。

技術的詳細

このコミットの核心は、src/cmd/gc/typecheck.cファイルにexportassignokという概念を導入し、appendcopyの型チェックロジックに組み込んだ点です。

exportassignokは、Goコンパイラの内部関数またはロジックの一部であり、特定の型が非エクスポートフィールドを含んでいる場合に、その型への暗黙的な代入が許可されるべきかどうかを判断します。このコミットでは、appendcopyの操作において、要素の型が非エクスポートフィールドを持つ構造体である場合、その操作をエラーとして扱うように変更されています。

具体的には、以下の変更が行われています。

  1. append関数への適用: appendの型チェックロジックにおいて、スライスの要素型(t->type)がexportassignokの条件を満たさない場合、つまり非エクスポートフィールドへの暗黙的な代入が発生する可能性がある場合に、コンパイルエラー(yyerror)を発生させ、処理を中断(goto error)します。

  2. copy関数への適用: copyの型チェックロジックにおいて、デスティネーションスライスの要素型(n->left->type->type)がexportassignokの条件を満たさない場合、同様にコンパイルエラーを発生させ、処理を中断します。

この変更により、コンパイラはappendcopyを通じて非エクスポートフィールドが不正に操作されることを事前に検出し、開発者に警告するようになります。これにより、Go言語のカプセル化の原則がより厳密に守られるようになります。

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

src/cmd/gc/typecheck.c

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -1032,6 +1032,9 @@ reswitch:
 			yyerror("first argument to append must be slice; have %lT", t);
 			goto error;
 		}
+		if(!exportassignok(t->type, "append"))
+			goto error;
+
 		if(n->isddd) {
 			if(args->next == nil) {
 				yyerror("cannot use ... on first argument to append");
@@ -1098,6 +1101,8 @@ reswitch:
 			yyerror("arguments to copy have different element types: %lT and %lT", n->left->type, n->right->type);
 			goto error;
 		}
+		if(!exportassignok(n->left->type->type, "copy"))
+			goto error;
 		goto ret;

 	case OCONV:

test/fixedbugs/bug378.go

--- /dev/null
+++ b/test/fixedbugs/bug378.go
@@ -0,0 +1,27 @@
+// errchk $G $D/$F.go
+
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Issue 1387
+package foo
+
+import "bytes"
+
+func i() {
+	a := make([]bytes.Buffer, 1)
+	b := a[0] // ERROR "unexported field"
+}
+
+func f() {
+	a := make([]bytes.Buffer, 1)
+	a = append(a, a...) // ERROR "unexported field"
+}
+
+
+func g() {
+	a := make([]bytes.Buffer, 1)
+	b := make([]bytes.Buffer, 1)
+	copy(b, a)	// ERROR "unexported field"
+}

コアとなるコードの解説

src/cmd/gc/typecheck.c の変更

  • appendの型チェック部分: if(!exportassignok(t->type, "append")) の行が追加されました。 t->typeappendの第一引数(スライス)の要素型を表します。この要素型がexportassignokのチェックをパスしない場合(つまり、非エクスポートフィールドへの暗黙的な代入が問題となる場合)、goto error;によってコンパイルエラーが発生します。

  • copyの型チェック部分: if(!exportassignok(n->left->type->type, "copy")) の行が追加されました。 n->left->type->typecopyの第一引数(デスティネーションスライス)の要素型を表します。この要素型がexportassignokのチェックをパスしない場合、同様にgoto error;によってコンパイルエラーが発生します。

これらの変更により、appendcopyが非エクスポートフィールドを持つ型を扱う際に、Go言語のカプセル化ルールに違反する操作を未然に防ぐことができるようになりました。

test/fixedbugs/bug378.go の追加

このファイルは、上記の変更が正しく機能することを確認するためのテストケースです。 bytes.Buffer型は、内部に非エクスポートフィールドを持っています。このテストでは、bytes.Bufferのスライスに対してappendcopy、および直接的な代入操作を試みています。

  • b := a[0]bytes.Buffer型の要素を直接代入しようとしています。これは非エクスポートフィールドへのアクセスとなるため、ERROR "unexported field"が期待されます。
  • a = append(a, a...)bytes.Bufferのスライスに対してappendを実行しています。この操作も非エクスポートフィールドへの暗黙的な代入を引き起こすため、ERROR "unexported field"が期待されます。
  • copy(b, a)bytes.Bufferのスライス間でcopyを実行しています。これも同様に非エクスポートフィールドへの暗黙的な代入となるため、ERROR "unexported field"が期待されます。

// errchk $G $D/$F.goというコメントは、このテストファイルがコンパイルエラーを発生させることを期待していることを示しています。このテストが成功するということは、コンパイラが非エクスポートフィールドへの不正な操作を正しく検出していることを意味します。

関連リンク

  • Go Change List: https://golang.org/cl/5348046

参考にした情報源リンク

  • このコミットのGitHubページ: https://github.com/golang/go/commit/151b2f15094168946993448f7f4a5e2a8441bd76
  • Go言語の公式ドキュメント(エクスポートルール、スライス、append、copy関数に関する一般的な情報)
  • Go言語のIssueトラッカー(ただし、Issue #1387は現在のGitHubリポジトリでは直接見つかりませんでした。これは、Goの初期のIssueトラッキングシステムや内部的なIssue番号である可能性があります。)
  • bytes.BufferのGoドキュメント(内部構造を確認するため)
  • Goコンパイラのソースコード(src/cmd/gc/ディレクトリ内の他のファイルを参照し、exportassignokの具体的な実装やyyerrorの挙動を理解するため)
  • Go言語の仕様書(型システム、エクスポートルールに関する公式な定義を確認するため)