[comp.lang.c] Warning messages

adamsf@turing.cs.rpi.edu (Frank Adams) (01/12/90)

[I have cross posted this to several difference news groups, reflecting
languages where it seems to me to be most immediately applicable - mostly
due to the presence of a "pragma" construct.  Since the examples given are
for the "C" language, I have directed followups there.  Followups concerned
with other specific languages should be directed to the appropriate news
group; those dealing with the idea/problem in general probably belong in
comp.lang.misc.]

Like Mr. Smith, I have come to believe that warning messages from a
compiler are a mistake -- but for a quite different reason, and with
a quite different resolution in mind.

Warning messages are fine for small programs, where one person can
read them all and understand them.  But for a large or medium sized
program, requiring anywhere from three programmers on up, they don't work.
In such a project, one typically has a period "make" or "build", where
all source changed since the last make (sometimes all source) is compiled.
If this compilation is done with warnings enabled, typically many warnings
will be generated -- more than can reasonably be dealt with.

For conscientious software engineers, the solution is to treat warnings as
errors, so that the final program generates no warning messages.  There are
two problems with this.  The first is that the compiler doesn't help: it
returns a "success" return code for a compilation with warning messages only,
so any automated make facility will not detect the error[1].  This problem
occurs even for relatively small projects, which still use a make file.

The obvious solution is to make the compiler treat warnings as errors, and
this is indeed what I am advocating.  But this runs smack into the second
problem: sometimes, the situation being warned about is exactly what you want
to do.

To deal with this, I propose introduction of a pragma, with the syntax:

#pragma permit <keyword>

This pragma would permit the warning condition associated with <keyword> to
be found in the next line, suppressing the error message.  In implementing
this pragma, compilers should follow the following guidelines:

(1) If the keyword is unrecognized, ignore the pragma.  It may be meaningful
to some other compiler.

(2) Similarly, if the keyword is recognized, but the corresponding condition
is not found, do *not* generate any sort of message.  Not all compilers will
define a condition identically, so some other compiler might need the pragma.

(3) The pragma should be associated with the syntactic element most closely
associated with the warning.  For example, many compilers will warn about an
unreferenced local variable at the point where it goes out of scope.  The
pragma should be associated instead with the point where the variable is
declared.

(4) To deal with the automatic code generation problem, there should of course
be a compiler flag to turn off warnings globally.

A partial list of keywords for the C language might include:

assignment - permit an assignment in a conditional context

noeffect - a statement or expression has no effect (for example, "a+b;", or
"a" in "a, b=c").

ambiguous - a statement has ambiguous side effects (on the other hand, I would
be inclined to regard this as simply an error).

unused - a variable is never referenced, or assigned but never used.

uninitialized - a variable is not necessarily initialized at some reference.

unreached - a statement can never be executed.

cast - a nonportable type cast is made (for example, pointer to int).

comparison - a comparison occurs whose value can be determined at compile time.
(Examples include "u<0" and "u<=-1", where "u" is unsigned; or "a==a".)

overflow - overflow occurs in evaluation of a constant expression (including
evaluation of a simple constant).  Note that the next (bignum) condition should
also be suppressed by this keyword.

bignum - a constant occurs which is legal on this machine, but bigger than
the the guaranteed minimum size for its type.  (For example, an int greater
than 32767 on a machine where ints are 32 bits.)

noprototype - a function is called without a prototype in scope.

precedence - don't complain about the use of the default precedence in cases
where that is commonly considered to be confusing (such as "&" and "==").

I would also propose a "#pragma unreachable" which asserts that the point in
the code where it occurs cannot be reached.  This will suppress warnings such
as no return value after a call to "exit" at the end of a function.

-----------------

[1] It would of course be *possible* to build a facility which checks the
compiler output for warning messages, but this requires it to be intimate
with the compiler, and much more complex than is otherwise necessary.

edm@vrdxhq.verdix.com (Ed Matthews) (01/13/90)

In article <2NNQZ%@rpi.edu> adamsf@turing.cs.rpi.edu (Frank Adams) writes:

>Warning messages are fine for small programs, where one person can
>read them all and understand them.  But for a large or medium sized
>program, requiring anywhere from three programmers on up, they don't work.

I tend to agree with you in general.

>The obvious solution is to make the compiler treat warnings as errors, and
>this is indeed what I am advocating.  But this runs smack into the second
>problem: sometimes, the situation being warned about is exactly what you want
>to do.
>
>To deal with this, I propose introduction of a pragma, with the syntax:
>
>#pragma permit <keyword>

