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

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

このコミットは、Goコンパイラ(cmd/gc)において、スライスや配列のインデックスがint型の最大値を超える場合に警告を発するように修正を加えるものです。特にGOARCH=386アーキテクチャでのビルド問題を修正することを目的としています。

コミット

commit e3977f0d3a20ec7311b939fd2e60d78f4c6031ef
Author: Ian Lance Taylor <iant@golang.org>
Date:   Wed Nov 7 17:34:06 2012 -0800

    cmd/gc: warn about slice indexes larger than int in typecheck pass
    
    Fixes GOARCH=386 build.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6810098

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

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

元コミット内容

cmd/gc: warn about slice indexes larger than int in typecheck pass

Fixes GOARCH=386 build.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6810098

変更の背景

この変更の主な背景は、Goコンパイラがスライスや配列のインデックスを処理する際に、そのインデックスがint型の最大値を超えてしまうケースを適切に検出していなかった点にあります。特にGOARCH=386(32ビットアーキテクチャ)のような環境では、int型の範囲がint64などのより広い型に比べて小さいため、この問題が顕在化しやすくなります。

Go言語では、スライスや配列のインデックスは通常int型で表現されます。しかし、コンパイル時に定数として与えられたインデックス値が、ターゲットアーキテクチャのint型で表現できる範囲を超えてしまう場合、コンパイラはそれを不正なインデックスとして認識し、エラーまたは警告を出すべきです。このコミット以前は、このようなケースが適切に処理されず、結果としてGOARCH=386環境でのビルドが失敗するなどの問題が発生していました。

この修正は、コンパイル時の型チェックフェーズ(typecheck pass)において、インデックス値がint型の最大値を超えていないかを明示的にチェックし、超えている場合には適切なエラーメッセージを生成することで、この問題を解決します。これにより、コンパイル時に潜在的なランタイムエラーを防ぎ、より堅牢なコード生成を可能にします。

前提知識の解説

このコミットを理解するためには、以下のGoコンパイラおよびGo言語の基本的な概念を理解しておく必要があります。

  • Goコンパイラ (cmd/gc): Go言語の公式コンパイラの一つで、Goソースコードを機械語に変換する役割を担います。gcは"Go compiler"の略です。
  • 型チェックフェーズ (typecheck pass): コンパイラの重要なフェーズの一つで、ソースコードがGo言語の型システム規則に準拠しているかを確認します。変数や式の型が正しく、互換性があるか、関数呼び出しの引数が正しい型であるかなどを検証します。このフェーズで型に関するエラーが検出されます。
  • スライスと配列のインデックス: Go言語におけるスライス([]T)と配列([N]T)は、要素にアクセスするために整数インデックスを使用します。Goの仕様では、これらのインデックスは通常int型で表現されます。
  • int: Go言語のint型は、プラットフォームに依存する符号付き整数型です。32ビットシステム(例: GOARCH=386)では32ビット幅、64ビットシステムでは64ビット幅を持ちます。そのため、int型の最大値はアーキテクチャによって異なります。
    • 32ビットシステム: intの最大値は約2 * 10^9 (20億)
    • 64ビットシステム: intの最大値は約9 * 10^18 (900京)
  • GOARCH=386: Goのビルド環境変数の一つで、ターゲットアーキテクチャをIntel 80386互換の32ビットCPUに設定します。
  • mpcmpfixfix: Goコンパイラの内部で使用される多倍長整数(mpは"multi-precision"の略)を比較する関数です。fixは固定小数点数を意味し、ここではコンパイル時に確定する定数値を扱います。この関数は2つの多倍長整数を比較し、その大小関係を返します。
  • maxintval[TINT]: TINTはGoのint型を表す内部定数です。maxintval[TINT]は、ターゲットアーキテクチャのint型が表現できる最大値を示す多倍長整数定数です。
  • yyerror: Goコンパイラの字句解析器/構文解析器(yacc/bisonによって生成されることが多い)がエラーメッセージを出力するために使用する関数です。コンパイルエラーが発生した場合に、ユーザーに分かりやすいメッセージを表示します。
  • src/cmd/gc/typecheck.c: Goコンパイラのソースコードの一部で、型チェックロジックが実装されているC言語のファイルです。

技術的詳細

このコミットは、Goコンパイラのsrc/cmd/gc/typecheck.cファイル内の型チェックロジックに修正を加えています。具体的には、配列やスライスのインデックスが定数である場合に、その値がターゲットアーキテクチャのint型で表現可能な最大値を超えていないかを検証する新しいチェックを追加しています。

Goコンパイラは、ソースコードを抽象構文木(AST)に変換し、そのASTに対して様々な最適化やチェックを行います。型チェックはその重要なステップの一つです。配列やスライスのインデックスアクセス(例: a[i])は、AST上では特定のノード(ONAMEOINDEXなど)として表現されます。

この修正では、インデックスが定数である場合に、その定数値を多倍長整数として扱い、maxintval[TINT](ターゲットint型の最大値)と比較します。比較にはmpcmpfixfix関数が使用されます。

  • mpcmpfixfix(n->right->val.u.xval, maxintval[TINT]) > 0
    • n->right->val.u.xval: インデックスとして使用されている定数ノードの値(多倍長整数形式)。
    • maxintval[TINT]: ターゲットアーキテクチャのint型が表現できる最大値。
    • mpcmpfixfix0より大きい値を返す場合、それはインデックス値がmaxintval[TINT]よりも大きいことを意味します。

