[インデックス 18815] ファイルの概要
このコミットは、Go言語の標準ライブラリ time
パッケージにおける ParseDuration
関数が、非常に大きな時間文字列をパースする際に発生する int64
オーバーフローの問題を修正するものです。具体的には、パースされた期間が time.Duration
型(基底型は int64
)で表現できる範囲を超えた場合に、不正な値(ランダムな値や負の値)が返される可能性があった問題を解決します。
コミット
commit 4ca6e588e4bac8bffa56dfe42526d7a12e7cb69c
Author: Adam Langley <agl@golang.org>
Date: Mon Mar 10 12:33:45 2014 -0400
time: handle int64 overflow in ParseDuration.
Previously, passing a long duration to ParseDuration could result in
random, even negative, values.
LGTM=r
R=golang-codereviews, bradfitz, r
CC=golang-codereviews
https://golang.org/cl/72120043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4ca6e588e4bac8bffa56dfe42526d7a12e7cb69c
元コミット内容
time: handle int64 overflow in ParseDuration.
Previously, passing a long duration to ParseDuration could result in
random, even negative, values.
LGTM=r
R=golang-codereviews, bradfitz, r
CC=golang-codereviews
https://golang.org/cl/72120043
変更の背景
Go言語の time
パッケージには、期間を表す Duration
型があります。この型は int64
を基底としており、ナノ秒単位で時間を表現します。ParseDuration
関数は、"1h30m" のような文字列から Duration
型の値を生成するために使用されます。
このコミットが導入される前は、ParseDuration
関数が非常に大きな期間(例えば、数百万時間)を表す文字列を処理する際に、内部的な計算で float64
を使用していました。float64
は int64
よりも広い範囲の値を表現できますが、精度には限界があります。問題は、float64
で計算された結果が int64
の最大値または最小値を超えた場合、その float64
の値を int64
に変換する際にオーバーフローが発生し、予期せぬ結果(例えば、非常に大きな正の期間が負の値として解釈される、またはランダムな値になる)が生じていたことです。
このようなオーバーフローは、プログラムの予期せぬ動作やクラッシュにつながる可能性があり、特に時間計算が重要なシステムでは深刻なバグとなり得ます。このコミットは、この潜在的なデータ破損を防ぎ、ParseDuration
関数が int64
の範囲を超える期間を検出した場合に明示的にエラーを返すようにすることで、堅牢性を向上させることを目的としています。
前提知識の解説
Go言語の time.Duration
型
Go言語の time
パッケージには、時間の期間を表す Duration
型が定義されています。
type Duration int64
この定義からわかるように、Duration
は int64
のエイリアスであり、ナノ秒単位で期間を保持します。int64
の最大値は約 9.22 × 10^18 です。これは約 292 年に相当します。したがって、292年を超える期間は time.Duration
で正確に表現できません。
int64
のオーバーフロー
コンピュータの数値型には、表現できる値の範囲に限りがあります。int64
は64ビットの符号付き整数型であり、その範囲は -2^63
から 2^63 - 1
までです。
- 最小値:
-9,223,372,036,854,775,808
(約 -9.22 × 10^18) - 最大値:
9,223,372,036,854,775,807
(約 9.22 × 10^18)
数値計算の結果がこの範囲を超えた場合、オーバーフロー(またはアンダーフロー)が発生します。Go言語では、整数型のオーバーフローはデフォルトでラップアラウンド(例えば、最大値に1を加えると最小値に戻る)しますが、浮動小数点数から整数への変換では、値が範囲外の場合に予期せぬ結果(例えば、最大値または最小値にクリップされる、あるいは実装依存の動作)になることがあります。
float64
と int64
の変換
float64
は倍精度浮動小数点数であり、int64
よりもはるかに広い範囲の値を表現できますが、その精度は有限です。非常に大きな整数を float64
で表現しようとすると、精度が失われる可能性があります。また、float64
から int64
への変換では、float64
の値が int64
の範囲外である場合、Go言語の仕様では結果が未定義となるか、あるいは特定のプラットフォームでは最大値や最小値にクリップされることがあります。このコミットの背景にある問題は、まさにこの変換時の挙動の不確実性とその結果としての不正な値の生成でした。
技術的詳細
このコミットの技術的な核心は、time.ParseDuration
関数が文字列からパースした期間の値を time.Duration
型(int64
)に変換する前に、その値が int64
の表現範囲内に収まっているかを明示的にチェックする点にあります。
ParseDuration
関数は、入力された期間文字列(例: "1h30m", "5s")を解析し、最終的にナノ秒単位の float64
値 f
を生成します。この f
が time.Duration
型の基底である int64
に変換される直前に、以下のチェックが追加されました。
if f < float64(-1<<63) || f > float64(1<<63-1) {
return 0, errors.New("time: overflow parsing duration")
}
このコードスニペットは、f
が int64
の最小値 (-1<<63
) より小さいか、または int64
の最大値 (1<<63-1
) より大きいかを評価します。
1<<63
は、int64
の最大値2^63 - 1
の次の値(符号なしの場合の2^63
)をint64
として表現しようとするとオーバーフローし、結果としてint64
の最小値-2^63
となります。したがって、float64(-1<<63)
はint64
の最小値をfloat64
で表現したものです。float64(1<<63-1)
はint64
の最大値をfloat64
で表現したものです。
もし f
がこの int64
の範囲外であれば、errors.New("time: overflow parsing duration")
を返して、パースが失敗したことを明示的に通知します。これにより、呼び出し元はオーバーフローを検出し、適切にエラーハンドリングを行うことができます。
また、この変更には、オーバーフローケースをテストするための新しいテストケースが src/pkg/time/time_test.go
に追加されています。これは、"3000000h"
(300万時間)という非常に大きな期間文字列を ParseDuration
に渡した場合に、エラーが返されることを確認するものです。300万時間は約342年に相当し、これは int64
で表現できる約292年という期間を明らかに超えるため、オーバーフローが発生するはずです。
コアとなるコードの変更箇所
src/pkg/time/format.go
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -1240,5 +1240,8 @@ func ParseDuration(s string) (Duration, error) {
if neg {
f = -f
}
+ if f < float64(-1<<63) || f > float64(1<<63-1) {
+ return 0, errors.New("time: overflow parsing duration")
+ }
return Duration(f), nil
}
src/pkg/time/time_test.go
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -842,6 +842,7 @@ var parseDurationTests = []struct {
{"-.", false, 0},
{".s", false, 0},
{"+.s", false, 0},
+ {"3000000h", false, 0}, // overflow
}
func TestParseDuration(t *testing.T) {
コアとなるコードの解説
src/pkg/time/format.go
の変更
ParseDuration
関数内で、期間を表す float64
型の変数 f
が最終的な Duration
型(int64
)に変換される直前に、オーバーフローチェックが追加されました。
if f < float64(-1<<63) || f > float64(1<<63-1) {
return 0, errors.New("time: overflow parsing duration")
}
float64(-1<<63)
: これはint64
の最小値をfloat64
にキャストしたものです。1<<63
はint64
の範囲外の値を生成しようとしますが、Goの整数オーバーフローのルールにより、int64
の最小値(-9223372036854775808
)になります。float64(1<<63-1)
: これはint64
の最大値(9223372036854775807
)をfloat64
にキャストしたものです。
この if
文は、パースされた期間 f
が int64
で表現できる範囲(int64
の最小値から最大値まで)を超えているかどうかをチェックします。もし範囲外であれば、0
と共に新しいエラー errors.New("time: overflow parsing duration")
を返します。これにより、呼び出し元は期間が大きすぎて表現できない場合に、明確なエラーを受け取ることができます。
src/pkg/time/time_test.go
の変更
parseDurationTests
というテストケースのスライスに、新しいエントリが追加されました。
{"3000000h", false, 0}, // overflow
"3000000h"
: これはテスト対象の期間文字列です。300万時間という非常に大きな値であり、int64
のDuration
型では表現できないオーバーフローを引き起こすことを意図しています。false
: この値は、ParseDuration
がこの入力に対してエラーを返すことを期待していることを示します。0
: 期待されるDuration
の値ですが、このテストケースの主な目的はエラーが返されることの確認であるため、ここではあまり重要ではありません。
このテストケースの追加により、ParseDuration
がオーバーフローを正しく検出し、エラーを返すという新しい動作が保証されます。
関連リンク
- Go
time
パッケージのドキュメント: https://pkg.go.dev/time - Go
time.Duration
のドキュメント: https://pkg.go.dev/time#Duration - Go
time.ParseDuration
のドキュメント: https://pkg.go.dev/time#ParseDuration
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go Code Review (Gerrit): https://golang.org/cl/72120043 (元の変更リスト)
- Go言語の整数型と浮動小数点数型に関する一般的な情報 (Go言語の公式ドキュメントやチュートリアル)
- A Tour of Go: https://go.dev/tour/basics/11 (数値型に関する基本的な情報)
- Go言語の仕様: https://go.dev/ref/spec#Numeric_types (数値型の詳細な仕様)
- Go言語の仕様: https://go.dev/ref/spec#Conversions (型変換に関する詳細な仕様)