The vulnerability as described in ISO/IEC TR 24772-1:2019 clause 6.51 applies to C++.
The C++ pre-processor allows the use of macros that are text-replaced before compilation.
Function-like macros look similar to functions but have different semantics. Because the arguments are text-replaced, expressions passed to a function-like macro may be evaluated multiple times. This can result in unintended and undefined behaviour [EWF] if the arguments have side effects or are pre-processor directives. Additionally, the arguments and body of function-like macros should be fully parenthesized to avoid unintended and undefined behaviour.
The following code example demonstrates undefined behaviour when a function-like macro is called with arguments that have side-effects (in this case, the increment operator) .
#define CUBE(X) ((X) \* (X) \* (X))
// ...
int i = 2;
int a = 81 / CUBE(++i);
The above example could expand to:
int a = 81 / ((++i) * (++i) * (++i));
which has undefined behaviour so this macro expansion is difficult to predict.
Another mechanism of failure can occur when the arguments within the body of a function-like macro are not fully parenthesized. The following example shows the CUBE macro without parenthesized arguments.
#define CUBE(X) (X \* X \* X)
// ...
int a = CUBE(2 + 1);
This example expands to:
int a = (2 + 1 * 2 + 1 * 2 + 1)
which evaluates to 7 instead of the intended 27.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Replace function-like macros with constexpr
inline functions where possible.
Replace normal macros with constexpr
variables where possible.
Replace conditional compilation with the preprocessor with if constexpr
where possible, e.g., in function bodies, including cases where compile-time define of a macro (as empty) controls if a macro definition is used to expand to an empty statement or another statement.
Replace preprocessor include directives with module import
where possible.
Prefer std::source_location
mechanisms over employing macros that use __LINE__
, __FILE__
, or __func__
.
If a function-like macro must be used, ensure that its parameters and body are parenthesized.
In a function-like macro, ensure that each argument is evaluated at most once.
Prohibit embedding the following in a function-like macro: pre-processor directives; or side-effects such as an assignment, increment/decrement, volatile access, or function call.
Only use macros for include guards, to control conditional compilation, or when the macro’s definition requires token pasting (##
.{.cpp}) or stringification (#
) of macro arguments.
Prohibit the use of macro paramters that are used as argument for ##
or #
in the macro.