]> Cypherpunks repositories - gostls13.git/commitdiff
time: allow long fractions in ParseDuration
authorRob Pike <r@golang.org>
Sun, 18 Sep 2016 04:35:42 +0000 (14:35 +1000)
committerRob Pike <r@golang.org>
Mon, 19 Sep 2016 19:46:09 +0000 (19:46 +0000)
The code scanned for an integer after a decimal point, which
meant things could overflow if the number was very precise
(0.1234123412341234123412342134s). This fix changes the
parser to stop adding precision once we run out of bits, rather
than trigger an erroneous overflow.

We could parse durations using floating-point arithmetic,
but since the type is int64 and float64 has only has 53 bits
of precision, that would be imprecise.

Fixes #15011.

Change-Id: If85e22b8f6cef12475e221169bb8f493bb9eb590
Reviewed-on: https://go-review.googlesource.com/29338
Reviewed-by: Costin Chirvasuta <costinc@gmail.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/time/format.go
src/time/time_test.go

index 18a73c45baf59285256a928e289e0eaf7a482da2..ea95f0be44ea6b27e28afebe06873195c45ca383 100644 (file)
@@ -1174,6 +1174,37 @@ func leadingInt(s string) (x int64, rem string, err error) {
        return x, s[i:], nil
 }
 
+// leadingFraction consumes the leading [0-9]* from s.
+// It is used only for fractions, so does not return an error on overflow,
+// it just stops accumulating precision.
+func leadingFraction(s string) (x int64, scale float64, rem string) {
+       i := 0
+       scale = 1
+       overflow := false
+       for ; i < len(s); i++ {
+               c := s[i]
+               if c < '0' || c > '9' {
+                       break
+               }
+               if overflow {
+                       continue
+               }
+               if x > (1<<63-1)/10 {
+                       // It's possible for overflow to give a positive number, so take care.
+                       overflow = true
+                       continue
+               }
+               y := x*10 + int64(c) - '0'
+               if y < 0 {
+                       overflow = true
+                       continue
+               }
+               x = y
+               scale *= 10
+       }
+       return x, scale, s[i:]
+}
+
 var unitMap = map[string]int64{
        "ns": int64(Nanosecond),
        "us": int64(Microsecond),
@@ -1236,13 +1267,7 @@ func ParseDuration(s string) (Duration, error) {
                if s != "" && s[0] == '.' {
                        s = s[1:]
                        pl := len(s)
-                       f, s, err = leadingInt(s)
-                       if err != nil {
-                               return 0, errors.New("time: invalid duration " + orig)
-                       }
-                       for n := pl - len(s); n > 0; n-- {
-                               scale *= 10
-                       }
+                       f, scale, s = leadingFraction(s)
                        post = pl != len(s)
                }
                if !pre && !post {
index fcc28ee99c7e345eabd479783991d8948f6e5e17..68236fd64db93f351f6f2b0f85e62296f1771c26 100644 (file)
@@ -840,6 +840,10 @@ var parseDurationTests = []struct {
        {"9223372036s854ms775us807ns", true, (1<<63 - 1) * Nanosecond},
        // large negative value
        {"-9223372036854775807ns", true, -1<<63 + 1*Nanosecond},
+       // huge string; issue 15011.
+       {"0.100000000000000000000h", true, 6 * Minute},
+       // This value tests the first overflow check in leadingFraction.
+       {"0.830103483285477580700h", true, 49*Minute + 48*Second + 372539827*Nanosecond},
 
        // errors
        {"", false, 0},