Skip to content

Commit 0dc6201

Browse files
Merge pull request #11927 from ktopley-apple/dispatch-time-overflows
Fix overflow traps in DispatchTime/DispatchWallTime/DispatchTimeInterval
2 parents 276f49a + c5af279 commit 0dc6201

File tree

2 files changed

+99
-17
lines changed

2 files changed

+99
-17
lines changed

stdlib/public/SDK/Dispatch/Time.swift

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,41 @@ extension DispatchWallTime {
114114
}
115115
}
116116

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

125160
internal var rawValue: Int64 {
126161
switch self {
127-
case .seconds(let s): return Int64(s) * Int64(NSEC_PER_SEC)
128-
case .milliseconds(let ms): return Int64(ms) * Int64(NSEC_PER_MSEC)
129-
case .microseconds(let us): return Int64(us) * Int64(NSEC_PER_USEC)
162+
case .seconds(let s): return clampedInt64Product(Int64(s), Int64(NSEC_PER_SEC))
163+
case .milliseconds(let ms): return clampedInt64Product(Int64(ms), Int64(NSEC_PER_MSEC))
164+
case .microseconds(let us): return clampedInt64Product(Int64(us), Int64(NSEC_PER_USEC))
130165
case .nanoseconds(let ns): return Int64(ns)
131166
case .never: return Int64.max
132167
}
@@ -153,16 +188,12 @@ public func -(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTim
153188
}
154189

155190
public func +(time: DispatchTime, seconds: Double) -> DispatchTime {
156-
let interval = seconds * Double(NSEC_PER_SEC)
157-
let t = __dispatch_time(time.rawValue,
158-
interval.isInfinite || interval.isNaN ? Int64.max : Int64(interval))
191+
let t = __dispatch_time(time.rawValue, toInt64Clamped(seconds * Double(NSEC_PER_SEC)));
159192
return DispatchTime(rawValue: t)
160193
}
161194

162195
public func -(time: DispatchTime, seconds: Double) -> DispatchTime {
163-
let interval = -seconds * Double(NSEC_PER_SEC)
164-
let t = __dispatch_time(time.rawValue,
165-
interval.isInfinite || interval.isNaN ? Int64.min : Int64(interval))
196+
let t = __dispatch_time(time.rawValue, toInt64Clamped(-seconds * Double(NSEC_PER_SEC)));
166197
return DispatchTime(rawValue: t)
167198
}
168199

@@ -177,15 +208,11 @@ public func -(time: DispatchWallTime, interval: DispatchTimeInterval) -> Dispatc
177208
}
178209

179210
public func +(time: DispatchWallTime, seconds: Double) -> DispatchWallTime {
180-
let interval = seconds * Double(NSEC_PER_SEC)
181-
let t = __dispatch_time(time.rawValue,
182-
interval.isInfinite || interval.isNaN ? Int64.max : Int64(interval))
211+
let t = __dispatch_time(time.rawValue, toInt64Clamped(seconds * Double(NSEC_PER_SEC)));
183212
return DispatchWallTime(rawValue: t)
184213
}
185214

186215
public func -(time: DispatchWallTime, seconds: Double) -> DispatchWallTime {
187-
let interval = -seconds * Double(NSEC_PER_SEC)
188-
let t = __dispatch_time(time.rawValue,
189-
interval.isInfinite || interval.isNaN ? Int64.min : Int64(interval))
216+
let t = __dispatch_time(time.rawValue, toInt64Clamped(-seconds * Double(NSEC_PER_SEC)));
190217
return DispatchWallTime(rawValue: t)
191218
}

