Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[basic.life] Remove description of impossible UB CWG2523 #2342

Closed
wants to merge 1 commit into from

Conversation

RedBeard0531
Copy link
Contributor

A destructor that is not called cannot produce side effects, therefore it is impossible to depend on those side effects.

A destructor that is not called cannot produce side effects, therefore it is impossible to depend on those side effects.
Copy link
Member

@jwakely jwakely left a comment

Choose a reason for hiding this comment

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

Nack.

It's talking about the side effects that would have been produced by the destructor. If the program's correctness relies on side effects of the destructor, and you don't run the destructor, the program is not correct.

@jwakely
Copy link
Member

jwakely commented Sep 25, 2018

That "shall" should probably be replaced, so "is not implicitly called".

@RedBeard0531
Copy link
Contributor Author

How is it possible for a program to rely on something guaranteed not to happen? If the destructor's side effects would have prevented some other UB, then obviously there is UB, but it seems odd to state that here since the UB must have been triggered by violating some other requirement. Unless you are implying that this clause is trying to say that behavior is undefined if a program produces an incorrect result due to not calling a destructor without otherwise committing any UB.

@CaseyCarter
Copy link
Contributor

If the program's correctness relies on side effects of the destructor, and you don't run the destructor, the program is not correct.

Are you suggesting that without this phrase in the Standard text, readers might assume that the side effects of a destructor occur even when that destructor is never called?

At best, this phrase deserves to be a note.

@FrankHB
Copy link
Contributor

FrankHB commented Sep 26, 2018

What does the phrase "depend on" mean here?

"The program's correctness" also sounds strange. What does "correctness" here mean exactly, besides "free from UB"? If there is UB, the program is definitely not correct to be a portable program. But how can the specification of a language guarantee the correctness of a program without any concrete expectation from the user?

@jwakely
Copy link
Member

jwakely commented Sep 26, 2018

@RedBeard0531

How is it possible for a program to rely on something guaranteed not to happen?

I don't see what's so hard to understand. If you have a destructor that performs an important operation, say unlocking a mutex, and if you fail to invoke that destructor for one of your objects, then your program might not work correctly. It might deadlock because other users of that mutex can never lock it.

The point is that if your destructor doesn't do anything important, it's OK if the object is never destroyed. On the other hand, if you rely on resources being freed (to prevent memory exhaustion, or mutex deadlocks, or whatever) then you'd better destroy your objects not leak them. That needs to be said somehow, even if only as a note.

@jwakely
Copy link
Member

jwakely commented Sep 26, 2018

@CaseyCarter

Are you suggesting that without this phrase in the Standard text, readers might assume that the side effects of a destructor occur even when that destructor is never called?

No. But readers could assume it's undefined to fail to destroy any object with a non-trivial destructor, even if that destructor has no side effects. That's not a completely unreasonable position.

The current wording is poor and should be improved, but not just removed.

@RedBeard0531
Copy link
Contributor Author

@jwakely

No. But readers could assume it's undefined to fail to destroy any object with a non-trivial destructor, even if that destructor has no side effects. That's not a completely unreasonable position.

That is exactly why I think this line needs to be deleted, or at least reworded to not imply UB in a case where the behavior is actually quite well defined. If a reader makes that assumption, then they are misinterpreting the standard (I think, does anyone in CWG claim that this clause adds normative UB?)

if you rely on resources being freed (to prevent memory exhaustion, or mutex deadlocks, or whatever) then you'd better destroy your objects not leak them.

Conversely, it is possible to rely on the resources not being freed in some cases. If I actively choose not to call the dtor, I can rely on the effects not occurring. Or at least that is how I interpret the prior clause of the sentence. In those cases, I don't want the specter of UB dangling there in a perfectly well defined case.

What would you want the note to say? I am struggling to think of anything that needs to be added to "the destructor shall not be implicitly called." That seems to clearly state the defined behavior, with little room for misinterpretation.

@CaseyCarter
Copy link
Contributor

Are you suggesting that without this phrase in the Standard text, readers might assume that the side effects of a destructor occur even when that destructor is never called?

No. But readers could assume it's undefined to fail to destroy any object with a non-trivial destructor, even if that destructor has no side effects. That's not a completely unreasonable position.

I think the first half of this sentence:

For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; ...

doesn't allow that interpretation, although it would be clearer if we were to strike the word "explicitly." AFAICS the second half of the sentence exists solely to inform us that implementations don't magically invoke destructors for objects whose lifetime is ended by reusing their storage. What do we all think of:

 For an object of a class type with a non-trivial destructor, the program is not required to
 call the destructor 
-explicitly 
 before the storage which the object occupies is reused or released
+. [Note: The destructor of such an object is not called implicitly.-end note]
-; however, if there is no explicit call to the destructor or if a delete-expression
-(7.6.2.5) is not used to release the storage, the destructor shall not be implicitly called
-and any program that depends on the side effects produced by the destructor has
-undefined behavior.

@jensmaurer
Copy link
Member

jensmaurer commented Sep 27, 2018

