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(LocalName: "StrictMode" , Default: false)), |
28 | PrintfLikeFunctions(utils::options::parseStringList( |
29 | Option: Options.get(LocalName: "PrintfLikeFunctions" , Default: "" ))), |
30 | FprintfLikeFunctions(utils::options::parseStringList( |
31 | Option: Options.get(LocalName: "FprintfLikeFunctions" , Default: "" ))), |
32 | ReplacementPrintFunction( |
33 | Options.get(LocalName: "ReplacementPrintFunction" , Default: "std::print" )), |
34 | ReplacementPrintlnFunction( |
35 | Options.get(LocalName: "ReplacementPrintlnFunction" , Default: "std::println" )), |
36 | IncludeInserter(Options.getLocalOrGlobal(LocalName: "IncludeStyle" , |
37 | Default: utils::IncludeSorter::IS_LLVM), |
38 | areDiagsSelfContained()), |
39 | MaybeHeaderToInclude(Options.get(LocalName: "PrintHeader" )) { |
40 | |
41 | if (PrintfLikeFunctions.empty() && FprintfLikeFunctions.empty()) { |
42 | PrintfLikeFunctions.emplace_back(args: "::printf" ); |
43 | PrintfLikeFunctions.emplace_back(args: "absl::PrintF" ); |
44 | FprintfLikeFunctions.emplace_back(args: "::fprintf" ); |
45 | FprintfLikeFunctions.emplace_back(args: "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(Options&: Opts, LocalName: "StrictMode" , Value: StrictMode); |
56 | Options.store(Options&: Opts, LocalName: "PrintfLikeFunctions" , |
57 | Value: serializeStringList(Strings: PrintfLikeFunctions)); |
58 | Options.store(Options&: Opts, LocalName: "FprintfLikeFunctions" , |
59 | Value: serializeStringList(Strings: FprintfLikeFunctions)); |
60 | Options.store(Options&: Opts, LocalName: "ReplacementPrintFunction" , Value: ReplacementPrintFunction); |
61 | Options.store(Options&: Opts, LocalName: "ReplacementPrintlnFunction" , Value: ReplacementPrintlnFunction); |
62 | Options.store(Options&: Opts, LocalName: "IncludeStyle" , Value: IncludeInserter.getStyle()); |
63 | if (MaybeHeaderToInclude) |
64 | Options.store(Options&: Opts, LocalName: "PrintHeader" , Value: *MaybeHeaderToInclude); |
65 | } |
66 | |
67 | void UseStdPrintCheck::registerPPCallbacks(const SourceManager &SM, |
68 | Preprocessor *PP, |
69 | Preprocessor *ModuleExpanderPP) { |
70 | IncludeInserter.registerPreprocessor(PP); |
71 | } |
72 | |
73 | static clang::ast_matchers::StatementMatcher |
74 | unusedReturnValue(clang::ast_matchers::StatementMatcher MatchedCallExpr) { |
75 | auto UnusedInCompoundStmt = |
76 | compoundStmt(forEach(MatchedCallExpr), |
77 | // The checker can't currently differentiate between the |
78 | // return statement and other statements inside GNU statement |
79 | // expressions, so disable the checker inside them to avoid |
80 | // false positives. |
81 | unless(hasParent(stmtExpr()))); |
82 | auto UnusedInIfStmt = |
83 | ifStmt(eachOf(hasThen(InnerMatcher: MatchedCallExpr), hasElse(InnerMatcher: MatchedCallExpr))); |
84 | auto UnusedInWhileStmt = whileStmt(hasBody(InnerMatcher: MatchedCallExpr)); |
85 | auto UnusedInDoStmt = doStmt(hasBody(InnerMatcher: MatchedCallExpr)); |
86 | auto UnusedInForStmt = |
87 | forStmt(eachOf(hasLoopInit(InnerMatcher: MatchedCallExpr), |
88 | hasIncrement(InnerMatcher: MatchedCallExpr), hasBody(InnerMatcher: MatchedCallExpr))); |
89 | auto UnusedInRangeForStmt = cxxForRangeStmt(hasBody(InnerMatcher: MatchedCallExpr)); |
90 | auto UnusedInCaseStmt = switchCase(forEach(MatchedCallExpr)); |
91 | |
92 | return stmt(anyOf(UnusedInCompoundStmt, UnusedInIfStmt, UnusedInWhileStmt, |
93 | UnusedInDoStmt, UnusedInForStmt, UnusedInRangeForStmt, |
94 | UnusedInCaseStmt)); |
95 | } |
96 | |
97 | void UseStdPrintCheck::registerMatchers(MatchFinder *Finder) { |
98 | if (!PrintfLikeFunctions.empty()) |
99 | Finder->addMatcher( |
100 | NodeMatch: unusedReturnValue( |
101 | MatchedCallExpr: callExpr(argumentCountAtLeast(N: 1), |
102 | hasArgument(N: 0, InnerMatcher: stringLiteral(isOrdinary())), |
103 | callee(InnerMatcher: functionDecl(unless(cxxMethodDecl()), |
104 | matchers::matchesAnyListedName( |
105 | NameList: PrintfLikeFunctions)) |
106 | .bind(ID: "func_decl" ))) |
107 | .bind(ID: "printf" )), |
108 | Action: this); |
109 | |
110 | if (!FprintfLikeFunctions.empty()) |
111 | Finder->addMatcher( |
112 | NodeMatch: unusedReturnValue( |
113 | MatchedCallExpr: callExpr(argumentCountAtLeast(N: 2), |
114 | hasArgument(N: 1, InnerMatcher: stringLiteral(isOrdinary())), |
115 | callee(InnerMatcher: functionDecl(unless(cxxMethodDecl()), |
116 | matchers::matchesAnyListedName( |
117 | NameList: FprintfLikeFunctions)) |
118 | .bind(ID: "func_decl" ))) |
119 | .bind(ID: "fprintf" )), |
120 | Action: this); |
121 | } |
122 | |
123 | void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) { |
124 | unsigned FormatArgOffset = 0; |
125 | const auto *OldFunction = Result.Nodes.getNodeAs<FunctionDecl>(ID: "func_decl" ); |
126 | const auto *Printf = Result.Nodes.getNodeAs<CallExpr>(ID: "printf" ); |
127 | if (!Printf) { |
128 | Printf = Result.Nodes.getNodeAs<CallExpr>(ID: "fprintf" ); |
129 | FormatArgOffset = 1; |
130 | } |
131 | |
132 | utils::FormatStringConverter Converter( |
133 | Result.Context, Printf, FormatArgOffset, StrictMode, getLangOpts()); |
134 | const Expr *PrintfCall = Printf->getCallee(); |
135 | const StringRef ReplacementFunction = Converter.usePrintNewlineFunction() |
136 | ? ReplacementPrintlnFunction |
137 | : ReplacementPrintFunction; |
138 | if (!Converter.canApply()) { |
139 | diag(PrintfCall->getBeginLoc(), |
140 | "unable to use '%0' instead of %1 because %2" ) |
141 | << ReplacementFunction << OldFunction->getIdentifier() |
142 | << Converter.conversionNotPossibleReason(); |
143 | return; |
144 | } |
145 | |
146 | DiagnosticBuilder Diag = |
147 | diag(PrintfCall->getBeginLoc(), "use '%0' instead of %1" ) |
148 | << ReplacementFunction << OldFunction->getIdentifier(); |
149 | |
150 | Diag << FixItHint::CreateReplacement( |
151 | CharSourceRange::getTokenRange(PrintfCall->getBeginLoc(), |
152 | PrintfCall->getEndLoc()), |
153 | ReplacementFunction); |
154 | Converter.applyFixes(Diag, SM&: *Result.SourceManager); |
155 | |
156 | if (MaybeHeaderToInclude) |
157 | Diag << IncludeInserter.createIncludeInsertion( |
158 | FileID: Result.Context->getSourceManager().getFileID(PrintfCall->getBeginLoc()), |
159 | Header: *MaybeHeaderToInclude); |
160 | } |
161 | |
162 | } // namespace clang::tidy::modernize |
163 | |