catch considered harmful

Spot the bug:

void CFoo::Bar() {

    m_array1[m_i++] = null;

    m_array2[m_j++] = null;

}

I’ll give you a hint – it relates to my last posting about “i = i +1;” being a bug.

One answer was to let the increment overflow.  Well in that case the bug seems pretty obvious.  Another answer is that you just have to be really careful.  Nobody wants to be careful any more when programming.  The remaining reasonable option was that integer overflows raise exceptions.

Ok, let’s explore that option.  Let’s say that the second of the two increments throws.  As long as the exception isn’t caught, everything’s OK – the failure was critical, the process terminated without any code attempting to rely on the (invalid) invariants in the CFoo object…

But why throw an exception if you don’t expect it to be caught?  Shouldn’t you call exit()/TerminateProcess()/whatever to just terminate the process?

So if you’re interested in throwing the exception you maybe should have written:

void CFoo::Bar() {

    int i = m_i++;

    try {

    Object o1 = m_array1[i];

        try {

            m_array1[i] = null;

            int j = m_j++;

            try {

                m_array2[i] = null;

            } catch {

                m_j = j;

                throw;

            }

        } catch {

            m_array1[i] = o1;

            throw;

        }

    } catch {

        m_i = i;

        throw;

    }

}

Well that’s still not quite right either; both GetValue and SetValue on System.Array may throw exceptions so even the recovery code may fail in which case the global invariants were not restored.  C++ has a hope of getting this right; let’s assume that the types are a value type with overloaded operators to throw on overflow…

void CFoo::Bar() {

    CResetter<CSizeT> r1(m_i);

    CResetter<CSizeT> r2(m_j);

    CObject *pArray1 = m_array1; // could throw

    CObject *pArray2 = m_array2; // could throw

    CResetter<CObject> r3(pArray1[m_i]);

    CResetter<CObject> r4(pArray2[m_j]);

    pArray1[m_i++] = null;

    pArray2[m_j++] = null;

    r1.Disarm(); r2.Disarm();

    r3.Disarm(); r4.Disarm();

}

Who writes code like that?  Who wants to write code like that?  There’s actually no way to do this “right” in the current CLR class library design since there’s no operator on System.Array that returns a reference which is the key to being able to do this in C++.

If someone catches the exception raised in the first case, whose bug is it?  The implementor of CFoo might say “well, I don’t expect anyone to catch exceptions my code throws” but the fact is that there are a lot of try / catch blocks sitting in interesting places like thread pools etc.

People mistakenly believe that there are kinds of exceptions which are uncatchable.  The truth is that there is a contract from the thrower through all the intervening frames to the catcher.  Anyone writing reusable code needs to understand this contract but do we really expect all the code in the world to be this rigorous and convoluted?

Note that the thread pool’s catch (Exception e) causes intervening unwinders to run, even if the exception is to be later propagated.  Running any code while invariants are false is dangerous at best.

Most of the exception literature came from people working in the areas of functional languages.  In a functional language this issue is moot since there weren't persistent side effects.  (I also worked on a programming environment once where each frame was a nested transaction and unwinding through a frame typically just rolled back the appropriate nested transaction.  But nobody wants to pay that kind of per-frame costs.)

I come back to postulate 1: programming is hard.  Programming shared components is harder.