Skip to content

Commit

Permalink
[clang-tidy] Add modernize-printf-to-std-print check
Browse files Browse the repository at this point in the history
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
mikecrowe authored and PiotrZSL committed Jun 26, 2023
1 parent 3b85be3 commit ec89cb9
Show file tree
Hide file tree
Showing 20 changed files with 2,735 additions and 1 deletion.
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
Expand Up @@ -37,6 +37,7 @@ add_clang_library(clangTidyModernizeModule
UseNoexceptCheck.cpp
UseNullptrCheck.cpp
UseOverrideCheck.cpp
UseStdPrintCheck.cpp
UseTrailingReturnTypeCheck.cpp
UseTransparentFunctorsCheck.cpp
UseUncaughtExceptionsCheck.cpp
Expand Down
Expand Up @@ -38,6 +38,7 @@
#include "UseNoexceptCheck.h"
#include "UseNullptrCheck.h"
#include "UseOverrideCheck.h"
#include "UseStdPrintCheck.h"
#include "UseTrailingReturnTypeCheck.h"
#include "UseTransparentFunctorsCheck.h"
#include "UseUncaughtExceptionsCheck.h"
Expand All @@ -64,6 +65,7 @@ class ModernizeModule : public ClangTidyModule {
CheckFactories.registerCheck<MakeSharedCheck>("modernize-make-shared");
CheckFactories.registerCheck<MakeUniqueCheck>("modernize-make-unique");
CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value");
CheckFactories.registerCheck<UseStdPrintCheck>("modernize-use-std-print");
CheckFactories.registerCheck<RawStringLiteralCheck>(
"modernize-raw-string-literal");
CheckFactories.registerCheck<RedundantVoidArgCheck>(
Expand Down
136 changes: 136 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
@@ -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
50 changes: 50 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h
@@ -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
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/utils/CMakeLists.txt
Expand Up @@ -11,6 +11,7 @@ add_clang_library(clangTidyUtils
ExceptionSpecAnalyzer.cpp
ExprSequence.cpp
FileExtensionsUtils.cpp
FormatStringConverter.cpp
FixItHintUtils.cpp
HeaderGuard.cpp
IncludeInserter.cpp
Expand Down

0 comments on commit ec89cb9

Please sign in to comment.