> On Jun 27, 2016, at 7:15 PM, Dave Abrahams <[email protected]> wrote:
> 
> 
> 
> Sent from my moss-covered three-handled family gradunza
> 
> On Jun 27, 2016, at 4:27 PM, Andrew Trick <[email protected] 
> <mailto:[email protected]>> wrote:
> 
>> 
>>> On Jun 27, 2016, at 3:35 PM, Dave Abrahams <[email protected] 
>>> <mailto:[email protected]>> 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

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to