4

From man 3 assert_perror:

The purpose of the assert macros is to help programmers find bugs in their programs, things that cannot happen unless there was a coding mistake. However, with system or library calls the situation is rather different, and error returns can happen, and will happen, and should be tested for. Not by an assert, where the test goes away when NDEBUG is defined, but by proper error handling code. Never use this macro.

I find it hard to understand the above rationale.

Granted, it is kind of obvious for some error codes. For example, according to man 2 recv, this function can fail with EINTR when “the receive was interrupted by delivery of a signal before any data were available”. For certain, the use of assert or assert_perror to test against this error code in most cases seems to be an exceptionally bad idea.

However, recv() can also fail with EFAULT if “the receive buffer pointer(s) point outside the process's address space”. Such a situation clearly “cannot happen unless there was a coding mistake”, and thus the primary purpose of testing against this error seems to be “to help programmers find bugs in their programs”. Despite whatever man 3 assert_perror says, I cannot see how the situation of recv() failing with EFAULT can be “rather different” from any other bug that can be tested against with an assert() macro.

Granted, in such a case, just as with any other situation when a clear bug has been detected at runtime, the program should probably stop. But this is also true for any other bug. If we bring up that argument against the use of assert_perror in such a situation, we automatically condemn the use of assert() for any non-computation-heavy test in favor of running this test at runtime and terminating the program should this test fail.

Why should we never use the assert_perror macro or test against library functions failing with certain error codes with the assert macro?

3 Answers 3

7

The advice is given because assert_perror generates no error checking code under NDEBUG, and, the rationale given is that you don't want library/system call sites to go checked under some build conditions and unchecked under others.

I believe the fear is that programmers will use assert_perror(err); and otherwise fail to have any additional error checking, which means that when NDEBUG is defined, there will be no error checking at all at such library/system call sites.

As assert_perror checks for all errors, not just those such as EFAULT, the authors feel that there are no valid use cases for this conditional (only under non-NDEBUG) broad error checking.

Put another way, you pretty much have to check for any possible errors after a library/system call, and in the case of error, not proceed as if the call succeed. Thus, there is no extra value in also having an assert_perror in addition to your normal error handling code.

This same admonishment doesn't apply to the more general assert, as the presumption is that programmers only use those to find bugs before release to production and not for proper error handling.

There are still use cases for looking for bugs in production with complicated software, though, and one large software company that I know uses assert and noWayAssert; where former are elided by flags (like NDEBUG) and the latter are never removed even in production.

1
  • In my code I uses macros "Assert" that stop the app when it is run under a debugger, boolean methods "Asserted" and "AssertionFailed" so you can simultaneously assert and check the outcome of the assertion to handle a problem, and a macro "AssertFatal" which will stop the app, whether debug or released - that's reserved for situations where the app is in real deep shit if the assertion fails.
    – gnasher729
    Apr 29, 2017 at 22:14
3

Asserts are there to find programming errors.

You assume that a certain error in a library call will never happen. That assumption may be right or wrong. If it is wrong, then you have a programming error. You use an assertion to find that programming error (if that error exists). So an assertion to check for an error that you assume will not happen is entirely correct.

"You should never use assert in this situation" is wrong. First, it's better than not using assert at all. Second, the author asks you to write error handling code for a situation that has never happened (as proved by the assert). Since it has never happened, the error handling code has not been test in a real life situation, and you cannot know how to handle the error in any meaningful way. You cannot test the error handling code. You can't guarantee or even assume that handling the error will lead to a better outcome for the user than not trying to handle it.

1

The purpose of the assert macros is to help programmers find bugs in their programs, things that cannot happen unless there was a coding mistake. However, with system or library calls the situation is rather different, and error returns can happen, and will happen, and should be tested for. Not by an assert, where the test goes away when NDEBUG is defined, but by proper error handling code. Never use this macro.

Correction: the purpose of the assert macros is to help internal developers find bugs in their programs, not external developers. They don't help external programmers at all if the library or system is built in release by the time they use it. Just going by the rationale of helping developers find bugs in their programs, say you're using a third party library (a release binary) and it starts flaking out. Is the problem in your code or theirs?

If the code only asserts and doesn't use error-handling, you can't really figure that out very easily. Even if the internal error in the library is caused by a programmer error on their part, at the very least the use of error handling as opposed to doing nothing at all in such a scenario would help you, the programmer in this case using their library, figure out that they have a bug in their code and not yours.

The addition of "internal" to "programmer" helps a lot to think about it. The point of an assert is not to find simply "programmer errors", but specifically "internal programmer errors." In this case an assertion failure can't necessarily be caught by these "external users" or "external programmers" if the internal check is omitted from a release build as opposed to, say, showing an error message they can see no matter what (actually that ties into a previous question I got from when a release_assert might be useful: this would be one case). They can't know if they used the function incorrectly, since they're probably using a release build of that library. They're left in the dark, so to speak, if all you do is assert in such a scenario. So we want to use some error checking mechanism that doesn't leave these "external programmers", or "external users", in the dark against their mistakes, and assertions don't do that (they only inform "internal programmers" of their mistakes).

So for libraries, you often have to do your share of fool-proofing of a kind that's almost like fool-proofing a user-end design, because the people using your libraries, even though they're programmers writing code against it, are like "external users". You don't want to leave them in the dark, guessing what went wrong, if they provide faulty inputs.

And that applies for me too using, say, operating system APIs. Thankfully many of them didn't simply assert in ways that I wouldn't have seen (since I'm using optimized release builds of these operating systems) when I provided an invalid input. They actually reported errors which I was able to catch and figure out what I did wrong. In this case I'm a "external user" of that library, and it was helpful that it caught my mistakes for me.

That said, I disagree with the rationale to never use this macro, since avoiding the use of an assert doesn't automatically make programmers check for all possible error conditions. As gnasher729 said, it's at least better than not using assert at all (and not checking for error conditions at all).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.