[swift-evolution] [Draft] UnsafeRawPointer API

Andrew Trick atrick at apple.com
Mon Jun 27 21:50:04 CDT 2016


> On Jun 27, 2016, at 7:15 PM, Dave Abrahams <dabrahams at apple.com> wrote:
> 
> 
> 
> Sent from my moss-covered three-handled family gradunza
> 
> On Jun 27, 2016, at 4:27 PM, Andrew Trick <atrick at apple.com <mailto:atrick at apple.com>> wrote:
> 
>> 
>>> On Jun 27, 2016, at 3:35 PM, Dave Abrahams <dabrahams at apple.com <mailto:dabrahams at apple.com>> wrote:
>>> 
>>>> Casting from a raw pointer to a typed pointer is only more dangerous
>>>> than other raw pointer operations because it is the first step in this
>>>> sequence of operations, which is undefined:
>>>> 
>>>> ptrA = rawPtr.cast(to: UnsafePointer<A>.self)
>>>> ptrA.initialize(with: A())
>>>> ptrA.deinitialize()
>>>> 
>>>> ptrB = rawPtr.cast(to: UnsafePointer<B>.self)
>>>> ptrB.initialize(with: B())
>>> 
>>> But it's trivial to get undefined behavior without any of that.  Just:
>>> 
>>>  _ = rawPtr.load(UnsafePointer<NonTrivialType>.self)
>> 
>> That's another way to obtain a typed pointer, but by itself it is well defined.
> 
> Sorry, I meant to dereference that typed pointer as part of the expression. Now boom.

Loading an UnsafePointer<T> from the contents of the raw pointer’s memory is fine if that’s what the memory contains. Dereferencing the loaded typed pointer is fine if that memory has been previously initialized with value of NonTrivialType.

Also, loading NonTrivialType directly from a raw pointer is fine if the memory contains an initialized NonTrivialType. This is fine:

  let v = rawPtr.load(NonTrivialType.self)

It returns an initialized `v` (a proper copy of the value in memory).

>> This is an important point, so I want to make sure I’m getting it across.
>> 
>> The following code is well-defined:
>> ```
>> ptrA = rawPtr.initialize(with: A())
>> ptrA.deinitialize()
>> ptrB = rawPtr.initialize(with: B())
>> ```
>> The following code is undefined:
>> ```
>> ptrA = rawPtr.cast(to: UnsafePointer<A>.self)
>> ptrA.initialize(with: A())
>> ptrA.deinitialize()
>> ptrB = rawPtr.cast(to: UnsafePointer<B>.self)
>> ptrB.initialize(with: B())
>> ```
>> It is hard to spot the difference between the two styles without drawing attention to the unsafe cast.
> How is that substantially different from my example?

There are different sources of memory unsafety. One rule is that (roughly) when memory is dereferenced as a type it should contain an initialized value of that type (or related type). As long as users follow this very intuitive rule, and don’t force-cast into a typed pointer, then the raw pointer API is very safe!

Another source of memory unsafety is strict aliasing. This is not intuitive.

I am showing an example in which the memory location is never accessed using a type that is inconsistent with its initialized value. There is nothing wrong with the order and type of the initialization, access, deinitialization operations, so the user has followed the first, intuitive rule. The only thing wrong with my example is that the initialization uses a typed pointer. You can’t have two initializations of the same memory both using a typed pointer but with unrelated types.

All of this presumes that `A` and `B` are layout compatible (same alignment and size).

It’s true that the user could run into the same problem if they obtain a typed pointer via unsafeBitCast or rawPtr.load(UnsafePointer<T>). But we don’t want to provide an API explicitly for converting raw pointers into typed pointers without indicating that it opens up a new safety hole…

The *safe* way to get a typed pointer is this:
  let ptrA = rawPtr.initialize(A.self, with: A())

If you’re getting a typed pointer any other way then you need to be aware of the strict aliasing rules, which I suspect most users will not want to think about.

So, UnsafeRawPointer.cast(to: UnsafePointer<T>.Type) is *not* as safe as any other operation because it opens up a very subtle issue involving strict aliasing.

-Andy

> 
>> I considered naming the cast `UnsafeRawPointer.bind<T>(to: T.Type)` to indicate that the allocated memory is being bound to a type for the entire duration of its allocation. But it's actually the call to `initialize` a typed pointer that binds the type.
>> 
>> -Andy

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160627/057c7f0c/attachment.html>


More information about the swift-evolution mailing list