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

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

このコミットは、Goコンパイラのsrc/cmd/gc/typecheck.cファイルに対する変更です。src/cmd/gcはGoコンパイラのフロントエンドの一部であり、typecheck.cはその中でも型チェック(type checking)を担当する重要なファイルです。型チェックは、プログラムがGo言語の型システム規則に準拠していることを検証するプロセスであり、コンパイルの初期段階で行われます。このファイルは、変数、式、関数呼び出しなどの型が正しく使用されているかを確認し、型に関するエラーを検出します。

コミット

commit 85d60a727cc25cb10cf2a3ff1fd68463a9749d34
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Thu Nov 1 18:45:19 2012 +0100

    cmd/gc: do simple bounds checking of constant indices/slices in typecheck.
    
    This should make the compiler emit errors specific to the bounds checking instead of overflow errors on the underlying types.
    
    Updates #4232.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6783054

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

https://github.com/golang/go/commit/85d60a727cc25cb10cf2a3ff1fd68463a9749d34

元コミット内容

cmd/gc: do simple bounds checking of constant indices/slices in typecheck.

This should make the compiler emit errors specific to the bounds checking instead of overflow errors on the underlying types.

Updates #4232.

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

変更の背景

この変更の背景には、Goコンパイラが定数インデックスやスライスに対して、より具体的で分かりやすいエラーメッセージを生成する必要性がありました。以前のコンパイラでは、配列やスライスのインデックスが定数で指定され、それが範囲外であった場合、コンパイラは「基になる型のオーバーフローエラー」として報告してしまうことがありました。これは、インデックス値が大きすぎるために、その値を格納する内部的な型がオーバーフローした結果としてエラーが発生していたためです。

しかし、このエラーメッセージは、プログラマにとって問題の根本原因(インデックスが範囲外であること)を直接的に示していませんでした。プログラマは、オーバーフローエラーを見て、なぜそれが起こったのか、そしてどのように修正すべきかを理解するのに苦労する可能性がありました。

このコミットは、この問題を解決するために導入されました。定数インデックスやスライスに対して、型チェックの段階で単純な境界チェックを行うことで、コンパイラは「インデックスが負である」または「配列の境界を超えている」といった、より正確で意味のあるエラーメッセージを直接出力できるようになります。これにより、開発者はコンパイル時に問題を迅速に特定し、修正できるようになります。

コミットメッセージにある Updates #4232 は、この変更がGoのIssue 4232に関連していることを示していますが、現在のGoリポジトリではこの番号のIssueは見つかりませんでした。しかし、その意図は、定数インデックスの境界チェックに関する既存の問題を解決することにあったと考えられます。

前提知識の解説

Go言語の型チェック (Type Checking)

型チェックは、コンパイラがソースコードを解析し、プログラム内のすべての操作がGo言語の型規則に準拠していることを確認するプロセスです。例えば、整数型の変数に文字列を代入しようとしたり、存在しないメソッドを呼び出したりするような型に関する誤りを検出します。型チェックは、プログラムの実行前に多くのエラーを捕捉し、実行時エラーのリスクを減らすのに役立ちます。

配列とスライス (Arrays and Slices)

  • 配列 (Array): Goの配列は、同じ型の要素を固定長で連続して格納するデータ構造です。配列の長さは型の一部であり、コンパイル時に決定されます。例: var a [5]int は5つの整数を格納できる配列を宣言します。
  • スライス (Slice): スライスは配列の上に構築された、より柔軟なデータ構造です。スライスは、基になる配列の一部を参照し、動的に長さを変更できます。スライスは、長さと容量(基になる配列の残りの要素数)を持ちます。例: var s []int はスライスを宣言します。

配列やスライスの要素にアクセスするには、インデックスを使用します。インデックスは0から始まり、length - 1までです。

定数伝播 (Constant Propagation)

コンパイラの最適化手法の一つで、プログラム中の定数値を可能な限り伝播させる(置き換える)ことです。例えば、x = 10; y = x + 5; というコードがある場合、コンパイラはy = 10 + 5; と書き換え、さらに y = 15; と書き換えることができます。これにより、実行時の計算を減らし、パフォーマンスを向上させることができます。このコミットでは、定数インデックスが型チェック時にその値として扱われることが重要です。

cmd/gc

cmd/gcは、Go言語の公式コンパイラの一部であり、Goのソースコードを機械語に変換する主要なツールです。gcは「Go Compiler」の略です。Goコンパイラは、フロントエンド(構文解析、型チェック、中間表現の生成など)とバックエンド(コード生成、最適化など)に分かれています。src/cmd/gc/typecheck.cは、このフロントエンドの型チェックフェーズを担当するC言語のソースファイルです。

OLITERAL

