Jump to page: 1 2
Thread overview
Destructor attribute inheritance, yea or nay?
May 22, 2017
Stanislav Blinov
May 26, 2017
Stanislav Blinov
May 26, 2017
Igor Shirkalin
May 26, 2017
Stanislav Blinov
May 26, 2017
Igor Shirkalin
May 26, 2017
Stanislav Blinov
May 26, 2017
Igor Shirkalin
May 26, 2017
Stanislav Blinov
May 27, 2017
Stanislav Blinov
May 27, 2017
Adam D. Ruppe
May 27, 2017
Stanislav Blinov
May 22, 2017
I'd like to hear what you guys think about this issue:

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

Marco argues that because "it currently doesn't work that way" (i.e. destructors are not inherited), the bug is invalid.

However, what this means in practice is:

- destroy()/rt_finalize() can never be anything but @system
- destructors of derived classes, and even destructors of aggregates (structs) can violate attributes, and the compiler does nothing to prevent that

Considering that the core runtime component - the GC - is the one that usually handles finalization, it follows that *GC collection can never be @safe*. And since collection only happens during allocation, it follows that allocation cannot be @safe either. Nor can they be @trusted, because destructors are effectively not restricted in any way. IOW, the "doesn't work that way" claim effectively hammers shut the coffin of memory safety as far as dynamic allocation is concerned, and that means the whole runtime and anything that depends on it.

I am of the opinion that the destructors should not be capable of violating the aggregated destruction attributes. This would allow the destroy() function to safely infer the correct attribute set for finalization, and propagate it to the calling code.

I.e. we could implement destroy() for classes as follows:

>void destroy(T)(T obj) if (is(T == class))
>{
>    (cast(_finalizeType!T)&rt_finalize)(cast(void*)obj);
>}
>
>void destroy(T)(T obj) if (is(T == interface))
>{
>    destroy(cast(Object)obj);
>}

>extern(C) void rt_finalize(void* p, bool det = true);

>extern(C)
>template _finalizeType(T)
>{
>    static if (is(T == Object))
>    {
>        alias _finalizeType = typeof(&rt_finalize);
>    }
>    else
>    {
>         alias _finalizeType = typeof((void* p, bool det = true) {
>            // generate a body that calls all the destructors in the chain,
>            // compiler should infer the intersection of attributes
>            // _Seq is an equivalent of std.meta.AliasSeq
>            // _Bases is an equivalent of std.traits.BaseClassesTuple
>            foreach (B; _Seq!(T, _Bases!T)) {
>                // __dtor, i.e. B.~this
>                static if (__traits(hasMember, B, "__dtor"))
>                    () { B obj; obj.__dtor; } ();
>                // __xdtor, i.e. dtors for all RAII members
>                static if (__traits(hasMember, B, "__xdtor"))
>                    () { B obj; obj.__xdtor; } ();
>            }
>        });
>    }
>}

This would keep the inferred attributes for code that actually calls destroy(). However, currently we cannot do that, because the language does not enforce attribute propagation in destructors, and at runtime, destroy() could be called via base class reference, while derived class violates the attributes:

class Base {
    ~this() @safe @nogc {}
}

class Derived : Base {
    ~this() {}
}

Base b = new Derived;
destroy(b); // infer @safe @nogc, while in reality this call is neither @safe nor @nogc, it is @system

Any thoughts?
May 26, 2017
On Monday, 22 May 2017 at 17:05:06 UTC, Stanislav Blinov wrote:

> Considering that the core runtime component - the GC - is the one that usually handles finalization, it follows that *GC collection can never be @safe*. And since collection only happens during allocation, it follows that allocation cannot be @safe either. Nor can they be @trusted, because destructors are effectively not restricted in any way.

This program, executed on my machine:

