Main Content

Side effect in arguments to unsafe macro

Macro contains arguments that can be evaluated multiple times or not evaluated

Description

This defect occurs when you call an unsafe macro with an expression that has a side effect.

  • Unsafe macro: When expanded, an unsafe macro evaluates its arguments multiple times or does not evaluate its argument at all.

    For instance, the ABS macro evaluates its argument x twice.

    #define ABS(x) (((x) < 0) ? -(x) : (x))

  • Side effect: When evaluated, an expression with a side effect modifies at least one of the variables in the expression.

    For instance, ++n modifies n, but n+1 does not modify n.

    The checker does not consider side effects in nested macros. The checker also does not consider function calls or volatile variable access as side effects.

Risk

If you call an unsafe macro with an expression that has a side effect, the expression is evaluated multiple times or not evaluated at all. The side effect can occur multiple times or not occur at all, causing unexpected behavior.

For instance, in the call MACRO(++n), you expect only one increment of the variable n. If MACRO is an unsafe macro, the increment happens more than once or does not happen at all.

The checker flags expressions with side effects in the assert macro because the assert macro is disabled in non-debug mode. To compile in non-debug mode, you define the NDEBUG macro during compilation. For instance, in GCC, you use the flag -DNDEBUG.

Fix

Evaluate the expression with a side effect in a separate statement, and then use the result as a macro argument.

For instance, instead of:

MACRO(++n);
perform the operation in two steps:
++n;
MACRO(n);
Alternatively, use an inline function instead of a macro. Pass the expression with side effect as argument to the inline function.

The checker considers modifications of a local variable defined only in the block scope of a macro body as a side effect. This defect cannot happen since the variable is visible only in the macro body. If you see a defect of this kind, ignore the defect.

Examples

expand all

#define ABS(x) (((x) < 0) ? -(x) : (x))
  
void func(int n) {
  /* Validate that n is within the desired range */
  int m = ABS(++n);
 
  /* ... */
}

In this example, the ABS macro evaluates its argument twice. The second evaluation can result in an unintended increment.

Correction — Separate Evaluation of Expression from Macro Usage

One possible correction is to first perform the increment, and then pass the result to the macro.

#define ABS(x) (((x) < 0) ? -(x) : (x))
  
void func(int n) {
  /* Validate that n is within the desired range */
  ++n;
  int m = ABS(n);
 
  /* ... */
}
Correction — Evaluate Expression in Inline Function

Another possible correction is to evaluate the expression in an inline function.

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);
 
  /* ... */
}

Result Information

Group: Programming
Language: C | C++
Default: Off
Command-Line Syntax: SIDE_EFFECT_IN_UNSAFE_MACRO_ARG
Impact: Medium

Version History

Introduced in R2018b