tags:

views:

558

answers:

11

In C and C++ assert is a very heavyweight routine, writing an error to stdout and terminating the program. In our application we have implemented a much more robust replacement for assert and given it its own macro. Every effort has been made to replace assert with our macro, however there are still many ways assert can be reintroduced (e.g., from internal third-party libraries, naïve injection, etc.)

Any suggestions on how we can reduce, limit or even eradicate uses of assert? The best answer will be one the compiler can catch for us so we don't have to babysit the code base as much as we do currently.

+1  A: 

The most obvious approach would seem to be to give your own version of assert its own name, slightly different from assert(). Then you can search the text, look at linker messages, etc., for the literal string "_assert" and you know you have a problem when you see it.

In my own code, I always use Assert(), which expands to my own function that performs an assertion, or expands to ((void)0) for the release build. The compiler will turn the ((void)0) expression into nothing, but it still counts as an expression. Thus

Assert(3 == x);

will turn into

((void)0);

and the semicolon has a place to go.

By the way, I once worked on a GUI application where the assert was a special GUI modal popup dialog. You had three choices: Ignore, Ignore forever, or Break. Ignore would ignore the assert and keep running. Ignore forever would set a flag, and until you restarted the program in the debugger, that assert would not fire anymore. Break would allow the assert to break into the debugger.

I don't remember how they guaranteed that each assert had its own flag. Maybe when you wrote the Assert() call you had to specify a unique integer? It would be nice if it was more automatic than that. I'm pretty sure that the actual implmentation was a bit vector, and it would set the bit when you chose ignore forever.

steveha
Or, after writing "myAssert", instead of looking for "assert" everywhere, just "#define assert myAssert" and let the preprocessor do the work. I DO NOT ADVOCATE THIS, I'm just saying it can be done.
Beta
@Beta:In most cases, that #define would work -- but for assert it really won't. Since the meaning of `assert()` can change (even in a single source file) based on whether NDEBUG is defined, it can/will be #undef'd and #define'd again behind your back.
Jerry Coffin
@Jerry Coffin: I'm out of my depth here (and my impression is that there's no safety once macros are at war) but couldn't one undefine NDEBUG, either in a header or in the makefiles (or both)? Wouldn't that stop casual attempts to assert?
Beta
The whole purpose of NDEBUG is to allow you to compile some code one way for your "debug" build, and another way for your "release" build. For example, the debug build has asserts, and the release build has nothing at all for asserts. So, defining NDEBUG for all builds means you get only a release build and no debug build. We actually want asserts in our debug build. I use asserts *heavily* in my code; they cost exactly nothing in the release build, so they are a great tool.
steveha
+10  A: 

I'm not sure I really understand the problem, actually. Asserts are only expensive if they go off, which is fine anyway, since you're now in an exception situation.

assert is only enabled in debug builds, so use the release build of a third-party library. But really, asserts shouldn't be going off every moment.

GMan
They don't go off every moment, but given the size of our application we want to do more than simply disappear in the event an assertion fails. The ability to handle an assertion failure more gracefully provides a huge benefit to both QE and development.
fbrereto
@fbrereto: You should throw exceptions instead of calling assert.
+1  A: 

Find assert in the library headers (assuming they're real files on your file system) and replace it with an invalid thing

// #define assert(condition) ... /* old definition */
#define assert(condition) ((condition) & "PLEASE DO NOT USE ASSERT" = 42)
pmg
And remember to do that again (and again ...) everytime you upgrade your compiler.
pmg
For "an invalid thing" I like to use: `#pragma error` The great thing is that this has almost the same effect whether the compiler supports it as a feature, or not. And it pretty clearly identifies that you intend an error there.
steveha
@steveha: I'm not sure you can use a #pragma error in a #define. I can't point to the location in the standard immediately, but I do not think it does what you think it does.
fbrereto
@fbrereto: you're right: C99, §6.10.3.4/3 says: "The resulting completely macro-replaced preprocessing token sequence is not processed as a preprocessing directive even if it resembles one, but all pragma unary operator expressions within it are then processed as specified in 6.10.9 below." A "pragma unary operator" is a _Pragma, not a #pragma.
Jerry Coffin
A: 

assert() is usually #define'd to be ((void)0) for release code (#define NDEBUG), so there is no overhead at all.

When using a testing version, is the performance overhead hurting your ability for the testing to be realistic?

Cade Roux
Pleas see my comment to GMan
fbrereto
A valid implementation of `assert()` *cannot* be defined to be empty for release code. For example, something like `assert(x), y=0;` is legitimate code, but would produce a syntax error if `assert()` became nothing in a release. A correct implementation would become something like `((void *)0)` when NDEBUG was defined.
Jerry Coffin
You can just use `((void)0)`, not need for pointer. :)
GMan
@Gman:Quite right -- my fingers "know" about casts to 'void *', but not 'void'...
Jerry Coffin
corrected to use 0;
Cade Roux
+1  A: 

