Skip to content

Commit f27f22b

Browse files
committed
[clang-tidy] Added bugprone-inc-dec-in-conditions check
Detects when a variable is both incremented/decremented and referenced inside a complex condition and suggests moving them outside to avoid ambiguity in the variable's value. Reviewed By: xgupta Differential Revision: https://reviews.llvm.org/D149015
1 parent 6dfa95d commit f27f22b

File tree

11 files changed

+305
-0
lines changed

11 files changed

+305
-0
lines changed

clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "ForwardingReferenceOverloadCheck.h"
2828
#include "ImplicitWideningOfMultiplicationResultCheck.h"
2929
#include "InaccurateEraseCheck.h"
30+
#include "IncDecInConditionsCheck.h"
3031
#include "IncorrectRoundingsCheck.h"
3132
#include "InfiniteLoopCheck.h"
3233
#include "IntegerDivisionCheck.h"
@@ -122,6 +123,8 @@ class BugproneModule : public ClangTidyModule {
122123
"bugprone-inaccurate-erase");
123124
CheckFactories.registerCheck<SwitchMissingDefaultCaseCheck>(
124125
"bugprone-switch-missing-default-case");
126+
CheckFactories.registerCheck<IncDecInConditionsCheck>(
127+
"bugprone-inc-dec-in-conditions");
125128
CheckFactories.registerCheck<IncorrectRoundingsCheck>(
126129
"bugprone-incorrect-roundings");
127130
CheckFactories.registerCheck<InfiniteLoopCheck>("bugprone-infinite-loop");

clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ add_clang_library(clangTidyBugproneModule
2323
ImplicitWideningOfMultiplicationResultCheck.cpp
2424
InaccurateEraseCheck.cpp
2525
SwitchMissingDefaultCaseCheck.cpp
26+
IncDecInConditionsCheck.cpp
2627
IncorrectRoundingsCheck.cpp
2728
InfiniteLoopCheck.cpp
2829
IntegerDivisionCheck.cpp
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//===--- IncDecInConditionsCheck.cpp - clang-tidy -------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "IncDecInConditionsCheck.h"
10+
#include "../utils/Matchers.h"
11+
#include "clang/AST/ASTContext.h"
12+
#include "clang/ASTMatchers/ASTMatchFinder.h"
13+
14+
using namespace clang::ast_matchers;
15+
16+
namespace clang::tidy::bugprone {
17+
18+
AST_MATCHER(BinaryOperator, isLogicalOperator) { return Node.isLogicalOp(); }
19+
20+
AST_MATCHER(UnaryOperator, isUnaryPrePostOperator) {
21+
return Node.isPrefix() || Node.isPostfix();
22+
}
23+
24+
AST_MATCHER(CXXOperatorCallExpr, isPrePostOperator) {
25+
return Node.getOperator() == OO_PlusPlus ||
26+
Node.getOperator() == OO_MinusMinus;
27+
}
28+
29+
void IncDecInConditionsCheck::registerMatchers(MatchFinder *Finder) {
30+
auto OperatorMatcher = expr(
31+
anyOf(binaryOperator(anyOf(isComparisonOperator(), isLogicalOperator())),
32+
cxxOperatorCallExpr(isComparisonOperator())));
33+
34+
Finder->addMatcher(
35+
expr(
36+
OperatorMatcher, unless(isExpansionInSystemHeader()),
37+
unless(hasAncestor(OperatorMatcher)), expr().bind("parent"),
38+
39+
forEachDescendant(
40+
expr(anyOf(unaryOperator(isUnaryPrePostOperator(),
41+
hasUnaryOperand(expr().bind("operand"))),
42+
cxxOperatorCallExpr(
43+
isPrePostOperator(),
44+
hasUnaryOperand(expr().bind("operand")))),
45+
hasAncestor(
46+
expr(equalsBoundNode("parent"),
47+
hasDescendant(
48+
expr(unless(equalsBoundNode("operand")),
49+
matchers::isStatementIdenticalToBoundNode(
50+
"operand"))
51+
.bind("second")))))
52+
.bind("operator"))),
53+
this);
54+
}
55+
56+
void IncDecInConditionsCheck::check(const MatchFinder::MatchResult &Result) {
57+
58+
SourceLocation ExprLoc;
59+
bool IsIncrementOp = false;
60+
61+
if (const auto *MatchedDecl =
62+
Result.Nodes.getNodeAs<CXXOperatorCallExpr>("operator")) {
63+
ExprLoc = MatchedDecl->getExprLoc();
64+
IsIncrementOp = (MatchedDecl->getOperator() == OO_PlusPlus);
65+
} else if (const auto *MatchedDecl =
66+
Result.Nodes.getNodeAs<UnaryOperator>("operator")) {
67+
ExprLoc = MatchedDecl->getExprLoc();
68+
IsIncrementOp = MatchedDecl->isIncrementOp();
69+
} else
70+
return;
71+
72+
diag(ExprLoc,
73+
"%select{decrementing|incrementing}0 and referencing a variable in a "
74+
"complex condition can cause unintended side-effects due to C++'s order "
75+
"of evaluation, consider moving the modification outside of the "
76+
"condition to avoid misunderstandings")
77+
<< IsIncrementOp;
78+
diag(Result.Nodes.getNodeAs<Expr>("second")->getExprLoc(),
79+
"variable is referenced here", DiagnosticIDs::Note);
80+
}
81+
82+
} // namespace clang::tidy::bugprone
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//===--- IncDecInConditionsCheck.h - clang-tidy -----------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INCDECINCONDITIONSCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INCDECINCONDITIONSCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::bugprone {
15+
16+
/// Detects when a variable is both incremented/decremented and referenced
17+
/// inside a complex condition and suggests moving them outside to avoid
18+
/// ambiguity in the variable's value.
19+
///
20+
/// For the user-facing documentation see:
21+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/inc-dec-in-conditions.html
22+
class IncDecInConditionsCheck : public ClangTidyCheck {
23+
public:
24+
IncDecInConditionsCheck(StringRef Name, ClangTidyContext *Context)
25+
: ClangTidyCheck(Name, Context) {}
26+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
27+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
28+
std::optional<TraversalKind> getCheckTraversalKind() const override {
29+
return TK_IgnoreUnlessSpelledInSource;
30+
}
31+
};
32+
33+
} // namespace clang::tidy::bugprone
34+
35+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INCDECINCONDITIONSCHECK_H

