Skip to content
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 crash with DateInterval. #20106

Closed
wants to merge 1 commit into from
Closed

Fix crash with DateInterval. #20106

wants to merge 1 commit into from

Conversation

kzaher
Copy link

@kzaher kzaher commented Oct 27, 2018

Fixes crash with DateInterval.

Steps to repro:

DateInterval(start: Date(timeIntervalSince1970: 0.0), end: Date(timeIntervalSince1970: 0.0)).hashValue

@swift-ci Please smoke test

Steps to repro:
```
DateInterval(start: Date(timeIntervalSince1970: 0.0), end: Date(timeIntervalSince1970: 0.0)).hashValue
```
@kzaher kzaher changed the title Fix crash with TimeInterval. Fix crash with DateInterval. Oct 27, 2018
@kzaher
Copy link
Author

kzaher commented Oct 29, 2018

@swift-ci please test

@compnerd
Copy link
Collaborator

@kzaher Thanks for the patch. Unfortunately, only certain people can trigger the CI :-)

@compnerd
Copy link
Collaborator

CC: @moiseev

@compnerd
Copy link
Collaborator

@swift-ci please test

@swift-ci
Copy link
Collaborator

Build failed
Swift Test OS X Platform
Git Sha - ca14b86

@moiseev
Copy link
Contributor

moiseev commented Oct 30, 2018

Thanks, @kzaher!
Can you also implement a test that proves there is indeed no crash in the code you mentioned?
Also, the similar change should be done in corelibs-foundation: https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/DateInterval.swift#L153

/cc @phausler and @millenomi

Copy link
Member

@phausler phausler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a simple unit test for this to not crash? just so we can validate it in the future.

return withUnsafeMutablePointer(to: &buf) {
$0.withMemoryRebound(to: UInt8.self, capacity: 2 * MemoryLayout<UInt>.size / MemoryLayout<UInt8>.size) {
return Int(bitPattern: CFHashBytes($0, CFIndex(MemoryLayout<UInt>.size * 2)))
$0.withMemoryRebound(to: UInt8.self, capacity: 2 * MemoryLayout<TimeInterval>.size / MemoryLayout<UInt8>.size) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe this is a sound usage of withMemoryRebound(to:capacity:) (and neither was original code's usage) – the method requires that you rebind to a type that has the same size and alignment as the current Pointee type (e.g UInt to Int). The reasoning for this is because it uses the capacity you pass in order to bind the memory back to the original type, which in this case would result in too many instances being re-bound back to TimeInterval.

One alternative is to use withUnsafeBytes(of:), for example (untested):

  return withUnsafeBytes(of: &buf) { buffer in
    Int(bitPattern: CFHashBytes(Array(buffer), buffer.count))
  }

Copy link
Member

@phausler phausler Oct 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array would not be ideal since that will deallocate after passing in, I would suggest to either bit cast since we are going directly to C or use , and also the internal function CFHashBytes takes a uint8_t * not a const uint8_t * because of "historical reasons"... however it does not mutate the pointer in any way so it is safe to cast to a mutable type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be reasonable:

return withUnsafeMutableBytes(of: &buf) {
    return CFHashBytes($0.baseAddress?.assumingMemoryBound(to: UInt8.self), $0.count)
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The array-to-pointer conversion should keep the Array valid for the duration of the call to CFHashBytes, so assuming CFHashBytes doesn't escape the pointer, there should be no problem there.

And ah, okay, if CFHashBytes requires a mutable pointer, you could do:

  return withUnsafeBytes(of: &buf) { buffer in
    var array = Array(buffer)
    return Int(bitPattern: CFHashBytes(&array, buffer.count))
  }

Edit: Actually, yeah your suggestion to use assumingMemoryBound(to:) is better if the pointer is going straight to C.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I understand correctly:

  • I should change it to:
return withUnsafeMutableBytes(of: &buf) {
    return CFHashBytes($0.baseAddress?.assumingMemoryBound(to: UInt8.self), $0.count)
}

Is this correct?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll try to do these changes tomorrow. Can somebody point me where should I add those tests?

I was only be able to found Date tests.

https://github.com/apple/swift-corelibs-foundation/blob/master/TestFoundation/TestDate.swift

Are there any DateInterval tests, am I missing something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused. I'm not sure why there are tests in both apple/swift and apple/swift-corelibs-foundation. What determines does it go into one or the other?

Where should these tests live? I'm assuming that I should only call hashValue for some old dateinterval and make sure that it doesn't crash.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Darwin, Swift depends on platform Foundation. Tests for the overlay that target Darwin should go in apple/swift as a result.

On non-Darwin, swift-corelibs-foundation depends on Swift. Tests for swift-corelibs-foundation outside of Darwin should go in apple/swift-corelibs-foundation.

@augustsaintfreytag
Copy link

I'd like to bump this issue, storing and handling dates and date intervals should be imperturbable core functionality. Is there a better workaround than (1) building my own DateInterval struct from scratch or (2) adding a custom, naive hashValue function to the struct storing the date interval?

@CodaFi
Copy link
Member

CodaFi commented Nov 18, 2019

The new Hashable implementation should address this crash. If not, please file another SR.

Closing this out. Thank you for the change @kzaher!

@CodaFi CodaFi closed this Nov 18, 2019
@augustsaintfreytag
Copy link

Fot anyone passing through, I've filed feedback FB7405252 with Apple in October 2019, they've now responded that I should recheck with macOS 10.15.4 and my original testing playground with dates before reference date (e.g. 01/01/1981) no longer crashes now. Apparently, this is now in fact fixed and resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants