Subversion Repositories tendra.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

<!-- Crown Copyright (c) 1998 -->
<HTML>
<HEAD>
<TITLE>C Checker Reference Manual: Data Flow and Variable Analysis</TITLE>
</HEAD>
<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#0000FF" VLINK="#400080" ALINK="#FF0000">
<A NAME=S42>
<H1>C Checker Reference Manual</H1>
<H3>January 1998</H3>
<A HREF="tdfc9.html"><IMG SRC="../images/next.gif" ALT="next section"></A>
<A HREF="tdfc7.html"><IMG SRC="../images/prev.gif" ALT="previous section"></A>
<A HREF="tdfc1.html"><IMG SRC="../images/top.gif" ALT="current document"></A>
<A HREF="../index.html"><IMG SRC="../images/home.gif" ALT="TenDRA home page">
</A>
<IMG SRC="../images/no_index.gif" ALT="document index"><P>
<HR>
<DL>
<DT><A HREF="#S43"><B>5.1 </B> - Introduction</A><DD>
<DT><A HREF="#S44"><B>5.2 </B> - Unreachable code analysis</A><DD>
<DT><A HREF="#S45"><B>5.3 </B> - Case fall through</A><DD>
<DT><A HREF="#S46"><B>5.4 </B> - Unusual flow in conditional statements</A><DD>
<DL>
<DT><A HREF="#S47"><B>5.4.1 </B> - Empty if statements</A><DD>
<DT><A HREF="#S48"><B>5.4.2 </B> - Use of assignments as control expressions</A>
<DD>
<DT><A HREF="#S49"><B>5.4.3 </B> - Constant control expressions</A><DD>
</DL>
<DT><A HREF="#S50"><B>5.5 </B> - Operator precedence</A><DD>
<DT><A HREF="#S51"><B>5.6 </B> - Variable analysis</A><DD>
<DL>
<DT><A HREF="#S52"><B>5.6.1 </B> - Order of evaluation</A><DD>
<DT><A HREF="#S53"><B>5.6.2 </B> - Modification between sequence points</A><DD>
<DT><A HREF="#S54"><B>5.6.3 </B> - Operand of sizeof operator</A><DD>
<DT><A HREF="#S55"><B>5.6.4 </B> - Unused variables</A><DD>
<DT><A HREF="#S56"><B>5.6.5 </B> - Values set and not used</A><DD>
<DT><A HREF="#S57"><B>5.6.6 </B> - Variable which has not been set
is used</A><DD>
</DL>
<DT><A HREF="#S58"><B>5.7 </B> - Overriding the variable analysis</A><DD>
<DL>
<DT><A HREF="#S59"><B>5.7.1 </B> - Discarding variables</A><DD>
<DT><A HREF="#S60"><B>5.7.2 </B> - Setting variables</A><DD>
<DT><A HREF="#S61"><B>5.7.3 </B> - Exhaustive switch statements</A><DD>
<DT><A HREF="#S62"><B>5.7.4 </B> - Non-returning functions</A><DD>
</DL>
<DT><A HREF="#S63"><B>5.8 </B> - Discard Analysis</A><DD>
<DL>
<DT><A HREF="#S64"><B>5.8.1 </B> - Discarded function returns</A><DD>
<DT><A HREF="#S65"><B>5.8.2 </B> - Discarded computed values</A><DD>
<DT><A HREF="#S66"><B>5.8.3 </B> - Unused static variables and procedures</A>
<DD>
</DL>
<DT><A HREF="#S67"><B>5.9 </B> - Overriding the discard analysis</A><DD>
<DL>
<DT><A HREF="#S68"><B>5.9.1 </B> - Discarding function returns and
computed values</A><DD>
<DT><A HREF="#S69"><B>5.9.2 </B> - Preserving unused statics</A><DD>
</DL>
</DL>

