February 27, 2015
On 2/26/15 8:21 PM, Zach the Mystic wrote:
> Can I ask you a general question about safety: If you became convinced
> that really great safety would *require* more function attributes, what
> would be the threshold for including them? I'm trying to "go the whole
> hog" with safety, but I'm paying what seems to me the necessary price --
> more parameter attributes. Some of these gains ("out!" parameters, e.g.)
> seem like they would only apply to very rare code, and yet they *must*
> be there, in order for functions to "talk" to each other accurately.
>
> Are you interested in accommodating the rare use cases for the sake of
> robust safety, or do you just want to stop at the very common use cases
> ("ref returns", e.g.)? "ref returns" will probably cover more than half
> of all use cases for memory safety. Each smaller category will require
> additions to what a function signature can contain (starting with
> expanding `return` to all reference types, e.g.), while covering a
> smaller number of actual use cases... but on the other hand, it's
> precisely because they cover fewer use cases that they will appear so
> much less often.

Safety is good to have, and the simple litmus test is if you slap @safe: at the top of all modules and you use no @trusted (or of course use it correctly), you should have memory safety, guaranteed.

A feature that is safe except for certain constructs is undesirable.

Generally having a large number of corner cases that require special language constructs to address is a Bad Sign.


Andrei

February 27, 2015
On Friday, 27 February 2015 at 14:02:58 UTC, Andrei Alexandrescu wrote:
> Safety is good to have, and the simple litmus test is if you slap @safe: at the top of all modules and you use no @trusted (or of course use it correctly), you should have memory safety, guaranteed.
>
> A feature that is safe except for certain constructs is undesirable.

It seems like you're agreeing with my general idea of "going the whole hog".

> Generally having a large number of corner cases that require special language constructs to address is a Bad Sign.

But D inherits C's separate compilation model. All these cool function and parameter attributes (pure, @safe, return ref, etc.) could be kept hidden and just used and they would Just Work if D didn't have to accommodate separation compilation. From my perspective, the only "Bad Sign" is that D has to navigate the tradeoff between:

* concise function signatures
* accurate communication between functions
* enabling separate compilation

It's like you have to sacrifice one to get the other two. Naturally I'm not keen on this, so I rush to see how far attribute inference for all functions can be taken. Then Dicebot suggests automated .di file generation with statically verified matching binaries:

http://forum.dlang.org/post/otejdbgnhmyvbyaxatsk@forum.dlang.org

The point is that I don't feel the ominous burden of a Bad Sign here, because of the inevitability of this conflict.
February 27, 2015
On Fri, Feb 27, 2015 at 06:02:57AM -0800, Andrei Alexandrescu via Digitalmars-d wrote: [...]
> Safety is good to have, and the simple litmus test is if you slap @safe: at the top of all modules and you use no @trusted (or of course use it correctly), you should have memory safety, guaranteed.
[...]

@safe has some pretty nasty holes right now... like:

	https://issues.dlang.org/show_bug.cgi?id=5270
	https://issues.dlang.org/show_bug.cgi?id=8838
	https://issues.dlang.org/show_bug.cgi?id=12822
	https://issues.dlang.org/show_bug.cgi?id=13442
	https://issues.dlang.org/show_bug.cgi?id=13534
	https://issues.dlang.org/show_bug.cgi?id=13536
	https://issues.dlang.org/show_bug.cgi?id=13537
	https://issues.dlang.org/show_bug.cgi?id=14136
	https://issues.dlang.org/show_bug.cgi?id=14138

There are probably other holes that we haven't discovered yet.

All in all, it's not looking like much of a guarantee right now.  It's more like a cheese grater.

This is a symptom of the fact that @safe, as currently implemented, starts by assuming the whole language is @safe, and then checking for exceptions that are deemed unsafe. Since D has become quite a large, complex language, many unsafe operations and unsafe combinations of features are bound to be overlooked (cf. combinatorial explosion), hence there are a lot of known holes and probably just as many, if not more, unknown ones. Trying to fix them is like playing whack-a-mole: there's always yet one more loophole that we overlooked, and that one hole compromises the whole system. Not to mention, every time a new language feature is added, @safe is potentially compromised by newly introduced combinations of features that are permitted by default.

