Skip to content

Commit 2db063f

Browse files
committed
Fix overflow traps in DispatchTime/DispatchWallTime/DispatchTimeInterval.
rdar://problem/34440944
1 parent 40fc1f3 commit 2db063f

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
@@ -119,6 +119,40 @@ public func ==(a: DispatchWallTime, b: DispatchWallTime) -> Bool {
119119
return a.rawValue == b.rawValue
120120
}
121121

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

130164
internal var rawValue: Int64 {
131165
switch self {
132-
case .seconds(let s): return Int64(s) * Int64(NSEC_PER_SEC)
133-
case .milliseconds(let ms): return Int64(ms) * Int64(NSEC_PER_MSEC)
134-
case .microseconds(let us): return Int64(us) * Int64(NSEC_PER_USEC)
166+
case .seconds(let s): return clampedInt64Product(Int64(s), Int64(NSEC_PER_SEC))
167+
case .milliseconds(let ms): return clampedInt64Product(Int64(ms), Int64(NSEC_PER_MSEC))
168+
case .microseconds(let us): return clampedInt64Product(Int64(us), Int64(NSEC_PER_USEC))
135169
case .nanoseconds(let ns): return Int64(ns)
136170
case .never: return Int64.max
137171
}
@@ -158,16 +192,12 @@ public func -(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTim
158192
}
159193

160194
public func +(time: DispatchTime, seconds: Double) -> DispatchTime {
161-
let interval = seconds * Double(NSEC_PER_SEC)
162-
let t = CDispatch.dispatch_time(time.rawValue,
163-
interval.isInfinite || interval.isNaN ? Int64.max : Int64(interval))
195+
let t = CDispatch.dispatch_time(time.rawValue, toInt64Clamped(seconds * Double(NSEC_PER_SEC)));
164196
return DispatchTime(rawValue: t)
165197
}
166198

167199
public func -(time: DispatchTime, seconds: Double) -> DispatchTime {
168-
let interval = -seconds * Double(NSEC_PER_SEC)
169-
let t = CDispatch.dispatch_time(time.rawValue,
170-
interval.isInfinite || interval.isNaN ? Int64.min : Int64(interval))
200+
let t = CDispatch.dispatch_time(time.rawValue, toInt64Clamped(-seconds * Double(NSEC_PER_SEC)));
171201
return DispatchTime(rawValue: t)
172202
}
173203

@@ -182,15 +212,11 @@ public func -(time: DispatchWallTime, interval: DispatchTimeInterval) -> Dispatc
182212
}
183213

184214
public func +(time: DispatchWallTime, seconds: Double) -> DispatchWallTime {
185-
let interval = seconds * Double(NSEC_PER_SEC)
186-
let t = CDispatch.dispatch_time(time.rawValue,
187-
interval.isInfinite || interval.isNaN ? Int64.max : Int64(interval))
215+
let t = CDispatch.dispatch_time(time.rawValue, toInt64Clamped(seconds * Double(NSEC_PER_SEC)));
188216
return DispatchWallTime(rawValue: t)
189217
}
190218

191219
public func -(time: DispatchWallTime, seconds: Double) -> DispatchWallTime {
192-
let interval = -seconds * Double(NSEC_PER_SEC)
193-
let t = CDispatch.dispatch_time(time.rawValue,
194-
interval.isInfinite || interval.isNaN ? Int64.min : Int64(interval))
220+
let t = CDispatch.dispatch_time(time.rawValue, toInt64Clamped(-seconds * Double(NSEC_PER_SEC)));
195221
return DispatchWallTime(rawValue: t)
196222
}

0 commit comments

Comments
 (0)