June 13, 2019
On Thursday, June 13, 2019 12:00:46 PM MDT Ola Fosheim Grøstad via Digitalmars-d wrote:
> On Thursday, 13 June 2019 at 17:39:56 UTC, Jonathan M Davis wrote:
> > As I understand it, a real problem related to this that exists right now is that llvm considers dereferencing null to be undefined behavior.
>
> Not really. Clang might.
>
> You should be able to control this by how you set up the optimizer:
>
> http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html

Johan Engelen (one of the LDC developers) has specifically brought this up as an issue before. It may very well be possible for the LDC developers to make LLVM behave differently, but as I understand it, dereferencing null is currently considered undefined behavior by llvm when ldc has it optimize code and that that can result in non-@safe befavior in @safe code.

- Jonathan M Davis




June 13, 2019
On Thursday, 13 June 2019 at 19:51:12 UTC, Timon Gehr wrote:
> On 13.06.19 21:44, Walter Bright wrote:
>> On 6/13/2019 4:30 AM, Timon Gehr wrote:
>>> Yes. IMHO this shouldn't be a @safe pure operation.
>> 
>> D regards converting a pointer to an int as safe, but not the other way around.
>
> I know, and that is fine, but casting a pointer to int is not pure. It glances into the global state maintained by the memory allocator.

The operation is pure. Accessing global state is the part that is not pure. For convience I guess you can allocate memory, otherwise it wouldn't be that useful. Pure for the most part guarantees you don't access global variables. Pure doesn't mean deterministic.
June 13, 2019
On 13.06.19 09:27, Walter Bright wrote:
> On 6/11/2019 7:37 AM, ag0aep6g wrote:
>> The spec very much defines @safe as "no undefined behavior".
>>
>> <https://dlang.org/spec/function.html#function-safety>: "Safe functions are functions that are statically checked to exhibit no possibility of undefined behavior."
> 
> The spec's wrong, because it doesn't do that.

Memory safety implies no undefined behavior. The only way the spec can be wrong [1] is if you say that corrupting memory has defined behavior in D, in which case the spec would be too weak, and not too strong like you are implying. Otherwise, "memory safe" and "no undefined behavior" are equivalent.


[1] Assuming here that we accept that @safe successfully protects against memory corruption, ignoring assumptions that need to be made on @trusted functions.
June 13, 2019
On Thursday, 13 June 2019 at 19:21:53 UTC, Tim wrote:
> On Tuesday, 11 June 2019 at 00:30:27 UTC, Walter Bright wrote:
>> If you want @safe to mean "no undefined behavior", that is a valid opinion, but that is not what @safe in D is currently defined as. It is currently defined as "memory safe". If you can find a case where an int with garbage in it can cause memory corruption in @safe code, that would indeed be a bug in D.
>
> The following code causes memory corruption, because a bool has garbage in it.
>
> import std.stdio;
>
> // Returns newly allocated data with increased value if b is true.
> // Returns same data otherwise.
> const(int)* test(immutable int *data, bool b) @trusted
> {
>     int *data2;
>     if(!b)
>     {
>         writeln("b seems to be false");
>         // The cast can be trusted, because data2 will only be modified if b is true.
>         data2 = cast(int*)data;
>     }
>     else
>     {
>         writeln("b seems to be true");
>         data2 = new int(*data);
>     }
>
>     if(b)
>     {
>         writeln("b seems to be true");
>         *data2 += 1;
>     }
>     return data2;
> }
>
> void main() @safe
> {
>     bool b = void;
>     immutable int *data = new immutable int(1);
>     writeln("data before: ", *data);
>     const int *data2 = test(data, b);
>     writeln("data after: ", *data);
>     writeln("data2 after: ", *data2);
> }
>
> After compiling it with dmd and running it I get the following output:
> data before: 1
> b seems to be false
> b seems to be true
> data after: 2
> data2 after: 2
>
> The immutable integer pointed to by data is modified. The function test is @trusted, but only modifies the immutable data, because b seems to be true and false at the same time.
> Normally a bool is internally 0 or 1 and the compiler can assume that. If b has another value and !b is implemented as ~b, then b and !b can both be true.
>
> This means, that uninitialized data can result in memory corruption.

This problem happens because you are used @trusted. If you used @safe you wouldn't be able to increment pointers and modify the values the way you did in @trusted.
June 13, 2019
On 13.06.19 22:20, Exil wrote:
> On Thursday, 13 June 2019 at 19:51:12 UTC, Timon Gehr wrote:
>> On 13.06.19 21:44, Walter Bright wrote:
>>> On 6/13/2019 4:30 AM, Timon Gehr wrote:
>>>> Yes. IMHO this shouldn't be a @safe pure operation.
>>>
>>> D regards converting a pointer to an int as safe, but not the other way around.
>>
>> I know, and that is fine, but casting a pointer to int is not pure. It glances into the global state maintained by the memory allocator.
> 
> The operation is pure.

No.

> Accessing global state is the part that is not pure. For convience I guess you can allocate memory, ...

It's an _abstraction_. You allocate because you need memory to represent your values, not because you want to know at which address your values will be located. The former is a necessity, the latter is not pure.

Purely functional programming languages also allocate memory at runtime, but it is hidden in the runtime system and kept separate from the language definition. In D, user code and runtime system can both be in the same code base, but the runtime system parts need to be in impure or pure @system code, not in your pure code.

> ... > Pure for the most part guarantees you don't
> access global variables.

No, it does not. It guarantees you don't read or write implicit state (i.e. state not accessible through the function arguments).