Another, harsher, interpretation of the "undefined behavior" part is that your compiler can rely on the user explicitly calling the destructor for each constructed object before the user ends the lifetime by memory reuse. The suggestions to strike this part from the wording would be a normative change, unfit for an editorial issue.

Feel free to update the pull request with a change for "shall" -> "is" and inserting "would" for the destructor call that doesn't actually happen. Anything beyond that seems to be CWG material; please talk to the -core reflector if you still feel there is an issue.

@jensmaurer jensmaurer added the needs rebase The pull request needs a git rebase to resolve merge conflicts. label Sep 27, 2018
@jensmaurer
Copy link
Member

jensmaurer commented Sep 27, 2018

Leaving editorial scope, I would actually be in favor of turning all "ends lifetime by memory reuse" situations for objects with non-trivial destructors into undefined behavior. This seems the clearer rule and much more in line with our enhanced memory model (e.g. pointers are more than addresses etc.)

@jwakely
Copy link
Member

jwakely commented Sep 27, 2018

I would actually be in favor of turning all "ends lifetime by memory reuse" situations for objects with non-trivial destructors into undefined behavior.

I'm ambivalent. I can imagine using a pmr::monotonic_buffer_resource to allocate objects from a large block of storage, and then at the end of some series of operations declaring everything in that memory to be "dead". You can just use pmr::monotonic_buffer_resource::release() to discard all the memory without doing individual deallocations. It might also make sense to skip running individual destructors. If you started a second series of operations that allocated from that storage again for new objects, you'd be reusing the storage.

@jensmaurer
Copy link
Member

jensmaurer commented Sep 27, 2018

@jwakely: Right, but if you just release() the entire storage range, aren't you relying on the fact that the destructors of the contained objects are trivial? Otherwise, you might be leaking memory or file descriptors or mutex locks. On the other hand, if your destructors are, in fact, trivial, nothing is lost by calling them (they will be no-ops anyway, and the calls very likely optimized away).

@jwakely
Copy link
Member

jwakely commented Sep 27, 2018

They could be empty, but not trivial. Or they might have side effects, but sometimes you might explicitly want to avoid the side effects. Maybe the destructors perform a "commit" step, and if you want to abandon the set of operations without committing, you just "leak" all the objects, so nothing is committed. I'm not saying that's the best way to design the system, but I bet somebody's done it, and do we gain anything by making that undefined? As long as nobody ever tries to access the old objects after the storage is reused, it will Just Work.

(This of course brings up the question again of what it means to "depend" on the side effects, or to depend on the lack of them.)

@jensmaurer jensmaurer added the decision-required A decision of the editorial group (or the Project Editor) is required. label Sep 27, 2018
@AlisdairM
Copy link
Contributor

I can guarantee at least one large scale C++ user has a non-trivial amount of code that relies on ending the lifetime of objects (with non-trivial destructors) by re-using storage is well-defined behavior. This is explicitly one of the motivations behind re-using memory pools with pmr allocators, where you determine everything in the pool has destructors that only release memory also in the pool - and so simply reclaim the pool when the whole data structure is no longer needed. Any change introducing broad UB here would be Core/EWG material, and likely contentious.

@jensmaurer jensmaurer added changes requested Changes to the wording or approach have been requested and not yet applied. and removed needs rebase The pull request needs a git rebase to resolve merge conflicts. labels Oct 10, 2018
@jensmaurer
Copy link
Member

jensmaurer commented Nov 6, 2018

Editorial meeting: The standard rules cannot depend on the programmer's intent. Pass to CWG with the intent of applying as-is.

@jensmaurer jensmaurer added cwg Issue must be reviewed by CWG. and removed changes requested Changes to the wording or approach have been requested and not yet applied. decision-required A decision of the editorial group (or the Project Editor) is required. labels Nov 6, 2018
@jensmaurer jensmaurer added the not-editorial Issue is not deemed editorial; the editorial issue is kept open for tracking. label Feb 24, 2019
@jensmaurer jensmaurer added the needs rebase The pull request needs a git rebase to resolve merge conflicts. label Nov 21, 2019
@frederick-vs-ja
Copy link
Contributor

frederick-vs-ja commented Dec 15, 2021

Is there any CWG issue submitted? Currently partially addressed by CWG2523 (unforturnately something covered by CWG2361/LWG3652 is talked in that issue, which is my fault).
This UB is exposed to constant evaluation since C++20 (via P0784R7), but the diagnostic for it seems impossible (roughly equivalent to the halting problem), and AFAIK currently no compiler diagnoses it.


Edit: CWG2523 is modified now to fully address this issue.

@jensmaurer jensmaurer changed the title [basic.life] Remove description of impossible UB [basic.life] Remove description of impossible UB CWG2523 Jun 30, 2022
@frederick-vs-ja
Copy link
Contributor

This should has been superseded by commit c755814 (in PR #6118).

@jensmaurer
Copy link
Member

CWG2523 has been applied.

@jensmaurer jensmaurer closed this Mar 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cwg Issue must be reviewed by CWG. needs rebase The pull request needs a git rebase to resolve merge conflicts. not-editorial Issue is not deemed editorial; the editorial issue is kept open for tracking.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants