diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -37,6 +37,7 @@ UseNoexceptCheck.cpp UseNullptrCheck.cpp UseOverrideCheck.cpp + UseStdPrintCheck.cpp UseTrailingReturnTypeCheck.cpp UseTransparentFunctorsCheck.cpp UseUncaughtExceptionsCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -38,6 +38,7 @@ #include "UseNoexceptCheck.h" #include "UseNullptrCheck.h" #include "UseOverrideCheck.h" +#include "UseStdPrintCheck.h" #include "UseTrailingReturnTypeCheck.h" #include "UseTransparentFunctorsCheck.h" #include "UseUncaughtExceptionsCheck.h" @@ -64,6 +65,7 @@ CheckFactories.registerCheck("modernize-make-shared"); CheckFactories.registerCheck("modernize-make-unique"); CheckFactories.registerCheck("modernize-pass-by-value"); + CheckFactories.registerCheck("modernize-use-std-print"); CheckFactories.registerCheck( "modernize-raw-string-literal"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h @@ -0,0 +1,50 @@ +//===--- UseStdPrintCheck.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_USESTDPRINTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USESTDPRINTCHECK_H + +#include "../ClangTidyCheck.h" +#include "../utils/IncludeInserter.h" + +namespace clang::tidy::modernize { +/// Convert calls to printf-like functions to std::print and std::println +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html +class UseStdPrintCheck : public ClangTidyCheck { +public: + UseStdPrintCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + if (ReplacementPrintFunction == "std::print" || + ReplacementPrintlnFunction == "std::println") + return LangOpts.CPlusPlus23; + return LangOpts.CPlusPlus; + } + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + bool StrictMode; + std::vector PrintfLikeFunctions; + std::vector FprintfLikeFunctions; + StringRef ReplacementPrintFunction; + StringRef ReplacementPrintlnFunction; + utils::IncludeInserter IncludeInserter; + std::optional MaybeHeaderToInclude; +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USESTDPRINTCHECK_H diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp @@ -0,0 +1,136 @@ +//===--- UseStdPrintCheck.cpp - clang-tidy-----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "UseStdPrintCheck.h" +#include "../utils/FormatStringConverter.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +namespace { +AST_MATCHER(StringLiteral, isOrdinary) { return Node.isOrdinary(); } +} // namespace + +UseStdPrintCheck::UseStdPrintCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + StrictMode(Options.getLocalOrGlobal("StrictMode", false)), + PrintfLikeFunctions(utils::options::parseStringList( + Options.get("PrintfLikeFunctions", ""))), + FprintfLikeFunctions(utils::options::parseStringList( + Options.get("FprintfLikeFunctions", ""))), + ReplacementPrintFunction( + Options.get("ReplacementPrintFunction", "std::print")), + ReplacementPrintlnFunction( + Options.get("ReplacementPrintlnFunction", "std::println")), + IncludeInserter(Options.getLocalOrGlobal("IncludeStyle", + utils::IncludeSorter::IS_LLVM), + areDiagsSelfContained()), + MaybeHeaderToInclude(Options.get("PrintHeader")) { + + if (PrintfLikeFunctions.empty() && FprintfLikeFunctions.empty()) { + PrintfLikeFunctions.push_back("::printf"); + PrintfLikeFunctions.push_back("absl::PrintF"); + FprintfLikeFunctions.push_back("::fprintf"); + FprintfLikeFunctions.push_back("absl::FPrintF"); + } + + if (!MaybeHeaderToInclude && (ReplacementPrintFunction == "std::print" || + ReplacementPrintlnFunction == "std::println")) + MaybeHeaderToInclude = ""; +} + +void UseStdPrintCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + using utils::options::serializeStringList; + Options.store(Opts, "StrictMode", StrictMode); + Options.store(Opts, "PrintfLikeFunctions", + serializeStringList(PrintfLikeFunctions)); + Options.store(Opts, "FprintfLikeFunctions", + serializeStringList(FprintfLikeFunctions)); + Options.store(Opts, "ReplacementPrintFunction", ReplacementPrintFunction); + Options.store(Opts, "ReplacementPrintlnFunction", ReplacementPrintlnFunction); + Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle()); + if (MaybeHeaderToInclude) + Options.store(Opts, "PrintHeader", *MaybeHeaderToInclude); +} + +void UseStdPrintCheck::registerPPCallbacks(const SourceManager &SM, + Preprocessor *PP, + Preprocessor *ModuleExpanderPP) { + IncludeInserter.registerPreprocessor(PP); +} + +void UseStdPrintCheck::registerMatchers(MatchFinder *Finder) { + if (!PrintfLikeFunctions.empty()) + Finder->addMatcher( + callExpr(argumentCountAtLeast(1), + hasArgument(0, stringLiteral(isOrdinary())), + callee(functionDecl( + unless(cxxMethodDecl()), + matchers::matchesAnyListedName(PrintfLikeFunctions)) + .bind("func_decl"))) + .bind("printf"), + this); + + if (!FprintfLikeFunctions.empty()) + Finder->addMatcher( + callExpr(argumentCountAtLeast(2), + hasArgument(1, stringLiteral(isOrdinary())), + callee(functionDecl(unless(cxxMethodDecl()), + matchers::matchesAnyListedName( + FprintfLikeFunctions)) + .bind("func_decl"))) + .bind("fprintf"), + this); +} + +void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) { + unsigned FormatArgOffset = 0; + const auto *OldFunction = Result.Nodes.getNodeAs("func_decl"); + const auto *Printf = Result.Nodes.getNodeAs("printf"); + if (!Printf) { + Printf = Result.Nodes.getNodeAs("fprintf"); + FormatArgOffset = 1; + } + + utils::FormatStringConverter Converter( + Result.Context, Printf, FormatArgOffset, StrictMode, getLangOpts()); + const Expr *PrintfCall = Printf->getCallee(); + const StringRef ReplacementFunction = Converter.usePrintNewlineFunction() + ? ReplacementPrintlnFunction + : ReplacementPrintFunction; + if (!Converter.canApply()) { + diag(PrintfCall->getBeginLoc(), + "unable to use '%0' instead of %1 because %2") + << ReplacementFunction << OldFunction->getIdentifier() + << Converter.conversionNotPossibleReason(); + return; + } + + DiagnosticBuilder Diag = + diag(PrintfCall->getBeginLoc(), "use '%0' instead of %1") + << ReplacementFunction << OldFunction->getIdentifier(); + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(PrintfCall->getBeginLoc(), + PrintfCall->getEndLoc()), + ReplacementFunction); + Converter.applyFixes(Diag, *Result.SourceManager); + + if (MaybeHeaderToInclude) + Diag << IncludeInserter.createIncludeInsertion( + Result.Context->getSourceManager().getFileID(PrintfCall->getBeginLoc()), + *MaybeHeaderToInclude); +} + +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt +++ b/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 diff --git a/clang-tools-extra/clang-tidy/utils/FormatStringConverter.h b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.h @@ -0,0 +1,98 @@ +//===--- 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 "clang/ASTMatchers/ASTMatchers.h" +#include + +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 { +public: + using ConversionSpecifier = clang::analyze_format_string::ConversionSpecifier; + using PrintfSpecifier = analyze_printf::PrintfSpecifier; + FormatStringConverter(ASTContext *Context, const CallExpr *Call, + unsigned FormatArgOffset, bool StrictMode, + const LangOptions &LO); + + bool canApply() const { return ConversionNotPossibleReason.empty(); } + const std::string &conversionNotPossibleReason() const { + return ConversionNotPossibleReason; + } + void applyFixes(DiagnosticBuilder &Diag, SourceManager &SM); + bool usePrintNewlineFunction() const { return UsePrintNewlineFunction; } + +private: + ASTContext *Context; + const bool CastMismatchedIntegerTypes; + const Expr *const *Args; + const unsigned NumArgs; + unsigned ArgsOffset; + const LangOptions &LangOpts; + std::string ConversionNotPossibleReason; + bool FormatStringNeededRewriting = false; + bool UsePrintNewlineFunction = false; + size_t PrintfFormatStringPos = 0U; + StringRef PrintfFormatString; + + /// Lazily-created c_str() call matcher + std::optional + StringCStrCallExprMatcher; + + const StringLiteral *FormatExpr; + std::string StandardFormatString; + + /// Casts to be used to wrap arguments to retain printf compatibility. + std::vector> ArgFixes; + std::vector ArgCStrRemovals; + + void emitAlignment(const PrintfSpecifier &FS, std::string &FormatSpec); + void emitSign(const PrintfSpecifier &FS, std::string &FormatSpec); + void emitAlternativeForm(const PrintfSpecifier &FS, std::string &FormatSpec); + void emitFieldWidth(const PrintfSpecifier &FS, std::string &FormatSpec); + void emitPrecision(const PrintfSpecifier &FS, std::string &FormatSpec); + void emitStringArgument(const Expr *Arg); + bool emitIntegerArgument(ConversionSpecifier::Kind ArgKind, const Expr *Arg, + unsigned ArgIndex, std::string &FormatSpec); + + bool emitType(const PrintfSpecifier &FS, const Expr *Arg, + std::string &FormatSpec); + bool convertArgument(const PrintfSpecifier &FS, const Expr *Arg, + std::string &StandardFormatString); + + bool HandlePrintfSpecifier(const PrintfSpecifier &FS, + const char *StartSpecifier, unsigned SpecifierLen, + const TargetInfo &Target) override; + + void appendFormatText(StringRef Text); + void finalizeFormatText(); + bool conversionNotPossible(std::string Reason) { + ConversionNotPossibleReason = std::move(Reason); + return false; + } +}; + +} // namespace clang::tidy::utils + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FORMATSTRINGCONVERTER_H diff --git a/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp @@ -0,0 +1,685 @@ +//===--- 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 "../utils/FixItHintUtils.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/FixIt.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Debug.h" + +using namespace clang::ast_matchers; +using namespace clang::analyze_printf; + +namespace clang::tidy::utils { +using clang::analyze_format_string::ConversionSpecifier; + +/// Is the passed type the actual "char" type, whether that be signed or +/// unsigned, rather than explicit signed char or unsigned char types. +static bool isRealCharType(const clang::QualType &Ty) { + using namespace clang; + const Type *DesugaredType = Ty->getUnqualifiedDesugaredType(); + if (const auto *BT = llvm::dyn_cast(DesugaredType)) + return (BT->getKind() == BuiltinType::Char_U || + BT->getKind() == BuiltinType::Char_S); + 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. +static std::optional +getCorrespondingSignedTypeName(const clang::QualType &QT) { + using namespace clang; + const auto UQT = QT.getUnqualifiedType(); + if (const auto *BT = llvm::dyn_cast(UQT)) { + 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; + } + } + + // Deal with fixed-width integer types from . Use std:: prefix only + // if the argument type does. + const std::string TypeName = UQT.getAsString(); + StringRef SimplifiedTypeName{TypeName}; + const bool InStd = SimplifiedTypeName.consume_front("std::"); + const StringRef Prefix = InStd ? "std::" : ""; + + if (SimplifiedTypeName.starts_with("uint") && + SimplifiedTypeName.ends_with("_t")) + return (Twine(Prefix) + SimplifiedTypeName.drop_front()).str(); + + if (SimplifiedTypeName == "size_t") + return (Twine(Prefix) + "ssize_t").str(); + + llvm::dbgs() << "Unknown corresponding signed type for non-BuiltinType '" + << UQT.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. +static std::optional +getCorrespondingUnsignedTypeName(const clang::QualType &QT) { + using namespace clang; + const auto UQT = QT.getUnqualifiedType(); + if (const auto *BT = llvm::dyn_cast(UQT)) { + 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 '" + << UQT.getAsString() << "'\n"; + return std::nullopt; + } + } + + // Deal with fixed-width integer types from . Use std:: prefix only + // if the argument type does. + const std::string TypeName = UQT.getAsString(); + StringRef SimplifiedTypeName{TypeName}; + const bool InStd = SimplifiedTypeName.consume_front("std::"); + const StringRef Prefix = InStd ? "std::" : ""; + + if (SimplifiedTypeName.starts_with("int") && + SimplifiedTypeName.ends_with("_t")) + return (Twine(Prefix) + "u" + SimplifiedTypeName).str(); + + if (SimplifiedTypeName == "ssize_t") + return (Twine(Prefix) + "size_t").str(); + if (SimplifiedTypeName == "ptrdiff_t") + return (Twine(Prefix) + "size_t").str(); + + llvm::dbgs() << "Unknown corresponding unsigned type for non-BuiltinType '" + << UQT.getAsString() << "'\n"; + return std::nullopt; +} + +static std::optional +castTypeForArgument(ConversionSpecifier::Kind ArgKind, + const clang::QualType &QT) { + if (ArgKind == ConversionSpecifier::Kind::uArg) + return getCorrespondingUnsignedTypeName(QT); + return getCorrespondingSignedTypeName(QT); +} + +static bool isMatchingSignedness(ConversionSpecifier::Kind ArgKind, + const clang::QualType &ArgType) { + if (const auto *BT = llvm::dyn_cast(ArgType)) { + // Unadorned char never matches any expected signedness since it + // could be signed or unsigned. + const auto ArgTypeKind = BT->getKind(); + if (ArgTypeKind == BuiltinType::Char_U || ArgTypeKind == BuiltinType::Char_S) + return false; + } + + if (ArgKind == ConversionSpecifier::Kind::uArg) + return ArgType->isUnsignedIntegerType(); + return ArgType->isSignedIntegerType(); +} + +namespace { +AST_MATCHER(clang::QualType, isRealChar) { + return clang::tidy::utils::isRealCharType(Node); +} +} // namespace + +static bool castMismatchedIntegerTypes(const CallExpr *Call, bool StrictMode) { + /// For printf-style functions, the signedness of the type printed is + /// indicated by the corresponding type in the format string. + /// std::print will determine the signedness from the type of the + /// argument. This means that it is necessary to generate a cast in + /// StrictMode to ensure that the exact behaviour is maintained. + /// However, for templated functions like absl::PrintF and + /// fmt::printf, the signedness of the type printed is also taken from + /// the actual argument like std::print, so such casts are never + /// necessary. printf-style functions are variadic, whereas templated + /// ones aren't, so we can use that to distinguish between the two + /// cases. + if (StrictMode) { + const FunctionDecl *FuncDecl = Call->getDirectCallee(); + assert(FuncDecl); + return FuncDecl->isVariadic(); + } + return false; +} + +FormatStringConverter::FormatStringConverter(ASTContext *ContextIn, + const CallExpr *Call, + unsigned FormatArgOffset, + bool StrictMode, + const LangOptions &LO) + : Context(ContextIn), + CastMismatchedIntegerTypes(castMismatchedIntegerTypes(Call, StrictMode)), + Args(Call->getArgs()), NumArgs(Call->getNumArgs()), + ArgsOffset(FormatArgOffset + 1), LangOpts(LO) { + assert(ArgsOffset <= NumArgs); + FormatExpr = llvm::dyn_cast( + Args[FormatArgOffset]->IgnoreImplicitAsWritten()); + assert(FormatExpr); + if (!FormatExpr->isOrdinary()) + return; // No wide string support yet + PrintfFormatString = FormatExpr->getString(); + + // Assume that the output will be approximately the same size as the input, + // but perhaps with a few escapes expanded. + const size_t EstimatedGrowth = 8; + StandardFormatString.reserve(PrintfFormatString.size() + EstimatedGrowth); + 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); + finalizeFormatText(); +} + +void FormatStringConverter::emitAlignment(const PrintfSpecifier &FS, + std::string &FormatSpec) { + ConversionSpecifier::Kind ArgKind = FS.getConversionSpecifier().getKind(); + + // We only care about alignment if a field width is specified + if (FS.getFieldWidth().getHowSpecified() != OptionalAmount::NotSpecified) { + if (ArgKind == 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('<'); + } + } +} + +void FormatStringConverter::emitSign(const PrintfSpecifier &FS, + std::string &FormatSpec) { + 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(' '); + } +} + +void FormatStringConverter::emitAlternativeForm(const PrintfSpecifier &FS, + std::string &FormatSpec) { + if (FS.hasAlternativeForm()) { + switch (FS.getConversionSpecifier().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; + } + } +} + +void FormatStringConverter::emitFieldWidth(const PrintfSpecifier &FS, + std::string &FormatSpec) { + { + 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; + } + } +} + +void FormatStringConverter::emitPrecision(const PrintfSpecifier &FS, + std::string &FormatSpec) { + 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; + } +} + +void FormatStringConverter::emitStringArgument(const Expr *Arg) { + // If the argument is the result of a call to std::string::c_str() or + // data() with a return type of char then we can remove that call and + // pass the std::string directly. We don't want to do so if the return + // type is not a char pointer (though it's unlikely that such code would + // compile without warnings anyway.) See RedundantStringCStrCheck. + + if (!StringCStrCallExprMatcher) { + // Lazily create the matcher + const auto StringDecl = type(hasUnqualifiedDesugaredType(recordType( + hasDeclaration(cxxRecordDecl(hasName("::std::basic_string")))))); + const auto StringExpr = expr( + anyOf(hasType(StringDecl), hasType(qualType(pointsTo(StringDecl))))); + + StringCStrCallExprMatcher = + cxxMemberCallExpr( + on(StringExpr.bind("arg")), callee(memberExpr().bind("member")), + callee(cxxMethodDecl(hasAnyName("c_str", "data"), + returns(pointerType(pointee(isRealChar())))))) + .bind("call"); + } + + auto CStrMatches = match(*StringCStrCallExprMatcher, *Arg, *Context); + if (CStrMatches.size() == 1) + ArgCStrRemovals.push_back(CStrMatches.front()); + else if (Arg->getType()->isPointerType()) { + const QualType Pointee = Arg->getType()->getPointeeType(); + // printf is happy to print signed char and unsigned char strings, but + // std::format only likes char strings. + if (Pointee->isCharType() && !isRealCharType(Pointee)) + ArgFixes.emplace_back(Arg, "reinterpret_cast("); + } +} + +bool FormatStringConverter::emitIntegerArgument( + ConversionSpecifier::Kind ArgKind, const Expr *Arg, unsigned ArgIndex, + std::string &FormatSpec) { + const clang::QualType &ArgType = Arg->getType(); + if (ArgType->isBooleanType()) { + // std::format will print bool as either "true" or "false" by default, + // but printf prints them as "0" or "1". Be compatible with printf by + // requesting decimal output. + FormatSpec.push_back('d'); + } else if (ArgType->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 = ArgType->getAs()) { + if (const std::optional MaybeCastType = + castTypeForArgument(ArgKind, ET->getDecl()->getIntegerType())) + ArgFixes.emplace_back( + Arg, (Twine("static_cast<") + *MaybeCastType + ">(").str()); + else + return conversionNotPossible( + (Twine("argument ") + Twine(ArgIndex) + " has unexpected enum type") + .str()); + } + } else if (CastMismatchedIntegerTypes && + !isMatchingSignedness(ArgKind, ArgType)) { + // 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 std::optional MaybeCastType = + castTypeForArgument(ArgKind, ArgType)) + ArgFixes.emplace_back( + Arg, (Twine("static_cast<") + *MaybeCastType + ">(").str()); + else + return conversionNotPossible( + (Twine("argument ") + Twine(ArgIndex) + " cannot be cast to " + + Twine(ArgKind == ConversionSpecifier::Kind::uArg ? "unsigned" + : "signed") + + " integer type to match format" + " specifier and StrictMode is enabled") + .str()); + } else if (isRealCharType(ArgType) || !ArgType->isIntegerType()) { + // Only specify integer if the argument is of a different type + FormatSpec.push_back('d'); + } + return true; +} + +/// Append the corresponding standard format string type fragment to FormatSpec, +/// and store any argument fixes for later application. +/// @returns true on success, false on failure +bool FormatStringConverter::emitType(const PrintfSpecifier &FS, const Expr *Arg, + std::string &FormatSpec) { + ConversionSpecifier::Kind ArgKind = FS.getConversionSpecifier().getKind(); + switch (ArgKind) { + case ConversionSpecifier::Kind::sArg: + emitStringArgument(Arg); + 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: + case ConversionSpecifier::Kind::uArg: + if (!emitIntegerArgument(ArgKind, Arg, FS.getArgIndex() + ArgsOffset, + FormatSpec)) + return false; + break; + case ConversionSpecifier::Kind::pArg: { + const clang::QualType &ArgType = Arg->getType(); + // std::format knows how to format void pointers and nullptrs + if (!ArgType->isNullPtrType() && !ArgType->isVoidPointerType()) + ArgFixes.emplace_back(Arg, "static_cast("); + 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 + return conversionNotPossible((Twine("argument ") + + Twine(FS.getArgIndex() + ArgsOffset) + + " has an unsupported format specifier") + .str()); + } + + return true; +} + +/// Append the standard format string equivalent of the passed PrintfSpecifier +/// to StandardFormatString and store any argument fixes for later application. +/// @returns true on success, false on failure +bool FormatStringConverter::convertArgument(const PrintfSpecifier &FS, + const Expr *Arg, + std::string &StandardFormatString) { + // The specifier must have an associated argument + assert(FS.consumesDataArgument()); + + 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 parts to potentially emit: + // [[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. + + emitAlignment(FS, FormatSpec); + emitSign(FS, FormatSpec); + emitAlternativeForm(FS, FormatSpec); + + if (FS.hasLeadingZeros()) + FormatSpec.push_back('0'); + + emitFieldWidth(FS, FormatSpec); + emitPrecision(FS, FormatSpec); + + if (!emitType(FS, Arg, FormatSpec)) + return false; + + if (!FormatSpec.empty()) { + StandardFormatString.push_back(':'); + StandardFormatString.append(FormatSpec); + } + + StandardFormatString.push_back('}'); + return true; +} + +/// Called for each format specifier by ParsePrintfString. +bool FormatStringConverter::HandlePrintfSpecifier(const PrintfSpecifier &FS, + const char *StartSpecifier, + unsigned SpecifierLen, + const TargetInfo &Target) { + + 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)); + + const ConversionSpecifier::Kind ArgKind = + FS.getConversionSpecifier().getKind(); + + // Skip over specifier + PrintfFormatStringPos = StartSpecifierPos + SpecifierLen; + assert(PrintfFormatStringPos <= PrintfFormatString.size()); + + FormatStringNeededRewriting = true; + + if (ArgKind == ConversionSpecifier::Kind::nArg) { + // std::print doesn't do the equivalent of %n + return conversionNotPossible("'%n' is not supported in format string"); + } + + if (ArgKind == ConversionSpecifier::Kind::PrintErrno) { + // std::print 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. + return conversionNotPossible("'%m' is not supported in format string"); + } + + if (ArgKind == ConversionSpecifier::PercentArg) { + StandardFormatString.push_back('%'); + return true; + } + + const unsigned ArgIndex = FS.getArgIndex() + ArgsOffset; + if (ArgIndex >= NumArgs) { + // Argument index out of range. Give up. + return conversionNotPossible( + (Twine("argument index ") + Twine(ArgIndex) + " is out of range") + .str()); + } + + return convertArgument(FS, Args[ArgIndex]->IgnoreImplicitAsWritten(), + StandardFormatString); +} + +/// 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(); + + if (StringRef(StandardFormatString).ends_with("\\n") && + !StringRef(StandardFormatString).ends_with("\\\\n")) { + UsePrintNewlineFunction = true; + FormatStringNeededRewriting = true; + StandardFormatString.erase(StandardFormatString.end() - 2, + StandardFormatString.end()); + } + + 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) { + 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, ")"); + } + + for (const auto &Match : ArgCStrRemovals) { + const auto *Call = Match.getNodeAs("call"); + const auto *Arg = Match.getNodeAs("arg"); + const auto *Member = Match.getNodeAs("member"); + const bool Arrow = Member->isArrow(); + const std::string ArgText = + Arrow ? utils::fixit::formatDereference(*Arg, *Context) + : tooling::fixit::getText(*Arg, *Context).str(); + if (!ArgText.empty()) + Diag << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText); + } +} +} // namespace clang::tidy::utils diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -181,6 +181,16 @@ Converts standard library type traits of the form ``traits<...>::type`` and ``traits<...>::value`` into ``traits_t<...>`` and ``traits_v<...>`` respectively. +- New :doc:`modernize-use-std-print + ` check. + + Converts calls to ``printf``, ``fprintf``, ``absl::PrintF``, + ``absl::FPrintf`` or other functions via configuration options, to + equivalent calls to C++23's ``std::print`` and ``std::println``, or other + functions via a configuration option, modifying the format string + appropriately and removing now-unnecessary calls to + ``std::string::c_str()`` and ``std::string::data()``. + - New :doc:`performance-avoid-endl ` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -302,6 +302,7 @@ `modernize-use-noexcept `_, "Yes" `modernize-use-nullptr `_, "Yes" `modernize-use-override `_, "Yes" + `modernize-use-std-print `_, "Yes" `modernize-use-trailing-return-type `_, "Yes" `modernize-use-transparent-functors `_, "Yes" `modernize-use-uncaught-exceptions `_, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst @@ -0,0 +1,147 @@ +.. title:: clang-tidy - modernize-use-std-print + +modernize-use-std-print +======================= + +Converts calls to ``printf``, ``fprintf``, ``absl::PrintF`` and +``absl::FPrintf`` to equivalent calls to C++23's ``std::print`` or +``std::println`` as appropriate, modifying the format string appropriately. +The replaced and replacement functions can be customised by configuration +options. Each argument that is the result of a call to ``std::string::c_str()`` and +``std::string::data()`` will have that now-unnecessary call removed in a +similar manner to the readability-redundant-string-cstr check. + +In other words, it turns lines like:: + +.. code-block:: c++ + + fprintf(stderr, "The %s is %3d\n", description.c_str(), value); + +into:: + +.. code-block:: c++ + + std::println(stderr, "The {} is {:3}", description, value); + +It doesn't do a bad job, but it's not perfect. In particular: + +- It assumes that the format string is correct for the arguments. 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 + ```` are removed correctly. + +- It supports field widths, precision, positional arguments, leading zeros, + leading ``+``, alignment and alternative forms. + +- Use of any unsupported flags or specifiers will cause the entire + statement to be left alone and a warning to be emitted. 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 + `ReplacementPrintlnFunction` option if the format string ends with ``\n`` + or `ReplacementPrintFunction` otherwise. +- the format string is rewritten to use the ``std::formatter`` language and + a ``\n`` is removed from the end. +- any arguments that corresponded to ``%p`` specifiers that + ``std::formatter`` wouldn't accept are wrapped in a ``static_cast`` + to ``const void *``. +- any arguments that corresponded to ``%s`` specifiers where the argument + is of ``signed char`` or ``unsigned char`` type are wrapped in a + ``reinterpret_cast``. +- any arguments where the format string and the parameter differ in + signedness will be wrapped in an approprate ``static_cast`` if `StrictMode` + is enabled. +- any arguments that end in a call to ``std::string::c_str()`` or + ``std::string_data()`` will have that call removed. + +Options +------- + +.. option:: StrictMode + + When `true`, the check will add casts when converting from variadic + functions like ``printf`` and printing signed or unsigned integer types + (including fixed-width integer types from ````, ``ptrdiff_t``, + ``size_t`` and ``ssize_t``) as the opposite signedness to ensure that + the output matches that of ``printf``. This does not apply when + converting from non-variadic functions such as ``absl::PrintF`` and + ``fmt::printf``. For example, with `StrictMode` enabled: + + .. code-block:: c++ + + int i = -42; + unsigned int u = 0xffffffff; + printf("%d %u\n", i, u); + + would be converted to:: + + .. code-block:: c++ + + std::print("{} {}\n", static_cast(i), static_cast(u)); + + to ensure that the output will continue to be the unsigned representation + of `-42` and the signed representation of `0xffffffff` (often + `4294967254` and `-1` respectively.) When `false` (which is the default), + these casts will not be added which may cause a change in the output. + +.. option:: PrintfLikeFunctions + + A semicolon-separated list of (fully qualified) extra function names to + replace, with the requirement that the first parameter contains the + printf-style format string and the arguments to be formatted follow + immediately afterwards. If neither this option nor + `FprintfLikeFunctions` are set then the default value for this option + is `printf; absl::PrintF`, otherwise it is empty. + + +.. option:: FprintfLikeFunctions + + A semicolon-separated list of (fully qualified) extra function names to + replace, 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. If neither this + option nor `PrintfLikeFunctions` are set then the default value for + this option is `fprintf; absl::FPrintF`, otherwise it is empty. + +.. option:: ReplacementPrintFunction + + The function that will be used to replace ``printf``, ``fprintf`` etc. + during conversion rather than the default ``std::print`` when the + originalformat string does not end with ``\n``. It is expected that the + function provides an interface that is compatible with ``std::print``. A + suitable candidate would be ``fmt::print``. + +.. option:: ReplacementPrintlnFunction + + The function that will be used to replace ``printf``, ``fprintf`` etc. + during conversion rather than the default ``std::println`` when the + original format string ends with ``\n``. It is expected that the + function provides an interface that is compatible with ``std::println``. + A suitable candidate would be ``fmt::println``. + +.. option:: PrintHeader + + The header that must be included for the declaration of + `ReplacementPrintFunction` so that a ``#include`` directive can be + added if required. If `ReplacementPrintFunction` is ``std::print`` + then this option will default to ````, otherwise this option will + default to nothing and no ``#include`` directive will be added. diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstddef copy from clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h copy to clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstddef --- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstddef @@ -1,4 +1,4 @@ -//===--- string.h - Stub header for tests------ -----------------*- C++ -*-===// +//===--- cstddef - 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,11 +6,14 @@ // //===----------------------------------------------------------------------===// -#ifndef _STRING_H_ -#define _STRING_H_ +#ifndef _CSTDDEF_ +#define _CSTDDEF_ -typedef __typeof__(sizeof(0)) size_t; +#include "stddef.h" -void *memcpy(void *dest, const void *src, size_t n); +namespace std { + using ::ptrdiff_t; + using ::size_t; +} -#endif // _STRING_H_ +#endif // _CSTDDEF_ diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdint b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdint new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdint @@ -0,0 +1,40 @@ +//===--- cstdint - 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. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _CSTDINT_ +#define _CSTDINT_ + +typedef __INTMAX_TYPE__ intmax_t; +typedef __UINTMAX_TYPE__ uintmax_t; + +namespace std { + using ::intmax_t; + using ::uintmax_t; +} + +#define DECLARE_INTTYPE(N) \ + typedef __INT ## N ## _TYPE__ int ## N ## _t; \ + typedef int ## N ## _t int_least ## N ## _t; \ + typedef int ## N ## _t int_fast ## N ## _t; \ + typedef __UINT ## N ## _TYPE__ uint ## N ## _t; \ + typedef uint ## N ## _t uint_least ## N ## _t; \ + typedef uint ## N ## _t uint_fast ## N ## _t; \ + namespace std { \ + using ::int ## N ## _t; \ + using ::int_least ## N ## _t; \ + using ::int_fast ## N ## _t; \ + using ::uint ## N ## _t; \ + using ::uint_least ## N ## _t; \ + using ::uint_fast ## N ## _t; \ + } // std + +// For reasons unknown, these aren't coming in from +DECLARE_INTTYPE(8) +DECLARE_INTTYPE(64) + +#endif // _CSTDINT__ diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio copy from clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h copy to clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio --- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio @@ -1,4 +1,4 @@ -//===--- string.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,11 +6,16 @@ // //===----------------------------------------------------------------------===// -#ifndef _STRING_H_ -#define _STRING_H_ +#ifndef _STDIO_ +#define _STDIO_ -typedef __typeof__(sizeof(0)) size_t; +#include -void *memcpy(void *dest, const void *src, size_t n); +namespace std { + using ::FILE; + using ::printf; + using ::fprintf; +} + +#endif // _STDIO_ -#endif // _STRING_H_ diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/inttypes.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/inttypes.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/inttypes.h @@ -0,0 +1,62 @@ +//===--- inttypes.h - 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. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _INTTYPES_H_ +#define _INTTYPES_H_ + +typedef __INTMAX_TYPE__ intmax_t; +typedef __INT64_TYPE__ int64_t; +typedef __INT32_TYPE__ int32_t; +typedef __INT16_TYPE__ int16_t; +typedef __INT8_TYPE__ int8_t; + +typedef __UINTMAX_TYPE__ uintmax_t; +typedef __UINT64_TYPE__ uint64_t; +typedef __UINT32_TYPE__ uint32_t; +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT8_TYPE__ uint8_t; + +#define PRIdMAX "lld" +#define PRId64 "lld" +#define PRId32 "d" +#define PRId16 "hd" +#define PRId8 "hhd" + +#define PRIiMAX "lli" +#define PRIi64 "lli" +#define PRIi32 "i" +#define PRIi16 "hi" +#define PRIi8 "hhi" + +#define PRIiFAST64 "lli" +#define PRIiFAST32 "i" +#define PRIiFAST16 "hi" +#define PRIiFAST8 "hhi" + +#define PRIiLEAST64 "lli" +#define PRIiLEAST32 "i" +#define PRIiLEAST16 "hi" +#define PRIiLEAST8 "hhi" + +#define PRIuMAX "llu" +#define PRIu64 "llu" +#define PRIu32 "u" +#define PRIu16 "hu" +#define PRIu8 "hhu" + +#define PRIuFAST64 "llu" +#define PRIuFAST32 "u" +#define PRIuFAST16 "hu" +#define PRIuFAST8 "hhu" + +#define PRIuLEAST64 "llu" +#define PRIuLEAST32 "u" +#define PRIuLEAST16 "hu" +#define PRIuLEAST8 "hhu" + +#endif // _INTTYPES_H_ diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stddef.h copy from clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h copy to clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stddef.h --- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stddef.h @@ -1,4 +1,4 @@ -//===--- string.h - Stub header for tests------ -----------------*- C++ -*-===// +//===--- stddef.h - 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,11 +6,10 @@ // //===----------------------------------------------------------------------===// -#ifndef _STRING_H_ -#define _STRING_H_ +#ifndef _STDDEF_H_ +#define _STDDEF_H_ -typedef __typeof__(sizeof(0)) size_t; +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef __SIZE_TYPE__ size_t; -void *memcpy(void *dest, const void *src, size_t n); - -#endif // _STRING_H_ +#endif _STDDEF_H_ diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h --- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h +++ b/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_ diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h --- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h @@ -9,7 +9,7 @@ #ifndef _STRING_H_ #define _STRING_H_ -typedef __typeof__(sizeof(0)) size_t; +#include "stddef.h" void *memcpy(void *dest, const void *src, size_t n); diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp @@ -0,0 +1,87 @@ +// RUN: %check_clang_tidy \ +// RUN: -std=c++23 %s modernize-use-std-print %t -- \ +// RUN: -config="{CheckOptions: [{key: StrictMode, value: true}]}" \ +// RUN: -- -isystem %clang_tidy_headers +// RUN: %check_clang_tidy \ +// RUN: -std=c++23 %s modernize-use-std-print %t -- \ +// RUN: -config="{CheckOptions: [{key: StrictMode, value: false}]}" \ +// RUN: -- -isystem %clang_tidy_headers + +#include +#include + +namespace absl +{ +// Use const char * for the format since the real type is hard to mock up. +template +int PrintF(const char *format, const Args&... args); + +template +int FPrintF(FILE* output, const char *format, const Args&... args); +} + +void printf_simple() { + absl::PrintF("Hello %s %d", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'PrintF' [modernize-use-std-print] + // CHECK-FIXES: std::print("Hello {} {}", "world", 42); +} + +void printf_newline() { + absl::PrintF("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'PrintF' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {} {}", "world", 42); + + using namespace absl; + PrintF("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'PrintF' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {} {}", "world", 42); +} + +// absl uses the type of the argument rather than the format string, so unsigned +// types will be printed as unsigned even if the format string indicates signed +// and vice-versa. This is exactly what std::print will do too, so no casts are +// required. +void printf_no_casts_in_strict_mode() { + using namespace absl; + + const unsigned short us = 42U; + PrintF("Integer %hd from unsigned short\n", us); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'PrintF' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {} from unsigned short", us); + + const short s = 42; + PrintF("Unsigned integer %hu from short\n", s); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'PrintF' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integer {} from short", s); +} + +void fprintf_simple(FILE *fp) { + absl::FPrintF(fp, "Hello %s %d", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'FPrintF' [modernize-use-std-print] + // CHECK-FIXES: std::print(fp, "Hello {} {}", "world", 42); +} + +void fprintf_newline(FILE *fp) { + absl::FPrintF(fp, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'FPrintF' [modernize-use-std-print] + // CHECK-FIXES: std::println(fp, "Hello {} {}", "world", 42); + + using namespace absl; + FPrintF(fp, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'FPrintF' [modernize-use-std-print] + // CHECK-FIXES: std::println(fp, "Hello {} {}", "world", 42); +} + +void fprintf_no_casts_in_strict_mode(FILE *fp) { + using namespace absl; + + const unsigned short us = 42U; + FPrintF(fp, "Integer %hd from unsigned short\n", us); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'FPrintF' [modernize-use-std-print] + // CHECK-FIXES: std::println(fp, "Integer {} from unsigned short", us); + + const short s = 42; + FPrintF(fp, "Unsigned integer %hu from short\n", s); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'FPrintF' [modernize-use-std-print] + // CHECK-FIXES: std::println(fp, "Unsigned integer {} from short", s); +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp @@ -0,0 +1,75 @@ +// RUN: %check_clang_tidy -std=c++23 %s modernize-use-std-print %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: [ \ +// RUN: { \ +// RUN: key: modernize-use-std-print.PrintfLikeFunctions, \ +// RUN: value: '::myprintf; mynamespace::myprintf2' \ +// RUN: }, \ +// RUN: { \ +// RUN: key: modernize-use-std-print.FprintfLikeFunctions, \ +// RUN: value: '::myfprintf; mynamespace::myfprintf2' \ +// RUN: } \ +// RUN: ] \ +// RUN: }" \ +// RUN: -- -isystem %clang_tidy_headers + +#include +#include + +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", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'myprintf' [modernize-use-std-print] + // CHECK-FIXES: std::print("Hello {} {}", "world", 42); +} + +void printf_newline() { + myprintf("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myprintf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {} {}", "world", 42); + + mynamespace::myprintf2("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myprintf2' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {} {}", "world", 42); + + using mynamespace::myprintf2; + myprintf2("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myprintf2' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {} {}", "world", 42); + + // When using custom options leave printf alone + printf("Hello %s %d\n", "world", 42); +} + +void fprintf_simple(FILE *fp) +{ + myfprintf(stderr, "Hello %s %d", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'myfprintf' [modernize-use-std-print] + // CHECK-FIXES: std::print(stderr, "Hello {} {}", "world", 42); +} + +void fprintf_newline(FILE *fp) +{ + myfprintf(stderr, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myfprintf' [modernize-use-std-print] + // CHECK-FIXES: std::println(stderr, "Hello {} {}", "world", 42); + + mynamespace::myfprintf2(stderr, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myfprintf2' [modernize-use-std-print] + // CHECK-FIXES: std::println(stderr, "Hello {} {}", "world", 42); + + using mynamespace::myfprintf2; + myfprintf2(stderr, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myfprintf2' [modernize-use-std-print] + // CHECK-FIXES: std::println(stderr, "Hello {} {}", "world", 42); + + // When using custom options leave fprintf alone + fprintf(stderr, "Hello %s %d\n", "world", 42); +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-fmt.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-fmt.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-fmt.cpp @@ -0,0 +1,46 @@ +// RUN: %check_clang_tidy %s modernize-use-std-print %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: [ \ +// RUN: { \ +// RUN: key: modernize-use-std-print.ReplacementPrintFunction, \ +// RUN: value: 'fmt::print' \ +// RUN: }, \ +// RUN: { \ +// RUN: key: modernize-use-std-print.ReplacementPrintlnFunction, \ +// RUN: value: 'fmt::println' \ +// RUN: }, \ +// RUN: { \ +// RUN: key: modernize-use-std-print.PrintHeader, \ +// RUN: value: '' \ +// RUN: } \ +// RUN: ] \ +// RUN: }" \ +// RUN: -- -isystem %clang_tidy_headers + +#include +// CHECK-FIXES: #include +#include + +void printf_simple() { + printf("Hello %s %d", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: fmt::print("Hello {} {}", "world", 42); +} + +void printf_newline() { + printf("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: fmt::println("Hello {} {}", "world", 42); +} + +void fprintf_simple(FILE *fp) { + fprintf(fp, "Hello %s %d", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::print' instead of 'fprintf' [modernize-use-std-print] + // CHECK-FIXES: fmt::print(fp, "Hello {} {}", "world", 42); +} + +void fprintf_newline(FILE *fp) { + fprintf(fp, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::println' instead of 'fprintf' [modernize-use-std-print] + // CHECK-FIXES: fmt::println(fp, "Hello {} {}", "world", 42); +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp @@ -0,0 +1,1263 @@ +// RUN: %check_clang_tidy -check-suffixes=,STRICT \ +// RUN: -std=c++23 %s modernize-use-std-print %t -- \ +// RUN: -config="{CheckOptions: [{key: StrictMode, value: true}]}" \ +// RUN: -- -isystem %clang_tidy_headers +// RUN: %check_clang_tidy -check-suffixes=,NOTSTRICT \ +// RUN: -std=c++23 %s modernize-use-std-print %t -- \ +// RUN: -config="{CheckOptions: [{key: StrictMode, value: false}]}" \ +// RUN: -- -isystem %clang_tidy_headers +#include +#include +#include +// CHECK-FIXES: #include +#include +#include +#include + +void printf_simple() { + printf("Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("Hello"); +} + +void printf_newline() { + printf("Hello\n"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello"); + + printf("Split" "\n"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Split"); + + printf("Double\n\n"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Double\n"); +} + +void printf_deceptive_newline() { + printf("Hello\\n"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("Hello\\n"); + + printf("Hello\x0a"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello"); +} + +void fprintf_simple() { + fprintf(stderr, "Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print] + // CHECK-FIXES: std::print(stderr, "Hello"); +} + +void std_printf_simple() { + std::printf("std::Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("std::Hello"); +} + +void printf_escape() { + printf("before \t"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("before \t"); + + printf("\n after"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("\n after"); + + printf("before \a after"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-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: use 'std::print' instead of 'printf' [modernize-use-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: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("not special \x1b\x01\x7f"); +} + +void printf_percent() { + printf("before %%"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("before %"); + + printf("%% after"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("% after"); + + printf("before %% after"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("before % after"); + + printf("Hello %% and another %%"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("Hello % and another %"); + + printf("Not a string %%s"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("Not a string %s"); +} + +void printf_curlies() { + printf("%d {}", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{} {{[{][{]}}}}", 42); + + printf("{}"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{{[{][{]}}}}"); +} + +void printf_unsupported_format_specifiers() { + int pos; + printf("%d %n %d\n", 42, &pos, 72); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::println' instead of 'printf' because '%n' is not supported in format string [modernize-use-std-print] + + printf("Error %m\n"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::println' instead of 'printf' because '%m' is not supported in format string [modernize-use-std-print] +} + +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: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("uint64:{} uintmax:{}", 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: use 'std::print' instead of 'printf' [modernize-use-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: use 'std::print' instead of 'printf' [modernize-use-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: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {:d} from bool", 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: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {:d} from bool", b); + + // The 'd' is always necessary if we pass a char since otherwise the + // parameter will be formatted as a character. In StrictMode, the + // cast is always necessary to maintain the printf behaviour since + // char may be unsigned, but this also means that the 'd' is not + // necessary. + const char c = 'A'; + printf("Integers %d %hhd from char\n", c, c); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integers {:d} {:d} from char", c, c); + // CHECK-FIXES-STRICT: std::println("Integers {} {} from char", static_cast(c), static_cast(c)); + + printf("Integers %i %hhi from char\n", c, c); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integers {:d} {:d} from char", c, c); + // CHECK-FIXES-STRICT: std::println("Integers {} {} from char", static_cast(c), static_cast(c)); + + const signed char sc = 'A'; + printf("Integers %d %hhd from signed char\n", sc, sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integers {} {} from signed char", sc, sc); + + printf("Integers %i %hhi from signed char\n", sc, sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integers {} {} from signed char", sc, sc); + + const unsigned char uc = 'A'; + printf("Integers %d %hhd from unsigned char\n", uc, uc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integers {} {} from unsigned char", uc, uc); + // CHECK-FIXES-STRICT: std::println("Integers {} {} from unsigned char", static_cast(uc), static_cast(uc)); + + printf("Integers %i %hhi from unsigned char\n", uc, uc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integers {} {} from unsigned char", uc, uc); + // CHECK-FIXES-STRICT: std::println("Integers {} {} from unsigned char", static_cast(uc), static_cast(uc)); + + const int8_t i8 = 42; + printf("Integer %" PRIi8 " from int8_t\n", i8); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {} from int8_t", i8); + + const int_fast8_t if8 = 42; + printf("Integer %" PRIiFAST8 " from int_fast8_t\n", if8); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {} from int_fast8_t", if8); + + const int_least8_t il8 = 42; + printf("Integer %" PRIiFAST8 " from int_least8_t\n", il8); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {} from int_least8_t", il8); + + const uint8_t u8 = 42U; + const std::uint8_t su8 = u8; + printf("Integers %" PRIi8 " and %" PRId8 " from uint8_t\n", u8, su8); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integers {} and {} from uint8_t", u8, su8); + // CHECK-FIXES-STRICT: std::println("Integers {} and {} from uint8_t", static_cast(u8), static_cast(su8)); + + const uint_fast8_t uf8 = 42U; + printf("Integer %" PRIiFAST8 " from uint_fast8_t\n", uf8); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from uint_fast8_t", uf8); + // CHECK-FIXES-STRICT: std::println("Integer {} from uint_fast8_t", static_cast(uf8)); + + const uint_least8_t ul8 = 42U; + printf("Integer %" PRIiLEAST8 " from uint_least8_t\n", ul8); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from uint_least8_t", ul8); + // CHECK-FIXES-STRICT: std::println("Integer {} from uint_least8_t", static_cast(ul8)); + + const short s = 42; + printf("Integer %hd from short\n", s); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {} from short", s); + + printf("Integer %hi from short\n", s); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {} from short", s); + + const unsigned short us = 42U; + printf("Integer %hd from unsigned short\n", us); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned short", us); + // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned short", static_cast(us)); + + printf("Integer %hi from unsigned short\n", us); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned short", us); + // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned short", static_cast(us)); + + const int i = 42; + printf("Integer %d from integer\n", i); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {} from integer", i); + + printf("Integer %i from integer\n", i); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {} from integer", i); + + const unsigned int ui = 42U; + printf("Integer %d from unsigned integer\n", ui); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned integer", ui); + // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned integer", static_cast(ui)); + + printf("Integer %i from unsigned integer\n", ui); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned integer", ui); + // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned integer", static_cast(ui)); + + const long l = 42L; + printf("Integer %ld from long\n", l); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {} from long", l); + + printf("Integer %li from long\n", l); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {} from long", l); + + const unsigned long ul = 42UL; + printf("Integer %ld from unsigned long\n", ul); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned long", ul); + // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned long", static_cast(ul)); + + printf("Integer %li from unsigned long\n", ul); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned long", ul); + // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned long", static_cast(ul)); + + const long long ll = 42LL; + printf("Integer %lld from long long\n", ll); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {} from long long", ll); + + printf("Integer %lli from long long\n", ll); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integer {} from long long", ll); + + const unsigned long long ull = 42ULL; + printf("Integer %lld from unsigned long long\n", ull); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned long long", ull); + // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned long long", static_cast(ull)); + + printf("Integer %lli from unsigned long long\n", ull); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned long long", ull); + // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned long long", static_cast(ull)); + + const intmax_t im = 42; + const std::intmax_t sim = im; + printf("Integers %jd and %jd from intmax_t\n", im, sim); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integers {} and {} from intmax_t", im, sim); + + printf("Integers %ji and %ji from intmax_t\n", im, sim); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integers {} and {} from intmax_t", im, sim); + + const uintmax_t uim = 42; + const std::uintmax_t suim = uim; + printf("Integers %jd and %jd from uintmax_t\n", uim, suim); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integers {} and {} from uintmax_t", uim, suim); + // CHECK-FIXES-STRICT: std::println("Integers {} and {} from uintmax_t", static_cast(uim), static_cast(suim)); + + printf("Integer %ji from intmax_t\n", uim); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from intmax_t", uim); + // CHECK-FIXES-STRICT: std::println("Integer {} from intmax_t", static_cast(uim)); + + const int ai[] = { 0, 1, 2, 3}; + const ptrdiff_t pd = &ai[3] - &ai[0]; + const std::ptrdiff_t spd = pd; + printf("Integers %td and %td from ptrdiff_t\n", pd, spd); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integers {} and {} from ptrdiff_t", pd, spd); + + printf("Integers %ti and %ti from ptrdiff_t\n", pd, spd); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Integers {} and {} from ptrdiff_t", pd, spd); + + const size_t z = 42UL; + const std::size_t sz = z; + printf("Integers %zd and %zd from size_t\n", z, sz); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Integers {} and {} from size_t", z, sz); + // CHECK-FIXES-STRICT: std::println("Integers {} and {} from size_t", static_cast(z), static_cast(sz)); +} + +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("Unsigned integer %u from bool\n", b); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integer {:d} from bool", b); + + const char c = 'A'; + printf("Unsigned integer %hhu from char\n", c); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {:d} from char", c); + // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from char", static_cast(c)); + + const signed char sc = 'A'; + printf("Unsigned integer %hhu from signed char\n", sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from signed char", sc); + // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from signed char", static_cast(sc)); + + printf("Unsigned integer %u from signed char\n", sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from signed char", sc); + // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from signed char", static_cast(sc)); + + const unsigned char uc = 'A'; + printf("Unsigned integer %hhu from unsigned char\n", uc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integer {} from unsigned char", uc); + + printf("Unsigned integer %u from unsigned char\n", uc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integer {} from unsigned char", uc); + + const int8_t i8 = 42; + printf("Unsigned integer %" PRIu8 " from int8_t\n", i8); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from int8_t", i8); + // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from int8_t", static_cast(i8)); + + const int_fast8_t if8 = 42; + printf("Unsigned integer %" PRIuFAST8 " from int_fast8_t\n", if8); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from int_fast8_t", if8); + // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from int_fast8_t", static_cast(if8)); + + const int_least8_t il8 = 42; + printf("Unsigned integer %" PRIuFAST8 " from int_least8_t\n", il8); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from int_least8_t", il8); + // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from int_least8_t", static_cast(il8)); + + const uint8_t u8 = 42U; + printf("Unsigned integer %" PRIu8 " from uint8_t\n", u8); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integer {} from uint8_t", u8); + + const uint_fast8_t uf8 = 42U; + printf("Unsigned integer %" PRIuFAST8 " from uint_fast8_t\n", uf8); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integer {} from uint_fast8_t", uf8); + + const uint_least8_t ul8 = 42U; + printf("Unsigned integer %" PRIuLEAST8 " from uint_least8_t\n", ul8); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integer {} from uint_least8_t", ul8); + + const short s = 42; + printf("Unsigned integer %hu from short\n", s); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from short", s); + // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from short", static_cast(s)); + + const unsigned short us = 42U; + printf("Unsigned integer %hu from unsigned short\n", us); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integer {} from unsigned short", us); + + const int i = 42; + printf("Unsigned integer %u from signed integer\n", i); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from signed integer", i); + // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from signed integer", static_cast(i)); + + const unsigned int ui = 42U; + printf("Unsigned integer %u from unsigned integer\n", ui); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integer {} from unsigned integer", ui); + + const long l = 42L; + printf("Unsigned integer %u from signed long\n", l); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from signed long", l); + // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from signed long", static_cast(l)); + + const unsigned long ul = 42UL; + printf("Unsigned integer %lu from unsigned long\n", ul); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integer {} from unsigned long", ul); + + const long long ll = 42LL; + printf("Unsigned integer %llu from long long\n", ll); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from long long", ll); + // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from long long", static_cast(ll)); + + const unsigned long long ull = 42ULL; + printf("Unsigned integer %llu from unsigned long long\n", ull); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integer {} from unsigned long long", ull); + + const intmax_t im = 42; + const std::intmax_t sim = im; + printf("Unsigned integers %ju and %ju from intmax_t\n", im, sim); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integers {} and {} from intmax_t", im, sim); + // CHECK-FIXES-STRICT: std::println("Unsigned integers {} and {} from intmax_t", static_cast(im), static_cast(sim)); + + const uintmax_t uim = 42U; + const std::uintmax_t suim = uim; + printf("Unsigned integers %ju and %ju from uintmax_t\n", uim, suim); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integers {} and {} from uintmax_t", uim, suim); + + const int ai[] = { 0, 1, 2, 3}; + const ptrdiff_t pd = &ai[3] - &ai[0]; + const std::ptrdiff_t spd = pd; + printf("Unsigned integers %tu and %tu from ptrdiff_t\n", pd, spd); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integers {} and {} from ptrdiff_t", pd, spd); + // CHECK-FIXES-STRICT: std::println("Unsigned integers {} and {} from ptrdiff_t", static_cast(pd), static_cast(spd)); + + const size_t z = 42U; + const std::size_t sz = z; + printf("Unsigned integers %zu and %zu from size_t\n", z, sz); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Unsigned integers {} and {} from size_t", z, sz); +} + +// 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: use 'std::println' instead of 'fprintf' [modernize-use-std-print] + // CHECK-FIXES: std::println(stderr, "Integer {} from integer", 42); + + fprintf(stderr, "Integer %i from integer\n", 65); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print] + // CHECK-FIXES: std::println(stderr, "Integer {} from integer", 65); + + fprintf(stderr, "Integer %i from char\n", 'A'); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println(stderr, "Integer {:d} from char", 'A'); + // CHECK-FIXES-STRICT: std::println(stderr, "Integer {} from char", static_cast('A')); + + fprintf(stderr, "Integer %d from char\n", 'A'); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print] + // CHECK-FIXES-NOTSTRICT: std::println(stderr, "Integer {:d} from char", 'A'); + // CHECK-FIXES-STRICT: std::println(stderr, "Integer {} from char", static_cast('A')); +} + +void printf_char() { + const char c = 'A'; + printf("Char %c from char\n", c); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Char {} from char", c); + + const signed char sc = 'A'; + printf("Char %c from signed char\n", sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Char {:c} from signed char", sc); + + const unsigned char uc = 'A'; + printf("Char %c from unsigned char\n", uc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Char {:c} from unsigned char", uc); + + const int i = 65; + printf("Char %c from integer\n", i); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Char {:c} from integer", i); + + const unsigned int ui = 65; + printf("Char %c from unsigned integer\n", ui); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Char {:c} from unsigned integer", ui); + + const unsigned long long ull = 65; + printf("Char %c from unsigned long long\n", ull); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Char {:c} from unsigned long long", ull); +} + +void printf_bases() { + printf("Hex %lx\n", 42L); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hex {:x}", 42L); + + printf("HEX %X\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("HEX {:X}", 42); + + printf("Oct %lo\n", 42L); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Oct {:o}", 42L); +} + +void printf_alternative_forms() { + printf("Hex %#lx\n", 42L); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hex {:#x}", 42L); + + printf("HEX %#X\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("HEX {:#X}", 42); + + printf("Oct %#lo\n", 42L); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Oct {:#o}", 42L); + + printf("Double %#f %#F\n", -42.0, -42.0); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Double {:#f} {:#F}", -42.0, -42.0); + + printf("Double %#g %#G\n", -42.0, -42.0); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Double {:#g} {:#G}", -42.0, -42.0); + + printf("Double %#e %#E\n", -42.0, -42.0); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Double {:#e} {:#E}", -42.0, -42.0); + + printf("Double %#a %#A\n", -42.0, -42.0); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Double {:#a} {:#A}", -42.0, -42.0); + + // Characters don't have an alternate form + printf("Char %#c\n", 'A'); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Char {}", 'A'); + + // Strings don't have an alternate form + printf("Char %#c\n", 'A'); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Char {}", 'A'); +} + +void printf_string() { + printf("Hello %s after\n", "Goodbye"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {} after", "Goodbye"); + + // std::print can't print signed char strings. + const signed char *sstring = reinterpret_cast("ustring"); + printf("signed char string %s\n", sstring); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("signed char string {}", reinterpret_cast(sstring)); + + // std::print can't print unsigned char strings. + const unsigned char *ustring = reinterpret_cast("ustring"); + printf("unsigned char string %s\n", ustring); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("unsigned char string {}", reinterpret_cast(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: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:f} after", f); + + printf("Hello %g after\n", f); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:g} after", f); + + printf("Hello %e after\n", f); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:e} after", 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: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:f} after", d); + + printf("Hello %g after\n", d); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:g} after", d); + + printf("Hello %e after\n", d); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:e} after", 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: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:f} after", ld); + + printf("Hello %g after\n", ld); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:g} after", ld); + + printf("Hello %e after\n", ld); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:e} after", ld); +} + +void printf_pointer() { + int i; + double j; + printf("Int* %p %s %p\n", &i, "Double*", &j); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Int* {} {} {}", static_cast(&i), "Double*", static_cast(&j)); + + printf("%p\n", nullptr); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("{}", nullptr); + + const auto np = nullptr; + printf("%p\n", np); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("{}", np); + + // NULL isn't a pointer, so std::print needs some help. + printf("%p\n", NULL); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("{}", static_cast(NULL)); + + printf("%p\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("{}", static_cast(42)); + + // If we already have a void pointer then no cast is required. + printf("%p\n", reinterpret_cast(44)); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("{}", reinterpret_cast(44)); + + const void *p; + printf("%p\n", p); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("{}", 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: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("{}", static_cast(pp)); + + printf("%p\n", printf_pointer); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("{}", static_cast(printf_pointer)); +} + +class AClass +{ + int member; + + void printf_this_pointer() + { + printf("%p\n", this); + // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("{}", static_cast(this)); + } + + void printf_pointer_to_member_function() + { + printf("%p\n", &AClass::printf_pointer_to_member_function); + // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("{}", static_cast(&AClass::printf_pointer_to_member_function)); + } + + void printf_pointer_to_member_variable() + { + printf("%p\n", &AClass::member); + // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("{}", static_cast(&AClass::member)); + } +}; + +void printf_positional_arg() { + printf("%1$d", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{0}", 42); + + printf("before %1$d", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("before {0}", 42); + + printf("%1$d after", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{0} after", 42); + + printf("before %1$d after", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-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: use 'std::print' instead of 'printf' [modernize-use-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: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Right-justified integer {:4} after", 42); + + printf("Right-justified double %4f\n", 227.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Right-justified double {:4f}", 227.2); + + printf("Right-justified double %4g\n", 227.4); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Right-justified double {:4g}", 227.4); + + printf("Right-justified integer with field width argument %*d after\n", 5, 424242); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Right-justified integer with field width argument {:{}} after", 5, 424242); + + printf("Right-justified integer with field width argument %2$*1$d after\n", 5, 424242); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Right-justified integer with field width argument {1:{0}} after", 5, 424242); + + printf("Right-justified string %20s\n", "Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Right-justified string {:>20}", "Hello"); + + printf("Right-justified string with field width argument %2$*1$s after\n", 20, "wibble"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Right-justified string with field width argument {1:>{0}} after", 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: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Left-justified integer {:<4}", 42); + + printf("Left-justified integer %--4d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Left-justified integer {:<4}", 42); + + printf("Left-justified double %-4f\n", 227.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Left-justified double {:<4f}", 227.2); + + printf("Left-justified double %-4g\n", 227.4); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Left-justified double {:<4g}", 227.4); + + printf("Left-justified integer with field width argument %-*d after\n", 5, 424242); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Left-justified integer with field width argument {:<{}} after", 5, 424242); + + printf("Left-justified integer with field width argument %2$-*1$d after\n", 5, 424242); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Left-justified integer with field width argument {1:<{0}} after", 5, 424242); + + printf("Left-justified string %-20s\n", "Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Left-justified string {:20}", "Hello"); + + printf("Left-justified string with field width argument %2$-*1$s after\n", 5, "wibble"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Left-justified string with field width argument {1:{0}} after", 5, "wibble"); +} + +void printf_precision() { + printf("Hello %.3f\n", 3.14159); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:.3f}", 3.14159); + + printf("Hello %10.3f\n", 3.14159); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:10.3f}", 3.14159); + + printf("Hello %.*f after\n", 10, 3.14159265358979323846); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:.{}f} after", 10, 3.14159265358979323846); + + printf("Hello %10.*f after\n", 3, 3.14159265358979323846); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:10.{}f} after", 3, 3.14159265358979323846); + + printf("Hello %*.*f after\n", 10, 4, 3.14159265358979323846); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:{}.{}f} after", 10, 4, 3.14159265358979323846); + + printf("Hello %1$.*2$f after\n", 3.14159265358979323846, 4); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {0:.{1}f} after", 3.14159265358979323846, 4); + + // Precision is ignored, but maintained on non-numeric arguments + printf("Hello %.5s\n", "Goodbye"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:.5}", "Goodbye"); + + printf("Hello %.5c\n", 'G'); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {:.5}", '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: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Hello {0:{1}.{2}f} after", 3.14159265358979323846, 4, 2); +} + +void printf_alternative_form() { + printf("Wibble %#x\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Wibble {:#x}", 42); + + printf("Wibble %#20x\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Wibble {:#20x}", 42); + + printf("Wibble %#020x\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Wibble {:#020x}", 42); + + printf("Wibble %#-20x\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Wibble {:<#20x}", 42); +} + +void printf_leading_plus() { + printf("Positive integer %+d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Positive integer {:+}", 42); + + printf("Positive double %+f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Positive double {:+f}", 42.2); + + printf("Positive double %+g\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Positive double {:+g}", 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: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Positive string {}", "string"); +} + +void printf_leading_space() { + printf("Spaced integer % d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Spaced integer {: }", 42); + + printf("Spaced integer %- d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Spaced integer {: }", 42); + + printf("Spaced double % f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Spaced double {: f}", 42.2); + + printf("Spaced double % g\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Spaced double {: g}", 42.2); +} + +void printf_leading_zero() { + printf("Leading zero integer %03d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Leading zero integer {:03}", 42); + + printf("Leading minus and zero integer %-03d minus ignored\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Leading minus and zero integer {:<03} minus ignored", 42); + + printf("Leading zero unsigned integer %03u\n", 42U); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Leading zero unsigned integer {:03}", 42U); + + printf("Leading zero double %03f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Leading zero double {:03f}", 42.2); + + printf("Leading zero double %03g\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Leading zero double {:03g}", 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: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Spaced integer {:+}", 42); + + printf("Spaced double %+ f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Spaced double {:+f}", 42.2); + + printf("Spaced double % +g\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Spaced double {:+g}", 42.2); +} + +void printf_leading_zero_and_plus() { + printf("Leading zero integer %+03d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Leading zero integer {:+03}", 42); + + printf("Leading zero double %0+3f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Leading zero double {:+03f}", 42.2); + + printf("Leading zero double %0+3g\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Leading zero double {:+03g}", 42.2); +} + +void printf_leading_zero_and_space() { + printf("Leading zero and space integer %0 3d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Leading zero and space integer {: 03}", 42); + + printf("Leading zero and space double %0 3f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Leading zero and space double {: 03f}", 42.2); + + printf("Leading zero and space double %0 3g\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("Leading zero and space double {: 03g}", 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: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(plain_enum)); + + SignedPlainEnum splain_enum; + printf("%d", splain_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(splain_enum)); + + BoolEnum bool_enum; + printf("%d", bool_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(bool_enum)); + + CharEnum char_enum; + printf("%d", char_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(char_enum)); + + SCharEnum schar_enum; + printf("%d", schar_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(schar_enum)); + + UCharEnum uchar_enum; + printf("%d", uchar_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(uchar_enum)); + + ShortEnum short_enum; + printf("%d", short_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(short_enum)); + + UShortEnum ushort_enum; + printf("%d", ushort_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(ushort_enum)); + + IntEnum int_enum; + printf("%d", int_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(int_enum)); + + UIntEnum uint_enum; + printf("%d", uint_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(uint_enum)); + + LongEnum long_enum; + printf("%d", long_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(long_enum)); + + ULongEnum ulong_enum; + printf("%d", ulong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(ulong_enum)); + + LongLongEnum longlong_enum; + printf("%d", longlong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(longlong_enum)); + + ULongLongEnum ulonglong_enum; + printf("%d", ulonglong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(ulonglong_enum)); +} + +void printf_enum_u() { + PlainEnum plain_enum; + printf("%u", plain_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(plain_enum)); + + SignedPlainEnum splain_enum; + printf("%u", splain_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(splain_enum)); + + BoolEnum bool_enum; + printf("%u", bool_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(bool_enum)); + + CharEnum char_enum; + printf("%u", char_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(char_enum)); + + SCharEnum schar_enum; + printf("%u", schar_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(schar_enum)); + + UCharEnum uchar_enum; + printf("%u", uchar_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(uchar_enum)); + + ShortEnum short_enum; + printf("%u", short_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(short_enum)); + + UShortEnum ushort_enum; + printf("%u", ushort_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(ushort_enum)); + + IntEnum int_enum; + printf("%u", int_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(int_enum)); + + UIntEnum uint_enum; + printf("%u", uint_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(uint_enum)); + + LongEnum long_enum; + printf("%u", long_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(long_enum)); + + ULongEnum ulong_enum; + printf("%u", ulong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(ulong_enum)); + + LongLongEnum longlong_enum; + printf("%u", longlong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(longlong_enum)); + + ULongLongEnum ulonglong_enum; + printf("%u", ulonglong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("{}", static_cast(ulonglong_enum)); +} + +void printf_string_function(const char *(*callback)()) { + printf("printf string from callback %s", callback()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("printf string from callback {}", callback()); +} + +template +struct X +{ + const CharType *str() const; +}; + +void printf_string_member_function(const X &x, const X &cx) { + printf("printf string from member function %s", x.str()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("printf string from member function {}", x.str()); + + printf("printf string from member function on const %s", cx.str()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("printf string from member function on const {}", cx.str()); +} + +void printf_string_cstr(const std::string &s1, const std::string &s2) { + printf("printf string one c_str %s", s1.c_str()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("printf string one c_str {}", s1); + + printf("printf string two c_str %s %s\n", s1.c_str(), s2.data()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("printf string two c_str {} {}", s1, s2); +} + +void printf_not_char_string_cstr(const std::wstring &ws1) { + // This test is to check that we only remove + // std::basic_string::c_str()/data() when CharType is char. I've + // been unable to come up with a genuine situation where someone would have + // actually successfully called those methods when this isn't the case without + // -Wformat warning, but it seems sensible to restrict removal regardless. + printf("printf bogus wstring c_str %s", ws1.c_str()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("printf bogus wstring c_str {}", ws1.c_str()); +} + +void fprintf_string_cstr(const std::string &s1) { + fprintf(stderr, "fprintf string c_str %s", s1.c_str()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print] + // CHECK-FIXES: std::print(stderr, "fprintf string c_str {}", s1); +} + +void printf_string_pointer_cstr(const std::string *s1, const std::string *s2) { + printf("printf string pointer one c_str %s", s1->c_str()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("printf string pointer one c_str {}", *s1); + + printf("printf string pointer two c_str %s %s\n", s1->c_str(), s2->data()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("printf string pointer two c_str {} {}", *s1, *s2); +} + +void fprintf_string_pointer_cstr(const std::string *s1) { + fprintf(stderr, "fprintf string pointer c_str %s", s1->c_str()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print] + // CHECK-FIXES: std::print(stderr, "fprintf string pointer c_str {}", *s1); +} + +template +struct iterator { + T *operator->(); + T &operator*(); +}; + +void printf_iterator_cstr(iterator i1, iterator i2) +{ + printf("printf iterator c_str %s %s\n", i1->c_str(), i2->data()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::println("printf iterator c_str {} {}", *i1, *i2); +} + +// Something that isn't std::string, so the calls to c_str() and data() must not +// be removed even though the printf call will be replaced. +struct S +{ + const char *c_str() const; + const char *data() const; +}; + +void p(S s1, S *s2) +{ + printf("Not std::string %s %s", s1.c_str(), s2->c_str()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("Not std::string {} {}", s1.c_str(), s2->c_str()); + + printf("Not std::string %s %s", s1.data(), s2->data()); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print] + // CHECK-FIXES: std::print("Not std::string {} {}", s1.data(), s2->data()); +}