1 | //===--- ImplicitConversionInLoopCheck.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 "ImplicitConversionInLoopCheck.h" |
10 | |
11 | #include "clang/AST/ASTContext.h" |
12 | #include "clang/AST/Decl.h" |
13 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
14 | #include "clang/ASTMatchers/ASTMatchers.h" |
15 | #include "clang/Lex/Lexer.h" |
16 | |
17 | using namespace clang::ast_matchers; |
18 | |
19 | namespace clang::tidy::performance { |
20 | |
21 | // Checks if the stmt is a ImplicitCastExpr with a CastKind that is not a NoOp. |
22 | // The subtlety is that in some cases (user defined conversions), we can |
23 | // get to ImplicitCastExpr inside each other, with the outer one a NoOp. In this |
24 | // case we skip the first cast expr. |
25 | static bool isNonTrivialImplicitCast(const Stmt *ST) { |
26 | if (const auto *ICE = dyn_cast<ImplicitCastExpr>(Val: ST)) { |
27 | return (ICE->getCastKind() != CK_NoOp) || |
28 | isNonTrivialImplicitCast(ICE->getSubExpr()); |
29 | } |
30 | return false; |
31 | } |
32 | |
33 | void ImplicitConversionInLoopCheck::registerMatchers(MatchFinder *Finder) { |
34 | // We look for const ref loop variables that (optionally inside an |
35 | // ExprWithCleanup) materialize a temporary, and contain a implicit |
36 | // conversion. The check on the implicit conversion is done in check() because |
37 | // we can't access implicit conversion subnode via matchers: has() skips casts |
38 | // and materialize! We also bind on the call to operator* to get the proper |
39 | // type in the diagnostic message. We use both cxxOperatorCallExpr for user |
40 | // defined operator and unaryOperator when the iterator is a pointer, like |
41 | // for arrays or std::array. |
42 | // |
43 | // Note that when the implicit conversion is done through a user defined |
44 | // conversion operator, the node is a CXXMemberCallExpr, not a |
45 | // CXXOperatorCallExpr, so it should not get caught by the |
46 | // cxxOperatorCallExpr() matcher. |
47 | Finder->addMatcher( |
48 | NodeMatch: traverse( |
49 | TK: TK_AsIs, |
50 | InnerMatcher: cxxForRangeStmt(hasLoopVariable( |
51 | InnerMatcher: varDecl( |
52 | hasType(InnerMatcher: qualType(references(InnerMatcher: qualType(isConstQualified())))), |
53 | hasInitializer( |
54 | InnerMatcher: expr(anyOf( |
55 | hasDescendant( |
56 | cxxOperatorCallExpr().bind(ID: "operator-call" )), |
57 | hasDescendant(unaryOperator(hasOperatorName(Name: "*" )) |
58 | .bind(ID: "operator-call" )))) |
59 | .bind(ID: "init" ))) |
60 | .bind(ID: "faulty-var" )))), |
61 | Action: this); |
62 | } |
63 | |
64 | void ImplicitConversionInLoopCheck::check( |
65 | const MatchFinder::MatchResult &Result) { |
66 | const auto *VD = Result.Nodes.getNodeAs<VarDecl>(ID: "faulty-var" ); |
67 | const auto *Init = Result.Nodes.getNodeAs<Expr>(ID: "init" ); |
68 | const auto *OperatorCall = |
69 | Result.Nodes.getNodeAs<Expr>(ID: "operator-call" ); |
70 | |
71 | if (const auto *Cleanup = dyn_cast<ExprWithCleanups>(Val: Init)) |
72 | Init = Cleanup->getSubExpr(); |
73 | |
74 | const auto *Materialized = dyn_cast<MaterializeTemporaryExpr>(Val: Init); |
75 | if (!Materialized) |
76 | return; |
77 | |
78 | // We ignore NoOp casts. Those are generated if the * operator on the |
79 | // iterator returns a value instead of a reference, and the loop variable |
80 | // is a reference. This situation is fine (it probably produces the same |
81 | // code at the end). |
82 | if (isNonTrivialImplicitCast(Materialized->getSubExpr())) |
83 | reportAndFix(Context: Result.Context, VD, OperatorCall); |
84 | } |
85 | |
86 | void ImplicitConversionInLoopCheck::reportAndFix(const ASTContext *Context, |
87 | const VarDecl *VD, |
88 | const Expr *OperatorCall) { |
89 | // We only match on const ref, so we should print a const ref version of the |
90 | // type. |
91 | QualType ConstType = OperatorCall->getType().withConst(); |
92 | QualType ConstRefType = Context->getLValueReferenceType(T: ConstType); |
93 | const char Message[] = |
94 | "the type of the loop variable %0 is different from the one returned " |
95 | "by the iterator and generates an implicit conversion; you can either " |
96 | "change the type to the matching one (%1 but 'const auto&' is always a " |
97 | "valid option) or remove the reference to make it explicit that you are " |
98 | "creating a new value" ; |
99 | diag(VD->getBeginLoc(), Message) << VD << ConstRefType; |
100 | } |
101 | |
102 | } // namespace clang::tidy::performance |
103 | |