Skip to content

Comments

fix Issue 14246 - RAII - proper destruction of partially constructed …#8697

Merged
WalterBright merged 1 commit intodlang:masterfrom
WalterBright:reboot14246
Sep 28, 2018
Merged

fix Issue 14246 - RAII - proper destruction of partially constructed …#8697
WalterBright merged 1 commit intodlang:masterfrom
WalterBright:reboot14246

Conversation

@WalterBright
Copy link
Member

…objects/structs

This is a reboot of #6816 , but puts it behind the compiler switch
-transition=dtorfields so it will work with incorrect existing code.

@dlang-bot
Copy link
Contributor

Thanks for your pull request, @WalterBright!

Bugzilla references

Auto-close Bugzilla Severity Description
14246 major RAII - proper destruction of partially constructed objects/structs

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub fetch digger
dub run digger -- build "master + dmd#8697"

@WalterBright WalterBright force-pushed the reboot14246 branch 4 times, most recently from ce2f721 to 4aa31a7 Compare September 14, 2018 22:48
@thewilsonator
Copy link
Contributor

thewilsonator commented Sep 14, 2018

Copying from my comment from earlier:

IIUC, the main problem was running destructors on objects that were not constructed (yes destructors should able to handle a non-constructed object, but such is reality. It will stay like that until (if at all) the compiler inserts units to do so).

If it is at all desired for this to eventually be the default behaviour then this needs to come with compiler generated unittests to verify the behaviour of destructors on non-constructed objects.

This also doesn't fix the problem of destructors that throw in the scope (failure) { this.fieldDtor(); } (yes destructors shouldn't throw, but again, such is reality and if its not tested...)

@thewilsonator
Copy link
Contributor

This appears to have regressed issue 9386.

@WalterBright WalterBright force-pushed the reboot14246 branch 2 times, most recently from 0f52a7e to 4ddc68d Compare September 15, 2018 00:04
@WalterBright
Copy link
Member Author

Issue 9386 is in sdtor.d, and is apparently passing.

@WalterBright
Copy link
Member Author

This also doesn't fix the problem of destructors that throw in the scope (failure) { this.fieldDtor(); }

It's no different from any other case of throwing inside a scope(failure). I.e. it's not a special case.

@andralex
Copy link
Member

So to clarify, if an field is left default-initialized, will the destructor get called for it?

@thewilsonator
Copy link
Contributor

Yes. Thats what broke lots of code last time, and why it is behind a switch this time. This will therefore only break code on request to fix the already broken code :)

@thewilsonator
Copy link
Contributor

For the record I don't think this is the correct way to fix 14246 (it may function as a stop gap for Weka in the interim though):
While in theory a destructor should be able to handle a default constructed object, the reality is not universally applicable. This either also needs a switch that injects a unittest to verify the above on user code or it needs to track initialisation by field.

Note that this only needs to be done for constructors that are nothrow and have fields that have destructors. A -vthrowingctor switch would be a nice way to discourage throwing in constructors (ditto for destructors).

@WalterBright
Copy link
Member Author

So to clarify, if an field is left default-initialized, will the destructor get called for it?

Yes.

@WalterBright
Copy link
Member Author

Thats what broke lots of code last time

According to the last thread on it, what broke the code was having a nothrow constructor call a throwing destructor, a @nogc constructor calling a non-@nogc destructor, an @safe constructor calling an @System destructor, etc.

@dnadlinger
Copy link
Contributor

According to the last thread on it, what broke the code was having a nothrow constructor call a throwing destructor, a @nogc constructor calling a non-@nogc destructor, an @safe constructor calling an @System destructor, etc.

Hence my suggestion that attributes on the ctor declaration should only apply to the user-defined body, not the dtor calls behind the scenes.

@WalterBright
Copy link
Member Author

Hence my suggestion that attributes on the ctor declaration should only apply to the user-defined body, not the dtor calls behind the scenes.

That makes the attributes meaningless, though. It shouldn't be hard for people to fix their destructors, and with the switch they'll have plenty of time to do it. Most of the fixes will be simply adding the attribute.

@WalterBright
Copy link
Member Author

While in theory a destructor should be able to handle a default constructed object, the reality is not universally applicable.

In D, a default initialized object is a valid object. To write code otherwise will be step-by-step fighting the language.

@dnadlinger
Copy link
Contributor

dnadlinger commented Sep 15, 2018

That makes the attributes meaningless, though.

It doesn't – they would still be checked at the call site (i.e. where the object is constructed).

@WalterBright
Copy link
Member Author

It doesn't – they would still be checked at the call site.

Traits are transitive. An @safe function calling code that is @System and still passing the checker is useless.

@dnadlinger
Copy link
Contributor

Traits are transitive. An @safe function calling code that is @System and still passing the checker is useless.

This is obvious and does not need repeating.

In cases problematic w.r.t. backwards-compatibility, the issue is that from the user's perspective, none of their constructor code is actually @system/…, only the implicitly invoked dtor is. Hence the suggestion to handle attributes like the following:

