================ @@ -0,0 +1,153 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "UseStringViewCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTDiagnostic.h" +#include "clang/AST/Stmt.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/Diagnostic.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +static constexpr StringRef StringViewClassKey = "string"; +static constexpr StringRef WStringViewClassKey = "wstring"; +static constexpr StringRef U8StringViewClassKey = "u8string"; +static constexpr StringRef U16StringViewClassKey = "u16string"; +static constexpr StringRef U32StringViewClassKey = "u32string"; + +static auto getStringTypeMatcher(StringRef CharType) { + return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasName(CharType)))); +} + +static void fixReturns(const FunctionDecl *FuncDecl, DiagnosticBuilder &Diag, + ASTContext &Context) { + auto Matches = match( + findAll(returnStmt(hasReturnValue(cxxConstructExpr(anyOf( + argumentCountIs(0), cxxTemporaryObjectExpr())))) + .bind("return")), + *FuncDecl->getBody(), Context); + for (const auto &Match : Matches) { + if (const auto *RetValue = + Match.getNodeAs<ReturnStmt>("return")->getRetValue()) + if (const auto ReturnSourceRange = RetValue->getSourceRange(); + ReturnSourceRange.isValid()) + Diag << FixItHint::CreateReplacement(ReturnSourceRange, "{}"); + } +} + +UseStringViewCheck::UseStringViewCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoredFunctions(utils::options::parseStringList( + Options.get("IgnoredFunctions", "toString$;ToString$;to_string$"))) { + parseReplacementStringViewClass( + Options.get("ReplacementStringViewClass", "")); +} + +void UseStringViewCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoredFunctions", + utils::options::serializeStringList(IgnoredFunctions)); + Options.store(Opts, "ReplacementStringViewClass", + (Twine("") + StringViewClassKey + "=" + StringViewClass + ";" + + WStringViewClassKey + "=" + WStringViewClass + ";" + + U8StringViewClassKey + "=" + U8StringViewClass + ";" + + U16StringViewClassKey + "=" + U16StringViewClass + ";" + + U32StringViewClassKey + "=" + U32StringViewClass) + .str()); +} + +void UseStringViewCheck::registerMatchers(MatchFinder *Finder) { + const auto IsStdString = getStringTypeMatcher("::std::basic_string"); + // TODO: also consider *StringViewClass types + const auto IsStdStringView = getStringTypeMatcher("::std::basic_string_view"); + const auto IgnoredFunctionsMatcher = + matchers::matchesAnyListedRegexName(IgnoredFunctions); + const auto TernaryOperator = conditionalOperator( + hasTrueExpression(ignoringParenImpCasts(stringLiteral())), + hasFalseExpression(ignoringParenImpCasts(stringLiteral()))); + const auto VirtualOrOperator = + cxxMethodDecl(anyOf(cxxConversionDecl(), isVirtual())); + Finder->addMatcher( + functionDecl( + isDefinition(), + unless(anyOf(VirtualOrOperator, IgnoredFunctionsMatcher, + ast_matchers::isExplicitTemplateSpecialization())), + returns(IsStdString), hasDescendant(returnStmt()), + unless(hasDescendant(returnStmt(hasReturnValue(unless( + anyOf(stringLiteral(), hasType(IsStdStringView), TernaryOperator, + cxxConstructExpr(anyOf( + allOf(hasType(IsStdString), argumentCountIs(0)), + allOf(isListInitialization(), + unless(cxxTemporaryObjectExpr()), + hasArgument(0, ignoringParenImpCasts( + stringLiteral())))))))))))) + .bind("func"), + this); +} + +void UseStringViewCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("func"); + assert(MatchedDecl); + bool ShouldAKA = false; + const std::string DesugaredTypeStr = + clang::desugarForDiagnostic( + *Result.Context, QualType(MatchedDecl->getReturnType()), ShouldAKA) + .getAsString(); + const StringRef DestReturnTypeStr = toStringViewTypeStr(DesugaredTypeStr); + + auto Diag = + diag(MatchedDecl->getTypeSpecStartLoc(), + "consider using '%0' to avoid unnecessary copying and allocations") + << DestReturnTypeStr; + + fixReturns(MatchedDecl, Diag, *Result.Context); + + for (const auto *FuncDecl : MatchedDecl->redecls()) + if (const SourceRange ReturnTypeRange = + FuncDecl->getReturnTypeSourceRange(); + ReturnTypeRange.isValid()) + Diag << FixItHint::CreateReplacement(ReturnTypeRange, DestReturnTypeStr); +} + +StringRef UseStringViewCheck::toStringViewTypeStr(StringRef Type) const { + if (Type.contains("wchar_t")) + return WStringViewClass; + if (Type.contains("char8_t")) + return U8StringViewClass; + if (Type.contains("char16_t")) + return U16StringViewClass; + if (Type.contains("char32_t")) + return U32StringViewClass; + return StringViewClass; +} + +void UseStringViewCheck::parseReplacementStringViewClass(StringRef Options) { ---------------- zeyi2 wrote:
Can we use `llvm::StringSwitch` here? Also, I'm thinking that `parseStringList` may introduce a potential use-after-free here. But I'm still trying to verify it. Will update when I get the result :) https://github.com/llvm/llvm-project/pull/172170 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
