A common use of catch(...)
, even in the STD library implementation, is to free objects or to return to a known state on failure.
catch(...)
has no information about the exception and therefore there is no way to reason about whether the code in the catch block will corrupt the process. Thus any code in a catch(...)
is a potential danger to the process and the data it manages.
Whenever an exception is thrown and the search for a catch encounters a catch(...)
block it must run all the destructors for the code inside the matching try
to get the stack back to the correct state to run the catch
block. At best this modifies the state, when it doesn't corrupt it. Then the catch block is likely to immediately rethrow the exception with the state at the most relevant part of the stack (where the throw
occurred) erased.
This erasure is particularly unwanted when the exception is ultimately not handled and a Windows Error Report is generated with a dump of the state after the unhelpful catch(...)
erased the part that would be most important to determine how to fix the bug.
Windows Error Reporting (WER) has changed the way I write code. I try to exit the process quickly (FAIL_FAST) whenever I reach an unexpected state. When there are no catch(...)
blocks in the way I get great bugs generated directly from the WER with no repro necessary and I quickly issue a fix.
The good thing is that none of the catch(...)
blocks are needed and that the replacements do not corrupt the state when the exception type is not known to the call stack. The common case where you might use catch(...)
to free an allocation or restore state can be replaced by an unwinder. In the case where catch(...)
is used to suppress an exception it should be replaced with a FAIL_FAST_ON_THROW([]{});
. This will not suppress the exception (which as described earlier invites data corruption), instead when an exception that is not expected and handled reaches this barrier a WER will be generated and the process will exit without any corruption.
These boundaries can be enforced using error contract functions:
template<typename Function> HRESULT HResultErrorContract(Function && function) { HRESULT result = S_OK; FAIL_FAST_ON_THROW( [&] { // any C++ exception that is uncaught or any SEH // will cause the process to exit try { unique_hresult hresult = ( std::forward<Function>(function)()); result = hresult.get(); } catch (const std::bad_alloc&) { result = E_OUTOFMEMORY; } catch (const unique_winerror::exception& e) { result = HRESULT_FROM_WIN32(e.get()); } catch (const unique_hresult::exception& e) { result = e.get(); } } ); return result; } HRESULT MyDllExport() { // no exception can get past // HResultErrorContract return HResultErrorContract( [&]() -> unique_hresult { // It is safe to throw C++ exceptions unique_hresult hresult; // RestoreState is only called on failure ON_UNWIND(unwindWork, []{ RestoreState(); }); hresult = Work(); if (hresult.ok()) { unwindWork.Dismiss(); } return hresult; } }
No comments:
Post a Comment