-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Fix overflow traps in DispatchTime/DispatchWallTime/DispatchTimeInterval #11927
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix overflow traps in DispatchTime/DispatchWallTime/DispatchTimeInterval #11927
Conversation
@swift-ci please smoke test |
1 similar comment
@swift-ci please smoke test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why these properties are desirable. You're going to say that DispatchTimeInterval.seconds(Int.max) == .milliseconds(Int.max)
, which is silly. And the other traps do seem like they should trap.
if m1 == 0 { return Int64(0) } | ||
if m1 > Int64.max/m2 { return Int64.max } | ||
if m1 < Int64.min/m2 { return Int64.min } | ||
return m1 * m2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use multiplyWithOverflow
for this instead…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean multipliedReportingOverflow()? I didn't know about that :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the change overall, the general rule in libdispatch is that operations on dispatch_time_t that overflow are clamped to DISPATCH_TIME_FOREVER. The intent of this change is to make the same true in Swift. For the change to DispatchTimeInterval, you are right that it might seem odd that DispatchTimeInterval.seconds(Int.max) == .milliseconds(Int.max), but DispatchTimeInterval is used as an offset to form a DispatchTime or DispatchWallTime and the result of using either of these values is going to be clamped to Dispatch[Wall]Time.distantFuture. Currently, you can't say "DispatchTime.now() + DispatchTimeInterval.seconds(Int.max)" because it traps. Of course Int.max is a pathological example - in reality, there have been cases where large values have been used that have caused traps and this had to be worked around by checking the value before passing it to dispatch and using DispatchTime.distantFuture instead. I probably should add some comments to DispatchTimeInterval that explain this behavior.
// 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: Double, _ m2: Double) -> Int64 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…and I'm not sure why this isn't just a transformation on a single Double.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain what you mean by that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like it's equivalent to toInt64Clamped(m1 * m2)
.
@swift-ci please smoke test |
Updated based on Jordan's comments. |
@swift-ci please smoke test |
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 } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I realized this check still isn't quite correct because Double doesn't have enough precision to represent Int64.max. One way to do it would be to check against Double(bitPattern: Double(Int64.max).bitPattern-1)
instead, but that's black magic. @stephentyrone, what's the right way to write this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it's not 100% correct, but it I think it is the closest I can get without "black magic". Maybe a version of this that can use the black magic should be added to the standard library. Anyway, it should be OK as it stands because we don't need to be completely precise - we just need to catch times that are far enough in the future that we should treat them as "forever".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add a clamping init from Int to Double, but that's outside the scope of this change. As a temporary hack, it suffices to make this if value >= Double(Int64.max)
; that does the right thing without black magic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jrose-apple More generally, instead of Double(bitPattern: x.bitPattern-1)
use x.nextDown
if you know x
is positive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I was looking for nextDown
and didn't see it. Thanks, Steve.
@swift-ci please smoke test |
1 similar comment
@swift-ci please smoke test |
@swift-ci please smoke test |
…val. rdar://problem/32678302
Squashed commits. |
@swift-ci please smoke test |
Looks like we've still got a problem here on a 32-bit iOS device.
Let me know if you want more information. |
(This is rdar://problem/34751238, by the way.) |
Fixes overflows in DispatchTime/DispatchWallTime/DispatchTimeInterval.
Examples:
DispatchTime.now() + Date.distantFuture.timeIntervalSinceNow // traps
DispatchTime.now() + Date.distantPast.timeIntervalSinceNow. // traps
let t = DispatchTimeInterval.seconds(Int.max)
t == t // Traps due to conversion to nanoseconds.
Also added tests for these and similar cases.