Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is “exception vomiting”?

Recently on CodeReview.SE, I came across an answer which talks about a technique called “exception vomiting”. Apparently this trick is used to exploit that exceptions have to be implemented in a thread-safe manner independent of whether the compiler supports thread_local variables.

I paste part of this answer below:

There's an existing technique which is not dissimilar referred to as "exception vomiting". Observe:

void f(void(*p)()) {
    p();
}
template<typename F> void real_f(F func) {
    try {
        throw func;
    } catch(...) {
        f([] {
            try {
                throw;
            } catch(F func) {
                func();
            }
        });
    }
}

This abuses the fact that the compiler must provide a thread-local stack for complex objects for use as exception storage, regardless of their support for other thread local features, and therefore enjoys much broad compiler support. The most obvious drawbacks are a) it's horrible, and b) it's limited to stack semantics.

My question is, how does that trick actually work and is it “safe”?

like image 796
Henri Menke Avatar asked Jul 11 '17 09:07

Henri Menke


1 Answers

This technique relies on the fact that exceptions must be implemented in a thread-safe manner in order for exceptions to be usable in a multi-threaded application. Even pre C++-11 compilers supported thread safe exceptions before threads became a part of the C++ standard.

Each thread throw/catch exceptions independently of other threads by using thread-specific storage for storing exceptions. throw without an argument rethrows the current exception stored in that thread-specific storage. This storage for the exception is used to store the function with its captured arguments (stateful lambda or any other callable).

The drawback of this technique is that throwing an exception normally involves a memory allocation, so it adds the overhead of new/delete invocations.


Another way to implement this is to use a non-portable but widely supported __thread storage specifier. This avoid the overhead of dynamic memory allocation:

void f(void(*p)()) { // The C-style function.
    p();
}

__thread void(*function)();

template<class Function>
void adapter() {
    (*reinterpret_cast<Function*>(function))();
}

template<typename F>
void invoke_f(F const& func) {
    function = reinterpret_cast<void(*)()>(&func);
    f(adapter<F const>);
}

int main(int ac, char**) {
    invoke_f([ac]{ std::cout << ac << '\n'; });
}
like image 73
Maxim Egorushkin Avatar answered Nov 14 '22 16:11

Maxim Egorushkin