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?

Reply via email to