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

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

このコミットは、Go言語のパーサー(go/parserパッケージ)における3インデックススライス(s[low:high:max])の構文解析に関する修正です。具体的には、3インデックススライスにおいて2番目と3番目のインデックスが欠落している場合に、パーサーが適切なエラーを報告するように改善されています。これにより、不正なスライス式がgofmtを通過してしまう問題(Issue 7305)が解決されました。

コミット

commit 13a5958db328a74f863d8391f453a81ae326fd31
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Feb 11 13:40:37 2014 -0800

    go/parser: check presence of 2nd and 3rd index in 3-index slice
    
    Fixes #7305.
    
    LGTM=adonovan
    R=bradfitz, adonovan
    CC=golang-codereviews
    https://golang.org/cl/58950045

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

https://github.com/golang/go/commit/13a5958db328a74f863d8391f453a81ae326fd31

元コミット内容

go/parser: check presence of 2nd and 3rd index in 3-index slice

Fixes #7305.

LGTM=adonovan
R=bradfitz, adonovan
CC=golang-codereviews
https://golang.org/cl/58950045

変更の背景

Go言語には、スライス式としてs[low:high](2インデックススライス)とs[low:high:max](3インデックススライス)の2種類があります。3インデックススライスは、スライスの容量(capacity)を明示的に指定するために使用されます。

このコミットが修正する問題(Issue 7305)は、go/parserが3インデックススライスを解析する際に、2番目(high)または3番目(max)のインデックスが欠落している場合に、構文エラーとして適切に検出していなかったことに起因します。例えば、s[i::k]s[i:j:]のような不正なスライス式が、パーサーによってエラーと認識されずに抽象構文木(AST)が生成されてしまうことがありました。

このような不正なASTが生成されると、gofmtのようなツールがそのASTを整形しようとした際に、予期せぬ動作やクラッシュを引き起こす可能性がありました。本来、構文エラーはパーサーの段階で検出され、それ以降の処理に進むべきではありません。このコミットは、このパーサーの不備を修正し、より堅牢な構文解析を実現することを目的としています。

前提知識の解説

Go言語のスライス

Go言語のスライスは、配列の一部を参照する軽量なデータ構造です。スライスは、内部的にポインタ、長さ(length)、容量(capacity)の3つの要素で構成されます。

  • 長さ (length): スライスが現在参照している要素の数。len()関数で取得できます。
  • 容量 (capacity): スライスの基底配列の先頭から、スライスが拡張できる最大の要素数。cap()関数で取得できます。

スライス式

Go言語では、既存の配列やスライスから新しいスライスを作成するためにスライス式を使用します。

  • 2インデックススライス: a[low:high]
    • lowからhigh-1までの要素を含む新しいスライスを作成します。
    • 長さはhigh - low、容量は基底配列のlowから最後までとなります。
  • 3インデックススライス: a[low:high:max]
    • lowからhigh-1までの要素を含む新しいスライスを作成します。
    • 長さはhigh - low、容量はmax - lowとなります。
    • maxhigh以上でなければならず、基底配列の範囲内である必要があります。
    • 3インデックススライスは、新しいスライスの容量を明示的に制限したい場合に使用されます。

go/parserパッケージ

go/parserパッケージは、Go言語のソースコードを解析し、抽象構文木(AST: Abstract Syntax Tree)を生成するための標準ライブラリです。ASTは、プログラムの構造を木構造で表現したもので、コンパイラ、リンター、コード整形ツール(gofmtなど)がコードを理解し、処理するために利用します。

パーサーは、字句解析器(lexer)から受け取ったトークンストリームを基に、Go言語の文法規則に従って構文解析を行います。構文エラーが検出された場合、パーサーは通常、エラーを報告し、解析を停止するか、エラーリカバリを試みます。

ast.SliceExprast.BadExpr

  • ast.SliceExpr: go/astパッケージで定義されている、スライス式を表すASTノードです。X(基底となる式)、Lbrack[の位置)、LowHighMax(それぞれlow, high, maxの式)、Slice3(3インデックススライスかどうかを示すブール値)、Rbrack]の位置)などのフィールドを持ちます。
  • ast.BadExpr: go/astパッケージで定義されている、不正な式を表すASTノードです。パーサーが構文エラーを検出したが、解析を続行するためにプレースホルダーとして使用されます。FromToフィールドは、不正な式の範囲を示します。

gofmt

gofmtは、Go言語のソースコードを標準的なスタイルに自動的に整形するツールです。go/parserが生成したASTを読み込み、それを基に整形されたコードを出力します。パーサーが不正なASTを生成すると、gofmtが正しく動作しない原因となることがあります。

技術的詳細

