1 //===--- UseStdPrintCheck.cpp - clang-tidy-----------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "UseStdPrintCheck.h" 10 #include "../utils/FormatStringConverter.h" 11 #include "../utils/Matchers.h" 12 #include "../utils/OptionsUtils.h" 13 #include "clang/ASTMatchers/ASTMatchFinder.h" 14 #include "clang/Lex/Lexer.h" 15 #include "clang/Tooling/FixIt.h" 16 17 using namespace clang::ast_matchers; 18 19 namespace clang::tidy::modernize { 20 21 namespace { 22 AST_MATCHER(StringLiteral, isOrdinary) { return Node.isOrdinary(); } 23 } // namespace 24 25 UseStdPrintCheck::UseStdPrintCheck(StringRef Name, ClangTidyContext *Context) 26 : ClangTidyCheck(Name, Context), 27 StrictMode(Options.getLocalOrGlobal("StrictMode", false)), 28 PrintfLikeFunctions(utils::options::parseStringList( 29 Options.get("PrintfLikeFunctions", ""))), 30 FprintfLikeFunctions(utils::options::parseStringList( 31 Options.get("FprintfLikeFunctions", ""))), 32 ReplacementPrintFunction( 33 Options.get("ReplacementPrintFunction", "std::print")), 34 ReplacementPrintlnFunction( 35 Options.get("ReplacementPrintlnFunction", "std::println")), 36 IncludeInserter(Options.getLocalOrGlobal("IncludeStyle", 37 utils::IncludeSorter::IS_LLVM), 38 areDiagsSelfContained()), 39 MaybeHeaderToInclude(Options.get("PrintHeader")) { 40 41 if (PrintfLikeFunctions.empty() && FprintfLikeFunctions.empty()) { 42 PrintfLikeFunctions.push_back("::printf"); 43 PrintfLikeFunctions.push_back("absl::PrintF"); 44 FprintfLikeFunctions.push_back("::fprintf"); 45 FprintfLikeFunctions.push_back("absl::FPrintF"); 46 } 47 48 if (!MaybeHeaderToInclude && (ReplacementPrintFunction == "std::print" || 49 ReplacementPrintlnFunction == "std::println")) 50 MaybeHeaderToInclude = "<print>"; 51 } 52 53 void UseStdPrintCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 54 using utils::options::serializeStringList; 55 Options.store(Opts, "StrictMode", StrictMode); 56 Options.store(Opts, "PrintfLikeFunctions", 57 serializeStringList(PrintfLikeFunctions)); 58 Options.store(Opts, "FprintfLikeFunctions", 59 serializeStringList(FprintfLikeFunctions)); 60 Options.store(Opts, "ReplacementPrintFunction", ReplacementPrintFunction); 61 Options.store(Opts, "ReplacementPrintlnFunction", ReplacementPrintlnFunction); 62 Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle()); 63 if (MaybeHeaderToInclude) 64 Options.store(Opts, "PrintHeader", *MaybeHeaderToInclude); 65 } 66 67 void UseStdPrintCheck::registerPPCallbacks(const SourceManager &SM, 68 Preprocessor *PP, 69 Preprocessor *ModuleExpanderPP) { 70 IncludeInserter.registerPreprocessor(PP); 71 } 72 73 void UseStdPrintCheck::registerMatchers(MatchFinder *Finder) { 74 if (!PrintfLikeFunctions.empty()) 75 Finder->addMatcher( 76 callExpr(argumentCountAtLeast(1), 77 hasArgument(0, stringLiteral(isOrdinary())), 78 callee(functionDecl( 79 unless(cxxMethodDecl()), 80 matchers::matchesAnyListedName(PrintfLikeFunctions)) 81 .bind("func_decl"))) 82 .bind("printf"), 83 this); 84 85 if (!FprintfLikeFunctions.empty()) 86 Finder->addMatcher( 87 callExpr(argumentCountAtLeast(2), 88 hasArgument(1, stringLiteral(isOrdinary())), 89 callee(functionDecl(unless(cxxMethodDecl()), 90 matchers::matchesAnyListedName( 91 FprintfLikeFunctions)) 92 .bind("func_decl"))) 93 .bind("fprintf"), 94 this); 95 } 96 97 void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) { 98 unsigned FormatArgOffset = 0; 99 const auto *OldFunction = Result.Nodes.getNodeAs<FunctionDecl>("func_decl"); 100 const auto *Printf = Result.Nodes.getNodeAs<CallExpr>("printf"); 101 if (!Printf) { 102 Printf = Result.Nodes.getNodeAs<CallExpr>("fprintf"); 103 FormatArgOffset = 1; 104 } 105 106 utils::FormatStringConverter Converter( 107 Result.Context, Printf, FormatArgOffset, StrictMode, getLangOpts()); 108 const Expr *PrintfCall = Printf->getCallee(); 109 const StringRef ReplacementFunction = Converter.usePrintNewlineFunction() 110 ? ReplacementPrintlnFunction 111 : ReplacementPrintFunction; 112 if (!Converter.canApply()) { 113 diag(PrintfCall->getBeginLoc(), 114 "unable to use '%0' instead of %1 because %2") 115 << ReplacementFunction << OldFunction->getIdentifier() 116 << Converter.conversionNotPossibleReason(); 117 return; 118 } 119 120 DiagnosticBuilder Diag = 121 diag(PrintfCall->getBeginLoc(), "use '%0' instead of %1") 122 << ReplacementFunction << OldFunction->getIdentifier(); 123 124 Diag << FixItHint::CreateReplacement( 125 CharSourceRange::getTokenRange(PrintfCall->getBeginLoc(), 126 PrintfCall->getEndLoc()), 127 ReplacementFunction); 128 Converter.applyFixes(Diag, *Result.SourceManager); 129 130 if (MaybeHeaderToInclude) 131 Diag << IncludeInserter.createIncludeInsertion( 132 Result.Context->getSourceManager().getFileID(PrintfCall->getBeginLoc()), 133 *MaybeHeaderToInclude); 134 } 135 136 } // namespace clang::tidy::modernize 137