June 17, 2021

On Thursday, 17 June 2021 at 19:06:31 UTC, Paul Backus wrote:

>

In order for get to have a safe interface, it must not be possible to call it from @safe code with an instance that has offset >= 2. Because of the bug in size, it is possible for @safe code to call get with such an instance. Therefore, get does not have a safe interface.

Yes, but if I make size() @trusted and fix the bug then interface is provably safe?

class A {

    this() @trusted {
        ptr = &buffer[0];
        offset = 0;
    }

    int get() @trusted { return ptr[offset]; }
    void set(int i) @trusted { this.offset = i&1; }
    int size() @trusted { return 2;}
private:
    int[2] buffer;
    int* ptr;
    int offset;
}



  Also, if I do this, it is probably safe, because of the invariant that is checked?

class A {

this() @trusted {
    ptr = &buffer[0];
    offset = 0;
}

int get() @trusted { return ptr[offset]; }
void set(int i) @trusted { this.offset = i&1; }
int size()@safe{ offset=2; return 2;}
invariant{ assert(0<= offset && offset <=1 ); }

private:
int[2] buffer;
int* ptr;
int offset;
}

June 17, 2021

On Thursday, 17 June 2021 at 20:25:22 UTC, Ola Fosheim Grøstad wrote:

>

On Thursday, 17 June 2021 at 19:06:31 UTC, Paul Backus wrote:

>

In order for get to have a safe interface, it must not be possible to call it from @safe code with an instance that has offset >= 2. Because of the bug in size, it is possible for @safe code to call get with such an instance. Therefore, get does not have a safe interface.

Yes, but if I make size() @trusted and fix the bug then interface is provably safe?

Assuming issue 20941 is fixed, yes.

>

Also, if I do this, it is probably safe, because of the invariant that is checked?

[...]

>
    invariant{ assert(0<= offset && offset <=1 ); }

Yes.

June 17, 2021
On Thursday, 17 June 2021 at 19:25:51 UTC, ag0aep6g wrote:
> On 17.06.21 20:46, Ola Fosheim Grøstad wrote:
>> What about it isn't safe? It is provably safe? Meaning, I can do a formal verification of it as being safe!?
>
> `offset` is an input to `get` (via `this`). `offset` is an int, so all possible values (int.min through int.max) are considered "safe values". `get` exhibits undefined behavior when `offset` is greater than 1. A function that can exhibit undefined behavior when called with only safe values cannot be @trusted.

But this.offset can provably only hold the value 0 or 1. What is the point of manually auditing @trusted if one impose arbitrary requirements like these? So I am basically forced to use a bool to represent offset for it to be considered safe?

One should start by defining invariants that will keep the class safe.

Then one should audit all methods with respect to the invariants.

The invariant is that this.offset cannot hold a value different from 0 and 1. And it is easy to prove.

(I asssume here that all methods are marked as @trusted)

June 17, 2021

On Thursday, 17 June 2021 at 20:33:33 UTC, Paul Backus wrote:

>

Assuming [issue 20941][1] is fixed, yes.

[…]

>

Yes.

Thanks.

There seems to be many interpretations of what safe means though. Right now, safe interfacing with C seems like opening Pandora's box.

Probably a good idea to write up a set of best practice rules for making safe interfacing with C libraries (with examples).

June 17, 2021
On Thursday, 17 June 2021 at 20:37:11 UTC, Ola Fosheim Grøstad wrote:
> But this.offset can provably only hold the value 0 or 1.

You mean if it holds a different value, then the program becomes invalid? Sure, that's easy to prove. Do you expect the compiler to do that proof, and then give you an error when you violate the invariant? That's not at all how D works.

> What is the point of manually auditing @trusted if one impose arbitrary requirements like these?

The point of manually auditing @trusted is to ensure that the function actually follows the requirements. If you don't want to bother making a function's interface safe, mark it @system.

> So I am basically forced to use a bool to represent offset for it to be considered safe?

