150
|
1 //===--- StrCatAppendCheck.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 "StrCatAppendCheck.h"
|
|
10 #include "clang/AST/ASTContext.h"
|
|
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
12
|
|
13 using namespace clang::ast_matchers;
|
|
14
|
|
15 namespace clang {
|
|
16 namespace tidy {
|
|
17 namespace abseil {
|
|
18
|
|
19 namespace {
|
|
20 // Skips any combination of temporary materialization, temporary binding and
|
|
21 // implicit casting.
|
|
22 AST_MATCHER_P(Stmt, IgnoringTemporaries, ast_matchers::internal::Matcher<Stmt>,
|
|
23 InnerMatcher) {
|
|
24 const Stmt *E = &Node;
|
|
25 while (true) {
|
|
26 if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(E))
|
|
27 E = MTE->getSubExpr();
|
|
28 if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
|
|
29 E = BTE->getSubExpr();
|
|
30 if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E))
|
|
31 E = ICE->getSubExpr();
|
|
32 else
|
|
33 break;
|
|
34 }
|
|
35
|
|
36 return InnerMatcher.matches(*E, Finder, Builder);
|
|
37 }
|
|
38
|
|
39 } // namespace
|
|
40
|
|
41 // TODO: str += StrCat(...)
|
|
42 // str.append(StrCat(...))
|
|
43
|
|
44 void StrCatAppendCheck::registerMatchers(MatchFinder *Finder) {
|
|
45 const auto StrCat = functionDecl(hasName("::absl::StrCat"));
|
|
46 // The arguments of absl::StrCat are implicitly converted to AlphaNum. This
|
|
47 // matches to the arguments because of that behavior.
|
|
48 const auto AlphaNum = IgnoringTemporaries(cxxConstructExpr(
|
|
49 argumentCountIs(1), hasType(cxxRecordDecl(hasName("::absl::AlphaNum"))),
|
|
50 hasArgument(0, ignoringImpCasts(declRefExpr(to(equalsBoundNode("LHS")),
|
|
51 expr().bind("Arg0"))))));
|
|
52
|
|
53 const auto HasAnotherReferenceToLhs =
|
|
54 callExpr(hasAnyArgument(expr(hasDescendant(declRefExpr(
|
|
55 to(equalsBoundNode("LHS")), unless(equalsBoundNode("Arg0")))))));
|
|
56
|
|
57 // Now look for calls to operator= with an object on the LHS and a call to
|
|
58 // StrCat on the RHS. The first argument of the StrCat call should be the same
|
|
59 // as the LHS. Ignore calls from template instantiations.
|
|
60 Finder->addMatcher(
|
173
|
61 traverse(ast_type_traits::TK_AsIs,
|
|
62 cxxOperatorCallExpr(
|
|
63 unless(isInTemplateInstantiation()),
|
|
64 hasOverloadedOperatorName("="),
|
|
65 hasArgument(0, declRefExpr(to(decl().bind("LHS")))),
|
|
66 hasArgument(
|
|
67 1, IgnoringTemporaries(
|
|
68 callExpr(callee(StrCat), hasArgument(0, AlphaNum),
|
|
69 unless(HasAnotherReferenceToLhs))
|
|
70 .bind("Call"))))
|
|
71 .bind("Op")),
|
150
|
72 this);
|
|
73 }
|
|
74
|
|
75 void StrCatAppendCheck::check(const MatchFinder::MatchResult &Result) {
|
|
76 const auto *Op = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("Op");
|
|
77 const auto *Call = Result.Nodes.getNodeAs<CallExpr>("Call");
|
|
78 assert(Op != nullptr && Call != nullptr && "Matcher does not work as expected");
|
|
79
|
|
80 // Handles the case 'x = absl::StrCat(x)', which has no effect.
|
|
81 if (Call->getNumArgs() == 1) {
|
|
82 diag(Op->getBeginLoc(), "call to 'absl::StrCat' has no effect");
|
|
83 return;
|
|
84 }
|
|
85
|
|
86 // Emit a warning and emit fixits to go from
|
|
87 // x = absl::StrCat(x, ...)
|
|
88 // to
|
|
89 // absl::StrAppend(&x, ...)
|
|
90 diag(Op->getBeginLoc(),
|
|
91 "call 'absl::StrAppend' instead of 'absl::StrCat' when appending to a "
|
|
92 "string to avoid a performance penalty")
|
|
93 << FixItHint::CreateReplacement(
|
|
94 CharSourceRange::getTokenRange(Op->getBeginLoc(),
|
|
95 Call->getCallee()->getEndLoc()),
|
|
96 "absl::StrAppend")
|
|
97 << FixItHint::CreateInsertion(Call->getArg(0)->getBeginLoc(), "&");
|
|
98 }
|
|
99
|
|
100 } // namespace abseil
|
|
101 } // namespace tidy
|
|
102 } // namespace clang
|