Opened 3 years ago

Last modified 3 years ago

#233 new defect

Colliding Exceptions Not Detected

Reported by: ajbeach Owned by:
Priority: minor Component: cfa-cc
Version: 1.0 Keywords:


Considering the following function:

void too_many(void) {
    try {
        throw exception_zero;
    } finally {
        throw exception_one;

Here what exception does this function throw? Both actually, unless the entire program to aborts before we get to throwing exception_one. The problem is if exception_zero is caught the stack will be unwind and run throw exception_one, if it is caught it will be caught outside the finally clause. The handler of exception_one cannot return control to the finally block so we cannot resume handling exception_zero (even if it's handler still exists) and there is a section of stack that wants to be unwound twice.

I believe this can only happen in code that is run during an unwind operation. Which means finally clauses and destructors. I do not believe there is a general way of handling this so I recommend the C++ approach and abort the program if it happens.

In terms of style I would recommend never throwing in a finally or destructor, but function calls make that impossible to check. So this might have to be a runtime check, an extra annotation used but the personality function that says "you shouldn't be unwinding/searching across this line" and aborts if there is an attempt to do so.

Change History (2)

comment:1 Changed 3 years ago by ajbeach

Priority: majorminor

comment:2 Changed 3 years ago by ajbeach

Peter's Comment:
This is what uC++ does.

#include <iostream>
using namespace std;

_Event zero {};
_Event one {};
int main() {
    try {
        try {
            _Throw zero();
        } _Finally {
            cout << "finally" << endl;
            _Throw one();
    } catch( zero ) { cout << "zero" << endl; }
    catch( one ) { cout << "one" << endl; }


@plg2[25]% a.out
uC++ Runtime error (UNIX pid:3458435) Raising an exception in a _Finally clause during exception propagation is disallowed.
Error occurred while executing task program main (0x7fffffffe490).

I did some research on Java and it uses "suppressed exceptions", with Throwable.addSuppressed and Throwable.getSuppressed. On a collision between two exceptions one is given priority and the other is suppressed by passing it to the priority exception's addSuppressed. The exceptions that come from finally clauses have priority unless it is an implicate finally as part of a try-with-resources "try (...) {...}" statement. (Java doesn't have destructors and exceptions from finalizers are ignored.)

Last thing is there was some conversation about why I recommended aborting on an exception escaping a destructor or finally (I'll just say finally for the rest) and not just on a collision between two exceptions. That is if in a finally you through an exception safely you need to first check you are not already unwinding the stack (including cancellation) before you throw. This can be easy to forget. Second you need a second alternate way to handle the error for when it is true. So why not just use that all the time? More general and involves less code. Admittedly I have never seen a disciplined instance of this ever so I am guessing a bit.

Note: See TracTickets for help on using tickets.