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