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

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

このコミットは、Goコンパイラ(cmd/gc)における配列の境界計算でのオーバーフローのバグを修正するものです。具体的には、配列のサイズが非常に大きい場合に発生する可能性のある、符号付き整数オーバーフローのチェックの順序を修正しています。これにより、不正な配列サイズが原因でコンパイラがクラッシュしたり、誤ったエラーを報告したりするのを防ぎます。また、この修正を検証するための新しいテストケースが追加されています。

コミット

commit bf9a00bc8f048e2ca8d9d12d0bcf5dfc41505693
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Sat Jun 1 16:33:54 2013 +0200

    cmd/gc: fix overflow in array bounds calculation.
    
    Fixes #5609.
    
    R=golang-dev, remyoudompheng, r
    CC=golang-dev
    https://golang.org/cl/9757045

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

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

元コミット内容

cmd/gc: fix overflow in array bounds calculation.

Fixes #5609.

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

変更の背景

このコミットは、Go言語のIssue 5609「overflow when calculating array size」を修正するために行われました。 元のコンパイラの実装では、配列の境界(サイズ)を計算する際に、まずその値が非負であるかどうかのチェックが行われ、その後に値がint型に収まるかどうかのオーバーフローチェックが行われていました。

問題は、非常に大きな符号なし整数(uint64の最大値など)が配列のサイズとして指定された場合に発生しました。Goコンパイラの内部では、配列のサイズは符号付き整数型(int)として扱われることがあります。uint64の最大値のような大きな値がintに変換されると、符号付き整数オーバーフローが発生し、その結果として負の値になる可能性があります。

元のコードのチェック順序では、このオーバーフローによって負の値になった場合、t->bound < 0のチェックが先に実行され、「array bound must be non-negative」(配列の境界は非負でなければならない)というエラーが報告されていました。しかし、真の問題は値が大きすぎてintに収まらないこと、つまりオーバーフローであるべきでした。ユーザーにとっては、「配列の境界が大きすぎる」という、より正確なエラーメッセージが表示されるべきでした。

このコミットは、チェックの順序を入れ替えることで、この論理的な問題を解決し、より適切なエラーメッセージをユーザーに提供することを目的としています。

前提知識の解説

Go言語の配列と型システム

Go言語では、配列は固定長であり、そのサイズは型の一部です。例えば、[10]int[20]intは異なる型と見なされます。配列のサイズはコンパイル時に決定される定数である必要があります。

Goコンパイラ (gc)

Go言語の公式コンパイラはgc(Go Compiler)と呼ばれます。gcは、Goのソースコードを機械語に変換する役割を担っています。コンパイルプロセスには、字句解析、構文解析、型チェック、最適化、コード生成など、複数のフェーズがあります。

型チェック (Type Checking)

型チェックはコンパイルフェーズの一つで、プログラムが型規則に準拠していることを検証します。これには、変数の型が正しいか、関数の引数と戻り値の型が一致しているか、そして今回のケースのように、配列のサイズが有効な定数であるかどうかの検証が含まれます。src/cmd/gc/typecheck.cは、この型チェックロジックの一部を実装しているファイルです。

整数オーバーフロー (Integer Overflow)

コンピュータの数値型には表現できる値の範囲に限りがあります。例えば、32ビットの符号付き整数(int32)は、約-20億から+20億までの値を表現できます。この範囲を超える値を代入しようとすると、オーバーフローが発生します。 符号なし整数(uint)は非負の値のみを表現し、その最大値は符号付き整数よりも大きくなります。 今回の問題は、uint64の最大値のような非常に大きな符号なし整数が、内部的に符号付き整数として扱われる際に、その最大値を超えてしまい、結果として負の値として解釈される「ラップアラウンド」という現象が原因で発生しました。

mpgetfixdoesoverflow

  • mpgetfix: Goコンパイラの内部で使われる多倍長整数(mpint)を、固定サイズの整数型(例えばint)に変換する関数です。この変換の際に、値がターゲットの型に収まらない場合にオーバーフローが発生する可能性があります。
  • doesoverflow: 特定の値が指定された型に収まるかどうかをチェックするコンパイラ内部のヘルパー関数です。この関数は、オーバーフローが発生するかどうかを事前に判断するために使用されます。

技術的詳細

このコミットの変更は、Goコンパイラのsrc/cmd/gc/typecheck.cファイル内の配列型を処理する部分にあります。 typecheck.cは、Goプログラムの型チェックを行う主要なファイルの一つです。特に、配列の型定義(例: var a [N]intN の部分)を処理する際に、そのサイズNが有効な定数であるかを検証します。

元のコードでは、配列の境界t->boundmpgetfix(v.u.xval)によって計算された後、以下の順序でチェックが行われていました。

  1. t->bound < 0:計算された境界が負であるかどうかのチェック。
  2. doesoverflow(v, types[TINT]):元の値vint型に収まるかどうかのオーバーフローチェック。

問題は、uint64の最大値(18446744073709551615)のような非常に大きな値がmpgetfixによってintに変換されると、符号付き整数オーバーフローが発生し、結果としてt->boundが負の値(例えば-1)になることです。

