December 14, 2019
On Saturday, 14 December 2019 at 18:46:38 UTC, Dennis wrote:
>     void* v2 = cast(void*) new C(); // not allowed, not a

Why not? Holding the door open for memory compaction on the GC heap?

December 14, 2019
On Saturday, 14 December 2019 at 19:18:58 UTC, Ola Fosheim Grøstad wrote:
> Why not? Holding the door open for memory compaction on the GC heap?

I don't know, I suggest allowing it (unless I'm missing something).
December 14, 2019
On Saturday, 14 December 2019 at 19:16:08 UTC, Joseph Rushton Wakeling wrote:
> Indeed.  Has anything I said suggested otherwise?

My bad, I thought you were implying that it shouldn't be @safe because it isn't useful.

> That's relevant to this discussion, because when asking "Should the cast to `void*` be @safe?" we are really asking, "Is it possible for the compiler to decide this without needing the developer to verify?"

The compiler should only be concerned with verifying what it knows. E.g. if a `bool*` reaches `@safe` code, it must be null or point to a mutable 0 or 1 value. I don't think adding any additional rules is helpful. You might also say "casting ubyte[] to char[] should be unsafe because the compiler doesn't know whether the developer's string functions exhibit undefined behavior on invalid unicode", but if the developer relies on correct unicode like that, his functions are incorrectly marked @trusted. If you want that guarantee, you should not use char[] but make your own struct type that ensures that.

A void* is an opaque pointer type. If I start writing a function like:
```
void setZero(void* ptr) @trusted {

}
```
I simply can't assume anything about ptr other than that it is a valid `void*`.

It does raise the question what can be assumed about a `void*`.
You could say that a void* must point to at least 1 byte of mutable memory, such that this function is correctly @trusted:

```
void setZero(void* ptr) @trusted {
    if (ptr) {
        *(cast(ubyte) ptr) = 0;
    }
}
```

However, I'd argue a void* is even more opaque than that, since it's not uncommon to use them as generic 'handles' in C libraries, such as HMODULE in the Windows API:
https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress

> we are creating opportunities for unsafe memory access that didn't exist previously.

I doubt that. If you have a code example, I'm very interested. I bet that any @trusted code that can be broken by allowing cast(void*) in @safe code can also be broken without that.

> No, that won't do.  What if you cast from a `ulong` to a `void*`?

That is `@safe`, unless there is a way to corrupt memory in `@safe` code by doing that.


December 14, 2019
On Saturday, 14 December 2019 at 20:53:49 UTC, Dennis wrote:
>> No, that won't do.  What if you cast from a `ulong` to a `void*`?
>
> That is `@safe`, unless there is a way to corrupt memory in `@safe` code by doing that.

No, it is not @safe, and for good reason.  When you cast an integral value to a `void*` that value gets reinterpreted as a memory address.  But you have absolutely no right to assume that it is a valid memory address.

    ulong u = 8;
    auto v = cast(void*) u;

... is totally unsafe, and the compiler rightly rejects it if you try to do that in a code block marked @safe.  But you shouldn't need the compiler to tell you to know that this is a really messed up thing to do.  How do you know that memory address 8 is in any way valid?

Things like this are WHY the spec has the rule that one cannot cast from a non-pointer type to `void*` in code marked @safe.
December 15, 2019
On Saturday, 14 December 2019 at 23:48:38 UTC, Joseph Rushton Wakeling wrote:
>     ulong u = 8;
>     auto v = cast(void*) u;
>
> ... is totally unsafe, and the compiler rightly rejects it if you try to do that in a code block marked @safe.

It is not type safe, but it is memory safe.

void has zero extent, so it will never be dereferenced without an unsafe action.

It is no different from having a pointer to a zero-length array, or a zero-length slice. Allocators will often allocate 1 byte if asked to allocate 0 though, to avoid such typing issues. I.e. to allow you to obtain unique identities with zero extent.

So it is problematic, but not because of void*. It is only problematic because other code is unsafe.

December 15, 2019
On Sunday, 15 December 2019 at 07:40:41 UTC, Ola Fosheim Grøstad wrote:
> On Saturday, 14 December 2019 at 23:48:38 UTC, Joseph Rushton Wakeling wrote:
>>     ulong u = 8;
>>     auto v = cast(void*) u;
>>
>> ... is totally unsafe, and the compiler rightly rejects it if you try to do that in a code block marked @safe.
>
> It is not type safe, but it is memory safe.
>
> void has zero extent, so it will never be dereferenced without an unsafe action.

