mikecrowe created this revision. Herald added subscribers: carlosgalvezp, xazax.hun. Herald added a reviewer: njames93. Herald added a project: All. mikecrowe requested review of this revision. Herald added a project: clang-tools-extra. Herald added a subscriber: cfe-commits.
Add FormatStringConverter utility class that is capable of converting printf-style format strings into std::format-style format strings along with recording a set of casts to wrap the arguments as required. Use FormatStringConverter to implement a new clang-tidy check that is capable of converting calls to printf, fprintf, absl::PrintF, absl::FPrintF, or any function configured by an option to calls to std::print or another function configured by an option. In other words, the check turns: fprintf(stderr, "The %s is %3d\n", answer, value); into: std::print(stderr, "The {} is {:3}\n", answer, value); if it can. std::print 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. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D149280 Files: clang-tools-extra/clang-tidy/modernize/CMakeLists.txt clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp clang-tools-extra/clang-tidy/modernize/PrintfToStdPrintCheck.cpp clang-tools-extra/clang-tidy/modernize/PrintfToStdPrintCheck.h clang-tools-extra/clang-tidy/utils/CMakeLists.txt clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp clang-tools-extra/clang-tidy/utils/FormatStringConverter.h clang-tools-extra/docs/ReleaseNotes.rst clang-tools-extra/docs/clang-tidy/checks/list.rst clang-tools-extra/docs/clang-tidy/checks/modernize/printf-to-std-print.rst clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-fmt-print-convert.cpp clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert-absl.cpp clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert-custom.cpp clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert.cpp
Index: clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert.cpp @@ -0,0 +1,1021 @@ +// RUN: %check_clang_tidy -std=c++2b %s modernize-printf-to-std-print %t -- -- -isystem %clang_tidy_headers + +#include <cstdio> +#include <inttypes.h> +#include <string.h> + +void printf_simple() { + printf("Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello"); +} + +void fprintf_simple() { + fprintf(stderr, "Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'fprintf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(stderr, "Hello"); +} + +void std_printf_simple() { + std::printf("std::Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("std::Hello"); +} + +void printf_escape() { + printf("before \t"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("before \t"); + + printf("\n after"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("\n after"); + + printf("before \a after"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("before \a after"); + + printf("Bell\a%dBackspace\bFF%s\fNewline\nCR\rTab\tVT\vEscape\x1b\x07%d", 42, "string", 99); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Bell\a{}Backspace\bFF{}\fNewline\nCR\rTab\tVT\vEscape\x1b\a{}", 42, "string", 99); + + printf("not special \x1b\x01\x7f"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("not special \x1b\x01\x7f"); +} + +void printf_percent() { + printf("before %%"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("before %"); + + printf("%% after"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("% after"); + + printf("before %% after"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("before % after"); + + printf("Hello %% and another %%"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello % and another %"); + + printf("Not a string %%s"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Not a string %s"); +} + +void printf_curlies() { + printf("%d {}", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{} {{[{][{]}}}}", 42); + + printf("{}"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{{[{][{]}}}}"); +} + +void printf_unsupported() { + int pos; + printf("%d %n %d\n", 42, &pos, 72); + // fmt doesn't do the equivalent of %n, so leave the call alone. + + printf("Error %m\n"); + // fmt doesn't support %m. In theory we could insert a strerror(errno) + // parameter (assuming that libc has a thread-safe implementation, which glibc + // does), but that would require keeping track of the input and output + // parameter indices for position arguments too. +} + +void printf_not_string_literal(const char *fmt) { + // We can't convert the format string if it's not a literal + printf(fmt, 42); +} + +void printf_inttypes_ugliness() { + // The one advantage of the checker seeing the token pasted version of the + // format string is that we automatically cope with the horrendously-ugly + // inttypes.h macros! + int64_t u64 = 42; + uintmax_t umax = 4242; + printf("uint64:%" PRId64 " uintmax:%" PRIuMAX "\n", u64, umax); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("uint64:{} uintmax:{}\n", u64, umax); +} + +void printf_raw_string() { + // This one doesn't require the format string to be changed, so it stays intact + printf(R"(First\Second)"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(R"(First\Second)"); + + // This one does require the format string to be changed, so unfortunately it + // gets reformatted as a normal string. + printf(R"(First %d\Second)", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("First {}\\Second", 42); +} + +void printf_integer_d() { + const bool b = true; + // The "d" type is necessary here for compatibility with printf since + // std::print will print booleans as "true" or "false". + printf("Integer %d from bool\n", b); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {:d} from bool\n", b); + + // The "d" type is necessary here for compatibility with printf since + // std::print will print booleans as "true" or "false". + printf("Integer %i from bool\n", b); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {:d} from bool\n", b); + + // The 'd' is always necessary since otherwise the parameter will be formatted as a character + const char c = 'A'; + printf("Integer %d from char\n", c); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {:d} from char\n", c); + + printf("Integer %i from char\n", 'A'); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {:d} from char\n", 'A'); + + const signed char sc = 'A'; + printf("Integer %hhd from signed char\n", sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from signed char\n", sc); + + printf("Integer %hhi from signed char\n", sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from signed char\n", sc); + + const unsigned char uc = 'A'; + printf("Integer %hhd from unsigned char\n", uc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned char\n", static_cast<signed char>(uc)); + + printf("Integer %hhi from signed char\n", sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from signed char\n", sc); + + const short s = 42; + printf("Integer %hd from short\n", s); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from short\n", s); + + printf("Integer %hi from short\n", s); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from short\n", s); + + const unsigned short us = 42U; + printf("Integer %hd from unsigned short\n", us); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned short\n", static_cast<short>(us)); + + printf("Integer %hi from unsigned short\n", us); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned short\n", static_cast<short>(us)); + + const int i = 42; + printf("Integer %d from integer\n", i); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from integer\n", i); + + printf("Integer %i from integer\n", i); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from integer\n", i); + + const unsigned int ui = 42U; + printf("Integer %d from unsigned integer\n", ui); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned integer\n", static_cast<int>(ui)); + + printf("Integer %i from unsigned integer\n", ui); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned integer\n", static_cast<int>(ui)); + + const long l = 42L; + printf("Integer %ld from long\n", l); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from long\n", l); + + printf("Integer %li from long\n", l); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from long\n", l); + + const unsigned long ul = 42UL; + printf("Integer %ld from unsigned long\n", ul); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned long\n", static_cast<long>(ul)); + + printf("Integer %li from unsigned long\n", ul); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned long\n", static_cast<long>(ul)); + + const long long ll = 42LL; + printf("Integer %lld from long long\n", ll); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from long long\n", ll); + + printf("Integer %lli from long long\n", ll); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from long long\n", ll); + + const unsigned long long ull = 42ULL; + printf("Integer %lld from unsigned long long\n", ull); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned long long\n", static_cast<long long>(ull)); + + printf("Integer %lli from unsigned long long\n", ull); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned long long\n", static_cast<long long>(ull)); + + const intmax_t im = 42; + printf("Integer %jd from intmax_t\n", im); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from intmax_t\n", im); + + printf("Integer %ji from intmax_t\n", im); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from intmax_t\n", im); + + // This doesn't currently work since getCorrespondingSignedTypeName() doesn't recognise uintmax_t. + const uintmax_t uim = 42; + printf("Integer %jd from uintmax_t\n", uim); + // CHECK-MESSAGES-NOTYET: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES-NOTYET: std::print("Integer {} from uintmax_t\n", static_cast<intmax_t>(im)); + + printf("Integer %ji from intmax_t\n", uim); + // CHECK-MESSAGES-NOTYET: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES-NOTYET: std::print("Integer {} from uintmax_t\n", static_cast<intmax_t>(im)); + + // We don't have ptrdiff_t here. + const int ai[] = { 0, 1, 2, 3}; + const auto pd = &ai[3] - &ai[0]; + printf("Integer %td from ptrdiff_t\n", pd); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from ptrdiff_t\n", pd); + + printf("Integer %ti from ptrdiff_t\n", pd); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from ptrdiff_t\n", pd); + + // This doesn't currently work since getCorrespondingSignedTypeName() doesn't + // recognize size_t. + const size_t z = 42UL; + printf("Integer %zd from size_t\n", z); + // CHECK-MESSAGES-NOTYET: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES-NOTYET: std::print("Integer {} from size_t\n", z); +} + +void printf_integer_u() +{ + const bool b = true; + // The "d" type is necessary here for compatibility with printf since + // std::print will print booleans as "true" or "false". + printf("Integer %u from bool\n", b); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {:d} from bool\n", b); + + const char c = 'A'; + printf("Unsigned integer %hhu from char\n", c); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Unsigned integer {} from char\n", static_cast<unsigned char>(c)); + + const unsigned char sc = 'A'; + printf("Integer %hhu from signed char\n", sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from signed char\n", sc); + + printf("Integer %u from signed char\n", sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from signed char\n", sc); + + const unsigned char uc = 'A'; + printf("Integer %hhu from unsigned char\n", uc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned char\n", uc); + + printf("Integer %u from unsigned char\n", uc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned char\n", uc); + + const short s = 42; + printf("Integer %hu from short\n", s); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from short\n", static_cast<unsigned short>(s)); + + const unsigned short us = 42U; + printf("Integer %hu from unsigned short\n", us); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned short\n", us); + + const int i = 42; + printf("Integer %u from signed integer\n", i); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from signed integer\n", static_cast<unsigned int>(i)); + + const unsigned int ui = 42U; + printf("Integer %u from unsigned integer\n", ui); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned integer\n", ui); + + const long l = 42L; + printf("Integer %u from signed long\n", l); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from signed long\n", static_cast<unsigned long>(l)); + + const unsigned long ul = 42UL; + printf("Integer %lu from unsigned long\n", ul); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned long\n", ul); + + const long long ll = 42LL; + printf("Integer %llu from long long\n", ll); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from long long\n", static_cast<unsigned long long>(ll)); + + const unsigned long long ull = 42ULL; + printf("Integer %llu from unsigned long long\n", ull); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned long long\n", ull); + + // This doesn't work yet since getCorrespondingUnsignedTypeName doesn't understand intmax_t. + const intmax_t im = 42; + printf("Integer %ju from intmax_t\n", im); + // CHECK-MESSAGES-NOTYET: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES-NOTYET: std::print("Integer {} from uintmax_t\n", uim); + + const uintmax_t uim = 42U; + printf("Integer %ju from uintmax_t\n", uim); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from uintmax_t\n", uim); + + // This can't currently be converted since getCorrespodningUnsignedTypeName + // doesn't understand ptrdiff_t. + const int ai[] = { 0, 1, 2, 3}; + const auto pd = &ai[3] - &ai[0]; + printf("Integer %tu from ptrdiff_t\n", pd); + // CHECK-MESSAGES-NOTYET: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES-NOTYET: std::print("Integer {} from ptrdiff_t\n", pd); + + const size_t z = 42U; + printf("%zu\n", z); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", z); +} + +// This checks that we get the argument offset right with the extra FILE * argument +void fprintf_integer() { + fprintf(stderr, "Integer %d from integer\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'fprintf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(stderr, "Integer {} from integer\n", 42); + + fprintf(stderr, "Integer %i from integer\n", 65); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'fprintf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(stderr, "Integer {} from integer\n", 65); + + fprintf(stderr, "Integer %i from char\n", 'A'); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'fprintf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(stderr, "Integer {:d} from char\n", 'A'); + + fprintf(stderr, "Integer %d from char\n", 'A'); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'fprintf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(stderr, "Integer {:d} from char\n", 'A'); +} + +void printf_char() { + const char c = 'A'; + printf("Char %c from char\n", c); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Char {} from char\n", c); + + const signed char sc = 'A'; + printf("Char %c from signed char\n", sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Char {:c} from signed char\n", sc); + + const unsigned char uc = 'A'; + printf("Char %c from unsigned char\n", uc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Char {:c} from unsigned char\n", uc); + + const int i = 65; + printf("Char %c from integer\n", i); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Char {:c} from integer\n", i); + + const unsigned int ui = 65; + printf("Char %c from unsigned integer\n", ui); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Char {:c} from unsigned integer\n", ui); + + const unsigned long long ull = 65; + printf("Char %c from unsigned long long\n", ull); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Char {:c} from unsigned long long\n", ull); +} + +void printf_bases() { + printf("Hex %lx\n", 42L); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hex {:x}\n", 42L); + + printf("HEX %X\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("HEX {:X}\n", 42); + + printf("Oct %lo\n", 42L); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Oct {:o}\n", 42L); +} + +void printf_alternative_forms() { + printf("Hex %#lx\n", 42L); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hex {:#x}\n", 42L); + + printf("HEX %#X\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("HEX {:#X}\n", 42); + + printf("Oct %#lo\n", 42L); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Oct {:#o}\n", 42L); + + printf("Double %#f %#F\n", -42.0, -42.0); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Double {:#f} {:#F}\n", -42.0, -42.0); + + printf("Double %#g %#G\n", -42.0, -42.0); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Double {:#g} {:#G}\n", -42.0, -42.0); + + printf("Double %#e %#E\n", -42.0, -42.0); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Double {:#e} {:#E}\n", -42.0, -42.0); + + printf("Double %#a %#A\n", -42.0, -42.0); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Double {:#a} {:#A}\n", -42.0, -42.0); + + // Characters don't have an alternate form + printf("Char %#c\n", 'A'); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Char {}\n", 'A'); + + // Strings don't have an alternate form + printf("Char %#c\n", 'A'); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Char {}\n", 'A'); +} + +void printf_string() { + printf("Hello %s after\n", "Goodbye"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {} after\n", "Goodbye"); + + // std::print can't print signed char strings. + const signed char *sstring = reinterpret_cast<const signed char *>("ustring"); + printf("signed char string %s\n", sstring); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("signed char string {}\n", reinterpret_cast<const char *>(sstring)); + + // std::print can't print unsigned char strings. + const unsigned char *ustring = reinterpret_cast<const unsigned char *>("ustring"); + printf("unsigned char string %s\n", ustring); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("unsigned char string {}\n", reinterpret_cast<const char *>(ustring)); +} + +void printf_float() { + // If the type is not specified then either f or e will be used depending on + // whichever is shorter. This means that it is necessary to be specific to + // maintain compatibility with printf. + + // TODO: Should we force a cast here, since printf will promote to double + // automatically, but std::format will not, which could result in different + // output? + + const float f = 42.0F; + printf("Hello %f after\n", f); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:f} after\n", f); + + printf("Hello %g after\n", f); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:g} after\n", f); + + printf("Hello %e after\n", f); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:e} after\n", f); +} + +void printf_double() { + // If the type is not specified then either f or e will be used depending on + // whichever is shorter. This means that it is necessary to be specific to + // maintain compatibility with printf. + + const double d = 42.0; + printf("Hello %f after\n", d); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:f} after\n", d); + + printf("Hello %g after\n", d); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:g} after\n", d); + + printf("Hello %e after\n", d); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:e} after\n", d); +} + +void printf_long_double() { + // If the type is not specified then either f or e will be used depending on + // whichever is shorter. This means that it is necessary to be specific to + // maintain compatibility with printf. + + const long double ld = 42.0L; + printf("Hello %Lf after\n", ld); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:f} after\n", ld); + + printf("Hello %g after\n", ld); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:g} after\n", ld); + + printf("Hello %e after\n", ld); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:e} after\n", ld); +} + +void printf_pointer() { + int i; + double j; + printf("Int* %p %s %p\n", &i, "Double*", &j); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Int* {} {} {}\n", reinterpret_cast<const void *>(&i), "Double*", reinterpret_cast<const void *>(&j)); + + printf("%p\n", nullptr); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", nullptr); + + const auto np = nullptr; + printf("%p\n", np); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", np); + + // NULL isn't a pointer, so std::print needs some help. + printf("%p\n", NULL); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", reinterpret_cast<const void *>(NULL)); + + printf("%p\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", reinterpret_cast<const void *>(42)); + + // If we already have a void pointer then no cast is required. + printf("%p\n", reinterpret_cast<const void *>(44)); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", reinterpret_cast<const void *>(44)); + + const void *p; + printf("%p\n", p); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", p); + + // But a pointer to a pointer to void does need a cast + const void **pp; + printf("%p\n", pp); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", reinterpret_cast<const void *>(pp)); + + printf("%p\n", printf_pointer); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", reinterpret_cast<const void *>(printf_pointer)); +} + +class AClass +{ + void printf_this_pointer() + { + printf("%p\n", this); + // CHECK-MESSAGES: [[@LINE-1]]:5: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", reinterpret_cast<const void *>(this)); + } +}; + +void printf_positional_arg() { + printf("%1$d", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{0}", 42); + + printf("before %1$d", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("before {0}", 42); + + printf("%1$d after", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{0} after", 42); + + printf("before %1$d after", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("before {0} after", 42); + + printf("before %2$d between %1$s after", "string", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("before {1} between {0} after", "string", 42); +} + +// printf always defaults to right justification,, no matter what the type is of +// the argument. std::format uses left justification by default for strings, and +// right justification for numbers. +void printf_right_justified() { + printf("Right-justified integer %4d after\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Right-justified integer {:4} after\n", 42); + + printf("Right-justified double %4f\n", 227.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Right-justified double {:4f}\n", 227.2); + + printf("Right-justified double %4g\n", 227.4); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Right-justified double {:4g}\n", 227.4); + + printf("Right-justified integer with field width argument %*d after\n", 5, 424242); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Right-justified integer with field width argument {:{}} after\n", 5, 424242); + + printf("Right-justified integer with field width argument %2$*1$d after\n", 5, 424242); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Right-justified integer with field width argument {1:{0}} after\n", 5, 424242); + + printf("Right-justified string %20s\n", "Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Right-justified string {:>20}\n", "Hello"); + + printf("Right-justified string with field width argument %2$*1$s after\n", 20, "wibble"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Right-justified string with field width argument {1:>{0}} after\n", 20, "wibble"); +} + +// printf always requires - for left justification, no matter what the type is +// of the argument. std::format uses left justification by default for strings, +// and right justification for numbers. +void printf_left_justified() { + printf("Left-justified integer %-4d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Left-justified integer {:<4}\n", 42); + + printf("Left-justified integer %--4d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Left-justified integer {:<4}\n", 42); + + printf("Left-justified double %-4f\n", 227.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Left-justified double {:<4f}\n", 227.2); + + printf("Left-justified double %-4g\n", 227.4); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Left-justified double {:<4g}\n", 227.4); + + printf("Left-justified integer with field width argument %-*d after\n", 5, 424242); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Left-justified integer with field width argument {:<{}} after\n", 5, 424242); + + printf("Left-justified integer with field width argument %2$-*1$d after\n", 5, 424242); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Left-justified integer with field width argument {1:<{0}} after\n", 5, 424242); + + printf("Left-justified string %-20s\n", "Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Left-justified string {:20}\n", "Hello"); + + printf("Left-justified string with field width argument %2$-*1$s after\n", 5, "wibble"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Left-justified string with field width argument {1:{0}} after\n", 5, "wibble"); +} + +void printf_precision() { + printf("Hello %.3f\n", 3.14159); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:.3f}\n", 3.14159); + + printf("Hello %10.3f\n", 3.14159); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:10.3f}\n", 3.14159); + + printf("Hello %.*f after\n", 10, 3.14159265358979323846); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:.{}f} after\n", 10, 3.14159265358979323846); + + printf("Hello %10.*f after\n", 3, 3.14159265358979323846); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:10.{}f} after\n", 3, 3.14159265358979323846); + + printf("Hello %*.*f after\n", 10, 4, 3.14159265358979323846); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:{}.{}f} after\n", 10, 4, 3.14159265358979323846); + + printf("Hello %1$.*2$f after\n", 3.14159265358979323846, 4); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {0:.{1}f} after\n", 3.14159265358979323846, 4); + + // Precision is ignored, but maintained on non-numeric arguments + printf("Hello %.5s\n", "Goodbye"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:.5}\n", "Goodbye"); + + printf("Hello %.5c\n", 'G'); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:.5}\n", 'G'); +} + +void printf_field_width_and_precision() { + printf("Hello %1$*2$.*3$f after\n", 3.14159265358979323846, 4, 2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {0:{1}.{2}f} after\n", 3.14159265358979323846, 4, 2); +} + +void printf_alternative_form() { + printf("Wibble %#x\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Wibble {:#x}\n", 42); + + printf("Wibble %#20x\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Wibble {:#20x}\n", 42); + + printf("Wibble %#020x\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Wibble {:#020x}\n", 42); + + printf("Wibble %#-20x\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Wibble {:<#20x}\n", 42); +} + +void printf_leading_plus() { + printf("Positive integer %+d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Positive integer {:+}\n", 42); + + printf("Positive double %+f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Positive double {:+f}\n", 42.2); + + printf("Positive double %+g\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Positive double {:+g}\n", 42.2); + + // Ignore leading plus on strings to avoid potential runtime exception where + // printf would have just ignored it. + printf("Positive string %+s\n", "string"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Positive string {}\n", "string"); +} + +void printf_leading_space() { + printf("Spaced integer % d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Spaced integer {: }\n", 42); + + printf("Spaced integer %- d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Spaced integer {: }\n", 42); + + printf("Spaced double % f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Spaced double {: f}\n", 42.2); + + printf("Spaced double % g\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Spaced double {: g}\n", 42.2); +} + +void printf_leading_zero() { + printf("Leading zero integer %03d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Leading zero integer {:03}\n", 42); + + printf("Leading minus and zero integer %-03d minus ignored\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Leading minus and zero integer {:<03} minus ignored\n", 42); + + printf("Leading zero unsigned integer %03u\n", 42U); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Leading zero unsigned integer {:03}\n", 42U); + + printf("Leading zero double %03f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Leading zero double {:03f}\n", 42.2); + + printf("Leading zero double %03g\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Leading zero double {:03g}\n", 42.2); +} + +void printf_leading_plus_and_space() { + // printf prefers plus to space. {fmt} will throw if both are present. + printf("Spaced integer % +d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Spaced integer {:+}\n", 42); + + printf("Spaced double %+ f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Spaced double {:+f}\n", 42.2); + + printf("Spaced double % +g\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Spaced double {:+g}\n", 42.2); +} + +void printf_leading_zero_and_plus() { + printf("Leading zero integer %+03d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Leading zero integer {:+03}\n", 42); + + printf("Leading zero double %0+3f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Leading zero double {:+03f}\n", 42.2); + + printf("Leading zero double %0+3g\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Leading zero double {:+03g}\n", 42.2); +} + +void printf_leading_zero_and_space() { + printf("Leading zero and space integer %0 3d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Leading zero and space integer {: 03}\n", 42); + + printf("Leading zero and space double %0 3f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Leading zero and space double {: 03f}\n", 42.2); + + printf("Leading zero and space double %0 3g\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Leading zero and space double {: 03g}\n", 42.2); +} + +// add signed plained enum too +enum PlainEnum { red }; +enum SignedPlainEnum { black = -42 }; +enum BoolEnum : unsigned int { yellow }; +enum CharEnum : char { purple }; +enum SCharEnum : signed char { aquamarine }; +enum UCharEnum : unsigned char { pink }; +enum ShortEnum : short { beige }; +enum UShortEnum : unsigned short { grey }; +enum IntEnum : int { green }; +enum UIntEnum : unsigned int { blue }; +enum LongEnum : long { magenta }; +enum ULongEnum : unsigned long { cyan }; +enum LongLongEnum : long long { taupe }; +enum ULongLongEnum : unsigned long long { brown }; + +void printf_enum_d() { + PlainEnum plain_enum; + printf("%d", plain_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<int>(plain_enum)); + + SignedPlainEnum splain_enum; + printf("%d", splain_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<int>(splain_enum)); + + BoolEnum bool_enum; + printf("%d", bool_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<int>(bool_enum)); + + CharEnum char_enum; + printf("%d", char_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<signed char>(char_enum)); + + SCharEnum schar_enum; + printf("%d", schar_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<signed char>(schar_enum)); + + UCharEnum uchar_enum; + printf("%d", uchar_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<signed char>(uchar_enum)); + + ShortEnum short_enum; + printf("%d", short_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<short>(short_enum)); + + UShortEnum ushort_enum; + printf("%d", ushort_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<short>(ushort_enum)); + + IntEnum int_enum; + printf("%d", int_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<int>(int_enum)); + + UIntEnum uint_enum; + printf("%d", uint_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<int>(uint_enum)); + + LongEnum long_enum; + printf("%d", long_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<long>(long_enum)); + + ULongEnum ulong_enum; + printf("%d", ulong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<long>(ulong_enum)); + + LongLongEnum longlong_enum; + printf("%d", longlong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<long long>(longlong_enum)); + + ULongLongEnum ulonglong_enum; + printf("%d", ulonglong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<long long>(ulonglong_enum)); +} + +void printf_enum_u() { + PlainEnum plain_enum; + printf("%u", plain_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(plain_enum)); + + SignedPlainEnum splain_enum; + printf("%u", splain_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(splain_enum)); + + BoolEnum bool_enum; + printf("%u", bool_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(bool_enum)); + + CharEnum char_enum; + printf("%u", char_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned char>(char_enum)); + + SCharEnum schar_enum; + printf("%u", schar_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned char>(schar_enum)); + + UCharEnum uchar_enum; + printf("%u", uchar_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned char>(uchar_enum)); + + ShortEnum short_enum; + printf("%u", short_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned short>(short_enum)); + + UShortEnum ushort_enum; + printf("%u", ushort_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned short>(ushort_enum)); + + IntEnum int_enum; + printf("%u", int_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(int_enum)); + + UIntEnum uint_enum; + printf("%u", uint_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(uint_enum)); + + LongEnum long_enum; + printf("%u", long_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned long>(long_enum)); + + ULongEnum ulong_enum; + printf("%u", ulong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned long>(ulong_enum)); + + LongLongEnum longlong_enum; + printf("%u", longlong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned long long>(longlong_enum)); + + ULongLongEnum ulonglong_enum; + printf("%u", ulonglong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast<unsigned long long>(ulonglong_enum)); +} Index: clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert-custom.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert-custom.cpp @@ -0,0 +1,56 @@ +// RUN: %check_clang_tidy -std=c++2b %s modernize-printf-to-std-print %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: [ \ +// RUN: { \ +// RUN: key: modernize-printf-to-std-print.PrintfLikeFunctions, \ +// RUN: value: '::myprintf; mynamespace::myprintf2' \ +// RUN: }, \ +// RUN: { \ +// RUN: key: modernize-printf-to-std-print.FprintfLikeFunctions, \ +// RUN: value: '::myfprintf; mynamespace::myfprintf2' \ +// RUN: } \ +// RUN: ] \ +// RUN: }" \ +// RUN: -- -isystem %clang_tidy_headers + +#include <cstdio> +#include <string.h> + +int myprintf(const char *, ...); +int myfprintf(FILE *fp, const char *, ...); + +namespace mynamespace { +int myprintf2(const char *, ...); +int myfprintf2(FILE *fp, const char *, ...); +} + +void printf_simple() { + myprintf("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'myprintf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {} {}\n", "world", 42); + + mynamespace::myprintf2("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'myprintf2' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {} {}\n", "world", 42); + + using mynamespace::myprintf2; + myprintf2("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'myprintf2' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {} {}\n", "world", 42); +} + +void fprintf_simple(FILE *fp) +{ + myfprintf(stderr, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'myfprintf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(stderr, "Hello {} {}\n", "world", 42); + + mynamespace::myfprintf2(stderr, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'myfprintf2' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(stderr, "Hello {} {}\n", "world", 42); + + using mynamespace::myfprintf2; + myfprintf2(stderr, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'myfprintf2' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(stderr, "Hello {} {}\n", "world", 42); +} Index: clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert-absl.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert-absl.cpp @@ -0,0 +1,36 @@ +// RUN: %check_clang_tidy -std=c++2b %s modernize-printf-to-std-print %t -- -- -isystem %clang_tidy_headers + +#include <cstdio> +#include <string.h> + +namespace absl +{ +// Use const char * for the format since the real type is hard to mock up. +template <typename... Args> +int PrintF(const char *format, const Args&... args); + +template <typename... Args> +int FPrintF(FILE* output, const char *format, const Args&... args); +} + +void printf_simple() { + absl::PrintF("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'PrintF' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {} {}\n", "world", 42); + + using namespace absl; + PrintF("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'PrintF' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {} {}\n", "world", 42); +} + +void fprintf_simple(FILE *fp) { + absl::FPrintF(fp, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'FPrintF' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(fp, "Hello {} {}\n", "world", 42); + + using namespace absl; + FPrintF(fp, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'FPrintF' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(fp, "Hello {} {}\n", "world", 42); +} Index: clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-fmt-print-convert.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-fmt-print-convert.cpp @@ -0,0 +1,21 @@ +// RUN: %check_clang_tidy %s modernize-printf-to-std-print %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: [{key: modernize-printf-to-std-print.PrintFunction, \ +// RUN: value: 'fmt::print'}] \ +// RUN: }" \ +// RUN: -- -isystem %clang_tidy_headers + +#include <cstdio> +#include <string.h> + +void printf_simple() { + printf("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'fmt::print' [modernize-printf-to-std-print] + // CHECK-FIXES: fmt::print("Hello {} {}\n", "world", 42); +} + +void fprintf_simple(FILE *fp) { + fprintf(fp, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'fprintf' with 'fmt::print' [modernize-printf-to-std-print] + // CHECK-FIXES: fmt::print(fp, "Hello {} {}\n", "world", 42); +} Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h =================================================================== --- clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h +++ clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h @@ -12,7 +12,13 @@ // A header intended to contain C standard input and output library // declarations. +typedef struct structFILE {} FILE; +extern FILE *stderr; + int printf(const char *, ...); +int fprintf(FILE *, const char *, ...); + +#define NULL (0) #endif // _STDIO_H_ Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio =================================================================== --- clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio +++ clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio @@ -1,4 +1,4 @@ -//===--- stdio.h - Stub header for tests ------------------------*- C++ -*-===// +//===--- cstdio - Stub header for tests -------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,13 +6,16 @@ // //===----------------------------------------------------------------------===// -#ifndef _STDIO_H_ -#define _STDIO_H_ +#ifndef _STDIO_ +#define _STDIO_ -// A header intended to contain C standard input and output library -// declarations. +#include <stdio.h> -int printf(const char *, ...); +namespace std { + using ::FILE; + using ::printf; + using ::fprintf; +} -#endif // _STDIO_H_ +#endif // _STDIO_ Index: clang-tools-extra/docs/clang-tidy/checks/modernize/printf-to-std-print.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/modernize/printf-to-std-print.rst @@ -0,0 +1,94 @@ +.. title:: clang-tidy - modernize-printf-to-std-print + +modernize-printf-to-std-print +============================= + +This check is capable of converting calls to printf to calls to std::print +whilst also modifying the format string appropriately. + +In other words, it turns lines like:: + + fprintf(stderr, "The %s is %3d\n", answer, value); + +into:: + + std::print(stderr, "The {} is {:3}\n", answer, value); + +and works for ``printf``, ``fprintf``, ``absl::PrintF``, ``absl::FPrintf`` +and any functions specified via the ``PrintfLikeFunctions`` and +``FprintfLikeFunctions`` options. + +It doesn't do a bad job, but it's not perfect. In particular: + +- It assumes that the input is mostly sane. If you get any warnings when + compiling with ``-Wformat`` then misbehaviour is possible. + +- At the point that the check runs, the AST contains a single + ``StringLiteral`` for the format string and any macro expansion, token + pasting, adjacent string literal concatenation and escaping has been + handled. Although it's possible for the check to automatically put the + escapes back, they may not be exactly as they were written (e.g. + ``"\x0a"`` will become ``"\n"`` and ``"ab" "cd"`` will become + ``"abcd"``.) This is helpful since it means that the PRIx macros from + ``<inttypes.h>`` are removed correctly. + +- It supports field widths, precision, positional arguments, leading zeros, + leading +, alignment and alternative forms. + +- It is assumed that the ``<print>`` (or other appropriate) header has + already been included. No attempt is made to include it. + +- Use of any unsupported flags or specifiers will cause the entire + statement to be left alone. Particular unsupported features are: + + - The ``%`` flag for thousands separators. + + - The glibc extension ``%m``. + +If conversion would be incomplete or unsafe then the entire invocation will +be left unchanged. + +If the call is deemed suitable for conversion then: + +- ``printf``, ``fprintf``, ``absl::PrintF``, ``absl::FPrintF`` and any + functions specified by the ``PrintfLikeFunctions`` option or + ``FprintfLikeFunctions`` are replaced with the function specified by the + ``PrintFunction`` option. +- the format string is rewritten to use the ``std::formatter`` language. +- any arguments that corresponded to ``%p`` specifiers that std::formatter + wouldn't accept are wrapped in a ``reinterpret_cast`` to ``const void *``. +- any arguments where the format string and the parameter differ in + signedness will be wrapped in an approprate ``static_cast``. For example, + given ``int i = 42``, then ``printf("%u\n", i)`` will become + ``std::printf("{}\n", static_cast<unsigned int>(i))``. + +Options +------- + +.. option:: PrintfLikeFunctions + + A semicolon-separated list of (fully qualified) function/method/operator + names, with the requirement that the first parameter contains the + printf-style format string and the arguments to be formatted follow + immediately afterwards. + +.. option:: FprintfLikeFunctions + + A semicolon-separated list of (fully qualified) function/method/operator + names, with the requirement that the first parameter is retained, the + second parameter contains the printf-style format string and the + arguments to be formatted follow immediately afterwards. + +.. option:: PrintFunction + + The function that will be used to replace ``printf``, ``fprintf`` etc. + during conversion rather than the default ``std::print``. It is expected + that the function provides an interface that is compatible with + std::print. A suitable candidate would be ``fmt::print``. + +Companion checks +---------------- + +It is helpful to use the `readability-redundant-string-cstr +<../readability/redundant-string-cstr.html>` check after this check to +remove now-unnecessary calls to ``std::string::c_str()``. Index: clang-tools-extra/docs/clang-tidy/checks/list.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/list.rst +++ clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -278,6 +278,7 @@ `modernize-make-shared <modernize/make-shared.html>`_, "Yes" `modernize-make-unique <modernize/make-unique.html>`_, "Yes" `modernize-pass-by-value <modernize/pass-by-value.html>`_, "Yes" + `modernize-printf-to-std-print <modernize/printf-to-std-print>`_, "Yes" `modernize-raw-string-literal <modernize/raw-string-literal.html>`_, "Yes" `modernize-redundant-void-arg <modernize/redundant-void-arg.html>`_, "Yes" `modernize-replace-auto-ptr <modernize/replace-auto-ptr.html>`_, "Yes" Index: clang-tools-extra/docs/ReleaseNotes.rst =================================================================== --- clang-tools-extra/docs/ReleaseNotes.rst +++ clang-tools-extra/docs/ReleaseNotes.rst @@ -168,6 +168,14 @@ Enforces consistent token representation for invoked binary, unary and overloaded operators in C++ code. +- New :doc: `modernize-printf-to-std-print + <clang-tidy/checks/modernize/printf-to-std-print>` check. + + Converts calls to ``printf``, ``fprintf``, ``absl::PrintF``, + ``absl::FPrintf`` or other functions via a configuration option, to + equivalent calls to C++23's ``std::print`` or another function via a + configuration option, modifying the format string appropriately. + New check aliases ^^^^^^^^^^^^^^^^^ Index: clang-tools-extra/clang-tidy/utils/FormatStringConverter.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/utils/FormatStringConverter.h @@ -0,0 +1,66 @@ +//===--- FormatStringConverter.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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Declaration of the FormatStringConverter class which is used to convert +/// printf format strings to C++ std::formatter format strings. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FORMATSTRINGCONVERTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FORMATSTRINGCONVERTER_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/FormatString.h" +#include "llvm/ADT/Optional.h" +#include <string> + +namespace clang::tidy::utils { + +/// Convert a printf-style format string to a std::formatter-style one, and +/// prepare any casts that are required to wrap the arguments to retain printf +/// compatibility. This class is expecting to work on the already-cooked format +/// string (i.e. all the escapes have been converted) so we have to convert them +/// back. This means that we might not convert them back using the same form. +class FormatStringConverter + : public clang::analyze_format_string::FormatStringHandler { + const ASTContext *Context; + bool ConversionPossible = true; + bool FormatStringNeededRewriting = false; + size_t PrintfFormatStringPos = 0U; + StringRef PrintfFormatString; + + const Expr *const *Args; + const unsigned NumArgs; + unsigned ArgsOffset; + const LangOptions &LangOpts; + + const StringLiteral *FormatExpr; + std::string StandardFormatString; + + /// Casts to be used to wrap arguments to retain printf compatibility. + std::vector<std::tuple<const Expr *, std::string>> ArgFixes; + + virtual bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS, + const char *startSpecifier, + unsigned specifierLen, + const TargetInfo &Target) override; + void appendFormatText(StringRef Text); + void finalizeFormatText(); + +public: + FormatStringConverter(const ASTContext *Context, const CallExpr *Call, + unsigned FormatArgOffset, const LangOptions &LO); + + bool canApply() const { return ConversionPossible; } + void applyFixes(DiagnosticBuilder &Diag, SourceManager &SM); +}; + +} // namespace clang::tidy::utils + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FORMATSTRINGCONVERTER_H Index: clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp @@ -0,0 +1,535 @@ +//===--- FormatStringConverter.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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Implementation of the FormatStringConverter class which is used to convert +/// printf format strings to C++ std::formatter format strings. +/// +//===----------------------------------------------------------------------===// + +#include "FormatStringConverter.h" +#include "clang/AST/Expr.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Debug.h" + +namespace { +/// Is the passed type the actual "char" type, whether that be signed or +/// unsigned, rather than explicit signed char or unsigned char types. +bool isRealCharType(const clang::QualType &Ty) { + using namespace clang; + if (const auto *BT = llvm::dyn_cast<BuiltinType>(Ty)) { + const bool result = (BT->getKind() == BuiltinType::Char_U || + BT->getKind() == BuiltinType::Char_S); + return result; + } else { + return false; + } +} + +/// If possible, return the text name of the signed type that corresponds to the +/// passed integer type. If the passed type is already signed then its name is +/// just returned. Only supports BuiltinTypes. +std::optional<llvm::StringRef> +getCorrespondingSignedTypeName(const clang::QualType &QT) { + using namespace clang; + if (const auto *BT = llvm::dyn_cast<BuiltinType>(QT)) { + switch (BT->getKind()) { + case BuiltinType::UChar: + case BuiltinType::Char_U: + case BuiltinType::SChar: + case BuiltinType::Char_S: + return "signed char"; + case BuiltinType::UShort: + case BuiltinType::Short: + return "short"; + case BuiltinType::UInt: + case BuiltinType::Int: + return "int"; + case BuiltinType::ULong: + case BuiltinType::Long: + return "long"; + case BuiltinType::ULongLong: + case BuiltinType::LongLong: + return "long long"; + default: + llvm::dbgs() << "Unknown corresponding signed type for BuiltinType '" + << QT.getAsString() << "'\n"; + return std::nullopt; + } + } else { + llvm::dbgs() << "Unknown corresponding signed type for non-BuiltinType '" + << QT.getAsString() << "'\n"; + return std::nullopt; + } +} + +/// If possible, return the text name of the unsigned type that corresponds to +/// the passed integer type. If the passed type is already unsigned then its +/// name is just returned. Only supports BuiltinTypes. +std::optional<llvm::StringRef> +getCorrespondingUnsignedTypeName(const clang::QualType &QT) { + using namespace clang; + if (const auto *BT = llvm::dyn_cast<BuiltinType>(QT)) { + switch (BT->getKind()) { + case BuiltinType::SChar: + case BuiltinType::Char_S: + case BuiltinType::UChar: + case BuiltinType::Char_U: + return "unsigned char"; + case BuiltinType::Short: + case BuiltinType::UShort: + return "unsigned short"; + case BuiltinType::Int: + case BuiltinType::UInt: + return "unsigned int"; + case BuiltinType::Long: + case BuiltinType::ULong: + return "unsigned long"; + case BuiltinType::LongLong: + case BuiltinType::ULongLong: + return "unsigned long long"; + default: + llvm::dbgs() << "Unknown corresponding unsigned type for BuiltinType '" + << QT.getAsString() << "'\n"; + return std::nullopt; + } + } else { + llvm::dbgs() << "Unknown corresponding unsigned type for non-BuiltinType '" + << QT.getAsString() << "'\n"; + return std::nullopt; + } +} +} // namespace + +namespace clang::tidy::utils { + +FormatStringConverter::FormatStringConverter(const ASTContext *ContextIn, + const CallExpr *Call, + unsigned FormatArgOffset, + const LangOptions &LO) + : Context(ContextIn), Args(Call->getArgs()), NumArgs(Call->getNumArgs()), + ArgsOffset(FormatArgOffset + 1), LangOpts(LO) { + assert(ArgsOffset <= NumArgs); + FormatExpr = llvm::dyn_cast<StringLiteral>( + Args[FormatArgOffset]->IgnoreImplicitAsWritten()); + assert(FormatExpr); + PrintfFormatString = FormatExpr->getString(); + + // Assume that the output will be approximately the same size as the input, + // but perhaps with a few escapes expanded. + StandardFormatString.reserve(PrintfFormatString.size() + 8); + StandardFormatString.push_back('\"'); + + const bool IsFreeBsdkPrintf = false; + + using clang::analyze_format_string::ParsePrintfString; + ParsePrintfString(*this, PrintfFormatString.data(), + PrintfFormatString.data() + PrintfFormatString.size(), + LangOpts, Context->getTargetInfo(), IsFreeBsdkPrintf); +} + +/// Called for each format specifier by ParsePrintfString. +bool FormatStringConverter::HandlePrintfSpecifier( + const analyze_printf::PrintfSpecifier &FS, const char *StartSpecifier, + unsigned SpecifierLen, const TargetInfo &Target) { + using namespace analyze_printf; + + const size_t StartSpecifierPos = StartSpecifier - PrintfFormatString.data(); + assert(StartSpecifierPos + SpecifierLen <= PrintfFormatString.size()); + + // Everything before the specifier needs copying verbatim + assert(StartSpecifierPos >= PrintfFormatStringPos); + + appendFormatText(StringRef(PrintfFormatString.begin() + PrintfFormatStringPos, + StartSpecifierPos - PrintfFormatStringPos)); + + using analyze_format_string::ConversionSpecifier; + const ConversionSpecifier Spec = FS.getConversionSpecifier(); + + if (Spec.getKind() == ConversionSpecifier::PercentArg) + StandardFormatString.push_back('%'); + else if (Spec.getKind() == ConversionSpecifier::Kind::nArg) { + // std::format doesn't do the equivalent of %n + ConversionPossible = false; + return false; + } else if (Spec.getKind() == ConversionSpecifier::Kind::PrintErrno) { + // std::format doesn't support %m. In theory we could insert a + // strerror(errno) parameter (assuming that libc has a thread-safe + // implementation, which glibc does), but that would require keeping track + // of the input and output parameter indices for position arguments too. + ConversionPossible = false; + return false; + } else { + StandardFormatString.push_back('{'); + + if (FS.usesPositionalArg()) { + // std::format argument identifiers are zero-based, whereas printf ones + // are one based. + assert(FS.getPositionalArgIndex() > 0U); + StandardFormatString.append(llvm::utostr(FS.getPositionalArgIndex() - 1)); + } + + // std::format format argument: + // [[fill]align][sign]["#"]["0"][width]["."precision][type] + std::string FormatSpec; + + // printf doesn't support specifying the fill character - it's always a + // space, so we never need to generate one. + + // Convert alignment + { + // We only care about alignment if a field width is specified + if (FS.getFieldWidth().getHowSpecified() != + OptionalAmount::NotSpecified) { + const ConversionSpecifier Spec = FS.getConversionSpecifier(); + if (Spec.getKind() == ConversionSpecifier::sArg) { + // Strings are left-aligned by default with std::format, so we only + // need to emit an alignment if this one needs to be right aligned. + if (!FS.isLeftJustified()) + FormatSpec.push_back('>'); + } else { + // Numbers are right-aligned by default with std::format, so we only + // need to emit an alignment if this one needs to be left aligned. + if (FS.isLeftJustified()) + FormatSpec.push_back('<'); + } + } + } + + // Convert sign + { + const ConversionSpecifier Spec = FS.getConversionSpecifier(); + // Ignore on something that isn't numeric. For printf it's would be a + // compile-time warning but ignored at runtime, but for std::format it + // ought to be a compile-time error. + if (Spec.isAnyIntArg() || Spec.isDoubleArg()) { + // + is preferred to ' ' + if (FS.hasPlusPrefix()) + FormatSpec.push_back('+'); + else if (FS.hasSpacePrefix()) + FormatSpec.push_back(' '); + } + } + + // Convert alternative form + if (FS.hasAlternativeForm()) { + switch (Spec.getKind()) { + case ConversionSpecifier::Kind::aArg: + case ConversionSpecifier::Kind::AArg: + case ConversionSpecifier::Kind::eArg: + case ConversionSpecifier::Kind::EArg: + case ConversionSpecifier::Kind::fArg: + case ConversionSpecifier::Kind::FArg: + case ConversionSpecifier::Kind::gArg: + case ConversionSpecifier::Kind::GArg: + case ConversionSpecifier::Kind::xArg: + case ConversionSpecifier::Kind::XArg: + case ConversionSpecifier::Kind::oArg: + FormatSpec.push_back('#'); + break; + default: + // Alternative forms don't exist for other argument kinds + break; + } + } + + // Convert leading zero + if (FS.hasLeadingZeros()) + FormatSpec.push_back('0'); + + // Convert field width + { + const OptionalAmount FieldWidth = FS.getFieldWidth(); + switch (FieldWidth.getHowSpecified()) { + case OptionalAmount::NotSpecified: + break; + case OptionalAmount::Constant: + FormatSpec.append(llvm::utostr(FieldWidth.getConstantAmount())); + break; + case OptionalAmount::Arg: + FormatSpec.push_back('{'); + if (FieldWidth.usesPositionalArg()) { + // std::format argument identifiers are zero-based, whereas printf + // ones are one based. + assert(FieldWidth.getPositionalArgIndex() > 0U); + FormatSpec.append( + llvm::utostr(FieldWidth.getPositionalArgIndex() - 1)); + } + FormatSpec.push_back('}'); + break; + case OptionalAmount::Invalid: + break; + } + } + + // Convert precision + { + const OptionalAmount FieldPrecision = FS.getPrecision(); + switch (FieldPrecision.getHowSpecified()) { + case OptionalAmount::NotSpecified: + break; + case OptionalAmount::Constant: + FormatSpec.push_back('.'); + FormatSpec.append(llvm::utostr(FieldPrecision.getConstantAmount())); + break; + case OptionalAmount::Arg: + FormatSpec.push_back('.'); + FormatSpec.push_back('{'); + if (FieldPrecision.usesPositionalArg()) { + // std::format argument identifiers are zero-based, whereas printf + // ones are one based. + assert(FieldPrecision.getPositionalArgIndex() > 0U); + FormatSpec.append( + llvm::utostr(FieldPrecision.getPositionalArgIndex() - 1)); + } + FormatSpec.push_back('}'); + break; + case OptionalAmount::Invalid: + break; + } + } + + // Convert type + { + if (FS.getArgIndex() + ArgsOffset >= NumArgs) { + // Argument index out of range. Give up. + ConversionPossible = false; + return false; + } + + // If we've got this far, then the specifier must have an associated + // argument + assert(FS.consumesDataArgument()); + + const Expr *Arg = + Args[FS.getArgIndex() + ArgsOffset]->IgnoreImplicitAsWritten(); + using analyze_format_string::ConversionSpecifier; + const ConversionSpecifier Spec = FS.getConversionSpecifier(); + switch (Spec.getKind()) { + case ConversionSpecifier::Kind::sArg: + if (Arg->getType()->isPointerType()) { + const auto Pointee = Arg->getType()->getPointeeType(); + // If the type is not char then std::format can't handle it without + // needing a cast + if (!isRealCharType(Pointee)) + ArgFixes.emplace_back(Arg, "reinterpret_cast<const char *>("); + } + // Strings never need to have their type specified. + break; + case ConversionSpecifier::Kind::cArg: + // The type must be "c" to get a character unless the type is exactly + // char (whether that be signed or unsigned for the target.) + if (!isRealCharType(Arg->getType())) + FormatSpec.push_back('c'); + break; + case ConversionSpecifier::Kind::dArg: + case ConversionSpecifier::Kind::iArg: + if (Arg->getType()->isBooleanType()) { + // std::format will print bool as either "true" or "false" by default, + // but printf prints them as "0" or "1". Ber compatible with printf by + // requesting decimal output. + FormatSpec.push_back('d'); + } else if (Arg->getType()->isEnumeralType()) { + // std::format will try to find a specialization to print the enum + // (and probably fail), whereas printf would have just expected it to + // be passed as its underlying type. However, printf will have forced + // the signedness based on the format string, so we need to do the + // same. + if (const auto *ET = Arg->getType()->getAs<EnumType>()) { + if (const auto MaybeSignedTypeName = getCorrespondingSignedTypeName( + ET->getDecl()->getIntegerType())) + ArgFixes.emplace_back( + Arg, + (Twine("static_cast<") + *MaybeSignedTypeName + ">(").str()); + else + ConversionPossible = false; + } + } else if (Arg->getType()->isUnsignedIntegerType()) { + // printf will happily print an unsigned type as signed if told to. + // Even -Wformat doesn't warn for this. std::format will format as + // unsigned unless we cast it. + if (const auto MaybeSignedTypeName = + getCorrespondingSignedTypeName(Arg->getType())) + ArgFixes.emplace_back( + Arg, + (Twine("static_cast<") + *MaybeSignedTypeName + ">(").str()); + else + ConversionPossible = false; + } else if (isRealCharType(Arg->getType()) || + !Arg->getType()->isIntegerType()) { + // Only specify integer if the argument is of a different type + FormatSpec.push_back('d'); + } + break; + case ConversionSpecifier::Kind::uArg: + if (Arg->getType()->isEnumeralType()) { + // std::format will try to find a specialization to print the enum + // (and probably fail), whereas printf would have just expected it to + // be passed as its underlying type. However, printf will have forced + // the signedness based on the format string, so we need to do the + // same. + if (const auto *ET = Arg->getType()->getAs<EnumType>()) { + if (const auto MaybeUnsignedTypeName = + getCorrespondingUnsignedTypeName( + ET->getDecl()->getIntegerType())) + ArgFixes.emplace_back( + Arg, (Twine("static_cast<") + *MaybeUnsignedTypeName + ">(") + .str()); + else + ConversionPossible = false; + } + } else if (Arg->getType()->isSignedIntegerType()) { + // printf will happily print an signed type as unsigned if told to. + // Even -Wformat doesn't warn for this. std::format will format as + // signed unless we cast it. + if (const auto MaybeUnsignedTypeName = + getCorrespondingUnsignedTypeName(Arg->getType())) + ArgFixes.emplace_back( + Arg, + (Twine("static_cast<") + *MaybeUnsignedTypeName + ">(").str()); + else + ConversionPossible = false; + } else if (isRealCharType(Arg->getType()) || + Arg->getType()->isBooleanType() || + !Arg->getType()->isIntegerType()) { + // Only specify integer if the argument is of a different type + FormatSpec.push_back('d'); + } + break; + case ConversionSpecifier::Kind::pArg: + if (Arg->getType()->isNullPtrType()) + ; // std::format knows how to format nullptr + else if (Arg->getType()->isVoidPointerType()) + ; // std::format knows how to format void pointers + else + ArgFixes.emplace_back(Arg, "reinterpret_cast<const void *>("); + break; + case ConversionSpecifier::Kind::xArg: + FormatSpec.push_back('x'); + break; + case ConversionSpecifier::Kind::XArg: + FormatSpec.push_back('X'); + break; + case ConversionSpecifier::Kind::oArg: + FormatSpec.push_back('o'); + break; + case ConversionSpecifier::Kind::aArg: + FormatSpec.push_back('a'); + break; + case ConversionSpecifier::Kind::AArg: + FormatSpec.push_back('A'); + break; + case ConversionSpecifier::Kind::eArg: + FormatSpec.push_back('e'); + break; + case ConversionSpecifier::Kind::EArg: + FormatSpec.push_back('E'); + break; + case ConversionSpecifier::Kind::fArg: + FormatSpec.push_back('f'); + break; + case ConversionSpecifier::Kind::FArg: + FormatSpec.push_back('F'); + break; + case ConversionSpecifier::Kind::gArg: + FormatSpec.push_back('g'); + break; + case ConversionSpecifier::Kind::GArg: + FormatSpec.push_back('G'); + break; + default: + // Something we don't understand + ConversionPossible = false; + return false; + } + } + + if (!FormatSpec.empty()) { + StandardFormatString.push_back(':'); + StandardFormatString.append(FormatSpec); + } + + StandardFormatString.push_back('}'); + } + + // Skip over specifier + PrintfFormatStringPos = StartSpecifierPos + SpecifierLen; + assert(PrintfFormatStringPos <= PrintfFormatString.size()); + + FormatStringNeededRewriting = true; + return true; +} + +/// Called at the very end just before applying fixes to capture the last part +/// of the format string. +void FormatStringConverter::finalizeFormatText() { + appendFormatText( + StringRef(PrintfFormatString.begin() + PrintfFormatStringPos, + PrintfFormatString.size() - PrintfFormatStringPos)); + PrintfFormatStringPos = PrintfFormatString.size(); + + StandardFormatString.push_back('\"'); +} + +/// Append literal parts of the format text, reinstating escapes as required. +void FormatStringConverter::appendFormatText(const StringRef Text) { + for (const char Ch : Text) { + if (Ch == '\a') + StandardFormatString += "\\a"; + else if (Ch == '\b') + StandardFormatString += "\\b"; + else if (Ch == '\f') + StandardFormatString += "\\f"; + else if (Ch == '\n') + StandardFormatString += "\\n"; + else if (Ch == '\r') + StandardFormatString += "\\r"; + else if (Ch == '\t') + StandardFormatString += "\\t"; + else if (Ch == '\v') + StandardFormatString += "\\v"; + else if (Ch == '\"') + StandardFormatString += "\\\""; + else if (Ch == '\\') + StandardFormatString += "\\\\"; + else if (Ch == '{') { + StandardFormatString += "{{"; + FormatStringNeededRewriting = true; + } else if (Ch == '}') { + StandardFormatString += "}}"; + FormatStringNeededRewriting = true; + } else if (Ch < 32) { + StandardFormatString += "\\x"; + StandardFormatString += llvm::hexdigit(Ch >> 4, true); + StandardFormatString += llvm::hexdigit(Ch & 0xf, true); + } else + StandardFormatString += Ch; + } +} + +/// Called by the check when it is ready to apply the fixes. +void FormatStringConverter::applyFixes(DiagnosticBuilder &Diag, + SourceManager &SM) { + finalizeFormatText(); + if (FormatStringNeededRewriting) { + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(FormatExpr->getBeginLoc(), + FormatExpr->getEndLoc()), + StandardFormatString); + } + for (const auto [Arg, Replacement] : ArgFixes) { + SourceLocation AfterOtherSide = + Lexer::findNextToken(Arg->getEndLoc(), SM, LangOpts)->getLocation(); + + Diag << FixItHint::CreateInsertion(Arg->getBeginLoc(), Replacement) + << FixItHint::CreateInsertion(AfterOtherSide, ")"); + } +} +} // namespace clang::tidy::utils Index: clang-tools-extra/clang-tidy/utils/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/utils/CMakeLists.txt +++ clang-tools-extra/clang-tidy/utils/CMakeLists.txt @@ -11,6 +11,7 @@ ExceptionSpecAnalyzer.cpp ExprSequence.cpp FileExtensionsUtils.cpp + FormatStringConverter.cpp FixItHintUtils.cpp HeaderGuard.cpp IncludeInserter.cpp Index: clang-tools-extra/clang-tidy/modernize/PrintfToStdPrintCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/modernize/PrintfToStdPrintCheck.h @@ -0,0 +1,37 @@ +//===--- PrintfConvertCheck.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_FMT_PRINTFTOSTDPRINTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FMT_PRINTFTOSTDPRINTCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::modernize { +/// Documentation goes here. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/fmt-to-std-print-check.html +class PrintfToStdPrintCheck : public ClangTidyCheck { + std::vector<StringRef> PrintfLikeFunctions; + std::vector<StringRef> FprintfLikeFunctions; + StringRef ReplacementPrintFunction; + +public: + PrintfToStdPrintCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + if (ReplacementPrintFunction == "std::print") + return LangOpts.CPlusPlus2b; + return LangOpts.CPlusPlus; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FMT_PRINTFTOSTDPRINTCHECK_H Index: clang-tools-extra/clang-tidy/modernize/PrintfToStdPrintCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/modernize/PrintfToStdPrintCheck.cpp @@ -0,0 +1,79 @@ +//===--- PrintfToStdPrintCheck.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 "PrintfToStdPrintCheck.h" +#include "../utils/FormatStringConverter.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +PrintfToStdPrintCheck::PrintfToStdPrintCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + PrintfLikeFunctions(utils::options::parseStringList( + Options.get("PrintfLikeFunctions", ""))), + FprintfLikeFunctions(utils::options::parseStringList( + Options.get("FprintfLikeFunctions", ""))), + ReplacementPrintFunction(Options.get("PrintFunction", "std::print")) { + PrintfLikeFunctions.push_back("printf"); + PrintfLikeFunctions.push_back("absl::PrintF"); + FprintfLikeFunctions.push_back("fprintf"); + FprintfLikeFunctions.push_back("absl::FPrintF"); +} + +void PrintfToStdPrintCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + traverse(TK_AsIs, + callExpr(callee(functionDecl(matchers::matchesAnyListedName( + PrintfLikeFunctions)) + .bind("func_decl")), + hasArgument(0, stringLiteral())) + .bind("printf")), + this); + + Finder->addMatcher( + traverse(TK_AsIs, + callExpr(callee(functionDecl(matchers::matchesAnyListedName( + FprintfLikeFunctions)) + .bind("func_decl")), + hasArgument(1, stringLiteral())) + .bind("fprintf")), + this); +} + +void PrintfToStdPrintCheck::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, getLangOpts()); + if (Converter.canApply()) { + const auto *PrintfCall = Printf->getCallee(); + DiagnosticBuilder Diag = + diag(PrintfCall->getBeginLoc(), "Replace %0 with '%1'") + << OldFunction->getIdentifier() << ReplacementPrintFunction; + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(PrintfCall->getBeginLoc(), + PrintfCall->getEndLoc()), + ReplacementPrintFunction); + Converter.applyFixes(Diag, *Result.SourceManager); + } +} + +} // namespace clang::tidy::modernize Index: clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -19,6 +19,7 @@ #include "MakeSharedCheck.h" #include "MakeUniqueCheck.h" #include "PassByValueCheck.h" +#include "PrintfToStdPrintCheck.h" #include "RawStringLiteralCheck.h" #include "RedundantVoidArgCheck.h" #include "ReplaceAutoPtrCheck.h" @@ -64,6 +65,8 @@ CheckFactories.registerCheck<MakeSharedCheck>("modernize-make-shared"); CheckFactories.registerCheck<MakeUniqueCheck>("modernize-make-unique"); CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value"); + CheckFactories.registerCheck<PrintfToStdPrintCheck>( + "modernize-printf-to-std-print"); CheckFactories.registerCheck<RawStringLiteralCheck>( "modernize-raw-string-literal"); CheckFactories.registerCheck<RedundantVoidArgCheck>( Index: clang-tools-extra/clang-tidy/modernize/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -18,6 +18,7 @@ MakeUniqueCheck.cpp ModernizeTidyModule.cpp PassByValueCheck.cpp + PrintfToStdPrintCheck.cpp RawStringLiteralCheck.cpp RedundantVoidArgCheck.cpp ReplaceAutoPtrCheck.cpp
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits