diff options
Diffstat (limited to 'clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.cpp')
-rw-r--r-- | clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.cpp | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.cpp b/clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.cpp new file mode 100644 index 00000000000..25b9d17e833 --- /dev/null +++ b/clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.cpp @@ -0,0 +1,102 @@ +//===--- StrCatAppendCheck.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StrCatAppendCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace abseil { + +namespace { +// Skips any combination of temporary materialization, temporary binding and +// implicit casting. +AST_MATCHER_P(Stmt, IgnoringTemporaries, ast_matchers::internal::Matcher<Stmt>, + InnerMatcher) { + const Stmt *E = &Node; + while (true) { + if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(E)) + E = MTE->getTemporary(); + if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E)) + E = BTE->getSubExpr(); + if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E)) + E = ICE->getSubExpr(); + else + break; + } + + return InnerMatcher.matches(*E, Finder, Builder); +} + +} // namespace + +// TODO: str += StrCat(...) +// str.append(StrCat(...)) + +void StrCatAppendCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + const auto StrCat = functionDecl(hasName("::absl::StrCat")); + // The arguments of absl::StrCat are implicitly converted to AlphaNum. This + // matches to the arguments because of that behavior. + const auto AlphaNum = IgnoringTemporaries(cxxConstructExpr( + argumentCountIs(1), hasType(cxxRecordDecl(hasName("::absl::AlphaNum"))), + hasArgument(0, ignoringImpCasts(declRefExpr(to(equalsBoundNode("LHS")), + expr().bind("Arg0")))))); + + const auto HasAnotherReferenceToLhs = + callExpr(hasAnyArgument(expr(hasDescendant(declRefExpr( + to(equalsBoundNode("LHS")), unless(equalsBoundNode("Arg0"))))))); + + // Now look for calls to operator= with an object on the LHS and a call to + // StrCat on the RHS. The first argument of the StrCat call should be the same + // as the LHS. Ignore calls from template instantiations. + Finder->addMatcher( + cxxOperatorCallExpr( + unless(isInTemplateInstantiation()), hasOverloadedOperatorName("="), + hasArgument(0, declRefExpr(to(decl().bind("LHS")))), + hasArgument(1, IgnoringTemporaries( + callExpr(callee(StrCat), hasArgument(0, AlphaNum), + unless(HasAnotherReferenceToLhs)) + .bind("Call")))) + .bind("Op"), + this); +} + +void StrCatAppendCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Op = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("Op"); + const auto *Call = Result.Nodes.getNodeAs<CallExpr>("Call"); + assert(Op != nullptr && Call != nullptr && "Matcher does not work as expected"); + + // Handles the case 'x = absl::StrCat(x)', which has no effect. + if (Call->getNumArgs() == 1) { + diag(Op->getBeginLoc(), "call to 'absl::StrCat' has no effect"); + return; + } + + // Emit a warning and emit fixits to go from + // x = absl::StrCat(x, ...) + // to + // absl::StrAppend(&x, ...) + diag(Op->getBeginLoc(), + "call 'absl::StrAppend' instead of 'absl::StrCat' when appending to a " + "string to avoid a performance penalty") + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(Op->getBeginLoc(), + Call->getCallee()->getEndLoc()), + "StrAppend") + << FixItHint::CreateInsertion(Call->getArg(0)->getBeginLoc(), "&"); +} + +} // namespace abseil +} // namespace tidy +} // namespace clang |