Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Want to know exception type when an exception is thrown. #539

Open
timtro opened this issue Nov 12, 2015 · 13 comments
Open

Want to know exception type when an exception is thrown. #539

timtro opened this issue Nov 12, 2015 · 13 comments

Comments

@timtro
Copy link

timtro commented Nov 12, 2015

Thanks in advance. I love Catch.

I generally try to make numerous exceptions with useful names such as NumberIsWayTooBig(). These inherit directly from std::exception:

class NumberIsWayTooBig : public std::exception() {};

When such an exception is thrown in a Catch test, I get a message:

FAILED:
due to unexpected exception with message:
  std::exception

I then have to write the test in a non-Catch test file to get the type of the exception object as I would normally expect it:

terminate called after throwing an instance of 'NumberIsWayTooBig'
  what():  std::exception

Is there any way to get the type of the particular exception object?

@webmaster128
Copy link
Contributor

Warning: Code untested.

AFAIK "std::exception" is the default return value for what() of an std::exception. So you could go for something like this:

class NumberIsWayTooBig : public std::exception {
public:
    const char* what() const override { return "NumberIsWayTooBig"; }
};

Having a exception message is always a good idea.

Printing the type name from within Catch could be done using typeid(ex).name() which then needs to be demangled. In our project we're using the demangle function from boost, which is not available in Catch.

There are different demangle function, which you can use depending on your target compiler and used libraries. If you have Boost in your code, you could use that to auto-generate the what() text based on the current exception:

class NumberIsWayTooBig : public std::exception {
public:
    const char* what() const override {
        return boost::core::demangle(typeid(*this).name()).c_str();
    }
};

That could be reused if you had a parent exception class for all of your exceptions.

@nabijaczleweli
Copy link
Contributor

Kinky ( ͡o ͜ʖ ͡o)

@PureAbstract
Copy link
Contributor

AFAIK "std::exception" is the default return value for what() of an std::exception

Describing that as the "default" return value is somewhat misleading - it just happens to be what some implementations happen to return (others do different things, e.g. an empty string). The standard describes std::exception::what as returning "an implementation-defined NTBS" (18.8.1), so that's all you can assume.

@philsquared
Copy link
Collaborator

Catch has the ability to translate custom exception types (even those that do not derive from std::exception) into strings.
Unfortunately (a) this was not documented and (b) anything deriving from std::exception was caught first - so only types not deriving from it worked this way.

I've just fixed both those things (in v1.3.0-develop.3)!
See the docs at the end of https://github.com/philsquared/Catch/blob/develop/docs/tostring.md.
There's not much to the docs but I think it's fairly simple. Let me know if you think it needs more.

@webmaster128
Copy link
Contributor

Great, if all that functionality is already available, couldn't we have a default output like

FAILED:
due to unexpected exception of type: Me::MyCalException
with message: divided by zero

@philsquared
Copy link
Collaborator

It does!
Well it doesn't print the type separately from the exception message. To do so would run into the demangling problems you already alluded to. That's not out of the question - but not extra complexity I want to add unnecessarily. So far I'm still falling in favour of not demangling names.

Or is there something else it's not doing that you're missing?

@timtro
Copy link
Author

timtro commented Dec 8, 2015

Sorry, lost track of this for a bit. Thanks Phil ( @philsquared ). I may play with that functionality at some point, but defining behaviour on a per-exception basis doesn't help me if I'm unsure of what exception is being thrown.

In the end, I took the advice of @webmaster128 to overrode the what() method in my exceptions. It means they take up much more real estate in my files, and more effort to write, but it does lead to better code. Writing specialized code to print something useful in unit tests didn't seem to be a good use of the effort.

But really, thanks so much Phil. Again, I really love Catch.

@webmaster128, Thanks for the advice. Also, I believe your override of what() is missing a noexcept specifier.

@webmaster128
Copy link
Contributor

@philsquared What you are missing is that Catch does not print the exception type at all. In any case, only the output of what() is shown.

I'd consider it a an unexpected behavior that GCC's std::exception puts it's name in the what(). According to http://en.cppreference.com/w/cpp/error/exception/exception the what() should be empty.

