1af79372dSMike Crowe //===--- UseStdFormatCheck.cpp - clang-tidy -------------------------------===// 2af79372dSMike Crowe // 3af79372dSMike Crowe // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4af79372dSMike Crowe // See https://llvm.org/LICENSE.txt for license information. 5af79372dSMike Crowe // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6af79372dSMike Crowe // 7af79372dSMike Crowe //===----------------------------------------------------------------------===// 8af79372dSMike Crowe 9af79372dSMike Crowe #include "UseStdFormatCheck.h" 10af79372dSMike Crowe #include "../utils/FormatStringConverter.h" 11af79372dSMike Crowe #include "../utils/Matchers.h" 12af79372dSMike Crowe #include "../utils/OptionsUtils.h" 13af79372dSMike Crowe #include "clang/ASTMatchers/ASTMatchFinder.h" 14af79372dSMike Crowe #include "clang/Lex/Lexer.h" 15af79372dSMike Crowe #include "clang/Tooling/FixIt.h" 16af79372dSMike Crowe 17af79372dSMike Crowe using namespace clang::ast_matchers; 18af79372dSMike Crowe 19af79372dSMike Crowe namespace clang::tidy::modernize { 20af79372dSMike Crowe 21af79372dSMike Crowe namespace { 22af79372dSMike Crowe AST_MATCHER(StringLiteral, isOrdinary) { return Node.isOrdinary(); } 23af79372dSMike Crowe } // namespace 24af79372dSMike Crowe 25af79372dSMike Crowe UseStdFormatCheck::UseStdFormatCheck(StringRef Name, ClangTidyContext *Context) 26af79372dSMike Crowe : ClangTidyCheck(Name, Context), 27af79372dSMike Crowe StrictMode(Options.getLocalOrGlobal("StrictMode", false)), 28af79372dSMike Crowe StrFormatLikeFunctions(utils::options::parseStringList( 29af79372dSMike Crowe Options.get("StrFormatLikeFunctions", ""))), 30af79372dSMike Crowe ReplacementFormatFunction( 31af79372dSMike Crowe Options.get("ReplacementFormatFunction", "std::format")), 32af79372dSMike Crowe IncludeInserter(Options.getLocalOrGlobal("IncludeStyle", 33af79372dSMike Crowe utils::IncludeSorter::IS_LLVM), 34af79372dSMike Crowe areDiagsSelfContained()), 35af79372dSMike Crowe MaybeHeaderToInclude(Options.get("FormatHeader")) { 36af79372dSMike Crowe if (StrFormatLikeFunctions.empty()) 37af79372dSMike Crowe StrFormatLikeFunctions.push_back("absl::StrFormat"); 38af79372dSMike Crowe 39af79372dSMike Crowe if (!MaybeHeaderToInclude && ReplacementFormatFunction == "std::format") 40af79372dSMike Crowe MaybeHeaderToInclude = "<format>"; 41af79372dSMike Crowe } 42af79372dSMike Crowe 43af79372dSMike Crowe void UseStdFormatCheck::registerPPCallbacks(const SourceManager &SM, 44af79372dSMike Crowe Preprocessor *PP, 45af79372dSMike Crowe Preprocessor *ModuleExpanderPP) { 46af79372dSMike Crowe IncludeInserter.registerPreprocessor(PP); 47*a199fb12SMike Crowe this->PP = PP; 48af79372dSMike Crowe } 49af79372dSMike Crowe 50af79372dSMike Crowe void UseStdFormatCheck::registerMatchers(MatchFinder *Finder) { 51af79372dSMike Crowe Finder->addMatcher( 52666d2242SMike Crowe callExpr(argumentCountAtLeast(1), 53666d2242SMike Crowe hasArgument(0, stringLiteral(isOrdinary())), 54bbcb6257SMike Crowe callee(functionDecl(matchers::matchesAnyListedName( 55666d2242SMike Crowe StrFormatLikeFunctions)) 56af79372dSMike Crowe .bind("func_decl"))) 57af79372dSMike Crowe .bind("strformat"), 58af79372dSMike Crowe this); 59af79372dSMike Crowe } 60af79372dSMike Crowe 61af79372dSMike Crowe void UseStdFormatCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 62af79372dSMike Crowe using utils::options::serializeStringList; 63af79372dSMike Crowe Options.store(Opts, "StrictMode", StrictMode); 64af79372dSMike Crowe Options.store(Opts, "StrFormatLikeFunctions", 65af79372dSMike Crowe serializeStringList(StrFormatLikeFunctions)); 66af79372dSMike Crowe Options.store(Opts, "ReplacementFormatFunction", ReplacementFormatFunction); 67af79372dSMike Crowe Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle()); 68af79372dSMike Crowe if (MaybeHeaderToInclude) 69af79372dSMike Crowe Options.store(Opts, "FormatHeader", *MaybeHeaderToInclude); 70af79372dSMike Crowe } 71af79372dSMike Crowe 72af79372dSMike Crowe void UseStdFormatCheck::check(const MatchFinder::MatchResult &Result) { 73af79372dSMike Crowe const unsigned FormatArgOffset = 0; 74af79372dSMike Crowe const auto *OldFunction = Result.Nodes.getNodeAs<FunctionDecl>("func_decl"); 75af79372dSMike Crowe const auto *StrFormat = Result.Nodes.getNodeAs<CallExpr>("strformat"); 76af79372dSMike Crowe 77af79372dSMike Crowe utils::FormatStringConverter::Configuration ConverterConfig; 78af79372dSMike Crowe ConverterConfig.StrictMode = StrictMode; 79*a199fb12SMike Crowe utils::FormatStringConverter Converter( 80*a199fb12SMike Crowe Result.Context, StrFormat, FormatArgOffset, ConverterConfig, 81*a199fb12SMike Crowe getLangOpts(), *Result.SourceManager, *PP); 82af79372dSMike Crowe const Expr *StrFormatCall = StrFormat->getCallee(); 83af79372dSMike Crowe if (!Converter.canApply()) { 84af79372dSMike Crowe diag(StrFormat->getBeginLoc(), 85af79372dSMike Crowe "unable to use '%0' instead of %1 because %2") 86af79372dSMike Crowe << StrFormatCall->getSourceRange() << ReplacementFormatFunction 87af79372dSMike Crowe << OldFunction->getIdentifier() 88af79372dSMike Crowe << Converter.conversionNotPossibleReason(); 89af79372dSMike Crowe return; 90af79372dSMike Crowe } 91af79372dSMike Crowe 92af79372dSMike Crowe DiagnosticBuilder Diag = 93af79372dSMike Crowe diag(StrFormatCall->getBeginLoc(), "use '%0' instead of %1") 94af79372dSMike Crowe << ReplacementFormatFunction << OldFunction->getIdentifier(); 95af79372dSMike Crowe Diag << FixItHint::CreateReplacement( 96bbcb6257SMike Crowe CharSourceRange::getTokenRange(StrFormatCall->getExprLoc(), 97bbcb6257SMike Crowe StrFormatCall->getEndLoc()), 98af79372dSMike Crowe ReplacementFormatFunction); 99af79372dSMike Crowe Converter.applyFixes(Diag, *Result.SourceManager); 100af79372dSMike Crowe 101af79372dSMike Crowe if (MaybeHeaderToInclude) 102af79372dSMike Crowe Diag << IncludeInserter.createIncludeInsertion( 103af79372dSMike Crowe Result.Context->getSourceManager().getFileID( 104af79372dSMike Crowe StrFormatCall->getBeginLoc()), 105af79372dSMike Crowe *MaybeHeaderToInclude); 106af79372dSMike Crowe } 107af79372dSMike Crowe 108af79372dSMike Crowe } // namespace clang::tidy::modernize 109