test/stdlib/Dispatch.swift

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,21 +126,47 @@ DispatchAPI.test("DispatchTime.addSubtract") {
126126
expectEqual(DispatchTime(uptimeNanoseconds: 1), then)
127127

128128
then = DispatchTime.now() - Double.nan
129+
expectEqual(DispatchTime.distantFuture, then)
130+
131+
then = DispatchTime.now() + Date.distantFuture.timeIntervalSinceNow
132+
expectEqual(DispatchTime(uptimeNanoseconds: UInt64.max), then)
133+
134+
then = DispatchTime.now() + Date.distantPast.timeIntervalSinceNow
129135
expectEqual(DispatchTime(uptimeNanoseconds: 1), then)
136+
137+
then = DispatchTime.now() - Date.distantFuture.timeIntervalSinceNow
138+
expectEqual(DispatchTime(uptimeNanoseconds: 1), then)
139+
140+
then = DispatchTime.now() - Date.distantPast.timeIntervalSinceNow
141+
expectEqual(DispatchTime(uptimeNanoseconds: UInt64.max), then)
130142
}
131143

132144
DispatchAPI.test("DispatchWallTime.addSubtract") {
145+
let distantPastRawValue = DispatchWallTime.distantFuture.rawValue - UInt64(1)
146+
133147
var then = DispatchWallTime.now() + Double.infinity
134148
expectEqual(DispatchWallTime.distantFuture, then)
135149

136150
then = DispatchWallTime.now() + Double.nan
137151
expectEqual(DispatchWallTime.distantFuture, then)
138152

139153
then = DispatchWallTime.now() - Double.infinity
140-
expectEqual(DispatchWallTime.distantFuture.rawValue - UInt64(1), then.rawValue)
154+
expectEqual(distantPastRawValue, then.rawValue)
141155

142156
then = DispatchWallTime.now() - Double.nan
143-
expectEqual(DispatchWallTime.distantFuture.rawValue - UInt64(1), then.rawValue)
157+
expectEqual(DispatchWallTime.distantFuture, then)
158+
159+
then = DispatchWallTime.now() + Date.distantFuture.timeIntervalSinceNow
160+
expectEqual(DispatchWallTime.distantFuture, then)
161+
162+
then = DispatchWallTime.now() + Date.distantPast.timeIntervalSinceNow
163+
expectEqual(distantPastRawValue, then.rawValue)
164+
165+
then = DispatchWallTime.now() - Date.distantFuture.timeIntervalSinceNow
166+
expectEqual(distantPastRawValue, then.rawValue)
167+
168+
then = DispatchWallTime.now() - Date.distantPast.timeIntervalSinceNow
169+
expectEqual(DispatchWallTime.distantFuture, then)
144170
}
145171

146172
DispatchAPI.test("DispatchTime.uptimeNanos") {
@@ -516,6 +542,35 @@ if #available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
516542
}
517543
}
518544

545+
DispatchAPI.test("DispatchTimeInterval") {
546+
// Basic tests that the correct value is stored and the == method works
547+
for i in stride(from:1, through: 100, by: 5) {
548+
expectEqual(DispatchTimeInterval.seconds(i), DispatchTimeInterval.milliseconds(i * 1000))
549+
expectEqual(DispatchTimeInterval.milliseconds(i), DispatchTimeInterval.microseconds(i * 1000))
550+
expectEqual(DispatchTimeInterval.microseconds(i), DispatchTimeInterval.nanoseconds(i * 1000))
551+
}
552+
553+
554+
// Check some cases that used to cause arithmetic overflow when evaluating the rawValue for ==
555+
var t = DispatchTimeInterval.seconds(Int.max)
556+
expectTrue(t == t) // This would crash.
557+
558+
t = DispatchTimeInterval.seconds(-Int.max)
559+
expectTrue(t == t) // This would crash.
560+
561+
t = DispatchTimeInterval.milliseconds(Int.max)
562+
expectTrue(t == t) // This would crash.
563+
564+
t = DispatchTimeInterval.milliseconds(-Int.max)
565+
expectTrue(t == t) // This would crash.
566+
567+
t = DispatchTimeInterval.microseconds(Int.max)
568+
expectTrue(t == t) // This would crash.
569+
570+
t = DispatchTimeInterval.microseconds(-Int.max)
571+
expectTrue(t == t) // This would crash.
572+
}
573+
519574
#if swift(>=4.0)
520575
DispatchAPI.test("DispatchTimeInterval.never.equals") {
521576
expectTrue(DispatchTimeInterval.never == DispatchTimeInterval.never)

0 commit comments

Comments
 (0)