jchl
jchl

Reputation: 6532

How can I determine at runtime whether there is a catch block for a particular C++ exception class?

On Linux, I would like to be able to determine whether a catch block for a particular class of exception (or a superclass of that class) is currently in scope (ignoring catch all blocks).

In particular, I would like to be able to implement the isThereACatchBlock function to behave as follows:

bool isThereACatchBlock( std::type_info const & ti ) {
    ...;
}

class MyException {
};

class MyDerivedException : public MyException {
};

class MyOtherException {
};

void f() {
    try {
        isThereACatchBlock( typeid( MyException ) ); // Should return true
        isThereACatchBlock( typeid( MyDerivedException ) ); // Should return true
        isThereACatchBlock( typeid( MyOtherException ) ); // Should return false
   } catch( MyException const & e ) {
   } catch( ... ) {
   }
}

I know that the system has this information, in order that it can implement exception handling correctly -- I believe it's stored in the .eh_frame and/or .gcc_except_table sections, as described in this post. However, I'm not sure if there is any easy(-ish) way for a program to interpret that information. Can anyone help?

Upvotes: 4

Views: 623

Answers (6)

shelleybutterfly
shelleybutterfly

Reputation: 3247

My thought is this as follows. Please keep in mind I am writing all code for this on the fly so it may not be perfect. :)

It's actually probably easier to explain in code, but I'll try to give the gist first. Since you are not interested in catch (...) I have not focused on detecting that, however, I think it would be relatively easy to modify the idea to handle that as well. (Note: originally I was going to use a pointer to the function as the way to tell which function you are in, but I have ended up going with the name because I wasn't thinking about virtual functions. I'm sure this can all be optimized if necessary though.)

Create the following:

  • a class with static versions of your desired functions
  • a special custom "stack" type to hold your exception information with operations that can tear the stack down based on that info
  • a struct containing a type strings and a void pointer string to hold the name of the function it was created in

Setup:

  • prior to your try, place a catch type struct with the type name being caught as well as with all exceptions caught in this function on the stack, along with a pointer to the name of the function.
  • the string to set for type is determined by quoting the type (so "..." is fine for default catches).
    • initially I was playing around with the idea of using typeid to get the undecorated type names, then using .raw_name() to get the mangled name
    • but it won't work for native types, or for non-virtual types, and there's really no need for the mangled names, so it's kind of pointless for this implementation

Teardown:

  • in every catch block, at the top, tear the stack down one beyond the type you are catching for the function you are in
  • following the last catch blocks in a function, tear the stack down one beyond the first instance of the teardown in the last catch

The main issue with this solution is that it is clearly very cumbersome.

One solution is [bet you saw this coming] macros.

ExceptionStackHandler.h

// ... 
// declaration of the class with the needed functions, perhaps
// inline definitions. the declaration of the stack. etc.
// ...
#if __STDC__ && __STDC_VERSION__ >= 199901L
    #define FN_NAME __func__
#else
    #define FN_NAME __FUNCTION__
#endif

// was thinking would be more to this; don't think we need it
//#define try_code(code) try { code } 

// this macro wraps the code such that expansion is not aborted 
// if there happen to be commas in the code.
#define protect(code) if (true) { code }

