[インデックス 14188] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における配列のサイズが大きすぎる場合のコンパイルエラーメッセージを改善するものです。具体的には、以下のファイルが変更されています。
src/cmd/gc/const.c
: 定数オーバーフローチェックのロジックが変更され、オーバーフローの判定とエラー報告が分離されました。src/cmd/gc/go.h
: 型定義ファイルで、配列の境界(bound
)を表すフィールドの型が変更されました。src/cmd/gc/typecheck.c
: 型チェックのロジックが更新され、配列の境界が大きすぎる場合に、より分かりやすいエラーメッセージを出力するように修正されました。test/fixedbugs/bug255.go
: 修正されたエラーメッセージを検証するためのテストケースが更新されました。
コミット
cmd/gc: サイズが大きすぎる配列に対するより親切なエラーメッセージ。
言語に不慣れな人は、以前のエラーメッセージが示唆していた唯一の情報である、int
と配列の間の関連性を知らないかもしれません。
Fixes #4256.
R=golang-dev, rsc CC=golang-dev https://golang.org/cl/6739048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a7a3fe72386df56d6bf6cd83fe346e1c72cf998a
元コミット内容
commit a7a3fe72386df56d6bf6cd83fe346e1c72cf998a
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date: Sun Oct 21 19:22:51 2012 +0200
cmd/gc: Friendlier errors on oversized arrays.
Someone new to the language may not know the connection between ints and arrays, which was the only thing that the previous error told you anything about.
Fixes #4256.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6739048
変更の背景
この変更の背景には、Go言語のコンパイラ(cmd/gc
)が、配列のサイズが非常に大きい場合に生成するエラーメッセージが、新規ユーザーにとって分かりにくいという問題がありました。具体的には、var g [1<<65]int
のような非常に大きな配列を宣言しようとした際に、以前のコンパイラは単に「overflows」というエラーを出力していました。このメッセージは、定数オーバーフローが発生していることを示唆していましたが、なぜ配列のサイズ指定が整数のオーバーフローと関連するのか、また、それが配列のサイズが大きすぎることに起因するのかが直感的に理解しにくいものでした。
Go言語は、シンプルさと分かりやすさを重視しており、エラーメッセージもその原則に従うべきです。このコミットは、GoのIssue #4256で報告された問題を解決するために行われました。Issue #4256では、このエラーメッセージの分かりにくさが指摘されており、より具体的なエラーメッセージが求められていました。この改善により、ユーザーは「array bound is too large」(配列の境界が大きすぎる)という、より直接的で理解しやすいメッセージを受け取ることができるようになり、問題の原因を迅速に特定できるようになります。
前提知識の解説
Go言語における配列の宣言とサイズ指定
Go言語では、配列は固定長であり、宣言時にそのサイズを決定する必要があります。例えば、var a [10]int
は10個の整数を格納できる配列を宣言します。この配列のサイズは、コンパイル時に定数として評価される必要があります。Goコンパイラは、この定数値を内部的に整数として扱います。
Goコンパイラ (cmd/gc
) の役割
cmd/gc
は、Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換する主要な役割を担っています。このコンパイラは、字句解析、構文解析、型チェック、最適化、コード生成といった一連のフェーズを経て、実行可能なバイナリを生成します。本コミットで変更されている src/cmd/gc
ディレクトリ内のファイルは、主に型チェックと定数評価に関連する部分です。
定数評価とオーバーフローの概念
Go言語では、コンパイル時に評価される定数(例: 100
、"hello"
、true
)が存在します。配列のサイズもこの定数の一つです。コンパイラは、これらの定数値を内部的に表現し、計算を行います。
オーバーフローとは、あるデータ型で表現できる数値の範囲を超えた値が計算結果として生じる現象を指します。例えば、32ビット整数型で表現できる最大値を超える計算結果が出た場合、オーバーフローが発生します。Goコンパイラは、配列のサイズを評価する際に、その値が内部的な整数型で表現できる範囲に収まっているかをチェックします。もし、1<<65
のように非常に大きな値が指定された場合、これは通常の整数型では表現できないため、オーバーフローと見なされます。
src/cmd/gc
ディレクトリの役割
src/cmd/gc
ディレクトリは、Goコンパイラの主要なソースコードが格納されている場所です。
const.c
: 定数に関する処理、特に定数の評価とオーバーフローチェックのロジックが含まれています。go.h
: コンパイラ全体で共有されるデータ構造や関数のプロトタイプが定義されているヘッダーファイルです。typecheck.c
: 型チェックのロジックが含まれており、Goプログラムの構文ツリーを走査し、各ノードの型が正しいか、型変換が可能かなどを検証します。配列のサイズ指定もこのフェーズでチェックされます。
技術的詳細
このコミットの主要な技術的変更点は、定数オーバーフローの検出ロジックとエラー報告メカニズムの分離、および配列サイズを扱う内部型の拡張です。
overflow
関数と doesoverflow
関数の導入
以前の overflow
関数は、定数のオーバーフローをチェックし、同時にエラーメッセージを yyerror
で出力していました。このコミットでは、この機能を doesoverflow
と overflow
の2つの関数に分離しました。
-
doesoverflow(Val v, Type *t)
:- この関数は、与えられた定数値
v
が、指定された型t
の範囲内でオーバーフローするかどうかを判定するだけで、エラーメッセージは出力しません。 - オーバーフローが発生する場合は
1
を返し、しない場合は0
を返します。 - これにより、オーバーフローの判定ロジックを再利用しやすくなり、異なるコンテキストで異なるエラーメッセージを出力することが可能になります。
- 内部的には、
mpcmpfixfix
(多倍長整数比較) やmpcmpfltflt
(多倍長浮動小数点数比較) といった関数を用いて、定数値がターゲット型の最小値と最大値の範囲内にあるかをチェックしています。
- この関数は、与えられた定数値
-
overflow(Val v, Type *t)
:- この関数は、まず
doesoverflow
を呼び出してオーバーフローが発生するかどうかを確認します。 doesoverflow
が1
を返した場合(オーバーフローが発生した場合)にのみ、具体的なエラーメッセージをyyerror
で出力します。- これにより、エラーメッセージのカスタマイズが容易になり、今回の「array bound is too large」のような、より文脈に即したメッセージを出力できるようになりました。
- この関数は、まず
src/cmd/gc/go.h
での Type
構造体の bound
フィールドの型変更
src/cmd/gc/go.h
内の Type
構造体は、Go言語の型情報を表現するために使用されます。この構造体には、配列のサイズを表す bound
フィールドがあります。
// 変更前
struct Type {
// ...
int32 bound; // negative is dynamic array
// ...
};
// 変更後
struct Type {
// ...
vlong bound; // negative is dynamic array
// ...
};
int32
からvlong
への変更: 以前はint32
(32ビット整数) で配列の境界を保持していましたが、1<<65
のような非常に大きな値は32ビット整数では表現できません。vlong
は、Goコンパイラ内部で使われる多倍長整数型(またはそれに準ずる非常に大きな整数を扱える型)であり、これにより、より大きな配列サイズを正確に表現・処理できるようになりました。この変更は、コンパイラが巨大な定数を扱う能力を向上させるために不可欠です。
src/cmd/gc/typecheck.c
での配列境界チェックロジックの変更
src/cmd/gc/typecheck.c
は、Goプログラムの型チェックを行う部分です。配列の宣言において、そのサイズが定数であること、そしてその値が有効な範囲内にあることを検証します。
// 変更前
// ...
if(t->bound < 0) {
yyerror("array bound must be non-negative");
goto error;
} else
overflow(v, types[TINT]); // ここで直接 overflow を呼び出し
// 変更後
// ...
if(t->bound < 0) {
yyerror("array bound must be non-negative");
goto error;
} else if(doesoverflow(v, types[TINT])) { // doesoverflow で判定
yyerror("array bound is too large"); // 新しいエラーメッセージ
goto error;
}
doesoverflow
の利用: 以前は、配列の境界が非負であることを確認した後、すぐにoverflow(v, types[TINT])
を呼び出していました。このoverflow
関数は、定数がTINT
型(Goのint
型に対応する内部型)の範囲を超えている場合に「overflows」という一般的なエラーを出力していました。- より具体的なエラーメッセージ: 変更後では、
doesoverflow(v, types[TINT])
を使ってオーバーフローが発生するかどうかを明示的にチェックします。もしオーバーフローが発生した場合、yyerror("array bound is too large")
という、より具体的で分かりやすいエラーメッセージを出力するように修正されました。これにより、ユーザーは配列のサイズが大きすぎることが問題であると直接的に理解できます。
test/fixedbugs/bug255.go
のテストケースの変更
このファイルは、特定のバグが修正されたことを確認するためのテストケースを含んでいます。
// 変更前
var g [1<<65]int // ERROR "overflows"
// 変更後
var g [1<<65]int // ERROR "array bound is too large|overflows"
- 期待されるエラーメッセージの更新:
var g [1<<65]int
というコードは、非常に大きな配列サイズを指定しており、以前は「overflows」というエラーが期待されていました。このコミットの変更により、新しいエラーメッセージ「array bound is too large」が出力されるようになるため、テストケースもその新しいメッセージ、または以前のメッセージのいずれかが出力されることを許容するように更新されました。これは、コンパイラの出力が期待通りに変化したことを検証するための重要なステップです。
コアとなるコードの変更箇所
src/cmd/gc/const.c
--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -348,13 +348,9 @@ toint(Val v)
return v;
}
-void
-overflow(Val v, Type *t)
+int
+doesoverflow(Val v, Type *t)
{
-// v has already been converted
-// to appropriate form for t.
-// if(t == T || t->etype == TIDEAL)
-// return;
switch(v.ctype) {
case CTINT:
case CTRUNE:
@@ -362,14 +358,14 @@ overflow(Val v, Type *t)
if(mpcmpfixfix(v.u.xval, minintval[t->etype]) < 0 ||
mpcmpfixfix(v.u.xval, maxintval[t->etype]) > 0)
- yyerror("constant %B overflows %T", v.u.xval, t);
+ return 1;
break;
case CTFLT:
if(!isfloat[t->etype])
fatal("overflow: %T floating-point constant", t);
if(mpcmpfltflt(v.u.fval, minfltval[t->etype]) <= 0 ||
mpcmpfltflt(v.u.fval, maxfltval[t->etype]) >= 0)
- yyerror("constant %#F overflows %T", v.u.fval, t);
+ return 1;
break;
case CTCPLX:
if(!iscomplex[t->etype])
@@ -378,7 +374,33 @@ overflow(Val v, Type *t)
mpcmpfltflt(&v.u.cval->real, maxfltval[t->etype]) >= 0 ||
mpcmpfltflt(&v.u.cval->imag, minfltval[t->etype]) <= 0 ||
mpcmpfltflt(&v.u.cval->imag, maxfltval[t->etype]) >= 0)
- yyerror("constant %#F overflows %T", v.u.fval, t);
+ return 1;
+ break;
+ }
+ return 0;
+}
+
+void
+overflow(Val v, Type *t)
+{
+ // v has already been converted
+ // to appropriate form for t.
+ if(t == T || t->etype == TIDEAL)
+ return;
+
+ if(!doesoverflow(v, t))
+ return;
+
+ switch(v.ctype) {
+ case CTINT:
+ case CTRUNE:
+ yyerror("constant %B overflows %T", v.u.xval, t);
+ break;
+ case CTFLT:
+ yyerror("constant %#F overflows %T", v.u.fval, t);
+ break;
+ case CTCPLX:
+ yyerror("constant %#F overflows %T", v.u.fval, t);
break;
}
}
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -178,7 +178,7 @@ struct Type
Strlit* note; // literal string annotation
// TARRAY
- int32 bound; // negative is dynamic array
+ vlong bound; // negative is dynamic array
int32 maplineno; // first use of TFORW as map key
int32 embedlineno; // first use of TFORW as embedded type
@@ -977,6 +977,7 @@ int isconst(Node *n, int ct);
Node* nodcplxlit(Val r, Val i);
Node* nodlit(Val v);
long nonnegconst(Node *n);
+int doesoverflow(Val v, Type *t);
void overflow(Val v, Type *t);
int smallintconst(Node *n);
Val toint(Val v);
src/cmd/gc/typecheck.c
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -390,8 +390,10 @@ reswitch:
if(t->bound < 0) {
yyerror("array bound must be non-negative");
goto error;
- } else
- overflow(v, types[TINT]);
+ } else if(doesoverflow(v, types[TINT])) {
+ yyerror("array bound is too large");
+ goto error;
+ }
}
typecheck(&r, Etype);
if(r->type == T)
test/fixedbugs/bug255.go
--- a/test/fixedbugs/bug255.go
+++ b/test/fixedbugs/bug255.go
@@ -12,4 +12,4 @@ var c [1.5]int // ERROR "truncated"
var d [\"abc\"]int // ERROR \"invalid array bound|not numeric\"
var e [nil]int // ERROR \"invalid array bound|not numeric\"
var f [e]int // ERROR \"invalid array bound|not constant\"
-var g [1<<65]int // ERROR \"overflows\"
+var g [1<<65]int // ERROR \"array bound is too large|overflows\"
コアとなるコードの解説
このコミットの核心は、定数オーバーフローの検出とエラー報告の役割分担を明確にし、より具体的なエラーメッセージを提供することにあります。
-
doesoverflow
関数の導入 (src/cmd/gc/const.c
):- 以前の
overflow
関数から、純粋なオーバーフロー判定ロジックがdoesoverflow
関数として抽出されました。 - この関数は、与えられた定数値
v
が、指定された型t
の範囲を超えている場合に1
を返し、そうでない場合は0
を返します。 - これにより、オーバーフローの有無をチェックする部分と、その結果に基づいてエラーメッセージを生成する部分が分離され、コードの再利用性と保守性が向上しました。
- 以前の
-
新しい
overflow
関数の実装 (src/cmd/gc/const.c
):- 新しい
overflow
関数は、まずdoesoverflow
を呼び出してオーバーフローの有無を確認します。 - オーバーフローが検出された場合にのみ、
yyerror
を使ってエラーメッセージを出力します。この構造により、overflow
関数はエラー報告の責任のみを負うようになります。
- 新しい
-
Type
構造体のbound
フィールドの型変更 (src/cmd/gc/go.h
):- 配列のサイズを格納する
Type
構造体のbound
フィールドがint32
からvlong
に変更されました。 - これは、
1<<65
のような非常に大きな配列サイズを、コンパイラが内部的に正確に表現できるようにするために必要です。vlong
は、Goコンパイラが多倍長整数を扱うための内部型であり、これにより、32ビット整数の範囲を超える値を扱うことが可能になります。
- 配列のサイズを格納する
-
配列境界チェックの修正 (
src/cmd/gc/typecheck.c
):- 配列のサイズをチェックする箇所で、
overflow(v, types[TINT])
の直接呼び出しが削除されました。 - 代わりに、
doesoverflow(v, types[TINT])
を使ってオーバーフローを判定し、もしオーバーフローが発生していれば、yyerror("array bound is too large")
という新しい、より具体的なエラーメッセージを出力するように変更されました。 - この変更により、ユーザーは「配列のサイズが大きすぎる」という直接的なフィードバックを受け取ることができ、問題の原因を迅速に理解できるようになります。
- 配列のサイズをチェックする箇所で、
これらの変更は、Goコンパイラのエラーメッセージの質を向上させ、特にGo言語の学習者にとって、より分かりやすいフィードバックを提供することを目的としています。
関連リンク
- Go Issue #4256: cmd/gc: friendlier errors on oversized arrays
- Gerrit Change-List: https://golang.org/cl/6739048
参考にした情報源リンク
- Go言語の公式ドキュメント (Go言語の配列、定数、コンパイラに関する一般的な情報)
- Goコンパイラのソースコード (特に
src/cmd/gc
ディレクトリ内のファイル構造と関数定義) - Go Issue Tracker (Issue #4256 の詳細)
- Gerrit Code Review (Change-List 6739048 の詳細)