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

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

このコミットは、Goコンパイラ(gc)におけるスライス範囲のチェックに関する改善です。特に、start > endとなる「逆転したスライス範囲(inverted slice range)」が指定された際に、意味のない定数オーバーフローエラーが表示されるのを防ぎ、より適切で分かりやすいエラーメッセージのみを出力するように修正しています。

コミット

commit 08918ba43851d28e860fdaeb79aed0738639a394
Author: Ian Lance Taylor <iant@golang.org>
Date:   Wed Dec 5 15:46:45 2012 -0800

    gc: avoid meaningless constant overflow error for inverted slice range
    
    Used to say:
    
    issue4251.go:12: inverted slice range
    issue4251.go:12: constant -1 overflows uint64
    issue4251.go:16: inverted slice range
    issue4251.go:16: constant -1 overflows uint64
    issue4251.go:20: inverted slice range
    issue4251.go:20: constant -1 overflows uint64
    
    With this patch, only gives the "inverted slice range" errors.
    
    R=golang-dev, daniel.morsing
    CC=golang-dev
    https://golang.org/cl/6871058

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

https://github.com/golang/go/commit/08918ba43851d28e860fdaeb79aed0738639a394

元コミット内容

Goコンパイラ(gc)において、逆転したスライス範囲(例: s[2:1])が指定された場合、以前は以下のような複数のエラーメッセージが出力されていました。

issue4251.go:12: inverted slice range
issue4251.go:12: constant -1 overflows uint64
issue4251.go:16: inverted slice range
issue4251.go:16: constant -1 overflows uint64
issue4251.go:20: inverted slice range
issue4251.go:20: constant -1 overflows uint64

このコミットの目的は、これらのエラーメッセージのうち、本質的ではない「constant -1 overflows uint64」というメッセージを抑制し、より直接的な「inverted slice range」エラーのみを出力するようにすることです。

変更の背景

Go言語では、スライス操作においてa[low:high]という形式で範囲を指定します。ここでlowは開始インデックス、highは終了インデックス(排他的)です。通常、lowhigh以下である必要があります。もしlow > highとなった場合、これは「逆転したスライス範囲」と呼ばれ、Go言語の仕様上不正な操作です。

この不正な操作に対して、Goコンパイラはエラーを報告する必要があります。しかし、以前の実装では、逆転したスライス範囲が検出された際に、その内部処理の過程で負の定数(-1など)が生成され、それがuint64型に変換される際に「constant -1 overflows uint64」という、本質的ではないオーバーフローエラーも同時に報告されていました。

このオーバーフローエラーは、ユーザーにとってはスライス範囲の誤りとは直接関係がなく、混乱を招く可能性がありました。開発者は、より明確で、問題の根本原因を指し示すエラーメッセージのみをユーザーに提示することを望んでいました。そのため、この冗長で意味のないエラーメッセージを抑制し、エラー報告の質を向上させるためにこの変更が行われました。

前提知識の解説

Go言語のスライス

Go言語のスライスは、配列の一部を参照する軽量なデータ構造です。[]Tのように宣言され、基盤となる配列の連続したセグメントを指します。スライスはs[low:high]という構文で作成または再スライスできます。ここでlowは開始インデックス(包含)、highは終了インデックス(排他的)です。lowhighより大きい場合、そのスライスは不正な「逆転したスライス範囲」となります。

Goコンパイラ(gc)の構造とフェーズ

Goコンパイラ(gc)は、複数のフェーズを経てソースコードを機械語に変換します。主要なフェーズには以下のようなものがあります。

  1. 字句解析 (Lexing): ソースコードをトークンに分割します。
  2. 構文解析 (Parsing): トークン列から抽象構文木(AST)を構築します。
  3. 型チェック (Type Checking): ASTを走査し、型の整合性をチェックします。このフェーズで、変数や式の型が正しいか、関数呼び出しの引数が適切かなどが検証されます。src/cmd/gc/typecheck.cはこのフェーズの主要な部分を担います。
  4. ウォーク (Walk): ASTをさらに変換し、最適化やコード生成のための準備を行います。例えば、高レベルな構文を低レベルな操作に変換したり、定数畳み込みを行ったりします。src/cmd/gc/walk.cはこのフェーズの一部です。
  5. コード生成 (Code Generation): 変換されたASTから最終的な機械語コードを生成します。

yyerror関数

yyerrorは、Goコンパイラ内でエラーメッセージを出力するために使用される関数です。コンパイル中に問題が検出された際に、この関数を呼び出すことで、ファイル名、行番号、そして指定されたエラーメッセージが標準エラー出力に表示されます。

