[swift-evolution] [Draft] UnsafeRawPointer API
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())
>>>>> 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
> 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())
>>> ptrB = rawPtr.initialize(with: B())
>>> The following code is undefined:
>>> ptrA = rawPtr.cast(to: UnsafePointer<A>.self)
>>> ptrA.initialize(with: A())
>>> 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
> 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.
More information about the swift-evolution