CertC-PRE31¶
Avoid side effects in arguments to unsafe macros
Required inputs: IR
An unsafe function-like macro is one whose expansion results in evaluating one of its parameters more than once or not at all. Never invoke an unsafe macro with arguments containing an assignment, increment, decrement, volatile access, input/output, or other expressions with side effects (including function calls, which may cause side effects).
The documentation for unsafe macros should warn against invoking them with arguments with side effects, but the responsibility is on the programmer using the macro. Because of the risks associated with their use, it is recommended that the creation of unsafe function-like macros be avoided. (See PRE00-C. Prefer inline or static functions to function-like macros.)
This rule is similar to EXP44-C. Do not rely on 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 that 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 that n is within the desired range */
++n;
int m = ABS(n);
/* ... */
}
Note the comment warning programmers that the macro is unsafe. The macro can
also be renamed
ABS_UNSAFE() to make it clear that the macro is unsafe. This
compliant solution, like all the compliant solutions for this rule, has
undefined behavior if the argument 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
iabs() function will truncate arguments of types wider than
int whose value is not in range of the latter type.
#include <complex.h>
#include <math.h>
static inline int iabs(int x) {
return (((x) < 0) ? -(x) : (x));
}
void func(int n) {
/* Validate that n is within the desired range */
int m = iabs(++n);
/* ... */
}
Compliant Solution
A more flexible 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 compute 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, 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
defaultgeneric 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 evaluate the macro parameter
v only 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 int sabs(short v) {
return v < 0 ? -v : v;
}
static inline int 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 that 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.
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, consequently guaranteeing that the operand will
be evaluated exactly once. Another GCC extension, known as
statement expression, makes it possible for the
block statement to appear where an expression is expected:
#define ABS(x) __extension__ ({ __typeof (x) tmp = x; \
tmp < 0 ? -tmp : tmp; })
Note that relying on such extensions makes code nonportable and violates MSC14-C. Do not introduce unnecessary platform dependencies.
Noncompliant Code Example (
assert())
The
assert() macro is a convenient mechanism for incorporating
diagnostic tests in code. (See
MSC11-C.
Incorporate diagnostic tests using assertions.) Expressions used as
arguments to the standard
assert() macro should not have side effects. The behavior of the
assert() macro depends on the definition of the object-like macro
NDEBUG. If the macro
NDEBUG is undefined, the
assert() macro is defined to evaluate its expression argument and,
if the result of the expression compares equal to 0, call the
abort() function. If
NDEBUG is defined,
assert is defined to expand to
((void)0). Consequently, the expression in the assertion is not
evaluated, and no side effects it may have had otherwise take place in
non-debugging executions of the code.
This noncompliant code example includes an
assert() macro containing an expression (
index++) that has a side effect:
#include <assert.h>
#include <stddef.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>
#include <stddef.h>
void process(size_t index) {
assert(index > 0); /* No side effect */
++index;
/* ... */
}
Exceptions
PRE31-C-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, this exception must be used with great care.
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 |
Related Guidelines
| Taxonomy | Taxonomy item | Relationship |
|---|
Bibliography
| [ Dewhurst 2002] | Gotcha #28, "Side Effects in Assertions" |
| [ ISO/IEC 9899:2011] | Subclause 6.5.1.1, "Generic Selection" |
| [ Plum 1985] | Rule 1-11 |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
side_effect_in_unsafe_macro_call |
Side-effect in call to unsafe macro |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
allowed_effects¶
allowed_effects : set[SimpleEffect] = set()
Option Types¶
These types are used by options listed above:
SimpleEffect¶
This enum is a simplified representation of the effects. It's used by stylecheck rules as `Set[SimpleEffect]` to allow the user to configure additional allowed effects.alloc_free
Memory allocations (malloc, free, new, delete).volatile
Accessing volatile memory.atomic
Atomic access; also used for inter-thread memory barriers.unknown_code
The "Unknown Code" effect appears when calling a function that has no definition in the IR.exceptions
Throwing C++ exceptions.optimizer_hint
Optimizer hints without semantic effect. Examples are '__builtin_assume' or '__builtin_unreachable'.