Skip to content

Commit 3f3df0c

Browse files
author
Vince Bridgers
committed
[analyzer] Port alpha.core.IdenticalExpr to Tidy checks and remove
This change removes the alpha.core.IdenticalExpr static analysis checker since it's checks are present in the clang-tidy checks misc-redundant-expression and bugprone-branch-clone. This check was implemented as a static analysis check using AST matching, and since alpha and duplicated in 2 clang-tidy checks may be removed. The existing LIT test was checked case by case, and the tidy checks were improved to maintain alpha.core.IdenticalExpr features.
1 parent b70eb86 commit 3f3df0c

File tree

11 files changed

+580
-661
lines changed

11 files changed

+580
-661
lines changed

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

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,209 @@ void BranchCloneCheck::registerMatchers(MatchFinder *Finder) {
102102
this);
103103
Finder->addMatcher(switchStmt().bind("switch"), this);
104104
Finder->addMatcher(conditionalOperator().bind("condOp"), this);
105+
Finder->addMatcher(ifStmt(hasDescendant(ifStmt())).bind("ifWithDescendantIf"),
106+
this);
107+
}
108+
109+
/// Determines whether two statement trees are identical regarding
110+
/// operators and symbols.
111+
///
112+
/// Exceptions: expressions containing macros or functions with possible side
113+
/// effects are never considered identical.
114+
/// Limitations: (t + u) and (u + t) are not considered identical.
115+
/// t*(u + t) and t*u + t*t are not considered identical.
116+
///
117+
static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1,
118+
const Stmt *Stmt2, bool IgnoreSideEffects) {
119+
120+
if (!Stmt1 || !Stmt2) {
121+
return !Stmt1 && !Stmt2;
122+
}
123+
124+
// If Stmt1 & Stmt2 are of different class then they are not
125+
// identical statements.
126+
if (Stmt1->getStmtClass() != Stmt2->getStmtClass())
127+
return false;
128+
129+
const auto *Expr1 = dyn_cast<Expr>(Stmt1);
130+
const auto *Expr2 = dyn_cast<Expr>(Stmt2);
131+
132+
if (Expr1 && Expr2) {
133+
// If Stmt1 has side effects then don't warn even if expressions
134+
// are identical.
135+
if (!IgnoreSideEffects && Expr1->HasSideEffects(Ctx))
136+
return false;
137+
// If either expression comes from a macro then don't warn even if
138+
// the expressions are identical.
139+
if ((Expr1->getExprLoc().isMacroID()) || (Expr2->getExprLoc().isMacroID()))
140+
return false;
141+
142+
// If all children of two expressions are identical, return true.
143+
Expr::const_child_iterator I1 = Expr1->child_begin();
144+
Expr::const_child_iterator I2 = Expr2->child_begin();
145+
while (I1 != Expr1->child_end() && I2 != Expr2->child_end()) {
146+
if (!*I1 || !*I2 || !isIdenticalStmt(Ctx, *I1, *I2, IgnoreSideEffects))
147+
return false;
148+
++I1;
149+
++I2;
150+
}
151+
// If there are different number of children in the statements, return
152+
// false.
153+
if (I1 != Expr1->child_end())
154+
return false;
155+
if (I2 != Expr2->child_end())
156+
return false;
157+
}
158+
159+
switch (Stmt1->getStmtClass()) {
160+
default:
161+
return false;
162+
case Stmt::CallExprClass:
163+
case Stmt::ArraySubscriptExprClass:
164+
case Stmt::ArraySectionExprClass:
165+
case Stmt::OMPArrayShapingExprClass:
166+
case Stmt::OMPIteratorExprClass:
167+
case Stmt::ImplicitCastExprClass:
168+
case Stmt::ParenExprClass:
169+
case Stmt::BreakStmtClass:
170+
case Stmt::ContinueStmtClass:
171+
case Stmt::NullStmtClass:
172+
return true;
173+
case Stmt::CStyleCastExprClass: {
174+
const auto *CastExpr1 = cast<CStyleCastExpr>(Stmt1);
175+
const auto *CastExpr2 = cast<CStyleCastExpr>(Stmt2);
176+
177+
return CastExpr1->getTypeAsWritten() == CastExpr2->getTypeAsWritten();
178+
}
179+
case Stmt::ReturnStmtClass: {
180+
const auto *ReturnStmt1 = cast<ReturnStmt>(Stmt1);
181+
const auto *ReturnStmt2 = cast<ReturnStmt>(Stmt2);
182+
183+
return isIdenticalStmt(Ctx, ReturnStmt1->getRetValue(),
184+
ReturnStmt2->getRetValue(), IgnoreSideEffects);
185+
}
186+
case Stmt::ForStmtClass: {
187+
const auto *ForStmt1 = cast<ForStmt>(Stmt1);
188+
const auto *ForStmt2 = cast<ForStmt>(Stmt2);
189+
190+
if (!isIdenticalStmt(Ctx, ForStmt1->getInit(), ForStmt2->getInit(),
191+
IgnoreSideEffects))
192+
return false;
193+
if (!isIdenticalStmt(Ctx, ForStmt1->getCond(), ForStmt2->getCond(),
194+
IgnoreSideEffects))
195+
return false;
196+
if (!isIdenticalStmt(Ctx, ForStmt1->getInc(), ForStmt2->getInc(),
197+
IgnoreSideEffects))
198+
return false;
199+
if (!isIdenticalStmt(Ctx, ForStmt1->getBody(), ForStmt2->getBody(),
200+
IgnoreSideEffects))
201+
return false;
202+
return true;
203+
}
204+
case Stmt::DoStmtClass: {
205+
const auto *DStmt1 = cast<DoStmt>(Stmt1);
206+
const auto *DStmt2 = cast<DoStmt>(Stmt2);
207+
208+
if (!isIdenticalStmt(Ctx, DStmt1->getCond(), DStmt2->getCond(),
209+
IgnoreSideEffects))
210+
return false;
211+
if (!isIdenticalStmt(Ctx, DStmt1->getBody(), DStmt2->getBody(),
212+
IgnoreSideEffects))
213+
return false;
214+
return true;
215+
}
216+
case Stmt::WhileStmtClass: {
217+
const auto *WStmt1 = cast<WhileStmt>(Stmt1);
218+
const auto *WStmt2 = cast<WhileStmt>(Stmt2);
219+
220+
if (!isIdenticalStmt(Ctx, WStmt1->getCond(), WStmt2->getCond(),
221+
IgnoreSideEffects))
222+
return false;
223+
if (!isIdenticalStmt(Ctx, WStmt1->getBody(), WStmt2->getBody(),
224+
IgnoreSideEffects))
225+
return false;
226+
return true;
227+
}
228+
case Stmt::IfStmtClass: {
229+
const auto *IStmt1 = cast<IfStmt>(Stmt1);
230+
const auto *IStmt2 = cast<IfStmt>(Stmt2);
231+
232+
if (!isIdenticalStmt(Ctx, IStmt1->getCond(), IStmt2->getCond(),
233+
IgnoreSideEffects))
234+
return false;
235+
if (!isIdenticalStmt(Ctx, IStmt1->getThen(), IStmt2->getThen(),
236+
IgnoreSideEffects))
237+
return false;
238+
if (!isIdenticalStmt(Ctx, IStmt1->getElse(), IStmt2->getElse(),
239+
IgnoreSideEffects))
240+
return false;
241+
return true;
242+
}
243+
case Stmt::CompoundStmtClass: {
244+
const auto *CompStmt1 = cast<CompoundStmt>(Stmt1);
245+
const auto *CompStmt2 = cast<CompoundStmt>(Stmt2);
246+
247+
if (CompStmt1->size() != CompStmt2->size())
248+
return false;
249+
250+
CompoundStmt::const_body_iterator I1 = CompStmt1->body_begin();
251+
CompoundStmt::const_body_iterator I2 = CompStmt2->body_begin();
252+
while (I1 != CompStmt1->body_end() && I2 != CompStmt2->body_end()) {
253+
if (!isIdenticalStmt(Ctx, *I1, *I2, IgnoreSideEffects))
254+
return false;
255+
++I1;
256+
++I2;
257+
}
258+
259+
return true;
260+
}
261+
case Stmt::CompoundAssignOperatorClass:
262+
case Stmt::BinaryOperatorClass: {
263+
const auto *BinOp1 = cast<BinaryOperator>(Stmt1);
264+
const auto *BinOp2 = cast<BinaryOperator>(Stmt2);
265+
return BinOp1->getOpcode() == BinOp2->getOpcode();
266+
}
267+
case Stmt::CharacterLiteralClass: {
268+
const auto *CharLit1 = cast<CharacterLiteral>(Stmt1);
269+
const auto *CharLit2 = cast<CharacterLiteral>(Stmt2);
270+
return CharLit1->getValue() == CharLit2->getValue();
271+
}
272+
case Stmt::DeclRefExprClass: {
273+
const auto *DeclRef1 = cast<DeclRefExpr>(Stmt1);
274+
const auto *DeclRef2 = cast<DeclRefExpr>(Stmt2);
275+
return DeclRef1->getDecl() == DeclRef2->getDecl();
276+
}
277+
case Stmt::IntegerLiteralClass: {
278+
const auto *IntLit1 = cast<IntegerLiteral>(Stmt1);
279+
const auto *IntLit2 = cast<IntegerLiteral>(Stmt2);
280+
281+
llvm::APInt I1 = IntLit1->getValue();
282+
llvm::APInt I2 = IntLit2->getValue();
283+
if (I1.getBitWidth() != I2.getBitWidth())
284+
return false;
285+
return I1 == I2;
286+
}
287+
case Stmt::FloatingLiteralClass: {
288+
const auto *FloatLit1 = cast<FloatingLiteral>(Stmt1);
289+
const auto *FloatLit2 = cast<FloatingLiteral>(Stmt2);
290+
return FloatLit1->getValue().bitwiseIsEqual(FloatLit2->getValue());
291+
}
292+
case Stmt::StringLiteralClass: {
293+
const auto *StringLit1 = cast<StringLiteral>(Stmt1);
294+
const auto *StringLit2 = cast<StringLiteral>(Stmt2);
295+
return StringLit1->getBytes() == StringLit2->getBytes();
296+
}
297+
case Stmt::MemberExprClass: {
298+
const auto *MemberStmt1 = cast<MemberExpr>(Stmt1);
299+
const auto *MemberStmt2 = cast<MemberExpr>(Stmt2);
300+
return MemberStmt1->getMemberDecl() == MemberStmt2->getMemberDecl();
301+
}
302+
case Stmt::UnaryOperatorClass: {
303+
const auto *UnaryOp1 = cast<UnaryOperator>(Stmt1);
304+
const auto *UnaryOp2 = cast<UnaryOperator>(Stmt2);
305+
return UnaryOp1->getOpcode() == UnaryOp2->getOpcode();
306+
}
307+
}
105308
}
106309

