[comp.unix.internals] Checking Exit Codes

jfh@rpp386.cactus.org (John F. Haugh II) (10/26/90)

In article <8645:Oct2521:49:5790@kramden.acf.nyu.edu> brnstnd@kramden.acf.nyu.edu (Dan Bernstein) writes:
>In article <1893@necisa.ho.necisa.oz> boyd@necisa.ho.necisa.oz (Boyd Roberts) writes:
>> Surely you mean that if you don't know how to handle a specific recovery
>> (for some unspecified error type) it's still treated as failure?
>
>Not at all. This might be true for calls that give me information, but
>close() is not such a call. Do you check the return value of assert()?

Sure, why not.  You do know that assert() is documented as returning
to the invoker?  You have to read the abort() page to find this out,
but coding

	assert (some impossible condition);
	function (! some impossible condition allowed);

is naive in non-user code.  What is going to happen one day when the
user does

	for (i = 1;i < NSIG;i++)
		(void) signal (i, SIG_IGN);

You better start checking the return code for assert() as well.
-- 
John F. Haugh II                             UUCP: ...!cs.utexas.edu!rpp386!jfh
Ma Bell: (512) 832-8832                           Domain: jfh@rpp386.cactus.org
"SCCS, the source motel!  Programs check in and never check out!"
		-- Ken Thompson

daveb@nostromo.austin.ibm.com (Dave Burton) (10/30/90)

In article <18647@rpp386.cactus.org> jfh@rpp386.cactus.org (John F. Haugh II) writes:
|In article <8645:Oct2521:49:5790@kramden.acf.nyu.edu> brnstnd@kramden.acf.nyu.edu (Dan Bernstein) writes:
|>This might be true for calls that give me information, but
|>close() is not such a call. Do you check the return value of assert()?
|
|Sure, why not.  You do know that assert() is documented as returning
|to the invoker?  You have to read the abort() page to find this out,

Bzzt.  assert() only "returns" to the invoker when the assertion is false.
Asserts are handled in

XPG3 by calling abort(), about which it states:
	"The _abort_() function does not return." [emphasis theirs]
	and
	"SIGABRT is not intended to be caught."

BSD4.3 by:
	directly calling exit(2).

SVR2.1 by:
	calling _assert(), which calls abort(), which calls
	kill(getpid(), SIGIOT);
	which, admittedly, could be caught.

SVR3.x by:
	<reference not available; hopefully better than SVR2.1>

AIX3.x by:
	calling _assert(), which calls abort(), which raise()s SIGABRT.
	If SIGABRT is caught and returns, it is masked out, and raise()d
	again.  If that doesn't make it, then exit() is called.  If SIGABRT
	doesn't return then the point is moot; the handler exit()d.
	
|but coding
|	assert (some impossible condition);
|	function (! some impossible condition allowed);
|is naive in non-user code.  What is going to happen one day when the
|user does
|	for (i = 1;i < NSIG;i++)
|		(void) signal (i, SIG_IGN);
|You better start checking the return code for assert() as well.

See the above "citations" regarding signal trapping.

All manuals (I have seen) state that the _assert_() macro returns no value.
And what happens to code _if_ assert() _did_ return a value?
Consider:

	#define	NDEBUG
	if (assert(x==y) == -1) {		/* bogus usage */
		perror("assert returned");
		exit(1);
	}

Since assert() is usually (XPG3, SVR2.x, BSD4.3, AIX3) defined as a null macro
if NDEBUG is #define'd, such code would not be portable (nor correct).

No, John.  The intent of assert() is that it compile away to nothing
in the presence of NDEBUG, without the baggage of #ifdef/#endif blocks.
The design of assert() is that it behave as a null statement when the
assertion is true, otherwise it should abort() the program.  This also
corresponds nicely to the kernel functionality of assert() - it panic()s.
--
Dave Burton
inet: daveb@bach.austin.ibm.com
uucp: cs.utexas.edu!ibmchs!auschs!nostromo!daveb

daveb@nostromo.austin.ibm.com (Dave Burton) (10/30/90)

