[インデックス 16617] ファイルの概要
このコミットは、Go言語の標準ライブラリtime
パッケージにおけるSub
メソッドの整数オーバーフロー問題を修正するものです。具体的には、time.Sub
がDuration
型(int64
)で表現できる範囲を超える結果を生成した場合に、適切な最小値または最大値を返すように変更されています。
コミット
commit fc0b5ef0fdc3bc90d93759b068f9be486f4252da
Author: Rick Arnold <rickarnoldjr@gmail.com>
Date: Fri Jun 21 18:07:57 2013 -0700
time: handle integer overflow in Sub
If time.Sub results in a value that won't fit in a Duration (int64),
return either the min or max int64 value as appropriate.
Fixes #5011.
R=golang-dev, bradfitz, r, rsc
CC=golang-dev
https://golang.org/cl/10328043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fc0b5ef0fdc3bc90d93759b068f9be486f4252da
元コミット内容
time: handle integer overflow in Sub
If time.Sub results in a value that won't fit in a Duration (int64),
return either the min or max int64 value as appropriate.
Fixes #5011.
R=golang-dev, bradfitz, r, rsc
CC=golang-dev
https://golang.org/cl/10328043
変更の背景
このコミットは、Go言語のtime
パッケージにおけるTime.Sub
メソッドが、2つのTime
値の差を計算する際に発生しうる整数オーバーフローの問題に対処するために行われました。time.Duration
型はint64
として定義されており、これは約290年間の期間を表現できます。しかし、非常に遠い過去または未来のTime
値同士の差を計算しようとすると、このint64
の範囲を超えてしまう可能性がありました。
元のSub
メソッドの実装では、単純に秒とナノ秒の差を計算してDuration
にキャストしていました。この計算結果がint64
の最大値(maxDuration
)や最小値(minDuration
)を超えた場合、予期せぬ値(オーバーフローやアンダーフローによる符号反転など)が返される可能性がありました。これは、プログラムのロジックに誤った時間差が伝播し、バグを引き起こす原因となります。
Go言語のIssue #5011(コミットメッセージに記載されていますが、Web検索では直接的なIssue番号のヒットは確認できませんでした。これは内部的なトラッキング番号であるか、または関連する議論が別のIssueに統合された可能性があります)は、このようなDuration
のオーバーフローが問題となるケースを指摘していたと考えられます。特に、time.Duration
がint64
であるため、非常に大きな時間差を表現しようとすると、その限界に達してしまうという根本的な制約があります。このコミットは、この制約内で最も安全かつ予測可能な挙動を提供することを目指しています。
前提知識の解説
time.Time
型: Go言語で特定の日時を表す構造体です。内部的には、エポック(1970年1月1日UTC)からの経過秒数とナノ秒数を保持しています。time.Duration
型: Go言語で時間の長さを表す型で、int64
のエイリアスです。ナノ秒単位で時間を表現し、約290年間の期間を表現できます。int64
の最大値は2^63 - 1
(約 9.22 x 10^18) です。int64
の最小値は-2^63
(約 -9.22 x 10^18) です。- 1秒は10^9ナノ秒なので、
maxDuration
は約9.22 x 10^9
秒、つまり約292
年に相当します。
- 整数オーバーフロー/アンダーフロー: 整数型で表現できる最大値を超えた場合に、値が最小値に戻ってしまう現象をオーバーフローと呼びます。逆に、最小値を下回った場合に最大値に戻ってしまう現象をアンダーフローと呼びます。Go言語の整数型は、デフォルトでラップアラウンド(循環)挙動を示します。
Time.Sub(u Time) Duration
メソッド:t
とu
の間の時間差をDuration
として返します。計算式はt - u
です。Time.Add(d Duration) Time
メソッド:t
にd
の期間を加算した新しいTime
値を返します。Time.Equal(u Time) bool
メソッド: 2つのTime
値が等しいかどうかを判定します。Time.Before(u Time) bool
メソッド:t
がu
より前の時刻であるかどうかを判定します。
技術的詳細
このコミットの主要な目的は、Time.Sub
メソッドがDuration
のint64
範囲を超える結果を生成した場合に、オーバーフローやアンダーフローによる不正な値を返すのではなく、Duration
の最大値または最小値を返すようにすることです。
変更は主に以下の点で行われています。
-
minDuration
とmaxDuration
定数の導入:time
パッケージ内に、Duration
型の最小値と最大値を表す定数minDuration
とmaxDuration
が導入されました。const ( minDuration Duration = -1 << 63 // int64の最小値 maxDuration Duration = 1<<63 - 1 // int64の最大値 )
これらの定数は、
int64
のビット表現を利用して、それぞれ最小値と最大値を正確に定義しています。 -
Time.Sub
メソッドのロジック変更:Time.Sub
メソッドの内部ロジックが変更され、計算されたDuration
がint64
の範囲内に収まっているかを検証するようになりました。 元の実装は単純にDuration(t.sec-u.sec)*Second + Duration(t.nsec-u.nsec)
でした。 新しい実装では、まずd
として通常の計算結果を求めます。d := Duration(t.sec-u.sec)*Second + Duration(t.nsec-u.nsec)
次に、この
d
が正しい値であるかを検証するために、u.Add(d).Equal(t)
というチェックを行います。u.Add(d)
は、時刻u
に計算された期間d
を加算した時刻を返します。- もし
d
が正しい期間であれば、u.Add(d)
の結果は元の時刻t
と等しくなるはずです。 - このチェックが
true
であれば、d
はDuration
の範囲内に収まっており、正しい値であると判断し、そのままd
を返します。
このチェックが
false
の場合、d
はオーバーフローまたはアンダーフローを起こしている可能性が高いと判断し、以下の条件で適切なminDuration
またはmaxDuration
を返します。t.Before(u)
がtrue
の場合(つまりt
がu
より前の時刻で、差が負になるべき場合)、計算結果がアンダーフローを起こして正の値になったか、または負の範囲外になったと判断し、minDuration
を返します。- それ以外の場合(つまり
t
がu
より後の時刻で、差が正になるべき場合)、計算結果がオーバーフローを起こして負の値になったか、または正の範囲外になったと判断し、maxDuration
を返します。
このロジックにより、Sub
メソッドは常にDuration
の有効な範囲内の値を返すことが保証されます。
コアとなるコードの変更箇所
src/pkg/time/time.go
--- a/src/pkg/time/time.go
+++ b/src/pkg/time/time.go
@@ -424,6 +424,11 @@ func (t Time) YearDay() int {
// largest representable duration to approximately 290 years.
type Duration int64
+const (
+ minDuration Duration = -1 << 63
+ maxDuration Duration = 1<<63 - 1
+)
+
// Common durations. There is no definition for units of Day or larger
// to avoid confusion across daylight savings time zone transitions.
//
@@ -611,10 +616,21 @@ func (t Time) Add(d Duration) Time {
return t
}
-// Sub returns the duration t-u.
+// Sub returns the duration t-u. If the result exceeds the maximum (or minimum)
+// value that can be stored in a Duration, the maximum (or minimum) duration
+// will be returned.
// To compute t-d for a duration d, use t.Add(-d).
func (t Time) Sub(u Time) Duration {
-\treturn Duration(t.sec-u.sec)*Second + Duration(t.nsec-u.nsec)
+\td := Duration(t.sec-u.sec)*Second + Duration(t.nsec-u.nsec)
+\t// Check for overflow or underflow.
+\tswitch {
+\tcase u.Add(d).Equal(t):
+\t\treturn d // d is correct
+\tcase t.Before(u):
+\t\treturn minDuration // t - u is negative out of range
+\tdefault:
+\t\treturn maxDuration // t - u is positive out of range
+\t}
}
// Since returns the time elapsed since t.
src/pkg/time/time_test.go
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -1327,6 +1327,40 @@ func TestLoadFixed(t *testing.T) {
}
}
+const (
+ minDuration Duration = -1 << 63
+ maxDuration Duration = 1<<63 - 1
+)
+
+var subTests = []struct {
+ t Time
+ u Time
+ d Duration
+}{
+ {Time{}, Time{}, Duration(0)},
+ {Date(2009, 11, 23, 0, 0, 0, 1, UTC), Date(2009, 11, 23, 0, 0, 0, 0, UTC), Duration(1)},
+ {Date(2009, 11, 23, 0, 0, 0, 0, UTC), Date(2009, 11, 24, 0, 0, 0, 0, UTC), -24 * Hour},
+ {Date(2009, 11, 24, 0, 0, 0, 0, UTC), Date(2009, 11, 23, 0, 0, 0, 0, UTC), 24 * Hour},
+ {Date(-2009, 11, 24, 0, 0, 0, 0, UTC), Date(-2009, 11, 23, 0, 0, 0, 0, UTC), 24 * Hour},
+ {Time{}, Date(2109, 11, 23, 0, 0, 0, 0, UTC), Duration(minDuration)},
+ {Date(2109, 11, 23, 0, 0, 0, 0, UTC), Time{}, Duration(maxDuration)},
+ {Time{}, Date(-2109, 11, 23, 0, 0, 0, 0, UTC), Duration(maxDuration)},
+ {Date(-2109, 11, 23, 0, 0, 0, 0, UTC), Time{}, Duration(minDuration)},
+ {Date(2290, 1, 1, 0, 0, 0, 0, UTC), Date(2000, 1, 1, 0, 0, 0, 0, UTC), 290*365*24*Hour + 71*24*Hour},
+ {Date(2300, 1, 1, 0, 0, 0, 0, UTC), Date(2000, 1, 1, 0, 0, 0, 0, UTC), Duration(maxDuration)},
+ {Date(2000, 1, 1, 0, 0, 0, 0, UTC), Date(2290, 1, 1, 0, 0, 0, 0, UTC), -290*365*24*Hour - 71*24*Hour},
+ {Date(2000, 1, 1, 0, 0, 0, 0, UTC), Date(2300, 1, 1, 0, 0, 0, 0, UTC), Duration(minDuration)},
+}
+
+func TestSub(t *testing.T) {
+ for i, st := range subTests {
+ got := st.t.Sub(st.u)
+ if got != st.d {
+ t.Errorf("#%d: Sub(%v, %v): got %v; want %v", i, st.t, st.u, got, st.d)
+ }
+ }
+}
+
func BenchmarkNow(b *testing.B) {
for i := 0; i < b.N; i++ {
t = Now()
コアとなるコードの解説
src/pkg/time/time.go
minDuration
とmaxDuration
定数:Duration
型が取りうる最小値と最大値を明示的に定義しています。これにより、オーバーフロー/アンダーフロー時の戻り値が明確になります。Sub
メソッドの変更:d := Duration(t.sec-u.sec)*Second + Duration(t.nsec-u.nsec)
: まず、従来の計算方法でDuration
を計算します。このd
は、まだオーバーフロー/アンダーフローを起こしている可能性があります。switch
文によるオーバーフロー/アンダーフローのチェック:case u.Add(d).Equal(t): return d
: この条件が最も重要です。計算されたd
をu
に加算し、その結果が元のt
と等しい場合、d
はDuration
の範囲内に収まっており、正しい値であると判断されます。このチェックは、int64
のラップアラウンド特性を利用して、オーバーフロー/アンダーフローが発生したかどうかを効率的に検出します。もしオーバーフロー/アンダーフローが発生していれば、u.Add(d)
の結果はt
と一致しないはずです。case t.Before(u): return minDuration
:t
がu
より前の時刻であるにもかかわらず、u.Add(d).Equal(t)
がfalse
だった場合、これはd
がアンダーフローを起こして負の範囲外になったことを意味します。この場合、minDuration
を返します。default: return maxDuration
: 上記のいずれでもない場合、t
はu
より後の時刻であるにもかかわらず、u.Add(d).Equal(t)
がfalse
だったことを意味します。これはd
がオーバーフローを起こして正の範囲外になったことを意味します。この場合、maxDuration
を返します。
この新しいロジックにより、Sub
メソッドは常にDuration
の有効な範囲内の値を返し、予期せぬ動作を防ぎます。
src/pkg/time/time_test.go
minDuration
とmaxDuration
定数: テストファイルでもこれらの定数を再定義しています。これは、テストケース内でこれらの定数を使用するためです。subTests
変数の導入:Sub
メソッドのテストケースを構造体のスライスとして定義しています。これにより、様々なシナリオ(通常の差分、負の差分、オーバーフロー/アンダーフローの境界値)を網羅的にテストできます。- 特に注目すべきは、
Time{}
(ゼロ値の時刻)と非常に遠い未来/過去の時刻との差を計算し、minDuration
やmaxDuration
が返されることを期待するテストケースです。例えば、{Time{}, Date(2109, 11, 23, 0, 0, 0, 0, UTC), Duration(minDuration)}
は、ゼロ時刻から約96年後の時刻を引くと、Duration
の最小値が返されることを確認しています。
- 特に注目すべきは、
TestSub
関数の追加:subTests
に定義された各テストケースをループで実行し、Sub
メソッドの戻り値が期待されるDuration
と一致するかどうかを検証しています。これにより、Sub
メソッドの新しいオーバーフロー/アンダーフロー処理が正しく機能していることが保証されます。
関連リンク
- Go言語の
time
パッケージのドキュメント: https://pkg.go.dev/time - Go言語の
Duration
型に関する議論(関連する可能性のあるIssue):time.Duration
のオーバーフローに関する一般的な議論: https://github.com/golang/go/issues/32501 (このコミットのIssue #5011とは異なるが、Duration
の限界に関する理解を深めるのに役立つ)
参考にした情報源リンク
- コミット情報:
/home/orange/Project/comemo/commit_data/16617.txt
- Go言語の
time
パッケージのソースコード (コミット時点のバージョン): https://github.com/golang/go/tree/fc0b5ef0fdc3bc90d93759b068f9be486f4252da/src/pkg/time - Web検索結果: "golang issue 5011 time.Sub overflow" (直接的なIssueは見つからなかったが、
time.Sub
の挙動やDuration
の限界に関する一般的な情報が得られた)