107310
void BranchCloneCheck::check(const MatchFinder::MatchResult &Result) {
@@ -269,6 +472,23 @@ void BranchCloneCheck::check(const MatchFinder::MatchResult &Result) {
269472
return;
270473
}
271474

475+
if (const auto *IS = Result.Nodes.getNodeAs<IfStmt>("ifWithDescendantIf")) {
476+
const Stmt *Then = IS->getThen();
477+
if (const CompoundStmt *CS = dyn_cast<CompoundStmt>(Then)) {
478+
if (!CS->body_empty()) {
479+
const auto *InnerIf = dyn_cast<IfStmt>(*CS->body_begin());
480+
if (InnerIf &&
481+
isIdenticalStmt(Context, IS->getCond(), InnerIf->getCond(),
482+
/*IgnoreSideEffects=*/false)) {
483+
diag(IS->getBeginLoc(), "if with identical inner if statement");
484+
diag(InnerIf->getBeginLoc(), "inner if starts here",
485+
DiagnosticIDs::Note);
486+
}
487+
}
488+
}
489+
return;
490+
}
491+
272492
llvm_unreachable("No if statement and no switch statement.");
273493
}
274494

clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -866,19 +866,16 @@ void RedundantExpressionCheck::registerMatchers(MatchFinder *Finder) {
866866
// Binary with equivalent operands, like (X != 2 && X != 2).
867867
Finder->addMatcher(
868868
traverse(TK_AsIs,
869-
binaryOperator(
870-
anyOf(isComparisonOperator(),
871-
hasAnyOperatorName("-", "/", "%", "|", "&", "^", "&&",
872-
"||", "=")),
873-
operandsAreEquivalent(),
874-
// Filter noisy false positives.
875-
unless(isInTemplateInstantiation()),
876-
unless(binaryOperatorIsInMacro()),
877-
unless(hasType(realFloatingPointType())),
878-
unless(hasEitherOperand(hasType(realFloatingPointType()))),
879-
unless(hasLHS(AnyLiteralExpr)),
880-
unless(hasDescendant(BannedIntegerLiteral)),
881-
unless(IsInUnevaluatedContext))
869+
binaryOperator(anyOf(isComparisonOperator(),
870+
hasAnyOperatorName("-", "/", "%", "|", "&",
871+
"^", "&&", "||", "=")),
872+
operandsAreEquivalent(),
873+
// Filter noisy false positives.
874+
unless(isInTemplateInstantiation()),
875+
unless(binaryOperatorIsInMacro()),
876+
unless(hasAncestor(arraySubscriptExpr())),
877+
unless(hasDescendant(BannedIntegerLiteral)),
878+
unless(IsInUnevaluatedContext))
882879
.bind("binary")),
883880
this);
884881

@@ -1238,6 +1235,59 @@ void RedundantExpressionCheck::check(const MatchFinder::MatchResult &Result) {
12381235
if (const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binary")) {
12391236
// If the expression's constants are macros, check whether they are
12401237
// intentional.
1238+
1239+
//
1240+
// Special case for floating-point representation.
1241+
//
1242+
// If expressions on both sides of comparison operator are of type float,
1243+
// then for some comparison operators no warning shall be
1244+
// reported even if the expressions are identical from a symbolic point of
1245+
// view. Comparison between expressions, declared variables and literals
1246+
// are treated differently.
1247+
//
1248+
// != and == between float literals that have the same value should NOT
1249+
// warn. < > between float literals that have the same value SHOULD warn.
1250+
//
1251+
// != and == between the same float declaration should NOT warn.
1252+
// < > between the same float declaration SHOULD warn.
1253+
//
1254+
// != and == between eq. expressions that evaluates into float
1255+
// should NOT warn.
1256+
// < > between eq. expressions that evaluates into float
1257+
// should NOT warn.
1258+
//
1259+
const Expr *LHS = BinOp->getLHS()->IgnoreParenImpCasts();
1260+
const Expr *RHS = BinOp->getRHS()->IgnoreParenImpCasts();
1261+
BinaryOperator::Opcode Op = BinOp->getOpcode();
1262+
1263+
const auto *DeclRef1 = dyn_cast<DeclRefExpr>(LHS);
1264+
const auto *DeclRef2 = dyn_cast<DeclRefExpr>(RHS);
1265+
const auto *FloatLit1 = dyn_cast<FloatingLiteral>(LHS);
1266+
const auto *FloatLit2 = dyn_cast<FloatingLiteral>(RHS);
1267+
if ((DeclRef1) && (DeclRef2)) {
1268+
if ((DeclRef1->getType()->hasFloatingRepresentation()) &&
1269+
(DeclRef2->getType()->hasFloatingRepresentation())) {
1270+
if (DeclRef1->getDecl() == DeclRef2->getDecl()) {
1271+
if ((Op == BO_EQ) || (Op == BO_NE)) {
1272+
return;
1273+
}
1274+
}
1275+
}
1276+
} else if ((FloatLit1) && (FloatLit2)) {
1277+
if (FloatLit1->getValue().bitwiseIsEqual(FloatLit2->getValue())) {
1278+
if ((Op == BO_EQ) || (Op == BO_NE)) {
1279+
return;
1280+
}
1281+
}
1282+
} else if (LHS->getType()->hasFloatingRepresentation()) {
1283+
// If any side of comparison operator still has floating-point
1284+
// representation, then it's an expression. Don't warn.
1285+
// Here only LHS is checked since RHS will be implicit casted to float.
1286+
return;
1287+
} else {
1288+
// No special case with floating-point representation, report as usual.
1289+
}
1290+
12411291
if (areSidesBinaryConstExpressions(BinOp, Result.Context)) {
12421292
const Expr *LhsConst = nullptr, *RhsConst = nullptr;
12431293
BinaryOperatorKind MainOpcode{}, SideOpcode{};

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ Changes in existing checks
151151
<clang-tidy/checks/altera/id-dependent-backward-branch>` check by fixing
152152
crashes from invalid code.
153153

154+
- Improved :doc:`bugprone-branch-clone
155+
<clang-tidy/checks/bugprone/branch-clone>` check to improve detection of
156+
branch clones by now detecting duplicate inner and outer if statements.
157+
154158
- Improved :doc:`bugprone-casting-through-void
155159
<clang-tidy/checks/bugprone/casting-through-void>` check to suggest replacing
156160
the offending code with ``reinterpret_cast``, to more clearly express intent.
@@ -203,6 +207,11 @@ Changes in existing checks
203207
<clang-tidy/checks/misc/definitions-in-headers>` check by rewording the
204208
diagnostic note that suggests adding ``inline``.
205209

210+
- Improved :doc:`misc-redundant-expression
211+
<clang-tidy/checks/misc/redundant-expression>` check by extending the
212+
checker to detect floating point and integer literals in redundant
213+
expressions.
214+
206215
- Improved :doc:`misc-unconventional-assign-operator
207216
<clang-tidy/checks/misc/unconventional-assign-operator>` check to avoid
208217
false positive for C++23 deducing this.

clang-tools-extra/docs/clang-tidy/checks/bugprone/branch-clone.rst

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,28 @@ If this is the intended behavior, then there is no reason to use a conditional
3232
statement; otherwise the issue can be solved by fixing the branch that is
3333
handled incorrectly.
3434

35-
The check also detects repeated branches in longer ``if/else if/else`` chains
35+
The check detects repeated branches in longer ``if/else if/else`` chains
3636
where it would be even harder to notice the problem.
3737

38+
The check also detects repeated inner and outer if statements that may
39+
be a result of a copy-paste error. This check cannot currently detect
40+
identical inner and outer if statements if code is between the if
41+
conditions. An example is as follows.
42+
43+
.. code-block:: c++
44+
45+
void test_warn_inner_if_1(int x) {
46+
if (x == 1) { // warns, if with identical inner if
47+
if (x == 1) // inner if is here
48+
;
49+
if (x == 1) { // does not warn, cannot detect
50+
int y = x;
51+
if (x == 1)
52+
;
53+
}
54+
}
55+
56+
3857
In ``switch`` statements the check only reports repeated branches when they are
3958
consecutive, because it is relatively common that the ``case:`` labels have
4059
some natural ordering and rearranging them would decrease the readability of

clang-tools-extra/docs/clang-tidy/checks/misc/redundant-expression.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,22 @@ Examples:
2323
(p->x == p->x) // always true
2424
(p->x < p->x) // always false
2525
(speed - speed + 1 == 12) // speed - speed is always zero
26+
int b = a | 4 | a // identical expr on both sides
27+
((x=1) | (x=1)) // expression is identical
28+
29+
Floats are handled except in the case that NaNs are checked like so:
30+
31+
.. code-block:: c++
32+
33+
int TestFloat(float F) {
34+
if (F == F) // Identical float values used
35+
return 1;
36+
return 0;
37+
}
38+
39+
int TestFloat(float F) {
40+
// Testing NaN.
41+
if (F != F && F == F) // does not warn
42+
return 1;
43+
return 0;
44+
}

0 commit comments

Comments
 (0)