Hi, Swift Evolution! I've been working on this proposal for roughly an entire day, and I think it can solve many of the problems we have with unsafe constructs in the standard library. Well, maybe just one or two. Probably. Thanks for your consideration!
-Jordan
ProbablySafeBufferPointer
- Proposal: APR-NNNN
- Author: Jordan Rose
- Review Manager: TBD
- Implementation: (hang on I'll post it in a minute)^W^W^W^W^W^W^W^W^H Prototype: ProbablySafeBufferPointer by jrose-apple · Pull Request #15669 · apple/swift · GitHub
- Status: Awaiting rejection
Introduction
Swift provides types for working safely with memory, including Array and Data, and some for working with raw pointers, like UnsafeBufferPointer. This proposal investigates the space between these two groups. Badly.
Motivation
Swift's types for working safely with memory are great. However, sometimes it becomes necessary to use raw pointers, and the mechanisms for doing so are all unsafe. Surely we should try to provide as much safety as possible when working with things like C that traffic in raw pointers.
Proposed solution
The standard library gains a new type, ProbablySafeBufferPointer<Element>
. This type is similar to UnsafeBufferPointer, but checks to make sure that accesses to the buffer are safe. Probably safe, anyway. (Hey, it's better than nothing, right?)
(no)
Detailed design
The public interface of ProbablySafeBufferPointer is shown below. It is deliberately designed to be familiar to users of UnsafeBufferPointer.
struct ProbablySafeBufferPointer<Element> {
init(
_ buffer: UnsafeBufferPointer<Element>)
/*convenience*/ init(
_ buffer: UnsafeMutableBufferPointer<Element>)
/*convenience*/ init(
start: UnsafePointer<Element>?,
count: Int)
subscript(index: Int) -> Element { get }
func withUnsafeBufferPointer<Result>(
_ operation: (UnsafeBufferPointer<Element>) throws -> Result
) rethrows -> Result
// I forgot UnsafeBufferPointer had this, but sure!
func deallocate()
}
extension ProbablySafeBufferPointer: RandomAccessCollection { … }
When constructed, ProbablySafeBufferPointer asserts* that the entire buffer is a contiguously allocated chunk of memory, either:
- part of a single allocation from
malloc
(the system heap allocator) - something in the "static" data that lives in the executable or a loaded library
- something on the current thread's stack
Memory from other threads' stacks is rejected, since accessing memory that came from other threads is probably not safe. The exception is an empty buffer (with a count of 0), which by definition is safe to (not) access no matter what start address you have!
Because allocations come and go, these conditions are re-checked on each subscript access and when calling withUnsafeBufferPointer
. Just to be different, deallocate
actually does no additional checking because it's assumed that the underlying system heap allocator already does it.
Taken together, this provides a non-owning buffer pointer that's nonetheless probably safe to access (as in, the program will abort rather than corrupting arbitrary memory in the process if it can figure out that something went wrong). Sure, it's not 100% safe---static data and stack data can't be bounds-checked, and heap data might get deallocated and something else reallocated in its place---but it's better than nothing, right?
(no)
* I said "asserts", but in practice precondition
is probably a better fit.
Future directions
This proposal only discusses a replacement for UnsafeBufferPointer, but the entire family of [Autoreleasing]Unsafe[Mutable][Raw][Buffer]Pointers could in theory benefit from this. Probably.
Source compatibility
ProbablySafeBufferPointer is a new type; for backwards-compatibility, no existing APIs will be migrated to ProbablySafeBufferPointer at this time.
Effect on ABI stability
If this proposal is accepted, the standard library will be forced to continue implementing this struct for all time, or at least until the next ABI break.
Effect on API resilience
None. Probably.
Alternatives considered
ASan is probably a better alternative 100% of the time on all platforms where it is supported. Continuing to use UnsafeBufferPointer is probably a better alternative on any new platforms Swift supports but ASan does not. Using safe types from the start is what you ought to be doing anyway whenever possible.
P.S. Happy April Fools' Day!