162 lines
6.9 KiB
C++
162 lines
6.9 KiB
C++
//===--- 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.emplace_back("::printf");
|
|
PrintfLikeFunctions.emplace_back("absl::PrintF");
|
|
FprintfLikeFunctions.emplace_back("::fprintf");
|
|
FprintfLikeFunctions.emplace_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
|