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