clang-tools-extra/clang-tidy/utils/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ add_clang_library(clangTidyUtils
1717
IncludeInserter.cpp
1818
IncludeSorter.cpp
1919
LexerUtils.cpp
20+
Matchers.cpp
2021
NamespaceAliaser.cpp
2122
OptionsUtils.cpp
2223
RenamerClangTidyCheck.cpp
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//===---------- Matchers.cpp - clang-tidy ---------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "Matchers.h"
10+
#include "ASTUtils.h"
11+
12+
namespace clang::tidy::matchers {
13+
14+
bool NotIdenticalStatementsPredicate::operator()(
15+
const clang::ast_matchers::internal::BoundNodesMap &Nodes) const {
16+
return !utils::areStatementsIdentical(Node.get<Stmt>(),
17+
Nodes.getNodeAs<Stmt>(ID), *Context);
18+
}
19+
20+
} // namespace clang::tidy::matchers

clang-tools-extra/clang-tidy/utils/Matchers.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,24 @@ matchesAnyListedName(llvm::ArrayRef<StringRef> NameList) {
140140
new MatchesAnyListedNameMatcher(NameList));
141141
}
142142

143+
// Predicate that verify if statement is not identical to one bound to ID node.
144+
struct NotIdenticalStatementsPredicate {
145+
bool
146+
operator()(const clang::ast_matchers::internal::BoundNodesMap &Nodes) const;
147+
148+
std::string ID;
149+
::clang::DynTypedNode Node;
150+
ASTContext *Context;
151+
};
152+
153+
// Checks if statement is identical (utils::areStatementsIdentical) to one bound
154+
// to ID node.
155+
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID) {
156+
NotIdenticalStatementsPredicate Predicate{
157+
ID, ::clang::DynTypedNode::create(Node), &(Finder->getASTContext())};
158+
return Builder->removeBindings(Predicate);
159+
}
160+
143161
} // namespace clang::tidy::matchers
144162

