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

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

このコミットは、test/ken/array.go ファイルに新しいテストケースを追加するものです。このファイルはGo言語のテストスイートの一部であり、配列やスライスに関する様々な挙動を検証するために使用されます。

コミット

commit 450c955bd973f153ba99c340022a424be5c75f73
Author: Ian Lance Taylor <iant@golang.org>
Date:   Fri Feb 3 06:29:30 2012 -0800

    test: test slice beyond len
    
    When slicing a slice, the bounds may be > len as long as they
    are <= cap.  Interestingly, gccgo got that wrong and still
    passed the testsuite and all the library tests.
    
    R=golang-dev, rsc, iant
    CC=golang-dev
    https://golang.org/cl/5622053

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

https://github.com/golang/go/commit/450c955bd973f153ba99c340022a424be5c75f73

元コミット内容

commit 450c955bd973f153ba99c340022a424be5c75f73
Author: Ian Lance Taylor <iant@golang.org>
Date:   Fri Feb 3 06:29:30 2012 -0800

    test: test slice beyond len
    
    When slicing a slice, the bounds may be > len as long as they
    are <= cap.  Interestingly, gccgo got that wrong and still
    passed the testsuite and all the library tests.
    
    R=golang-dev, rsc, iant
    CC=golang-dev
    https://golang.org/cl/5622053

変更の背景

このコミットの主な背景は、Go言語のコンパイラの一つであるgccgoが、スライスのスライス操作において、len(現在の長さ)を超えてcap(容量)までの範囲でスライスを作成する際の挙動を誤って解釈していたという問題にあります。

Goのスライスは、基盤となる配列への参照であり、lencapという2つの重要なプロパティを持ちます。スライス操作(s[low:high])では、highインデックスが現在のスライスのlenを超えていても、基盤となる配列のcap以内であれば有効です。これにより、既存の基盤配列のメモリを再利用しつつ、スライスの「ビュー」を拡張することができます。

しかし、gccgoはこの仕様を正しく実装しておらず、lenを超えるスライス操作を誤って処理していました。驚くべきことに、このバグが存在するにもかかわらず、既存のテストスイートや標準ライブラリのテストはすべてパスしていました。これは、既存のテストがこの特定のエッジケースを十分にカバーしていなかったことを示唆しています。

このコミットは、gccgoのこの誤った挙動を露呈させ、修正を促すための新しいテストケースを追加することで、Go言語の仕様に対するコンパイラの準拠を強化することを目的としています。

前提知識の解説

Go言語のスライス

Go言語のスライスは、配列のセグメントを参照するデータ構造です。スライスは、基盤となる配列、そのセグメントの長さ(len)、およびそのセグメントが基盤となる配列から拡張できる最大容量(cap)の3つの要素で構成されます。

  • len (長さ): スライスに含まれる要素の数です。len0からcapまでの範囲で変動します。
  • cap (容量): スライスの基盤となる配列の先頭から、スライスが参照している部分の末尾までの要素の数です。これは、スライスが再割り当てなしで保持できる要素の最大数を示します。

スライスのスライス操作 (s[low:high])

Goでは、既存のスライスから新しいスライスを作成する「スライスのスライス」操作が可能です。この操作はs[low:high]という形式で行われます。

  • low: 新しいスライスの開始インデックス(基盤となる配列に対する相対位置)。
  • high: 新しいスライスの終了インデックス(基盤となる配列に対する相対位置)。highは含まれません。

この操作において重要なのは、highインデックスが元のスライスのlenを超えていても、元のスライスのcap以下であれば有効であるという点です。

例:

s := make([]int, 3, 5) // len=3, cap=5 のスライスを作成
// s は現在 [0 0 0] を参照している。基盤配列は [0 0 0 X X] のようなイメージ。

// s[0:5] は有効なスライス操作
// 新しいスライスは len=5, cap=5 となり、基盤配列全体を参照する。
// これは、元のスライスの len (3) を超えているが、cap (5) 以内であるため許容される。
new_s := s[0:5]

もしhighcapを超えた場合、Goランタイムはパニック(slice bounds out of range)を引き起こします。

