xref: /freebsd/sys/contrib/openzfs/.github/codeql/custom-queries/cpp/AssignmentOfComparisonAsCondition.ql (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
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