In article <4057@awdprime.UUCP> daveb@bach.austin.ibm.com (myself) wrote:
|Bzzt.  assert() only "returns" to the invoker when the assertion is false.
								     ^^^^^
Bzzt myself.  Of course I meant true.
If the assertion is false, the program aborts.
--
Dave Burton
inet: daveb@bach.austin.ibm.com
uucp: cs.utexas.edu!ibmchs!auschs!nostromo!daveb

jfh@rpp386.cactus.org (John F. Haugh II) (10/30/90)

In article <4057@awdprime.UUCP> daveb@bach.austin.ibm.com (Dave Burton) writes:
>In article <18647@rpp386.cactus.org> jfh@rpp386.cactus.org (John F. Haugh II) writes:
>No, John.  The intent of assert() is that it compile away to nothing
>in the presence of NDEBUG, without the baggage of #ifdef/#endif blocks.
>The design of assert() is that it behave as a null statement when the
>assertion is true, otherwise it should abort() the program.

so, you will always assume that failing asserts never return?  this
means that your portable programs aren't.  accepting the statement
that assert() returns control to your program in strange and unexpected
ways only requires that you pay careful attention to the placement
of your assert() statements, whereas rejecting this statement may
lead to strange problems if you have an assert() with the SIGIOT
signal ignored or caught.

you could splatter your code with feature test macros for each
and every one of those versions you listed, with special behavior
to be followed.  or you could take the easy way out, listen to
your uncle fred, and just put an exit after failing assert()
calls.  this is easy, portable, and foolproof.  it has the novel
property that it always works.

>                                                             This also
>corresponds nicely to the kernel functionality of assert() - it panic()s.

that may be true - but the kernel assert() function is not defined
in the System V Programmer's Reference Manual sitting next to me.
i checked the namelist on the kernel running here.  no assert.  hmmm.
perhaps this is another IBMism?  always assume the worst; never
count on undocumented behavior or nonstandard features.

the point of this article was to illustrate that ignoring unexpected
returns can lead to unexpected results, not to serve as an
oppurtunity for someone to research every version of assert().

close() may return an error.  assert() may return on failure.  this
is how you write robust code.  you can argue if catching strange
returns is worth it, or you can sit back and count your bug reports.
-- 
John F. Haugh II                             UUCP: ...!cs.utexas.edu!rpp386!jfh
Ma Bell: (512) 832-8832                           Domain: jfh@rpp386.cactus.org
"SCCS, the source motel!  Programs check in and never check out!"
		-- Ken Thompson

seanf@sco.COM (Sean Fagan) (10/31/90)

In article <18658@rpp386.cactus.org> jfh@rpp386.cactus.org (John F. Haugh II) writes:
>so, you will always assume that failing asserts never return?  

Yes.  I believe that's what ANSI (and POSIX) says.  At least one
implementation (ours, to be honest 8-)) makes sure that abort() will not
return.  The only way, from my glancing of the object code (disassembled, of
course), to get it to return would be to send the process a signal it *does*
catch, and then longjmp() out of that signal handler.

