digitalmars.D - Destructor attribute inheritance, yea or nay?
- Stanislav Blinov (38/76) May 22 2017 I'd like to hear what you guys think about this issue:
- Stanislav Blinov (3/57) May 26 2017 prints this:
- Igor Shirkalin (3/6) May 26 2017 If your destructor is not @safe and @nogc, why not to make it be
- Stanislav Blinov (15/23) May 26 2017 Destructors of derived classes are called implicitly on
- Igor Shirkalin (4/19) May 26 2017 Doesn't that mean if compiler can't call inherited destructor
- Stanislav Blinov (15/28) May 26 2017 It is very possible, and it should be possible, otherwise we
- Igor Shirkalin (10/33) May 26 2017 Hm, you've said it is decision of GC (see bellow), so how can it
- Stanislav Blinov (26/33) May 26 2017 It actually does matter. It doesn't manually release the
- Stanislav Blinov (8/11) May 27 2017 By the absence of replies from those who (I think) should care I
- Adam D. Ruppe (5/6) May 27 2017 That's me. I think the @attribute mess is completely broken and
- Stanislav Blinov (3/10) May 27 2017 Thanks, option 1 just became less of a one.
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 22 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: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.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: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:It is very possible, and it should be possible, otherwise we couldn't even think about deterministic destruction.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.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".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:48:24 UTC, Stanislav Blinov wrote:Hm, you've said it is decision of GC (see bellow), so how can it be deterministic?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.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.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".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 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 promiseIs 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 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 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.
May 27 2017
On Saturday, 27 May 2017 at 13:32:57 UTC, Adam D. Ruppe wrote:On Saturday, 27 May 2017 at 10:11:38 UTC, Stanislav Blinov wrote:Hm. That's a strategy, perhaps I should try it :)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.Thanks, option 1 just became less of a one.
May 27 2017