Merge pull request #301 from ktopley-apple/dispatch-time-overflows

Fix overflow traps in DispatchTime/DispatchWallTime/DispatchTimeInterval
diff --git a/src/swift/Time.swift b/src/swift/Time.swift
index 5522758..b6cd33c 100644
--- a/src/swift/Time.swift
+++ b/src/swift/Time.swift
@@ -123,6 +123,40 @@
 	}
 }
 
+// Returns m1 * m2, clamped to the range [Int64.min, Int64.max].
+// Because of the way this function is used, we can always assume
+// that m2 > 0.
+private func clampedInt64Product(_ m1: Int64, _ m2: Int64) -> Int64 {
+	assert(m2 > 0, "multiplier must be positive")
+	let (result, overflow) = m1.multipliedReportingOverflow(by: m2)
+	if overflow {
+		return m1 > 0 ? Int64.max : Int64.min
+	}
+	return result
+}
+
+// Returns its argument clamped to the range [Int64.min, Int64.max].
+private func toInt64Clamped(_ value: Double) -> Int64 {
+	if value.isNaN { return Int64.max }
+	if value >= Double(Int64.max) { return Int64.max }
+	if value <= Double(Int64.min) { return Int64.min }
+	return Int64(value)
+}
+
+/// Represents a time interval that can be used as an offset from a `DispatchTime`
+/// or `DispatchWallTime`.
+///
+/// For example:
+/// 	let inOneSecond = DispatchTime.now() + DispatchTimeInterval.seconds(1)
+///
+/// If the requested time interval is larger then the internal representation
+/// permits, the result of adding it to a `DispatchTime` or `DispatchWallTime`
+/// is `DispatchTime.distantFuture` and `DispatchWallTime.distantFuture`
+/// respectively. Such time intervals compare as equal:
+///
+/// 	let t1 = DispatchTimeInterval.seconds(Int.max)
+///		let t2 = DispatchTimeInterval.milliseconds(Int.max)
+///		let result = t1 == t2   // true
 public enum DispatchTimeInterval {
 	case seconds(Int)
 	case milliseconds(Int)
@@ -133,9 +167,9 @@
 
 	internal var rawValue: Int64 {
 		switch self {
-		case .seconds(let s): return Int64(s) * Int64(NSEC_PER_SEC)
-		case .milliseconds(let ms): return Int64(ms) * Int64(NSEC_PER_MSEC)
-		case .microseconds(let us): return Int64(us) * Int64(NSEC_PER_USEC)
+		case .seconds(let s): return clampedInt64Product(Int64(s), Int64(NSEC_PER_SEC))
+		case .milliseconds(let ms): return clampedInt64Product(Int64(ms), Int64(NSEC_PER_MSEC))
+		case .microseconds(let us): return clampedInt64Product(Int64(us), Int64(NSEC_PER_USEC))
 		case .nanoseconds(let ns): return Int64(ns)
 		case .never: return Int64.max
 		}
@@ -162,16 +196,12 @@
 }
 
 public func +(time: DispatchTime, seconds: Double) -> DispatchTime {
-	let interval = seconds * Double(NSEC_PER_SEC)
-	let t = CDispatch.dispatch_time(time.rawValue,
-		interval.isInfinite || interval.isNaN ? Int64.max : Int64(interval))
+	let t = CDispatch.dispatch_time(time.rawValue, toInt64Clamped(seconds * Double(NSEC_PER_SEC)));
 	return DispatchTime(rawValue: t)
 }
 
 public func -(time: DispatchTime, seconds: Double) -> DispatchTime {
-	let interval = -seconds * Double(NSEC_PER_SEC)
-	let t = CDispatch.dispatch_time(time.rawValue,
-		interval.isInfinite || interval.isNaN ? Int64.min : Int64(interval))
+	let t = CDispatch.dispatch_time(time.rawValue, toInt64Clamped(-seconds * Double(NSEC_PER_SEC)));
 	return DispatchTime(rawValue: t)
 }
 
@@ -186,15 +216,11 @@
 }
 
 public func +(time: DispatchWallTime, seconds: Double) -> DispatchWallTime {
-	let interval = seconds * Double(NSEC_PER_SEC)
-	let t = CDispatch.dispatch_time(time.rawValue,
-		interval.isInfinite || interval.isNaN ? Int64.max : Int64(interval))
+	let t = CDispatch.dispatch_time(time.rawValue, toInt64Clamped(seconds * Double(NSEC_PER_SEC)));
 	return DispatchWallTime(rawValue: t)
 }
 
 public func -(time: DispatchWallTime, seconds: Double) -> DispatchWallTime {
-	let interval = -seconds * Double(NSEC_PER_SEC)
-	let t = CDispatch.dispatch_time(time.rawValue,
-		interval.isInfinite || interval.isNaN ? Int64.min : Int64(interval))
+	let t = CDispatch.dispatch_time(time.rawValue, toInt64Clamped(-seconds * Double(NSEC_PER_SEC)));
 	return DispatchWallTime(rawValue: t)
 }