Conversation
| ~HalfedgeDS_list() noexcept { | ||
| try { | ||
| clear(); | ||
| } catch (...) {} | ||
| } |
There was a problem hiding this comment.
How is it possible that a clear() function can trigger? What does it do?!
There was a problem hiding this comment.
... Oh... is it about assertions in the clear() functions, that can trigger?
There was a problem hiding this comment.
precisely, they are all from CGAL_assertion. I didn't think the overhead of this code in release builds was significant, to warrant using CGAL_assertion_code
|
There are conflicts with master |
0f03798 to
cdd577d
Compare
|
This PR, and particularly 9f9e1e2 is the one responsible for all the compilation errors about |
Ah, yes. When the class inherits from another that has a ~Handle_for() noexcept
{
try {
if (--(ptr_->count) == 0) {
Allocator_traits::destroy(allocator, ptr_);
allocator.deallocate( ptr_, 1);
}
} catch(...) {}
}This means you lose the exception in the destructor entirely, but its better than program termination which is what happens when you throw from a noexcept method. |
cdd577d to
1373150
Compare
|
@lrineau Fixed as I proposed above. |
|
@GilesBathgate Where can we see your fix ? The force push kind of hides the modifications |
|
|
Thanks ! |
| } | ||
|
|
||
| ~Handle_for() | ||
| ~Handle_for() noexcept |
There was a problem hiding this comment.
That's useless, it is noexcept by default.
There was a problem hiding this comment.
It's probably a hang-over from when instead I'd had CGAL_NOEXCEPT(CGAL_NO_ASSERTIONS_BOOL)
I've since found some more uncaught exception issues with the static analysis on the latest runs (where I upgraded from 5.0.3 to master) I will clean this up along with the others and put them in a new PR.
| try{ | ||
| if (--(ptr_->count) == 0) { | ||
| Allocator_traits::destroy(allocator, ptr_); | ||
| allocator.deallocate( ptr_, 1); | ||
| } | ||
| } catch(...) {} |
There was a problem hiding this comment.
I am not going to fight it, but I want to mention that I am not fond of this new code
- it is ugly
- it goes against the default specified by the language
There was a problem hiding this comment.
Many of the destructors in the CGAL codebase can throw because of a CGAL_assertion, so when it calls destroy/deallocate here the program will terminate. (presumably, that's not desirable, that's what the change intends to prevent)
In most places, this is handled by a CGAL_destructor_assertion_catch which preserves the exception when possible. But here because the noexcept is enforced by a base class we can only swallow the exception.
However you might agree that the correct way to solve all these problems, is never to throw from a destructor in the first place. Then there wouldn't have to be workarounds for the default behaviour specified by the language.
There was a problem hiding this comment.
Many of the destructors in the CGAL codebase can throw because of a CGAL_assertion, so when it calls destroy/deallocate here the program will terminate. (presumably, that's not desirable, that's what the change intends to prevent)
It isn't obvious that it isn't desirable. The standard didn't gratuitously introduce std::terminate, it was better in those circumstances than data corruption or security holes.
In most places, this is handled by a
CGAL_destructor_assertion_catchwhich preserves the exception when possible. But here because the noexcept is enforced by a base class we can only swallow the exception.However you might agree that the correct way to solve all these problems, is never to throw from a destructor in the first place. Then there wouldn't have to be workarounds for the default behaviour specified by the language.
And assertions are not supposed to trigger, so that's good, no need to introduce workarounds for the case where they trigger 😉
Presumably, we aren't checking preconditions on user input in those destructors. If we assert, it probably means a bug in CGAL, or a side effect of some memory corruption, anyway something bad. We can disable those checks, as your patch does, that makes your checker happy and possibly fails to detect an issue. Or we can have checks that abort if they cannot throw exceptions, which is what the language specifies.
I wonder if separating more clearly debug assertions from checks that can make sense for users would reduce the number of destructors affected.
Some assertions are meant to protect from users who don't know what they are doing, say a user constructing Interval_nt(3,2). CGAL never constructs an interval with a>b, but since CGAL and the user's code call the same constructor, they go through the same assertion, and it looks like the CGAL could throw although it won't. If this happens in a noexcept function (like a destructor), the compiler has to generate instructions on what to do with that non-existing exception, for instance ignore it or abort. The amount of code is essentially the same, there shouldn't be much impact on performance, and since the exception never happens, the behavior is also the same. One code is simpler (no need for try-catch), and on the off chance some memory corruption causes the assertion to trigger, it will safely abort instead of executing random instructions.
Anyway, I said I won't fight...
There was a problem hiding this comment.
I appreciate your input @mglisse . It's always worthwhile having a discussion even if there are differences in opinion.
There was a problem hiding this comment.
Presumably, we aren't checking preconditions on user input in those destructors.
Not a destructor, but here is a noexcept method with a CGAL_precondition
cgal/STL_Extension/include/CGAL/Handle.h
Lines 70 to 79 in 77e5d1d
There was a problem hiding this comment.
Not a destructor, but here is a noexcept method with a
CGAL_precondition
Yes, I think I remember adding this noexcept and wondering about the precondition. In the end, I was ok with having the program terminate if a null pointer was found in debug mode. The precondition isn't there in release mode. Not testing in debug mode would be a regression. Making the function conditionally noexcept would have been an option, but it can change the path taken by the code, and it felt more useful to lie a bit and have debug mode test something closer to release mode, than to insist on propagating the error as an exception. Of course different opinions are always possible.
There was a problem hiding this comment.
Making the function conditionally noexcept
In our usage of the CGAL library (RapCAD and OpenSCAD) we enable CGAL_precondition in release mode, so this would be more suitable for us I think.
There was a problem hiding this comment.
Can't you check the prerequisites in inputs before calling the functions? Or is it too expensive compared to do a try-catch?
There was a problem hiding this comment.
Can't you check the prerequisites in inputs before calling the functions?
Not sure I follow what you mean @sloriot
Summary of Changes
In a previous issue ( #451 ) a
CGAL_destructor_assertionmacro was added to handle assertions made within destructors. When an exception is thrown from a destructor while the stack is being unwound, the existence of any uncaught exceptions cause the program to terminate immediately, without correct error handling. Sometimes it's safe to throw an exception even while std::uncaught_exception() == true. For example, if stack unwinding causes an object to be destructed, the destructor for that object could run code that throws an exception as long as the exception is caught by some catch block before escaping the destructor. That said coverity static analysis has found several places in the codebase where this is not the case, i.e the destructor calls some code which makes aCGAL_assertion()of some form or another, but doesn't catch the exception.I propose a new macro:
This preserves the assertion exception by rethrowing it if the stack is not being unwound and there are no uncaught exceptions. But in the case where we are recovering from an exception already, only that exception is preserved. A program which demonstrates this behaviour can be seen here: http://cpp.sh/6p7ka
This macro has then been applied to all places identified by static analysis as potentially throwing exceptions from CGAL_assertions. As before ( in #451 ) the
CGAL_NOEXCEPT(CGAL_NO_ASSERTIONS_BOOL)macro has to be applied, where needed.An additional Macro
CGAL_destructor_warningwas also added, this replicates the behaviour ofCGAL_destructor_assertion, except emits a::CGAL::warning_failRelease Management