Rather, what *should* have been done is to start with @safe *rejecting* everything in the language, and then gradually relaxed to permit more operations as they are vetted to be safe on a case-by-case basis. That way, instead of having a long list of holes in @safe that need to be plugged, we *already* have guaranteed safety and just need to allow more safe operations that are currently prohibited. @safe bugs should have been of the form "operation X is rejected but ought to be legal", rather than "operation X is accepted but compromises @safe". In the former case we would already have achieved guaranteed safety, but in the latter case, as is the current situation, we don't have guaranteed safety and it's an uphill battle to get there (and we don't know if we'll ever arrive).

See: https://issues.dlang.org/show_bug.cgi?id=12941


T

-- 
Verbing weirds language. -- Calvin (& Hobbes)
February 27, 2015
On 2/27/15 7:33 AM, H. S. Teoh via Digitalmars-d wrote:
> On Fri, Feb 27, 2015 at 06:02:57AM -0800, Andrei Alexandrescu via Digitalmars-d wrote:
> [...]
>> Safety is good to have, and the simple litmus test is if you slap
>> @safe: at the top of all modules and you use no @trusted (or of course
>> use it correctly), you should have memory safety, guaranteed.
> [...]
>
> @safe has some pretty nasty holes right now... like:
>
> 	https://issues.dlang.org/show_bug.cgi?id=5270
> 	https://issues.dlang.org/show_bug.cgi?id=8838
> 	https://issues.dlang.org/show_bug.cgi?id=12822
> 	https://issues.dlang.org/show_bug.cgi?id=13442
> 	https://issues.dlang.org/show_bug.cgi?id=13534
> 	https://issues.dlang.org/show_bug.cgi?id=13536
> 	https://issues.dlang.org/show_bug.cgi?id=13537
> 	https://issues.dlang.org/show_bug.cgi?id=14136
> 	https://issues.dlang.org/show_bug.cgi?id=14138
>
> There are probably other holes that we haven't discovered yet.

Yah, @safe is in need of some good TLC. How about we make it a priority for 2.068?

> All in all, it's not looking like much of a guarantee right now.  It's
> more like a cheese grater.
>
> This is a symptom of the fact that @safe, as currently implemented,
> starts by assuming the whole language is @safe, and then checking for
> exceptions that are deemed unsafe. Since D has become quite a large,
> complex language, many unsafe operations and unsafe combinations of
> features are bound to be overlooked (cf. combinatorial explosion), hence
> there are a lot of known holes and probably just as many, if not more,
> unknown ones.

I'd have difficulty agreeing with this. The issues you quoted don't seem to follow a pattern of combinatorial explosion.

On another vein, consider that the Java Virtual Machine has had for many, many years bugs in its safety, even though it was touted to be safe from day one. With each of the major bugs, naysayers claimed it's unfixable and it belies the claim of memory safety.

A @safe function may assume that the code surrounding it has not broken memory integrity. Under that assumption, it is required (and automatically checked) that it leaves the system with memory integrity. This looks like a reasonable stance to me, and something I'm committed to work with.

> Trying to fix them is like playing whack-a-mole: there's
> always yet one more loophole that we overlooked, and that one hole
> compromises the whole system. Not to mention, every time a new language
> feature is added, @safe is potentially compromised by newly introduced
> combinations of features that are permitted by default.

There aren't many large features to be added, and at this point with @safe being a major priority I just find it difficult to understand this pessimism.

Probably a good thing to do, whether you're right or overly pessimistic, is to fix these bugs. In the worst case we have a slightly tighter "cheese grate". In the best case we get to safety.

> Rather, what *should* have been done is to start with @safe *rejecting*
> everything in the language, and then gradually relaxed to permit more
> operations as they are vetted to be safe on a case-by-case basis.

Yah, time travel is always so enticing. What I try to do is avoid telling people sentences that start with "You/We should have". They're not productive. Instead I want to focus on what we should do starting now.

> See: https://issues.dlang.org/show_bug.cgi?id=12941

I'm unclear how this is actionable.


Andrei

February 27, 2015
Andrei Alexandrescu:

> Safety is good to have, and the simple litmus test is if you slap @safe: at the top of all modules and you use no @trusted (or of course use it correctly), you should have memory safety, guaranteed.

I have suggested to switch to @safe by default:
https://issues.dlang.org/show_bug.cgi?id=13838

Bye,
bearophile
February 27, 2015
On Friday, 27 February 2015 at 15:35:46 UTC, H. S. Teoh wrote:
> @safe has some pretty nasty holes right now... like:
>
> 	https://issues.dlang.org/show_bug.cgi?id=5270
> 	https://issues.dlang.org/show_bug.cgi?id=8838

My new reference safety system:

http://forum.dlang.org/post/offurllmuxjewizxedab@forum.dlang.org

...would solve the above two bugs. In fact, it's designed precisely for bugs like those. Here's your failing use case for bug 5270. I'll explain how my system would track and catch the bug:

int delegate() globDg;

void func(scope int delegate() dg) {
        globDg = dg; // should be rejected but isn't
        globDg();
}

If func is marked @safe and no attribute inference is permitted, this would error, as it copies a reference parameter to a global. However, let's assume we have inference. The signature would now be inferred to:

void func(noscope scope int delegate() dg);

