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

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

このコミットは、Go言語のreflectパッケージにおけるMakeSlice関数の堅牢性を向上させるための変更です。具体的には、MakeSlice関数に無効なlen(長さ)やcap(容量)の引数が渡された場合に、即座にパニック(実行時エラー)を発生させるように修正されています。これにより、不正な引数による予期せぬ動作や、後続の処理でのクラッシュを防ぎ、プログラムの安定性を高めます。

コミット

commit 11cc5a26d51bb707e6e40c796827f5b3a9b6be04
Author: David Symonds <dsymonds@golang.org>
Date:   Fri Mar 16 17:28:16 2012 +1100

    reflect: panic if MakeSlice is given bad len/cap arguments.
    
    Fixes #3330.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/5847043

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

https://github.com/golang/go/commit/11cc5a26d51bb707e6e40c796827f5b3a9b6be04

元コミット内容

reflect: panic if MakeSlice is given bad len/cap arguments.

このコミットは、reflect.MakeSlice関数に不正なlen(長さ)またはcap(容量)の引数が与えられた場合にパニックを発生させるように修正します。これはIssue #3330を修正するものです。

変更の背景

Go言語のreflectパッケージは、実行時に型情報を検査し、値を操作するための機能を提供します。reflect.MakeSlice関数は、指定された型、長さ、容量を持つ新しいスライスを作成するために使用されます。

スライスの長さ(len)と容量(cap)には、以下のような基本的な制約があります。

  1. lenは非負でなければならない。
  2. capは非負でなければならない。
  3. lencap以下でなければならない(0 <= len <= cap)。

このコミットが導入される前は、reflect.MakeSlice関数がこれらの制約を満たさない不正なlencapの引数を受け取った場合、明確なエラーを返さずに、予期せぬ動作を引き起こす可能性がありました。例えば、負の長さや容量を持つスライスを作成しようとすると、メモリの不正アクセスや、後続の操作でランタイムパニックが発生する可能性がありました。

Issue #3330は、このような不正な引数に対するMakeSliceの挙動が未定義であり、潜在的なバグの原因となることを指摘していました。このコミットは、このような不正な状態を早期に検出し、開発者に問題があることを明確に伝えるために、即座にパニックを発生させるように変更されました。これにより、デバッグが容易になり、より堅牢なプログラムの作成が促進されます。

前提知識の解説

Go言語のreflectパッケージ

reflectパッケージは、Goプログラムが自身の構造を検査し、実行時に変数の型や値を操作するための機能を提供します。これは、ジェネリックなデータ構造の操作、シリアライゼーション/デシリアライゼーション、RPCフレームワーク、テストツールなど、高度なプログラミングパターンで利用されます。しかし、リフレクションは型安全性を損なう可能性があり、パフォーマンスオーバーヘッドも伴うため、必要な場合にのみ慎重に使用すべきです。

Go言語のスライス(Slice)

