blob: b1e1189d4ed7757678be835fa3957ef31f72e7fe [file] [log] [blame]
//===--- 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);
}
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<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