From fd5327bb7b7032e41f5af07ee54bec455a913ad0 Mon Sep 17 00:00:00 2001 From: Johan Fylling Date: Thu, 9 Dec 2021 14:18:55 +0100 Subject: [PATCH 1/4] topdown: Fixing nanos int overflow issue for time.* built-in functions The go Time.UnixNano() function result is undefined for dates that cannot fit into an int64 when converted to Unix time in nanoseconds. Updating built-in functions to return error if date is too low/high. Fixes: #4098 Signed-off-by: Johan Fylling --- test/cases/testdata/time/test-time-0948.yaml | 131 ++++++------------ test/cases/testdata/time/test-time-0949.yaml | 133 ++++++------------- test/cases/testdata/time/test-time-0968.yaml | 98 ++------------ test/cases/testdata/time/test-time-0969.yaml | 97 ++------------ topdown/time.go | 41 +++++- 5 files changed, 150 insertions(+), 350 deletions(-) diff --git a/test/cases/testdata/time/test-time-0948.yaml b/test/cases/testdata/time/test-time-0948.yaml index a3593ff0a1..4630d35c74 100644 --- a/test/cases/testdata/time/test-time-0948.yaml +++ b/test/cases/testdata/time/test-time-0948.yaml @@ -1,95 +1,50 @@ cases: -- data: - a: - - 1 - - 2 - - 3 - - 4 - b: - v1: hello - v2: goodbye - c: - - x: - - true - - false - - foo - "y": - - null - - 3.14159 - z: - p: true - q: false - d: - e: - - bar - - baz - f: - - xs: - - 1 - ys: - - 2 - - xs: - - 2 - ys: - - 3 - g: - a: - - 1 - - 0 - - 0 - - 0 - b: - - 0 - - 2 - - 0 - - 0 - c: - - 0 - - 0 - - 0 - - 4 - h: - - - 1 - - 2 - - 3 - - - 2 - - 3 - - 4 - l: - - a: bob - b: -1 - c: - - 1 - - 2 - - 3 - - 4 - - a: alice - b: 1 - c: - - 2 - - 3 - - 4 - - 5 - d: null - m: [] - numbers: - - "1" - - "2" - - "3" - - "4" - strings: - bar: 2 - baz: 3 - foo: 1 - three: 3 +- note: time/parse_nanos modules: - | package generated - - p = ns { - time.parse_ns("2006-01-02T15:04:05Z07:00", "2017-06-02T19:00:00-07:00", ns) + + p[case_id] = ns { + case := input.cases[case_id] + time.parse_ns(case.layout, case.value, ns) } - note: time/parse nanos query: data.generated.p = x + input: { cases: { + 1: { layout: "2006-01-02T15:04:05Z07:00", value: "2017-06-02T19:00:00-07:00" }, # RFC3339 + 2: { layout: "2006-01-02T15:04:05Z07:00", value: "1970-01-01T00:00:00-00:00" }, # Earliest valid time + 3: { layout: "2006-01-02T15:04:05Z07:00", value: "2262-04-11T23:47:16.854775807-00:00" }, # Latest valid time + 4: { layout: "01/02 03:04:05PM '06 -0700", value: "06/02 07:00:00PM '17 -0700" }, # Layout + 5: { layout: "02 Jan 06 15:04 -0700", value: "02 Jun 17 19:00 -0700" }, # RFC822Z + }} want_result: - - x: 1496455200000000000 + - x: { + 1: 1496455200000000000, + 2: 0, + 3: 9223372036854775807, + 4: 1496455200000000000, + 5: 1496455200000000000 + } +- note: time/parse_nanos_too_small + modules: + - | + package generated + + p = ns { + time.parse_ns("2006-01-02T15:04:05Z07:00", "1969-12-31T23:59:59.999999999-00:00", ns) + } + query: data.generated.p = x + strict_error: true + want_error_code: eval_builtin_error + want_error: 'time is before 1970-01-01 00:00:00 +0000 UTC, and cannot be converted to epoch nano seconds' +- note: time/parse_nanos_too_large + modules: + - | + package generated + + p = ns { + time.parse_ns("2006-01-02T15:04:05Z07:00", "2262-04-11T23:47:16.854775808-00:00", ns) + } + query: data.generated.p = x + strict_error: true + want_error_code: eval_builtin_error + want_error: 'time is after 2262-04-11 23:47:16.854775807 +0000 UTC, and cannot be converted to epoch nano seconds' diff --git a/test/cases/testdata/time/test-time-0949.yaml b/test/cases/testdata/time/test-time-0949.yaml index cf73b1f501..665af54073 100644 --- a/test/cases/testdata/time/test-time-0949.yaml +++ b/test/cases/testdata/time/test-time-0949.yaml @@ -1,95 +1,46 @@ cases: -- data: - a: - - 1 - - 2 - - 3 - - 4 - b: - v1: hello - v2: goodbye - c: - - x: - - true - - false - - foo - "y": - - null - - 3.14159 - z: - p: true - q: false - d: - e: - - bar - - baz - f: - - xs: - - 1 - ys: - - 2 - - xs: - - 2 - ys: - - 3 - g: - a: - - 1 - - 0 - - 0 - - 0 - b: - - 0 - - 2 - - 0 - - 0 - c: - - 0 - - 0 - - 0 - - 4 - h: - - - 1 - - 2 - - 3 - - - 2 - - 3 - - 4 - l: - - a: bob - b: -1 - c: - - 1 - - 2 - - 3 - - 4 - - a: alice - b: 1 - c: - - 2 - - 3 - - 4 - - 5 - d: null - m: [] - numbers: - - "1" - - "2" - - "3" - - "4" - strings: - bar: 2 - baz: 3 - foo: 1 - three: 3 +- note: time/parse_rfc3339_nanos modules: - - | - package generated - - p = ns { - time.parse_rfc3339_ns("2017-06-02T19:00:00-07:00", ns) - } - note: time/parse rfc3339 nanos + - | + package generated + + p[time] = ns { + time = input.cases[_] + time.parse_rfc3339_ns(time, ns) + } query: data.generated.p = x + input: { cases: [ + "2017-06-02T19:00:00-07:00", + "1970-01-01T00:00:00-00:00", + "2262-04-11T23:47:16.854775807-00:00" + ]} want_result: - - x: 1496455200000000000 + - x: { + "1970-01-01T00:00:00-00:00": 0, + "2017-06-02T19:00:00-07:00": 1496455200000000000, + "2262-04-11T23:47:16.854775807-00:00": 9223372036854775807 + } +- note: time/parse_rfc3339_nanos_too_small + modules: + - | + package generated + + p = ns { + time.parse_rfc3339_ns("1969-12-31T23:59:59.999999999-00:00", ns) + } + query: data.generated.p = x + strict_error: true + want_error_code: eval_builtin_error + want_error: 'time is before 1970-01-01 00:00:00 +0000 UTC, and cannot be converted to epoch nano seconds' +- note: time/parse_rfc3339_nanos_too_large + modules: + - | + package generated + + p = ns { + time.parse_rfc3339_ns("2262-04-11T23:47:16.854775808-00:00", ns) + } + query: data.generated.p = x + strict_error: true + want_error_code: eval_builtin_error + want_error: 'time is after 2262-04-11 23:47:16.854775807 +0000 UTC, and cannot be converted to epoch nano seconds' diff --git a/test/cases/testdata/time/test-time-0968.yaml b/test/cases/testdata/time/test-time-0968.yaml index 08bc623d71..39e5b1240f 100644 --- a/test/cases/testdata/time/test-time-0968.yaml +++ b/test/cases/testdata/time/test-time-0968.yaml @@ -1,87 +1,5 @@ cases: -- data: - a: - - 1 - - 2 - - 3 - - 4 - b: - v1: hello - v2: goodbye - c: - - x: - - true - - false - - foo - "y": - - null - - 3.14159 - z: - p: true - q: false - d: - e: - - bar - - baz - f: - - xs: - - 1 - ys: - - 2 - - xs: - - 2 - ys: - - 3 - g: - a: - - 1 - - 0 - - 0 - - 0 - b: - - 0 - - 2 - - 0 - - 0 - c: - - 0 - - 0 - - 0 - - 4 - h: - - - 1 - - 2 - - 3 - - - 2 - - 3 - - 4 - l: - - a: bob - b: -1 - c: - - 1 - - 2 - - 3 - - 4 - - a: alice - b: 1 - c: - - 2 - - 3 - - 4 - - 5 - d: null - m: [] - numbers: - - "1" - - "2" - - "3" - - "4" - strings: - bar: 2 - baz: 3 - foo: 1 - three: 3 +- note: time/add_date year month day modules: - | package generated @@ -90,7 +8,19 @@ cases: time.add_date(1585852421593912000, 3, 9, 12, __local1__) __local0__ = __local1__ } - note: time/add_date year month day + query: data.generated.p = x want_result: - x: 1705257221593912000 +- note: time/add_date too large result + modules: + - | + package generated + + p = ns { + time.add_date(0, 2262, 1, 1, ns) + } + query: data.generated.p = x + strict_error: true + want_error_code: eval_builtin_error + want_error: 'time is after 2262-04-11 23:47:16.854775807 +0000 UTC, and cannot be converted to epoch nano seconds' \ No newline at end of file diff --git a/test/cases/testdata/time/test-time-0969.yaml b/test/cases/testdata/time/test-time-0969.yaml index b554093f65..daa1b60364 100644 --- a/test/cases/testdata/time/test-time-0969.yaml +++ b/test/cases/testdata/time/test-time-0969.yaml @@ -1,87 +1,5 @@ cases: -- data: - a: - - 1 - - 2 - - 3 - - 4 - b: - v1: hello - v2: goodbye - c: - - x: - - true - - false - - foo - "y": - - null - - 3.14159 - z: - p: true - q: false - d: - e: - - bar - - baz - f: - - xs: - - 1 - ys: - - 2 - - xs: - - 2 - ys: - - 3 - g: - a: - - 1 - - 0 - - 0 - - 0 - b: - - 0 - - 2 - - 0 - - 0 - c: - - 0 - - 0 - - 0 - - 4 - h: - - - 1 - - 2 - - 3 - - - 2 - - 3 - - 4 - l: - - a: bob - b: -1 - c: - - 1 - - 2 - - 3 - - 4 - - a: alice - b: 1 - c: - - 2 - - 3 - - 4 - - 5 - d: null - m: [] - numbers: - - "1" - - "2" - - "3" - - "4" - strings: - bar: 2 - baz: 3 - foo: 1 - three: 3 +- note: time/add_date negative values modules: - | package generated @@ -90,7 +8,18 @@ cases: time.add_date(1585852421593912000, -1, -1, -1, __local1__) __local0__ = __local1__ } - note: time/add_date negative values query: data.generated.p = x want_result: - x: 1551465221593912000 +- note: time/add_date too small result + modules: + - | + package generated + + p = ns { + time.add_date(0, -1, -1, -1, ns) + } + query: data.generated.p = x + strict_error: true + want_error_code: eval_builtin_error + want_error: 'time is before 1970-01-01 00:00:00 +0000 UTC, and cannot be converted to epoch nano seconds' diff --git a/topdown/time.go b/topdown/time.go index b1e94eb651..b96f64d4b7 100644 --- a/topdown/time.go +++ b/topdown/time.go @@ -19,6 +19,25 @@ import ( var tzCache map[string]*time.Location var tzCacheMutex *sync.Mutex +var minDateAllowedForNsConversion = time.Date(1970, 01, 01, 0, 0, 0, 0, time.UTC) +var maxDateAllowedForNsConversion = time.Date(2262, 4, 11, 23, 47, 16, 854775807, time.UTC) + +func toSafeUnixNano(t time.Time) (int64, error) { + if t.Before(minDateAllowedForNsConversion) { + // Earlier dates than this exhibits undefined Time.UnixNano() behaviour. + return 0, fmt.Errorf("time is before %v, and cannot be converted to epoch nano seconds", + minDateAllowedForNsConversion) + } + + if t.After(maxDateAllowedForNsConversion) { + // Later dates than this exhibits undefined Time.UnixNano() behaviour. + return 0, fmt.Errorf("time is after %v, and cannot be converted to epoch nano seconds", + maxDateAllowedForNsConversion) + } + + return t.UnixNano(), nil +} + func builtinTimeNowNanos(bctx BuiltinContext, _ []*ast.Term, iter func(*ast.Term) error) error { return iter(bctx.Time) } @@ -40,7 +59,12 @@ func builtinTimeParseNanos(a, b ast.Value) (ast.Value, error) { return nil, err } - return ast.Number(int64ToJSONNumber(result.UnixNano())), nil + ns, err := toSafeUnixNano(result) + if err != nil { + return nil, err + } + + return ast.Number(int64ToJSONNumber(ns)), nil } func builtinTimeParseRFC3339Nanos(a ast.Value) (ast.Value, error) { @@ -55,7 +79,12 @@ func builtinTimeParseRFC3339Nanos(a ast.Value) (ast.Value, error) { return nil, err } - return ast.Number(int64ToJSONNumber(result.UnixNano())), nil + ns, err := toSafeUnixNano(result) + if err != nil { + return nil, err + } + + return ast.Number(int64ToJSONNumber(ns)), nil } func builtinParseDurationNanos(a ast.Value) (ast.Value, error) { @@ -121,7 +150,13 @@ func builtinAddDate(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Te } result := t.AddDate(years, months, days) - return iter(ast.NewTerm(ast.Number(int64ToJSONNumber(result.UnixNano())))) + + ns, err := toSafeUnixNano(result) + if err != nil { + return err + } + + return iter(ast.NewTerm(ast.Number(int64ToJSONNumber(ns)))) } func builtinDiff(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { From 93ee2acf1fbf49812e28839357e4bf3b82b9abb9 Mon Sep 17 00:00:00 2001 From: Johan Fylling Date: Thu, 9 Dec 2021 18:18:33 +0100 Subject: [PATCH 2/4] Fixup into commit fd5327bb7b7032e41f5af07ee54bec455a913ad0 (squash before merge) Adjusting min allowed time to be '1677-09-21 00:12:43.145224192'. Signed-off-by: Johan Fylling --- test/cases/testdata/time/test-time-0948.yaml | 10 +++++----- test/cases/testdata/time/test-time-0949.yaml | 20 +++++++++++--------- test/cases/testdata/time/test-time-0968.yaml | 2 +- test/cases/testdata/time/test-time-0969.yaml | 4 ++-- topdown/time.go | 9 ++++++--- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/test/cases/testdata/time/test-time-0948.yaml b/test/cases/testdata/time/test-time-0948.yaml index 4630d35c74..15c5327d3a 100644 --- a/test/cases/testdata/time/test-time-0948.yaml +++ b/test/cases/testdata/time/test-time-0948.yaml @@ -11,7 +11,7 @@ cases: query: data.generated.p = x input: { cases: { 1: { layout: "2006-01-02T15:04:05Z07:00", value: "2017-06-02T19:00:00-07:00" }, # RFC3339 - 2: { layout: "2006-01-02T15:04:05Z07:00", value: "1970-01-01T00:00:00-00:00" }, # Earliest valid time + 2: { layout: "2006-01-02T15:04:05Z07:00", value: "1677-09-21T00:12:43.145224192-00:00" }, # Earliest valid time 3: { layout: "2006-01-02T15:04:05Z07:00", value: "2262-04-11T23:47:16.854775807-00:00" }, # Latest valid time 4: { layout: "01/02 03:04:05PM '06 -0700", value: "06/02 07:00:00PM '17 -0700" }, # Layout 5: { layout: "02 Jan 06 15:04 -0700", value: "02 Jun 17 19:00 -0700" }, # RFC822Z @@ -19,7 +19,7 @@ cases: want_result: - x: { 1: 1496455200000000000, - 2: 0, + 2: -9223372036854775808, 3: 9223372036854775807, 4: 1496455200000000000, 5: 1496455200000000000 @@ -30,12 +30,12 @@ cases: package generated p = ns { - time.parse_ns("2006-01-02T15:04:05Z07:00", "1969-12-31T23:59:59.999999999-00:00", ns) + time.parse_ns("2006-01-02T15:04:05Z07:00", "1677-09-21T00:12:43.145224191-00:00", ns) } query: data.generated.p = x strict_error: true want_error_code: eval_builtin_error - want_error: 'time is before 1970-01-01 00:00:00 +0000 UTC, and cannot be converted to epoch nano seconds' + want_error: 'time is before 1677-09-21 00:12:43.145224192 +0000 UTC, and cannot be converted to epoch nanoseconds' - note: time/parse_nanos_too_large modules: - | @@ -47,4 +47,4 @@ cases: query: data.generated.p = x strict_error: true want_error_code: eval_builtin_error - want_error: 'time is after 2262-04-11 23:47:16.854775807 +0000 UTC, and cannot be converted to epoch nano seconds' + want_error: 'time is after 2262-04-11 23:47:16.854775807 +0000 UTC, and cannot be converted to epoch nanoseconds' diff --git a/test/cases/testdata/time/test-time-0949.yaml b/test/cases/testdata/time/test-time-0949.yaml index 665af54073..2418819828 100644 --- a/test/cases/testdata/time/test-time-0949.yaml +++ b/test/cases/testdata/time/test-time-0949.yaml @@ -4,21 +4,23 @@ cases: - | package generated - p[time] = ns { - time = input.cases[_] - time.parse_rfc3339_ns(time, ns) + p[t] = ns { + t = input.cases[_] + time.parse_rfc3339_ns(t, ns) } query: data.generated.p = x input: { cases: [ - "2017-06-02T19:00:00-07:00", + "1677-09-21T00:12:43.145224192-00:00", # Earliest valid time "1970-01-01T00:00:00-00:00", - "2262-04-11T23:47:16.854775807-00:00" + "2017-06-02T19:00:00-07:00", + "2262-04-11T23:47:16.854775807-00:00" # Latest valid time ]} want_result: - x: { + "1677-09-21T00:12:43.145224192-00:00": -9223372036854775808, "1970-01-01T00:00:00-00:00": 0, "2017-06-02T19:00:00-07:00": 1496455200000000000, - "2262-04-11T23:47:16.854775807-00:00": 9223372036854775807 + "2262-04-11T23:47:16.854775807-00:00": 9223372036854775807, } - note: time/parse_rfc3339_nanos_too_small modules: @@ -26,12 +28,12 @@ cases: package generated p = ns { - time.parse_rfc3339_ns("1969-12-31T23:59:59.999999999-00:00", ns) + time.parse_rfc3339_ns("1677-09-21T00:12:43.145224191-00:00", ns) } query: data.generated.p = x strict_error: true want_error_code: eval_builtin_error - want_error: 'time is before 1970-01-01 00:00:00 +0000 UTC, and cannot be converted to epoch nano seconds' + want_error: 'time is before 1677-09-21 00:12:43.145224192 +0000 UTC, and cannot be converted to epoch nanoseconds' - note: time/parse_rfc3339_nanos_too_large modules: - | @@ -43,4 +45,4 @@ cases: query: data.generated.p = x strict_error: true want_error_code: eval_builtin_error - want_error: 'time is after 2262-04-11 23:47:16.854775807 +0000 UTC, and cannot be converted to epoch nano seconds' + want_error: 'time is after 2262-04-11 23:47:16.854775807 +0000 UTC, and cannot be converted to epoch nanoseconds' diff --git a/test/cases/testdata/time/test-time-0968.yaml b/test/cases/testdata/time/test-time-0968.yaml index 39e5b1240f..61962b9d4a 100644 --- a/test/cases/testdata/time/test-time-0968.yaml +++ b/test/cases/testdata/time/test-time-0968.yaml @@ -23,4 +23,4 @@ cases: query: data.generated.p = x strict_error: true want_error_code: eval_builtin_error - want_error: 'time is after 2262-04-11 23:47:16.854775807 +0000 UTC, and cannot be converted to epoch nano seconds' \ No newline at end of file + want_error: 'time is after 2262-04-11 23:47:16.854775807 +0000 UTC, and cannot be converted to epoch nanoseconds' \ No newline at end of file diff --git a/test/cases/testdata/time/test-time-0969.yaml b/test/cases/testdata/time/test-time-0969.yaml index daa1b60364..a9ee074619 100644 --- a/test/cases/testdata/time/test-time-0969.yaml +++ b/test/cases/testdata/time/test-time-0969.yaml @@ -17,9 +17,9 @@ cases: package generated p = ns { - time.add_date(0, -1, -1, -1, ns) + time.add_date(-9223372036854775808, 0, 0, -1, ns) } query: data.generated.p = x strict_error: true want_error_code: eval_builtin_error - want_error: 'time is before 1970-01-01 00:00:00 +0000 UTC, and cannot be converted to epoch nano seconds' + want_error: 'time is before 1677-09-21 00:12:43.145224192 +0000 UTC, and cannot be converted to epoch nanoseconds' diff --git a/topdown/time.go b/topdown/time.go index b96f64d4b7..a332f4a997 100644 --- a/topdown/time.go +++ b/topdown/time.go @@ -19,19 +19,22 @@ import ( var tzCache map[string]*time.Location var tzCacheMutex *sync.Mutex -var minDateAllowedForNsConversion = time.Date(1970, 01, 01, 0, 0, 0, 0, time.UTC) +// -9223372036854775808ns, int64 wraparound point +var minDateAllowedForNsConversion = time.Date(1677, 9, 21, 0, 12, 43, 145224192, time.UTC) + +// 9223372036854775807ns, int64 wraparound point var maxDateAllowedForNsConversion = time.Date(2262, 4, 11, 23, 47, 16, 854775807, time.UTC) func toSafeUnixNano(t time.Time) (int64, error) { if t.Before(minDateAllowedForNsConversion) { // Earlier dates than this exhibits undefined Time.UnixNano() behaviour. - return 0, fmt.Errorf("time is before %v, and cannot be converted to epoch nano seconds", + return 0, fmt.Errorf("time is before %v, and cannot be converted to epoch nanoseconds", minDateAllowedForNsConversion) } if t.After(maxDateAllowedForNsConversion) { // Later dates than this exhibits undefined Time.UnixNano() behaviour. - return 0, fmt.Errorf("time is after %v, and cannot be converted to epoch nano seconds", + return 0, fmt.Errorf("time is after %v, and cannot be converted to epoch nanoseconds", maxDateAllowedForNsConversion) } From 90bcaf9a0776a129035ae6b16f86a48d241d61ff Mon Sep 17 00:00:00 2001 From: Johan Fylling Date: Fri, 10 Dec 2021 11:29:00 +0100 Subject: [PATCH 3/4] Fixed changes requested by @srenatus (squash before merge) Making nanosecond overflow error message more concise. Updating relevant time.* built-ins to be registered with RegisterBuiltinFunc(). Signed-off-by: Johan Fylling --- test/cases/testdata/time/test-time-0948.yaml | 4 +- test/cases/testdata/time/test-time-0949.yaml | 4 +- test/cases/testdata/time/test-time-0968.yaml | 2 +- test/cases/testdata/time/test-time-0969.yaml | 2 +- topdown/time.go | 73 +++++++------------- 5 files changed, 32 insertions(+), 53 deletions(-) diff --git a/test/cases/testdata/time/test-time-0948.yaml b/test/cases/testdata/time/test-time-0948.yaml index 15c5327d3a..5eadffb222 100644 --- a/test/cases/testdata/time/test-time-0948.yaml +++ b/test/cases/testdata/time/test-time-0948.yaml @@ -35,7 +35,7 @@ cases: query: data.generated.p = x strict_error: true want_error_code: eval_builtin_error - want_error: 'time is before 1677-09-21 00:12:43.145224192 +0000 UTC, and cannot be converted to epoch nanoseconds' + want_error: 'time outside of valid range' - note: time/parse_nanos_too_large modules: - | @@ -47,4 +47,4 @@ cases: query: data.generated.p = x strict_error: true want_error_code: eval_builtin_error - want_error: 'time is after 2262-04-11 23:47:16.854775807 +0000 UTC, and cannot be converted to epoch nanoseconds' + want_error: 'time outside of valid range' diff --git a/test/cases/testdata/time/test-time-0949.yaml b/test/cases/testdata/time/test-time-0949.yaml index 2418819828..73ad63e322 100644 --- a/test/cases/testdata/time/test-time-0949.yaml +++ b/test/cases/testdata/time/test-time-0949.yaml @@ -33,7 +33,7 @@ cases: query: data.generated.p = x strict_error: true want_error_code: eval_builtin_error - want_error: 'time is before 1677-09-21 00:12:43.145224192 +0000 UTC, and cannot be converted to epoch nanoseconds' + want_error: 'time outside of valid range' - note: time/parse_rfc3339_nanos_too_large modules: - | @@ -45,4 +45,4 @@ cases: query: data.generated.p = x strict_error: true want_error_code: eval_builtin_error - want_error: 'time is after 2262-04-11 23:47:16.854775807 +0000 UTC, and cannot be converted to epoch nanoseconds' + want_error: 'time outside of valid range' diff --git a/test/cases/testdata/time/test-time-0968.yaml b/test/cases/testdata/time/test-time-0968.yaml index 61962b9d4a..f7196da07d 100644 --- a/test/cases/testdata/time/test-time-0968.yaml +++ b/test/cases/testdata/time/test-time-0968.yaml @@ -23,4 +23,4 @@ cases: query: data.generated.p = x strict_error: true want_error_code: eval_builtin_error - want_error: 'time is after 2262-04-11 23:47:16.854775807 +0000 UTC, and cannot be converted to epoch nanoseconds' \ No newline at end of file + want_error: 'time outside of valid range' \ No newline at end of file diff --git a/test/cases/testdata/time/test-time-0969.yaml b/test/cases/testdata/time/test-time-0969.yaml index a9ee074619..09929f4ff9 100644 --- a/test/cases/testdata/time/test-time-0969.yaml +++ b/test/cases/testdata/time/test-time-0969.yaml @@ -22,4 +22,4 @@ cases: query: data.generated.p = x strict_error: true want_error_code: eval_builtin_error - want_error: 'time is before 1677-09-21 00:12:43.145224192 +0000 UTC, and cannot be converted to epoch nanoseconds' + want_error: 'time outside of valid range' diff --git a/topdown/time.go b/topdown/time.go index a332f4a997..481326dade 100644 --- a/topdown/time.go +++ b/topdown/time.go @@ -7,6 +7,7 @@ package topdown import ( "encoding/json" "fmt" + "math" "math/big" "strconv" "sync" @@ -19,75 +20,58 @@ import ( var tzCache map[string]*time.Location var tzCacheMutex *sync.Mutex -// -9223372036854775808ns, int64 wraparound point -var minDateAllowedForNsConversion = time.Date(1677, 9, 21, 0, 12, 43, 145224192, time.UTC) +// 1677-09-21T00:12:43.145224192-00:00 +var minDateAllowedForNsConversion = time.Unix(0, math.MinInt64) -// 9223372036854775807ns, int64 wraparound point -var maxDateAllowedForNsConversion = time.Date(2262, 4, 11, 23, 47, 16, 854775807, time.UTC) +// 2262-04-11T23:47:16.854775807-00:00 +var maxDateAllowedForNsConversion = time.Unix(0, math.MaxInt64) -func toSafeUnixNano(t time.Time) (int64, error) { - if t.Before(minDateAllowedForNsConversion) { - // Earlier dates than this exhibits undefined Time.UnixNano() behaviour. - return 0, fmt.Errorf("time is before %v, and cannot be converted to epoch nanoseconds", - minDateAllowedForNsConversion) +func toSafeUnixNano(t time.Time, iter func(*ast.Term) error) error { + if t.Before(minDateAllowedForNsConversion) || t.After(maxDateAllowedForNsConversion) { + return fmt.Errorf("time outside of valid range") } - if t.After(maxDateAllowedForNsConversion) { - // Later dates than this exhibits undefined Time.UnixNano() behaviour. - return 0, fmt.Errorf("time is after %v, and cannot be converted to epoch nanoseconds", - maxDateAllowedForNsConversion) - } - - return t.UnixNano(), nil + return iter(ast.NewTerm(ast.Number(int64ToJSONNumber(t.UnixNano())))) } func builtinTimeNowNanos(bctx BuiltinContext, _ []*ast.Term, iter func(*ast.Term) error) error { return iter(bctx.Time) } -func builtinTimeParseNanos(a, b ast.Value) (ast.Value, error) { - +func builtinTimeParseNanos(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { + a := operands[0].Value format, err := builtins.StringOperand(a, 1) if err != nil { - return nil, err + return err } + b := operands[1].Value value, err := builtins.StringOperand(b, 2) if err != nil { - return nil, err + return err } result, err := time.Parse(string(format), string(value)) if err != nil { - return nil, err - } - - ns, err := toSafeUnixNano(result) - if err != nil { - return nil, err + return err } - return ast.Number(int64ToJSONNumber(ns)), nil + return toSafeUnixNano(result, iter) } -func builtinTimeParseRFC3339Nanos(a ast.Value) (ast.Value, error) { - +func builtinTimeParseRFC3339Nanos(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { + a := operands[0].Value value, err := builtins.StringOperand(a, 1) if err != nil { - return nil, err + return err } result, err := time.Parse(time.RFC3339, string(value)) if err != nil { - return nil, err - } - - ns, err := toSafeUnixNano(result) - if err != nil { - return nil, err + return err } - return ast.Number(int64ToJSONNumber(ns)), nil + return toSafeUnixNano(result, iter) } func builtinParseDurationNanos(a ast.Value) (ast.Value, error) { @@ -131,7 +115,7 @@ func builtinWeekday(a ast.Value) (ast.Value, error) { return ast.String(weekday), nil } -func builtinAddDate(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { +func builtinAddDate(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { t, err := tzTime(operands[0].Value) if err != nil { return err @@ -154,15 +138,10 @@ func builtinAddDate(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Te result := t.AddDate(years, months, days) - ns, err := toSafeUnixNano(result) - if err != nil { - return err - } - - return iter(ast.NewTerm(ast.Number(int64ToJSONNumber(ns)))) + return toSafeUnixNano(result, iter) } -func builtinDiff(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { +func builtinDiff(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { t1, err := tzTime(operands[0].Value) if err != nil { return err @@ -305,8 +284,8 @@ func int64ToJSONNumber(i int64) json.Number { func init() { RegisterBuiltinFunc(ast.NowNanos.Name, builtinTimeNowNanos) - RegisterFunctionalBuiltin1(ast.ParseRFC3339Nanos.Name, builtinTimeParseRFC3339Nanos) - RegisterFunctionalBuiltin2(ast.ParseNanos.Name, builtinTimeParseNanos) + RegisterBuiltinFunc(ast.ParseRFC3339Nanos.Name, builtinTimeParseRFC3339Nanos) + RegisterBuiltinFunc(ast.ParseNanos.Name, builtinTimeParseNanos) RegisterFunctionalBuiltin1(ast.ParseDurationNanos.Name, builtinParseDurationNanos) RegisterFunctionalBuiltin1(ast.Date.Name, builtinDate) RegisterFunctionalBuiltin1(ast.Clock.Name, builtinClock) From 03b1e5c122bca8b2156254a2092f5ded86f651c2 Mon Sep 17 00:00:00 2001 From: Johan Fylling Date: Fri, 10 Dec 2021 12:15:46 +0100 Subject: [PATCH 4/4] Fixed changes requested by @srenatus (squash before merge) Updating docs for built-in functions. Signed-off-by: Johan Fylling --- docs/content/policy-reference.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/policy-reference.md b/docs/content/policy-reference.md index 106c8e53ae..1d1e2fc9a9 100644 --- a/docs/content/policy-reference.md +++ b/docs/content/policy-reference.md @@ -800,13 +800,13 @@ result_valid_hs256 := io.jwt.verify_hs256(result_hs256, "foo") | Built-in | Description | Wasm Support | | ------- |-------------|---------------| | ``output := time.now_ns()`` | ``output`` is a ``number`` representing the current time since epoch in nanoseconds. | ``SDK-dependent`` | -| ``output := time.parse_ns(layout, value)`` | ``output`` is a ``number`` representing the time ``value`` in nanoseconds since epoch. See the [Go `time` package documentation](https://golang.org/pkg/time/#Parse) for more details on ``layout``. | ``SDK-dependent`` | -| ``output := time.parse_rfc3339_ns(value)`` | ``output`` is a ``number`` representing the time ``value`` in nanoseconds since epoch. | ``SDK-dependent`` | +| ``output := time.parse_ns(layout, value)`` | ``output`` is a ``number`` representing the time ``value`` in nanoseconds since epoch; or ``undefined`` if outside the valid time range that can fit within an ``int64``. See the [Go `time` package documentation](https://golang.org/pkg/time/#Parse) for more details on ``layout``. | ``SDK-dependent`` | +| ``output := time.parse_rfc3339_ns(value)`` | ``output`` is a ``number`` representing the time ``value`` in nanoseconds since epoch; or ``undefined`` if outside the valid time range that can fit within an ``int64``. | ``SDK-dependent`` | | ``output := time.parse_duration_ns(duration)`` | ``output`` is a ``number`` representing the duration ``duration`` in nanoseconds. See the [Go `time` package documentation](https://golang.org/pkg/time/#ParseDuration) for more details on ``duration``. | ``SDK-dependent`` | | ``output := time.date(ns)``
``output := time.date([ns, tz])``
| ``output`` is of the form ``[year, month, day]``, which includes the ``year``, ``month`` (0-12), and ``day`` (0-31) as ``number``s representing the date from the nanoseconds since epoch (``ns``) in the timezone (``tz``), if supplied, or as UTC.| ``SDK-dependent`` | | ``output := time.clock(ns)``
``output := time.clock([ns, tz])``
| ``output`` is of the form ``[hour, minute, second]``, which outputs the ``hour``, ``minute`` (0-59), and ``second`` (0-59) as ``number``s representing the time of day for the nanoseconds since epoch (``ns``) in the timezone (``tz``), if supplied, or as UTC. | ``SDK-dependent`` | | ``day := time.weekday(ns)``
``day := time.weekday([ns, tz])``
| outputs the ``day`` as ``string`` representing the day of the week for the nanoseconds since epoch (``ns``) in the timezone (``tz``), if supplied, or as UTC. | ``SDK-dependent`` | -| ``output := time.add_date(ns, years, months, days)`` | ``output`` is a ``number`` representing the time since epoch in nanoseconds after adding the ``years``, ``months`` and ``days`` to ``ns``. See the [Go `time` package documentation](https://golang.org/pkg/time/#Time.AddDate) for more details on ``add_date``. | ``SDK-dependent`` | +| ``output := time.add_date(ns, years, months, days)`` | ``output`` is a ``number`` representing the time since epoch in nanoseconds after adding the ``years``, ``months`` and ``days`` to ``ns``; or ``undefined`` if outside the valid time range that can fit within an ``int64``. See the [Go `time` package documentation](https://golang.org/pkg/time/#Time.AddDate) for more details on ``add_date``. | ``SDK-dependent`` | | ``output := time.diff(ns1, ns2)``
``output := time.diff([ns1, tz1], [ns2, tz2])``
| ``output`` is of the form ``[year(s), month(s), day(s), hour(s), minute(s), second(s)]``, which outputs ``year(s)``, ``month(s)`` (0-11), ``day(s)`` (0-30), ``hour(s)``(0-23), ``minute(s)``(0-59) and ``second(s)``(0-59) as ``number``s representing the difference between the the two timestamps in nanoseconds since epoch (``ns1`` and ``ns2``), in the timezones (``tz1`` and ``tz2``, respectively), if supplied, or as UTC. | ``SDK-dependent`` | > Multiple calls to the `time.now_ns` built-in function within a single policy