Perform explicit tests to determine success, true/false, and equality to improve the readability and maintainability of code and for compatibility with common conventions.
In particular, do not default the test for nonzero. For instance, suppose a foo()
function returns 0 to indicate failure or a nonzero value to indicate success. Testing for inequality with 0,
if (foo() != 0) ...
is preferable to
if (foo()) ...
despite the convention that 0 indicates failure. Explicitly testing for inequality with 0 benefits maintainability if foo()
is later modified to return −1 rather than 0 on failure.
This recommendation is derived from and considers the implications of the following common conventions:
- Functions return 0 if false and nonzero if true [StackOvflw 2009].
- Function failures can typically be indicated by −1 or any nonzero number.
- Comparison functions (such as the standard library function
strcmp()
, which has a trinary return value) return 0 if the arguments are equal and nonzero otherwise (see strcmp function).
Noncompliant Code Example
In this noncompliant code example, is_banned()
returns 0 if false and nonzero if true:
LinkedList bannedUsers; int is_banned(User usr) { int x = 0; Node cur_node = (bannedUsers->head); while (cur_node != NULL) { if(!strcmp((char *)cur_node->data, usr->name)) { x++; } cur_node = cur_node->next; } return x; } void processRequest(User usr) { if(is_banned(usr) == 1) { return; } serveResults(); }
If a banned user is listed twice, the user is granted access. Although is_banned()
follows the common convention of returning nonzero for true, processRequest
checks for equality only with 1.
Compliant Solution
Because most functions guarantee a return value of nonzero only for true, the preceding code is better written by checking for inequality with 0 (false), as follows:
LinkedList bannedUsers; int is_banned(User usr) { int x = 0; Node cur_node = (bannedUsers->head); while(cur_node != NULL) { if (strcmp((char *)cur_node->data, usr->name)==0) { x++; } cur_node = cur_node->next; } return x; } void processRequest(User usr) { if (is_banned(usr) != 0) { return; } serveResults(); }
Noncompliant Code Example
In noncompliant code, function status can typically be indicated by returning −1 on failure or any nonnegative number on success. This is a common convention in the standard C library, but it is discouraged in ERR02-C. Avoid in-band error indicators.
Although failures are frequently indicated by a return value of 0, some common conventions may conflict in the future with code in which the test for nonzero is not explicit. In this case, defaulting the test for nonzero welcomes bugs if and when a developer modifies validateUser()
to return an error code or −1 rather than 0 to indicate a failure (all of which are also common conventions).
int validateUser(User usr) { if(listContains(validUsers, usr)) { return 1; } return 0; } void processRequest(User usr, Request request) { if(!validateUser(usr)) { return "invalid user"; } else { serveResults(); } }
Although the code will work as intended, it is possible that a future modification will result in the following:
errno_t validateUser(User usr) { if(list_contains(allUsers, usr) == 0) { return 303; /* User not found error code */ } if(list_contains(validUsers, usr) == 0) { return 304; /* Invalid user error code */ } return 0; } void processRequest(User usr, Request request) { if(!validateUser(usr)) { return "invalid user"; } else { serveResults(); } }
In this code, the programmer intended to add error code functionality to indicate the cause of a validation failure. The new code, however, validates any invalid or nonexisting user. Because there is no explicit test in processRequest()
, the logical error is not obvious and seems correct by certain conventions.
Compliant Solution
This compliant code is preferable for improved maintenance. By defining what constitutes a failure and explicitly testing for it, the behavior is clearly implied, and future modifications are more likely to preserve it. If a future modification is made, such as in the previous example, it is immediately obvious that the if
statement in processRequest()
does not correctly utilize the specification of validateUser()
.
int validateUser(User usr) { if(list_contains(validUsers, usr)) { return 1; } return 0; } void processRequest(User usr, Request request) { if(validateUser(usr) == 0) { return "invalid user"; } else { serveResults(); } }
Noncompliant Code Example
Comparison functions (such as the standard library strcmp()
function) return 0 if the arguments are equal and nonzero otherwise.
Because many comparison functions return 0 for equality and nonzero for inequality, they can cause confusion when used to test for equality. If someone were to switch the following strcmp()
call with a function testing for equality, but the programmer did not follow the same convention as strcmp()
, the programmer might instinctively just replace the function name. Also, when quickly reviewed, the code could easily appear to test for inequality.
void login(char *usr, char *pw) { User user = find_user(usr); if (!strcmp((user->password),pw)) { grantAccess(); } else { denyAccess("Incorrect Password"); } }
The preceding code works correctly. However, to simplify the login code or to facilitate checking a user's password more than once, a programmer can separate the password-checking code from the login function in the following way:
int check_password(User *user, char *pw_given) { if (!strcmp((user->password),pw_given)) { return 1; } return 0; } void login(char *usr, char *pw) { User user = find_user(usr); if (!check_password(user, pw)) { grantAccess(); } else { denyAccess("Incorrect Password"); } }
In an attempt to leave the previous logic intact, the developer just replaces strcmp()
with a call to the new function. However, doing so produces incorrect behavior. In this case, any user who inputs an incorrect password is granted access. Again, two conventions conflict and produce code that is easily corrupted when modified. To make code maintainable and to avoid these conflicts, such a result should never be defaulted.
Compliant Solution
This compliant solution, using a comparison function for this purpose, is the preferred approach. By performing an explicit test, any programmer who wishes to modify the equality test can clearly see the implied behavior and convention that is being followed.
void login(char *usr, char *pw) { User user = find_user(usr); if (strcmp((user->password),pw) == 0) { grantAccess(); } else { denyAccess("Incorrect Password"); } }
Risk Assessment
Code that does not conform to the common practices presented is difficult to maintain. Bugs can easily arise when modifying helper functions that evaluate true/false or success/failure. Bugs can also easily arise when modifying code that tests for equality using a comparison function that obeys the same conventions as standard library functions such as strcmp
.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
EXP20-C | Medium | Probable | Low | P12 | L1 |
Automated Detection
Tool | Version | Checker | Description |
Astrée | 24.04 | Supported indirectly via MISRA C:2004 Rule 13.2. | |
Axivion Bauhaus Suite | 7.2.0 | CertC-EXP20 | Fully implemented |
Helix QAC | 2024.2 | C3344, C4116 | |
LDRA tool suite | 9.7.1 | 114 S | Partially implemented |
Parasoft C/C++test | 2023.1 | CERT_C-EXP20-a | Avoid comparing values with TRUE macro/enum constant using equality operators ("==", "!=") |
PC-lint Plus | 1.4 | 697 | Partially supported: reports comparisons of Boolean values to constants other than 0 |
Bibliography
[StackOvflw 2009] | "Should I Return TRUE/FALSE Values from a C Function?" |
8 Comments
David Svoboda
Robert Seacord
David has some interesting points. I started reading this and thought I wouldn't like it, but I actually think you should go ahead and finish developing this guideline.
I agree with David that this could easily be considered a style guideline. As such, you should follow David's advice and try to relate this to real world defects/vulnerabilities but also perhaps you can add a "style" label to the guideline so that we could perhaps treat these differently if we ever decide to. Similarly, another recent guideline on always using open and close braces, even around single statements, could also be viewed as stylistic. The problem with these guidelines is the number of false positives they will generate if applied to existing code. For new code, they are probably not bad ideas.
Performing explicit tests to determine success, true/false, and equality makes code both maintainable and compliant with common conventions. In the case that the return value will never change, an explicit test is still preferable to clearly communicate the numeric, rather than boolean, value of the test.
The title has to change as well. Perhaps "Perform explicit tests to determine success, true/false, and equality"
(1) Function failures can typically be indicated by one of the following return values: -1, a non-zero number.
I think I would have described the convention as:
(1) Function failures are typically indicated by a negative return value.
do you disagree?
These conventions are not entirely non-controversial and is a point of contention for many programmers during code reviews. Here is a typical discussion among programmers on the subject of convention (1).
This could be a second compliant solution to your second NCE (which David correctly suggests you should lead off with)
Unknown User (ecollazo)
Does the function failure returning non-zero make more sense now? I also added the errno_t stuff.
David Svoboda
The rule is coming along nicely. More comments:
Unknown User (ecollazo)
I made all these changes and added a more comprehensive NCCE for the last strcmp example. I'm looking into the ctype.h functions right now but welcome more recommendations if you have any.
Thanks!
David Svoboda
Only one comment: The References section should be the last item in the rule. Also, please use the conventions we use for citing references (which are evident in most of the other rules on this wiki.) For the strcmp() reference, why don't you just cite the definition of strcmp() from the C99 standard?
Yozo TODA
I added missing bang symbol and closing parens in the sample code.
The first non-compliant code contains
if
expressionif(!strcmp()){ ...}, and I revised in CS to
if(strcmp()==0){ ... }
.this comparison function issue is treated in the third example, and adding some more explanation may be better...
Joe Sewell
Note that non-compliant code example #2 is a bit of a stretch. While it's technically correct, by changing the return value as indicated, the coder is changing the rules of calling
validate_user
after the fact, and/or the user of the function was not informed properly of the intent of the return values.This recommendation treds dangerously close to supporting the ill-conceived use of
if (some_value == True)
, which is not the same asif (some_value)
(c.f.C FAQ, Booleans, question 9.2).