// normal catch and processing
#define catch_code(seed_code, catch_type, catch_code) \
    ExceptionStackHandler.Stack.Push(exceptionItem(#catch_type, FN_NAME)); \
    seed_code \
    catch (catch_type Ex) \
    { \
        ExceptionStackHandler.Stack.PopThrough(#catch_type, FN_NAME); \
        catch_code \
    } 

// you *must* close a try with one of the following two calls, otherwise
// some items may be missed when clearing out the stack

// catch of all remaining types
#define close_catchall(seed_code, last_catch_type, catch_code) \
    seed_code \
    catch (...) \
    { \
        ExceptionStackHandler.Stack.PopThrough(#last_catch_type, FN_NAME); \
        catch_code \
    } \
    ExceptionStackHandler.Stack.PopThrough(#last_catch_type, FN_NAME); \

// cleanup of code without catching remaining types
#define close_nocatch(last_catch_type, catch_code) \
    seed_code \
    ExceptionStackHandler.Stack.PopThrough(#last_catch_type, FN_NAME)

Then in your code it would look like

bool isTheRoofOnFire(bool& isWaterNeeded)
{
    // light some matches, drip some kerosene, drop a zippo

    close_nocatch
    (
    catch_code
    (
    catch_code 
    (
    //--------------------------------------------------------
    // try block for the roof
    try
    {
        protect (
            // we don't need no water
            if (isWaterNeeded)
                isWaterNeeded = false;
        )
    }
    // End Try Block
    //--------------------------------------------------------
    ,
    //--------------------------------------------------------
    // catch(string Ex)
    string,
    protect (
      if (Ex == "Don't let it burn!")
          isWaterNeed = true;

      throw "I put the water on the fire anyway.";
    )
    )
    // END - catch (string Ex) 
    //--------------------------------------------------------
    ,
    //--------------------------------------------------------
    // catch(RoofCollapsedException Ex)
    RoofCollapsedException
    try_code (
        protect (
            if (RoofCollapsedException.isAnythingWeCanDo == false)
                throw new TooLateException(RoofCollapsedException);
            else
                isWaterNeeded = true;
        )
    )
    // END - catch(RoofCollapsedException Ex)
    //--------------------------------------------------------
    )
    // closing without catchall exception handler
    //--------------------------------------------------------
}

Now, I admit, it's ugly. Reeeal ugly. I'm sure there's a better way to write those macros, but as just a theoretical proof-of-concept I don't think there's anything there that doesn't work. But the real solution can't be this hard. The reason it doesn't turn out well isn't that the idea is that ugly. It's just that the macros can't implement it in a clean way. Since it's such a regular pattern there just oughtta be a way to make it happen without even touching your source code. If only the C preprocessor wasn't the only choice...

;) So. Actually I think there may be. A superior solution is to use a more-powerful preprocessor, that gives cleaner C++ code by allowing you to compile even without being additionally preprocessed (e.g. directives as comments). I think that it would be fairly easy to write something up using a tool like CS-Script (which will run under Mono) and I believe some examples are included in the documentation of the 'precompiler' process which lets you do this. And, really, for this: you don't even need directives. Directives would be cool, but you don't need a general purpose macro processor to do what you need. Of course, it goes without saying that you could write it in anything that has the capability to process a text file.

Although I have not tried to implement it yet, I think this could perhaps be just a processor that is run on an entire group of files that require no modifications directly to the code whatsoever. (Find all try/catch blocks in the file, gather up the types, create the extra statements, and write out the file.) Perhaps move the directory the Makefile pulls the build files from, then prior to compiling, process all the files and put the output in the new build subdirectory. I bet LINQ could do it in a few little statements, although that doesn't mean I can write the LINQ. :) I still bet it wouldn't be that big of a task, amd it would be the perfect way to implement the solution; define the stack, the class, and the static checker functions in the class.

Which reminds me... for "completeness":

bool ExceptionStackHandling::isThereACatchBlock(string Type)
{
    return (ExceptionStackHandling.Stack.peekOnType(Type) > 0);
}

So, in closing: I can hardly imagine dealing with code like the code that you end up having with the macros. Now, I didn't indent, and I suppose with that it would become semi-more semi-readable, but the problem has just moved: now if you have seven exception types being handled, you have seven indents pushing everything off the screen. But I do think something can be done with an simple external application that does it all automatically for you.

Upvotes: 2

Dave S
Dave S

Reputation: 21113

Reading one of your comments, I saw that one reason you want to do this is to avoid generating a backtrace for handled exceptions, but unhandled exceptions, you want to have the backtrace.

If that's why you want to do this, you can use std::set_terminate() to set up a termination handler, which is called when an unhandled exception occurs. Testing with my own backtrace handler, the backtrace shows trace all the way up to the throw() that caused the failure, since the throw basically calls the terminate handler directly, after it realizes the exception is not going to be caught.

Note, this only captures the stack from the most recent throw until it's terminated. If you catch and then rethrow the exception, the stack between the initial throw and the catch is unwound and no longer available.

int foo() {
  throw std::runtime_error("My error");    
}
std::terminate_handler old_term_func;
void term_func() {      
  // Insert backtrace generation/output here
  old_term_func();
}

void test() {
  try {
    foo();
  } catch (const std::runtime_error& e) {
    std::cout <<"Exception caught, no backtrace generated" << std::endl;
  }    
  foo(); // Exception not caught, will call term_func    
}
int main() {
  old_term_func = std::set_terminate( term_func ) ;
  test();
}

Upvotes: 4

You can solve this in a very kludgey kind of way by making a "canary" process that tests what happens and reports the result. I put together a "proof of concept" example:

#include <exception>
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>

struct SomeException : public std::exception {
};

template <typename E>
struct isThereACatch {
  isThereACatch() : result(doTest()) {
  }

  operator bool() const {
    return result;
  }

private:
  const bool result;

  struct TestException : public E {
    TestException(int f) {
      fd = f;
    }

    virtual ~TestException() throw() { 
      notify(true);
      exit(0);
    }

    static void notify(bool result) {
      const ssize_t ret = write(fd, &result, sizeof(result));
      assert(sizeof(bool) == ret);
    }

    static void unhandled() {
      notify(false);
      exit(0);
    }

    static int fd;
  };

  static bool doTest() {
    int pipes[2];
    const int ret = pipe(pipes);
    assert(!ret);
    const pid_t pid = fork();
    if (pid) {
      // we're parent, wait for the child to return
      bool caught;
      const ssize_t ret = read(pipes[0], &caught, sizeof(caught));
      assert(sizeof(bool) == ret);
      int status;
      waitpid(pid, &status, 0);
      return caught;
    }
    else {
      // if we are the child (i.e. pid was 0) use our own default handler
      std::set_terminate(TestException::unhandled);
      // Then throw one and watch
      throw TestException(pipes[1]);
    }
  }
};

template <typename E>
int isThereACatch<E>::TestException::fd;

int main() {
  try {
    isThereACatch<std::exception> e1;
    isThereACatch<SomeException> e2;
    std::cout << "std::exception - "  << e1 << std::endl;
    std::cout << "SomeException - " << e2 << std::endl;
  }
  catch (const SomeException& ex) {
  }
  std::cout << "Still running..."  << std::endl;
}

It has the advantage that it's semi-portable. Would I use it in anger though? Probably not. My big concern would be that there might be significant side-effects from some of the exceptions that do weird and wonderful (but unexpected) things. E.g. a destructor which deletes a file or worse. You'll also need to ensure the exceptions you test are default constructible and not primitive types. The other problem with my example is thread safety, more than just the trivial fd static member of TestException - you probably need to make any other threads suspend whilst the canary process is running.

Disclaimer: Doing this is probably a bad idea.

Upvotes: 2

MSalters
MSalters

Reputation: 179859

"I know that the system has this information, in order that it can implement exception handling correctly"

That's untrue. A system may use a true stack for exception handlers, i.e. a stack where you can only access the top exception handler. In C++ as it stands, you never need to know whether another exception handler exists before deciding whether the top one is entered. Either the exception thrown is handled by the top handler, and you use it, ot it's not handled and you pop the top exception handler unused.

In your case, the only exception handler that might be visible to the runtime is therefore catch( MyException ). That means you cannot know that isThereACatchBlock( typeid( MyOtherException ) ); should be false. The only way to access the exception handler "behind" catch( MyException ) might be to throw an exception unhandled by catch( MyException ).

Upvotes: 0

You can play with the rethrow...

void isThereACatchBlock(bool& p_ret)
{
    try
    {
        throw;
    }
    catch(const MyException& p_ex)
    {
        p_ret = true;
        throw;
    }
    catch(const MyOtherException& p_ex)
    {
        p_ret =false;
        throw;
    }
    catch(...)
    {
        p_ret = false;
        throw;
    }

}

Upvotes: -1

SoapBox
SoapBox

Reputation: 20609

I don't know how you could check what catch blocks exist, it would probably take significant effort and would likely break if you changed even minor versions of the same compiler. Your comment indicates what you really want is to get stack traces in the constructors of your exceptions, rather than the location they are caught.

That is actually easy to do in linux/gcc with the backtrace and backtrace_symbols functions GCC will happily give you a stack dump. See also: the man page and this SO question (note the question is about crashes, but you can do this at any time in your program whether it is crashing or not).

This will still generate stack dumps even if the exception was caught by some piece of your code (... or otherwise), but it will let the code continue running rather than calling abort() or terminate(). But you can look through the logs to find which one caused your problem, you shouldn't have that many (if you do, you're probably using exceptions wrong... they are a poor substitute for if/else/while or returning an error code sometimes),

Upvotes: 1

Related Questions