gccgo

gccgoは、GCC(GNU Compiler Collection)のフロントエンドとして実装されたGo言語のコンパイラです。Go言語の公式コンパイラであるgcとは異なる実装であり、GoプログラムをGCCのバックエンドを通じてコンパイルします。異なる実装であるため、Go言語の仕様に対する解釈や最適化の挙動がgcと異なる場合があります。

技術的詳細

このコミットが対処している技術的な問題は、Go言語のスライス操作におけるhighインデックスの許容範囲に関するgccgoの誤った実装です。

Go言語の仕様では、スライスのスライス操作s[low:high]において、highインデックスは0 <= low <= high <= cap(s)という条件を満たす必要があります。ここで重要なのは、highが元のスライスのlen(s)を超えていても、cap(s)以内であれば有効であるという点です。この挙動は、既存の基盤配列のメモリを効率的に再利用し、スライスの「ビュー」を拡張するために不可欠です。

しかし、gccgoは過去にこの仕様を正しく扱えていませんでした。具体的には、highlenを超えているがcap以内であるようなスライス操作に対して、gccgoが誤ったコードを生成したり、期待される結果を返さなかったりするバグが存在したと考えられます。

コミットメッセージにある「Interestingly, gccgo got that wrong and still passed the testsuite and all the library tests.」という記述は、このバグが既存のテストでは検出されなかったことを示しています。これは、Goのテストスイートが、スライスのlencapの境界条件を厳密にテストするような特定のシナリオを欠いていたためです。

このコミットで追加されたテストケースは、まさにこのエッジケース、つまりlenを超えてcapまでの範囲でスライスを作成するシナリオを明示的に検証することで、gccgoのようなコンパイラがGo言語の仕様に厳密に準拠していることを保証しようとしています。このようなテストは、異なるコンパイラ実装間での互換性を高め、Goプログラムの移植性と信頼性を向上させる上で非常に重要です。

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

変更はtest/ken/array.goファイルに対して行われました。

--- a/test/ken/array.go
+++ b/test/ken/array.go
@@ -68,6 +68,9 @@ func testpdpd() {
 
  	a = a[5:25]
  	res(sumpd(a), 5, 25)
+
+ 	a = a[30:95]
+ 	res(sumpd(a), 35, 100)
 }
 
 // call ptr fixed with ptr fixed

具体的には、testpdpd関数内に以下の2行が追加されています。

 	a = a[30:95]
 	res(sumpd(a), 35, 100)

コアとなるコードの解説

追加されたコードは、testpdpd関数内の既存のスライス操作の後に続きます。

  1. a = a[30:95]

    • この行が、lenを超えてcapまでの範囲でスライスを作成するテストの核心です。
    • この時点でのスライスaの具体的なlencapは、testpdpd関数のこれまでの処理に依存しますが、このテストが意図しているのは、aの現在のlenが30未満であり、かつcapが95以上であるような状況です。
    • もしalenが30未満で、capが95以上であれば、このスライス操作はGoの仕様上有効です。新しいスライスaは、元の基盤配列のインデックス30から94までの要素を参照するようになります。
    • gccgoがこの操作を誤って処理した場合、後続のsumpd(a)の結果が期待通りにならないか、あるいはパニックを引き起こす可能性があります。
  2. res(sumpd(a), 35, 100)

    • res関数は、テスト結果を検証するためのユーティリティ関数です。
    • sumpd(a)は、新しくスライスされたaの要素の合計を計算します。
    • 35100は、それぞれ期待される合計値と、おそらくスライスの長さや要素の範囲に関連する期待値です。
    • この行は、a = a[30:95]というスライス操作がGoの仕様通りに正しく行われた場合に、sumpd(a)が特定の期待値を返すことを検証します。もしgccgoがスライス操作を誤っていた場合、このres関数が失敗し、バグが検出されることになります。

このテストケースは、Goのスライスが持つ柔軟なスライス機能、特にlencapの関係性を正しく理解し、実装しているかを検証するための重要なエッジケースをカバーしています。

関連リンク

参考にした情報源リンク