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

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

このコミットは、Goコンパイラ(cmd/gc)における文字列のインデックス処理に関するエラーハンドリングを改善し、配列やスライスのインデックス処理のエラーハンドリングと統一することを目的としています。これにより、コンパイル時のエラーメッセージがより正確で一貫性のあるものになります。

コミット

commit d82dcadb57836bb9b1d66a3c9a5de77bf9f41f3f
Author: Russ Cox <rsc@golang.org>
Date:   Sun Feb 3 02:01:05 2013 -0500

    cmd/gc: clean up string index errors
    
    Unify with array/slice errors, which were already good.
    
    Fixes #4232.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7271046

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

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

元コミット内容

cmd/gc: clean up string index errors Unify with array/slice errors, which were already good. Fixes #4232.

このコミットは、Goコンパイラのcmd/gc部分において、文字列のインデックスエラー処理を整理し、既存の配列およびスライスのエラー処理と統合することを目的としています。これにより、Issue #4232で報告された問題が修正されます。

変更の背景

Go言語では、文字列、配列、スライスといったシーケンス型に対してインデックスアクセスが可能です。しかし、これらの型に対するインデックスが不正な場合(例えば、負のインデックスや範囲外のインデックス)、コンパイラは適切なエラーを報告する必要があります。

このコミット以前は、文字列のインデックスエラー処理が配列やスライスのそれと異なっており、エラーメッセージの質や一貫性に問題がありました。特に、文字列のインデックスが負の値であったり、文字列の長さを超える値であったりする場合に、より明確で具体的なエラーメッセージを生成する必要がありました。Issue #4232は、このような文字列インデックスに関する特定のバグを指摘しており、このコミットはその修正として行われました。

この変更の背景には、Goコンパイラが生成するエラーメッセージの品質向上と、ユーザーエクスペリエンスの改善という意図があります。開発者がコードの誤りをより迅速かつ正確に特定できるよう、エラーメッセージの明確性と一貫性を高めることが重要視されました。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。

  • Go言語のインデックスアクセス: Go言語では、文字列、配列、スライスに対して[]演算子を用いて要素にアクセスします。
    • 文字列: 文字列はバイトのシーケンスとして扱われ、s[i]i番目のバイト(byte型)を取得します。
    • 配列: 固定長で、a[i]i番目の要素を取得します。
    • スライス: 可変長で、配列の一部を参照するものです。s[i]i番目の要素を取得します。
  • コンパイラの型チェック: コンパイラの主要なフェーズの一つに型チェックがあります。これは、プログラムがGo言語の型システム規則に準拠しているかを確認するプロセスです。インデックスアクセスの場合、インデックスが整数型であるか、そしてその値が有効な範囲内にあるかなどがチェックされます。
  • cmd/gc: Go言語の公式コンパイラの実装の一つです。Goのソースコードを機械語に変換する役割を担います。typecheck.cは、このコンパイラの一部であり、型チェックロジックが記述されています。
  • yyerror: Goコンパイラ内で使用されるエラー報告関数の一つで、コンパイル時にエラーメッセージを出力するために使われます。
  • OLITERAL: コンパイラの内部表現で、リテラル値(定数)を表すノードです。
  • mpgetfix: 多倍長整数(mpint)からint64への変換を行う関数です。コンパイラが定数値を扱う際に使用されます。
  • isfixedarray(t): 型tが固定長の配列であるかを判定する関数です。
  • t->bound: 固定長配列の要素数を表します。
  • isconst(n->left, CTSTR): ノードn->leftが定数文字列であるかを判定します。
  • n->left->val.u.sval->len: 定数文字列の長さを表します。

技術的詳細

このコミットの技術的な核心は、src/cmd/gc/typecheck.cにおけるOINDEX(インデックスアクセス演算子)の型チェックロジックの変更にあります。

変更前は、TARRAY(配列/スライス)とTSTRING(文字列)のインデックスチェックロジックが別々のcaseブロックに分かれていました。このコミットでは、TSTRINGの処理をTARRAYcaseブロックに統合し、共通のロジックでインデックスの妥当性を検証するように変更されています。

