An unsafe function-like macro is one that evaluates a parameter more than once in the code expansion or never evaluates the parameter at all. Never invoke an unsafe macro with arguments containing an assignment, increment, decrement, volatile access, input/output, or other side effects (including function calls, which may cause side effects).
The documentation for unsafe macros must warn about putting side effects on the invocation, but the responsibility is on the programmer using the macro. Because of the risks associated with their use, it is recommended that you avoid the creation of unsafe macro functions. (See PRE00-C. Prefer inline or static functions to function-like macros.)
The assert()
macro is an excellent example of an unsafe macro. Its argument may be evaluated once or not at all, depending on the NDEBUG
macro. For more information, see EXP31-C. Avoid side effects in assertions.
Noncompliant Code Example
One problem with unsafe macros is side effects on macro arguments, as shown by this noncompliant code example:
#define ABS(x) (((x) < 0) ? -(x) : (x)) /* ... */ m = ABS(++n);
The invocation of the ABS()
macro in this example expands to
m = (((++n) < 0) ? -(++n) : (++n));
The resulting code is well defined but causes n
to be incremented twice rather than once.
Compliant Solution
One compliant solution is simply not to invoke an unsafe macro with arguments containing an assignment, increment, decrement, or function call, as in the following example:
#define ABS(x) (((x) < 0) ? -(x) : (x)) /* UNSAFE */ /* ... */ ++n; m = ABS(n);
Note the comment declaring the macro unsafe as a warning for programmers. Alternatively, the macro can be renamed ABS_UNSAFE()
to make it painfully apparent that the macro is unsafe.
Compliant Solution
A preferable compliant solution is to declare ABS()
as an inline function. (See PRE00-C. Prefer inline or static functions to function-like macros and PRE12-C. Do not define unsafe macros.)
inline int abs(int x) { return (((x) < 0) ? -(x) : (x)); } /* ... */ m = abs(++n);
This solution eliminates the problem of recalling which macros are safe and which are not. Unlike the ABS()
macro, which operates on operands of any type, the abs()
function accepts arguments only of type int
. C11 provides a way to overload expressions with arguments of different types, but C99 provides no such mechanism. Consequently, there is no way to achieve such genericity using the facilities of the standard C99 language. However, some C implementations provide extensions that make it possible to solve the original problem without using functions.
Compliant Solution (GCC)
GCC's Statement Expressions along with the __typeof
extension make it possible to declare and assign the value of the macro operand to a temporary of the same type and perform the computation on the temporary, thus guaranteeing that the operand will be evaluated exactly once:
#define ABS(x) ({ __typeof (x) __tmp = x; __tmp < 0 ? -__tmp : __tmp; })
Note that relying on such extensions makes code nonportable and goes against MSC14-C. Do not introduce unnecessary platform dependencies.
This code comes very close to violating DCL37-C. Do not declare or define a reserved identifier. It technically complies with this rule because it falls under exception DCL37-EX1. However, this code is potentially unsafe if it were invoked with a variable named __tmp
. Such calling code would constitute a genuine violation of DCL37-C. Finally, this code is unsafe if it is ever invoked on a platform where __tmp
actually has special meaning (see DCL37-C for more information). These are considered acceptable problems, as C provides no mechanism to declare a variable in a scope that is guaranteed to be distinct from all other variables in the same scope.
Exceptions
PRE31-EX1: An exception can be made for invoking an unsafe macro with a function call argument provided that the function has no side effects. However, it is easy to forget about obscure side effects that a function might have, especially library functions for which source code is not available; even changing errno
is a side effect. Unless the function is user-written and does nothing but perform a computation and return its result without calling any other functions, it is likely that many developers will forget about some side effect. Consequently, although this exception is allowed, it is not recommended.
Risk Assessment
Invoking an unsafe macro with an argument that has side effects may cause those side effects to occur more than once. This practice can lead to unexpected program behavior.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
PRE31-C | low | unlikely | low | P3 | L3 |
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
ECLAIR | 1.2 | CC2.PRE31 | Fully implemented |
9.7.1 | 9 S | Partially implemented | |
PRQA QA-C | Unable to render {include} The included page could not be found. | 3454 | Fully implemented |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
CERT C Secure Coding Standard | PRE12-C. Do not define unsafe macros |
CERT C++ Secure Coding Standard | PRE31-CPP. Avoid side-effects in arguments to unsafe macros |
ISO/IEC TR 24772:2013 | Pre-processor Directives [NMP] |
MISRA C:2012 | Rule 20.5 (advisory) |
Bibliography
[Plum 1985] | Rule 1-11 |