This does not seem like an optimal solution to me.  Semantic
interpretation of warnings is a function of the environment, not of the
compiler.  For example, the environment may disallow you to make public any
unit which has warnings.  This is the level at which warnings should be
enforced.  The environment should allow, on a case-by-case basis, units
with warnings to be made public when the unit has been reviewed and 
explicit authorization to make that unit public has been given.  

Note that your solution requires explicit modification of the source code.
This requires, at a minimum, recompilation of one unit, and potentially
recompilation of a lot of units.  Furthermore, should conditions change
and you decide that wish to once again treat the warning as an error, you
must go back in and modify the unit and suffer the recompilation penalty.

NOTES:  I have done most of my programming in Ada, for which it has long been
recognized that an environment is necessary.  Environments for C seem to
be scarcer, though becoming more common.  For lack of time, I don't follow
comp.lang.c, so if anyone wants to discuss this further, e-mail is best.
-- 

Ed Matthews                                                edm@verdix.com
Verdix Corporation Headquarters                            (703) 378-7600
Chantilly, Virginia

karl@haddock.ima.isc.com (Karl Heuer) (01/13/90)

In article <2NNQZ%@rpi.edu> adamsf@turing.cs.rpi.edu (Frank Adams) writes:
>...The obvious solution is to make the compiler treat warnings as errors, and
>this is indeed what I am advocating.  But this runs smack into the second
>problem: sometimes, the situation being warned about is exactly what you want
>to do.

Of the constructs that produce warnings from Unix C compilers (including
lint), most can be disabled with a slight rewrite at no cost to performance.
I expect this is true of non-Unix compilers too, though I have less experience
there.

>assignment - permit an assignment in a conditional context

I would prefer rewriting it as an explicit compare against zero.

>precedence - don't complain about the use of the default precedence in cases
>where that is commonly considered to be confusing (such as "&" and "==").

So why not add the "redundant" parens instead of a pragma that says you know
what you're doing?  See _The Elements of Programming Style_ for the example
where a user deliberately omitted an optional keyword (an "else" or "end" or
something; I don't know PL/I) and tossed in several lines of commentary to
explain why it was kosher to do so.

>noeffect ... unused ... unreached ...

If the conditions for such dangling code/declarations can be determined (not
always easy, I admit), you can simply |#if| them out.

>noprototype - a function is called without a prototype in scope.

Under what conditions is it desirable to invoke a function without a
prototype?

>I would also propose a "#pragma unreachable" which asserts that the point in
>the code where it occurs cannot be reached.  This will suppress warnings such
>as no return value after a call to "exit" at the end of a function.

Better would be a |#pragma nonreturning| that could be attached to the
*declarations* of functions like |exit()|.  Then you only need to specify them
once per function, rather than once per use.  |#pragma unreachable| may still
have some uses, though.

I'd like to suggest some additions:

aligned - attached to the declaration of a function like |malloc()| to inform
the compiler that it's *always* safe to cast this to any pointer type (note
that |void *| does *not* include such a guarantee).

fallthrough - (at the end of a case, just before the next case label) the
absence of a |break| here is intentional.

monomorphic - the arguments covered by the ellipsis in this prototype will all
have the same type as the last declared argument (e.g. for |execl|).  this
pragma would normally *add* opportunities for warnings (e.g. when the user
forgets to cast the NULL that terminates the list).

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint

rfg@ics.uci.edu (Ron Guilmette) (01/14/90)

In article <2NNQZ%@rpi.edu> adamsf@turing.cs.rpi.edu (Frank Adams) writes:
>
>For conscientious software engineers, the solution is to treat warnings as
>errors, so that the final program generates no warning messages... 

Agreed.  I always do.

>
>The obvious solution is to make the compiler treat warnings as errors...

Or bettey yet, give the programmer direct control (see below).

>
>To deal with this, I propose introduction of a pragma, with the syntax:
>
>#pragma permit <keyword>

Sorry, but I have to disagree.  I think this is a truly dumb idea.

In an ideal universe, several things would be true which are not necessarily
true now:

	For any type of warning that your compiler could possible emit, there
	would be a simple and easy way of changing the code such that it had
	essentially the same semantics, but did not get the warning.  The
	GNU C compiler seems to measure up to this "requirement" as far
	as I have been able to tell.

	Programmers would be able to control the generation of both warnings
	and (trivially) recoverable errors (like assigning incompatible
	pointers types in C++) in detail.  Normally, there should be a default
	"treatment" for each individual "message".  The "treatment" 
	consists of two independently controlable parts, i.e. (1) the
	"issuance" treatment (should messages for this particular condition
	be issued or suppressed) and the "severity" treatment (should this
	condition be considered to be an error or a warning as far as the
	exit status of the compiler is concerned).  User's should be allowed
	to specify (on a per compilation basis) cases where they wish some
	kind of non-default "treatment" for any particular type of "condition".
	Obviously, one way to do this is via command line options, but I for
	one would probably want to override so many default message treatments
	that it would probably make sense (for me anyway) to be able to have
	my compiler automatically look for either an environment variable or
	for a .treatment file in my home directory which would describe all
	of the zillions of non-default treatments that I personally prefer.