> Pure doesn't mean deterministic.

Yes, it does. A function can only fail to be deterministic if it accesses implicit state that changes between function invocations.
June 13, 2019
On Thursday, 13 June 2019 at 20:55:34 UTC, Exil wrote:
> This problem happens because you are used @trusted. If you used @safe you wouldn't be able to increment pointers and modify the values the way you did in @trusted.

The @trusted function behaves correctly if it receives valid input. The memory corruption only happens, because the @safe function passes a value, that is not expected by the compiler.
June 13, 2019
On 13.06.19 22:55, Exil wrote:
> On Thursday, 13 June 2019 at 19:21:53 UTC, Tim wrote:
>> On Tuesday, 11 June 2019 at 00:30:27 UTC, Walter Bright wrote:
>>> If you want @safe to mean "no undefined behavior", that is a valid opinion, but that is not what @safe in D is currently defined as. It is currently defined as "memory safe". If you can find a case where an int with garbage in it can cause memory corruption in @safe code, that would indeed be a bug in D.
>>
>> The following code causes memory corruption, because a bool has garbage in it.
>>
>> import std.stdio;
>>
>> // Returns newly allocated data with increased value if b is true.
>> // Returns same data otherwise.
>> const(int)* test(immutable int *data, bool b) @trusted
>> {
>>     int *data2;
>>     if(!b)
>>     {
>>         writeln("b seems to be false");
>>         // The cast can be trusted, because data2 will only be modified if b is true.
>>         data2 = cast(int*)data;
>>     }
>>     else
>>     {
>>         writeln("b seems to be true");
>>         data2 = new int(*data);
>>     }
>>
>>     if(b)
>>     {
>>         writeln("b seems to be true");
>>         *data2 += 1;
>>     }
>>     return data2;
>> }
>>
>> void main() @safe
>> {
>>     bool b = void;
>>     immutable int *data = new immutable int(1);
>>     writeln("data before: ", *data);
>>     const int *data2 = test(data, b);
>>     writeln("data after: ", *data);
>>     writeln("data2 after: ", *data2);
>> }
>>
>> After compiling it with dmd and running it I get the following output:
>> data before: 1
>> b seems to be false
>> b seems to be true
>> data after: 2
>> data2 after: 2
>>
>> The immutable integer pointed to by data is modified. The function test is @trusted, but only modifies the immutable data, because b seems to be true and false at the same time.
>> Normally a bool is internally 0 or 1 and the compiler can assume that. If b has another value and !b is implemented as ~b, then b and !b can both be true.
>>
>> This means, that uninitialized data can result in memory corruption.
> 
> This problem happens because you are used @trusted. If you used @safe you wouldn't be able to increment pointers and modify the values the way you did in @trusted.

What that code illustrates is that `void` initialization leads to boolean values that are both `true` and `false` at the same time.

The @trusted function in the example can't do anything bad assuming that `b` is either true or false. If `void` initialization is @safe, @trusted functions can no longer assume that boolean parameters cannot be `true` and `false` at the same time. What's more, neither can @safe functions.

In my book, an optimizer that can't elide the `if(b)` in the above code is a bad optimizer. Therefore either `void` initialization should not be @safe, or `void` initialization of a bool should be guaranteed to result in a value that is either true or false, but not both.
June 13, 2019
On 13.06.19 23:10, Tim wrote:
> On Thursday, 13 June 2019 at 20:55:34 UTC, Exil wrote:
>> This problem happens because you are used @trusted. If you used @safe you wouldn't be able to increment pointers and modify the values the way you did in @trusted.
> 
> The @trusted function behaves correctly if it receives valid input. The memory corruption only happens, because the @safe function passes a value, that is not expected by the compiler.

Basically, it could be argued that `void` initialization of a `bool` variable is already memory corruption. It's indistinguishable from someone holding a dangling `char*` to your `bool` memory and then writing a value other than '\0' or '\xff'.
June 13, 2019
On 13.06.19 21:21, Tim wrote:
> import std.stdio;
> 
> // Returns newly allocated data with increased value if b is true.
> // Returns same data otherwise.
> const(int)* test(immutable int *data, bool b) @trusted
> {
>      int *data2;
>      if(!b)
>      {
>          writeln("b seems to be false");
>          // The cast can be trusted, because data2 will only be modified if b is true.
>          data2 = cast(int*)data;
>      }
>      else
>      {
>          writeln("b seems to be true");
>          data2 = new int(*data);
>      }
> 
>      if(b)
>      {
>          writeln("b seems to be true");
>          *data2 += 1;
>      }
>      return data2;
> }
> 
> void main() @safe
> {
>      bool b = void;
>      immutable int *data = new immutable int(1);
>      writeln("data before: ", *data);
>      const int *data2 = test(data, b);
>      writeln("data after: ", *data);
>      writeln("data2 after: ", *data2);
> }

Nice one!

It's cute how bool, of all things, shares a safety-critical property with pointers: not all bytes make valid bools/pointers.
June 13, 2019
On Thursday, 13 June 2019 at 20:55:34 UTC, Exil wrote:
> This problem happens because you are used @trusted. If you used @safe you wouldn't be able to increment pointers and modify the values the way you did in @trusted.

Here is a completly @safe version:

import std.stdio;

static int[2] data;
static int[253] data2;

void test(bool b) @safe
{
	data[b]++;
}

void main() @safe
{
	bool b = void;
	writeln(data, data2);
	test(b);
	writeln(data, data2);	
}

If b is valid only data can change. But for me data2 changes, even though it is never written to.