Which brings us back to the start of the discussion: @safe checks are not just about the strictest definition of memory safety, but also about actions that open up a path to unsafe behaviour unless they are carefully validated.

You may not accept the principle but that is the reality of the spec.

> It is no different from having a pointer to a zero-length array, or a zero-length slice. Allocators will often allocate 1 byte if asked to allocate 0 though, to avoid such typing issues. I.e. to allow you to obtain unique identities with zero extent.

I'm not sure that's true. The pointer of a zero-length array should be guaranteed to be `null` or to point to a valid section of GC memory. An arbitrary integral value reinterpreted as a memory address has no such guarantee.

BTW, note that the array/slice `.ptr` property is not @safe, precisely because it opens the door to looking into a buffer of memory that one is not supposed to have access to.

> So it is problematic, but not because of void*. It is only problematic because other code is unsafe.

These are not the criteria that @safe operates by. But FWIW in these cases you're right that there's nothing magical about `void*`. It's the casting of a non-pointer type to ANY pointer type that is considered a violation of @safe.


December 15, 2019
BTW, note that the spec defines a @safe function as one that has been statically checked to exhibit no undefined behaviour.

Casting from an non-pointer type to a pointer seems a pretty good example of something that makes it impossible to statically confirm that no undefined behaviour is taking place.
December 15, 2019
On 15.12.19 10:32, Joseph Rushton Wakeling wrote:
> BTW, note that the spec defines a @safe function as one that has been statically checked to exhibit no undefined behaviour.
> 
> Casting from an non-pointer type to a pointer seems a pretty good example of something that makes it impossible to statically confirm that no undefined behaviour is taking place.

The other side is saying it's possible for void*. It goes like this:

1) By itself, an invalid pointer doesn't exhibit UB.
2) Dereferencing an invalid pointer does exhibit UB.
3) There is no other way to trigger UB with an invalid pointer.
4) Dereferencing void* isn't allowed in @safe code.

Conclusion: An invalid void* cannot lead to UB in @safe code. So casting anything to void* can be allowed there.

I'm pretty sure that sentences 1, 2, and 4 are correct. Number 3 seems to be the interesting one. A counter-example (using an invalid pointer to trigger UB without dereferencing the pointer) would shut the argument down.
December 15, 2019
On Sunday, 15 December 2019 at 09:32:12 UTC, Joseph Rushton Wakeling wrote:
> BTW, note that the spec defines a @safe function as one that has been statically checked to exhibit no undefined behaviour.
>
> Casting from an non-pointer type to a pointer seems a pretty good example of something that makes it impossible to statically confirm that no undefined behaviour is taking place.

Not impossible?

It only becomes a problem if @trusted code do unsafe things, like taking *void pointers that it intends to dereference.

You just need to show that *void pointers that @safe code can write to are not dereferenced.

December 15, 2019
On Saturday, 14 December 2019 at 23:48:38 UTC, Joseph Rushton Wakeling wrote:
>     ulong u = 8;
>     auto v = cast(void*) u;
> ... is totally unsafe, and the compiler rightly rejects it if you try to do that in a code block marked @safe.

It is not sufficient for causing memory corruption in @safe code.

> But you shouldn't need the compiler to tell you to know that this is a really messed up thing to do.

If "messed up" means that it can cause memory corruption in @safe code, then you haven't demonstrated that yet.
It "messed up" means that it is a bad practice, then we are back to "@safe is about eliminating memory corruption possibilities, not bug free / good practice / useful".

> How do you know that memory address 8 is in any way valid?

You don't. That's the point of void*, you cannot safely cast it to any pointer. You can safely cast it to an integer and use that, or pass it to @safe functions that use it safely.

```
import std.stdio: writeln;
void main() {
    import core.sys.windows.winbase: CreateSemaphoreW, CloseHandle;

    void* fromMe = cast(void*) 516; // currently not @safe, but should be
    void* fromC  = CreateSemaphoreW(null, 8, 8, null);

    writeln("fromMe: ", cast(size_t) fromMe); // 516
    writeln("fromC:  ", cast(size_t) fromC);  // could be 516 as well

    CloseHandle(fromMe); // likely won't succeed, but won't corrupt memory
    CloseHandle(fromC);

    badTrusted(fromMe); // NO!
    badTrusted(fromC); // NO! But still possible when disallowing @safe cast(void*)
}

void badTrusted(void* ptr) @trusted {
    writeln(*(cast(ubyte*) ptr)); // this is simply bad
}

```