parts/6.51.Pre-processorDirectives-NMP.md

6.51 Pre-processor Directives [NMP]

6.51.1 Applicability to language

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.

Both issues shown above are a result of the text replacement mechanisms of preprocessing. Usually such function-like macros can be replaced by type-safe constexpr functions that do not suffer from the vulnerabilities caused by text replacement, as shown below:

  constexpr auto CUBE(auto const x) { return x * x * x; }

Historically, one use of function-like macros was to provide source-location information via the compiler-provided macros __LINE__, __FILE__, and __func__. Modern C++ provides std::source_location with the std::sources_location::current() default function argument that allows passing that information from a function’s call site without the need to use a macro.

Similar issues can apply to object-like macros:

#define THREE 1+2
  auto x = THREE*THREE

becomes

  auto x = 1+2*1+2 // 5, not 6 as expected

Most object-like macros can be replaced by constexpr variables that do not suffer from this vulnerability.

A remaining use case for function-like macros is the use of “stringification” (#) of macro arguments or joining macro arguments to form a new token (##). C++ does not specify the order of evaluation, hence care is necessary in macros that contain multiple instances of these operators.

double operator""_ms(const char *, unsigned long );
#define jstringify( x, y ) # x ## y
auto s = jstringify( 0, _ms );   //Expands to "0"_ms or "0_ms"

6.51.2 Avoidance mechanisms for language users

To avoid the vulnerability or mitigate its ill effects, C++ software developers can: