C++ exception implementation on Windows
On windows c++ exceptions are implemented using an exception facility provided by the OS. The OS exception system (Structured Exception Handling or SEH) does not have the same semantics as C++ exception handling.
Windows SEH works in C or assembly code as well as C++. the C++ compiler and the OS are both involved. SEH is used to report many runtime errors and logic errors in code. Often they are unrecoverable. Access Violation, Stack Overflow, RPC errors, are all reported using SEH exceptions. Here is some code that uses SEH
LONG WINAPI MyExceptionFilter(
__in struct _EXCEPTION_POINTERS *ExceptionInfo,
__in DWORD ExceptionCode
)
{
// code in an exception filter must be
// very robust, careful and have minimal
// dependencies. often t\filters are run
// at times of extreme duress or with
// malicious intent
//
// must return one of these three values:
// EXCEPTION_CONTINUE_EXECUTION
// re execute the instruction that
// triggered the exception. if it
// fails again the new exception
// will be marked
// EXCEPTION_NONCONTINUABLE_EXCEPTION
// EXCEPTION_CONTINUE_SEARCH
// call the next registered
// exception filter
// EXCEPTION_EXECUTE_HANDLER
// run all the finally blocks for
// nested try's and then runs the
// __except block and then runs
// the finally block and then
// continues after the __try/
// __except/__finally
return EXCEPTION_CONTINUE_SEARCH;
}
void* ptr = nullptr;
__try
{
ptr = malloc(1);
if (!ptr) {
__leave;
}
Work(ptr); // might AV or overflow stack, etc..
// or explicitly call RaiseException() to
// raise an SEH
} __except (
// registers MyExceptionFilter for any SEH
// that occurs in the __try
MyExceptionFilter(
GetExceptionInformation(),
GetExceptionCode()))
{
// MyExceptionFilter returned
// EXCEPTION_EXECUTE_HANDLER
}
__finally()
{
// the stack is unwinding due to a return
// or an exception that is handled here or
// up the stack from here
if (ptr) {
free(ptr);
}
}
Hopefully it is obvious that an SEH exception will not cause C++ destructors to be called without some more work. The C++ compiler supports __try/__except/__finally and also supports try/catch, but not in the same function. Also, the C++ compiler will not allow __try/__except/__finally to be used in a function that has a local variable with a destructor.
Disclaimer - this is inaccurate yet provides a mental picture suitable for reasoning about the intersection of SEH and C++. - When the C++ compiler sees a try it inserts a __try, and an __except with a filter that is passed information about the catch clauses and catch blocks and destructors in scope. The C++ compiler replaces each throw with a RaiseException() that stores the exception object in the SEH ExceptionInformation. Then the SEH mechanism calls the registered filter and the filter tries to match the catch block type to the exception object type. If a catch block matches the filter calls the appropriate destructors and the appropriate catch block and then returns EXCEPTION_EXECUTE_HANDLER to continue after the catch block.
The C++ compiler also does not implement throw() semantics. Using what we know we can implement those semantics:
#define FAIL_FAST_FILTER() \
__except(FailFastFilter(GetExceptionInformation())) \
{ \
} do {} while(0,0)
inline
LONG WINAPI FailFastFilter(
__in struct _EXCEPTION_POINTERS* exceptionInfo)
{
RaiseFailFastException(
exceptionInfo - > ExceptionRecord,
exceptionInfo - > ContextRecord,
0);
return EXCEPTION_CONTINUE_SEARCH;
}
template<typename Function>
auto FailFastOnThrow(
Function && function) -> decltype(
std::forward<Function>(function)())
{
//
// __ try must be isolated in its own
// function in order for the compiler
// to reason about C++ unwind in the
// calling and called functions.
//
__try {
return std::forward <Function >(function)();
}
FAIL_FAST_FILTER();
}
#define FAIL_FAST_ON_THROW(Function) \
FailFastOnThrow((Function))
HRESULT MyDllExport()
{
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;
hresult = Work();
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;
}
Next time we will build error contract functions to reduce the amount of code in each export.