Add guards for Int64 overflow in `Timer` methods (#29)

Motivation:

`Timer.record*` methods are generic to cover all cases of `BinaryInteger` values and converts the passed value into `Int64` before doing time unit conversions and overflow checks.

Modifications:

All methods that accept `BinaryInteger` types and converts to `Int64` now guards against values higher than `Int64.max`

Result:

Users can reliably use the `Timer.record*` APIs with `BinaryInteger` types without concern for unknowing fatal errors.
This commit is contained in:
Nathan Harris 2019-05-24 22:41:41 -07:00 committed by tomer doron
parent 4a9e0de637
commit e4883dab79
3 changed files with 28 additions and 0 deletions

View File

@ -273,6 +273,8 @@ public class Timer {
/// - value: Duration to record.
@inlinable
public func recordMicroseconds<DataType: BinaryInteger>(_ duration: DataType) {
guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
let result = Int64(duration).multipliedReportingOverflow(by: 1000)
if result.overflow {
self.recordNanoseconds(Int64.max)
@ -296,6 +298,8 @@ public class Timer {
/// - value: Duration to record.
@inlinable
public func recordMilliseconds<DataType: BinaryInteger>(_ duration: DataType) {
guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
let result = Int64(duration).multipliedReportingOverflow(by: 1_000_000)
if result.overflow {
self.recordNanoseconds(Int64.max)
@ -319,6 +323,8 @@ public class Timer {
/// - value: Duration to record.
@inlinable
public func recordSeconds<DataType: BinaryInteger>(_ duration: DataType) {
guard duration <= Int64.max else { return self.recordNanoseconds(Int64.max) }
let result = Int64(duration).multipliedReportingOverflow(by: 1_000_000_000)
if result.overflow {
self.recordNanoseconds(Int64.max)

View File

@ -35,6 +35,7 @@ extension MetricsTests {
("testTimerBlock", testTimerBlock),
("testTimerVariants", testTimerVariants),
("testTimerOverflow", testTimerOverflow),
("testTimerHandlesUnsignedOverflow", testTimerHandlesUnsignedOverflow),
("testGauge", testGauge),
("testGaugeBlock", testGaugeBlock),
("testMUX", testMUX),

View File

@ -217,6 +217,27 @@ class MetricsTests: XCTestCase {
XCTAssertEqual(testTimer.values[3].1, Int64.max, "expected value to match")
}
func testTimerHandlesUnsignedOverflow() throws {
// bootstrap with our test metrics
let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics)
// run the test
let timer = Timer(label: "test-timer")
let testTimer = timer.handler as! TestTimer
// micro
timer.recordMicroseconds(UInt64.max)
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
XCTAssertEqual(testTimer.values[0].1, Int64.max, "expected value to match")
// milli
timer.recordMilliseconds(UInt64.max)
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
XCTAssertEqual(testTimer.values[1].1, Int64.max, "expected value to match")
// seconds
timer.recordSeconds(UInt64.max)
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
XCTAssertEqual(testTimer.values[2].1, Int64.max, "expected value to match")
}
func testGauge() throws {
// bootstrap with our test metrics
let metrics = TestMetrics()