blob: 951e0acf79b1ae328cb085dc63200c62d608a17d [file] [log] [blame]
//===--- 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