1 //===--- UseStdPrintCheck.cpp - clang-tidy-----------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
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"
17 using namespace clang::ast_matchers
;
19 namespace clang::tidy::modernize
{
22 AST_MATCHER(StringLiteral
, isOrdinary
) { return Node
.isOrdinary(); }
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")) {
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");
48 if (!MaybeHeaderToInclude
&& (ReplacementPrintFunction
== "std::print" ||
49 ReplacementPrintlnFunction
== "std::println"))
50 MaybeHeaderToInclude
= "<print>";
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
);
67 void UseStdPrintCheck::registerPPCallbacks(const SourceManager
&SM
,
69 Preprocessor
*ModuleExpanderPP
) {
70 IncludeInserter
.registerPreprocessor(PP
);
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
82 unless(hasParent(stmtExpr())));
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
));
93 return stmt(anyOf(UnusedInCompoundStmt
, UnusedInIfStmt
, UnusedInWhileStmt
,
94 UnusedInDoStmt
, UnusedInForStmt
, UnusedInRangeForStmt
,
98 void UseStdPrintCheck::registerMatchers(MatchFinder
*Finder
) {
99 if (!PrintfLikeFunctions
.empty())
102 callExpr(argumentCountAtLeast(1),
103 hasArgument(0, stringLiteral(isOrdinary())),
104 callee(functionDecl(matchers::matchesAnyListedName(
105 PrintfLikeFunctions
))
110 if (!FprintfLikeFunctions
.empty())
113 callExpr(argumentCountAtLeast(2),
114 hasArgument(1, stringLiteral(isOrdinary())),
115 callee(functionDecl(matchers::matchesAnyListedName(
116 FprintfLikeFunctions
))
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");
127 Printf
= Result
.Nodes
.getNodeAs
<CallExpr
>("fprintf");
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();
151 DiagnosticBuilder Diag
=
152 diag(PrintfCall
->getBeginLoc(), "use '%0' instead of %1")
153 << ReplacementFunction
<< OldFunction
->getIdentifier();
155 Diag
<< FixItHint::CreateReplacement(
156 CharSourceRange::getTokenRange(PrintfCall
->getExprLoc(),
157 PrintfCall
->getEndLoc()),
158 ReplacementFunction
);
159 Converter
.applyFixes(Diag
, *Result
.SourceManager
);
161 if (MaybeHeaderToInclude
)
162 Diag
<< IncludeInserter
.createIncludeInsertion(
163 Result
.Context
->getSourceManager().getFileID(PrintfCall
->getBeginLoc()),
164 *MaybeHeaderToInclude
);
167 } // namespace clang::tidy::modernize