このコミットの主要な変更点は、src/pkg/go/parser/parser.go内のparseIndexOrSlice関数にあります。この関数は、配列やスライスのインデックス操作やスライス式を解析する役割を担っています。

変更前は、3インデックススライスを解析する際に、コロンの数(ncolons)が2であること(つまりs[low:high:max]形式であること)はチェックしていましたが、high(2番目のインデックス)やmax(3番目のインデックス)に対応する式が実際に存在するかどうかは、型チェックの段階に任せていました。しかし、このアプローチでは、パーサーが不正な構文を許容し、不完全なASTを生成してしまう問題がありました。

今回の修正では、ncolons == 2の場合、つまり3インデックススライスであると判断された場合に、以下の追加チェックが導入されました。

  1. 2番目のインデックスの存在チェック: index[1] == nilの場合、つまりhighに相当する式が解析されなかった場合、p.error(colons[0], "2nd index required in 3-index slice")を呼び出してエラーを報告します。同時に、index[1]にはast.BadExprが割り当てられ、ASTの整合性を保ちつつ、不正な部分をマークします。colons[0]は最初のコロンの位置を示します。
  2. 3番目のインデックスの存在チェック: index[2] == nilの場合、つまりmaxに相当する式が解析されなかった場合、p.error(colons[1], "3rd index required in 3-index slice")を呼び出してエラーを報告します。同様に、index[2]にはast.BadExprが割り当てられます。colons[1]は2番目のコロンの位置を示します。

これらのチェックをパーサーの段階で導入することで、不正な3インデックススライスが早期に検出され、エラーが報告されるようになります。これにより、gofmtのような後続のツールが不完全なASTを処理しようとして問題を起こすことを防ぎます。

また、src/pkg/go/parser/short_test.goには、この修正を検証するための新しいテストケースが追加されました。これらのテストケースは、2番目または3番目のインデックスが欠落している様々な不正な3インデックススライス式を意図的に含み、パーサーが期待通りにエラーを報告することを確認します。

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

src/pkg/go/parser/parser.go

--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -1168,16 +1168,19 @@ func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr {
 		defer un(trace(p, "IndexOrSlice"))
 	}
 
+	const N = 3 // change the 3 to 2 to disable 3-index slices
 	lbrack := p.expect(token.LBRACK)
 	p.exprLev++
-	var index [3]ast.Expr // change the 3 to 2 to disable slice expressions w/ cap
+	var index [N]ast.Expr
+	var colons [N - 1]token.Pos
 	if p.tok != token.COLON {
 		index[0] = p.parseRhs()
 	}
 	ncolons := 0