この条件が真である場合、つまりインデックスがint型の最大値を超えている場合、yyerror関数を呼び出してコンパイルエラーを発生させます。エラーメッセージは「invalid %s index %N (index too large)」となり、%sには「array」または「slice」が、%Nには不正なインデックス値が挿入されます。

このチェックは、以下の3つの主要な箇所に追加されています。

  1. 単一の配列/スライスインデックス(OINDEXノード)のチェック。
  2. スライス式の下限インデックス(OAS2FUNCノードのn->right->left)のチェック。
  3. スライス式の上限インデックス(OAS2FUNCノードのn->right->right)のチェック。

これにより、コンパイル時にインデックスのオーバーフローを早期に検出し、ランタイムでの予期せぬ動作やクラッシュを防ぐことができます。特に32ビットシステムでは、このチェックがより重要になります。

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

diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c
index 9b42772393..2d1dbd75f1 100644
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -828,6 +828,10 @@ reswitch:
 				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);
+				else if(mpcmpfixfix(n->right->val.u.xval, maxintval[TINT]) > 0) {
+					why = isfixedarray(t) ? "array" : "slice";
+					yyerror("invalid %s index %N (index too large)", why, n->right);
+				}
 			}
 			break;
 
@@ -947,6 +951,8 @@ reswitch:
 				yyerror("invalid slice index %N (index must be non-negative)", n->right->left);
 			else if(tp != nil && tp->bound > 0 && mpgetfix(n->right->left->val.u.xval) > tp->bound)
 				yyerror("invalid slice index %N (out of bounds for %d-element array)", n->right->left, tp->bound);
+				else if(mpcmpfixfix(n->right->left->val.u.xval, maxintval[TINT]) > 0)
+				yyerror("invalid slice index %N (index too large)", n->right->left);
 			}
 		}
 		if(n->right->right != N) {
@@ -961,6 +969,8 @@ reswitch:
 				yyerror("invalid slice index %N (index must be non-negative)", n->right->right);
 			else if(tp != nil && tp->bound > 0 && mpgetfix(n->right->right->val.u.xval) > tp->bound)
 				yyerror("invalid slice index %N (out of bounds for %d-element array)", n->right->right, tp->bound);
+				else if(mpcmpfixfix(n->right->right->val.u.xval, maxintval[TINT]) > 0)
+				yyerror("invalid slice index %N (index too large)", n->right->right);
 			}
 		}
 		goto ret;

コアとなるコードの解説

追加されたコードは、既存のインデックスチェック(負のインデックスや配列の境界外チェック)の後に、新たな条件分岐として挿入されています。

  1. 単一インデックスのチェック (行 831-834):

    else if(mpcmpfixfix(n->right->val.u.xval, maxintval[TINT]) > 0) {
        why = isfixedarray(t) ? "array" : "slice";
        yyerror("invalid %s index %N (index too large)", why, n->right);
    }
    
    • n->rightは、インデックスとして使用されているASTノードを指します。n->right->val.u.xvalはそのノードが持つ定数値(多倍長整数形式)です。
    • mpcmpfixfix(..., maxintval[TINT]) > 0は、インデックス値が現在のアーキテクチャのint型で表現できる最大値(maxintval[TINT])を超えているかをチェックします。
    • why = isfixedarray(t) ? "array" : "slice";は、エラーメッセージで使用する文字列を、対象が固定長配列かスライスかに応じて「array」または「slice」に設定します。
    • yyerror(...)は、指定されたフォーマットでエラーメッセージを出力します。
  2. スライス式の下限インデックスのチェック (行 951-952):

    else if(mpcmpfixfix(n->right->left->val.u.xval, maxintval[TINT]) > 0)
    yyerror("invalid slice index %N (index too large)", n->right->left);
    
    • スライス式(例: s[low:high])の下限インデックスlowに対するチェックです。n->right->leftlowのASTノードを指します。
    • 同様に、lowの値がint型の最大値を超えていないかをmpcmpfixfixで確認し、超えていればエラーを出力します。
  3. スライス式の上限インデックスのチェック (行 965-966):

    else if(mpcmpfixfix(n->right->right->val.u.xval, maxintval[TINT]) > 0)
    yyerror("invalid slice index %N (index too large)", n->right->right);
    
    • スライス式の上限インデックスhighに対するチェックです。n->right->righthighのASTノードを指します。
    • highの値がint型の最大値を超えていないかをmpcmpfixfixで確認し、超えていればエラーを出力します。

これらの変更により、コンパイル時にインデックスのオーバーフローをより厳密にチェックし、特に32ビット環境でのビルドの堅牢性を向上させています。

関連リンク

参考にした情報源リンク

  • Go言語のint型について: https://go.dev/ref/spec#Numeric_types
  • Goコンパイラの内部構造に関する一般的な情報 (Goのソースコードや関連ドキュメント):
    • Goのソースコードリポジトリ: https://github.com/golang/go
    • Goのコンパイラに関するブログ記事やドキュメント (例: "Go compiler internals"などで検索)
  • mpcmpfixfixmaxintvalのようなコンパイラ内部のシンボルに関する情報 (Goのソースコードを直接参照するか、Goコンパイラの開発者向けドキュメントを参照)
    • src/cmd/compile/internal/gc/builtin.go (Go 1.5以降のコンパイラではGo言語で書かれているため、関連する定数や関数定義が見つかる可能性があります)
    • src/cmd/compile/internal/big/int.go (多倍長整数演算に関するコード)
  • yyerrorに関する情報 (Yacc/Bisonのドキュメントや、コンパイラ開発に関する一般的な情報)