|
| 1 | +/** |
| 2 | + * @id c/cert/side-effects-in-arguments-to-unsafe-macros |
| 3 | + * @name PRE31-C: Avoid side effects in arguments to unsafe macros |
| 4 | + * @description Macro arguments can be expanded multiple times which can cause side-effects to be |
| 5 | + * evaluated multiple times. |
| 6 | + * @kind problem |
| 7 | + * @precision low |
| 8 | + * @problem.severity error |
| 9 | + * @tags external/cert/id/pre31-c |
| 10 | + * correctness |
| 11 | + * external/cert/obligation/rule |
| 12 | + */ |
| 13 | + |
| 14 | +import cpp |
| 15 | +import codingstandards.c.cert |
| 16 | +import codingstandards.cpp.Macro |
| 17 | +import codingstandards.cpp.SideEffect |
| 18 | +import codingstandards.cpp.StructuralEquivalence |
| 19 | +import codingstandards.cpp.sideeffect.DefaultEffects |
| 20 | +import codingstandards.cpp.sideeffect.Customizations |
| 21 | + |
| 22 | +class FunctionCallEffect extends GlobalSideEffect::Range { |
| 23 | + FunctionCallEffect() { |
| 24 | + exists(Function f | |
| 25 | + f = this.(FunctionCall).getTarget() and |
| 26 | + // Not a side-effecting function |
| 27 | + not f.(BuiltInFunction).getName() = "__builtin_expect" and |
| 28 | + // Not side-effecting functions |
| 29 | + not exists(string name | |
| 30 | + name = |
| 31 | + [ |
| 32 | + "acos", "asin", "atan", "atan2", "ceil", "cos", "cosh", "exp", "fabs", "floor", "fmod", |
| 33 | + "frexp", "ldexp", "log", "log10", "modf", "pow", "sin", "sinh", "sqrt", "tan", "tanh", |
| 34 | + "cbrt", "erf", "erfc", "exp2", "expm1", "fdim", "fma", "fmax", "fmin", "hypot", "ilogb", |
| 35 | + "lgamma", "llrint", "llround", "log1p", "log2", "logb", "lrint", "lround", "nan", |
| 36 | + "nearbyint", "nextafter", "nexttoward", "remainder", "remquo", "rint", "round", |
| 37 | + "scalbln", "scalbn", "tgamma", "trunc" |
| 38 | + ] and |
| 39 | + f.hasGlobalOrStdName([name, name + "f", name + "l"]) |
| 40 | + ) |
| 41 | + ) |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +class CrementEffect extends LocalSideEffect::Range { |
| 46 | + CrementEffect() { this instanceof CrementOperation } |
| 47 | +} |
| 48 | + |
| 49 | +from |
| 50 | + FunctionLikeMacro flm, MacroInvocation mi, Expr e, SideEffect sideEffect, int i, string arg, |
| 51 | + string sideEffectDesc |
| 52 | +where |
| 53 | + not isExcluded(e, SideEffects4Package::sideEffectsInArgumentsToUnsafeMacrosQuery()) and |
| 54 | + sideEffect = getASideEffect(e) and |
| 55 | + flm.getAnInvocation() = mi and |
| 56 | + not exists(mi.getParentInvocation()) and |
| 57 | + mi.getAnExpandedElement() = e and |
| 58 | + // Only consider arguments that are expanded multiple times, and do not consider "stringified" arguments |
| 59 | + count(int index | index = flm.getAParameterUse(i) and not flm.getBody().charAt(index) = "#") > 1 and |
| 60 | + arg = mi.getExpandedArgument(i) and |
| 61 | + ( |
| 62 | + sideEffect instanceof CrementEffect and |
| 63 | + exists(arg.indexOf(sideEffect.(CrementOperation).getOperator())) and |
| 64 | + sideEffectDesc = "the use of the " + sideEffect.(CrementOperation).getOperator() + " operator" |
| 65 | + or |
| 66 | + sideEffect instanceof FunctionCallEffect and |
| 67 | + exists(arg.indexOf(sideEffect.(FunctionCall).getTarget().getName() + "(")) and |
| 68 | + sideEffectDesc = |
| 69 | + "a call to the function '" + sideEffect.(FunctionCall).getTarget().getName() + "'" |
| 70 | + ) |
| 71 | +select sideEffect, |
| 72 | + "Argument " + mi.getUnexpandedArgument(i) + " to unsafe macro '" + flm.getName() + |
| 73 | + "' is expanded to '" + arg + "' multiple times and includes " + sideEffectDesc + |
| 74 | + " as a side-effect." |
0 commit comments