3

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?

Community
  • 1
  • 1
jchl
  • 6,332
  • 4
  • 27
  • 51
  • 5
    Surely any solution is implementation specific and differs for different compilers, architectures etc. – David Heffernan Aug 09 '11 at 13:27
  • 9
    Just curious why it matters? –  Aug 09 '11 at 13:29
  • 7
    You most certainly cannot do it in *standard* C++. And I don't know any particular solutions for your implementation, but I am also sure that you're going to do something really ugly here. Don't abuse exceptions. Exceptions should be thrown in exceptional situations, regardless of whether these are handled or not – Armen Tsirunyan Aug 09 '11 at 13:32
  • @David Heffernan: Yes, I expect the solution will have to be compiler- and perhaps architecture-specific. I'm looking for a solution that will work with gcc on Linux (x86_64). – jchl Aug 09 '11 at 13:33
  • @Code Monkey: What we're attempting to do is to get a stack trace at the point when an uncaught exception is raised. Normally, this could be done with a termination handler. However, in the environment in which our code operates, for reasons outside our control there is a `catch( ... ) { abort(); }` at top-level. We want to modify our exception class constructors to detect whether the exception is going to be caught by some more specific handler and to abort() immediately in the constructor if not. Yes, it's horrible. But if it's possible, it's probably the best solution to our problem. – jchl Aug 09 '11 at 13:37
  • I see nothing in the documentation nor in `cxxabi.h` header which is the place where I'd expect to find such thing if there was an implementation defined API for it. You can now look at the run-time... – AProgrammer Aug 09 '11 at 13:47
  • 6
    @jchl this is an example where it may have served you better to ask how to solve the root problem (want to know stack dumps from the place the exception is thrown rather than where it is caught/when abort is called) instead of asking how to implement the solution you thought of. – SoapBox Aug 09 '11 at 13:57
  • @SoapBox: Possibly you are right, though even if there is a better solution to the root problem, the particular question I posed here is still an interesting one. – jchl Aug 09 '11 at 14:14

6 Answers6

4

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();
}
Dave S
  • 20,507
  • 3
  • 48
  • 68
  • 1
    According to the OPs comments, though, it isn't an unhandled exception. There is a global `catch( ... ) { abort(); }` block, so the `std::terminate_handler` won't be called. – rcollyer Aug 20 '11 at 03:52
2

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.

Flexo
  • 87,323
  • 22
  • 191
  • 272
  • Interesting idea, though it doesn't address the original problem (in particular, needing to distinguish between whether the exception is caught by a `catch(ExceptionClass e)` or a `catch(...)` block), and the performance would likely be worse than just caching a stack trace in the constructor of every exception. – jchl Aug 22 '11 at 09:59
  • If you were willing to modify the `catch(...)` block to play along it would work, but I think you're right about the performance. This was mostly an "ooh I wonder if I could make that idea work like that" moment for me. – Flexo Aug 22 '11 at 10:19
2

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.

shelleybutterfly
  • 3,216
  • 15
  • 32
  • 1
    This code looks hideous. The macros make things worse rather than better. And this doesn't answer the original question. However, it does address the underlying problem (which is better than any of the other answers) and is actually rather similar to the solution we're planning to implement (though we're using RAII to modify the stack). +1 for suggesting using a pre-processor to modify all try..catch blocks -- I have to think about that. – jchl Aug 22 '11 at 10:09
  • :) glad you're on your way to a solution at least! and I totally agree, I actually did things like this on a fairly small scale and it was horrid. it's impossible to debug, impossible to read (no highlighting or intellisense). for a large scale system I bet you lose more time than is gained from having your environment info. so, I totally agree with you that in this form it's not a solution. I so wish for meta-programming in comments; (an example like that in the CS-Script stuff makes me drool). I think soon I will write a general purpose one like that, but a one-off should work for you. – shelleybutterfly Aug 22 '11 at 10:46
1

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),

Community
  • 1
  • 1
SoapBox
  • 20,457
  • 3
  • 51
  • 87
  • Building a stack trace using `backtrace`/`backtrace_symbols` in the exception constructor is a possible solution (one which we have already implemented, in fact), but the performance impact for exceptions that are handled is too great. You could argue that we are using exceptions "wrong", but again, this is something that is out of our control. – jchl Aug 09 '11 at 14:06
  • @jchl I would assume you would leave the backtrace code off in a release/production build and only use it for debug. The question then becomes why are you concerned about the speed of your debug build? – SoapBox Aug 09 '11 at 23:26
  • 1
    No, we enable backtraces in production as well. Production code is never entirely bug free, and when you hit a bug in production it's even more important to have a backtrace to help debugging. – jchl Aug 15 '11 at 12:35
0

"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 ).

MSalters
  • 173,980
  • 10
  • 155
  • 350
-1

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;
    }

}