June 18, 2021
On 18/06/2021 1:54 AM, ag0aep6g wrote:
>> Ok, but it is not _realistic_ to think that D users will not write code that they think is _good enough_ for their purpose. Since there is no way to verify that they  adhere to idealistic principles, it won't happen.
> 
> I think there's almost a consensus that @trusted isn't quite good enough, exactly because no one can be bothered to use it as intended. That's why the incremental improvements mentioned above are being worked on. I don't think anyone is working on redesigning (or reinterpreting) the whole thing from the ground up. And I would expect strong push-back from Walter if someone tried.

What might be a good thing to have is make the compiler able to produce an audit report for @trusted code. Along with a list of all @trusted symbols in source code (not generated).

You can diff the list to see what symbols are added, and ensure somebody audits it at PR time with the help of the CI.
June 17, 2021
On 6/16/21 9:07 PM, Paul Backus wrote:
> On Thursday, 17 June 2021 at 00:34:12 UTC, Steven Schveighoffer wrote:
>> On 6/16/21 5:32 PM, Paul Backus wrote:
>>> On Wednesday, 16 June 2021 at 21:26:08 UTC, Bruce Carneal wrote:
>>>> I like the notion that others have mentioned of @safe checking by default within @trusted code (which would require @system blocks to disable checking).  Perhaps we could adopt an opt-in strategy where such @safe checking is triggered by the presence of an @system block.
>>>
>>> Under this proposal, @system lambdas/blocks within @trusted code would have the exact same semantics as @trusted blocks/lambdas within @safe code currently do. It's pure bikeshedding.
>>
>> Yes, and that leaves @safe code to actually not require manual checking, as opposed to today, where any @safe code with @trusted blocks today requires manual checking of all the @safe code (I agree just changing trusted/system code this way, and doing nothing with @safe would be bikeshedding).
>>
>> In reality, @safe code should be a function of its inputs, and what is considered a safe input. With @trusted lambdas, the inputs are "everything".
> 
> It's impossible to guarantee, at the language level, that @safe code can never require manual review. The programmer is allowed to use any and all knowledge at their disposal to verify the memory safety of @trusted (or in your proposal, @system-block) code, including knowledge about @safe code.

The goal is to guarantee that *as long as* your @trusted functions and blocks have a @safe interface, then @safe code does not need to be checked. When I say "not require review" I mean "I have checked all the @trusted code, and it has a sound @safe interface, so all @safe code that may call it have no need for review." We will never have a marking that is language-guaranteed to not require review.

To put it another way, as long as you aren't using @trusted escapes that leak implementation details, your @safe code shouldn't need a review. The problem is that trusted lambdas are not only the norm, it's actually required, due to template inference.

Right now, a @safe function can only be "assumed safe" as long as there are no @trusted blocks in it. Once there is one trusted block, then you have to review the whole function. The same thing goes for data invariants (though that spreads to the whole module instead).

Not having to review code for memory safety is supposed to be the major point of @safe.

> You might say, "the only thing a @trusted function can possibly know about a @safe function is its signature, so that doesn't matter," but that's not quite true. If the @trusted function and the @safe function are in the same module, the @trusted function can (in principle) rely on the inner workings of the safe function without invalidating its proof of memory safety, since the programmer knows that any given version of the @trusted function will only ever call the corresponding version of the @safe function.

A truly "correct" @trusted function should defensively be callable and provide appropriate return data for any possible @safe interface. A trusted escape accepts as a parameter "everything" from the outer function, and returns "everything". Which means the @safe function can add or subtract *at will* any parameters of any type or return values that it wants. This just isn't reviewable separately.

We might as well call a spade a spade.

> Of course, such a @trusted function should never pass code review. But the fact that it can exist in principle means that you cannot truly rely on @safe to mean "no manual checking required", even if @trusted lambdas and nested functions are forbidden.

If D had a hard requirement (enforced by having undefined behavior threats) that @trusted functions should always have a safe interface, then you could say that @safe code needs no review. Limiting what a @trusted function can do helps the ability to verify that the interface is indeed safe.

This is all kind of moot though, I don't see any change to the safe regime happening. The best we might get is DIP1035.

-Steve
June 17, 2021

On Thursday, 17 June 2021 at 12:52:40 UTC, Ola Fosheim Grøstad wrote:

>

My take on this is that interfacing with C/C++ undermines @safe to such an extent that C/C++ interop isn't really as big of a selling point as it is made out to be (meaning you have to choose either @safe or C/C++ interop). I think that is a problem. If you have two big features then you shouldn't have to choose. The conception of @safe has to work well for people who write large application with lots of C/C++ interop.

No language can do this. C++ API does not provide any safety guarantees, so calling a C++ function means that it needs to be manually verified, or it's authors trusted, BY DEFINITION.

The only way around this would be to implement an automatic safety checker on C++ side. If it has to work out of the box on most real-world code, I don't think we will see such a complex checker in decades.

I suspect you're trying to say that because of the above, we would have to conclude that good C++ interop and memory safety guarantees should never be mixed in one language, D or otherwise.

If that's the case, the only conclusion I can draw is that D philosophy is fundamentally wrong from your point of view. D is all about letting the programmer pick the paradigm according to the situation, instead of being designed for just one of them. This philosophy is rooted so deep that if it proves to be just plain wrong, were best off to just ditch D and switch to other languages.

I sure hope that won't happen.

June 17, 2021
On Thursday, 17 June 2021 at 14:30:58 UTC, Steven Schveighoffer wrote:
> [snip]
> The goal is to guarantee that *as long as* your @trusted functions and blocks have a @safe interface, then @safe code does not need to be checked. When I say "not require review" I mean "I have checked all the @trusted code, and it has a sound @safe interface, so all @safe code that may call it have no need for review." We will never have a marking that is language-guaranteed to not require review.
>

I think I've suggested before a @safe-strict, or something like that, that is the same as @safe but only allows calling @safe-strict functions (@safe/@trusted/@system functions can all call a @safe-strict function). The idea would be that those shouldn't need to be reviewed for the issues that @safe attempts to address.

You could then have @safe-strict blocks to complement the @system blocks you discussed previously (I've come along to the idea that @trusted blocks is a bad idea, you really just need these two). The idea would be that the @safe-strict blocks within a @safe/@trusted/@system function would be mechanically checked and since they do not call any @safe/@trusted/@system not need to be reviewed. Of course, one might argue that this results in mixing up @system code with @safe-strict code. However, it also helps separate out the safer parts of unsafe code. For instance, this approach means that you have a backwards compatible way to have the code in a trusted function that is not in a system block be checked manually (see below, @system block would be optional).

```d
@trusted void foo()
{
    @safe-strict {
        //mechanically checked code that can't call any @safe/@trusted/@system functions
    }
    //system code
}
```
June 17, 2021
On 2021-06-16 17:22, Walter Bright wrote:
> On 6/16/2021 6:09 AM, Sönke Ludwig wrote:
>> There are 800 of these in vibe.d alone.
> 
> That is concerning. But it isn't necessarily cause for redesigning @trusted. For example, I removed (in aggregate) a great deal of unsafe allocation code from the backend simply by moving all that code into one resizable array abstraction.
> 
> Piece by piece, I've been removing the unsafe code from the backend. There really should be very, very little of it.
> 
> 
>> There has also been an issue where the delegate workaround was erroneously flagged as a heap delegate, causing considerable GC memory load.
> 
> I can't think of a case where:
> 
> () @trusted { ... }();
> 
> would make it a heap delegate. Such cases should be in bugzilla.
> 
> 
>> `@trusted` *should* probably not even be available for functions (of course it is not a desirable breaking change to disallow that now, though).
> 
> The idea is to encourage programmers to think about organizing code so that there are clear separations between safe and system code. Interleaving the two on a line-by-line basis defeats the purpose.

I think the whole discussion should be redirected toward simplifying `pure` instead.

* There are many legitimate reasons to want impure code act as pure.
* There is no easy recourse as there is for @trusted. All approaches are crazily convoluted.
June 17, 2021
On Thu, Jun 17, 2021 at 11:49:50AM -0400, Andrei Alexandrescu via Digitalmars-d wrote: [...]
> I think the whole discussion should be redirected toward simplifying `pure` instead.
> 
> * There are many legitimate reasons to want impure code act as pure. * There is no easy recourse as there is for @trusted. All approaches are crazily convoluted.

What are the actual advantages of code being marked pure?  I'm all for generalizing pure, but does it bring enough actual benefits to be worth the effort?

I'm not talking about theoretical benefits, but actual benefits that the compiler can actually make use of to emit better code.  I used to be a big fan of pure, but in practice I've found that it doesn't make *that* much of a difference in terms of codegen.  Maybe I'm missing something, in which case I'd love to be enlightened.


T

-- 
Perhaps the most widespread illusion is that if we were in power we would behave very differently from those who now hold it---when, in truth, in order to get power we would have to become very much like them. -- Unknown
June 17, 2021

On Thursday, 17 June 2021 at 14:30:58 UTC, Steven Schveighoffer wrote:

>

On 6/16/21 9:07 PM, Paul Backus wrote:

>

It's impossible to guarantee, at the language level, that @safe code can never require manual review. The programmer is allowed to use any and all knowledge at their disposal to verify the memory safety of @trusted (or in your proposal, @system-block) code, including knowledge about @safe code.

The goal is to guarantee that as long as your @trusted functions and blocks have a @safe interface, then @safe code does not need to be checked. When I say "not require review" I mean "I have checked all the @trusted code, and it has a sound @safe interface, so all @safe code that may call it have no need for review." We will never have a marking that is language-guaranteed to not require review.

To put it another way, as long as you aren't using @trusted escapes that leak implementation details, your @safe code shouldn't need a review. The problem is that trusted lambdas are not only the norm, it's actually required, due to template inference.

Right now, a @safe function can only be "assumed safe" as long as there are no @trusted blocks in it. Once there is one trusted block, then you have to review the whole function. The same thing goes for data invariants (though that spreads to the whole module instead).

Not having to review code for memory safety is supposed to be the major point of @safe.

Consider the following example:

size_t favoriteNumber() @safe { return 42; }

int favoriteElement(ref int[50] array) @trusted
{
    // This is memory safe because we know favoriteNumber returns 42
    return array.ptr[favoriteNumber()];
}

favoriteElement has a safe interface. There is no argument you can pass to it from @safe code that can possibly result in memory corruption.

However, if you change favoriteNumber to return something different (for example, 69), this may no longer be the case. So changes to favoriteNumber--a @safe function with no @trusted escapes--must still be manually reviewed to ensure that memory safety is maintained.

There is no language change you can make (short of removing @trusted entirely) that will prevent this situation from arising.

June 17, 2021
On Thursday, 17 June 2021 at 16:21:53 UTC, Paul Backus wrote:
> On Thursday, 17 June 2021 at 14:30:58 UTC, Steven Schveighoffer wrote:
>> [...]
>
> Consider the following example:
>
> ```d
> size_t favoriteNumber() @safe { return 42; }
>
> int favoriteElement(ref int[50] array) @trusted
> {
>     // This is memory safe because we know favoriteNumber returns 42
>     return array.ptr[favoriteNumber()];
> }
> ```
>
> `favoriteElement` has a safe interface. There is no argument you can pass to it from `@safe` code that can possibly result in memory corruption.
>
> However, if you change `favoriteNumber` to return something different (for example, 69), this may no longer be the case. So changes to `favoriteNumber`--a `@safe` function with no `@trusted` escapes--must still be manually reviewed to ensure that memory safety is maintained.
>
> There is no language change you can make (short of removing `@trusted` entirely) that will prevent this situation from arising.

Apart from reviewers asking the author of favoriteElement  to assert that the index is appropriate ...

That's exactly the reason behind a review of trusted function, you need to check only it's source code.



June 17, 2021

On Thursday, 17 June 2021 at 13:19:01 UTC, Mathias LANG wrote:

>

Wat ? That doesn't make any sense. A function that would free its input has to be @system.

Yes, but that was not the point.

If you call C-code, it may release a pointer deep down in the datastructure without setting the pointer to NULL. The invariant ensures that it isn't used.

Then you call D, and D claims that the pointer has to be null or you break @trusted , even if you never dereference?

Also, it claims that you cannot make assumptions of size, which would be an invariant (e.g. a size field would be tied to an invariant). This is not a language level guarantee, so it cannot be used...

>

Anything that deals with an array of memory offset needs to be encapsulated in its own data structure. @safe is about exposing a @safe interface, that is, something that can't be misused. If you use an array of memory offsets, then you have to do pointer arithmetic, which is not @safe.

But this is not enough. As @trusted apparently requires you to assume that the datastructure can be filled with garabage and using it should still always be @safe? Otherwise you assume that invariants hold.

>

You can't mark a function as trusted if it accepts an array of memory offset and just uses it. And you can't call that "correctly implemented", either.

Why not? It is protected by private. You CAN if you only access it with @trusted memberfunctions. However if you have one @safe memberfunction then a bug in that one could accidentally modify the offsets.

As a consequence you can ONLY have @trusted member functions, not even a single @safe member function, that does nothing can be allowed. As it could in theory contain a bug that could change the offsets.

Or, where am I wrong now?

June 17, 2021

On Thursday, 17 June 2021 at 13:54:06 UTC, ag0aep6g wrote:

>

That's right. An @trusted function cannot ever advance a pointer it received from the outside. It can only assume that the pointer can be dereferenced.

This is way too constraining.

At the very least it should accept a Phobos ownership-transfer wrapper where you can an obtain a buffer as a one-time transfer. Meaning, if you try to obtain it, it is moved out of the wrapper.

That would be perfectly safe.

>

You're losing me. You wrote that the lexer advances a pointer to a "character". I figure that means it has a char* parameter. What's a filebuffer? If it's a struct around a char*, why is the lexer manipulating the pointer directly instead of calling some method of the filebuffer?

Let us assume filebuffer is just a wrapper that transfers ownership. It prevents borrowing and ownership can only be transferred once. This is fully encapsulated. That is the invariant for the filebuffer.

Yet, as it only is an invariant, you would need to reallocate the buffer then stream over the filebuffer into your own buffer according to the requirements you've been kind to point out to me.

>

An example in code (instead of descriptions) would go a long way.

I guess, but it is too simple. It is conceptually just if (*ptr==0) return 0; return *(++ptr);.

For the @trusted to rely on this it has to know that it has unique access to the buffer.

The filebuffer object guarantees this, but it is an invariant, it cannot be checked by the language.

As a consequence you trigger completely pointless copying.

>

I think there's almost a consensus that @trusted isn't quite good enough, exactly because no one can be bothered to use it as intended.

I am sure that it will be used as intended in Phobos, or at least that this is achievable, but not in the kind of libraries that would be targeting games and such.

And well, one selling point for D could be that it is possible to write games in a safer environment than C++...

>

Phobos isn't even close to adhering to it. Yes, that's a problem.

Well, then nobody else will either. :-/