Skip to content

Commit 48389a3

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 0d38f64 commit 48389a3

File tree

11 files changed

+566
-663
lines changed

11 files changed

+566
-663
lines changed

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

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

107309
void BranchCloneCheck::check(const MatchFinder::MatchResult &Result) {
@@ -269,6 +471,21 @@ void BranchCloneCheck::check(const MatchFinder::MatchResult &Result) {
269471
return;
270472
}
271473

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

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

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -855,9 +855,6 @@ static bool areExprsMacroAndNonMacro(const Expr *&LhsExpr,
855855
} // namespace
856856

857857
void RedundantExpressionCheck::registerMatchers(MatchFinder *Finder) {
858-
const auto AnyLiteralExpr = ignoringParenImpCasts(
859-
anyOf(cxxBoolLiteral(), characterLiteral(), integerLiteral()));
860-
861858
const auto BannedIntegerLiteral =
862859
integerLiteral(expandedByMacro(KnownBannedMacroNames));
863860
const auto IsInUnevaluatedContext = expr(anyOf(
@@ -866,19 +863,16 @@ void RedundantExpressionCheck::registerMatchers(MatchFinder *Finder) {
866863
// Binary with equivalent operands, like (X != 2 && X != 2).
867864
Finder->addMatcher(
868865
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))
866+
binaryOperator(anyOf(isComparisonOperator(),
867+
hasAnyOperatorName("-", "/", "%", "|", "&",
868+
"^", "&&", "||", "=")),
869+
operandsAreEquivalent(),
870+
// Filter noisy false positives.
871+
unless(isInTemplateInstantiation()),
872+
unless(binaryOperatorIsInMacro()),
873+
unless(hasAncestor(arraySubscriptExpr())),
874+
unless(hasDescendant(BannedIntegerLiteral)),
875+
unless(IsInUnevaluatedContext))
882876
.bind("binary")),
883877
this);
884878

@@ -1238,6 +1232,50 @@ void RedundantExpressionCheck::check(const MatchFinder::MatchResult &Result) {
12381232
if (const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binary")) {
12391233
// If the expression's constants are macros, check whether they are
12401234
// intentional.
1235+
1236+
//
1237+
// Special case for floating-point representation.
1238+
//
1239+
// If expressions on both sides of comparison operator are of type float,
1240+
// then for some comparison operators no warning shall be
1241+
// reported even if the expressions are identical from a symbolic point of
1242+
// view. Comparison between expressions, declared variables and literals
1243+
// are treated differently.
1244+
//
1245+
// != and == between float literals that have the same value should NOT
1246+
// warn. < > between float literals that have the same value SHOULD warn.
1247+
//
1248+
// != and == between the same float declaration should NOT warn.
1249+
// < > between the same float declaration SHOULD warn.
1250+
//
1251+
// != and == between eq. expressions that evaluates into float
1252+
// should NOT warn.
1253+
// < > between eq. expressions that evaluates into float
1254+
// should NOT warn.
1255+
//
1256+
const Expr *LHS = BinOp->getLHS()->IgnoreParenImpCasts();
1257+
const Expr *RHS = BinOp->getRHS()->IgnoreParenImpCasts();
1258+
const BinaryOperator::Opcode Op = BinOp->getOpcode();
1259+
const bool OpEqualEQorNE = ((Op == BO_EQ) || (Op == BO_NE));
1260+
1261+
const auto *DeclRef1 = dyn_cast<DeclRefExpr>(LHS);
1262+
const auto *DeclRef2 = dyn_cast<DeclRefExpr>(RHS);
1263+
const auto *FloatLit1 = dyn_cast<FloatingLiteral>(LHS);
1264+
const auto *FloatLit2 = dyn_cast<FloatingLiteral>(RHS);
1265+
1266+
if (DeclRef1 && DeclRef2 &&
1267+
DeclRef1->getType()->hasFloatingRepresentation() &&
1268+
DeclRef2->getType()->hasFloatingRepresentation() &&
1269+
(DeclRef1->getDecl() == DeclRef2->getDecl()) && OpEqualEQorNE) {
1270+
return;
1271+
}
1272+
1273+
if (FloatLit1 && FloatLit2 &&
1274+
FloatLit1->getValue().bitwiseIsEqual(FloatLit2->getValue()) &&
1275+
OpEqualEQorNE) {
1276+
return;
1277+
}
1278+
12411279
if (areSidesBinaryConstExpressions(BinOp, Result.Context)) {
12421280
const Expr *LhsConst = nullptr, *RhsConst = nullptr;
12431281
BinaryOperatorKind MainOpcode{}, SideOpcode{};

clang-tools-extra/docs/ReleaseNotes.rst

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

157+
- Improved :doc:`bugprone-branch-clone
158+
<clang-tidy/checks/bugprone/branch-clone>` check to improve detection of
159+
branch clones by now detecting duplicate inner and outer if statements.
160+
157161
- Improved :doc:`bugprone-casting-through-void
158162
<clang-tidy/checks/bugprone/casting-through-void>` check to suggest replacing
159163
the offending code with ``reinterpret_cast``, to more clearly express intent.
@@ -220,6 +224,11 @@ Changes in existing checks
220224
<clang-tidy/checks/misc/definitions-in-headers>` check by rewording the
221225
diagnostic note that suggests adding ``inline``.
222226

227+
- Improved :doc:`misc-redundant-expression
228+
<clang-tidy/checks/misc/redundant-expression>` check by extending the
229+
checker to detect floating point and integer literals in redundant
230+
expressions.
231+
223232
- Improved :doc:`misc-unconventional-assign-operator
224233
<clang-tidy/checks/misc/unconventional-assign-operator>` check to avoid
225234
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)