OLITERALmpgetfix

  • OLITERAL: GoコンパイラのASTノードの種類の一つで、リテラル値(定数)を表します。
  • mpgetfix: 多倍長整数(mpint)から固定小数点数(fix)を取得する関数です。コンパイラ内部で数値定数を扱う際に使用されます。

技術的詳細

このコミットの技術的な核心は、Goコンパイラの型チェックフェーズとウォークフェーズにおけるスライス範囲の検証ロジックの変更と、エラー報告のタイミングの調整にあります。

変更点と目的

  1. src/cmd/gc/typecheck.cにおける変更:

    • 以前は、スライスインデックスが負の値、配列の境界外、または大きすぎる場合にyyerrorを呼び出していましたが、その後にgoto error;がありませんでした。これにより、エラーが報告された後も処理が続行され、後続のチェックで別の(そして場合によっては誤解を招く)エラーが生成される可能性がありました。
    • このコミットでは、これらのyyerror呼び出しの直後にgoto error;を追加しています。これにより、不正なスライスインデックスが検出された時点で直ちにエラー処理に移行し、それ以上無意味なチェックやエラーメッセージの生成を防ぎます。
    • 最も重要な変更は、typecheck.creswitchラベルの直前、スライス範囲のチェックを行う箇所に、逆転したスライス範囲(n->right->left->val.u.xval、つまりlown->right->right->val.u.xval、つまりhighより大きい場合)を明示的にチェックするロジックが追加されたことです。このチェックがOLITERAL(定数)のスライスインデックスに対して行われ、yyerror("inverted slice index %N > %N", ...)を呼び出し、その後goto error;で処理を終了します。
    • これにより、「逆転したスライス範囲」というエラーが、他の潜在的なエラー(特に「constant -1 overflows uint64」)が発生する前に、型チェックフェーズの早い段階で捕捉され、報告されるようになります。
  2. src/cmd/gc/walk.cにおける変更:

    • walk.csliceany関数から、以前存在していた「inverted slice range」のチェックロジックが削除されました。具体的には、lbv = -1; hbv = -1;の初期化と、if(lbv >= 0 && hbv >= 0 && lbv > hbv) yyerror("inverted slice range");という条件分岐が削除されています。
    • これは、逆転したスライス範囲のチェックがtypecheck.cに移動されたため、walk.cでの重複したチェックが不要になったことを意味します。コンパイラの異なるフェーズで同じエラーを二重にチェックする必要はなく、より早いフェーズでエラーを捕捉することで、コンパイルプロセスを効率化し、エラーメッセージの重複を防ぐことができます。

エラーメッセージの抑制メカニズム

以前は、s[2:1]のような逆転したスライス範囲が指定された場合、コンパイラ内部でhigh - lowのような計算が行われ、結果が負の値(例: -1)になることがありました。この負の値が符号なし整数型(uint64)として扱われる際に、オーバーフローが発生し、「constant -1 overflows uint64」というエラーが報告されていました。

このコミットにより、typecheck.cで逆転したスライス範囲が検出された時点で即座にyyerrorを呼び出し、goto error;で処理を中断するようになりました。これにより、後続のウォークフェーズやその他の処理で、この負の定数に関連するオーバーフローエラーが発生する機会がなくなります。結果として、ユーザーには「inverted slice range」という、問題の根本原因を正確に伝えるエラーメッセージのみが表示されるようになります。

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

src/cmd/gc/typecheck.c

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -950,12 +950,16 @@ reswitch:
 			        goto error;
 			}
 			if(n->right->left->op == OLITERAL) {
-				if(mpgetfix(n->right->left->val.u.xval) < 0)
+				if(mpgetfix(n->right->left->val.u.xval) < 0) {
 					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)
+					goto error;
+				} 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)
+					goto error;
+				} else if(mpcmpfixfix(n->right->left->val.u.xval, maxintval[TINT]) > 0) {
 					yyerror("invalid slice index %N (index too large)", n->right->left);
+					goto error;
+				}
 			}
 		}
 		if(n->right->right != N) {
@@ -966,14 +970,26 @@ reswitch:
 			        goto error;
 			}
 			if(n->right->right->op == OLITERAL) {
-				if(mpgetfix(n->right->right->val.u.xval) < 0)
+				if(mpgetfix(n->right->right->val.u.xval) < 0) {
 					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)
+					goto error;
+				} 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)
+					goto error;
+				} else if(mpcmpfixfix(n->right->right->val.u.xval, maxintval[TINT]) > 0) {
 					yyerror("invalid slice index %N (index too large)", n->right->right);
+					goto error;
+				}
+			}
+		}
+		if(n->right->left != N
+		   && n->right->right != N
+		   && n->right->left->op == OLITERAL
+		   && n->right->right->op == OLITERAL
+		   && mpcmpfixfix(n->right->left->val.u.xval, n->right->right->val.u.xval) > 0) {
+			yyerror("inverted slice index %N > %N", n->right->left, n->right->right);
+			goto error;
 			}
 		}
 		goto ret;