Goコンパイラの内部表現において、OLITERALはリテラル値(定数)を表すノードの操作コード(opcode)です。例えば、10"hello"のような値はOLITERALノードとして表現されます。このコミットでは、インデックスがOLITERALであるかどうかをチェックすることで、それが定数であるかどうかを判断しています。

mpgetfix

mpgetfixは、Goコンパイラの内部で多倍長整数(multi-precision integer)を扱うための関数です。OLITERALノードに格納されている定数値(特に整数)を取得するために使用されます。

isfixedarray

isfixedarrayは、Goコンパイラの内部で型が固定長の配列であるかどうかを判断するための関数です。スライスとは異なり、配列はコンパイル時にサイズが固定されます。

t->bound

t->boundは、Goコンパイラの内部で配列の境界(サイズ)を表すフィールドです。isfixedarraytrueの場合、このt->boundには配列の要素数が格納されています。

yyerror

yyerrorは、Goコンパイラがエラーメッセージを出力するために使用する関数です。コンパイル時に検出されたエラーをユーザーに報告します。

技術的詳細

このコミットは、src/cmd/gc/typecheck.cファイルのcase TARRAY:case TSLICE:のセクションに、定数インデックスに対する境界チェックのロジックを追加しています。

配列インデックスのチェック (case TARRAY:)

変更前は、配列インデックスが非整数型である場合にのみエラーを報告していました。 変更後は、以下のチェックが追加されました。

  1. 非整数型インデックスのチェック: 以前と同様に、インデックスが非整数型である場合はエラーを報告します。
  2. 定数インデックスのチェック:
    • インデックスがOLITERAL(定数)である場合、その値を取得します。
    • 負のインデックスのチェック: mpgetfix(n->right->val.u.xval) < 0 でインデックスが負であるかをチェックします。負の場合、「invalid %s index %N (index must be non-negative)」というエラーメッセージを出力します。%sには「array」または「slice」が入ります。
    • 配列の境界チェック: isfixedarray(t) && t->bound > 0 && mpgetfix(n->right->val.u.xval) >= t->bound で、固定長配列であり、かつインデックスが配列の境界(t->bound)以上であるかをチェックします。境界を超える場合、「invalid array index %N (out of bounds for %d-element array)」というエラーメッセージを出力します。

スライスインデックスのチェック (case TSLICE:)

スライス操作では、s[low:high]のようにlowhighの2つのインデックスが関与する場合があります。このコミットでは、これらのインデックスが定数である場合に負のチェックを追加しています。

  1. n->right->left (lowインデックス) のチェック:
    • n->right->left->op == OLITERAL && mpgetfix(n->right->left->val.u.xval) < 0 で、lowインデックスが定数であり、かつ負であるかをチェックします。負の場合、「invalid slice index %N (index must be non-negative)」というエラーメッセージを出力します。
  2. n->right->right (highインデックス) のチェック:
    • n->right->right->op == OLITERAL && mpgetfix(n->right->right->val.u.xval) < 0 で、highインデックスが定数であり、かつ負であるかをチェックします。負の場合、「invalid slice index %N (index must be non-negative)」というエラーメッセージを出力します。

これらの変更により、コンパイル時に定数インデックスの不正な使用を早期に検出し、より明確なエラーメッセージを開発者に提供できるようになりました。これにより、デバッグの労力が削減され、コードの品質が向上します。

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

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -817,9 +817,18 @@ reswitch:
 
 		case TARRAY:
 			defaultlit(&n->right, T);
-			if(n->right->type != T && !isint[n->right->type->etype])
-				yyerror("non-integer array index %N", n->right);
 			n->type = t->type;
+			if(n->right->type != T && !isint[n->right->type->etype]) {
+				yyerror("non-integer array index %N", n->right);
+				break;
+			}
+			if(n->right->op == OLITERAL) {
+			       	if(mpgetfix(n->right->val.u.xval) < 0) {
+					why = isfixedarray(t) ? "array" : "slice";
+					yyerror("invalid %s index %N (index must be non-negative)", why, n->right);
+				} else if(isfixedarray(t) && t->bound > 0 && mpgetfix(n->right->val.u.xval) >= t->bound)
+					yyerror("invalid array index %N (out of bounds for %d-element array)", n->right, t->bound);
+			}
 			break;
 
 		case TMAP:
@@ -912,6 +921,8 @@ reswitch:
 				yyerror("invalid slice index %N (type %T)", n->right->left, t);
 				goto error;
 			}
+			if(n->right->left->op == OLITERAL && mpgetfix(n->right->left->val.u.xval) < 0)
+				yyerror("invalid slice index %N (index must be non-negative)", n->right->left);
 		}
 		if(n->right->right != N) {
 			if((t = n->right->right->type) == T)
@@ -920,6 +931,8 @@ reswitch:
 				yyerror("invalid slice index %N (type %T)", n->right->right, t);
 				goto error;
 			}
