|  | //===- RedundantStringCStrCheck.cpp - Check for redundant c_str calls -----===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  | // | 
|  | //  This file implements a check for redundant calls of c_str() on strings. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "RedundantStringCStrCheck.h" | 
|  | #include "../utils/FixItHintUtils.h" | 
|  | #include "../utils/Matchers.h" | 
|  | #include "../utils/OptionsUtils.h" | 
|  | #include "clang/Lex/Lexer.h" | 
|  | #include "clang/Tooling/FixIt.h" | 
|  |  | 
|  | using namespace clang::ast_matchers; | 
|  |  | 
|  | namespace clang::tidy::readability { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | AST_MATCHER(MaterializeTemporaryExpr, isBoundToLValue) { | 
|  | return Node.isBoundToLvalueReference(); | 
|  | } | 
|  |  | 
|  | } // end namespace | 
|  |  | 
|  | RedundantStringCStrCheck::RedundantStringCStrCheck(StringRef Name, | 
|  | ClangTidyContext *Context) | 
|  | : ClangTidyCheck(Name, Context), | 
|  | StringParameterFunctions(utils::options::parseStringList( | 
|  | Options.get("StringParameterFunctions", ""))) { | 
|  | if (getLangOpts().CPlusPlus20) | 
|  | StringParameterFunctions.emplace_back("::std::format"); | 
|  | if (getLangOpts().CPlusPlus23) | 
|  | StringParameterFunctions.emplace_back("::std::print"); | 
|  | } | 
|  |  | 
|  | void RedundantStringCStrCheck::registerMatchers( | 
|  | ast_matchers::MatchFinder *Finder) { | 
|  | // Match expressions of type 'string' or 'string*'. | 
|  | const auto StringDecl = type(hasUnqualifiedDesugaredType(recordType( | 
|  | hasDeclaration(cxxRecordDecl(hasName("::std::basic_string")))))); | 
|  | const auto StringExpr = | 
|  | expr(anyOf(hasType(StringDecl), hasType(qualType(pointsTo(StringDecl))))); | 
|  |  | 
|  | // Match string constructor. | 
|  | const auto StringConstructorExpr = expr(anyOf( | 
|  | cxxConstructExpr(argumentCountIs(1), | 
|  | hasDeclaration(cxxMethodDecl(hasName("basic_string")))), | 
|  | cxxConstructExpr( | 
|  | argumentCountIs(2), | 
|  | hasDeclaration(cxxMethodDecl(hasName("basic_string"))), | 
|  | // If present, the second argument is the alloc object which must not | 
|  | // be present explicitly. | 
|  | hasArgument(1, cxxDefaultArgExpr())))); | 
|  |  | 
|  | // Match string constructor. | 
|  | const auto StringViewConstructorExpr = cxxConstructExpr( | 
|  | argumentCountIs(1), | 
|  | hasDeclaration(cxxMethodDecl(hasName("basic_string_view")))); | 
|  |  | 
|  | // Match a call to the string 'c_str()' method. | 
|  | const auto StringCStrCallExpr = | 
|  | cxxMemberCallExpr(on(StringExpr.bind("arg")), | 
|  | callee(memberExpr().bind("member")), | 
|  | callee(cxxMethodDecl(hasAnyName("c_str", "data")))) | 
|  | .bind("call"); | 
|  | const auto HasRValueTempParent = | 
|  | hasParent(materializeTemporaryExpr(unless(isBoundToLValue()))); | 
|  | // Detect redundant 'c_str()' calls through a string constructor. | 
|  | // If CxxConstructExpr is the part of some CallExpr we need to | 
|  | // check that matched ParamDecl of the ancestor CallExpr is not rvalue. | 
|  | Finder->addMatcher( | 
|  | traverse( | 
|  | TK_AsIs, | 
|  | cxxConstructExpr( | 
|  | anyOf(StringConstructorExpr, StringViewConstructorExpr), | 
|  | hasArgument(0, StringCStrCallExpr), | 
|  | unless(anyOf(HasRValueTempParent, hasParent(cxxBindTemporaryExpr( | 
|  | HasRValueTempParent)))))), | 
|  | this); | 
|  |  | 
|  | // Detect: 's == str.c_str()'  ->  's == str' | 
|  | Finder->addMatcher( | 
|  | cxxOperatorCallExpr( | 
|  | hasAnyOverloadedOperatorName("<", ">", ">=", "<=", "!=", "==", "+"), | 
|  | anyOf(allOf(hasArgument(0, StringExpr), | 
|  | hasArgument(1, StringCStrCallExpr)), | 
|  | allOf(hasArgument(0, StringCStrCallExpr), | 
|  | hasArgument(1, StringExpr)))), | 
|  | this); | 
|  |  | 
|  | // Detect: 'dst += str.c_str()'  ->  'dst += str' | 
|  | // Detect: 's = str.c_str()'  ->  's = str' | 
|  | Finder->addMatcher( | 
|  | cxxOperatorCallExpr(hasAnyOverloadedOperatorName("=", "+="), | 
|  | hasArgument(0, StringExpr), | 
|  | hasArgument(1, StringCStrCallExpr)), | 
|  | this); | 
|  |  | 
|  | // Detect: 'dst.append(str.c_str())'  ->  'dst.append(str)' | 
|  | Finder->addMatcher( | 
|  | cxxMemberCallExpr(on(StringExpr), callee(decl(cxxMethodDecl(hasAnyName( | 
|  | "append", "assign", "compare")))), | 
|  | argumentCountIs(1), hasArgument(0, StringCStrCallExpr)), | 
|  | this); | 
|  |  | 
|  | // Detect: 'dst.compare(p, n, str.c_str())'  ->  'dst.compare(p, n, str)' | 
|  | Finder->addMatcher( | 
|  | cxxMemberCallExpr(on(StringExpr), | 
|  | callee(decl(cxxMethodDecl(hasName("compare")))), | 
|  | argumentCountIs(3), hasArgument(2, StringCStrCallExpr)), | 
|  | this); | 
|  |  | 
|  | // Detect: 'dst.find(str.c_str())'  ->  'dst.find(str)' | 
|  | Finder->addMatcher( | 
|  | cxxMemberCallExpr(on(StringExpr), | 
|  | callee(decl(cxxMethodDecl(hasAnyName( | 
|  | "find", "find_first_not_of", "find_first_of", | 
|  | "find_last_not_of", "find_last_of", "rfind")))), | 
|  | anyOf(argumentCountIs(1), argumentCountIs(2)), | 
|  | hasArgument(0, StringCStrCallExpr)), | 
|  | this); | 
|  |  | 
|  | // Detect: 'dst.insert(pos, str.c_str())'  ->  'dst.insert(pos, str)' | 
|  | Finder->addMatcher( | 
|  | cxxMemberCallExpr(on(StringExpr), | 
|  | callee(decl(cxxMethodDecl(hasName("insert")))), | 
|  | argumentCountIs(2), hasArgument(1, StringCStrCallExpr)), | 
|  | this); | 
|  |  | 
|  | // Detect redundant 'c_str()' calls through a StringRef constructor. | 
|  | Finder->addMatcher( | 
|  | traverse( | 
|  | TK_AsIs, | 
|  | cxxConstructExpr( | 
|  | // Implicit constructors of these classes are overloaded | 
|  | // wrt. string types and they internally make a StringRef | 
|  | // referring to the argument.  Passing a string directly to | 
|  | // them is preferred to passing a char pointer. | 
|  | hasDeclaration(cxxMethodDecl(hasAnyName( | 
|  | "::llvm::StringRef::StringRef", "::llvm::Twine::Twine"))), | 
|  | argumentCountIs(1), | 
|  | // The only argument must have the form x.c_str() or p->c_str() | 
|  | // where the method is string::c_str().  StringRef also has | 
|  | // a constructor from string which is more efficient (avoids | 
|  | // strlen), so we can construct StringRef from the string | 
|  | // directly. | 
|  | hasArgument(0, StringCStrCallExpr))), | 
|  | this); | 
|  |  | 
|  | if (!StringParameterFunctions.empty()) { | 
|  | // Detect redundant 'c_str()' calls in parameters passed to std::format in | 
|  | // C++20 onwards and std::print in C++23 onwards. | 
|  | Finder->addMatcher( | 
|  | traverse(TK_AsIs, | 
|  | callExpr(callee(functionDecl(matchers::matchesAnyListedName( | 
|  | StringParameterFunctions))), | 
|  | forEachArgumentWithParam(StringCStrCallExpr, | 
|  | parmVarDecl()))), | 
|  | this); | 
|  | } | 
|  | } | 
|  |  | 
|  | void RedundantStringCStrCheck::check(const MatchFinder::MatchResult &Result) { | 
|  | const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call"); | 
|  | const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg"); | 
|  | const auto *Member = Result.Nodes.getNodeAs<MemberExpr>("member"); | 
|  | bool Arrow = Member->isArrow(); | 
|  | // Replace the "call" node with the "arg" node, prefixed with '*' | 
|  | // if the call was using '->' rather than '.'. | 
|  | std::string ArgText = | 
|  | Arrow ? utils::fixit::formatDereference(*Arg, *Result.Context) | 
|  | : tooling::fixit::getText(*Arg, *Result.Context).str(); | 
|  | if (ArgText.empty()) | 
|  | return; | 
|  |  | 
|  | diag(Call->getBeginLoc(), "redundant call to %0") | 
|  | << Member->getMemberDecl() | 
|  | << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText); | 
|  | } | 
|  |  | 
|  | } // namespace clang::tidy::readability |