145163
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ Improvements to clang-tidy
116116
New checks
117117
^^^^^^^^^^
118118

119+
- New :doc:`bugprone-inc-dec-in-conditions
120+
<clang-tidy/checks/bugprone/inc-dec-in-conditions>` check.
121+
122+
Detects when a variable is both incremented/decremented and referenced inside
123+
a complex condition and suggests moving them outside to avoid ambiguity in
124+
the variable's value.
125+
119126
- New :doc:`bugprone-multi-level-implicit-pointer-conversion
120127
<clang-tidy/checks/bugprone/multi-level-implicit-pointer-conversion>` check.
121128

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
.. title:: clang-tidy - bugprone-inc-dec-in-conditions
2+
3+
bugprone-inc-dec-in-conditions
4+
==============================
5+
6+
Detects when a variable is both incremented/decremented and referenced inside a
7+
complex condition and suggests moving them outside to avoid ambiguity in the
8+
variable's value.
9+
10+
When a variable is modified and also used in a complex condition, it can lead to
11+
unexpected behavior. The side-effect of changing the variable's value within the
12+
condition can make the code difficult to reason about. Additionally, the
13+
developer's intended timing for the modification of the variable may not be
14+
clear, leading to misunderstandings and errors. This can be particularly
15+
problematic when the condition involves logical operators like ``&&`` and
16+
``||``, where the order of evaluation can further complicate the situation.
17+
18+
Consider the following example:
19+
20+
.. code-block:: c++
21+
22+
int i = 0;
23+
// ...
24+
if (i++ < 5 && i > 0) {
25+
// do something
26+
}
27+
28+
In this example, the result of the expression may not be what the developer
29+
intended. The original intention of the developer could be to increment ``i``
30+
after the entire condition is evaluated, but in reality, i will be incremented
31+
before ``i > 0`` is executed. This can lead to unexpected behavior and bugs in
32+
the code. To fix this issue, the developer should separate the increment
33+
operation from the condition and perform it separately. For example, they can
34+
increment ``i`` in a separate statement before or after the condition is
35+
evaluated. This ensures that the value of ``i`` is predictable and consistent
36+
throughout the code.
37+
38+
.. code-block:: c++
39+
40+
int i = 0;
41+
// ...
42+
i++;
43+
if (i <= 5 && i > 0) {
44+
// do something
45+
}
46+
47+
Another common issue occurs when multiple increments or decrements are performed
48+
on the same variable inside a complex condition. For example:
49+
50+
.. code-block:: c++
51+
52+
int i = 4;
53+
// ...
54+
if (i++ < 5 || --i > 2) {
55+
// do something
56+
}
57+
58+
There is a potential issue with this code due to the order of evaluation in C++.
59+
The ``||`` operator used in the condition statement guarantees that if the first
60+
operand evaluates to ``true``, the second operand will not be evaluated. This
61+
means that if ``i`` were initially ``4``, the first operand ``i < 5`` would
62+
evaluate to ``true`` and the second operand ``i > 2`` would not be evaluated.
63+
As a result, the decrement operation ``--i`` would not be executed and ``i``
64+
would hold value ``5``, which may not be the intended behavior for the developer.
65+
66+
To avoid this potential issue, the both increment and decrement operation on
67+
``i`` should be moved outside the condition statement.

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ Clang-Tidy Checks
9393
`bugprone-forwarding-reference-overload <bugprone/forwarding-reference-overload.html>`_,
9494
`bugprone-implicit-widening-of-multiplication-result <bugprone/implicit-widening-of-multiplication-result.html>`_, "Yes"
9595
`bugprone-inaccurate-erase <bugprone/inaccurate-erase.html>`_, "Yes"
96+
`bugprone-inc-dec-in-conditions <bugprone/inc-dec-in-conditions.html>`_,
9697
`bugprone-incorrect-roundings <bugprone/incorrect-roundings.html>`_,
9798
`bugprone-infinite-loop <bugprone/infinite-loop.html>`_,
9899
`bugprone-integer-division <bugprone/integer-division.html>`_,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// RUN: %check_clang_tidy %s bugprone-inc-dec-in-conditions %t
2+
3+
template<typename T>
4+
struct Iterator {
5+
Iterator operator++(int);
6+
Iterator operator--(int);
7+
Iterator& operator++();
8+
Iterator& operator--();
9+
T operator*();
10+
bool operator==(Iterator) const;
11+
bool operator!=(Iterator) const;
12+
};
13+
14+
template<typename T>
15+
struct Container {
16+
Iterator<T> begin();
17+
Iterator<T> end();
18+
};
19+
20+
bool f(int x) {
21+
return (++x != 5 or x == 10);
22+
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: incrementing and referencing a variable in a complex condition can cause unintended side-effects due to C++'s order of evaluation, consider moving the modification outside of the condition to avoid misunderstandings [bugprone-inc-dec-in-conditions]
23+
}
24+
25+
bool f2(int x) {
26+
return (x++ != 5 or x == 10);
27+
// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: incrementing and referencing a variable in a complex condition can cause unintended side-effects due to C++'s order of evaluation, consider moving the modification outside of the condition to avoid misunderstandings [bugprone-inc-dec-in-conditions]
28+
}
29+
30+
bool c(Container<int> x) {
31+
auto it = x.begin();
32+
return (it++ != x.end() and *it);
33+
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: incrementing and referencing a variable in a complex condition can cause unintended side-effects due to C++'s order of evaluation, consider moving the modification outside of the condition to avoid misunderstandings [bugprone-inc-dec-in-conditions]
34+
}
35+
36+
bool c2(Container<int> x) {
37+
auto it = x.begin();
38+
return (++it != x.end() and *it);
39+
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: incrementing and referencing a variable in a complex condition can cause unintended side-effects due to C++'s order of evaluation, consider moving the modification outside of the condition to avoid misunderstandings [bugprone-inc-dec-in-conditions]
40+
}
41+
42+
bool d(int x) {
43+
return (--x != 5 or x == 10);
44+
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: decrementing and referencing a variable in a complex condition can cause unintended side-effects due to C++'s order of evaluation, consider moving the modification outside of the condition to avoid misunderstandings [bugprone-inc-dec-in-conditions]
45+
}
46+
47+
bool d2(int x) {
48+
return (x-- != 5 or x == 10);
49+
// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: decrementing and referencing a variable in a complex condition can cause unintended side-effects due to C++'s order of evaluation, consider moving the modification outside of the condition to avoid misunderstandings [bugprone-inc-dec-in-conditions]
50+
}
51+
52+
bool g(Container<int> x) {
53+
auto it = x.begin();
54+
return (it-- != x.end() and *it);
55+
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: decrementing and referencing a variable in a complex condition can cause unintended side-effects due to C++'s order of evaluation, consider moving the modification outside of the condition to avoid misunderstandings [bugprone-inc-dec-in-conditions]
56+
}
57+
58+
bool g2(Container<int> x) {
59+
auto it = x.begin();
60+
return (--it != x.end() and *it);
61+
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: decrementing and referencing a variable in a complex condition can cause unintended side-effects due to C++'s order of evaluation, consider moving the modification outside of the condition to avoid misunderstandings [bugprone-inc-dec-in-conditions]
62+
}
63+
64+
bool doubleCheck(Container<int> x) {
65+
auto it = x.begin();
66+
auto it2 = x.begin();
67+
return (--it != x.end() and ++it2 != x.end()) and (*it == *it2);
68+
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: decrementing and referencing a variable in a complex condition can cause unintended side-effects due to C++'s order of evaluation, consider moving the modification outside of the condition to avoid misunderstandings [bugprone-inc-dec-in-conditions]
69+
// CHECK-MESSAGES: :[[@LINE-2]]:31: warning: incrementing and referencing a variable in a complex condition can cause unintended side-effects due to C++'s order of evaluation, consider moving the modification outside of the condition to avoid misunderstandings [bugprone-inc-dec-in-conditions]
70+
}

0 commit comments

Comments
 (0)