That might work (haven't given it much thought). What you're supposed to do is wait for DIP 1035, or recreate its @system variables in a library. What people actually do is ignore the rules and live the dangerous lifes of safety outlaws.

> One should start by defining invariants that will keep the class safe.
>
> Then one should audit all methods with respect to the invariants.

You can do that with @system. @safe and @trusted won't help enforcing your custom invariants.
June 17, 2021

On Thursday, 17 June 2021 at 20:42:20 UTC, Ola Fosheim Grøstad wrote:

>

On Thursday, 17 June 2021 at 20:33:33 UTC, Paul Backus wrote:

>

Assuming issue 20941 is fixed, yes.

[…]

>

Yes.

Thanks.

There seems to be many interpretations of what safe means though. Right now, safe interfacing with C seems like opening Pandora's box.

Probably a good idea to write up a set of best practice rules for making safe interfacing with C libraries (with examples).

A lot of people on the D forums have an incomplete or incorrect understanding of what memory safety means, and how D's @safe, @trusted and @system attributes can be used to help prove a program memory-safe. The interpretation that I and ag0aep6g have been describing is the correct one.

Re: interfacing with C, the best guarantee you can reasonably hope to achieve is "my @trusted code is memory safe as long as the C functions I'm calling behave as specified in the relevant documentation or standard." I go into more detail about this in my blog post on memory safety in D.

June 17, 2021

On Thursday, 17 June 2021 at 20:57:27 UTC, ag0aep6g wrote:

>

You mean if it holds a different value, then the program becomes invalid? Sure, that's easy to prove. Do you expect the compiler to do that proof, and then give you an error when you violate the invariant? That's not at all how D works.

I mean that this should satisfy the requirements (fixed a bug that allowed ptr to be changed after construction):

class A {

    this() @trusted {
        ptr = &buffer[0];
        offset = 0;
    }

    int get() @trusted { return ptr[offset]; }
    void set(int i) @trusted { this.offset = i&1; }
    int size()@trusted{ return 2;}
private:
    int[2] buffer;
    const(int*) ptr;
    int offset;
}
  1. There is no way for @safe code to make this unsafe.

  2. I have through audit proven that all methods keep offset within the required range.

>

The point of manually auditing @trusted is to ensure that the function actually follows the requirements.

Then the requirements need to be made more explicit, because people seem to disagree as to what is required.

>

variables in a library. What people actually do is ignore the rules and live the dangerous lifes of safety outlaws.

No, that is not what I want. I want to define the necessary invariants to uphold safety. Then prove that my class remains within the constraints.

Yes, preventing @safe code from writing to some variables might make that task easier. In the meanwhile I can just make all methods @trusted.

>

You can do that with @system. @safe and @trusted won't help enforcing your custom invariants.

But @trusted requires you to manually audit the invariants needed, so why force a weakening of the invariants that are needed to make it safe? That makes no sense, if you already are required to conduct a manual audit. The compiler has no say in this either way.

People will do it, and they will be correct and sensible for doing so. By requiring an audit we already presume that the auditor has the required skillset to reason about these issues at a formal level!

June 17, 2021

On Thursday, 17 June 2021 at 21:00:13 UTC, Paul Backus wrote:

>

On Thursday, 17 June 2021 at 20:42:20 UTC, Ola Fosheim Grøstad wrote:

>

On Thursday, 17 June 2021 at 20:33:33 UTC, Paul Backus wrote:

>

Assuming [issue 20941][1] is fixed, yes.

[…]

>

Yes.
[...]
The interpretation that I and ag0aep6g have been describing is the correct one.

Yet I would answer "no" where you answered "yes" above.

The question was: "Yes, but if I make size() @trusted and fix the bug then interface is provably safe?".

The corresponding code:

class A {

    this() @trusted {
        ptr = &buffer[0];
        offset = 0;
    }

    int get() @trusted { return ptr[offset]; }
    void set(int i) @trusted { this.offset = i&1; }
    int size() @trusted { return 2;}
private:
    int[2] buffer;
    int* ptr;
    int offset;
}

In my opinion, that code is fundamentally equivalent to this (regarding the safety of get):

int get(int* ptr, int offset) @trusted { return ptr[offset]; }

That function does not have a safe interface, because it exhibits undefined behavior wenn called like get(new int, 1000), which @safe code can do.

private, other methods, the constructor - those things don't matter.

June 17, 2021

On Wednesday, 16 June 2021 at 23:52:02 UTC, Bruce Carneal wrote:

>

If I understand your meaning here, I disagree. I think @safe/@trusted is very useful, essential even, in code bases that are changing. It is a demarcation tool that lets us carve out ever larger safe areas.

Ok, I think I understand better what you meant now.

So, essentially, one is currently forced to make perfectly @safe code @trusted because if there is any possibility that a @safe method contains a bug (even if 100% unlikely) that affects assumptions made in @trusted code then that @safe method can no longer be considered safe.

So basically one wants @trusted code to be checked by the compiler just like @safe, and then instead explicitly turn off the checking in a more narrow unsafe region within the @trusted method.

Unless pending DIPs turn out to remove this issue completely. Time will show, I guess.

June 17, 2021

On Thursday, 17 June 2021 at 21:00:13 UTC, Paul Backus wrote:

>

A lot of people on the D forums have an incomplete or incorrect understanding of what memory safety means, and how D's @safe, @trusted and @system attributes can be used to help prove a program memory-safe. The interpretation that I and ag0aep6g have been describing is the correct one.

There is a difference between proving a program memory safe and having to assume that all @safe code is maximally broken. Which is what is required here.

Also, ag0aep6g are not happy with proving that the class is memory safe. It has to be memory safe when non-pointer values are overwritten with garbage. He wants you to look at each @trusted method in isolation, not the class as a whole.

>

Re: interfacing with C, the best guarantee you can reasonably hope to achieve is "my @trusted code is memory safe as long as the C functions I'm calling behave as specified in the relevant documentation or standard."

More tricky that that I think. If I obtain a DOM node from a C library, then that DOM node points to the parent node, and so on, effectively I could have the entire C heap in my hands.

You basically either have to create slow wrappers around everything or manually modify the C-bindings so you give less information to the D compiler than the C compiler has (basically censor out back pointers from the struct)??

Or maybe not. Either way, it is not trivial to reason about the consequences that you get from obtaining not just objects from C-code, but nodes in a connected graph.

I haven't tried to create such safe bindings, maybe I am wrong. Maybe there are some easy patterns one can follow.