Skip to content

Commit 947b51c

Browse files
Merge pull request swiftlang#301 from ktopley-apple/dispatch-time-overflows
Fix overflow traps in DispatchTime/DispatchWallTime/DispatchTimeInterval
2 parents 7f330ed + 2db063f commit 947b51c

File tree

1 file changed

+41
-15
lines changed

1 file changed

+41
-15
lines changed

src/swift/Time.swift

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,40 @@ extension DispatchWallTime {
123123
}
124124
}
125125

126+
// Returns m1 * m2, clamped to the range [Int64.min, Int64.max].
127+
// Because of the way this function is used, we can always assume
128+
// that m2 > 0.
129+
private func clampedInt64Product(_ m1: Int64, _ m2: Int64) -> Int64 {
130+
assert(m2 > 0, "multiplier must be positive")
131+
let (result, overflow) = m1.multipliedReportingOverflow(by: m2)
132+
if overflow {
133+
return m1 > 0 ? Int64.max : Int64.min
134+
}
135+
return result
136+
}
137+
138+
// Returns its argument clamped to the range [Int64.min, Int64.max].
139+
private func toInt64Clamped(_ value: Double) -> Int64 {
140+
if value.isNaN { return Int64.max }
141+
if value >= Double(Int64.max) { return Int64.max }
142+
if value <= Double(Int64.min) { return Int64.min }
143+
return Int64(value)
144+
}
145+
146+
/// Represents a time interval that can be used as an offset from a `DispatchTime`
147+
/// or `DispatchWallTime`.
148+
///
149+
/// For example:
150+
/// let inOneSecond = DispatchTime.now() + DispatchTimeInterval.seconds(1)
151+
///
152+
/// If the requested time interval is larger then the internal representation
153+
/// permits, the result of adding it to a `DispatchTime` or `DispatchWallTime`
154+
/// is `DispatchTime.distantFuture` and `DispatchWallTime.distantFuture`
155+
/// respectively. Such time intervals compare as equal:
156+
///
157+
/// let t1 = DispatchTimeInterval.seconds(Int.max)
158+
/// let t2 = DispatchTimeInterval.milliseconds(Int.max)
159+
/// let result = t1 == t2 // true
126160
public enum DispatchTimeInterval {
127161
case seconds(Int)
128162
case milliseconds(Int)
@@ -133,9 +167,9 @@ public enum DispatchTimeInterval {
133167

134168
internal var rawValue: Int64 {
135169
switch self {
136-
case .seconds(let s): return Int64(s) * Int64(NSEC_PER_SEC)
137-
case .milliseconds(let ms): return Int64(ms) * Int64(NSEC_PER_MSEC)
138-
case .microseconds(let us): return Int64(us) * Int64(NSEC_PER_USEC)
170+
case .seconds(let s): return clampedInt64Product(Int64(s), Int64(NSEC_PER_SEC))
171+
case .milliseconds(let ms): return clampedInt64Product(Int64(ms), Int64(NSEC_PER_MSEC))
172+
case .microseconds(let us): return clampedInt64Product(Int64(us), Int64(NSEC_PER_USEC))
139173
case .nanoseconds(let ns): return Int64(ns)
140174
case .never: return Int64.max
141175
}
@@ -162,16 +196,12 @@ public func -(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTim
162196
}
163197

164198
public func +(time: DispatchTime, seconds: Double) -> DispatchTime {
165-
let interval = seconds * Double(NSEC_PER_SEC)
166-
let t = CDispatch.dispatch_time(time.rawValue,
167-
interval.isInfinite || interval.isNaN ? Int64.max : Int64(interval))
199+
let t = CDispatch.dispatch_time(time.rawValue, toInt64Clamped(seconds * Double(NSEC_PER_SEC)));
168200
return DispatchTime(rawValue: t)
169201
}
170202

171203
public func -(time: DispatchTime, seconds: Double) -> DispatchTime {
172-
let interval = -seconds * Double(NSEC_PER_SEC)
173-
let t = CDispatch.dispatch_time(time.rawValue,
174-
interval.isInfinite || interval.isNaN ? Int64.min : Int64(interval))
204+
let t = CDispatch.dispatch_time(time.rawValue, toInt64Clamped(-seconds * Double(NSEC_PER_SEC)));
175205
return DispatchTime(rawValue: t)
176206
}
177207

@@ -186,15 +216,11 @@ public func -(time: DispatchWallTime, interval: DispatchTimeInterval) -> Dispatc
186216
}
187217

188218
public func +(time: DispatchWallTime, seconds: Double) -> DispatchWallTime {
189-
let interval = seconds * Double(NSEC_PER_SEC)
190-
let t = CDispatch.dispatch_time(time.rawValue,
191-
interval.isInfinite || interval.isNaN ? Int64.max : Int64(interval))
219+
let t = CDispatch.dispatch_time(time.rawValue, toInt64Clamped(seconds * Double(NSEC_PER_SEC)));
192220
return DispatchWallTime(rawValue: t)
193221
}
194222

195223
public func -(time: DispatchWallTime, seconds: Double) -> DispatchWallTime {
196-
let interval = -seconds * Double(NSEC_PER_SEC)
197-
let t = CDispatch.dispatch_time(time.rawValue,
198-
interval.isInfinite || interval.isNaN ? Int64.min : Int64(interval))
224+
let t = CDispatch.dispatch_time(time.rawValue, toInt64Clamped(-seconds * Double(NSEC_PER_SEC)));
199225
return DispatchWallTime(rawValue: t)
200226
}

0 commit comments

Comments
 (0)