Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# master
*Please add new entries at the top.*
1. Fixed integer overflow for `DispatchTimeInterval` in FoundationExtensions.swift (#506)

# 3.0.0-alpha.1
# 3.0.0-alpha.1
Expand Down
30 changes: 13 additions & 17 deletions Sources/FoundationExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ extension DispatchTimeInterval {
case let .milliseconds(ms):
return TimeInterval(TimeInterval(ms) / 1000.0)
case let .microseconds(us):
return TimeInterval(Int64(us) * Int64(NSEC_PER_USEC)) / TimeInterval(NSEC_PER_SEC)
return TimeInterval(Int64(us)) * TimeInterval(NSEC_PER_USEC) / TimeInterval(NSEC_PER_SEC)
case let .nanoseconds(ns):
return TimeInterval(ns) / TimeInterval(NSEC_PER_SEC)
case .never:
Expand All @@ -120,23 +120,19 @@ extension DispatchTimeInterval {

/// Scales a time interval by the given scalar specified in `rhs`.
///
/// - note: This method is only used internally to "scale down" a time
/// interval. Specifically it's used only to scale intervals to 10%
/// of their original value for the default `leeway` parameter in
/// `Scheduler.schedule(after:action:)` schedule and similar
/// other methods.
///
/// If seconds is over 200,000, 10% is ~2,000, and hence we end up
/// with a value of ~2,000,000,000. Not quite overflowing a signed
/// integer on 32-bit platforms, but close.
///
/// Even still, 200,000 seconds should be a rarely (if ever)
/// specified interval for our APIs. And even then, folks should be
/// smart and specify their own `leeway` parameter.
///
/// - returns: Scaled interval in microseconds
/// - returns: Scaled interval in minimal appropriate unit
internal static func *(lhs: DispatchTimeInterval, rhs: Double) -> DispatchTimeInterval {
let seconds = lhs.timeInterval * rhs
return .microseconds(Int(seconds * 1000 * 1000))
var result: DispatchTimeInterval = .never
if let integerTimeInterval = Int(exactly: (seconds * 1000 * 1000 * 1000).rounded()) {
result = .nanoseconds(integerTimeInterval)
} else if let integerTimeInterval = Int(exactly: (seconds * 1000 * 1000).rounded()) {
result = .microseconds(integerTimeInterval)
} else if let integerTimeInterval = Int(exactly: (seconds * 1000).rounded()) {
result = .milliseconds(integerTimeInterval)
} else if let integerTimeInterval = Int(exactly: (seconds).rounded()) {
result = .seconds(integerTimeInterval)
}
return result
}
}
7 changes: 7 additions & 0 deletions Tests/ReactiveSwiftTests/FoundationExtensionsSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ class FoundationExtensionsSpec: QuickSpec {
expect((DispatchTimeInterval.seconds(5) * 0.5).timeInterval).to(beCloseTo(DispatchTimeInterval.milliseconds(2500).timeInterval))
expect((DispatchTimeInterval.seconds(1) * 0.25).timeInterval).to(beCloseTo(DispatchTimeInterval.milliseconds(250).timeInterval))
}

it("should not introduce integer overflow upon scale") {
expect((DispatchTimeInterval.seconds(Int.max) * 0.01).timeInterval).to(beCloseTo(10 * DispatchTimeInterval.milliseconds(Int.max).timeInterval, within: 1))
expect((DispatchTimeInterval.milliseconds(Int.max) * 0.01).timeInterval).to(beCloseTo(10 * DispatchTimeInterval.microseconds(Int.max).timeInterval, within: 1))
expect((DispatchTimeInterval.microseconds(Int.max) * 0.01).timeInterval).to(beCloseTo(10 * DispatchTimeInterval.nanoseconds(Int.max).timeInterval, within: 1))
expect((DispatchTimeInterval.seconds(Int.max) * 10).timeInterval) == Double.infinity
}

it("should produce the expected TimeInterval values") {
expect(DispatchTimeInterval.seconds(1).timeInterval).to(beCloseTo(1.0))
Expand Down