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

[インデックス 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 で出力していました。このコミットでは、この機能を doesoverflowoverflow の2つの関数に分離しました。

  • doesoverflow(Val v, Type *t):

    • この関数は、与えられた定数値 v が、指定された型 t の範囲内でオーバーフローするかどうかを判定するだけで、エラーメッセージは出力しません。
    • オーバーフローが発生する場合は 1 を返し、しない場合は 0 を返します。
    • これにより、オーバーフローの判定ロジックを再利用しやすくなり、異なるコンテキストで異なるエラーメッセージを出力することが可能になります。
    • 内部的には、mpcmpfixfix (多倍長整数比較) や mpcmpfltflt (多倍長浮動小数点数比較) といった関数を用いて、定数値がターゲット型の最小値と最大値の範囲内にあるかをチェックしています。
  • overflow(Val v, Type *t):

    • この関数は、まず doesoverflow を呼び出してオーバーフローが発生するかどうかを確認します。
    • doesoverflow1 を返した場合(オーバーフローが発生した場合)にのみ、具体的なエラーメッセージを 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\"

コアとなるコードの解説

このコミットの核心は、定数オーバーフローの検出とエラー報告の役割分担を明確にし、より具体的なエラーメッセージを提供することにあります。

  1. doesoverflow 関数の導入 (src/cmd/gc/const.c):

    • 以前の overflow 関数から、純粋なオーバーフロー判定ロジックが doesoverflow 関数として抽出されました。
    • この関数は、与えられた定数値 v が、指定された型 t の範囲を超えている場合に 1 を返し、そうでない場合は 0 を返します。
    • これにより、オーバーフローの有無をチェックする部分と、その結果に基づいてエラーメッセージを生成する部分が分離され、コードの再利用性と保守性が向上しました。
  2. 新しい overflow 関数の実装 (src/cmd/gc/const.c):

    • 新しい overflow 関数は、まず doesoverflow を呼び出してオーバーフローの有無を確認します。
    • オーバーフローが検出された場合にのみ、yyerror を使ってエラーメッセージを出力します。この構造により、overflow 関数はエラー報告の責任のみを負うようになります。
  3. Type 構造体の bound フィールドの型変更 (src/cmd/gc/go.h):

    • 配列のサイズを格納する Type 構造体の bound フィールドが int32 から vlong に変更されました。
    • これは、1<<65 のような非常に大きな配列サイズを、コンパイラが内部的に正確に表現できるようにするために必要です。vlong は、Goコンパイラが多倍長整数を扱うための内部型であり、これにより、32ビット整数の範囲を超える値を扱うことが可能になります。
  4. 配列境界チェックの修正 (src/cmd/gc/typecheck.c):

    • 配列のサイズをチェックする箇所で、overflow(v, types[TINT]) の直接呼び出しが削除されました。
    • 代わりに、doesoverflow(v, types[TINT]) を使ってオーバーフローを判定し、もしオーバーフローが発生していれば、yyerror("array bound is too large") という新しい、より具体的なエラーメッセージを出力するように変更されました。
    • この変更により、ユーザーは「配列のサイズが大きすぎる」という直接的なフィードバックを受け取ることができ、問題の原因を迅速に理解できるようになります。

これらの変更は、Goコンパイラのエラーメッセージの質を向上させ、特にGo言語の学習者にとって、より分かりやすいフィードバックを提供することを目的としています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (Go言語の配列、定数、コンパイラに関する一般的な情報)
  • Goコンパイラのソースコード (特に src/cmd/gc ディレクトリ内のファイル構造と関数定義)
  • Go Issue Tracker (Issue #4256 の詳細)
  • Gerrit Code Review (Change-List 6739048 の詳細)