[インデックス 14144] ファイルの概要
このコミットは、Go言語の標準ライブラリtime
パッケージにおけるParseDuration
関数の挙動を修正するものです。具体的には、期間を表す文字列(例: "52763797000ns")に含まれる数値が32ビット符号付き整数の最大値(約2 * 10^9)を超える場合に、正しくパースできるように改善されました。
コミット
commit f2045aadd9bd09b1d1371ee9bc2817f50d7587fd
Author: David Symonds <dsymonds@golang.org>
Date: Mon Oct 15 07:50:13 2012 +1100
time: accept numbers larger than 2^32 in ParseDuration.
Fixes #3374.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6683047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f2045aadd9bd09b1d1371ee9bc2817f50d7587fd
元コミット内容
time: accept numbers larger than 2^32 in ParseDuration.
Fixes #3374.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6683047
変更の背景
Go言語のtime
パッケージには、期間を表す文字列(例: "1h30m", "5s")をtime.Duration
型に変換するParseDuration
関数があります。この関数は、内部的に文字列から数値を抽出するためにleadingInt
というヘルパー関数を使用しています。
以前の実装では、leadingInt
関数が数値をint
型(多くのシステムで32ビット符号付き整数)として扱っていました。このため、期間文字列に含まれる数値がint
型の最大値(2^31 - 1、約21億)を超えると、オーバーフローが発生し、ParseDuration
が正しく動作しない、または予期せぬ結果を返すという問題がありました。
具体的には、Go issue #3374として報告されており、例えば非常に長い期間(例: 52763797000ナノ秒)を表現しようとすると、この制限に引っかかっていました。このような大きな数値は、特にナノ秒単位で時間を扱う場合や、非常に長いタイムアウト期間などを設定する際に必要となることがあります。このコミットは、このint
型の制限を緩和し、より大きな数値を扱えるようにすることで、ParseDuration
関数の堅牢性と実用性を向上させることを目的としています。
前提知識の解説
- Go言語の
time
パッケージ: Go言語の標準ライブラリの一部で、時間、期間、日付などを扱うための機能を提供します。time.Duration
型は、ナノ秒単位で時間を表す整数型です。 time.ParseDuration
関数:time
パッケージの関数で、"1h30m"のような期間文字列をtime.Duration
型にパースします。int
型とint64
型: Go言語における整数型です。int
: 実行環境に依存する符号付き整数型で、通常は32ビットまたは64ビットです。多くのシステムでは32ビットとして実装されています。32ビットの場合、表現できる値の範囲は約 -2 * 10^9 から 2 * 10^9 です。int64
: 常に64ビットの符号付き整数型です。表現できる値の範囲は非常に大きく、約 -9 * 10^18 から 9 * 10^18 です。
- オーバーフロー: 変数に格納できる最大値を超えた数値を代入しようとしたときに発生する現象です。これにより、値が予期せぬものになったり、エラーが発生したりします。
leadingInt
関数: このコミットで変更される内部ヘルパー関数で、文字列の先頭から連続する数字を整数として抽出し、残りの文字列を返す役割を担っています。
技術的詳細
このコミットの主要な技術的変更点は、time
パッケージ内の数値パース処理において、32ビット整数型(int
)の代わりに64ビット整数型(int64
)を使用するように変更したことです。
具体的には、以下の関数と関連するロジックが修正されました。
leadingInt
関数の戻り値の型変更:- 以前は
int
型を返していましたが、int64
型を返すように変更されました。これにより、この関数がパースできる数値の最大値が、32ビット整数の約2 * 10^9から64ビット整数の約9 * 10^18へと大幅に拡張されました。
- 以前は
leadingInt
関数内のオーバーフローチェックの更新:- 数値のパース中にオーバーフローが発生しないようにするためのチェックロジックが、
int
型の最大値(1<<31-10
)からint64
型の最大値(1<<63-10
)に対応するように更新されました。これにより、より大きな数値でも安全に処理できるようになりました。
- 数値のパース中にオーバーフローが発生しないようにするためのチェックロジックが、
atoi
関数とParseDuration
関数でのleadingInt
の利用方法の調整:atoi
関数(文字列を整数に変換する内部関数)とParseDuration
関数は、leadingInt
の戻り値がint64
になったことに合わせて、受け取る変数の型をint
からint64
に変更しました。atoi
関数では、最終的にint
型にキャストし直していますが、これはatoi
が返す値が通常はint
の範囲に収まることを想定しているためです。しかし、leadingInt
がより大きな値を処理できるようになったことで、ParseDuration
が間接的に大きな数値を扱えるようになりました。
これらの変更により、ParseDuration
は、例えば"52763797000ns"のような、32ビット整数では表現できない大きなナノ秒単位の期間も正確にパースできるようになりました。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルと関数は以下の通りです。
src/pkg/time/format.go
:atoi
関数:leadingInt
の戻り値を受け取る変数の型がint
からint64
に変更され、その後int
にキャストされるようになりました。leadingInt
関数:- 関数のシグネチャが
func leadingInt(s string) (x int, rem string, err error)
からfunc leadingInt(s string) (x int64, rem string, err error)
に変更されました。 - オーバーフローチェックの条件が
x >= (1<<31-10)/10
からx >= (1<<63-10)/10
に変更されました。 - 数値の加算ロジックが
x = x*10 + int(c) - '0'
からx = x*10 + int64(c) - '0'
に変更されました。
- 関数のシグネチャが
ParseDuration
関数:leadingInt
の戻り値を受け取る変数の型がvar x int
からvar x int64
に変更されました。
src/pkg/time/time_test.go
:parseDurationTests
変数に、大きな数値を含む新しいテストケース{"52763797000ns", true, 52763797000 * Nanosecond}
が追加されました。
コアとなるコードの解説
以下に、変更された主要なコードスニペットとその解説を示します。
src/pkg/time/format.go
の leadingInt
関数
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -954,18 +955,18 @@ func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string,
var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
// leadingInt consumes the leading [0-9]* from s.
-func leadingInt(s string) (x int, rem string, err error) {
+func leadingInt(s string) (x int64, rem string, err error) {
i := 0
for ; i < len(s); i++ {
\tc := s[i]
\tif c < '0' || c > '9' {
\t\tbreak
\t}
-\t\tif x >= (1<<31-10)/10 {
+\t\tif x >= (1<<63-10)/10 {
\t\t// overflow
\t\treturn 0, "", errLeadingInt
\t}
-\t\tx = x*10 + int(c) - '0'
+\t\tx = x*10 + int64(c) - '0'
}
return x, s[i:], nil
}
func leadingInt(s string) (x int64, rem string, err error)
:- この行は、
leadingInt
関数の戻り値の型がint
からint64
に変更されたことを示しています。これにより、x
変数に格納できる数値の範囲が拡張されます。
- この行は、
if x >= (1<<63-10)/10
:- これはオーバーフローチェックの条件です。
1<<63-1
は64ビット符号付き整数の最大値です。(1<<63-10)/10
は、次の桁を読み込んだときにオーバーフローしないようにするための安全マージンを考慮したチェックです。この変更により、int64
の範囲でのオーバーフローを検出できるようになりました。
- これはオーバーフローチェックの条件です。
x = x*10 + int64(c) - '0'
:- 文字列から抽出した数字
c
をint64
にキャストしてからx
に加算しています。これにより、計算中に中間的なオーバーフローが発生するのを防ぎ、x
がint64
として正しく更新されることを保証します。
- 文字列から抽出した数字
src/pkg/time/format.go
の atoi
関数
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -323,7 +323,8 @@ func atoi(s string) (x int, err error) {
\t\tneg = true
\t\ts = s[1:]
\t}
-\t\tx, rem, err := leadingInt(s)
+\t\tq, rem, err := leadingInt(s)
+\t\tx = int(q)
\tif err != nil || rem != "" {
\t\treturn 0, atoiError
\t}
q, rem, err := leadingInt(s)
:leadingInt
がint64
を返すようになったため、その戻り値を受け取る新しい変数q
がint64
型として宣言されます。
x = int(q)
:atoi
関数自体の戻り値はint
型であるため、leadingInt
から得られたint64
値q
をint
型に明示的にキャストしています。これは、atoi
が通常パースする数値がint
の範囲に収まることを前提としています。
src/pkg/time/format.go
の ParseDuration
関数
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -1010,7 +1011,7 @@ func ParseDuration(s string) (Duration, error) {
for s != "" {
\tg := float64(0) // this element of the sequence
-\t\tvar x int
+\t\tvar x int64
\tvar err error
\t// The next character must be [0-9.]
var x int64
:ParseDuration
関数内でleadingInt
の戻り値を受け取る変数x
の型がint
からint64
に変更されました。これにより、ParseDuration
はleadingInt
が返す大きな数値を直接扱うことができるようになります。
src/pkg/time/time_test.go
のテストケース追加
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -999,6 +999,8 @@ var parseDurationTests = []struct {
{\"-2m3.4s\", true, -(2*Minute + 3*Second + 400*Millisecond)},\n {\"1h2m3s4ms5us6ns\", true, 1*Hour + 2*Minute + 3*Second + 4*Millisecond + 5*Microsecond + 6*Nanosecond},\n {\"39h9m14.425s\", true, 39*Hour + 9*Minute + 14*Second + 425*Millisecond},\n+\t// large value\n+\t{\"52763797000ns\", true, 52763797000 * Nanosecond},\n \n // errors\n {\"\", false, 0},\
{"52763797000ns", true, 52763797000 * Nanosecond}
:- この新しいテストケースは、32ビット整数では表現できない大きな数値(527億ナノ秒)を含む期間文字列が正しくパースされることを検証します。これは、今回の変更が意図通りに機能していることを確認するための重要なテストです。
関連リンク
- Go issue #3374: https://github.com/golang/go/issues/3374 (このコミットが修正した問題のトラッカー)
- Gerrit Change-ID 6683047: https://golang.org/cl/6683047 (このコミットのコードレビューページ)
参考にした情報源リンク
- Go言語の公式ドキュメント:
time
パッケージ - Go言語のソースコード:
src/pkg/time/format.go
,src/pkg/time/time_test.go
- Go issue #3374の議論