The problem with suppressing and/or enabling warnings and/or errors directly
within the source files themselves is that there is no reason to believe that
the poor dumb son-of-a-bitch who inherits the maintenance responsibilities for
my crappy code after I have died (or changed jobs) will have the same
preferences regarding message treatments that I have (or had).  He may actually
*want* to see all those warnings that I had suppressed (at least while he is
initially trying to figure out what the hell I did).  If they were suppressed
via #pragma's burried in a thousand places in my source files, then this poor
schmuck will have a lot of editing to do.

I think it would be much better to let this other "maintainer" have *his* own
.treatment file in *his* home directory which describes *his* own preferences
for the behavior of compilers.

All power to the programmer!

// rfg

karl@haddock.ima.isc.com (Karl Heuer) (01/17/90)

In article <25AFE8A6.14024@paris.ics.uci.edu> rfg@ics.uci.edu (Ron Guilmette) writes:
>In article <2NNQZ%@rpi.edu> adamsf@turing.cs.rpi.edu (Frank Adams) writes:
>>For conscientious software engineers, the solution is to treat warnings as
>>errors, so that the final program generates no warning messages...
>
>Agreed.  I always do.

I try to, and in fact I normally compile with all warnings enabled.  There's
one that still gives me grief, which I haven't yet decided how to resolve.

Assume that, for reasons beyond the scope of this discussion$, |main()|%
terminates with |exit()| rather than |return|.  What is the correct way to
declare it?  I was using |void main()|, but X3J11 didn't bless that variant,
so I've stopped using it.  If I leave it blank, just writing |main()|, then I
get a warning about it being implicitly |int|.  (And quite rightly so; I've
never liked that anyway.)  Defining it as |int main()| is conforming and
silences gcc (since I've configured it to know that |exit()| is nonreturning),
but (a) it's misleading and (b) at any time, gcc or some other compiler could
decide that declaring a nonreturning function with a type other than |void|
deserves a warning.

If the Committee had chosen to not even legalize the common |int main(void)|&,
then a similar problem would occur when the compiler complains about args
declared but not used.

Karl W. Z. Heuer (karl@haddock.isc.com or ima!haddock!karl), The Walking Lint
________
$ If you say that the solution is to use |return| instead of |exit()|, I can't
  accept that.  Both are correct, the arguments for one are about as good as
  for the other, and it's not the sort of thing that the compiler should try
  to enforce.
% I say |main()| only for brevity.  In actual code I would write either
  |main(void)| or |main(int argc, char **argv)|.
& I think I agree with Doug that this would probably have been the better
  solution.  Zero-arg |main| would then be reduced to a Common Extension, like
  three-arg |main|.

tneff@bfmny0.UU.NET (Tom Neff) (01/20/90)

In article <15673@haddock.ima.isc.com> karl@haddock.ima.isc.com (Karl Heuer) writes:
>$ If you say that the solution is to use |return| instead of |exit()|, I can't
>  accept that.  Both are correct, the arguments for one are about as good as
>  for the other, and it's not the sort of thing that the compiler should try
>  to enforce.

Just use both.

It executes the way you want, and parses the way C wants.
-- 
"Take off your engineering hat   = "The filter has      | Tom Neff
and put on your management hat." = discreting sources." | tneff@bfmny0.UU.NET

karl@haddock.ima.isc.com (Karl Heuer) (01/23/90)

In article <15102@bfmny0.UU.NET> tneff@bfmny0.UU.NET (Tom Neff) writes:
>In article <15673@haddock.ima.isc.com> karl@haddock (Karl Heuer) writes:
|>If you say that the solution is to use |return| instead of |exit()|, I can't
|>accept that.  Both are correct, the arguments for one are about as good as
|>for the other, and it's not the sort of thing that the compiler should try
|>to enforce.
|
|Just use both.  It executes the way you want, and parses the way C wants.

Then I would be inviting the compiler to complain about unreachable code.
Remember, I've configured it to know that |exit()| is nonreturning.

Karl W. Z. Heuer (karl@haddock.isc.com or ima!haddock!karl), The Walking Lint