この場合、元のコードではt->bound < 0の条件が先に真となり、「array bound must be non-negative」というエラーメッセージが表示されていました。しかし、ユーザーが意図したのは「負の値を指定した」のではなく、「あまりにも大きな値を指定した」ことでした。

このコミットでは、この2つのチェックの順序を入れ替えています。

変更後のチェック順序:

  1. doesoverflow(v, types[TINT]):元の値vint型に収まるかどうかのオーバーフローチェック。
  2. t->bound < 0:計算された境界が負であるかどうかのチェック。

この変更により、uint64の最大値のような大きな値が指定された場合、まずdoesoverflowが真となり、「array bound is too large」という、より適切で正確なエラーメッセージがユーザーに表示されるようになります。その後、もしオーバーフローが発生せず、かつ明示的に負の値が指定された場合にのみ、t->bound < 0のチェックが実行され、「array bound must be non-negative」というエラーが表示されます。

この修正は、コンパイラのエラーメッセージの精度を向上させ、ユーザーが問題をより迅速に理解し、修正できるようにすることを目的としています。

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

diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c
index 12839009e3..550021de69 100644
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -420,12 +420,12 @@ reswitch:
 			        goto error;
 			}
 			t->bound = mpgetfix(v.u.xval);
-			if(t->bound < 0) {
-				yyerror("array bound must be non-negative");
-				goto error;
-			} else if(doesoverflow(v, types[TINT])) {
+			if(doesoverflow(v, types[TINT])) {
 				yyerror("array bound is too large"); 
 				goto error;
+			} else if(t->bound < 0) {
+				yyerror("array bound must be non-negative");
+				goto error;
 			}
 		}
 		typecheck(&r, Etype);
diff --git a/test/fixedbugs/issue5609.go b/test/fixedbugs/issue5609.go
new file mode 100644
index 0000000000..34619b3418
--- /dev/null
+++ b/test/fixedbugs/issue5609.go
@@ -0,0 +1,13 @@
+// errorcheck
+
+// Copyright 2013 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 5609: overflow when calculating array size
+
+package pkg
+
+const Large uint64 = 18446744073709551615
+
+var foo [Large]uint64 // ERROR "array bound is too large"

コアとなるコードの解説

変更はsrc/cmd/gc/typecheck.cファイルのreswitchラベル内の配列の境界チェック部分に集中しています。

変更前:

			t->bound = mpgetfix(v.u.xval);
			if(t->bound < 0) {
				yyerror("array bound must be non-negative");
				goto error;
			} else if(doesoverflow(v, types[TINT])) {
				yyerror("array bound is too large"); 
				goto error;
			}
  1. t->bound = mpgetfix(v.u.xval);
    • ここで、多倍長整数v.u.xvalが固定サイズの整数(t->bound)に変換されます。この変換でオーバーフローが発生し、t->boundが負の値になる可能性があります。
  2. if(t->bound < 0)
    • 変換後のt->boundが負であるかをチェックします。もし負であれば、「array bound must be non-negative」というエラーが出力されます。
  3. else if(doesoverflow(v, types[TINT]))
    • t->bound < 0が偽の場合(つまりt->boundが非負の場合)にのみ、元の値vint型に収まるかどうかのオーバーフローチェックが行われます。

変更後:

			t->bound = mpgetfix(v.u.xval);
			if(doesoverflow(v, types[TINT])) {
				yyerror("array bound is too large"); 
				goto error;
			} else if(t->bound < 0) {
				yyerror("array bound must be non-negative");
				goto error;
			}
  1. t->bound = mpgetfix(v.u.xval);
    • これは変更前と同じです。
  2. if(doesoverflow(v, types[TINT]))
    • このチェックが最初に移動しました。 ここでは、mpgetfixによる変換が行われる前の元の値vが、int型に収まるかどうかをチェックします。uint64の最大値のような非常に大きな値が指定された場合、ここでオーバーフローが検出され、「array bound is too large」というエラーが即座に報告されます。これにより、t->boundが負の値にラップアラウンドしたとしても、より適切なエラーメッセージが優先されます。
  3. else if(t->bound < 0)
    • doesoverflowが偽の場合(つまり、値がint型に収まる場合)にのみ、t->boundが負であるかどうかのチェックが行われます。これは、ユーザーが明示的に負の値を指定した場合にのみトリガーされるべきエラーです。

この変更により、コンパイラは「値が大きすぎるためにオーバーフローした」という真の原因を正確に特定し、それに対応するエラーメッセージを優先的に表示するようになりました。

また、test/fixedbugs/issue5609.goという新しいテストファイルが追加されています。 このテストは、const Large uint64 = 18446744073709551615uint64の最大値)という定数を定義し、var foo [Large]uint64という配列を宣言しています。// ERROR "array bound is too large"というコメントは、この行で「array bound is too large」というエラーがコンパイラによって報告されることを期待していることを示しています。このテストの追加により、修正が正しく機能していることが検証されます。

関連リンク

参考にした情報源リンク