-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Revert "Foundation: fix missing string terminator" #2676
Merged
+14
−4
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Hmm, this is documented to call
free
: https://developer.apple.com/documentation/foundation/nsstring/1413830-initwithbytesnocopyI think we should replicate this comment and the caller should make sure that the memory is allocated using
malloc
. WDYT?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.
So:
free()
is ultimately compatible with the allocation done by CF, ObjC and Swift. This is sort of a non-issue on those platforms.This has two sides: a contract side and an implementation side.
On the contract side: in ObjC, the original API here takes a
void *
and the language does not mandate how you produce that pointer, only that it be valid. It makes sense here to specify precisely the deallocation strategy.But. This is Swift. And in Swift, this type is
UnsafeMutableRawPointer
— a type that does have a language-provided default deallocation facility. And in this language, since there are far fewer guarantees wrt the memory layout of language constructs*, the vast majority of these pointers will be produced by the.allocate(byteCount:alignment:)
method of that class or the equivalent bound variations in the non-Raw
pointers.My opinion here is that we can deviate from Darwin Foundation as follows:
.deallocate()
will eventually call through tofree()
on Linux and Darwin. We are going to rely on this property, for now, or conditionality the change away fromfree()
on these OSes, so long that it interoperates correctly with buffers allocated with.allocate(…)
.On Windows and other OSes where it's not guaranteed that
.allocate(…)
use malloc, we mandate thatfreeWhenDone: true
use.deallocate()
instead offree()
. This puts an onus on the caller to use.allocate(…)
rather than use OS facilities directly. I think this is a reasonable requirement to make: callers that choose a nonstandard allocation strategy on other OSes are already mandated to usefreeWhenDone: false
with these pointers and handle deallocation themselves;malloc()
just happens to not be the standard on those OSes.The contract side is straightforward-ish — we're just extending the existing contract to go from 'specifically free()' to 'the system-preferred deallocation strategy, which on Linux and Darwin is free()'. The implementation side puts a burden on implementors on those OSes where this is not the case — which means that existing setups don't regress.
I think I am in favor of making this change conditional on the OS, and we should note it in the release notes (in Docs/ReleaseNotes*.md) as a well-known deviation from Darwin behavior.
(* that is, you "can't just" take a pointer to a Swift.Array like you can for an array in C, and have strong guarantees about the memory layout of that object unless you are working hand in hand with the compiler and the standard library implementations.)
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.
@millenomi but what happens to all the code that was previously correctly matching
malloc
withfreeWhenDone: true
? That works today and will then break, correct?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.
@weissi I actually have code that passes raw buffer originated from C module (which uses malloc internally) to
String(bytesNoCopy:...
with "freeWenDone: true". And it crashes with this patch. I understand motivation though.@compnerd I think we need to add one more detail to the docs. Official documentation doesn't clarify when
freeWhenDone
takes effect. It is unclear that on Windows we are allowed to do manual cleanup right after String init. As user, I'd rather think thatString(bytesNoCopy:..
will need specified buffer alive until deinit.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.
@lxbndr Thanks for chiming in! I mean it is documented to
free
so I think it shouldfree
. If we think we need a Swift version of this, why not create a newString(bytesNoCopy:...deallocateWhenDone:)
that would call deallocate?I'm pretty strongly against just changing the contract.
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.
@lxbndr Are you saying that memory allocated with
malloc
but then passed as anUnsafeRawMutableBufferPointer
then hasdeallocate()
called on it crashes? Is this on windows? Because that sounds like another bug.@weichsel
Data
has the following initialiser:init(bytesNoCopy bytes: UnsafeMutableRawPointer, count: Int, deallocator: Data.Deallocator)
where the deallocator can be specified including a custom one passed with a closure so that probably offers a good way to do it although it needsdeallocate()
added intoData.Deallocator
https://developer.apple.com/documentation/foundation/data/deallocatorThere 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.
@spevans Yes, this is on Windows. deallocateWhenDone:false together with manual free() works like a charm. Initial PR description now seems about a bit different case. It's about Swift buffers which can't be passed to free(). I think I need look closer on my case.
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.
@weissi Feels like band-aid, I agree. Approach with custom deallocator like in
Data
looks way better.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.
@lxbndr We're looking into a more comprehensive solution that will fix the issue for good, but a quick search on GitHub shows that using
UMRP.alloc…(…)
withfreeWhenDone:
is ubiquitous and prevalent.