具体的な変更点は以下の通りです。

  1. TSTRINGTARRAYの統合:
    • TSTRINGcaseブロックが削除され、そのロジックがTARRAYcaseブロック内に移動しました。
    • これにより、インデックスが非整数である場合のチェックや、リテラルインデックスの範囲チェックが、文字列、配列、スライスで共通化されました。
  2. 戻り値の型の明確化:
    • 文字列のインデックスアクセス(s[i])の結果の型が、以前はtypes[TUINT](符号なし整数)と曖昧だったものが、types[TUINT8]byte型)と明示的に設定されるようになりました。これはGo言語の仕様に合致しています。
  3. エラーメッセージの統一と具体化:
    • whyという変数が導入され、インデックスアクセス対象が「string」「array」「slice」のいずれであるかを動的に設定するようになりました。
    • これにより、エラーメッセージが「non-integer %s index %N」(例: non-integer string index)のように、より具体的な情報を含むようになりました。
    • 負のインデックスに対するエラーメッセージも「invalid %s index %N (index must be non-negative)」のように統一されました。
  4. 文字列の境界チェックの追加:
    • 定数文字列に対するインデックスアクセスの場合に、インデックスが文字列の長さを超えていないかを明示的にチェックするロジックが追加されました。
    • else if(isconst(n->left, CTSTR) && mpgetfix(n->right->val.u.xval) >= n->left->val.u.sval->len)
      • これは、n->leftが定数文字列であり、かつインデックスn->rightが文字列の長さ以上である場合に、yyerror("invalid string index %N (out of bounds for %d-byte string)", n->right, n->left->val.u.sval->len);というエラーを発生させます。
    • これにより、コンパイル時に文字列のインデックスが範囲外であることを検出できるようになり、実行時パニックを未然に防ぐことができます。
  5. maxintval[TINT]によるインデックスの最大値チェック:
    • インデックスがint型の最大値を超える場合のチェックも、mpcmpfixfix(n->right->val.u.xval, maxintval[TINT]) > 0という形で統一的に行われるようになりました。

これらの変更により、Goコンパイラは文字列、配列、スライスのインデックスに関するエラーをより正確に、かつ一貫性のあるメッセージで報告できるようになりました。

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

src/cmd/gc/typecheck.c ファイルの reswitch ラベル内の case TARRAY: ブロックが主要な変更箇所です。

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -832,23 +832,34 @@ reswitch:
 		yyerror("invalid operation: %N (index of type %T)", n, t);
 		goto error;
 
+
+		case TSTRING:
 		case TARRAY:
 			defaultlit(&n->right, T);
-			n->type = t->type;
+			if(t->etype == TSTRING)
+				n->type = types[TUINT8];
+			else
+				n->type = t->type;
+			why = "string";
+			if(t->etype == TARRAY) {
+				if(isfixedarray(t))
+					why = "array";
+				else
+					why = "slice";
+			}
 			if(n->right->type != T && !isint[n->right->type->etype]) {
-				yyerror("non-integer array index %N", n->right);
+				yyerror("non-integer %s index %N", why, n->right);
 				break;
 			}
 			if(n->right->op == OLITERAL) {
-			       	if(mpgetfix(n->right->val.u.xval) < 0) {
-					why = isfixedarray(t) ? "array" : "slice";
+			       	if(mpgetfix(n->right->val.u.xval) < 0)
 				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)
+				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";
+				else if(isconst(n->left, CTSTR) && mpgetfix(n->right->val.u.xval) >= n->left->val.u.sval->len)
+				yyerror("invalid string index %N (out of bounds for %d-byte string)", n->right, n->left->val.u.sval->len);
+				else if(mpcmpfixfix(n->right->val.u.xval, maxintval[TINT]) > 0)
 				yyerror("invalid %s index %N (index too large)", why, n->right);
-				}
 			}
 			break;
 
@@ -860,13 +871,6 @@ reswitch:
 			n->type = t->type;
 			n->op = OINDEXMAP;
 			break;
-
-		case TSTRING:
-			defaultlit(&n->right, types[TUINT]);
-			if(n->right->type != T && !isint[n->right->type->etype])
-				yyerror("non-integer string index %N", n->right);
-			n->type = types[TUINT8];
-			break;
 		}
 		goto ret;

また、テストファイルtest/fixedbugs/bug205.gotest/fixedbugs/issue4232.goも変更されています。

test/fixedbugs/bug205.goでは、期待されるエラーメッセージがより具体的になるように更新されています。

--- a/test/fixedbugs/bug205.go
+++ b/test/fixedbugs/bug205.go
@@ -11,8 +11,8 @@ var s string;
 var m map[string]int;
 
 func main() {
-	println(t["hi"]);	// ERROR "integer"
-	println(s["hi"]);	// ERROR "integer" "to type uint"
-	println(m[0]);	// ERROR "map index"
+	println(t["hi"]);	// ERROR "non-integer slice index"
+	println(s["hi"]);	// ERROR "non-integer string index"
+	println(m[0]);	// ERROR "as type string in map index"
 }

test/fixedbugs/issue4232.goは、このコミットで新たに追加されたテストファイルで、配列、スライス、文字列に対する様々な不正なインデックスアクセス(負の値、範囲外の値)を網羅的にテストし、期待されるエラーメッセージが正しく出力されることを確認しています。

--- /dev/null
+++ b/test/fixedbugs/issue4232.go
@@ -0,0 +1,33 @@
+// 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.
+
+package p
+
+func f() {
+	var a [10]int
+	_ = a[-1] // ERROR "invalid array index -1"
+	_ = a[-1:] // ERROR "invalid slice index -1"
+	_ = a[:-1] // ERROR "invalid slice index -1"
+	_ = a[10] // ERROR "invalid array index 10"
+
+	var s []int
+	_ = s[-1] // ERROR "invalid slice index -1"
+	_ = s[-1:] // ERROR "invalid slice index -1"
+	_ = s[:-1] // ERROR "invalid slice index -1"
+	_ = s[10]
+
+	const c = "foo"
+	_ = c[-1] // ERROR "invalid string index -1"
+	_ = c[-1:] // ERROR "invalid slice index -1"
+	_ = c[:-1] // ERROR "invalid slice index -1"
+	_ = c[3] // ERROR "invalid string index 3"
+
+	var t string
+	_ = t[-1] // ERROR "invalid string index -1"
+	_ = t[-1:] // ERROR "invalid slice index -1"
+	_ = t[:-1] // ERROR "invalid slice index -1"
+	_ = t[3]
+}

コアとなるコードの解説

src/cmd/gc/typecheck.cの変更は、Goコンパイラの型チェックフェーズにおけるインデックス演算子(OINDEX)の処理を根本的に改善しています。

以前は、文字列のインデックス処理が配列やスライスとは独立していましたが、この変更により、TARRAYcaseブロック内でTSTRINGも処理されるようになりました。これは、これらの型がインデックスアクセスという共通の操作を持つため、その検証ロジックを統合することでコードの重複を減らし、保守性を高めるという設計思想に基づいています。

why変数の導入は、エラーメッセージの動的な生成を可能にし、よりユーザーフレンドリーなエラー報告を実現します。例えば、yyerror("non-integer %s index %N", why, n->right);という行では、whyの値に応じて「non-integer array index」や「non-integer string index」といった具体的なメッセージが出力されます。

特に重要なのは、定数文字列に対する境界チェックの追加です。isconst(n->left, CTSTR) && mpgetfix(n->right->val.u.xval) >= n->left->val.u.sval->lenという条件は、コンパイル時にインデックスが文字列の範囲外であるかを厳密にチェックします。これにより、実行時エラー(パニック)を未然に防ぎ、開発者がコンパイル段階で問題を特定できるようになります。これは、Go言語の「早期にエラーを検出する」という設計原則に合致しています。

また、文字列のインデックスアクセス結果の型がtypes[TUINT8]byte型)と明示されたことも重要です。Go言語では文字列はUTF-8エンコードされたバイトのシーケンスであり、s[i]i番目のバイトを返します。この変更は、コンパイラがGo言語のセマンティクスを正確に反映していることを示しています。

これらの変更は、Goコンパイラの堅牢性とエラー報告の品質を向上させ、開発者がより効率的にGoコードを記述・デバッグできるように貢献しています。

関連リンク

参考にした情報源リンク

  • Go言語のIssue #4232: https://github.com/golang/go/issues/4232
  • Gerrit Change-Id: 7271046 (Goプロジェクトのコードレビューシステム)
  • Go言語のコンパイラに関する一般的な情報源(例: Goコンパイラの内部構造に関するブログ記事や論文など)
    • Goコンパイラのソースコード自体
    • Go言語のメーリングリストやフォーラムでの議論
    • Go言語の公式ブログ
    • Go言語の仕様書