<HR>
<H1>5  Data Flow and Variable Analysis</H1>
<A NAME=S43>
<HR><H2>5.1  Introduction</H2>
The checker has a number of features which can be used to help track
down potential programming errors relating to the use of variables
within a source file and the flow of control through the program.
Examples of this are detecting sections of unused code, flagging expressions
that depend upon the order of evaluation where the order is not defined,
checking for unused static variables, etc.<P>
<A NAME=S44>
<HR><H2>5.2  <A NAME=2>Unreachable code analysis</H2>
Consider the following function definition:<P>
<PRE>
        int f ( int n )
        {
                if ( n ) {
                        return ( 1 );
                } else {
                        return ( 0 );
                }
                return ( 2 );
        }
</PRE>
The final return statement is redundant since it can never be reached.
The test for unreachable code is controlled by:<P>
<PRE>
        #pragma TenDRA unreachable code <EM>permit</EM>
</PRE>
where permit is replaced by <CODE>disallow</CODE> to give an error
if unreached code is detected, <CODE>warning</CODE> to give a warning,
or <CODE>allow</CODE> to disable the test (this is the default).<P>
There are also equivalent command-line options to tchk of the form
<CODE>-X:unreached=</CODE><CODE>state</CODE>, where <CODE>state</CODE>
can be <CODE>check</CODE>, <CODE>warn</CODE> or <CODE>dont</CODE>.<P>
Annotations to the code in the form of user-defined keywords may be
used to indicate that a certain statement is genuinely reached or
unreached. These keywords are introduced using:<P>
<PRE>
        #pragma TenDRA keyword REACHED for set reachable
        #pragma TenDRA keyword UNREACHED for set unreachable
</PRE>
The statement REACHED then indicates that this portion of the program
is actually reachable, whereas UNREACHED indicates that it is unreachable.
For example, one way of fixing the program above might be to say that
the final return is reachable (this is a blatant lie, but never mind).
This would be done as follows:<P>
<PRE>
        int f ( int n ) {
                if ( n ) {
                        return ( 1 );
        } else {
                        return ( 0 )
                }
                REACHED
                return ( 2 );
        }
</PRE>
An example of the use of UNREACHED might be in the function below
which falls out of the bottom without a return statement. We might
know that, because it is never called with c equal to zero, the end
of the function is never reached. This could be indicated as follows:<P>
<PRE>
        int f ( int c ){
                if ( c ) return ( 1 );
                UNREACHED
        }
</PRE>
As always, if new keywords are introduced into a program then definitions
need to be provided for <BR>
conventional compilers. In this case, this can be done as follows:<P>
<PRE>
        #ifdef __TenDRA__
        #pragma TenDRA keyword REACHED for set reachable
        #pragma TenDRA keyword UNREACHED for set unreachable
        #else
        #define REACHED
        #define UNREACHED
        #endif
</PRE>
<A NAME=S45>
<HR><H2>5.3  <A NAME=6>Case fall through</H2>
Another flow analysis check concerns fall through in case statements.
For example, in:<P>
<PRE>
        void f ( int n )
        {
                switch ( n ) {
                        case 1 : puts ( &quot;one&quot; );
                        case 2 : puts ( &quot;two&quot; );
                }
        }
</PRE>
the control falls through from the first case to the second. This
may be due to an error in the program (a missing break statement),
or be deliberate. Even in the latter case, the code is not particularly
maintainable as it stands - there is always the risk when adding a
new case that it will interrupt this carefully contrived flow. Thus
it is customary to comment all case fall throughs to serve as a warning.<P>
In the default mode, the TenDRA C checker ignores all such fall throughs.
A check to detect fall through in case statements is controlled by:<P>
<PRE>
        #pragma TenDRA fall into case <EM>permit</EM>
</PRE>
where <CODE>permit</CODE> is <CODE>allow</CODE> (no errors), <CODE>warning
</CODE> (warn about case fall through) or <CODE>disallow</CODE> (raise
errors for case fall through).<P>
There are also equivalent command-line options to tcc of the form
-<CODE>X:fall_thru=</CODE><CODE>state</CODE>, where <CODE>state</CODE>
can be <CODE>check</CODE>, <CODE>warn</CODE> or <CODE>dont</CODE>.<P>
Deliberate case fall throughs can be indicated by means of a keyword,
which has been introduced using:<P>
<PRE>
        #pragma TenDRA keyword FALL_THROUGH for fall into case
</PRE>
Then, if the example above were deliberate, this could be indicated
by:<P>
<PRE>
        void f ( int n ){
                switch ( n ) {
                        case 1 : puts ( &quot;one&quot; );
                        FALL_THROUGH
                        case 2 : puts ( &quot;two&quot; );
                }
        }
</PRE>
Note that FALL_THROUGH is inserted between the two cases, rather than
at the end of the list of statements following the first case.<P>
If a keyword is introduced in this way, then an alternative definition
needs to be introduced for conventional compilers. This might be done
as follows:<P>
<PRE>
        #ifdef __TenDRA__
        #pragma TenDRA keyword FALL_THROUGH for fall into case
        #else
        #define FALL_THROUGH
        #endif
</PRE>
<A NAME=S46>
<HR><H2>5.4  Unusual flow in conditional statements</H2>
The following three checks are designed to detect possible errors
in conditional statements. <P>
<A NAME=S47>
<H3>5.4.1  <A NAME=9>Empty if statements</H3>
Consider the following C statements:<P>
<PRE>
        if( var1 == 1 ) ;
        var2 = 0 ;
</PRE>
The conditional statement serves no purpose here and the second statement
will always be executed regardless of the value of var1. This is almost
certainly not what the programmer intended to write. A test for if
statements with no body is controlled by:<P>
<PRE>
        #pragma TenDRA extra ; after conditional <EM>permit</EM>
</PRE>
with the usual <CODE>allow</CODE> (this is the default setting), <CODE>warning
</CODE> and <CODE>disallow</CODE> options for <EM>permit</EM>.<P>
<A NAME=S48>
<H3>5.4.2  <A NAME=11>Use of assignments as control expressions</H3>
Using the C assignment operator, `=', when the equality operator `=='
was intended is an extremely common problem. The pragma:<P>
<PRE>
        #pragma TenDRA assignment as bool <EM>permit</EM>