+			if(n->right->right->op == OLITERAL && mpgetfix(n->right->right->val.u.xval) < 0)
+				yyerror("invalid slice index %N (index must be non-negative)", n->right->right);
 		}
 		l = n->left;
 		if((t = l->type) == T)

コアとなるコードの解説

case TARRAY: の変更点

 			defaultlit(&n->right, T);
-			if(n->right->type != T && !isint[n->right->type->etype])
-				yyerror("non-integer array index %N", n->right);
 			n->type = t->type;
+			if(n->right->type != T && !isint[n->right->type->etype]) {
+				yyerror("non-integer array index %N", n->right);
+				break;
+			}
+			if(n->right->op == OLITERAL) {
+			       	if(mpgetfix(n->right->val.u.xval) < 0) {
+					why = isfixedarray(t) ? "array" : "slice";
+					yyerror("invalid %s index %N (index must be non-negative)", why, n->right);
+				} else if(isfixedarray(t) && t->bound > 0 && mpgetfix(n->right->val.u.xval) >= t->bound)
+					yyerror("invalid array index %N (out of bounds for %d-element array)", n->right, t->bound);
+			}
  • 変更前: if(n->right->type != T && !isint[n->right->type->etype]) yyerror("non-integer array index %N", n->right);
    • これは、配列インデックスが整数型でない場合にエラーを報告する既存のチェックです。
  • 変更後:
    • if(n->right->type != T && !isint[n->right->type->etype]) { yyerror("non-integer array index %N", n->right); break; }
      • 既存の非整数型インデックスのチェックが{}ブロックで囲まれ、エラー発生時にbreakで処理を抜けるようになりました。
    • if(n->right->op == OLITERAL)
      • インデックスが定数(リテラル)である場合にのみ、以下の境界チェックを実行します。
    • if(mpgetfix(n->right->val.u.xval) < 0)
      • 定数インデックスの値が負であるかをチェックします。
      • why = isfixedarray(t) ? "array" : "slice";
        • エラーメッセージのために、対象が配列かスライスかを判断します。
      • yyerror("invalid %s index %N (index must be non-negative)", why, n->right);
        • 負のインデックスに対するエラーメッセージを出力します。
    • else if(isfixedarray(t) && t->bound > 0 && mpgetfix(n->right->val.u.xval) >= t->bound)
      • 対象が固定長配列であり、かつ配列のサイズが0より大きく、定数インデックスが配列の境界(t->bound)以上であるかをチェックします。
      • yyerror("invalid array index %N (out of bounds for %d-element array)", n->right, t->bound);
        • 配列の境界を超えたインデックスに対するエラーメッセージを出力します。

case TSLICE: の変更点

 			}
+			if(n->right->left->op == OLITERAL && mpgetfix(n->right->left->val.u.xval) < 0)
+				yyerror("invalid slice index %N (index must be non-negative)", n->right->left);
 		}
 		if(n->right->right != N) {
 			if((t = n->right->right->type) == T)
@@ -920,6 +931,8 @@ reswitch:
 				yyerror("invalid slice index %N (type %T)", n->right->right, t);
 				goto error;
 			}
+			if(n->right->right->op == OLITERAL && mpgetfix(n->right->right->val.u.xval) < 0)
+				yyerror("invalid slice index %N (index must be non-negative)", n->right->right);
 		}
  • if(n->right->left->op == OLITERAL && mpgetfix(n->right->left->val.u.xval) < 0)
    • スライスのlowインデックス(n->right->left)が定数であり、かつ負であるかをチェックします。
    • yyerror("invalid slice index %N (index must be non-negative)", n->right->left);
      • 負のlowインデックスに対するエラーメッセージを出力します。
  • if(n->right->right->op == OLITERAL && mpgetfix(n->right->right->val.u.xval) < 0)
    • スライスのhighインデックス(n->right->right)が定数であり、かつ負であるかをチェックします。
    • yyerror("invalid slice index %N (index must be non-negative)", n->right->right);
      • 負のhighインデックスに対するエラーメッセージを出力します。

これらの変更により、Goコンパイラは定数インデックスの配列およびスライスアクセスにおいて、より厳密かつ具体的なコンパイル時エラーを提供できるようになり、開発者の生産性向上に貢献しています。

関連リンク

参考にした情報源リンク

  • Go言語のコンパイラ設計に関する一般的な知識
  • Go言語の配列とスライスの動作に関する知識
  • Goコンパイラの内部構造に関する一般的な知識
  • Go Issue 4232は現在のGoリポジトリでは見つかりませんでした。