1

I have a C library (callable from C and C++ code) which handles invalid input by simply exiting. It looks like this

#ifdef __cplusplus
extern "C" {
#endif

void exitWithError(const char* func) {
    printf("woopsie in %s", func);
    exit(1);
}

void myfunc(int i) {
    if (i < 0)
        exitWithError(__func__);
}

#ifdef __cplusplus
}
#endif

This library is compiled in "C mode", even when linked with C++ code. I.e. using

g++ -x c <abovelibrary.c>

I'm using this library in C++ code, and desire it to throw an exception, in lieu of exiting. E.g.

void exitWithError(const char* func) {
    throw std::invalid_argument( func );
}

Is it possible to use pre-processor directives to redefine exitWithError in C++, so that it throws an exception to the external calling C++ code, but is still compatible by the internal calling C code?

Can this further be done without modifying the original C library (although this is not a strict requirement)?

For context, I'm using the C++ Catch2 library to unit test the underlying C library, and wish to test that invalid user inputs are being correctly handled (using Catch2's REQUIRE_THROWS macro). I'm using C++14 if that matters, and the C library conforms to C99.

Anti Earth
  • 4,671
  • 13
  • 52
  • 83
  • 0. That's a poorly designed library, yell at upstream to fix it. 1. If it's an actual library (either static or shared, but not a loose object), it should "just work" (but remember to `extern "C"`), but beware of objects left in an invalid state. 2. Consider forking and testing how the child process exits (but `_exit` may be safer than `exit`). Tbh I'm surprised more unit test libraries don't support this. 3. You can use linker scripts to "modify" an existing library without modifying the source, but I wouldn't bother. – o11c Oct 25 '19 at 01:10
  • Could you alter the library with a function pointer to use as an error handler callback? Then just write your callback in C++ and set the pointer. – LegendofPedro Oct 25 '19 at 01:14
  • 1
    If the library exits on error, its probably not going to bother getting into a safe state after an error. Even if you succeed in having it throw, you can't know that it's in a usable state. It'll likely just be in whatever bad state it was at the time. – François Andrieux Oct 25 '19 at 04:09
  • @o11c in this crime, I am both detective and culprit. The library is an esoteric scientific project I joined which needed a much bigger refactor to correctly handle user input validation (see github.com/QuEST-Kit/QuEST if interested). Could you elaborate on what should just work in 1.? I was unable to modify the C library and still compile sucessfully in "C mode" `g++ -x c ...` – Anti Earth Oct 25 '19 at 14:06
  • @LegendofPedro I'm afraid I can't change the API, and not sure I could cleanly to that in the backend – Anti Earth Oct 25 '19 at 14:07
  • @FrançoisAndrieux you're quite right, I'll need to be careful to clean up myself after catching the injected insertion. Thanks for the heads up! – Anti Earth Oct 25 '19 at 14:08
  • 2
    @AntiEarth Beware that I am specifically worried about the internal state of the library. Whatever partial work was achieved before the error might not have been undone. Resources such as mutex locks might not have been released. The core of the issue is if the library designer didn't expect the error to be recovered from, they are not held to any post condition at all. Unless you look at the library's code, you can't know that it's even possible to clean up after your exception is thrown. – François Andrieux Oct 25 '19 at 14:19
  • @FrançoisAndrieux noted - I am the C library designer :) – Anti Earth Oct 28 '19 at 19:32
  • @AntiEarth Then it sounds like you are just asking if a C library can "transit" a C++ exception. That is, if a callback called from a C library can throw such that it propagates through the C library and back to a C++ call site that can catch it. This is not my field of expertise but my understanding is that no, this won't work. – François Andrieux Oct 28 '19 at 19:36
  • @FrançoisAndrieux then unfortunately your intuition has failed you, because yes, it does work. How exactly it handles the exception can be tricky and sometimes undefined behaviour, but it's certainly possible. o11c's suggesting indeed works – Anti Earth Oct 28 '19 at 19:49
  • @AntiEarth That's interesting. Though that must introduce all kinds of head aches. Considering that C can't implement RAII, I can't imagine how one would work around not being sure when a function might exit. I'm glad this works for you, but I hope you don't have too many possible failure modes. – François Andrieux Oct 28 '19 at 20:42
  • 1
    @FrançoisAndrieux haha yea it gives me the same worry in the pit of my stomach :) Thankfully I'm only 'injecting' these exceptions for the sake of unit testing that the original C library was going to exit at all, so maybe I'll only answer for this crime at the pearly gates – Anti Earth Oct 29 '19 at 12:14

1 Answers1

0

As per this question, we can declare exitWithError as a weak symbol from within the C library

#pragma weak exitWithError
void exitWithError(const char* func) {
    printf("woopsie in %s", func);
    exit(1);
}

and redefine it in C++, throwing an exception.

extern "C" void exitWithError(const char* func) {
    throw std::invalid_argument(func);
}

As pointed out in the comments, one must be absolutely sure they understand the internal state of the C library when exitWithError is invoked, and hence that it's safe to continue after catching the exception.

Anti Earth
  • 4,671
  • 13
  • 52
  • 83