4

Our C codebase is using assert to check that preconditions/post conditions are being met.

#include <cstdlib>
#include <cassert>

void Aborting_Function(int n);

int main(){

    Aborting_Function(1); //good
    Aborting_Function(0); //calls std::abort()

    //Is it possible to recover somehow?
    //And continue on...
}

void Aborting_Function(int n){
    assert(n > 0);
    //impl...
}

In unit testing, I want to verify that functions are properly following their contracts
(aborting when they should).

Is it possible to recover from std::abort?

I realize it seems somewhat repetitive to have unit tests check exactly same thing that the assertions should be checking, but this would be helpful as we could automate the checking of particular use cases that should not work.

Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • 4
    Do not use `assert(x)` but rather `our_assert(x)` which is conditionally defined to do nothing (release), `assert(x)` (debugging), do something fancy (testing)? – Dietmar Kühl Dec 10 '15 at 20:10
  • I do not get a point. For unit-testing of the function which aborts on incorrect input you expected result would be abort. What's the problem? – SergeyA Dec 10 '15 at 20:13
  • @DietmarKühl that's potentially our best route. It would rely on us updating large portions of our codebase, but it may be the only way. – Trevor Hickey Dec 10 '15 at 20:13
  • According to the standard `abort()`: *The program is terminated without executing destructors for objects of automatic, thread, or static storage duration and without calling functions passed to atexit()* – NathanOliver Dec 10 '15 at 20:15
  • @SergeyA We want a list of test cases, where we can confirm that "yes, these 50 seperate function calls will each abort given their specified input.". However, when the first test case aborts, there doesn't seem to be a way to recover. – Trevor Hickey Dec 10 '15 at 20:15
  • @TrevorHickey, just fork before doing so and collect the exit code. – SergeyA Dec 10 '15 at 20:35

5 Answers5

5

Short answer, "no".

Rather than subverting abort(), you may want to consider using the google test framework.

