A customer wanted to log exceptions that emerged from a function, so they used the WIL scope_exit
object to specify a block of code to run during exception unwinding.
void DoSomething() { auto logException = wil::scope_exit([&] { Log("DoSomething failed", wil::ResultFromCaughtException()); }); ⟦ do stuff that might throw exceptions ⟧ // made it to the end - cancel the logging logException.release(); }
They found, however, that instead of logging the exception, the code in the scope_exit
was crashing.
They debugged into the ResultÂFromÂCaughtÂException
function, which eventually reaches something like this:
try { throw; } catch (⟦ blah blah ⟧) { ⟦ blah blah ⟧ } catch (⟦ blah blah ⟧) { ⟦ blah blah ⟧ } catch (...) { ⟦ blah blah ⟧ }
The idea is that the code rethrows the exception, then tries to catch it in various ways, and when it is successful, it uses the caught object to calculate a result code.
And that’s where the problem lies.
It’s sort of implied by the name ResultÂFromÂCaughtÂException
that it tries to calculate a result from an exception that was caught. But the scope_exit
functor is called during unwinding that results from an uncaught exception.
There is no caught exception to get a result from!
The C++ language says that a rethrowing throw
rethrows the exception that is being handled, where “being handled” roughly means “is executing the body of its catch
clause”. If you try a rethrowing throw when there is no exception being handled, then it’s straight to jail. (Formally, std::
.)
The solution, then, is to put the ResultÂFromÂCaughtÂException
somewhere inside a catch
block, like perhaps this:
void DoSomething() { try { ⟦ do stuff that might throw exceptions ⟧ } catch (...) { Log("DoSomething failed", wil::ResultFromCaughtException()); throw; }) }
After logging the exception, we rethrow it so that the search for a handler can continue.
Bonus chatter: You can avoid a layer of indentation by using function-try.
void DoSomething() try { ⟦ do stuff that might throw exceptions ⟧ } catch (...) { Log("DoSomething failed", wil::ResultFromCaughtException()); throw; }
Ouch, so I guess C++ doesn’t have any way to let destructors attach info to an in-flight exception, you always have to wrap code in a try block outside the destructor? I actually didn’t know that, how unfortunate. I suppose the next best thing is wrapping everything in a lambda that is called by a helper function that wraps the lambda call in a try block so it can attach info in one or more of the catch clauses or rethrow it as a nested exception.