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), PP(nullptr), 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.emplace_back("::printf"); 43 PrintfLikeFunctions.emplace_back("absl::PrintF"); 44 FprintfLikeFunctions.emplace_back("::fprintf"); 45 FprintfLikeFunctions.emplace_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 this->PP = PP; 72 } 73 74 static clang::ast_matchers::StatementMatcher 75 unusedReturnValue(clang::ast_matchers::StatementMatcher MatchedCallExpr) { 76 auto UnusedInCompoundStmt = 77 compoundStmt(forEach(MatchedCallExpr), 78 // The checker can't currently differentiate between the 79 // return statement and other statements inside GNU statement 80 // expressions, so disable the checker inside them to avoid 81 // false positives. 82 unless(hasParent(stmtExpr()))); 83 auto UnusedInIfStmt = 84 ifStmt(eachOf(hasThen(MatchedCallExpr), hasElse(MatchedCallExpr))); 85 auto UnusedInWhileStmt = whileStmt(hasBody(MatchedCallExpr)); 86 auto UnusedInDoStmt = doStmt(hasBody(MatchedCallExpr)); 87 auto UnusedInForStmt = 88 forStmt(eachOf(hasLoopInit(MatchedCallExpr), 89 hasIncrement(MatchedCallExpr), hasBody(MatchedCallExpr))); 90 auto UnusedInRangeForStmt = cxxForRangeStmt(hasBody(MatchedCallExpr)); 91 auto UnusedInCaseStmt = switchCase(forEach(MatchedCallExpr)); 92 93 return stmt(anyOf(UnusedInCompoundStmt, UnusedInIfStmt, UnusedInWhileStmt, 94 UnusedInDoStmt, UnusedInForStmt, UnusedInRangeForStmt, 95 UnusedInCaseStmt)); 96 } 97 98 void UseStdPrintCheck::registerMatchers(MatchFinder *Finder) { 99 if (!PrintfLikeFunctions.empty()) 100 Finder->addMatcher( 101 unusedReturnValue( 102 callExpr(argumentCountAtLeast(1), 103 hasArgument(0, stringLiteral(isOrdinary())), 104 callee(functionDecl(matchers::matchesAnyListedName( 105 PrintfLikeFunctions)) 106 .bind("func_decl"))) 107 .bind("printf")), 108 this); 109 110 if (!FprintfLikeFunctions.empty()) 111 Finder->addMatcher( 112 unusedReturnValue( 113 callExpr(argumentCountAtLeast(2), 114 hasArgument(1, stringLiteral(isOrdinary())), 115 callee(functionDecl(matchers::matchesAnyListedName( 116 FprintfLikeFunctions)) 117 .bind("func_decl"))) 118 .bind("fprintf")), 119 this); 120 } 121 122 void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) { 123 unsigned FormatArgOffset = 0; 124 const auto *OldFunction = Result.Nodes.getNodeAs<FunctionDecl>("func_decl"); 125 const auto *Printf = Result.Nodes.getNodeAs<CallExpr>("printf"); 126 if (!Printf) { 127 Printf = Result.Nodes.getNodeAs<CallExpr>("fprintf"); 128 FormatArgOffset = 1; 129 } 130 131 utils::FormatStringConverter::Configuration ConverterConfig; 132 ConverterConfig.StrictMode = StrictMode; 133 ConverterConfig.AllowTrailingNewlineRemoval = true; 134 assert(PP && "Preprocessor should be set by registerPPCallbacks"); 135 utils::FormatStringConverter Converter( 136 Result.Context, Printf, FormatArgOffset, ConverterConfig, getLangOpts(), 137 *Result.SourceManager, *PP); 138 const Expr *PrintfCall = Printf->getCallee(); 139 const StringRef ReplacementFunction = Converter.usePrintNewlineFunction() 140 ? ReplacementPrintlnFunction 141 : ReplacementPrintFunction; 142 if (!Converter.canApply()) { 143 diag(PrintfCall->getBeginLoc(), 144 "unable to use '%0' instead of %1 because %2") 145 << PrintfCall->getSourceRange() << ReplacementFunction 146 << OldFunction->getIdentifier() 147 << Converter.conversionNotPossibleReason(); 148 return; 149 } 150 151 DiagnosticBuilder Diag = 152 diag(PrintfCall->getBeginLoc(), "use '%0' instead of %1") 153 << ReplacementFunction << OldFunction->getIdentifier(); 154 155 Diag << FixItHint::CreateReplacement( 156 CharSourceRange::getTokenRange(PrintfCall->getExprLoc(), 157 PrintfCall->getEndLoc()), 158 ReplacementFunction); 159 Converter.applyFixes(Diag, *Result.SourceManager); 160 161 if (MaybeHeaderToInclude) 162 Diag << IncludeInserter.createIncludeInsertion( 163 Result.Context->getSourceManager().getFileID(PrintfCall->getBeginLoc()), 164 *MaybeHeaderToInclude); 165 } 166 167 } // namespace clang::tidy::modernize 168