-	for p.tok == token.COLON && ncolons < len(index)-1 {
-		p.next()
+	for p.tok == token.COLON && ncolons < len(colons) {
+		colons[ncolons] = p.pos
 		ncolons++
+		p.next()
 		if p.tok != token.COLON && p.tok != token.RBRACK && p.tok != token.EOF {
 			index[ncolons] = p.parseRhs()
 		}
@@ -1187,7 +1190,21 @@ func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr {
 
 	if ncolons > 0 {
 		// slice expression
-		return &ast.SliceExpr{X: x, Lbrack: lbrack, Low: index[0], High: index[1], Max: index[2], Slice3: ncolons == 2, Rbrack: rbrack}
+		slice3 := false
+		if ncolons == 2 {
+			slice3 = true
+			// Check presence of 2nd and 3rd index here rather than during type-checking
+			// to prevent erroneous programs from passing through gofmt (was issue 7305).
+			if index[1] == nil {
+				p.error(colons[0], "2nd index required in 3-index slice")
+				index[1] = &ast.BadExpr{From: colons[0] + 1, To: colons[1]}
+			}
+			if index[2] == nil {
+				p.error(colons[1], "3rd index required in 3-index slice")
+				index[2] = &ast.BadExpr{From: colons[1] + 1, To: rbrack}
+			}
+		}
+		return &ast.SliceExpr{X: x, Lbrack: lbrack, Low: index[0], High: index[1], Max: index[2], Slice3: slice3, Rbrack: rbrack}
 	}
 
 	return &ast.IndexExpr{X: x, Lbrack: lbrack, Index: index[0], Rbrack: rbrack}

src/pkg/go/parser/short_test.go

--- a/src/pkg/go/parser/short_test.go
+++ b/src/pkg/go/parser/short_test.go
@@ -76,7 +76,11 @@ var invalids = []string{
 	`package p; func f() { _ = x = /* ERROR "expected '=='" */ 0 {}};`,
 	`package p; func f() { _ = 1 == func()int { var x bool; x = x = /* ERROR "expected '=='" */ true; return x }() };`,
 	`package p; func f() { var s []int; _ = s[] /* ERROR "expected operand" */ };`,
-	`package p; func f() { var s []int; _ = s[::: /* ERROR "expected ']'" */ ] };`,
+	`package p; func f() { var s []int; _ = s[i:j: /* ERROR "3rd index required" */ ] };`,
+	`package p; func f() { var s []int; _ = s[i: /* ERROR "2nd index required" */ :k] };`,
+	`package p; func f() { var s []int; _ = s[i: /* ERROR "2nd index required" */ :] };`,
+	`package p; func f() { var s []int; _ = s[: /* ERROR "2nd index required" */ :] };`,
+	`package p; func f() { var s []int; _ = s[: /* ERROR "2nd index required" */ ::] };`,
 	`package p; func f() { var s []int; _ = s[i:j:k: /* ERROR "expected ']'" */ l] };`,
 }
 

コアとなるコードの解説

src/pkg/go/parser/parser.goの変更

  1. const N = 3var colons [N - 1]token.Posの追加:

    • Nはスライス式の最大インデックス数(low, high, maxの3つ)を定義する定数です。将来的に2インデックススライスのみをサポートするように変更する場合に、この値を2に変更するだけで済むように設計されています。
    • colons配列は、スライス式内のコロン(:)の位置を記録するために追加されました。これにより、エラーメッセージで正確な位置を指し示すことができます。
  2. コロンの解析ロジックの変更:

    • 変更前はfor p.tok == token.COLON && ncolons < len(index)-1という条件でコロンを処理していましたが、変更後はfor p.tok == token.COLON && ncolons < len(colons)となり、colons配列の長さを基準に処理するようになりました。
    • colons[ncolons] = p.posが追加され、各コロンのトークン位置が記録されるようになりました。
  3. 3インデックススライスにおけるインデックス存在チェックの追加:

    • if ncolons == 2のブロック内で、slice3 = trueが設定され、これが3インデックススライスであることを示します。
    • if index[1] == nil: 2番目のインデックス(high)が解析されなかった場合に実行されます。
      • p.error(colons[0], "2nd index required in 3-index slice"): 最初のコロンの位置(colons[0])を指し示し、「2nd index required in 3-index slice」(3インデックススライスでは2番目のインデックスが必要です)というエラーメッセージを出力します。
      • index[1] = &ast.BadExpr{From: colons[0] + 1, To: colons[1]}: 不正な式を表すast.BadExprノードをindex[1]に割り当てます。これにより、ASTの構造を維持しつつ、エラー箇所をマークします。Fromは最初のコロンの直後、Toは2番目のコロンの位置を示します。
    • if index[2] == nil: 3番目のインデックス(max)が解析されなかった場合に実行されます。
      • p.error(colons[1], "3rd index required in 3-index slice"): 2番目のコロンの位置(colons[1])を指し示し、「3rd index required in 3-index slice」(3インデックススライスでは3番目のインデックスが必要です)というエラーメッセージを出力します。
      • index[2] = &ast.BadExpr{From: colons[1] + 1, To: rbrack}: 同様にast.BadExprindex[2]に割り当てます。Fromは2番目のコロンの直後、Toは右角括弧(])の位置を示します。

これらの変更により、パーサーは3インデックススライスの構文規則をより厳密にチェックし、不正な形式を早期に検出してエラーを報告するようになりました。

src/pkg/go/parser/short_test.goの変更

invalidsという文字列スライスに、以下の新しいテストケースが追加されました。これらはすべて、2番目または3番目のインデックスが欠落している不正な3インデックススライス式です。

  • `package p; func f() { var s []int; _ = s[i:j: /* ERROR "3rd index required" */ ] };`: 3番目のインデックスが欠落しているケース。
  • `package p; func f() { var s []int; _ = s[i: /* ERROR "2nd index required" */ :k] };`: 2番目のインデックスが欠落しているケース。
  • `package p; func f() { var s []int; _ = s[i: /* ERROR "2nd index required" */ :] };`: 2番目と3番目のインデックスが両方欠落しているが、コロンが2つあるケース。
  • `package p; func f() { var s []int; _ = s[: /* ERROR "2nd index required" */ :] };`: lowが省略され、2番目と3番目のインデックスが両方欠落しているケース。
  • `package p; func f() { var s []int; _ = s[: /* ERROR "2nd index required" */ ::] };`: lowが省略され、2番目と3番目のインデックスが両方欠落しており、コロンが3つあるように見える不正なケース。

これらのテストケースは、パーサーが期待されるエラーメッセージ("3rd index required"または"2nd index required")を正確に出力することを確認します。

関連リンク

参考にした情報源リンク