>import std.stdio;
>
>class Innocious
>{
>    ~this() @safe {}
>}
>
>class Malicious : Innocious
>{
>    int[] data;
>
>    this() @safe
>    {
>        data = new int[1000000];
>    }
>
>    ~this()
>    {
>        writeln("    Sure, here you go:");
>        writeln("      import std.random;");
>        writeln("      auto n = uniform(1, uint.max);");
>        writeln("      *(cast(int*)n) = 0xbadf00d;");
>    }
>}
>
>void important() @safe
>{
>    writeln("I am working here, i'm not doing anything dangerous...");
>    scope(exit) writeln("I'm good, no, I'm awesome. You can trust me!");
>    writeln("  Good GC, would you kindly give me some room to maneuver?");
>    int[] storage = new int[1000000];
>    /* do some calculations... */
>}
>
>void oblivious() @safe
>{
>    Innocious i = new Malicious();
>    /* do something with i and then leave it for GC. */
>}
>
>void main()
>{
>    oblivious();
>    important();
>}

prints this:

> I am working here, i'm not doing anything dangerous...
>   Good GC, would you kindly give me some room to maneuver?
>     Sure, here you go:
>       import std.random;
>       auto n = uniform(1, uint.max);
>       *(cast(int*)n) = 0xbadf00d;
> I'm good, no, I'm awesome. You can trust me!

May 26, 2017
On Monday, 22 May 2017 at 17:05:06 UTC, Stanislav Blinov wrote:
> I'd like to hear what you guys think about this issue:
>
> https://issues.dlang.org/show_bug.cgi?id=15246
>
> [...]

If your destructor is not @safe and @nogc, why not to make it be the same or call inherited destructor implicity?
May 26, 2017
On Friday, 26 May 2017 at 17:08:40 UTC, Igor Shirkalin wrote:
> On Monday, 22 May 2017 at 17:05:06 UTC, Stanislav Blinov wrote:
>> I'd like to hear what you guys think about this issue:
>>
>> https://issues.dlang.org/show_bug.cgi?id=15246
>>
>> [...]
>
> If your destructor is not @safe and @nogc, why not to make it be the same or call inherited destructor implicity?

Destructors of derived classes are called implicitly on finalization. The net effect is that such finalization adopts the weakest set of attributes among all the destructors it calls.

There are two sides of this problem: one is that we cannot have deterministic destruction (i.e. manually allocate/free classes) while keeping attribute inference: under current rules, finalization has to be @system. This one can be tackled if the language provided strict rules of attribute inheritance in destructors.

Another side, clearly demonstrated by my second post, is that non-deterministic destruction cannot be @safe, period. Because when GC collects and calls destructors, it calls all of them, regardless of their @safe status, even when the collection is triggered inside a @safe function.
May 26, 2017
On Friday, 26 May 2017 at 17:17:39 UTC, Stanislav Blinov wrote:
> Destructors of derived classes are called implicitly on finalization. The net effect is that such finalization adopts the weakest set of attributes among all the destructors it calls.
I'm sorry, I ment explicitly. I hope it is not possible.
>
> There are two sides of this problem: one is that we cannot have deterministic destruction (i.e. manually allocate/free classes) while keeping attribute inference: under current rules, finalization has to be @system. This one can be tackled if the language provided strict rules of attribute inheritance in destructors.
>
> Another side, clearly demonstrated by my second post, is that non-deterministic destruction cannot be @safe, period. Because when GC collects and calls destructors, it calls all of them, regardless of their @safe status, even when the collection is triggered inside a @safe function.

Doesn't that mean if compiler can't call inherited destructor despite of GC it must be error?


May 26, 2017
On Friday, 26 May 2017 at 17:32:38 UTC, Igor Shirkalin wrote:
> On Friday, 26 May 2017 at 17:17:39 UTC, Stanislav Blinov wrote:
>> Destructors of derived classes are called implicitly on finalization. The net effect is that such finalization adopts the weakest set of attributes among all the destructors it calls.
> I'm sorry, I ment explicitly. I hope it is not possible.

It is very possible, and it should be possible, otherwise we couldn't even think about deterministic destruction.

>> Another side, clearly demonstrated by my second post, is that non-deterministic destruction cannot be @safe, period. Because when GC collects and calls destructors, it calls all of them, regardless of their @safe status, even when the collection is triggered inside a @safe function.
>
> Doesn't that mean if compiler can't call inherited destructor despite of GC it must be error?

1) Destructors are not "inherited" in D. Each derived class has it's own independent destructor. That's why they don't inherit any attributes either.

2) Compiler doesn't call destructors for classes. It is done either manually (by calling destroy()) or by the GC. Look at the example in the second post: I'm in @safe function (important()), I need some memory. I ask for it, the GC decides to do a collection before giving me memory. And during that collection it calls a @system destructor.
So the language and runtime are effectively in disagreement: language says "no @system calls in @safe context", runtime says "whatever, I need to call those destructors".
May 26, 2017
On Friday, 26 May 2017 at 17:48:24 UTC, Stanislav Blinov wrote:
>> I'm sorry, I ment explicitly. I hope it is not possible.
>
> It is very possible, and it should be possible, otherwise we couldn't even think about deterministic destruction.
Hm, you've said it is decision of GC (see bellow), so how can it be deterministic?
>
>>> Another side, clearly demonstrated by my second post, is that non-deterministic destruction cannot be @safe, period. Because when GC collects and calls destructors, it calls all of them, regardless of their @safe status, even when the collection is triggered inside a @safe function.
>>
>> Doesn't that mean if compiler can't call inherited destructor despite of GC it must be error?
>
> 1) Destructors are not "inherited" in D. Each derived class has it's own independent destructor. That's why they don't inherit any attributes either.
>
> 2) Compiler doesn't call destructors for classes. It is done either manually (by calling destroy()) or by the GC. Look at the example in the second post: I'm in @safe function (important()), I need some memory. I ask for it, the GC decides to do a collection before giving me memory. And during that collection it calls a @system destructor.
> So the language and runtime are effectively in disagreement: language says "no @system calls in @safe context", runtime says "whatever, I need to call those destructors".

Your example is very interesting and it derives some questions.
First, why 'oblivious' function does not free Malicious object (no matter GC or not GC). What if 'important' function needs some "external an not safe" resource used by 'oblivious'? Is it all about @safe that stops allowing it? If so, @safe is really important feature in Dlang. Second, same as first, it looks like I got it.
May 26, 2017
On Friday, 26 May 2017 at 18:58:46 UTC, Igor Shirkalin wrote:

> First, why 'oblivious' function does not free Malicious object (no matter GC or not GC).

It actually does matter. It doesn't manually release the resources precisely because it relies on the GC. I've made it overly explicit, but in real world it could have just as easily been an implicit allocation done by some library function (say, Phobos), perhaps even without giving me an actual reference to allocated memory. The name of this function reflects this: it doesn't know or care what's going on inside it.

> What if 'important' function needs some "external an not safe" resource used by 'oblivious'?

That's the point. 'important' has nothing to do with 'oblivious' at all, yet it *may* suffer from side effects that originate in 'oblivious' at an unspecified point in time during program execution. What's worse, at a glance it would look like @safe function breaking it's own promise

> Is it all about @safe that stops allowing it? If so, @safe is really important feature in Dlang. Second, same as first, it looks like I got it.

Per language rules, you're not allowed to call @system functions in @safe code:

void important() @safe
{
    auto obj = new Malicious();
    obj.destroy(); // this will be a compiler error, destroy() is @system
}

However, the runtime currently ignores this altogether, and happily calls that same @system function while executing that same @safe function, or rather, may or may not call depending on conditions beyond our control. If that doesn't sound bad, I don't know what does.
May 27, 2017
On Monday, 22 May 2017 at 17:05:06 UTC, Stanislav Blinov wrote:
> I'd like to hear what you guys think about this issue:
>
> https://issues.dlang.org/show_bug.cgi?id=15246
>
> Any thoughts?

By the absence of replies from those who (I think) should care I conclude that either:

1. I'm saying something stupidly silly, and people are too polite to point it out;
2. Everybody knows this already;
3. Nobody actually cares.

I'm hoping it's 1 or 2...
May 27, 2017
On Saturday, 27 May 2017 at 10:11:38 UTC, Stanislav Blinov wrote:
> 3. Nobody actually cares.

That's me. I think the @attribute mess is completely broken and mostly just ignore it.

That said, I do agree with you: it SHOULD work like you describe if we want the @attributes to be meaningful.
« First   ‹ Prev
1 2