This has the DEATH_TEST (documentation here: https://github.com/google/googletest/blob/master/googletest/docs/V1_7_AdvancedGuide.md)

Essentially what this does is fork a child process and checks whether the statement causes it to exit (which it would do if it aborted).

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
2

For unit testing one can substitute abort() and longjmp() from there, or when testing via C++ throwing from there.

For example (expressed as C++):

#include <cassert>
#include <csetjmp>
#include <stdexcept>
#include <iostream>

#ifndef lest_ABORT_SIGNATURE
# if _MSC_VER
#  define lest_NORETURN  __declspec(noreturn)
#  define lest_ABORT_SIGNATURE()  _ACRTIMP lest_NORETURN void __cdecl abort(void)
# else
#  define lest_NORETURN  [[noreturn]]
#  define lest_ABORT_SIGNATURE()  lest_NORETURN void __cdecl abort()
# endif
#else
# ifndef  lest_NORETURN
#  define lest_NORETURN
# endif
#endif

#if USE_LONGJMP
    jmp_buf env;

    lest_ABORT_SIGNATURE()
    {
        std::longjmp( env, 1 );
    }
#else
    struct Abort{};

    lest_NORETURN void my_abort()
    {
        throw Abort{};
    }

    lest_ABORT_SIGNATURE()
    {
        // throw indirectly and prevent warning in VC14:
        my_abort();
    }
#endif

int main()
{
#if USE_LONGJMP
    if ( ! setjmp( env ) )
    {
        std::cout << "assert(false):\n";
        assert( false );
    }
    else
    {
        std::cout << "Intercepted abort\n";
    }
#else
    try
    {
        std::cout << "assert(false):\n";
        assert( false );
    }
    catch ( Abort const & )
    {
        std::cout << "Caught Abort\n";
    }
    catch ( std::exception const & e )
    {
        std::cout << "Exception: " << e.what() << "\n";
    }
#endif
    std::cout << "End\n";
}

#if 0
cl  -EHsc -DUSE_LONGJMP=1 abort-own.cpp && abort-own.exe
g++ -Wall -DUSE_LONGJMP=1 -std=c++11 -o abort-own.exe abort-own.cpp && abort-own.exe
#endif

Compiling with VC14 (VS2015) and runnning with:

cl -EHsc -DUSE_LONGJMP=1 abort-own.cpp && abort-own.exe

yields the following output:

...
assert(false):
Assertion failed: false, file abort-own.cpp, line 45
Intercepted abort
End

Compiling with a pre-VC14 compiler produces a link error:

LIBCMT.lib(abort.obj) : error LNK2005: _abort already defined in {file}
{exe} : fatal error LNK1169: one or more multiply defined symbols found

This may be cured by:

Compiling via g++ using -std=c++03 or -std=c++11 does not lead to a multiply defined symbol.

More elaborate versions of the code above I'm developing for the lest test framework:

Community
  • 1
  • 1
Martin Moene
  • 929
  • 11
  • 18
  • +1 to testing with setjmp/longjmp, we've got a test that shows our crash handler can handle segfaults that checks it with that. Of course you can also take the SIGABRT handler and implement it there instead... – dascandy Jan 08 '16 at 13:53
  • @dascandy longjmp from a SIGABRT handler [only _appears_ to work](https://gist.github.com/martinmoene/d886a76cb38655c6395f), as does throwing from it. According to the standard, there's precious little you can do from such a handler however. See also the other answers on this page and [here](http://www.securecoding.cert.org/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers#SIG30-C.Callonlyasynchronous-safefunctionswithinsignalhandlers-NoncompliantCodeExample(longjmp())). – Martin Moene Jan 08 '16 at 15:26
  • Technically the general case says you can't throw. Pragmatically, in a test running environment, being able to expect an abort & then continuing the tests may be useful. – dascandy Jan 09 '16 at 18:45
1

According to POSIX,

The abort() function shall cause abnormal process termination to occur, unless the signal SIGABRT is being caught and the signal handler does not return.

This implies that if you catch the signal and the signal handler returns, it is still required to terminate the program (say, by resetting the signal handler to the default termination behaviour, and then raising the signal again).

So the only way to "recover" is to catch the signal and not return from the signal handler. So the line after Aborting_Function(0) can't be reached. Furthermore, you probably don't want your program to spend the rest of its life in a signal handler anyway, because then all outside variables become unsafe to access (except for lock-free atomics). It's not very nice.

On Windows, though? I have no idea.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Can you use `setjmp` and `longjmp` to get out of the aborting context? – Barmar Dec 10 '15 at 20:22
  • @Barmar I believe so, but that doesn't solve the issue with accessing variables outside the signal handler. Also you can only jump to a place where you've already been. – Brian Bi Dec 10 '15 at 20:27
  • I was thinking that you would call `setjmp` just before calling `Aborting_Function()`, and then test a variable that the `SIGABRT` sets. – Barmar Dec 10 '15 at 20:34
0

Is it possible to recover from std::abort?

It depends on what you mean by recover from. From http://en.cppreference.com/w/cpp/utility/program/abort

Causes abnormal program termination unless SIGABRT is being caught by a signal handler passed to signal and the handler does not return.

If you want to be able to continue from where std::abort was called, then the answer is No. If you want to be able to do something before the program exits, then the answer is Yes.

I am guessing that you would like to be able to continue from where std::abort was called. Hence, the answer is No for your use case.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
0

There's a way to do that but it's slightly non-conformant.

You can use HippoMocks (disclaimer: I'm the author) to mock the function and to make it throw an exception instead, which you then use to check in your test framework. You cannot make it return a value, as it's marked noreturn and the compiler will not have generated any code to handle returns from it.

EXPECT_CALL(&abort).Throw(42);

Note that this violates at least 5 different rules in C and C++, so the bigger question is, should you? As far as I can tell, you use asserts to guard against things that show internal inconsistency. These are things that you should not be testing for. All your programs should be equally valid with and without asserts. If you expect any kind of behaviour from your code that you want to test, then that should never be an assert, as it's not an unexpected state (heck, you're testing that state, so it's a well-tested one at that).

So you can, but you shouldn't want to.

dascandy
  • 7,184
  • 1
  • 29
  • 50
  • Death tests -which does include exit()- _are_ [considered useful](http://stackoverflow.com/a/3698752/437272). – Martin Moene Jan 08 '16 at 15:39
  • Interesting, would you mind to give an indication as to which 5 rules? – Martin Moene Jan 08 '16 at 15:57
  • 1. It takes the address of a C++ standard function, which is not allowed. 2. It overwrites it with x86 bytecode that redirects to another function, changing it at runtime. This only changes the PLT entry so it's not even that great, but for unittests it's OK. 3. It replaces a C function with a C++-generated function which is not guaranteed to work. (ie, calling conventions) 4. It throws an exception from a C function marked as noreturn. 5. There's at least a guideline that says you shouldn't throw ints. Ok, that's a stretch, but it's still not quite nice. – dascandy Jan 09 '16 at 18:40