If source code is under your control :

#define NDEBUG
// before
#include <assert.h> 
// or other header that includes assert.h

Or using precompiled header or compile options to define NDEBUG

For third-part binaries, using release version of them?

OwnWaterloo
+1  A: 

it would depend (at least in part) on what you're changing. Assuming that you don't mind it printing out its normal message, and mostly want to get rid of it calling abort(), you could consider leaving assert() alone, and instead defining your own version of abort(). In theory, doing that isn't portable -- but in reality, abort() is a fairly normal function in the standard library, and if you link your own instead, you get its behavior. Sometimes (especially some Microsoft linkers) you have to do a bit of work to get the linker to cooperate in replacing their abort() with yours, but it's rarely very difficult.

Jerry Coffin
+1  A: 

It can be handy to improve upon the built-in assertion facility (to provide stack traces, core dumps, who knows). In that case, if you're having problems getting your developers to follow whatever standards you have (like "instead of assert() use SUPER_ASSERT()" or whatever), you can just put your own assert.h header in the include path ahead of the compiler's runtime directory of headers.

That'll pretty much guarantee that anyone using the standard assert() macro will get a compiler error or get your assertion functionality (depending on what you have your assert.h header do).

Michael Burr
Ah, the directory path preemption is clever!
fbrereto
A: 

You seem to be missing the fact that the third-party code is most likely written under the assumption of "standard" assert behavior. I.e. the code expects the program to terminate on failed assertion. The code that follows the assertion normally cannot and will not work correctly if the asserted condition is broken. In 99 cases out of 100 it will not work at all. In 99 cases out of 100 it will simply crash, i.e. the program will terminate anyway.

To believe that by overriding the assert behavior in third-party code you will somehow make the program live longer is naive at best.

AndreyT
To assume the OP didn't already know that is naive at best. Did it ever cross your mind, that core dump, a stack trace, even a normal crash will provide more useful information for debugging?
drhirsch
A: 

You could patch the executable and replace all calls to the assert function with NOPs.

Or you could patch the code of the assert function itself to immediately return. This still would result in unnecessary calls, but you just have to change one place.

Admitted, this is a wacky idea, but it certainly is possible. This could even be part of the build script.

DR
+1  A: 

I think your question is totally valid. If you have you have implemented you own error handling you may want to

  1. Always trigger asserts even in release builds.
  2. Implement better error reporting in case an assert triggers. You may want to send error reports or write to log files.

That being said, I don't see any solution that always works.

  • If you are lucky, the 3rd party libraries use ASSERT macros that you can redefine yourself as long as the file defining this macro has some sort of #pragma once or #ifndef __HEADERFILE_H__ #define __HEADERFILE_H__ provision against multiple inclusion. Include the header file separately, redefine ASSERT and you're good.

  • If they directly include assert.h or cassert you can only patch the code I guess. Make minimal code changes, save the changes as patch files and when you update the library hope that the patches still work. Add the patches to version control.

    If this doesn't work, rethink the question if you really need internal asserts in 3rd party libraries. Ship release builds only, this gets rid of the asserts, and add your ASSERTs to check for correctness inside your code. Check for validity of return values. If such an ASSERT is triggered, you can still dive into the 3rd party code to see what caused the problem.

Sebastian
+1  A: 

I think the question is valid.

My very own assert expands to asm("int3") if triggered, which is equivalent to a breakpoint. I also found that vastly more useful for debugging than a simple termintation.

I simply called it "ASSERT()" instead of the normal "assert()" and avoided the use of assert() at all.

drhirsch