//===--- UseStdPrintCheck.cpp - clang-tidy-----------------------*- C++ -*-===// // // 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 "UseStdPrintCheck.h" #include "../utils/FormatStringConverter.h" #include "../utils/Matchers.h" #include "../utils/OptionsUtils.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" #include "clang/Tooling/FixIt.h" using namespace clang::ast_matchers; namespace clang::tidy::modernize { namespace { AST_MATCHER(StringLiteral, isOrdinary) { return Node.isOrdinary(); } } // namespace UseStdPrintCheck::UseStdPrintCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), StrictMode(Options.getLocalOrGlobal("StrictMode", false)), PrintfLikeFunctions(utils::options::parseStringList( Options.get("PrintfLikeFunctions", ""))), FprintfLikeFunctions(utils::options::parseStringList( Options.get("FprintfLikeFunctions", ""))), ReplacementPrintFunction( Options.get("ReplacementPrintFunction", "std::print")), ReplacementPrintlnFunction( Options.get("ReplacementPrintlnFunction", "std::println")), IncludeInserter(Options.getLocalOrGlobal("IncludeStyle", utils::IncludeSorter::IS_LLVM), areDiagsSelfContained()), MaybeHeaderToInclude(Options.get("PrintHeader")) { if (PrintfLikeFunctions.empty() && FprintfLikeFunctions.empty()) { PrintfLikeFunctions.emplace_back("::printf"); PrintfLikeFunctions.emplace_back("absl::PrintF"); FprintfLikeFunctions.emplace_back("::fprintf"); FprintfLikeFunctions.emplace_back("absl::FPrintF"); } if (!MaybeHeaderToInclude && (ReplacementPrintFunction == "std::print" || ReplacementPrintlnFunction == "std::println")) MaybeHeaderToInclude = ""; } void UseStdPrintCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { using utils::options::serializeStringList; Options.store(Opts, "StrictMode", StrictMode); Options.store(Opts, "PrintfLikeFunctions", serializeStringList(PrintfLikeFunctions)); Options.store(Opts, "FprintfLikeFunctions", serializeStringList(FprintfLikeFunctions)); Options.store(Opts, "ReplacementPrintFunction", ReplacementPrintFunction); Options.store(Opts, "ReplacementPrintlnFunction", ReplacementPrintlnFunction); Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle()); if (MaybeHeaderToInclude) Options.store(Opts, "PrintHeader", *MaybeHeaderToInclude); } void UseStdPrintCheck::registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { IncludeInserter.registerPreprocessor(PP); } static clang::ast_matchers::StatementMatcher unusedReturnValue(clang::ast_matchers::StatementMatcher MatchedCallExpr) { auto UnusedInCompoundStmt = compoundStmt(forEach(MatchedCallExpr), // The checker can't currently differentiate between the // return statement and other statements inside GNU statement // expressions, so disable the checker inside them to avoid // false positives. unless(hasParent(stmtExpr()))); auto UnusedInIfStmt = ifStmt(eachOf(hasThen(MatchedCallExpr), hasElse(MatchedCallExpr))); auto UnusedInWhileStmt = whileStmt(hasBody(MatchedCallExpr)); auto UnusedInDoStmt = doStmt(hasBody(MatchedCallExpr)); auto UnusedInForStmt = forStmt(eachOf(hasLoopInit(MatchedCallExpr), hasIncrement(MatchedCallExpr), hasBody(MatchedCallExpr))); auto UnusedInRangeForStmt = cxxForRangeStmt(hasBody(MatchedCallExpr)); auto UnusedInCaseStmt = switchCase(forEach(MatchedCallExpr)); return stmt(anyOf(UnusedInCompoundStmt, UnusedInIfStmt, UnusedInWhileStmt, UnusedInDoStmt, UnusedInForStmt, UnusedInRangeForStmt, UnusedInCaseStmt)); } void UseStdPrintCheck::registerMatchers(MatchFinder *Finder) { if (!PrintfLikeFunctions.empty()) Finder->addMatcher( unusedReturnValue( callExpr(argumentCountAtLeast(1), hasArgument(0, stringLiteral(isOrdinary())), callee(functionDecl(unless(cxxMethodDecl()), matchers::matchesAnyListedName( PrintfLikeFunctions)) .bind("func_decl"))) .bind("printf")), this); if (!FprintfLikeFunctions.empty()) Finder->addMatcher( unusedReturnValue( callExpr(argumentCountAtLeast(2), hasArgument(1, stringLiteral(isOrdinary())), callee(functionDecl(unless(cxxMethodDecl()), matchers::matchesAnyListedName( FprintfLikeFunctions)) .bind("func_decl"))) .bind("fprintf")), this); } void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) { unsigned FormatArgOffset = 0; const auto *OldFunction = Result.Nodes.getNodeAs("func_decl"); const auto *Printf = Result.Nodes.getNodeAs("printf"); if (!Printf) { Printf = Result.Nodes.getNodeAs("fprintf"); FormatArgOffset = 1; } utils::FormatStringConverter Converter( Result.Context, Printf, FormatArgOffset, StrictMode, getLangOpts()); const Expr *PrintfCall = Printf->getCallee(); const StringRef ReplacementFunction = Converter.usePrintNewlineFunction() ? ReplacementPrintlnFunction : ReplacementPrintFunction; if (!Converter.canApply()) { diag(PrintfCall->getBeginLoc(), "unable to use '%0' instead of %1 because %2") << ReplacementFunction << OldFunction->getIdentifier() << Converter.conversionNotPossibleReason(); return; } DiagnosticBuilder Diag = diag(PrintfCall->getBeginLoc(), "use '%0' instead of %1") << ReplacementFunction << OldFunction->getIdentifier(); Diag << FixItHint::CreateReplacement( CharSourceRange::getTokenRange(PrintfCall->getBeginLoc(), PrintfCall->getEndLoc()), ReplacementFunction); Converter.applyFixes(Diag, *Result.SourceManager); if (MaybeHeaderToInclude) Diag << IncludeInserter.createIncludeInsertion( Result.Context->getSourceManager().getFileID(PrintfCall->getBeginLoc()), *MaybeHeaderToInclude); } } // namespace clang::tidy::modernize