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 should warn against 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 a convenient mechanism for incorporating diagnostic tests in code. (See MSC11-C. Incorporate diagnostic tests using assertions.) Expressions used with the standard assert
macro should not have side effects. The behavior of the assert
macro depends on the status of the NDEBUG
preprocessor symbol. If NDEBUG
is undefined, the assert
macro is defined to evaluate its expression argument and abort if the result of the expression compares equal to 0
. If NDEBUG
is defined, assert
is defined to expand to ((void)0)
. Consequently, any side effects resulting from evaluation of the expression in the assertion are lost in non-debugging versions of the code.
This rule is similar to EXP44-C. Do not use side effects in operands to sizeof, _Alignof, or _Generic.
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)) void func(int n) { /* Validate n is within the desired range */ int 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
In this compliant solution, the increment operation ++n
is performed before the call to the unsafe macro.
#define ABS(x) (((x) < 0) ? -(x) : (x)) /* UNSAFE */ void func(int n) { /* Validate n is within the desired range */ ++n; int m = ABS(n); /* ... */ }
Note the comment informing programmers that the macro is unsafe. The macro can also be renamed ABS_UNSAFE()
to make it painfully apparent that the macro is unsafe. This compliant solution, like all the compliant solutions for this rule, have undefined behavior if to ABS()
is equal to the minimum (most negative) value for the signed integer type. See INT32-C. Ensure that operations on signed integers do not result in overflow for more information.
Compliant Solution
This compliant solution follows the guidance of PRE00-C. Prefer inline or static functions to function-like macros by defining an inline function iabs()
to replace the ABS()
macro. Unlike the ABS()
macro, which operates on operands of any type, the abs()
function accepts arguments only of type int
.
#include <complex.h> #include <math.h> static inline int iabs(int x) { return (((x) < 0) ? -(x) : (x)); } void func(int n) { /* Validate n is within the desired range */ ++n; int m = abs(n); /* ... */ }
Compliant Solution
The preferred compliant solution is to declare the ABS()
macro using a _Generic
selection. To support all arithmetic data types, this solution also makes use of inline functions to handle integer absolute values. (See PRE00-C. Prefer inline or static functions to function-like macros and PRE12-C. Do not define unsafe macros.)
According to the C Standard, subclause 6.5.1.1 paragraph 3 [ISO/IEC 9899:2011],
The controlling expression of a generic selection is not evaluated. If a generic selection has a generic association with a type name that is compatible with the type of the controlling expression, then the result expression of the generic selection is the expression in that generic association. Otherwise, the result expression of the generic selection is the expression in the
default
generic association. None of the expressions from any other generic association of the generic selection is evaluated.
Because the expression is not evaluated as part of the generic selection, the use of a macro in this solution is guaranteed to only evaluate the macro parameter v
once.
#include <complex.h> #include <math.h> static inline long long llabs(long long v) { return v < 0 ? -v : v; } static inline long labs(long v) { return v < 0 ? -v : v; } static inline int iabs(int v) { return v < 0 ? -v : v; } static inline short sabs(short v) { return v < 0 ? -v : v; } static inline signed char scabs(signed char v) { return v < 0 ? -v : v; } #define ABS(v) _Generic(v, signed char : scabs \ short : sabs \ int : iabs \ long : labs \ long long : llabs \ float : fabsf \ double : fabs \ long double : fabsl \ double complex : cabs \ float complex : cabsf \ long double complex : cabsl)(v) void func(int n) { /* Validate n is within the desired range */ int m = ABS(++n); /* ... */ }
Generic selections were introduced in C11 and are not available in C99 and earlier editions of the C standard. However, some C implementations provide extensions that make it possible to solve the original problem without using functions.
Compliant Solution (GCC)
GCC's __typeof
extension makes 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) do { __typeof (x) __tmp = x; __tmp < 0 ? -__tmp : __tmp; } while(0)
Note that relying on such extensions makes code nonportable and violates MSC14-C. Do not introduce unnecessary platform dependencies.
This code comes 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.
Noncompliant Code Example (assert()
)
This noncompliant code example includes an assert
macro containing an expression (index++
) that has a side effect:
#include <assert.h> void process(size_t index) { assert(index++ > 0); /* Side effect */ /* ... */ }
Compliant Solution (assert()
)
This compliant solution avoids the possibility of side effects in assertions by moving the expression containing the side effect outside of the assert
macro.
#include <assert.h> void process(size_t index) { assert(index > 0); /* No side effect */ ++index; /* ... */ }
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 |
Tool | Version | Checker | Description |
---|---|---|---|
2017.07 | ASSERT_SIDE_EFFECTS | Can detect the specific instance where assertion contains an operation/function call that may have a side effect | |
1.2 | CC2.EXP31 | Fully implemented | |
9.7.1 | 9 S | Fully implemented | |
PRQA QA-C | Unable to render {include} The included page could not be found. | 3440 | 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 | EXP31-CPP. Avoid side effects in assertions |
CERT Oracle Secure Coding Standard for Java | EXP06-J. Do not use side-effecting expressions in assertions |
Bibliography
[ISO/IEC 9899:2011] | Subclause 6.5.1.1, "Generic selection" |
[Plum 1985] | Rule 1-11 |
[Dewhurst 2002] | Gotcha 28: "Side Effects in Assertions" |