Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[clang-tidy] Add modernize-printf-to-std-print check
Add FormatStringConverter utility class that is capable of converting printf-style format strings into std::print-style format strings along with recording a set of casts to wrap the arguments as required and removing now-unnecessary calls to std::string::c_str() and std::string::data() Use FormatStringConverter to implement a new clang-tidy check that is capable of converting calls to printf, fprintf, absl::PrintF, absl::FPrintF, or any functions configured by an option to calls to std::print and std::println, or other functions configured by options. In other words, the check turns: fprintf(stderr, "The %s is %3d\n", description.c_str(), value); into: std::println(stderr, "The {} is {:3}", description, value); if it can. std::print and std::println can do almost anything that standard printf can, but the conversion has some some limitations that are described in the documentation. If conversion is not possible then the call remains unchanged. Depends on D153716 Reviewed By: PiotrZSL Differential Revision: https://reviews.llvm.org/D149280
- Loading branch information
Showing
20 changed files
with
2,735 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
//===--- 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.push_back("::printf"); | ||
PrintfLikeFunctions.push_back("absl::PrintF"); | ||
FprintfLikeFunctions.push_back("::fprintf"); | ||
FprintfLikeFunctions.push_back("absl::FPrintF"); | ||
} | ||
|
||
if (!MaybeHeaderToInclude && (ReplacementPrintFunction == "std::print" || | ||
ReplacementPrintlnFunction == "std::println")) | ||
MaybeHeaderToInclude = "<print>"; | ||
} | ||
|
||
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); | ||
} | ||
|
||
void UseStdPrintCheck::registerMatchers(MatchFinder *Finder) { | ||
if (!PrintfLikeFunctions.empty()) | ||
Finder->addMatcher( | ||
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( | ||
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<FunctionDecl>("func_decl"); | ||
const auto *Printf = Result.Nodes.getNodeAs<CallExpr>("printf"); | ||
if (!Printf) { | ||
Printf = Result.Nodes.getNodeAs<CallExpr>("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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
//===--- UseStdPrintCheck.h - 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USESTDPRINTCHECK_H | ||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USESTDPRINTCHECK_H | ||
|
||
#include "../ClangTidyCheck.h" | ||
#include "../utils/IncludeInserter.h" | ||
|
||
namespace clang::tidy::modernize { | ||
/// Convert calls to printf-like functions to std::print and std::println | ||
/// | ||
/// For the user-facing documentation see: | ||
/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html | ||
class UseStdPrintCheck : public ClangTidyCheck { | ||
public: | ||
UseStdPrintCheck(StringRef Name, ClangTidyContext *Context); | ||
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { | ||
if (ReplacementPrintFunction == "std::print" || | ||
ReplacementPrintlnFunction == "std::println") | ||
return LangOpts.CPlusPlus23; | ||
return LangOpts.CPlusPlus; | ||
} | ||
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, | ||
Preprocessor *ModuleExpanderPP) override; | ||
void storeOptions(ClangTidyOptions::OptionMap &Opts) override; | ||
void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
std::optional<TraversalKind> getCheckTraversalKind() const override { | ||
return TK_IgnoreUnlessSpelledInSource; | ||
} | ||
|
||
private: | ||
bool StrictMode; | ||
std::vector<StringRef> PrintfLikeFunctions; | ||
std::vector<StringRef> FprintfLikeFunctions; | ||
StringRef ReplacementPrintFunction; | ||
StringRef ReplacementPrintlnFunction; | ||
utils::IncludeInserter IncludeInserter; | ||
std::optional<StringRef> MaybeHeaderToInclude; | ||
}; | ||
|
||
} // namespace clang::tidy::modernize | ||
|
||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USESTDPRINTCHECK_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.