</PRE>
is used to control the treatment of assignments used as the controlling
expression of a conditional statement or a loop, e.g.<P>
<PRE>
        if( var = 1 ) { ...
</PRE>
The options for <EM>permit</EM> are <CODE>allow</CODE>, <CODE>warning</CODE>
and <CODE>disallow</CODE>. The default setting allows assignments
to be used as control statements without raising an error.<P>
<A NAME=S49>
<H3>5.4.3  <A NAME=13>Constant control expressions 
</H3>
Statements with constant control expressions are not really conditional
at all since the value of the control statement can be evaluated statically.
Although this feature is sometimes used in loops, relying on a break,
goto or return statement to end the loop, it may be useful to detect
all constant control expressions to check that they are deliberate.
The check for statically constant control expressions is controlled
using:<P>
<PRE>
        #pragma TenDRA const conditional <EM>permit</EM>
</PRE>
where permit may be replaced by <CODE>disallow</CODE> to give an error
when constant control expressions are encountered, <CODE>warning </CODE><EM>to
replace the error by a warning,</EM><CODE> </CODE>or the check may
be switched off using the <CODE>allow</CODE> (this is the default).<P>
<A NAME=S50>
<HR><H2>5.5  <A NAME=15>Operator precedence</H2>
The ISO C standard section 6.3, provides a set of rules governing
the order in which operators within expressions should be applied.
These rules are said to specify the operator precedence and are summarised
in the table over the page. Operators on the same line have the same
precedence and the rows are in order of decreasing precedence. Note
that the unary +, -, * and &amp; operators have higher precedence
than the binary forms and thus appear higher in the table.<P>
The precedence of operators is not always intuitive and often leads
to unexpected results when expressions are evaluated. A particularly
common example is to write:<P>
<PRE>
        if ( var &amp; TEST == 1) { ...
        }
        else { ...
</PRE>
assuming that the control expression will be evaluated as:<P>
<PRE>
        ( ( var &amp; TEST ) == 1 )
</PRE>
However, the == operator has a higher precedence than the bitwise
&amp; operator and the control expression is evaluated as:<P>
<PRE>
        ( var &amp; ( TEST == 1 ) )
</PRE>
which in general will give a different result<P>
.<BR><IMG SRC="table4.gif"><BR>
<P>
The TenDRA C checker can be configured to flag expressions containing
certain operators whose precedence is commonly confused, namely:<P>
<UL>
<LI>&amp;&amp; <EM>versus</EM> ||<P>
<LI>&lt;&lt; <EM>and</EM> &gt;&gt; <EM>versus</EM> + <EM>and</EM>
-<P>
<LI>&amp; <EM>versus</EM> == != &lt; &gt; &lt;= &gt;= + <EM>and</EM>
-<P>
<LI>^ <EM>versus</EM> &amp; == |= &lt; &gt; &lt;= &gt;= + <EM>and</EM>
-<P>
<LI>| <EM>versus</EM> ^ &amp; == |= &lt; &gt; &lt;= &gt;= + <EM>and</EM>
-<P>
</UL>
The check is switched off by default and is controlled using:<P>
<PRE>
        #pragma TenDRA operator precedence <EM>status</EM>
</PRE>
where <EM>status</EM> is <CODE>on</CODE>, <CODE>warning</CODE> or
<CODE>off</CODE>.<P>
<A NAME=S51>
<HR><H2>5.6  Variable analysis</H2>
The variable analysis checks are controlled by:<P>
<PRE>
        #pragma TenDRA variable analysis status
</PRE>
where <CODE>status</CODE> is <CODE>on</CODE>, <CODE>warning</CODE>
or <CODE>off</CODE> as usual. The checks are switched off in the default
mode.<P>
There are also equivalent command line options to tchk of the form
<CODE>-X:variable=</CODE><CODE>state</CODE>, where <CODE>state</CODE>
can be <CODE>check</CODE>, <CODE>warn</CODE> or <CODE>dont</CODE>.<P>
The variable analysis is concerned with the evaluation of expressions
and the use of local variables, including function arguments. Occasionally
it may not be possible to statically perform a full analysis on an
expression or variable and in these cases the messages produced indicate
that there may be a problem. If a full analysis is possible a definite
error or warning is produced. The individual checks are listed in
sections 5.6.1 to 5.6.6 and section 5.7 describes the source annotations
which can be used to fine-tune the variable analysis.<P>
<A NAME=S52>
<H3>5.6.1  Order of evaluation</H3>
The ISO C standard specifies certain points in the expression syntax
at which all prior expressions encountered are guaranteed to have
been evaluated. These positions are called sequence points and occur:
<P>
<UL>
<LI>after the arguments and function expression of a function call
have been evaluated but before the call itself;<P>
<LI>after the first operand of a logical &amp;&amp;, or || operator;
<P>
<LI>after the first operand of the conditional operator, ?:; <P>
<LI>after the first operand of the comma operator; <P>
<LI>at the end of any full expression  (a full expression may take
one of the following forms: an initialiser; the expression in an expression
statement; the controlling expression in an <CODE>if</CODE>, <CODE>while</CODE>,
<CODE>do</CODE> or <CODE>switch 
</CODE> statement; each of the three optional expressions of a <CODE>for</CODE>
statement; or the optional expression of a <CODE>return</CODE> statement).<P>
</UL>
Between two sequence points however, the order in which the operands
of an operator are evaluated, and the order in which side effects
take place is unspecified - any order which conforms to the operator
precedence rules above is permitted. For example:<P>
<PRE>
        var = i + arr[ i++ ] ;
</PRE>
may evaluate to different values on different machines, depending
on which argument of the + operator is evaluated first. The checker
can detect expressions which depend on the order of evaluation of
sub-expressions between sequence points and these are flagged as errors
or warnings when the variable analysis is enabled.<P>
<A NAME=S53>
<H3>5.6.2  Modification between sequence points</H3>
The ISO C standard states that if an object is modified more than
once, or is modified and accessed other than to determine the new
value, between two sequence points, then the behaviour is undefined.
Thus the result of:<P>
<PRE>
        var = arr[i++] + i++ ;
</PRE>
is undefined, since the value of i is being incremented twice between
sequence points. This behaviour is detected by the variable analysis.<P>
<A NAME=S54>
<H3>5.6.3  Operand of sizeof operator</H3>
According to the ISO C standard, section 6.3.3.4, the operand of the
sizeof operator is not itself evaluated. If the operand has any side-effects
these will not occur. When the variable analysis is enabled, the checker
detects the use of expressions with side-effects in the operand of
the sizeof operator.<P>
<A NAME=S55>
<H3>5.6.4  Unused variables</H3>
As part of the variable analysis, a simple test applied to each local
variable at the end of its scope to determine whether it has been
used in that scope. For example, in:<P>
<PRE>
        int f ( int n )
        {
                int r;
                return ( 0 );
        }
</PRE>
both the function argument n and the local variable r are unused.<P>
<A NAME=S56>
<H3>5.6.5  Values set and not used</H3>
This is a more complex test since it is applied to every instance
of setting the variable. For example, in:<P>
<PRE>
        int f ( int n )
        {
                int r = 1;
                r = 5;
                return ( r );
        }
</PRE>
the first value r is set to 1 and is not used before it is overwritten
by 5 (this second value is used however). This test requires some
flow analysis. For example, if the program is modified to:<P>
<PRE>
        int f ( int n )
        {
                int r = 1;
                if ( n == 3 ) {
                        r = 5;
                }
                return ( r );
        }
</PRE>
the initial value of r is used when n != 3, so no error is detected.
However in:<P>
<PRE>
        int f ( int n )
        {
                int r = 1;
                if ( n == 3 ) {
                        r = 5;
                } else {
                        r = 6;
                }
                return ( r );
        }
</PRE>
the initial value of r is overwritten regardless of the result of
the conditional, and hence is unused.<P>
<A NAME=S57>
<H3>5.6.6  Variable which has not been set is used</H3>
This test also requires some flow analysis, for example in:<P>
<PRE>
        int f ( int n )
        {
                int r;
                if ( n == 3 ) {
                        r = 5;
                }
                return ( r );
        }
</PRE>
the use of the variable r as a return value is reported because there
are paths leading to this statement in which r is not set (i.e. when
n != 3). However, in:<P>
<PRE>
        int f ( int n )
        {
                int r;
                if ( n == 3 ) {
                        r = 5;
                } else {
                        r = 6;
                }
                return ( r );
        }
</PRE>
r is always set before it is used, so no error is detected.<P>
<A NAME=S58>
<HR><H2>5.7  Overriding the variable analysis</H2>
Although many of the problems discovered by the variable analysis
are genuine mistakes, some may be as the result of deliberate decisions
by the program writer. In this case, more information needs to be
provided to the checker to convey the programmer's intentions. Four
constructs are provided for this purpose: the discard variable, the
set variable, the exhaustive switch and the non-returning function.<P>
<A NAME=S59>
<H3>5.7.1  <A NAME=26>Discarding variables</H3>
Actively discarding a variable counts as a use of that variable in
the variable analysis, and so can be used to suppress messages concerning
unused variables and values assigned to variables. There are two distinct
methods to indicate that the variable x is to be discarded. The first
uses a pragma:<P>
<PRE>
        #pragma TenDRA discard x;
</PRE>
which the checker treats as if it were a C statement, ending in a
semicolon. Having a statement which is noticed by one compiler but
ignored by another can lead to problems. For example, in:<P>
<PRE>
        if ( n == 3 )
        #pragma TenDRA discard x;
                puts ( &quot;n is three&quot; );
</PRE>
tchk believes that x is discarded if n == 3 and the message is always
printed, whereas other compilers will ignore the #pragma statement
and think that the message is printed if n == 3. An alternative, in
many ways neater, solution is to introduce a new keyword for discarding
variables. For example, to introduce the keyword DISCARD for this
purpose, the pragma:<P>
<PRE>
        #pragma TenDRA keyword DISCARD for discard variable
</PRE>
should be used. The variable x can then be discarded by means of the
statement:<P>
<PRE>
        DISCARD ( x );
</PRE>
A dummy definition for DISCARD to use with normal compilers needs
to be given in order to maintain compilability with those compilers.
For example, a complete definition of DISCARD might be:<P>
<PRE>
        #ifdef __TenDRA__
        #pragma TenDRA keyword DISCARD for discard variable
        #else
        #define DISCARD(x) (( void ) 0 )
        #endif
</PRE>
Discarding a variable changes its assignment state to unset, so that
any subsequent uses of the variable, without an intervening assignment
to it, lead to a &quot;variable used before being set&quot; error.
This feature can be exploited if the same variable is used for distinct
purposes in different parts of its scope, by causing the variable
analysis to treat the different uses separately. For example, in:<P>
<PRE>
        void f ( void ) {
                int i = 0;
                while ( i++ &lt; 10 )
                        { puts ( &quot;hello&quot; ); }
                while ( i++ &lt; 10 ) 
                        { puts ( &quot;goodbye&quot; ); }
        }
</PRE>
which is intended to print both messages ten times, the two uses of
i as a loop counter are independent - they could have been implemented
with different variables. By discarding i after the first loop, the
second loop can be analysed separately. In this way, the error of
failing to reset i to 0 can be detected.<P>
<A NAME=S60>
<H3>5.7.2  <A NAME=29>Setting variables</H3>
In addition to discarding variables, it is also possible to set them.
In deliberately setting a variable, the programmer is telling the
checker to assume that some value will always have been assigned to
the variable by that point, so that any &quot;variable used without
being set&quot; errors can be suppressed. This construct is particularly
useful in programs with complex flow control, to help out the variable
analysis. For example, in:<P>
<PRE>
        void f ( int n )
        {
                int r;
                if ( n != 0 ) r = n;
                if ( n &gt; 2 ) {
                        printf ( &quot;%d\n&quot;, r );
                }
        }
</PRE>
r is only used if n &gt; 2, in which case we also have n != 0, so
that r has already been initialised. However, in its flow analysis,
the TenDRA C checker treats all the conditionals it meets as if they
were independent and does not look for any such complex dependencies
(indeed it is possible to think of examples where such analysis would
be impossible). Instead, it needs the programmer to clarify the flow
of the program by asserting that r will be set if the second condition
is true.<P>
Programmers may assert that the variable, r, is set either by means
of a pragma:<P>
<PRE>
        #pragma TenDRA set r;
</PRE>
or by using, for example:<P>
<PRE>
        SET ( r );
</PRE>
where SET is a keyword which has previously been introduced to stand
for the variable setting construct using:<P>
<PRE>
        #pragma TenDRA keyword SET for set
</PRE>
(cf. DISCARD above).<P>
<P>
<A NAME=S61>
<H3>5.7.3  <A NAME=32>Exhaustive switch statements 
</H3>
A special case of a flow control construct which may be used to set
the value of a variable is a switch statement. Consider the program:<P>
<PRE>
        char *f ( int n ){
                char *r;
                switch ( n ) {
                        case 1:r=&quot;one&quot;;break;
                        case 2:r=&quot;two&quot;;break;
                        case 3:r=&quot;three&quot;;break;
                }
                return ( r );
        }
</PRE>
This leads to an error indicating that r is used but not set, because
it is not set if n lies outside the three cases in the switch statement.
However, the programmer might know that f is only ever called with
these three values, and hence that r is always set before it is used.
This information could be expressed by asserting that r is set at
the end of the switch construct (see above), but it would be better
to express the cause of this setting rather than just its effect.
The reason why r is always set is that <BR>
the switch statement is exhaustive - there are case statements for
all the possible values of n.<P>
Programmers may assert that a switch statement is exhaustive by means
of a pragma immediately following it. For example, in the above case
it would take the form:<P>
<PRE>
        ....
        switch ( n )
        #pragma TenDRA exhaustive
                {
                        case 1:r=&quot;one&quot;;break;
                        ....
</PRE>
Again, there is an option to introduce a keyword, EXHAUSTIVE say,
for exhaustive switch statements using:<P>
<PRE>
        #pragma TenDRA keyword EXHAUSTIVE for exhaustive
</PRE>
Using this form, the example program becomes:<P>
<PRE>
        switch ( n ) EXHAUSTIVE {
                case 1:r=&quot;one&quot;;break;
</PRE>
In order to maintain compatibility with existing compilers, a dummy
definition for EXHAUSTIVE must be introduced for them to use. For
example, a complete definition of EXHAUSTIVE might be:<P>
<PRE>
        #ifdef __TenDRA__
        #pragma TenDRA keyword EXHAUSTIVE for exhaustive
        #else
        #define EXHAUSTIVE
        #endif
</PRE>
<A NAME=S62>
<H3>5.7.4  <A NAME=34>Non-returning functions  
</H3>
Consider a modified version of the program above, in which calls to
f with an argument other than 1, 2 or 3 cause an error message to
be printed:<P>
<PRE>
        extern void error (const char*);
        char *f ( int n ) {
                char *r;
                switch ( n ) {
                        case 1:r=&quot;one&quot;;break;
                        case 2:r=&quot;two&quot;;break;
                        case 3:r=&quot;three&quot;;break;
                        default:error(&quot;Illegal value&quot;);
                }
                return ( r );
        }
</PRE>
This causes an error because, in the default case, r is not set before
it is used. However, depending on the semantics of the function, error,
the return statement may never be reached in this case. This is because
the fact that a function returns void can mean one of two distinct
things:<P>
<OL>
<LI>That the function does not return a value. This is the usual meaning
of void.<P>
<LI>That the function never returns, for example the library function,
exit, uses void in this sense.<P>
</OL>
If error never returns, then the program above is correct; otherwise,
an unset value of r may be returned.<P>
Therefore, we need to be able to declare the fact that a function
never returns. This is done by introducing a new type to stand for
the non-returning meaning of void (some compilers use volatile void
for this purpose). This is done by means of the pragma:<P>
<PRE>
        #pragma TenDRA type VOID for bottom
</PRE>
to introduce a type VOID (although any identifier may be used) with
this meaning. The declaration of error can then be expressed as:<P>
<PRE>
        extern VOID error (const char *);
</PRE>
In order to maintain compatibility with existing compilers a definition
of VOID needs to be supplied. For example:<P>
<PRE>
        #ifdef __TenDRA__
        #pragma TenDRA type VOID for bottom
        #else
        typedef void VOID;
        #endif
</PRE>
The largest class of non-returning functions occurs in the various
standard APIs - for example, exit and abort. The TenDRA descriptions
of these APIs contain this information. The information that a function
does not return is taken into account in all flow analysis contexts.
For example, in:<P>
<PRE>
        #include &lt;stdlib.h&gt;
        
        int f ( int n )
        {
                exit ( EXIT_FAILURE );
                return ( n );
        }
</PRE>
n is unused because the return statement is not reached (a fact that
can also be determined by the unreachable code analysis in section
5.2).<P>
<A NAME=S63>
<HR><H2>5.8  <A NAME=36>Discard Analysis</H2>
A couple of examples of what might be termed &quot;discard analysis&quot;
have already been described - discarded (unused) local variables and
discarded (unused) assignments to local variables (see section 5.6.4
and 5.6.5). The checker can perform three more types of discard analysis:
discarded function returns, discarded computations and unused static
variables and procedures. These three tests may be controlled as a
group using:<P>
<PRE>
        #pragma TenDRA discard analysis <EM>status</EM>
</PRE>
where <EM>status</EM> is <CODE>on</CODE>, <CODE>warning</CODE> or
<CODE>off</CODE>.<P>
In addition, each of the component tests may be switched on and off
independently using pragmas of the form:<P>
<PRE>
        #pragma TenDRA discard analysis (function return) <EM>status</EM>
        #pragma TenDRA discard analysis (value) <EM>status</EM>
        #pragma TenDRA discard analysis (static) <EM>status</EM>
</PRE>
There are also equivalent command line options to tchk of the form
<CODE>-X:</CODE><CODE>test</CODE><CODE>=</CODE><CODE>state</CODE>,
where <CODE>test</CODE> can be <CODE>discard_all</CODE>, <CODE>discard_func_ret
</CODE>, <CODE>discard_value</CODE> or <CODE>unused_static</CODE>,
and <CODE>state</CODE> can be <CODE>check</CODE>, <CODE>warn</CODE>
or <CODE>dont</CODE>. These checks are all switched off in the default
mode. <P>
Detailed descriptions of the individual checks follow in sections
5.8.1 - 5.8.3. Section 5.9 describes the facilities for fine-tuning
the discard analysis.<P>
<A NAME=S64>
<H3>5.8.1  <A NAME=38>Discarded function returns</H3>
Functions which return a value which is not used form the commonest
instances of discarded values. For example, in:<P>
<PRE>
        #include &lt;stdio.h&gt;
        int main ()
        {
                puts ( &quot;hello&quot; );
                return ( 0 );
        }
</PRE>
the function, puts, returns an int value, indicating whether an error
has occurred, which is ignored.<P>
<A NAME=S65>
<H3>5.8.2  <A NAME=40>Discarded computed values</H3>
A rarer instance of a discarded object, and one which is almost always
an error, is where a value is computed but not used. For example,
in:<P>
<PRE>
        int f ( int n ) {
                int r = 4 
                if ( n == 3 ) {
                        r == 5;
                }
                return ( r );
        }
</PRE>
the value r == 5 is computed but not used. This is actually because
it is a misprint for r = 5.<P>
<A NAME=S66>
<H3>5.8.3  <A NAME=42>Unused static variables and procedures</H3>
The final example of discarded values, which perhaps more properly
belongs with the variable analysis tests mentioned above, is for static
objects which are unused in the source module in which they are defined.
Of course this means that they are unused in the entire program. Such
objects can usually be removed.<P>
<A NAME=S67>
<HR><H2>5.9  Overriding the discard analysis</H2>
As with the variable analysis, certain constructs may be used to provide
the checker with extra information about a program, to convey the
programmer's intentions more clearly.<P>
<A NAME=S68>
<H3>5.9.1  <A NAME=44>Discarding function returns and computed values</H3>
Unwanted function returns and, more rarely, discarded computed values,
may be actively ignored to indicate to the discard analysis that the
value is being discarded deliberately. This can be done using the
traditional method of casting the value to void:<P>
<PRE>
        ( void ) puts ( &quot;hello&quot; );
</PRE>
or by introducing a keyword, IGNORE say, for discarding a value. This
is done using a pragma of the form:<P>
<PRE>
        #pragma TenDRA keyword IGNORE for discard value
</PRE>
The example discarded value then becomes:<P>
<PRE>
        IGNORE puts ( &quot;hello&quot; );
</PRE>
Of course it is necessary to introduce a definition of IGNORE for
conventional compilers in order to maintain compilability. A suitable
definition might be:<P>
<PRE>
        #ifdef __TenDRA__
        #pragma TenDRA keyword IGNORE for discard value
        #else
        #define IGNORE ( void )
        #endif
</PRE>
<A NAME=S69>
<H3>5.9.2  <A NAME=46>Preserving unused statics</H3>
Occasionally unused static values are introduced deliberately into
programs. The fact that the static variables or procedures x, y and
z are deliberately unused may be indicated by introducing the pragma:<P>
<PRE>
        #pragma TenDRA suspend static x y z
</PRE>
at the outer level after the definition of all three objects.<P>
<P>
<!-- FM pgf ignored -->
<HR>
<P><I>Part of the <A HREF="../index.html">TenDRA Web</A>.<BR>Crown
Copyright &copy; 1998.</I></P>
</BODY>
</HTML>