Skip to content

Commit a199fb1

Browse files
authored
[clang-tidy] Only expand <inttypes.h> macros in modernize-use-std-format/print (#97911)
Expanding all macros in the printf/absl::StrFormat format string before conversion could easily break code if those macros are expanded change their definition between builds. It's important for this check to expand the <inttypes.h> PRI macros though, so let's ensure that the presence of any other macros in the format string causes the check to emit a warning and not perform any conversion.
1 parent d905b1c commit a199fb1

File tree

12 files changed

+251
-43
lines changed

12 files changed

+251
-43
lines changed

clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ void UseStdFormatCheck::registerPPCallbacks(const SourceManager &SM,
4444
Preprocessor *PP,
4545
Preprocessor *ModuleExpanderPP) {
4646
IncludeInserter.registerPreprocessor(PP);
47+
this->PP = PP;
4748
}
4849

4950
void UseStdFormatCheck::registerMatchers(MatchFinder *Finder) {
@@ -75,9 +76,9 @@ void UseStdFormatCheck::check(const MatchFinder::MatchResult &Result) {
7576

7677
utils::FormatStringConverter::Configuration ConverterConfig;
7778
ConverterConfig.StrictMode = StrictMode;
78-
utils::FormatStringConverter Converter(Result.Context, StrFormat,
79-
FormatArgOffset, ConverterConfig,
80-
getLangOpts());
79+
utils::FormatStringConverter Converter(
80+
Result.Context, StrFormat, FormatArgOffset, ConverterConfig,
81+
getLangOpts(), *Result.SourceManager, *PP);
8182
const Expr *StrFormatCall = StrFormat->getCallee();
8283
if (!Converter.canApply()) {
8384
diag(StrFormat->getBeginLoc(),

clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class UseStdFormatCheck : public ClangTidyCheck {
4444
StringRef ReplacementFormatFunction;
4545
utils::IncludeInserter IncludeInserter;
4646
std::optional<StringRef> MaybeHeaderToInclude;
47+
Preprocessor *PP = nullptr;
4748
};
4849

4950
} // namespace clang::tidy::modernize

clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ void UseStdPrintCheck::registerPPCallbacks(const SourceManager &SM,
6868
Preprocessor *PP,
6969
Preprocessor *ModuleExpanderPP) {
7070
IncludeInserter.registerPreprocessor(PP);
71+
this->PP = PP;
7172
}
7273

7374
static clang::ast_matchers::StatementMatcher
@@ -131,7 +132,8 @@ void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) {
131132
ConverterConfig.StrictMode = StrictMode;
132133
ConverterConfig.AllowTrailingNewlineRemoval = true;
133134
utils::FormatStringConverter Converter(
134-
Result.Context, Printf, FormatArgOffset, ConverterConfig, getLangOpts());
135+
Result.Context, Printf, FormatArgOffset, ConverterConfig, getLangOpts(),
136+
*Result.SourceManager, *PP);
135137
const Expr *PrintfCall = Printf->getCallee();
136138
const StringRef ReplacementFunction = Converter.usePrintNewlineFunction()
137139
? ReplacementPrintlnFunction

clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class UseStdPrintCheck : public ClangTidyCheck {
3636
}
3737

3838
private:
39+
Preprocessor *PP;
3940
bool StrictMode;
4041
std::vector<StringRef> PrintfLikeFunctions;
4142
std::vector<StringRef> FprintfLikeFunctions;

clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "clang/ASTMatchers/ASTMatchFinder.h"
1919
#include "clang/Basic/LangOptions.h"
2020
#include "clang/Lex/Lexer.h"
21+
#include "clang/Lex/Preprocessor.h"
2122
#include "clang/Tooling/FixIt.h"
2223
#include "llvm/ADT/StringExtras.h"
2324
#include "llvm/Support/Debug.h"
@@ -195,11 +196,10 @@ static bool castMismatchedIntegerTypes(const CallExpr *Call, bool StrictMode) {
195196
return false;
196197
}
197198

198-
FormatStringConverter::FormatStringConverter(ASTContext *ContextIn,
199-
const CallExpr *Call,
200-
unsigned FormatArgOffset,
201-
const Configuration ConfigIn,
202-
const LangOptions &LO)
199+
FormatStringConverter::FormatStringConverter(
200+
ASTContext *ContextIn, const CallExpr *Call, unsigned FormatArgOffset,
201+
const Configuration ConfigIn, const LangOptions &LO, SourceManager &SM,
202+
Preprocessor &PP)
203203
: Context(ContextIn), Config(ConfigIn),
204204
CastMismatchedIntegerTypes(
205205
castMismatchedIntegerTypes(Call, ConfigIn.StrictMode)),
@@ -208,11 +208,22 @@ FormatStringConverter::FormatStringConverter(ASTContext *ContextIn,
208208
assert(ArgsOffset <= NumArgs);
209209
FormatExpr = llvm::dyn_cast<StringLiteral>(
210210
Args[FormatArgOffset]->IgnoreImplicitAsWritten());
211+
211212
if (!FormatExpr || !FormatExpr->isOrdinary()) {
212213
// Function must have a narrow string literal as its first argument.
213214
conversionNotPossible("first argument is not a narrow string literal");
214215
return;
215216
}
217+
218+
if (const std::optional<StringRef> MaybeMacroName =
219+
formatStringContainsUnreplaceableMacro(Call, FormatExpr, SM, PP);
220+
MaybeMacroName) {
221+
conversionNotPossible(
222+
("format string contains unreplaceable macro '" + *MaybeMacroName + "'")
223+
.str());
224+
return;
225+
}
226+
216227
PrintfFormatString = FormatExpr->getString();
217228

218229
// Assume that the output will be approximately the same size as the input,
@@ -230,6 +241,50 @@ FormatStringConverter::FormatStringConverter(ASTContext *ContextIn,
230241
finalizeFormatText();
231242
}
232243

244+
std::optional<StringRef>
245+
FormatStringConverter::formatStringContainsUnreplaceableMacro(
246+
const CallExpr *Call, const StringLiteral *FormatExpr, SourceManager &SM,
247+
Preprocessor &PP) {
248+
// If a macro invocation surrounds the entire call then we don't want that to
249+
// inhibit conversion. The whole format string will appear to come from that
250+
// macro, as will the function call.
251+
std::optional<StringRef> MaybeSurroundingMacroName;
252+
if (SourceLocation BeginCallLoc = Call->getBeginLoc();
253+
BeginCallLoc.isMacroID())
254+
MaybeSurroundingMacroName =
255+
Lexer::getImmediateMacroName(BeginCallLoc, SM, PP.getLangOpts());
256+
257+
for (auto I = FormatExpr->tokloc_begin(), E = FormatExpr->tokloc_end();
258+
I != E; ++I) {
259+
const SourceLocation &TokenLoc = *I;
260+
if (TokenLoc.isMacroID()) {
261+
const StringRef MacroName =
262+
Lexer::getImmediateMacroName(TokenLoc, SM, PP.getLangOpts());
263+
264+
if (MaybeSurroundingMacroName != MacroName) {
265+
// glibc uses __PRI64_PREFIX and __PRIPTR_PREFIX to define the prefixes
266+
// for types that change size so we must look for multiple prefixes.
267+
if (!MacroName.starts_with("PRI") && !MacroName.starts_with("__PRI"))
268+
return MacroName;
269+
270+
const SourceLocation TokenSpellingLoc = SM.getSpellingLoc(TokenLoc);
271+
const OptionalFileEntryRef MaybeFileEntry =
272+
SM.getFileEntryRefForID(SM.getFileID(TokenSpellingLoc));
273+
if (!MaybeFileEntry)
274+
return MacroName;
275+
276+
HeaderSearch &HS = PP.getHeaderSearchInfo();
277+
// Check if the file is a system header
278+
if (!isSystem(HS.getFileDirFlavor(*MaybeFileEntry)) ||
279+
llvm::sys::path::filename(MaybeFileEntry->getName()) !=
280+
"inttypes.h")
281+
return MacroName;
282+
}
283+
}
284+
}
285+
return std::nullopt;
286+
}
287+
233288
void FormatStringConverter::emitAlignment(const PrintfSpecifier &FS,
234289
std::string &FormatSpec) {
235290
ConversionSpecifier::Kind ArgKind = FS.getConversionSpecifier().getKind();

clang-tools-extra/clang-tidy/utils/FormatStringConverter.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ class FormatStringConverter
4040

4141
FormatStringConverter(ASTContext *Context, const CallExpr *Call,
4242
unsigned FormatArgOffset, Configuration Config,
43-
const LangOptions &LO);
43+
const LangOptions &LO, SourceManager &SM,
44+
Preprocessor &PP);
4445

4546
bool canApply() const { return ConversionNotPossibleReason.empty(); }
4647
const std::string &conversionNotPossibleReason() const {
@@ -110,6 +111,10 @@ class FormatStringConverter
110111

111112
void appendFormatText(StringRef Text);
112113
void finalizeFormatText();
114+
static std::optional<StringRef>
115+
formatStringContainsUnreplaceableMacro(const CallExpr *CallExpr,
116+
const StringLiteral *FormatExpr,
117+
SourceManager &SM, Preprocessor &PP);
113118
bool conversionNotPossible(std::string Reason) {
114119
ConversionNotPossibleReason = std::move(Reason);
115120
return false;

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,13 @@ Changes in existing checks
198198

199199
- Improved :doc:`modernize-use-std-format
200200
<clang-tidy/checks/modernize/use-std-format>` check to support replacing
201-
member function calls too.
201+
member function calls too and to only expand macros starting with ``PRI``
202+
and ``__PRI`` from ``<inttypes.h>`` in the format string.
202203

203204
- Improved :doc:`modernize-use-std-print
204205
<clang-tidy/checks/modernize/use-std-print>` check to support replacing
205-
member function calls too.
206+
member function calls too and to only expand macros starting with ``PRI``
207+
and ``__PRI`` from ``<inttypes.h>`` in the format string.
206208

207209
- Improved :doc:`performance-avoid-endl
208210
<clang-tidy/checks/performance/avoid-endl>` check to use ``std::endl`` as

clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-format.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ into:
2323

2424
The check uses the same format-string-conversion algorithm as
2525
`modernize-use-std-print <../modernize/use-std-print.html>`_ and its
26-
shortcomings are described in the documentation for that check.
26+
shortcomings and behaviour in combination with macros are described in the
27+
documentation for that check.
2728

2829
Options
2930
-------

clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,15 @@ into:
2424
std::println(stderr, "The {} is {:3}", description, value);
2525

2626
If the `ReplacementPrintFunction` or `ReplacementPrintlnFunction` options
27-
are left, or assigned to their default values then this check is only
28-
enabled with `-std=c++23` or later.
27+
are left at or set to their default values then this check is only enabled
28+
with `-std=c++23` or later.
29+
30+
Macros starting with ``PRI`` and ``__PRI`` from `<inttypes.h>` are
31+
expanded, escaping is handled and adjacent strings are concatenated to form
32+
a single ``StringLiteral`` before the format string is converted. Use of
33+
any other macros in the format string will cause a warning message to be
34+
emitted and no conversion will be performed. The converted format string
35+
will always be a single string literal.
2936

3037
The check doesn't do a bad job, but it's not perfect. In particular:
3138

@@ -34,13 +41,10 @@ The check doesn't do a bad job, but it's not perfect. In particular:
3441
possible.
3542

3643
- At the point that the check runs, the AST contains a single
37-
``StringLiteral`` for the format string and any macro expansion, token
38-
pasting, adjacent string literal concatenation and escaping has been
39-
handled. Although it's possible for the check to automatically put the
40-
escapes back, they may not be exactly as they were written (e.g.
41-
``"\x0a"`` will become ``"\n"`` and ``"ab" "cd"`` will become
42-
``"abcd"``.) This is helpful since it means that the ``PRIx`` macros from
43-
``<inttypes.h>`` are removed correctly.
44+
``StringLiteral`` for the format string where escapes have been expanded.
45+
The check tries to reconstruct escape sequences, they may not be the same
46+
as they were written (e.g. ``"\x41\x0a"`` will become ``"A\n"`` and
47+
``"ab" "cd"`` will become ``"abcd"``.)
4448

4549
- It supports field widths, precision, positional arguments, leading zeros,
4650
leading ``+``, alignment and alternative forms.

clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/inttypes.h

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,40 +21,46 @@ typedef __UINT32_TYPE__ uint32_t;
2121
typedef __UINT16_TYPE__ uint16_t;
2222
typedef __UINT8_TYPE__ uint8_t;
2323

24-
#define PRIdMAX "lld"
25-
#define PRId64 "lld"
24+
#if __WORDSIZE == 64
25+
# define __PRI64_PREFIX "l"
26+
#else
27+
# define __PRI64_PREFIX "ll"
28+
#endif
29+
30+
#define PRIdMAX __PRI64_PREFIX "d"
31+
#define PRId64 __PRI64_PREFIX "d"
2632
#define PRId32 "d"
2733
#define PRId16 "hd"
2834
#define PRId8 "hhd"
2935

30-
#define PRIiMAX "lli"
31-
#define PRIi64 "lli"
36+
#define PRIiMAX __PRI64_PREFIX "i"
37+
#define PRIi64 __PRI64_PREFIX "i"
3238
#define PRIi32 "i"
3339
#define PRIi16 "hi"
3440
#define PRIi8 "hhi"
3541

36-
#define PRIiFAST64 "lli"
42+
#define PRIiFAST64 __PRI64_PREFIX "i"
3743
#define PRIiFAST32 "i"
3844
#define PRIiFAST16 "hi"
3945
#define PRIiFAST8 "hhi"
4046

41-
#define PRIiLEAST64 "lli"
47+
#define PRIiLEAST64 __PRI64_PREFIX "i"
4248
#define PRIiLEAST32 "i"
4349
#define PRIiLEAST16 "hi"
4450
#define PRIiLEAST8 "hhi"
4551

46-
#define PRIuMAX "llu"
47-
#define PRIu64 "llu"
52+
#define PRIuMAX __PRI64_PREFIX "u"
53+
#define PRIu64 __PRI64_PREFIX "u"
4854
#define PRIu32 "u"
4955
#define PRIu16 "hu"
5056
#define PRIu8 "hhu"
5157

52-
#define PRIuFAST64 "llu"
58+
#define PRIuFAST64 __PRI64_PREFIX "u"
5359
#define PRIuFAST32 "u"
5460
#define PRIuFAST16 "hu"
5561
#define PRIuFAST8 "hhu"
5662

57-
#define PRIuLEAST64 "llu"
63+
#define PRIuLEAST64 __PRI64_PREFIX "u"
5864
#define PRIuLEAST32 "u"
5965
#define PRIuLEAST16 "hu"
6066
#define PRIuLEAST8 "hhu"

clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-format.cpp

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
// RUN: %check_clang_tidy \
22
// RUN: -std=c++20 %s modernize-use-std-format %t -- \
33
// RUN: -config="{CheckOptions: {StrictMode: true}}" \
4-
// RUN: -- -isystem %clang_tidy_headers
4+
// RUN: -- -isystem %clang_tidy_headers \
5+
// RUN: -DPRI_CMDLINE_MACRO="\"s\"" \
6+
// RUN: -D__PRI_CMDLINE_MACRO="\"s\""
57
// RUN: %check_clang_tidy \
68
// RUN: -std=c++20 %s modernize-use-std-format %t -- \
79
// RUN: -config="{CheckOptions: {StrictMode: false}}" \
8-
// RUN: -- -isystem %clang_tidy_headers
10+
// RUN: -- -isystem %clang_tidy_headers \
11+
// RUN: -DPRI_CMDLINE_MACRO="\"s\"" \
12+
// RUN: -D__PRI_CMDLINE_MACRO="\"s\""
913
#include <string>
1014
// CHECK-FIXES: #include <format>
15+
#include <inttypes.h>
1116

1217
namespace absl
1318
{
@@ -102,18 +107,74 @@ std::string StrFormat_macros() {
102107
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
103108
// CHECK-FIXES: std::format("Hello {}", 42);
104109

105-
// The format string is replaced even though it comes from a macro, this
106-
// behaviour is required so that that <inttypes.h> macros are replaced.
107-
#define FORMAT_STRING "Hello %s"
108-
auto s2 = absl::StrFormat(FORMAT_STRING, 42);
109-
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
110-
// CHECK-FIXES: std::format("Hello {}", 42);
111-
112110
// Arguments that are macros aren't replaced with their value, even if they are rearranged.
113111
#define VALUE 3.14159265358979323846
114112
#define WIDTH 10
115113
#define PRECISION 4
116114
auto s3 = absl::StrFormat("Hello %*.*f", WIDTH, PRECISION, VALUE);
117115
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
118116
// CHECK-FIXES: std::format("Hello {:{}.{}f}", VALUE, WIDTH, PRECISION);
117+
118+
const uint64_t u64 = 42;
119+
const uint32_t u32 = 32;
120+
std::string s;
121+
122+
auto s4 = absl::StrFormat("Replaceable macro at end %" PRIu64, u64);
123+
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
124+
// CHECK-FIXES: std::format("Replaceable macro at end {}", u64);
125+
126+
auto s5 = absl::StrFormat("Replaceable macros in middle %" PRIu64 " %" PRIu32 "\n", u64, u32);
127+
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
128+
// CHECK-FIXES: std::format("Replaceable macros in middle {} {}\n", u64, u32);
129+
130+
// These need PRI and __PRI prefixes so that the check get as far as looking for
131+
// where the macro comes from.
132+
#define PRI_FMT_MACRO "s"
133+
#define __PRI_FMT_MACRO "s"
134+
135+
auto s6 = absl::StrFormat("Unreplaceable macro at end %" PRI_FMT_MACRO, s.c_str());
136+
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro 'PRI_FMT_MACRO' [modernize-use-std-format]
137+
138+
auto s7 = absl::StrFormat(__PRI_FMT_MACRO " Unreplaceable macro at beginning %s", s);
139+
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro '__PRI_FMT_MACRO' [modernize-use-std-format]
140+
141+
auto s8 = absl::StrFormat("Unreplacemable macro %" PRI_FMT_MACRO " in the middle", s);
142+
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro 'PRI_FMT_MACRO' [modernize-use-std-format]
143+
144+
auto s9 = absl::StrFormat("First macro is replaceable %" PRIu64 " but second one is not %" __PRI_FMT_MACRO, u64, s);
145+
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro '__PRI_FMT_MACRO' [modernize-use-std-format]
146+
147+
// Needs a PRI prefix so that we get as far as looking for where the macro comes from
148+
auto s10 = absl::StrFormat(" macro from command line %" PRI_CMDLINE_MACRO, s);
149+
// CHECK-MESSAGES: [[@LINE-1]]:14: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro 'PRI_CMDLINE_MACRO' [modernize-use-std-format]
150+
151+
// Needs a __PRI prefix so that we get as far as looking for where the macro comes from
152+
auto s11 = absl::StrFormat(" macro from command line %" __PRI_CMDLINE_MACRO, s);
153+
// CHECK-MESSAGES: [[@LINE-1]]:14: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro '__PRI_CMDLINE_MACRO' [modernize-use-std-format]
154+
155+
// We ought to be able to fix this since the macro surrounds the whole call
156+
// and therefore can't change the format string independently. This is
157+
// required to be able to fix calls inside Catch2 macros for example.
158+
#define SURROUND_ALL(x) x
159+
auto s12 = SURROUND_ALL(absl::StrFormat("Macro surrounding entire invocation %" PRIu64, u64));
160+
// CHECK-MESSAGES: [[@LINE-1]]:27: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
161+
// CHECK-FIXES: auto s12 = SURROUND_ALL(std::format("Macro surrounding entire invocation {}", u64));
162+
163+
// But having that surrounding macro shouldn't stop us ignoring an
164+
// unreplaceable macro elsewhere.
165+
auto s13 = SURROUND_ALL(absl::StrFormat("Macro surrounding entire invocation with unreplaceable macro %" PRI_FMT_MACRO, s));
166+
// CHECK-MESSAGES: [[@LINE-1]]:27: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro 'PRI_FMT_MACRO' [modernize-use-std-format]
167+
168+
// At the moment at least the check will replace occurrences where the
169+
// function name is the result of expanding a macro.
170+
#define SURROUND_FUNCTION_NAME(x) absl:: x
171+
auto s14 = SURROUND_FUNCTION_NAME(StrFormat)("Hello %d", 4442);
172+
// CHECK-MESSAGES: [[@LINE-1]]:14: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
173+
// CHECK-FIXES: auto s14 = std::format("Hello {}", 4442);
174+
175+
// We can't safely fix occurrences where the macro may affect the format
176+
// string differently in different builds.
177+
#define SURROUND_FORMAT(x) "!" x
178+
auto s15 = absl::StrFormat(SURROUND_FORMAT("Hello %d"), 4443);
179+
// CHECK-MESSAGES: [[@LINE-1]]:14: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro 'SURROUND_FORMAT' [modernize-use-std-format]
119180
}

0 commit comments

Comments
 (0)