スライスはGo言語における可変長シーケンスのデータ型です。配列の上に構築されており、配列の一部を参照する「ビュー」のようなものです。スライスは以下の3つの要素で構成されます。

  • ポインタ: スライスが参照する基底配列の先頭要素へのポインタ。
  • 長さ(Length, len: スライスに含まれる要素の数。len(s)で取得できます。
  • 容量(Capacity, cap: スライスの基底配列の先頭から、スライスが拡張できる最大要素数。cap(s)で取得できます。

スライスはmake([]T, len, cap)のようにして作成できます。ここでTは要素の型です。

lencapの制約

スライスのlencapには以下の関係が常に成り立ちます。 0 <= len <= cap

  • lenが負の値であることは論理的にありえません。スライスに負の数の要素が含まれることはありません。
  • capが負の値であることも論理的にありえません。基底配列の容量が負になることはありません。
  • lencapを超えることはありません。スライスは基底配列の範囲内でしか要素を持つことができません。

これらの制約が破られた場合、それはプログラムの論理的な誤りを示しており、未定義の動作やランタイムエラーにつながる可能性があります。

技術的詳細

このコミットの技術的な詳細は、reflect.MakeSlice関数が受け取るlencapの引数に対して、明示的なバリデーション(検証)ロジックを追加した点にあります。

以前のMakeSliceの実装では、これらの引数が不正な値(例: 負の数、len > cap)であっても、そのまま内部処理を進めてしまう可能性がありました。これにより、以下のような問題が発生する恐れがありました。

  1. メモリの不正アクセス: 負の長さや容量でスライスを初期化しようとすると、メモリ確保のシステムコールが不正な引数を受け取り、クラッシュしたり、予期せぬメモリ領域を操作したりする可能性があります。
  2. 未定義の動作: Goランタイムが不正なスライス構造を扱おうとした際に、その挙動が保証されず、予測不能な結果を招く可能性があります。
  3. デバッグの困難さ: 問題が発生した際に、その根本原因がMakeSliceへの不正な引数にあることを特定するのが困難になる可能性があります。エラーが実際に発生する場所が、MakeSliceの呼び出し箇所から離れている場合があるためです。

このコミットでは、これらの問題を解決するために、MakeSlice関数の冒頭で以下の3つの条件チェックを追加しました。

  • len < 0
  • cap < 0
  • len > cap

これらの条件のいずれかが真である場合、panic関数を呼び出して実行を中断します。panicは、回復不能なエラーが発生したことを示し、プログラムの実行を停止させます。これにより、不正な引数による問題が早期に、かつ明確に報告されるようになります。

この変更は、reflectパッケージのAPIを使用する開発者に対して、MakeSliceに渡す引数の正当性を保証する責任があることを明確に促します。また、Go言語の設計哲学である「失敗は早期に、かつ明確に」という原則に沿った改善と言えます。

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

変更はsrc/pkg/reflect/value.goファイルに対して行われました。

--- a/src/pkg/reflect/value.go
+++ b/src/pkg/reflect/value.go
@@ -1632,6 +1632,15 @@ func MakeSlice(typ Type, len, cap int) Value {
 	if typ.Kind() != Slice {
 		panic("reflect.MakeSlice of non-slice type")
 	}
+	if len < 0 {
+		panic("reflect.MakeSlice: negative len")
+	}
+	if cap < 0 {
+		panic("reflect.MakeSlice: negative cap")
+	}
+	if len > cap {
+		panic("reflect.MakeSlice: len > cap")
+	}
 
 	// Declare slice so that gc can see the base pointer in it.
 	var x []byte

コアとなるコードの解説

追加されたコードは、MakeSlice関数の既存の型チェック(if typ.Kind() != Slice)の直後に挿入されています。

  1. if len < 0 { panic("reflect.MakeSlice: negative len") }

    • この行は、引数lenが負の値であるかどうかをチェックします。もし負であれば、"reflect.MakeSlice: negative len"というメッセージと共にパニックを発生させます。
  2. if cap < 0 { panic("reflect.MakeSlice: negative cap") }

    • この行は、引数capが負の値であるかどうかをチェックします。もし負であれば、"reflect.MakeSlice: negative cap"というメッセージと共にパニックを発生させます。
  3. if len > cap { panic("reflect.MakeSlice: len > cap") }

    • この行は、引数lenが引数capよりも大きいかどうかをチェックします。もしlencapより大きければ、"reflect.MakeSlice: len > cap"というメッセージと共にパニックを発生させます。

これらのチェックは、スライスの長さと容量に関する基本的な数学的・論理的制約を強制するものです。これらの制約が破られた場合、それはプログラマの意図しない、または不正な状態を示しているため、早期にパニックを発生させることで、問題の特定と修正を容易にしています。

関連リンク

  • Go言語のreflectパッケージのドキュメント: https://pkg.go.dev/reflect
  • Go言語のスライスに関する公式ブログ記事: https://go.dev/blog/slices-intro
  • Go言語のIssue #3330: reflect.MakeSlice should panic on bad len/cap arguments (このコミットが修正したIssue)
    • 正確なIssueページは、GoのIssueトラッカーの移行により、古いIssue番号では直接アクセスできない場合がありますが、コミットメッセージに記載されているため、この変更の背景にある問題として認識されています。

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(src/pkg/reflect/value.go
  • Go言語のIssueトラッカー (Issue #3330)
  • Go言語のコードレビューシステム (CL 5847043)