struct Foo {
  this() @safe @nogc pure { … }
  static __ctor(ref Foo f) { // inferred attributes
    scope (failure) f.__fieldDtor();
    f.this();
  }
  …
}

This is obviously pseudo-code – there wouldn't actually be two functions, etc. –, but should clarify what is meant.

@WalterBright
Copy link
Member Author

I understand your proposal. But the result is that the constructor's behavior will not conform to its attributes, and so will engender a critical bug report. I'm afraid I'd have to agree :-(
There are only two ways forward:

  1. do not fix 14246
  2. expect users to fix their code
  3. do what C++ does and come up with a new kind of constructor and then have conferences and seminars trying to explain how great it is that there are two kinds of constructors and which kind users should use :-)

@thewilsonator
Copy link
Contributor

  1. Tell users how to fix their code with informative error messages

This is a result of a chain of consequences:

  • The constructor may throw an exception
  • The constructor is @safe/@nogc
  • The struct (sub-objects)'s have destructors
  • The (sub-)object's destructor is @system/gc

So the error message should be

`@safe @nogc` constructor of `Foo` may throw, calling `@system` garbage collecting destructor `Foo.somefield.__dtor()`
Note: use -transition=14246 to infer the constructor `@system` and garbage collecting 

When using -transition=14246, issue a warning/deprecation. Or issue a warning/deprecation by default and make -transition=14246 suppress it.

`@safe @nogc` constructor of `Foo` may throw, calling `@system` garbage collecting destructor `Foo.somefield.__dtor()`
It will be inferred `@system` and garbage collecting.

Note that this will not change the safeness or nogcness of

{
    auto foo = Foo();
}

since the destructor is called. Problems will arise when dynamically allocating a Foo, though.

@Shachar
Copy link

Shachar commented Sep 16, 2018

I have a reservation regarding this feature as implemented and =void fields. Essentially, the only way to make sure that a member that is =void does not get a destructor run on uninitialized data is to initialize it at the very beginning, and make sure (somehow?) that nothing throws until it's done.

@WalterBright
Copy link
Member Author

WalterBright commented Sep 16, 2018

Keep in mind that =void initialization is for @system code only, for good reason. It'll be up to the user to know what they're doing, and the compiler can't help. For example:

struct S { T* p; ~this() { whatever(p); } }
...
void foo() {  S s = void; }

is also going to fail miserably.

@Shachar
Copy link

Shachar commented Sep 16, 2018

Having it @system only is fine. Requiring me to know what I'm doing is fine. Presenting me with a problem I have no way of solving is a problem.

So, assuming @system, how do I solve the problem?

@WalterBright
Copy link
Member Author

Some options:

  1. don't use =void; initializers for objects with destructors
  2. make sure the object is initialized before encountering code that can throw
  3. don't use throwing constructors

Also, consider that the only point to =void; is efficiency. But adding an exception frame around every individual field (the C++ method) has its own runtime costs. (The C++ notion of zero-cost exceptions is a complete sham - they're expensive.) Remember also that zero initialization is quite cheap.

@Shachar
Copy link

Shachar commented Sep 16, 2018

I'm not sure I follow. Let's put in a concrete example:

struct StaticArrayContainer(T) {
private:
  T[10_000_000] data;
  size_t len = 0;
public:
  this(size_t initialLength) {
    // default initialize the first initialLength elements
    this.len = initialLength;
  }
}

How do you suggest we avoid unnecessarily initializing 10 million elements of unknown size?


nothrow void notthrow() { }

class C66
Copy link
Contributor

@RazvanN7 RazvanN7 Sep 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this code? I don't see any instances of this class anywhere. Maybe it should be moved to the compilable directory?

Copy link
Member Author

@WalterBright WalterBright Sep 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this code?

To compile successfully.

Maybe it should be moved to the compilable directory?

Perhaps, but I wanted the struct destructor tests in one file.

@WalterBright
Copy link
Member Author

@Shachar is the destructor for T, or for StaticArrayContainer?

@Shachar
Copy link

Shachar commented Sep 17, 2018

@WalterBright IT's for T.

The idea is to have a container with a fixed maximal capacity, but which otherwise behaves like an array (with variable length). Setting length to longer produces elements initialized to init. Setting it to lower would destruct the existing elements.
You know nothing about T.

@WalterBright
Copy link
Member Author

@Shachar One method is to type the array elements as void, and then provide access via overloading opSlice and opIndex that cast it to type T.

Copy link
Contributor

@RazvanN7 RazvanN7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

* https://issues.dlang.org/show_bug.cgi?id=14246
*/
AggregateDeclaration ad = ctor.toParent2().isAggregateDeclaration();
if (ad && ad.fieldDtor && global.params.dtorFields)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the check for ad really necessary? Isn't this already checked in semantic1 ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes, as a result of previous errors and error recovery, the AST isn't always in a completely consistent state. Expression nodes could be ErrorExp, Type nodes could be Terror, and even that isn't always done properly.

@WalterBright WalterBright merged commit 1933ecd into dlang:master Sep 28, 2018
@WalterBright WalterBright deleted the reboot14246 branch September 28, 2018 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants