RFC: ProbablySafeBufferPointer

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

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!

17 Likes

I like this “probably unsafe” manifesto and wish to subscribe to your mailing list.

I feel there is room to take it more literally and combine this with the Random Unification proposal. StochasticPointer would be a fine addition to stdlib.

7 Likes

That could be used to get a really good RNG: Take eight bytes from a random place in memory — you could even iterate the process to get incredible random data!
But first things first: We should focus on getting lolcode into Swift today.

2 Likes