My Reference Safety System (DIP???)
Zach the Mystic via Digitalmars-d
digitalmars-d at puremagic.com
Thu Feb 26 08:40:25 PST 2015
On Wednesday, 25 February 2015 at 18:08:55 UTC, deadalnix wrote:
> On Wednesday, 25 February 2015 at 01:12:15 UTC, Zach the Mystic
> wrote:
>> int r; // declaration scopedepth(0)
>>
>> void fun(int a /*scopedepth(0)*/) {
>> int b; // depth(1)
>> {
>> int c; // depth(2)
>> {
>> int d; // (3)
>> }
>> {
>> int e; // (3)
>> }
>> }
>> int f; // (1)
>> }
>>
>
> You have element of differing lifetime at scope depth 0 so far.
Sorry for the delay.
I made a mistake. Parameter `a` will have a *declaration* scope
of 1, just like int b above. It's *reference* scope will have
depth 0, with the "mystery" bit for the first parameter set.
>> Principle 5: It's always un at safe to copy a declaration scope
>> from a higher scopedepth to a reference variable stored at
>> lower scopedepth. DIP69 tries to banish this type of thing
>> only in `scope` variables, but I'm not afraid to banish it in
>> all @safe code period:
>>
>> void gun() @safe {
>> T* t; // t's declaration depth: 1
>> T u;
>> {
>> T* uu = &u; // fine, this is normal
>> T tt;
>> t = &tt; // t's reference depth: 2, error, un at safe
>> }
>> // now t is corrupted
>> }
>>
>
> Bingo. However, when you throw goto into the mix, weird thing
> happens. The general idea is good but need refining.
I addressed this further down, in Principle 10. My proposed
solution has the compiler detecting the presence of code which
could both 1) be visited again (through a jump label or a loop)
and 2) is in a branching condition. In these cases it pushes any
statement which copies a reference onto a special stack. When the
branching condition finishes, it revisits the stack, "reheating"
the scopes in reverse order. If there is a way to defeat this
technique, it must be very convoluted, since the scopes do
nothing but accumulate possibilities. It may even be
mathematically impossible.
>> Principle 7: In this system, all scopes are *transitive*: any
>> reference type with double indirections inherits the scope of
>> the outermost reference. Think of it this way:
>>
>
> It is more complex than that, and this is where most proposals
> fail short (including this one and DIP69). If you want to
> disallow the assignment of a reference to something with a
> short lifetime, you can't consider scope transitive when used
> as a lvalue. You can, however, consider it transitive when used
> as an rvalue.
>
> The more general rule is that you want to consider the largest
> possible lifetime of an lvalue, and the smallest possible one
> for an rvalue.
>
> When going through an indirection, that will differ, unless we
> choose to tag all indirections, which is undesirable.
I'm unclear about what you're saying. Can you give an example in
code?
>> Principle 8: Any time a reference is copied, the reference
>> scope inherits the *maximum* of the two scope depths:
>>
>
> That makes control flow analysis easier, so I can buy this :)
>
>> Principle 8: We don't need to know! For all intents and
>> purposes, a reference parameter has infinite lifetime for the
>> duration of the function it is compiled in. Whenever we copy
>> any reference, we do a bitwise OR on *all* of the mystery
>> scopes. The new reference accumulates every scope it has ever
>> had access to, directly or indirectly.
>>
>
> That would allow to copy a parameter reference to a global,
> which is dead unsafe.
Actually, it's not unsafe, so long as you have the parameter
attribute `noscope` (or possibly `static`) working for you:
void fun(T* a) {
static T* t;
*t = a; // this might be safe
}
The truth is, this *might* be safe. It's only unsafe if the
parameter `a` is located on the stack. From within the function,
the compiler can't possibly know this. But if it forces you to
mark `a` with `noscope` (or is allowed to infer the same), it
tells the caller all it needs to know about `a`. Simply put, it's
an error to pass a local to a `noscope` parameter. And it runs
all the way down: any parameter which it itself passed to a
`noscope` parameter must also be marked `noscope`. (Note: I'm
actually preferring the name `static` at this point, but using
`noscope` for consistency):
T* fun(noscope T* a) {
static T* t;
*t = a; // this might be safe
}
void tun(T* b) {
T c;
fun(&c); // error, local
fun(b); // error, unless b also marked (or inferred) `noscope`
}
> There is some goodness in there. Please address my comment and
> tell me if I'm wrong, but I think you didn't covered all bases.
The only base I'm really worried about is the lvalue vs rvalue
base. Hopefully we can fix that!
More information about the Digitalmars-d
mailing list