/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "StringConcatenationUseCmstrcatCheck.h" #include #include #include namespace clang { namespace tidy { namespace cmake { using namespace ast_matchers; StringConcatenationUseCmstrcatCheck::StringConcatenationUseCmstrcatCheck( StringRef Name, ClangTidyContext* Context) : ClangTidyCheck(Name, Context) { } void StringConcatenationUseCmstrcatCheck::registerMatchers(MatchFinder* Finder) { auto IsString = expr(hasType(qualType(hasUnqualifiedDesugaredType( recordType(hasDeclaration(classTemplateSpecializationDecl( hasName("::std::basic_string"), hasTemplateArgument( 0, templateArgument(refersToType(asString("char"))))))))))); auto IsChar = expr(hasType(asString("char"))); auto IsCharPtr = expr(hasType(pointerType(pointee(asString("const char"))))); auto IsStringConcat = cxxOperatorCallExpr(hasOperatorName("+"), anyOf(allOf(hasLHS(IsString), hasRHS(IsString)), allOf(hasLHS(IsString), hasRHS(IsChar)), allOf(hasLHS(IsString), hasRHS(IsCharPtr)), allOf(hasLHS(IsChar), hasRHS(IsString)), allOf(hasLHS(IsCharPtr), hasRHS(IsString)))); auto IsStringAppend = cxxOperatorCallExpr( hasOperatorName("+="), hasLHS(IsString), anyOf(hasRHS(IsString), hasRHS(IsChar), hasRHS(IsCharPtr))); auto IsStringConcatWithLHS = cxxOperatorCallExpr( IsStringConcat, optionally(hasLHS(materializeTemporaryExpr( has(cxxBindTemporaryExpr(has(IsStringConcat.bind("lhs")))))))) .bind("concat"); auto IsStringAppendWithRHS = cxxOperatorCallExpr( IsStringAppend, optionally(hasRHS(materializeTemporaryExpr(has(implicitCastExpr( has(cxxBindTemporaryExpr(has(IsStringConcat.bind("rhs")))))))))) .bind("append"); Finder->addMatcher(IsStringConcatWithLHS, this); Finder->addMatcher(IsStringAppendWithRHS, this); } void StringConcatenationUseCmstrcatCheck::check( const MatchFinder::MatchResult& Result) { const CXXOperatorCallExpr* AppendNode = Result.Nodes.getNodeAs("append"); const CXXOperatorCallExpr* ConcatNode = Result.Nodes.getNodeAs("concat"); if (AppendNode != nullptr) { if (AppendNode->getBeginLoc().isValid()) { assert(InProgressExprChains.find(AppendNode) == InProgressExprChains.end()); ExprChain TmpExprChain = std::make_pair(OperatorType::PlusEquals, std::vector{ AppendNode }); const CXXOperatorCallExpr* RHSNode = Result.Nodes.getNodeAs("rhs"); if (RHSNode != nullptr) { if (RHSNode->getBeginLoc().isValid()) { InProgressExprChains[RHSNode] = std::move(TmpExprChain); } } else { issueCorrection(TmpExprChain, Result); } } } if (ConcatNode != nullptr) { if (ConcatNode->getBeginLoc().isValid()) { ExprChain TmpExprChain; if (!(InProgressExprChains.find(ConcatNode) == InProgressExprChains.end())) { TmpExprChain = std::move(InProgressExprChains[ConcatNode]); InProgressExprChains.erase(ConcatNode); if (TmpExprChain.first == OperatorType::PlusEquals) { TmpExprChain.second.insert(TmpExprChain.second.begin() + 1, ConcatNode); } else { TmpExprChain.second.insert(TmpExprChain.second.begin(), ConcatNode); } } else { TmpExprChain = std::make_pair( OperatorType::Plus, std::vector{ ConcatNode }); } const CXXOperatorCallExpr* LHSNode = Result.Nodes.getNodeAs("lhs"); if (LHSNode != nullptr) { if (LHSNode->getBeginLoc().isValid()) { InProgressExprChains[LHSNode] = std::move(TmpExprChain); } } else { issueCorrection(TmpExprChain, Result); } } } } void StringConcatenationUseCmstrcatCheck::issueCorrection( const ExprChain& Chain, const MatchFinder::MatchResult& Result) { std::vector FixIts; const CXXOperatorCallExpr* ExprNode; std::vector::const_iterator It = Chain.second.begin(); if (Chain.first == OperatorType::PlusEquals) { ExprNode = *It; StringRef LHS = Lexer::getSourceText( CharSourceRange::getTokenRange(ExprNode->getArg(0)->getSourceRange()), Result.Context->getSourceManager(), Result.Context->getLangOpts()); FixIts.push_back(FixItHint::CreateReplacement( ExprNode->getExprLoc(), "= cmStrCat(" + LHS.str() + ",")); It++; } else { ExprNode = *It; FixIts.push_back( FixItHint::CreateInsertion(ExprNode->getBeginLoc(), "cmStrCat(")); } while (It != std::end(Chain.second)) { ExprNode = *It; FixIts.push_back( FixItHint::CreateReplacement(ExprNode->getOperatorLoc(), ",")); It++; } It--; ExprNode = *It; StringRef LastToken = Lexer::getSourceText( CharSourceRange::getTokenRange( ExprNode->getArg(1)->getSourceRange().getEnd()), Result.Context->getSourceManager(), Result.Context->getLangOpts()); FixIts.push_back(FixItHint::CreateInsertion( ExprNode->getEndLoc().getLocWithOffset(LastToken.str().size()), ")")); It = Chain.second.begin(); ExprNode = *It; if (Chain.first == OperatorType::PlusEquals) { this->diag(ExprNode->getOperatorLoc(), "use cmStrCat() instead of string append") << FixIts; } else { this->diag(ExprNode->getBeginLoc(), "use cmStrCat() instead of string concatenation") << FixIts; } } } } }