src/cmd/gc/walk.c

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -2517,8 +2517,6 @@ sliceany(Node* n, NodeList **init)
 		else
 			bv = mpgetfix(bound->val.u.xval);
 	}
-	lbv = -1;
-	hbv = -1;
 
 	if(isconst(hb, CTINT)) {
 		hbv = mpgetfix(hb->val.u.xval);
@@ -2536,8 +2534,6 @@ sliceany(Node* n, NodeList **init)
 		if(lbv == 0)
 			lb = N;
 	}
-	if(lbv >= 0 && hbv >= 0 && lbv > hbv)
-		yyerror("inverted slice range");
 
 	// dynamic checks convert all bounds to unsigned to save us the bound < 0 comparison
 	// generate

test/fixedbugs/issue4251.go

--- /dev/null
+++ b/test/fixedbugs/issue4251.go
@@ -0,0 +1,21 @@
+// errorcheck
+
+// Copyright 2012 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 4251: slice with inverted range is an error.
+
+package p
+
+func F1(s []byte) []byte {
+	return s[2:1]		// ERROR "inverted"
+}
+
+func F2(a [10]byte) []byte {
+	return a[2:1]		// ERROR "inverted"
+}
+
+func F3(s string) string {
+	return s[2:1]		// ERROR "inverted"
+}

コアとなるコードの解説

src/cmd/gc/typecheck.cの変更点

  • goto error;の追加: 既存の無効なスライスインデックスチェック(負の値、範囲外、大きすぎる値)のyyerror呼び出しの直後にgoto error;が追加されました。これにより、これらのエラーが検出された時点で、それ以上コンパイル処理を進めずにエラー状態に移行し、後続の無意味なエラーメッセージの発生を防ぎます。これは、エラー報告の「早期終了」戦略を強化するものです。
  • 逆転スライス範囲の新規チェック:
    if(n->right->left != N
       && n->right->right != N
       && n->right->left->op == OLITERAL
       && n->right->right->op == OLITERAL
       && mpcmpfixfix(n->right->left->val.u.xval, n->right->right->val.u.xval) > 0) {
        yyerror("inverted slice index %N > %N", n->right->left, n->right->right);
        goto error;
    }
    
    このコードブロックは、スライスの開始インデックス(n->right->left)と終了インデックス(n->right->right)が両方ともリテラル(定数)である場合に、開始インデックスが終了インデックスよりも大きいかどうかをチェックします。mpcmpfixfix関数は2つの多倍長整数を比較し、最初の引数が2番目の引数より大きい場合に正の値を返します。 もし開始インデックスが終了インデックスより大きい場合、yyerrorを呼び出して「inverted slice index」エラーを報告し、goto error;で処理を終了します。この変更により、逆転したスライス範囲が型チェックフェーズで明示的に捕捉され、他のエラーに先行して報告されるようになります。

src/cmd/gc/walk.cの変更点

  • sliceany関数から、逆転したスライス範囲をチェックする以下のコードが削除されました。
    lbv = -1;
    hbv = -1;
    // ...
    if(lbv >= 0 && hbv >= 0 && lbv > hbv)
        yyerror("inverted slice range");
    
    この削除は、逆転したスライス範囲のチェックがtypecheck.cに移動されたため、walk.cでの重複したチェックが不要になったことを示しています。これにより、コンパイラのコードベースが整理され、エラーチェックのロジックが一箇所に集約されます。

test/fixedbugs/issue4251.goの追加

この新しいテストファイルは、s[2:1]のような逆転したスライス範囲を含むGoコードの例を提供しています。各関数(F1, F2, F3)は、スライス、配列、文字列に対して逆転したスライス操作を行い、// ERROR "inverted"コメントによって、コンパイラが「inverted」というキーワードを含むエラーメッセージを出力することを期待しています。このテストは、コミットによって導入された変更が正しく機能し、期待されるエラーメッセージのみが報告されることを検証するために使用されます。

関連リンク

  • Go言語の公式Issueトラッカー: https://golang.org/issue/4251 (このコミットが解決したIssueの可能性が高い)
  • Gerrit Code Review (Goプロジェクトのコードレビューシステム): https://golang.org/cl/6871058 (このコミットに対応する変更リスト)

参考にした情報源リンク

  • Go言語の公式ドキュメント: https://go.dev/doc/
  • Goコンパイラのソースコード(特にsrc/cmd/gcディレクトリ)
  • Go言語のスライスに関する公式ブログ記事やチュートリアル