But there's now way I know of to make sure that such a thing doesn't happen.
(Well, you could, in abort(), block all signals, I suppose, and, now that I
think about it, I think I'll see about getting us to do so 8-).)

-- 
-----------------+
Sean Eric Fagan  | "Quoth the raven,"
seanf@sco.COM    | "Eat my shorts!"
uunet!sco!seanf  |     -- Lisa and Bart Simpson
(408) 458-1422   | Any opinions expressed are my own, not my employers'.

jim@segue.segue.com (Jim Balter) (10/31/90)

In article <18658@rpp386.cactus.org> jfh@rpp386.cactus.org (John F. Haugh II) writes:
>you could take the easy way out, listen to
>your uncle fred, and just put an exit after failing assert()
>calls.  this is easy, portable, and foolproof.  it has the novel
>property that it always works.

You could avoid some embarrassment by testing your claims before making them
public.  It would appear that you've never actually used assert, or that when
you do you don't follow the practice you recommend.  The only way to achieve
what you suggest is

	assert(condition); if ( !condition ) exit(1);

Of course, only a fool would code this way to account for the possibility
that SIGABORT is being caught and the signal routine returns.  Such a SIGABORT
handler is a coding error; adding a firewall to "guard" against that coding
error, especially after a failed assert, is idiotic.  Given that you were lucky
enough to get to the assert in the first place, who the hell cares what happens
after the assert?  The assert means that the program is in an unexpected,
undefined, unpredictable state.

Then again, perhaps I can bump my code production for the code line counters
and improve my code robustness at the same time by adding the following every
few lines:

	assert(0 == 0); /* check that compiler is still working */
	if ( 0 != 0 )
	{
		abort();	/* in case assert didn't call abort */
		exit(1);	/* in case abort returned */
		printf("exit returned unexpectedly\n");
				/* perhaps the library exit routine has been
				   overridden with a routine that returns;
				   here we follow the pseudo-wizardly principle
				   that all errors must be reported to the user
				 */
		/* etc. */
	}

jfh@rpp386.cactus.org (John F. Haugh II) (11/01/90)

In article <4430@segue.segue.com> jim@segue.segue.com (Jim Balter) writes:
>You could avoid some embarrassment by testing your claims before making them
>public.  It would appear that you've never actually used assert, or that when
>you do you don't follow the practice you recommend.

ah, but i do try these things out -

| Script is typescript, started Wed Oct 31 08:09:42 1990
| rpp386-> cat abort.c
| #include <signal.h>
| #include <assert.h>
| #include <stdio.h>
| 
| main (argc, argv)
| int	argc;
| char	**argv;
| {
| 	signal (SIGIOT, SIG_IGN);
| 	assert (argc == 2);
| 	printf ("hi dave!\n");
| }
| rpp386-> cc -o abort abort.c
| abort.c
| rpp386-> ./abort
| Assertion failed: expr, file abort.c, line 10
| hi dave!
| rpp386-> exit
| Script done Wed Oct 31 08:10:00 1990

some people ...
-- 
John F. Haugh II                             UUCP: ...!cs.utexas.edu!rpp386!jfh
Ma Bell: (512) 832-8832                           Domain: jfh@rpp386.cactus.org
"SCCS, the source motel!  Programs check in and never check out!"
		-- Ken Thompson

jim@segue.segue.com (Jim Balter) (11/02/90)

In article <18664@rpp386.cactus.org> jfh@rpp386.cactus.org (John F. Haugh II) writes:
>In article <4430@segue.segue.com> jim@segue.segue.com (Jim Balter) writes:
>>You could avoid some embarrassment by testing your claims before making them
>>public.  It would appear that you've never actually used assert, or that when
>>you do you don't follow the practice you recommend.
>
>ah, but i do try these things out -

>| 	signal (SIGIOT, SIG_IGN);
>| 	assert (argc == 2);
>| 	printf ("hi dave!\n");

The claim I was referring to was not that assert can be made to return
on some systems, but rather that easy portable programming of asserts
involve a call to exit. 

In the normal use of assert, the code following the assert is executed under
normal conditions.  The examples you give are simply not real uses of assert.
Assert is not intended to be used to print error messages, as you seem to use
it in your malloc example.  A reasonable use is something like

	assert(ptr != NULL); /* I (the programmer) believe that the logic of
				the program precludes the possibility of ptr
				being NULL at this point.  If there is a
				coding error that does allow ptr to be NULL,
				this assert will detect it and prevent
				the next line from referencing location 0,
				as long as the program is not being compiled
				with NDEBUG defined */
	foo(*ptr);


Now, just where do you intend to insert the exit in this fragment of code?

>some people ...

Indeed.

jfh@rpp386.cactus.org (John F. Haugh II) (11/04/90)

In article <4447@segue.segue.com> jim@segue.segue.com (Jim Balter) writes:
>In the normal use of assert, the code following the assert is executed under
>normal conditions.  The examples you give are simply not real uses of assert.
>Assert is not intended to be used to print error messages, as you seem to use
>it in your malloc example.  A reasonable use is something like

as i said, it was a fake example.  you pay your money, you take your chances.

i typically don't use assert, for exactly the reasons i've mentioned.  if i
want a core dump, i write "abort()".  if i want an error message, i use
perror or fprintf.  but i have found assert to vary from machine to machine
enough to be useless.  on one system, it may core dump.  on another, it
exits with no core dump ever.  on yet another it always exits even if you
ignore the signal and keep it from dumping core.  on still yet another you
can't ignore or catch the signal and it always dumps core (and always exits).
so, describe the exact behavior of assert() without reference to a standards
document.

as another poster wrote, it appears that ANSI C (yes, i've had my eyes
checked lately ;-) specifies that abort() can't return.  this is good news.
-- 
John F. Haugh II                             UUCP: ...!cs.utexas.edu!rpp386!jfh
Ma Bell: (512) 832-8832                           Domain: jfh@rpp386.cactus.org
"SCCS, the source motel!  Programs check in and never check out!"
		-- Ken Thompson

jim@segue.segue.com (Jim Balter) (11/05/90)

In article <18689@rpp386.cactus.org> jfh@rpp386.cactus.org (John F. Haugh II) writes:
>as i said, it was a fake example.  you pay your money, you take your chances.

I hope no one is paying you money for your advice.

>i typically don't use assert, for exactly the reasons i've mentioned.  if i
>want a core dump, i write "abort()".  if i want an error message, i use
>perror or fprintf.

And if you want to make an assertion about a logical state, you ...
oh, a new concept for you, eh?

>but i have found assert to vary from machine to machine
>enough to be useless.

In other words, all your talk about how to use assert portably is hot air.
Why did you bother?

The fact of the matter is that there is plenty of portable code that uses
assert, without an explicit call to exit.  What assert does after it prints a
message is irrelevant to portability because proper usage of assert (as opposed
to your examples) in working code never prints a message.  An assertion failure
should only occur during debugging, and indicates a logic error in the program.
The fact that some implementations may return from assert if SIGABORT is
ignored is irrelevant because (a) no real program ignores SIGABORT (b) the
printing of an assert message is an indication that the program has
malfunctioned and needs debugging, and the behavior of the program *after* the
assert message is printed is of little concern.  And whether abort dumps core
or not is an issue outside the ANSI C standard and is hardly a portability
issue.  Once you get an assert failure; it is time to debug; you can force all
the core dumps you want.

jfh@rpp386.cactus.org (John F. Haugh II) (11/07/90)

In article <4460@segue.segue.com> jim@segue.segue.com (Jim Balter) writes:
>In article <18689@rpp386.cactus.org> jfh@rpp386.cactus.org (John F. Haugh II) writes:
>>as i said, it was a fake example.  you pay your money, you take your chances.
>
>I hope no one is paying you money for your advice.

well, there appears to be some confusion.  it is not my "opinion" or
my "advice" that assert() may return, but rather a statement of
reality.  like it or not, on certain machines

	signal (SIGIOT, SIG_IGN);
	assert (0 == 1);
	puts ("this will never print out");

WILL print out.  i don't have to agree with it or disagree with it
or like it or not - it is simply a statement of fact.

furthermore, the statement that assert() prints an error and then
calls abort() is likewise false.  if your application (for example,
debugging) =expects= that assert(FALSE) generate a core dump due to
abort() being called, you are making an assumption that does not
take into account every perverse implementation of assert() ever
made - including a very popular unix-derivative from microsoft
called "xenix".  again, i neither have to agree or disagree that
this is a good idea - i am simply reporting a fact.

>                                                 And whether abort dumps core
>or not is an issue outside the ANSI C standard and is hardly a portability
>issue.  Once you get an assert failure; it is time to debug; you can force all
>the core dumps you want.

there are more compilers in this world that ANSI C compilers.  if
you want to pretend ANSI C is the only C language out there, then
of course "assert" always gives the expected result.  i mean, in
a perfect world everything is perfect.  i have to use at least
3 different C compilers every day.  i don't have the luxury of
assuming every C compiler is ANSI compliant.
-- 
John F. Haugh II                             UUCP: ...!cs.utexas.edu!rpp386!jfh
Ma Bell: (512) 832-8832                           Domain: jfh@rpp386.cactus.org
"SCCS, the source motel!  Programs check in and never check out!"
		-- Ken Thompson