Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ add_clang_library(clangTidyModernizeModule
UseNullptrCheck.cpp
UseOverrideCheck.cpp
UseStartsEndsWithCheck.cpp
UseStdFormatCheck.cpp
UseStdNumbersCheck.cpp
UseStdPrintCheck.cpp
UseTrailingReturnTypeCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "UseNullptrCheck.h"
#include "UseOverrideCheck.h"
#include "UseStartsEndsWithCheck.h"
#include "UseStdFormatCheck.h"
#include "UseStdNumbersCheck.h"
#include "UseStdPrintCheck.h"
#include "UseTrailingReturnTypeCheck.h"
Expand Down Expand Up @@ -76,6 +77,7 @@ class ModernizeModule : public ClangTidyModule {
"modernize-use-designated-initializers");
CheckFactories.registerCheck<UseStartsEndsWithCheck>(
"modernize-use-starts-ends-with");
CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format");
CheckFactories.registerCheck<UseStdNumbersCheck>(
"modernize-use-std-numbers");
CheckFactories.registerCheck<UseStdPrintCheck>("modernize-use-std-print");
Expand Down
107 changes: 107 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//===--- UseStdFormatCheck.cpp - clang-tidy -------------------------------===//
//
// 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 "UseStdFormatCheck.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

UseStdFormatCheck::UseStdFormatCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
StrictMode(Options.getLocalOrGlobal("StrictMode", false)),
StrFormatLikeFunctions(utils::options::parseStringList(
Options.get("StrFormatLikeFunctions", ""))),
ReplacementFormatFunction(
Options.get("ReplacementFormatFunction", "std::format")),
IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
utils::IncludeSorter::IS_LLVM),
areDiagsSelfContained()),
MaybeHeaderToInclude(Options.get("FormatHeader")) {
if (StrFormatLikeFunctions.empty())
StrFormatLikeFunctions.push_back("absl::StrFormat");

if (!MaybeHeaderToInclude && ReplacementFormatFunction == "std::format")
MaybeHeaderToInclude = "<format>";
}

void UseStdFormatCheck::registerPPCallbacks(const SourceManager &SM,
Preprocessor *PP,
Preprocessor *ModuleExpanderPP) {
IncludeInserter.registerPreprocessor(PP);
}

void UseStdFormatCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
callExpr(argumentCountAtLeast(1),
hasArgument(0, stringLiteral(isOrdinary())),
callee(functionDecl(unless(cxxMethodDecl()),
matchers::matchesAnyListedName(
StrFormatLikeFunctions))
.bind("func_decl")))
.bind("strformat"),
this);
}

void UseStdFormatCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
using utils::options::serializeStringList;
Options.store(Opts, "StrictMode", StrictMode);
Options.store(Opts, "StrFormatLikeFunctions",
serializeStringList(StrFormatLikeFunctions));
Options.store(Opts, "ReplacementFormatFunction", ReplacementFormatFunction);
Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
if (MaybeHeaderToInclude)
Options.store(Opts, "FormatHeader", *MaybeHeaderToInclude);
}

void UseStdFormatCheck::check(const MatchFinder::MatchResult &Result) {
const unsigned FormatArgOffset = 0;
const auto *OldFunction = Result.Nodes.getNodeAs<FunctionDecl>("func_decl");
const auto *StrFormat = Result.Nodes.getNodeAs<CallExpr>("strformat");

utils::FormatStringConverter::Configuration ConverterConfig;
ConverterConfig.StrictMode = StrictMode;
utils::FormatStringConverter Converter(Result.Context, StrFormat,
FormatArgOffset, ConverterConfig,
getLangOpts());
const Expr *StrFormatCall = StrFormat->getCallee();
if (!Converter.canApply()) {
diag(StrFormat->getBeginLoc(),
"unable to use '%0' instead of %1 because %2")
<< StrFormatCall->getSourceRange() << ReplacementFormatFunction
<< OldFunction->getIdentifier()
<< Converter.conversionNotPossibleReason();
return;
}

DiagnosticBuilder Diag =
diag(StrFormatCall->getBeginLoc(), "use '%0' instead of %1")
<< ReplacementFormatFunction << OldFunction->getIdentifier();
Diag << FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(StrFormatCall->getSourceRange()),
ReplacementFormatFunction);
Converter.applyFixes(Diag, *Result.SourceManager);