Yeah it's obviously weird having both `scope` and `noscope`, but that's pure coincidence, and moreover, I think the use of `scope` here would be made obsolete by my system anyway. (Note also that the `noscope` bikeshed has been suggested to be painted `static` instead -- it's not about the name, yet... ;-)

void sub() {
        int x;
        func(() { return ++x; });
}

Well I suppose this rvalue delegate is allocated on the stack, which will have local reference scope. This is where you'd get the safety error in the case of attribute inference, as you can't pass a local reference to a `noscope` parameter. The rest is just a foregone conclusion (added here for completion):

void trashme() {
        import std.stdio;
        writeln(globDg()); // prints garbage
}

void main() {
        sub();
        trashme();
}

The next bug, 8838, is a very simple case, I think:

int[] foo() @safe
{
    int[5] a;
    return a[];
}

`a`, being a static array, would have a reference scope depth of 1, and when you copy the reference to make a dynamic array in the return value, the reference scope inherits that of `a`. Any scope system would catch this one, I'm afraid. Mine seems like overkill in this case. :-/
February 27, 2015
On Fri, Feb 27, 2015 at 07:57:22AM -0800, Andrei Alexandrescu via Digitalmars-d wrote:
> On 2/27/15 7:33 AM, H. S. Teoh via Digitalmars-d wrote:
> >On Fri, Feb 27, 2015 at 06:02:57AM -0800, Andrei Alexandrescu via Digitalmars-d wrote: [...]
> >>Safety is good to have, and the simple litmus test is if you slap @safe: at the top of all modules and you use no @trusted (or of course use it correctly), you should have memory safety, guaranteed.
> >[...]
> >
> >@safe has some pretty nasty holes right now... like:
> >
> >	https://issues.dlang.org/show_bug.cgi?id=5270
> >	https://issues.dlang.org/show_bug.cgi?id=8838
> >	https://issues.dlang.org/show_bug.cgi?id=12822
> >	https://issues.dlang.org/show_bug.cgi?id=13442
> >	https://issues.dlang.org/show_bug.cgi?id=13534
> >	https://issues.dlang.org/show_bug.cgi?id=13536
> >	https://issues.dlang.org/show_bug.cgi?id=13537
> >	https://issues.dlang.org/show_bug.cgi?id=14136
> >	https://issues.dlang.org/show_bug.cgi?id=14138
> >
> >There are probably other holes that we haven't discovered yet.
> 
> Yah, @safe is in need of some good TLC. How about we make it a priority for 2.068?

If we're going to do that, let's do it right. Let's outlaw everything in @safe and then start expanding it by adding explicitly-vetted operations. See below.


> >All in all, it's not looking like much of a guarantee right now. It's more like a cheese grater.
> >
> >This is a symptom of the fact that @safe, as currently implemented, starts by assuming the whole language is @safe, and then checking for exceptions that are deemed unsafe. Since D has become quite a large, complex language, many unsafe operations and unsafe combinations of features are bound to be overlooked (cf. combinatorial explosion), hence there are a lot of known holes and probably just as many, if not more, unknown ones.
> 
> I'd have difficulty agreeing with this. The issues you quoted don't seem to follow a pattern of combinatorial explosion.

No, what I meant was that in an "assume safe unless proven otherwise" system, there's bound to be holes because the combinatorial explosion of feature combinations makes it almost certain there's *some* unsafe combination we haven't thought of yet that the compiler currently accepts. And it may be a long time before we discover this flaw.

This means that the current implementation almost certainly has holes (and in fact it has quite a few known ones, and very likely more as-yet unknown ones), therefore it's not much of a "guarantee" of safety at all.

What I'm proposing is that we reverse that: start with prohibiting everything, which is by definition safe, since doing nothing is guaranteed to be safe. Then slowly add to it the things that are deemed safe after careful review, until it becomes a usable subset of the language. This way, we actually *have* the guarantee of safety from day one, and all we have to do is to make sure each new addition to the list of permitted operations doesn't introduce any new holes. And even in the event that it does, the damage is confined because we know exactly where the problem came from: we know that X commits in the past @safe had no holes, and now there's a hole, so git bisect will quickly locate the offending change.

Whereas in our current approach, everything is permitted by default, which means the safety guarantee is broken *by default*, except where we noticed and plugged it. We're starting with a cheese grater and plugging the holes one by one, hoping that one day it will become a solid plate. Why not start with a solid plate in the first place, and make sure we don't accidentally punch holes through it?


> On another vein, consider that the Java Virtual Machine has had for many, many years bugs in its safety, even though it was touted to be safe from day one. With each of the major bugs, naysayers claimed it's unfixable and it belies the claim of memory safety.

Fallacy: Language X did it this way, therefore it's correct to do it this way.


> A @safe function may assume that the code surrounding it has not broken memory integrity. Under that assumption, it is required (and automatically checked) that it leaves the system with memory integrity. This looks like a reasonable stance to me, and something I'm committed to work with.

That's beside the point. Assuming the surrounding context is safe or not has no bearing on whether certain combinations of operations inside the @safe function has unsafe semantics -- because the compiler failed to recognize a certain construct as unsafe. The latter is what I'm talking about.


> >Trying to fix them is like playing whack-a-mole: there's always yet one more loophole that we overlooked, and that one hole compromises the whole system. Not to mention, every time a new language feature is added, @safe is potentially compromised by newly introduced combinations of features that are permitted by default.
> 
> There aren't many large features to be added, and at this point with @safe being a major priority I just find it difficult to understand this pessimism.

It's not about the size of a new feature.  Every new feature, even a seemingly small one, causes an exponential growth in the number of language features one may put together, thereby increasing the surface area for some feature combinations to interact in unexpected ways. Surely you must know this, since this is why we generally try not to add new language features if they don't pull their own weight.

The problem with this is that when compiling @safe code, the compiler is not looking at a list of permitted features, but checking a list of prohibited features. So by default, new feature X (along with all combinations of it with existing language features) is permitted unless somebody took the pains to evaluate its safety in every possible context in which it might be used, *and* check for all those cases when compiling in @safe mode. Given the size of the language, something is bound to be missed. So the safety guarantee may have been silently broken, but we're none the wiser until some unfortunate user stumbles upon it and takes the time to file a bug. Until then, @safe is broken but we don't even know about it.

If, OTOH, the compiler checks against a list of permitted features instead, feature X will be rejected in @safe code by default, and we would slowly expand the scope of X within @safe code by adding specific instances of it to the list of permitted features as we find them. If we miss any case, there's no problem -- it gets (wrongly) rejected at compile time, but nothing will slip through that might break @safe guarantees. We just get a rejects-valid bug report, add that use case to the permitted list, and close the bug. Safety is not broken throughout the process.


[...]
> >Rather, what *should* have been done is to start with @safe *rejecting* everything in the language, and then gradually relaxed to permit more operations as they are vetted to be safe on a case-by-case basis.
> 
> Yah, time travel is always so enticing. What I try to do is avoid telling people sentences that start with "You/We should have". They're not productive. Instead I want to focus on what we should do starting now.
> 
> >See: https://issues.dlang.org/show_bug.cgi?id=12941
> 
> I'm unclear how this is actionable.
[...]

What about this, if we're serious about @safe actually *guaranteeing* anything: after 2.067 is released, we reimplement @safe by making it reject every language construct by default. (This will, of course, cause all @safe code to no longer compile.) Then we slowly add back individual language features to the list of permitted operations in @safe code until existing @safe code successfully compiles. That gives us a reliable starting point where we *know* that @safe is actually, y'know, safe.

Of course, many legal things will now be (wrongly) rejected in @safe code, but that's OK, because we will add them to the list of things permitted in @safe code as we find them. Meanwhile, @safe actually *guarantees* safety.  As opposed to the current situation, where @safe sorta-kinda gives you memory safety, provided you don't use unanticipated combinations of features that the compiler failed to recognize as unsafe, or use new features that weren't thoroughly checked beforehand, or do something blatantly stupid, or do something known to trigger a compiler bug, or ... -- then maybe, fingers crossed, you will have memory safety. Or so we hope.


T

-- 
Question authority. Don't ask why, just do it.
February 27, 2015
On 2/27/15 1:07 PM, H. S. Teoh via Digitalmars-d wrote:
> What about this, if we're serious about @safe actually*guaranteeing*
> anything: after 2.067 is released, we reimplement @safe by making it
> reject every language construct by default.

I don't think this is practical. It's a huge amount of work over a long time.

Besides, even with that approach there's still no guarantee; implementation bugs are always possible in either approach.

I think the closest thing to what you're after is progress and preservation proofs on top of a core subset of the language. It would be great if somebody wanted to do this.


Andrei

February 27, 2015
On Friday, 27 February 2015 at 21:09:51 UTC, H. S. Teoh wrote:
> No, what I meant was that in an "assume safe unless proven otherwise"
> system, there's bound to be holes because the combinatorial explosion of
> feature combinations makes it almost certain there's *some* unsafe
> combination we haven't thought of yet that the compiler currently
> accepts. And it may be a long time before we discover this flaw.
>


To be back to the original problem, there are various instances of:
 - A is safe and useful in safe code, let's not making it unsafe !
 - B is safe and useful in safe code, let's not making it unsafe !

Yet A and B may be unsafe used together, so one of them should be made unsafe. You ends up in the same situation than exposed in the first post.
February 27, 2015
On Friday, 27 February 2015 at 21:09:51 UTC, H. S. Teoh wrote:
>> >	https://issues.dlang.org/show_bug.cgi?id=12822
>> >	https://issues.dlang.org/show_bug.cgi?id=13442
>> >	https://issues.dlang.org/show_bug.cgi?id=13534
>> >	https://issues.dlang.org/show_bug.cgi?id=13536
>> >	https://issues.dlang.org/show_bug.cgi?id=13537
>> >	https://issues.dlang.org/show_bug.cgi?id=14136
>> >	https://issues.dlang.org/show_bug.cgi?id=14138
>> >
>> >There are probably other holes that we haven't discovered yet.

I wanted to say that besides the first two bugs I tried to address, none of the rest in your list involves more than just telling the compiler to check for this or that, whatever the case may be, per bug. Maybe blanket use of `@trusted` to bypass an over-cautious compiler is the only real danger I personally am able to worry about.

I simplified my thinking by dividing everything into "in function" and "outside of function". So I ask, within a function, what do I need to know to ensure everything is safe? And then, from outside a function, what do I need to know to ensure everything is safe? The function has inputs and outputs, sources and destinations.