1*22649d4dSMartin Matuska/** 2*22649d4dSMartin Matuska * @name Ambiguous assignment-of-comparison in a condition 3*22649d4dSMartin Matuska * @description Finds any assignment (`=`, `+=`, `-=`, `|=`, …) that appears in 4*22649d4dSMartin Matuska * a branching condition and whose rvalue is an unparenthesized 5*22649d4dSMartin Matuska * comparison. 6*22649d4dSMartin Matuska * 7*22649d4dSMartin Matuska * Comparison binds tighter than assignment, so 8*22649d4dSMartin Matuska * `if ((x = foo() < 0))` / `if ((x += foo() < 0))` 9*22649d4dSMartin Matuska * assigns the boolean result of the comparison. Programmers often 10*22649d4dSMartin Matuska * meant 11*22649d4dSMartin Matuska * `if ((x = foo()) < 0)` / `if ((x += foo()) < 0)` 12*22649d4dSMartin Matuska * (assign/update then compare). GCC `-Wparentheses` is silenced by 13*22649d4dSMartin Matuska * the outer parentheses in both forms, so the compiler does not 14*22649d4dSMartin Matuska * catch the mistake (see openzfs/zfs#18874). 15*22649d4dSMartin Matuska * 16*22649d4dSMartin Matuska * Rule (intentionally simple): 17*22649d4dSMartin Matuska * assignment in a branch condition 18*22649d4dSMartin Matuska * AND rvalue is a comparison 19*22649d4dSMartin Matuska * AND that comparison is not parenthesized. 20*22649d4dSMartin Matuska * 21*22649d4dSMartin Matuska * That is enough: 22*22649d4dSMartin Matuska * - Explicit assign-then-compare `((x = foo()) < 0)` has no 23*22649d4dSMartin Matuska * comparison as the assignment's rvalue. 24*22649d4dSMartin Matuska * - Explicit compare-then-assign `((x = (foo() < 0)))` has a 25*22649d4dSMartin Matuska * parenthesized comparison as the rvalue. 26*22649d4dSMartin Matuska * - Parentheses that only wrap a subexpression of the comparison 27*22649d4dSMartin Matuska * (e.g. `(foo())`) do not count as parenthesizing the 28*22649d4dSMartin Matuska * comparison itself. 29*22649d4dSMartin Matuska * @kind problem 30*22649d4dSMartin Matuska * @problem.severity error 31*22649d4dSMartin Matuska * @precision high 32*22649d4dSMartin Matuska * @id cpp/ambiguous-assignment-of-comparison-in-condition 33*22649d4dSMartin Matuska * @tags reliability 34*22649d4dSMartin Matuska * correctness 35*22649d4dSMartin Matuska * external/cwe/cwe-783 36*22649d4dSMartin Matuska * external/cwe/cwe-480 37*22649d4dSMartin Matuska */ 38*22649d4dSMartin Matuska 39*22649d4dSMartin Matuskaimport cpp 40*22649d4dSMartin Matuska 41*22649d4dSMartin Matuska/** 42*22649d4dSMartin Matuska * The condition expression of a branching construct 43*22649d4dSMartin Matuska * (if / while / do-while / for / ternary `?:`). 44*22649d4dSMartin Matuska */ 45*22649d4dSMartin MatuskaExpr branchCondition() { 46*22649d4dSMartin Matuska result = any(IfStmt s).getCondition() 47*22649d4dSMartin Matuska or 48*22649d4dSMartin Matuska result = any(WhileStmt s).getCondition() 49*22649d4dSMartin Matuska or 50*22649d4dSMartin Matuska result = any(DoStmt s).getCondition() 51*22649d4dSMartin Matuska or 52*22649d4dSMartin Matuska result = any(ForStmt s).getCondition() 53*22649d4dSMartin Matuska or 54*22649d4dSMartin Matuska result = any(ConditionalExpr c).getCondition() 55*22649d4dSMartin Matuska} 56*22649d4dSMartin Matuska 57*22649d4dSMartin Matuska/** 58*22649d4dSMartin Matuska * Holds if `e` is the branch condition, or appears anywhere under it 59*22649d4dSMartin Matuska * (including under `&&` / `||` / `!`, and through parentheses/conversions). 60*22649d4dSMartin Matuska */ 61*22649d4dSMartin Matuskapredicate inBranchCondition(Expr e) { 62*22649d4dSMartin Matuska e = branchCondition() 63*22649d4dSMartin Matuska or 64*22649d4dSMartin Matuska // Structural child of something already in a branch condition 65*22649d4dSMartin Matuska exists(Expr parent | 66*22649d4dSMartin Matuska parent.getAChild() = e and 67*22649d4dSMartin Matuska inBranchCondition(parent) 68*22649d4dSMartin Matuska ) 69*22649d4dSMartin Matuska or 70*22649d4dSMartin Matuska // ParenthesisExpr and other conversions hang off getConversion(), not getAChild() 71*22649d4dSMartin Matuska exists(Expr base | 72*22649d4dSMartin Matuska base.getConversion+() = e and 73*22649d4dSMartin Matuska inBranchCondition(base) 74*22649d4dSMartin Matuska ) 75*22649d4dSMartin Matuska or 76*22649d4dSMartin Matuska exists(Expr conv | 77*22649d4dSMartin Matuska e.getConversion+() = conv and 78*22649d4dSMartin Matuska inBranchCondition(conv) 79*22649d4dSMartin Matuska ) 80*22649d4dSMartin Matuska} 81*22649d4dSMartin Matuska 82*22649d4dSMartin Matuskafrom Assignment a, ComparisonOperation cmp 83*22649d4dSMartin Matuskawhere 84*22649d4dSMartin Matuska // Core rule: assignment in a branch condition whose rvalue is an 85*22649d4dSMartin Matuska // unparenthesized comparison. Covers `=` and all compound forms (`+=`, …) 86*22649d4dSMartin Matuska // because Assignment is the common base class. 87*22649d4dSMartin Matuska a.getRValue() = cmp and 88*22649d4dSMartin Matuska not cmp.isParenthesised() and 89*22649d4dSMartin Matuska inBranchCondition(a) and 90*22649d4dSMartin Matuska // Skip AST from templates that were never instantiated (incomplete / non-code). 91*22649d4dSMartin Matuska not a.isFromUninstantiatedTemplate(_) 92*22649d4dSMartin Matuskaselect a, 93*22649d4dSMartin Matuska "Assignment (`" + a.getOperator() + 94*22649d4dSMartin Matuska "`) in a branch condition has an unparenthesized comparison as its rvalue; " + 95*22649d4dSMartin Matuska "likely meant `((x " + a.getOperator() + " expr) op val)` rather than " + 96*22649d4dSMartin Matuska "`(x " + a.getOperator() + " expr op val)`." 97