if (MaybeHeaderToInclude)
Diag << IncludeInserter.createIncludeInsertion(
Result.Context->getSourceManager().getFileID(
StrFormatCall->getBeginLoc()),
*MaybeHeaderToInclude);
}

} // namespace clang::tidy::modernize
51 changes: 51 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//===--- UseStdFormatCheck.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_MODERNIZE_USESTDFORMATCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDFORMATCHECK_H

#include "../ClangTidyCheck.h"
#include "../utils/IncludeInserter.h"

namespace clang::tidy::modernize {

/// Converts calls to absl::StrFormat, or other functions via configuration
/// options, to C++20's std::format, or another function via a configuration
/// option, modifying the format string appropriately and removing
/// now-unnecessary calls to std::string::c_str() and std::string::data().
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-format.html
class UseStdFormatCheck : public ClangTidyCheck {
public:
UseStdFormatCheck(StringRef Name, ClangTidyContext *Context);
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
if (ReplacementFormatFunction == "std::format")
return LangOpts.CPlusPlus20;
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> StrFormatLikeFunctions;
StringRef ReplacementFormatFunction;
utils::IncludeInserter IncludeInserter;
std::optional<StringRef> MaybeHeaderToInclude;
};

} // namespace clang::tidy::modernize

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDFORMATCHECK_H
5 changes: 4 additions & 1 deletion clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,11 @@ void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) {
FormatArgOffset = 1;
}

utils::FormatStringConverter::Configuration ConverterConfig;
ConverterConfig.StrictMode = StrictMode;
ConverterConfig.AllowTrailingNewlineRemoval = true;
utils::FormatStringConverter Converter(
Result.Context, Printf, FormatArgOffset, StrictMode, getLangOpts());
Result.Context, Printf, FormatArgOffset, ConverterConfig, getLangOpts());
const Expr *PrintfCall = Printf->getCallee();
const StringRef ReplacementFunction = Converter.usePrintNewlineFunction()
? ReplacementPrintlnFunction
Expand Down
16 changes: 10 additions & 6 deletions clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,11 @@ static bool castMismatchedIntegerTypes(const CallExpr *Call, bool StrictMode) {
FormatStringConverter::FormatStringConverter(ASTContext *ContextIn,
const CallExpr *Call,
unsigned FormatArgOffset,
bool StrictMode,
const Configuration ConfigIn,
const LangOptions &LO)
: Context(ContextIn),
CastMismatchedIntegerTypes(castMismatchedIntegerTypes(Call, StrictMode)),
: Context(ContextIn), Config(ConfigIn),
CastMismatchedIntegerTypes(
castMismatchedIntegerTypes(Call, ConfigIn.StrictMode)),
Args(Call->getArgs()), NumArgs(Call->getNumArgs()),
ArgsOffset(FormatArgOffset + 1), LangOpts(LO) {
assert(ArgsOffset <= NumArgs);
Expand Down Expand Up @@ -627,9 +628,12 @@ void FormatStringConverter::finalizeFormatText() {

// It's clearer to convert printf("Hello\r\n"); to std::print("Hello\r\n")
// than to std::println("Hello\r");
if (StringRef(StandardFormatString).ends_with("\\n") &&
!StringRef(StandardFormatString).ends_with("\\\\n") &&
!StringRef(StandardFormatString).ends_with("\\r\\n")) {
// Use StringRef until C++20 std::string::ends_with() is available.
const auto StandardFormatStringRef = StringRef(StandardFormatString);
if (Config.AllowTrailingNewlineRemoval &&
StandardFormatStringRef.ends_with("\\n") &&
!StandardFormatStringRef.ends_with("\\\\n") &&
!StandardFormatStringRef.ends_with("\\r\\n")) {
UsePrintNewlineFunction = true;
FormatStringNeededRewriting = true;
StandardFormatString.erase(StandardFormatString.end() - 2,
Expand Down
9 changes: 8 additions & 1 deletion clang-tools-extra/clang-tidy/utils/FormatStringConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@ class FormatStringConverter
public:
using ConversionSpecifier = clang::analyze_format_string::ConversionSpecifier;
using PrintfSpecifier = analyze_printf::PrintfSpecifier;

struct Configuration {
bool StrictMode = false;
bool AllowTrailingNewlineRemoval = false;
};

FormatStringConverter(ASTContext *Context, const CallExpr *Call,
unsigned FormatArgOffset, bool StrictMode,
unsigned FormatArgOffset, Configuration Config,
const LangOptions &LO);

bool canApply() const { return ConversionNotPossibleReason.empty(); }
Expand All @@ -45,6 +51,7 @@ class FormatStringConverter

private:
ASTContext *Context;
const Configuration Config;
const bool CastMismatchedIntegerTypes;
const Expr *const *Args;
const unsigned NumArgs;
Expand Down
9 changes: 9 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ New checks
Finds initializer lists for aggregate types that could be
written as designated initializers instead.

- New :doc:`modernize-use-std-format
<clang-tidy/checks/modernize/use-std-format>` check.

Converts calls to ``absl::StrFormat``, or other functions via
configuration options, to C++20's ``std::format``, or another function
via a configuration option, modifying the format string appropriately and
removing now-unnecessary calls to ``std::string::c_str()`` and
``std::string::data()``.

- New :doc:`readability-enum-initial-value
<clang-tidy/checks/readability/enum-initial-value>` check.

Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ Clang-Tidy Checks
:doc:`modernize-use-nullptr <modernize/use-nullptr>`, "Yes"
:doc:`modernize-use-override <modernize/use-override>`, "Yes"
:doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "Yes"
:doc:`modernize-use-std-format <modernize/use-std-format>`, "Yes"
:doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes"
:doc:`modernize-use-std-print <modernize/use-std-print>`, "Yes"
:doc:`modernize-use-trailing-return-type <modernize/use-trailing-return-type>`, "Yes"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
.. title:: clang-tidy - modernize-use-std-format

modernize-use-std-format
========================

Converts calls to ``absl::StrFormat``, or other functions via
configuration options, to C++20's ``std::format``, or another function
via a configuration option, modifying the format string appropriately and
removing now-unnecessary calls to ``std::string::c_str()`` and
``std::string::data()``.

For example, it turns lines like

.. code-block:: c++

return absl::StrFormat("The %s is %3d", description.c_str(), value);

into:

.. code-block:: c++

return std::format("The {} is {:3}", description, value);

The check uses the same format-string-conversion algorithm as
`modernize-use-std-print <../modernize/use-std-print.html>`_ and its
shortcomings are described in the documentation for that check.

Options
-------

.. option:: StrictMode

When `true`, the check will add casts when converting from variadic
functions and printing signed or unsigned integer types (including
fixed-width integer types from ``<cstdint>``, ``ptrdiff_t``, ``size_t``
and ``ssize_t``) as the opposite signedness to ensure that the output
would matches that of a simple wrapper for ``std::sprintf`` that
accepted a C-style variable argument list. For example, with
`StrictMode` enabled,

.. code-block:: c++

extern std::string strprintf(const char *format, ...);
int i = -42;
unsigned int u = 0xffffffff;
return strprintf("%d %u\n", i, u);

would be converted to

.. code-block:: c++

return std::format("{} {}\n", static_cast<unsigned int>(i), static_cast<int>(u));

to ensure that the output will continue to be the unsigned representation
of -42 and the signed representation of 0xffffffff (often 4294967254
and -1 respectively). When `false` (which is the default), these casts
will not be added which may cause a change in the output. Note that this
option makes no difference for the default value of
`StrFormatLikeFunctions` since ``absl::StrFormat`` takes a function
parameter pack and is not a variadic function.

.. option:: StrFormatLikeFunctions

A semicolon-separated list of (fully qualified) function names to
replace, with the requirement that the first parameter contains the
printf-style format string and the arguments to be formatted follow
immediately afterwards. The default value for this option is
`absl::StrFormat`.

.. option:: ReplacementFormatFunction

The function that will be used to replace the function set by the
`StrFormatLikeFunctions` option rather than the default
`std::format`. It is expected that the function provides an interface
that is compatible with ``std::format``. A suitable candidate would be
`fmt::format`.

.. option:: FormatHeader

The header that must be included for the declaration of
`ReplacementFormatFunction` so that a ``#include`` directive can be added if
required. If `ReplacementFormatFunction` is `std::format` then this option will
default to ``<format>``, otherwise this option will default to nothing
and no ``#include`` directive will be added.
Loading