As you can see in the following output, no exception name is printed (source below).

a.out is a Catch v1.3.0-develop.4 host application.
Run with -? for options

-------------------------------------------------------------------------------
std::exception
-------------------------------------------------------------------------------
extest.cpp:29
...............................................................................

extest.cpp:29: FAILED:
due to unexpected exception with message:
  std::exception

-------------------------------------------------------------------------------
Exception with empty what()
-------------------------------------------------------------------------------
extest.cpp:33
...............................................................................

extest.cpp:33: FAILED:
due to unexpected exception with message:

-------------------------------------------------------------------------------
Exception with custom what()
-------------------------------------------------------------------------------
extest.cpp:37
...............................................................................

extest.cpp:37: FAILED:
due to unexpected exception with message:
  Some whatever message

===============================================================================
test cases: 3 | 3 failed
assertions: 3 | 3 failed

https://gist.github.com/webmaster128/61b16767742fb1f2bfa3

@PureAbstract
Copy link
Contributor

I'd consider it a an unexpected behavior that GCC's std::exception puts it's name in the what()

It's not unexpected - but neither is it expected.. that just happens to be what the version of GCC that you're using today happens to have done today. A different version of GCC, or a different compiler, or another day, may all behave differently.

According to http://en.cppreference.com/w/cpp/error/exception/exception the what() should be empty.

cppreference is mistaken. As I've mentioned previously, the standard says:

virtual const char* what() const noexcept;
Returns: An implementation-defined ntbs.

Which may (or may not) be empty. It may (or may not) be "std::exception". Or it may be "no exception was thrown". Or anything else, really (as long as the implementation defines what it is) - the only thing we can say with any certainty is that it's some null-terminated byte sequence.

@webmaster128
Copy link
Contributor

Thanks @PureAbstract for going into detail. The only point that matters here in case of Catch is that the exception name is not printed, even if it looks like that at first glance.

@cincodenada
Copy link
Contributor

cincodenada commented Jan 15, 2019

Did the stringification ability mentioned above make it to Catch2? I just ended up chasing down a Boost exception (from boost::algorithm::hex) that uses typed exceptions without messages, which hits this case, so Catch2 just returns a nonspecific std::exception message as noted elsewhere in this thread.

It would be really nice to have the exception type printed - heck, I'd even take a mangled version, although I realize that's probably not something you want in an actual release.

@cincodenada
Copy link
Contributor

cincodenada commented Jan 15, 2019

After further investigation, I discovered CATCH_TRANSLATE_EXCEPTION, which allows you to implement your own exception translator which is a nice workaround. For example:

#include "catch.hpp"

#include <cxxabi.h>
#include <typeinfo>

CATCH_TRANSLATE_EXCEPTION(std::exception& e) {
  std::string s;
  int status;

  const char* name = typeid(e).name();
  char* realname = abi::__cxa_demangle(name, 0, 0, &status);
  if(realname) {
    s.append(realname);
  } else {
    s.append(name);
  }
  s.append(": ");
  s.append(e.what());
  free(realname);
  return s;
}

Will print the demangled type along with the exception for exceptions that inherit from std::exception. This demangling is for GCC/Clang, you'll have to Google for how to do demangling for your compiler if it's different. You can also use e.g. boost::core::demangle() to do the dirty work for you.

It would still be nice to have exception names by default, but this is a good clean workaround for anyone who wants it now!

@AlexisWilke
Copy link

It seems to me that such an exception:

class NumberIsWayTooBig : public std::exception() {};

is actually not quite right.

You should allow for a message and use std::runtime_error() (something you could not prevent at compile time) or std::logic_error() (something that should be fixable at compile time.)

I have exceptions defined in this way:

class NumberError : public std::runtime_error
{
public:
    NumberError(std::string const & msg) : runtime_error(msg) {}
};

And when a number is too big:

if(number > max) throw NumberError("this number is way too big");

Then you already have a message and the std::exception::what() function will return your message "this number is way too big". I often even include the function name in the exception message so I can find it easily. "foo(): this number is way too big".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants