[swift-evolution] [Draft] UnsafeRawPointer API

Dave Abrahams dabrahams at apple.com
Tue Jun 28 00:11:31 CDT 2016


on Mon Jun 27 2016, Andrew Trick <atrick-AT-apple.com> wrote:

>> 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.

Yes of course.  The point was that rawPtr points to uninitialized
memory.

> 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!

I still maintain that using cast to get an UnsafePointer that you'll
dereference is no less safe than using load to get an UnsafePointer that
you'll dereference.

> 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.

IMO load is even worse, because there might not even be a valid pointer
of any kind in the address you're loading.  I just don't see a
fundamental distinction here.

-- 
Dave


More information about the swift-evolution mailing list