Skip to content

Commit 6bcbe9f

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 2d7ec7f commit 6bcbe9f

File tree

11 files changed

+569
-663
lines changed

11 files changed

+569
-663
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,211 @@ void BranchCloneCheck::registerMatchers(MatchFinder *Finder) {
102102
this);
103103
Finder->addMatcher(switchStmt().bind("switch"), this);
104104
Finder->addMatcher(conditionalOperator().bind("condOp"), this);
105+
Finder->addMatcher(
106+
ifStmt((hasThen(hasDescendant(ifStmt())))).bind("ifWithDescendantIf"),
107+
this);
108+
}
109+
110+
/// Determines whether two statement trees are identical regarding
111+
/// operators and symbols.
112+
///
113+
/// Exceptions: expressions containing macros or functions with possible side
114+
/// effects are never considered identical.
115+
/// Limitations: (t + u) and (u + t) are not considered identical.
116+
/// t*(u + t) and t*u + t*t are not considered identical.
117+
///
118+
static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1,
119+
const Stmt *Stmt2, bool IgnoreSideEffects) {
120+
121+
if (!Stmt1 || !Stmt2)
122+
return !Stmt1 && !Stmt2;
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+
if (!llvm::all_of(llvm::zip(CompStmt1->body(), CompStmt2->body()),
251+
[&Ctx, IgnoreSideEffects](
252+
std::tuple<const Stmt *, const Stmt *> stmtPair) {
253+
const Stmt *stmt0 = std::get<0>(stmtPair);
254+
const Stmt *stmt1 = std::get<1>(stmtPair);
255+
return isIdenticalStmt(Ctx, stmt0, stmt1,
256+
IgnoreSideEffects);
257+
})) {
258+
return false;
259+
}
260+
261+
return true;
262+
}
263+
case Stmt::CompoundAssignOperatorClass:
264+
case Stmt::BinaryOperatorClass: {
265+
const auto *BinOp1 = cast<BinaryOperator>(Stmt1);
266+
const auto *BinOp2 = cast<BinaryOperator>(Stmt2);
267+
return BinOp1->getOpcode() == BinOp2->getOpcode();
268+
}
269+
case Stmt::CharacterLiteralClass: {
270+
const auto *CharLit1 = cast<CharacterLiteral>(Stmt1);
271+
const auto *CharLit2 = cast<CharacterLiteral>(Stmt2);
272+
return CharLit1->getValue() == CharLit2->getValue();
273+
}
274+
case Stmt::DeclRefExprClass: {
275+
const auto *DeclRef1 = cast<DeclRefExpr>(Stmt1);
276+
const auto *DeclRef2 = cast<DeclRefExpr>(Stmt2);
277+
return DeclRef1->getDecl() == DeclRef2->getDecl();
278+
}
279+
case Stmt::IntegerLiteralClass: {
280+
const auto *IntLit1 = cast<IntegerLiteral>(Stmt1);
281+
const auto *IntLit2 = cast<IntegerLiteral>(Stmt2);
282+
283+
llvm::APInt I1 = IntLit1->getValue();
284+
llvm::APInt I2 = IntLit2->getValue();
285+
if (I1.getBitWidth() != I2.getBitWidth())
286+
return false;
287+
return I1 == I2;
288+
}
289+
case Stmt::FloatingLiteralClass: {
290+
const auto *FloatLit1 = cast<FloatingLiteral>(Stmt1);
291+
const auto *FloatLit2 = cast<FloatingLiteral>(Stmt2);
292+
return FloatLit1->getValue().bitwiseIsEqual(FloatLit2->getValue());
293+
}
294+
case Stmt::StringLiteralClass: {
295+
const auto *StringLit1 = cast<StringLiteral>(Stmt1);
296+
const auto *StringLit2 = cast<StringLiteral>(Stmt2);
297+
return StringLit1->getBytes() == StringLit2->getBytes();
298+
}
299+
case Stmt::MemberExprClass: {
300+
const auto *MemberStmt1 = cast<MemberExpr>(Stmt1);
301+
const auto *MemberStmt2 = cast<MemberExpr>(Stmt2);
302+
return MemberStmt1->getMemberDecl() == MemberStmt2->getMemberDecl();
303+
}
304+
case Stmt::UnaryOperatorClass: {
305+
const auto *UnaryOp1 = cast<UnaryOperator>(Stmt1);
306+
const auto *UnaryOp2 = cast<UnaryOperator>(Stmt2);
307+
return UnaryOp1->getOpcode() == UnaryOp2->getOpcode();
308+
}
309+
}
105310
}
106311

107312
void BranchCloneCheck::check(const MatchFinder::MatchResult &Result) {
@@ -269,6 +474,21 @@ void BranchCloneCheck::check(const MatchFinder::MatchResult &Result) {
269474
return;
270475
}
271476

477+
if (const auto *IS = Result.Nodes.getNodeAs<IfStmt>("ifWithDescendantIf")) {
478+
const Stmt *Then = IS->getThen();
479+
auto CS = dyn_cast<CompoundStmt>(Then);
480+
if (CS && (!CS->body_empty())) {
481+
const auto *InnerIf = dyn_cast<IfStmt>(*CS->body_begin());
482+
if (InnerIf && isIdenticalStmt(Context, IS->getCond(), InnerIf->getCond(),
483+
/*IgnoreSideEffects=*/false)) {
484+
diag(IS->getBeginLoc(), "if with identical inner if statement");
485+
diag(InnerIf->getBeginLoc(), "inner if starts here",
486+
DiagnosticIDs::Note);
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: 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

0 commit comments

Comments
 (0)