| //===--- 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<BuiltinType>(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<std::string> |
| getCorrespondingSignedTypeName(const clang::QualType &QT) { |
| using namespace clang; |
| const auto UQT = QT.getUnqualifiedType(); |
| if (const auto *BT = llvm::dyn_cast<BuiltinType>(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 <cstdint>. 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<std::string> |
| getCorrespondingUnsignedTypeName(const clang::QualType &QT) { |
| using namespace clang; |
| const auto UQT = QT.getUnqualifiedType(); |
| if (const auto *BT = llvm::dyn_cast<BuiltinType>(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 <cstdint>. 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<std::string> |
| 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<BuiltinType>(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<StringLiteral>( |
| 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::maybeRotateArguments(const PrintfSpecifier &FS) { |
| unsigned ArgCount = 0; |
| const OptionalAmount FieldWidth = FS.getFieldWidth(); |
| const OptionalAmount FieldPrecision = FS.getPrecision(); |
| |
| if (FieldWidth.getHowSpecified() == OptionalAmount::Arg && |
| !FieldWidth.usesPositionalArg()) |
| ++ArgCount; |
| if (FieldPrecision.getHowSpecified() == OptionalAmount::Arg && |
| !FieldPrecision.usesPositionalArg()) |
| ++ArgCount; |
| |
| if (ArgCount) |
| ArgRotates.emplace_back(FS.getArgIndex() + ArgsOffset, ArgCount); |
| } |
| |
| 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<const char *>("); |
| } |
| } |
| |
| 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<EnumType>()) { |
| if (const std::optional<std::string> 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<std::string> 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<const void *>("); |
| break; |
| } |
| case ConversionSpecifier::Kind::xArg: |
| FormatSpec.push_back('x'); |
| break; |
| case ConversionSpecifier::Kind::XArg: |
| FormatSpec.push_back('X'); |
| break; |
| case ConversionSpecifier::Kind::oArg: |
| FormatSpec.push_back('o'); |
| break; |
| case ConversionSpecifier::Kind::aArg: |
| FormatSpec.push_back('a'); |
| break; |
| case ConversionSpecifier::Kind::AArg: |
| FormatSpec.push_back('A'); |
| break; |
| case ConversionSpecifier::Kind::eArg: |
| FormatSpec.push_back('e'); |
| break; |
| case ConversionSpecifier::Kind::EArg: |
| FormatSpec.push_back('E'); |
| break; |
| case ConversionSpecifier::Kind::fArg: |
| FormatSpec.push_back('f'); |
| break; |
| case ConversionSpecifier::Kind::FArg: |
| FormatSpec.push_back('F'); |
| break; |
| case ConversionSpecifier::Kind::gArg: |
| FormatSpec.push_back('g'); |
| break; |
| case ConversionSpecifier::Kind::GArg: |
| FormatSpec.push_back('G'); |
| break; |
| default: |
| // Something we don't understand |
| 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); |
| maybeRotateArguments(FS); |
| |
| 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(); |
| |
| // It's clearer to convert printf("Hello\r\n"); to std::print("Hello\r\n") |
| // than to std::println("Hello\r"); |
| if (StringRef(StandardFormatString).ends_with("\\n") && |
| !StringRef(StandardFormatString).ends_with("\\\\n") && |
| !StringRef(StandardFormatString).ends_with("\\r\\n")) { |
| 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<CallExpr>("call"); |
| const auto *Arg = Match.getNodeAs<Expr>("arg"); |
| const auto *Member = Match.getNodeAs<MemberExpr>("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); |
| } |
| |
| // ArgCount is one less than the number of arguments to be rotated. |
| for (auto [ValueArgIndex, ArgCount] : ArgRotates) { |
| assert(ValueArgIndex < NumArgs); |
| assert(ValueArgIndex > ArgCount); |
| |
| // First move the value argument to the right place. |
| Diag << tooling::fixit::createReplacement(*Args[ValueArgIndex - ArgCount], |
| *Args[ValueArgIndex], *Context); |
| |
| // Now shift down the field width and precision (if either are present) to |
| // accommodate it. |
| for (size_t Offset = 0; Offset < ArgCount; ++Offset) |
| Diag << tooling::fixit::createReplacement( |
| *Args[ValueArgIndex - Offset], *Args[ValueArgIndex - Offset - 1], |
| *Context); |
| } |
| } |
| } // namespace clang::tidy::utils |