// Andy Friesen --

Using C++ Macros to Inline Repetitious Code

11 Feb 2009

One of the neat things the IMVU client has is balls-awesome crash handling. We do a number of things to ensure that, any time our client crashes for any reason, that we find out as much as we can about them. This serves a bunch of purposes: we use the raw number of crashes to determine whether or not we have broken something, and we use all the information we can get out of the crashes (stack traces, memory dumps, log files, and the like) to fix them.

One of the tricks to testing crash handling is that you can’t really do it without crashing. :) To that end, we have a large array of functions that exist only to crash our client. Moreover, we have parameterized them on the context in which we would like the crash to occur: with Python on the stack, without, from a WndProc, from within an exception handler, and so forth

This would be a terrible source of duplicated code, but for a satanic little trick that you can do with the C preprocessor. Since I’m such a nice guy, I’ll even tell you what it is right up front, so you can skip the rest of this post if you want:

Macros can be macro arguments.

Here’s how it works:

First, we’re going to define a list of crashes, using the C preprocessor:


#define CRASH_LIST(F)               \
    F(WriteToNull)                  \
    F(ReadFromNull)                 \
    F(JumpToNull)                   \
    F(CallPureCall)                 \
    F(ThrowFromExceptionHandler)    \
    F(DestroySun)

Weird, right? Right. Now, the first thing we have to do with all of these crashes, is declare functions in a header file:

#define DECLARE_CRASH(T)       \
    void T();

CRASH_LIST(DECLARE_CRASH)

Next, we need to declare functions that crash within a WndProc:

#define DECLARE_WNDPROC_CRASH(T)       \
    void T ## InWndProc();

CRASH_LIST(DECLARE_WNDPROC_CRASH)

Then, we need to implement the crashes themselves:

void WriteToNull() {
    *((int*)0) = 1234;
}

/* etc */

Yeah, we can’t use the fun macro trick this time. :( You can’t win them all, I guess. Next, crashing in WndProc:

HWND createCrashWindow() { /*this is boring win32 gook */ }

LRESULT crasherWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    if (msg == IMVU_WM_CRASH) {
        void (*crashFunc)() = reinterpret_cast<void (*)()>(wParam);
        crashFunc();
        return 0
    } else {
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }
}

void crashInWindow(void (*crashFunc)()) {
    HWND hWnd = createCrashWindow();
    PostMessage(hWnd, IMVU_WM_CRASH, reinterpret_cast<WPARAM>(crashFunc), 0);
}

#define IMPLEMENT_WNDPROC_CRASH(T)  \
    void T ## InWndProc() {         \
        crashInWindow(&T);          \
    }

CRASH_LIST(IMPLEMENT_WNDPROC_CRASH)

And lastly, we need to be able to call all of these functions from Python:

#define IMPLEMENT_BOOST_PYTHON_CRASH(T)    \
    def(#T, &T);                        \
    def(#T "InWndProc", &T ## InWndProc);

CRASH_LIST(IMPLEMENT_BOOST_PYTHON_CRASH)

Now, I’ll be the first one to wail in terror at how theoretically terrible the C preprocessor is, and what it does to maintainability, but, in this case, at least, the payoff is undeniable: with very small effort, and without having to build some kind of “CrashRegistry” framework, we can add additional crash cases to our client.