17#include "clang/AST/Expr.h"
18#include "clang/ASTMatchers/ASTMatchFinder.h"
19#include "clang/Basic/LangOptions.h"
20#include "clang/Lex/Lexer.h"
21#include "clang/Lex/Preprocessor.h"
22#include "clang/Tooling/FixIt.h"
23#include "llvm/ADT/StringExtras.h"
24#include "llvm/Support/Debug.h"
27using namespace clang::analyze_printf;
30using clang::analyze_format_string::ConversionSpecifier;
35 using namespace clang;
36 const Type *DesugaredType = Ty->getUnqualifiedDesugaredType();
37 if (
const auto *BT = llvm::dyn_cast<BuiltinType>(DesugaredType))
38 return (BT->getKind() == BuiltinType::Char_U ||
39 BT->getKind() == BuiltinType::Char_S);
46static std::optional<std::string>
48 using namespace clang;
49 const auto UQT = QT.getUnqualifiedType();
50 if (
const auto *BT = llvm::dyn_cast<BuiltinType>(UQT)) {
51 switch (BT->getKind()) {
52 case BuiltinType::UChar:
53 case BuiltinType::Char_U:
54 case BuiltinType::SChar:
55 case BuiltinType::Char_S:
57 case BuiltinType::UShort:
58 case BuiltinType::Short:
60 case BuiltinType::UInt:
61 case BuiltinType::Int:
63 case BuiltinType::ULong:
64 case BuiltinType::Long:
66 case BuiltinType::ULongLong:
67 case BuiltinType::LongLong:
70 llvm::dbgs() <<
"Unknown corresponding signed type for BuiltinType '"
71 << QT.getAsString() <<
"'\n";
78 const std::string TypeName = UQT.getAsString();
79 StringRef SimplifiedTypeName{TypeName};
80 const bool InStd = SimplifiedTypeName.consume_front(
"std::");
81 const StringRef Prefix = InStd ?
"std::" :
"";
83 if (SimplifiedTypeName.starts_with(
"uint") &&
84 SimplifiedTypeName.ends_with(
"_t"))
85 return (Twine(Prefix) + SimplifiedTypeName.drop_front()).str();
87 if (SimplifiedTypeName ==
"size_t")
88 return (Twine(Prefix) +
"ssize_t").str();
90 llvm::dbgs() <<
"Unknown corresponding signed type for non-BuiltinType '"
91 << UQT.getAsString() <<
"'\n";
98static std::optional<std::string>
100 using namespace clang;
101 const auto UQT = QT.getUnqualifiedType();
102 if (
const auto *BT = llvm::dyn_cast<BuiltinType>(UQT)) {
103 switch (BT->getKind()) {
104 case BuiltinType::SChar:
105 case BuiltinType::Char_S:
106 case BuiltinType::UChar:
107 case BuiltinType::Char_U:
108 return "unsigned char";
109 case BuiltinType::Short:
110 case BuiltinType::UShort:
111 return "unsigned short";
112 case BuiltinType::Int:
113 case BuiltinType::UInt:
114 return "unsigned int";
115 case BuiltinType::Long:
116 case BuiltinType::ULong:
117 return "unsigned long";
118 case BuiltinType::LongLong:
119 case BuiltinType::ULongLong:
120 return "unsigned long long";
122 llvm::dbgs() <<
"Unknown corresponding unsigned type for BuiltinType '"
123 << UQT.getAsString() <<
"'\n";
130 const std::string TypeName = UQT.getAsString();
131 StringRef SimplifiedTypeName{TypeName};
132 const bool InStd = SimplifiedTypeName.consume_front(
"std::");
133 const StringRef Prefix = InStd ?
"std::" :
"";
135 if (SimplifiedTypeName.starts_with(
"int") &&
136 SimplifiedTypeName.ends_with(
"_t"))
137 return (Twine(Prefix) +
"u" + SimplifiedTypeName).str();
139 if (SimplifiedTypeName ==
"ssize_t")
140 return (Twine(Prefix) +
"size_t").str();
141 if (SimplifiedTypeName ==
"ptrdiff_t")
142 return (Twine(Prefix) +
"size_t").str();
144 llvm::dbgs() <<
"Unknown corresponding unsigned type for non-BuiltinType '"
145 << UQT.getAsString() <<
"'\n";
149static std::optional<std::string>
151 const clang::QualType &QT) {
152 if (ArgKind == ConversionSpecifier::Kind::uArg)
158 const clang::QualType &ArgType) {
159 if (
const auto *BT = llvm::dyn_cast<BuiltinType>(ArgType)) {
162 const auto ArgTypeKind = BT->getKind();
163 if (ArgTypeKind == BuiltinType::Char_U ||
164 ArgTypeKind == BuiltinType::Char_S)
168 if (ArgKind == ConversionSpecifier::Kind::uArg)
169 return ArgType->isUnsignedIntegerType();
170 return ArgType->isSignedIntegerType();
174AST_MATCHER(clang::QualType, isRealChar) {
192 const FunctionDecl *
FuncDecl = Call->getDirectCallee();
200 ASTContext *ContextIn,
const CallExpr *Call,
unsigned FormatArgOffset,
201 const Configuration ConfigIn,
const LangOptions &LO, SourceManager &SM,
203 : Context(ContextIn), Config(ConfigIn),
204 CastMismatchedIntegerTypes(
206 Args(Call->getArgs()), NumArgs(Call->getNumArgs()),
207 ArgsOffset(FormatArgOffset + 1), LangOpts(LO) {
208 assert(ArgsOffset <= NumArgs);
209 FormatExpr = llvm::dyn_cast<StringLiteral>(
210 Args[FormatArgOffset]->IgnoreUnlessSpelledInSource());
212 assert(FormatExpr && FormatExpr->isOrdinary());
214 if (
const std::optional<StringRef> MaybeMacroName =
215 formatStringContainsUnreplaceableMacro(Call, FormatExpr, SM, PP);
217 conversionNotPossible(
218 (
"format string contains unreplaceable macro '" + *MaybeMacroName +
"'")
223 PrintfFormatString = FormatExpr->getString();
227 const size_t EstimatedGrowth = 8;
228 StandardFormatString.reserve(PrintfFormatString.size() + EstimatedGrowth);
229 StandardFormatString.push_back(
'\"');
231 const bool IsFreeBsdkPrintf =
false;
233 using clang::analyze_format_string::ParsePrintfString;
234 ParsePrintfString(*
this, PrintfFormatString.data(),
235 PrintfFormatString.data() + PrintfFormatString.size(),
236 LangOpts, Context->getTargetInfo(), IsFreeBsdkPrintf);
237 finalizeFormatText();
240std::optional<StringRef>
241FormatStringConverter::formatStringContainsUnreplaceableMacro(
242 const CallExpr *Call,
const StringLiteral *FormatExpr, SourceManager &SM,
247 std::optional<StringRef> MaybeSurroundingMacroName;
248 if (
const SourceLocation BeginCallLoc = Call->getBeginLoc();
249 BeginCallLoc.isMacroID())
250 MaybeSurroundingMacroName =
251 Lexer::getImmediateMacroName(BeginCallLoc, SM, PP.getLangOpts());
253 for (
auto I = FormatExpr->tokloc_begin(), E = FormatExpr->tokloc_end();
255 const SourceLocation &TokenLoc = *I;
256 if (TokenLoc.isMacroID()) {
257 const StringRef MacroName =
258 Lexer::getImmediateMacroName(TokenLoc, SM, PP.getLangOpts());
260 if (MaybeSurroundingMacroName != MacroName) {
263 if (!MacroName.starts_with(
"PRI") && !MacroName.starts_with(
"__PRI"))
266 const SourceLocation TokenSpellingLoc = SM.getSpellingLoc(TokenLoc);
267 const OptionalFileEntryRef MaybeFileEntry =
268 SM.getFileEntryRefForID(SM.getFileID(TokenSpellingLoc));
272 HeaderSearch &HS = PP.getHeaderSearchInfo();
274 if (!isSystem(HS.getFileDirFlavor(*MaybeFileEntry)) ||
275 llvm::sys::path::filename(MaybeFileEntry->getName()) !=
284void FormatStringConverter::emitAlignment(
const PrintfSpecifier &FS,
285 std::string &FormatSpec) {
286 const ConversionSpecifier::Kind ArgKind =
287 FS.getConversionSpecifier().getKind();
290 if (FS.getFieldWidth().getHowSpecified() != OptionalAmount::NotSpecified) {
291 if (ArgKind == ConversionSpecifier::sArg) {
294 if (!FS.isLeftJustified())
295 FormatSpec.push_back(
'>');
299 if (FS.isLeftJustified())
300 FormatSpec.push_back(
'<');
305void FormatStringConverter::emitSign(
const PrintfSpecifier &FS,
306 std::string &FormatSpec) {
312 if (Spec.isAnyIntArg() || Spec.isDoubleArg()) {
314 if (FS.hasPlusPrefix())
315 FormatSpec.push_back(
'+');
316 else if (FS.hasSpacePrefix())
317 FormatSpec.push_back(
' ');
321void FormatStringConverter::emitAlternativeForm(
const PrintfSpecifier &FS,
322 std::string &FormatSpec) {
323 if (FS.hasAlternativeForm()) {
324 switch (FS.getConversionSpecifier().getKind()) {
325 case ConversionSpecifier::Kind::aArg:
326 case ConversionSpecifier::Kind::AArg:
327 case ConversionSpecifier::Kind::eArg:
328 case ConversionSpecifier::Kind::EArg:
329 case ConversionSpecifier::Kind::fArg:
330 case ConversionSpecifier::Kind::FArg:
331 case ConversionSpecifier::Kind::gArg:
332 case ConversionSpecifier::Kind::GArg:
333 case ConversionSpecifier::Kind::xArg:
334 case ConversionSpecifier::Kind::XArg:
335 case ConversionSpecifier::Kind::oArg:
336 FormatSpec.push_back(
'#');
345void FormatStringConverter::emitFieldWidth(
const PrintfSpecifier &FS,
346 std::string &FormatSpec) {
348 const OptionalAmount FieldWidth = FS.getFieldWidth();
349 switch (FieldWidth.getHowSpecified()) {
350 case OptionalAmount::NotSpecified:
352 case OptionalAmount::Constant:
353 FormatSpec.append(llvm::utostr(FieldWidth.getConstantAmount()));
355 case OptionalAmount::Arg:
356 FormatSpec.push_back(
'{');
357 if (FieldWidth.usesPositionalArg()) {
360 assert(FieldWidth.getPositionalArgIndex() > 0U);
361 FormatSpec.append(llvm::utostr(FieldWidth.getPositionalArgIndex() - 1));
363 FormatSpec.push_back(
'}');
365 case OptionalAmount::Invalid:
371void FormatStringConverter::emitPrecision(
const PrintfSpecifier &FS,
372 std::string &FormatSpec) {
373 const OptionalAmount FieldPrecision = FS.getPrecision();
374 switch (FieldPrecision.getHowSpecified()) {
375 case OptionalAmount::NotSpecified:
377 case OptionalAmount::Constant:
378 FormatSpec.push_back(
'.');
379 FormatSpec.append(llvm::utostr(FieldPrecision.getConstantAmount()));
381 case OptionalAmount::Arg:
382 FormatSpec.push_back(
'.');
383 FormatSpec.push_back(
'{');
384 if (FieldPrecision.usesPositionalArg()) {
387 assert(FieldPrecision.getPositionalArgIndex() > 0U);
389 llvm::utostr(FieldPrecision.getPositionalArgIndex() - 1));
391 FormatSpec.push_back(
'}');
393 case OptionalAmount::Invalid:
398void FormatStringConverter::maybeRotateArguments(
const PrintfSpecifier &FS) {
399 unsigned ArgCount = 0;
400 const OptionalAmount FieldWidth = FS.getFieldWidth();
401 const OptionalAmount FieldPrecision = FS.getPrecision();
403 if (FieldWidth.getHowSpecified() == OptionalAmount::Arg &&
404 !FieldWidth.usesPositionalArg())
406 if (FieldPrecision.getHowSpecified() == OptionalAmount::Arg &&
407 !FieldPrecision.usesPositionalArg())
411 ArgRotates.emplace_back(FS.getArgIndex() + ArgsOffset, ArgCount);
414void FormatStringConverter::emitStringArgument(
unsigned ArgIndex,
422 if (!StringCStrCallExprMatcher) {
424 const auto StringDecl = type(hasUnqualifiedDesugaredType(recordType(
425 hasDeclaration(cxxRecordDecl(hasName(
"::std::basic_string"))))));
426 const auto StringExpr = expr(
427 anyOf(hasType(StringDecl), hasType(qualType(pointsTo(StringDecl)))));
429 StringCStrCallExprMatcher =
431 on(StringExpr.bind(
"arg")), callee(memberExpr().bind(
"member")),
432 callee(cxxMethodDecl(hasAnyName(
"c_str",
"data"),
433 returns(pointerType(pointee(isRealChar()))))))
437 auto CStrMatches =
match(*StringCStrCallExprMatcher, *Arg, *Context);
438 if (CStrMatches.size() == 1)
439 ArgCStrRemovals.push_back(CStrMatches.front());
440 else if (Arg->getType()->isPointerType()) {
441 const QualType
Pointee = Arg->getType()->getPointeeType();
445 ArgFixes.emplace_back(ArgIndex,
"reinterpret_cast<const char *>(");
449bool FormatStringConverter::emitIntegerArgument(
450 ConversionSpecifier::Kind ArgKind,
const Expr *Arg,
unsigned ArgIndex,
451 std::string &FormatSpec) {
452 const clang::QualType &ArgType = Arg->getType();
453 if (ArgType->isBooleanType()) {
457 FormatSpec.push_back(
'd');
458 }
else if (ArgType->isEnumeralType()) {
464 if (
const auto *ED = ArgType->getAsEnumDecl()) {
465 if (
const std::optional<std::string> MaybeCastType =
467 ArgFixes.emplace_back(
468 ArgIndex, (Twine(
"static_cast<") + *MaybeCastType +
">(").str());
470 return conversionNotPossible(
471 (Twine(
"argument ") + Twine(ArgIndex) +
" has unexpected enum type")
474 }
else if (CastMismatchedIntegerTypes &&
479 if (
const std::optional<std::string> MaybeCastType =
481 ArgFixes.emplace_back(
482 ArgIndex, (Twine(
"static_cast<") + *MaybeCastType +
">(").str());
484 return conversionNotPossible(
485 (Twine(
"argument ") + Twine(ArgIndex) +
" cannot be cast to " +
486 Twine(ArgKind == ConversionSpecifier::Kind::uArg ?
"unsigned"
488 " integer type to match format"
489 " specifier and StrictMode is enabled")
491 }
else if (
isRealCharType(ArgType) || !ArgType->isIntegerType()) {
493 FormatSpec.push_back(
'd');
501bool FormatStringConverter::emitType(
const PrintfSpecifier &FS,
const Expr *Arg,
502 std::string &FormatSpec) {
503 const ConversionSpecifier::Kind ArgKind =
504 FS.getConversionSpecifier().getKind();
506 case ConversionSpecifier::Kind::sArg:
507 emitStringArgument(FS.getArgIndex() + ArgsOffset, Arg);
509 case ConversionSpecifier::Kind::cArg:
513 FormatSpec.push_back(
'c');
515 case ConversionSpecifier::Kind::dArg:
516 case ConversionSpecifier::Kind::iArg:
517 case ConversionSpecifier::Kind::uArg:
518 if (!emitIntegerArgument(ArgKind, Arg, FS.getArgIndex() + ArgsOffset,
522 case ConversionSpecifier::Kind::pArg: {
523 const clang::QualType &ArgType = Arg->getType();
525 if (!ArgType->isNullPtrType() && !ArgType->isVoidPointerType())
526 ArgFixes.emplace_back(FS.getArgIndex() + ArgsOffset,
527 "static_cast<const void *>(");
530 case ConversionSpecifier::Kind::xArg:
531 FormatSpec.push_back(
'x');
533 case ConversionSpecifier::Kind::XArg:
534 FormatSpec.push_back(
'X');
536 case ConversionSpecifier::Kind::oArg:
537 FormatSpec.push_back(
'o');
539 case ConversionSpecifier::Kind::aArg:
540 FormatSpec.push_back(
'a');
542 case ConversionSpecifier::Kind::AArg:
543 FormatSpec.push_back(
'A');
545 case ConversionSpecifier::Kind::eArg:
546 FormatSpec.push_back(
'e');
548 case ConversionSpecifier::Kind::EArg:
549 FormatSpec.push_back(
'E');
551 case ConversionSpecifier::Kind::fArg:
552 FormatSpec.push_back(
'f');
554 case ConversionSpecifier::Kind::FArg:
555 FormatSpec.push_back(
'F');
557 case ConversionSpecifier::Kind::gArg:
558 FormatSpec.push_back(
'g');
560 case ConversionSpecifier::Kind::GArg:
561 FormatSpec.push_back(
'G');
565 return conversionNotPossible((Twine(
"argument ") +
566 Twine(FS.getArgIndex() + ArgsOffset) +
567 " has an unsupported format specifier")
577bool FormatStringConverter::convertArgument(
const PrintfSpecifier &FS,
579 std::string &StandardFormatString) {
581 assert(FS.consumesDataArgument());
583 StandardFormatString.push_back(
'{');
585 if (FS.usesPositionalArg()) {
588 assert(FS.getPositionalArgIndex() > 0U);
589 StandardFormatString.append(llvm::utostr(FS.getPositionalArgIndex() - 1));
594 std::string FormatSpec;
599 emitAlignment(FS, FormatSpec);
600 emitSign(FS, FormatSpec);
601 emitAlternativeForm(FS, FormatSpec);
603 if (FS.hasLeadingZeros())
604 FormatSpec.push_back(
'0');
606 emitFieldWidth(FS, FormatSpec);
607 emitPrecision(FS, FormatSpec);
608 maybeRotateArguments(FS);
610 if (!emitType(FS, Arg, FormatSpec))
613 if (!FormatSpec.empty()) {
614 StandardFormatString.push_back(
':');
615 StandardFormatString.append(FormatSpec);
618 StandardFormatString.push_back(
'}');
623bool FormatStringConverter::HandlePrintfSpecifier(
const PrintfSpecifier &FS,
624 const char *StartSpecifier,
625 unsigned SpecifierLen,
626 const TargetInfo &Target) {
628 const size_t StartSpecifierPos = StartSpecifier - PrintfFormatString.data();
629 assert(StartSpecifierPos + SpecifierLen <= PrintfFormatString.size());
632 assert(StartSpecifierPos >= PrintfFormatStringPos);
634 appendFormatText(StringRef(PrintfFormatString.begin() + PrintfFormatStringPos,
635 StartSpecifierPos - PrintfFormatStringPos));
637 const ConversionSpecifier::Kind ArgKind =
638 FS.getConversionSpecifier().getKind();
641 PrintfFormatStringPos = StartSpecifierPos + SpecifierLen;
642 assert(PrintfFormatStringPos <= PrintfFormatString.size());
644 FormatStringNeededRewriting =
true;
646 if (ArgKind == ConversionSpecifier::Kind::nArg) {
648 return conversionNotPossible(
"'%n' is not supported in format string");
651 if (ArgKind == ConversionSpecifier::Kind::PrintErrno) {
656 return conversionNotPossible(
"'%m' is not supported in format string");
659 if (ArgKind == ConversionSpecifier::PercentArg) {
660 StandardFormatString.push_back(
'%');
664 const unsigned ArgIndex = FS.getArgIndex() + ArgsOffset;
665 if (ArgIndex >= NumArgs) {
667 return conversionNotPossible(
668 (Twine(
"argument index ") + Twine(ArgIndex) +
" is out of range")
672 return convertArgument(FS, Args[ArgIndex]->IgnoreImplicitAsWritten(),
673 StandardFormatString);
678void FormatStringConverter::finalizeFormatText() {
680 StringRef(PrintfFormatString.begin() + PrintfFormatStringPos,
681 PrintfFormatString.size() - PrintfFormatStringPos));
682 PrintfFormatStringPos = PrintfFormatString.size();
687 const auto StandardFormatStringRef = StringRef(StandardFormatString);
688 if (Config.AllowTrailingNewlineRemoval &&
689 StandardFormatStringRef.ends_with(
"\\n") &&
690 !StandardFormatStringRef.ends_with(
"\\\\n") &&
691 !StandardFormatStringRef.ends_with(
"\\r\\n")) {
692 UsePrintNewlineFunction =
true;
693 FormatStringNeededRewriting =
true;
694 StandardFormatString.erase(StandardFormatString.end() - 2,
695 StandardFormatString.end());
698 StandardFormatString.push_back(
'\"');
702void FormatStringConverter::appendFormatText(
const StringRef Text) {
703 for (
const char Ch : Text) {
705 StandardFormatString +=
"\\a";
707 StandardFormatString +=
"\\b";
709 StandardFormatString +=
"\\f";
711 StandardFormatString +=
"\\n";
713 StandardFormatString +=
"\\r";
715 StandardFormatString +=
"\\t";
717 StandardFormatString +=
"\\v";
719 StandardFormatString +=
"\\\"";
721 StandardFormatString +=
"\\\\";
722 else if (Ch ==
'{') {
723 StandardFormatString +=
"{{";
724 FormatStringNeededRewriting =
true;
725 }
else if (Ch ==
'}') {
726 StandardFormatString +=
"}}";
727 FormatStringNeededRewriting =
true;
728 }
else if (Ch < 32) {
729 StandardFormatString +=
"\\x";
730 StandardFormatString += llvm::hexdigit(Ch >> 4,
true);
731 StandardFormatString += llvm::hexdigit(Ch & 0xf,
true);
733 StandardFormatString += Ch;
738 ASTContext &Context) {
739 const auto *Arg = CStrRemovalMatch.getNodeAs<Expr>(
"arg");
740 const auto *Member = CStrRemovalMatch.getNodeAs<MemberExpr>(
"member");
741 const bool Arrow = Member->isArrow();
743 : tooling::fixit::getText(*Arg, Context).str();
749 if (FormatStringNeededRewriting) {
750 Diag << FixItHint::CreateReplacement(
751 CharSourceRange::getTokenRange(FormatExpr->getBeginLoc(),
752 FormatExpr->getEndLoc()),
753 StandardFormatString);
757 for (
auto [ValueArgIndex, ArgCount] : ArgRotates) {
758 assert(ValueArgIndex < NumArgs);
759 assert(ValueArgIndex > ArgCount);
763 if (
const auto CStrRemovalMatch =
764 std::find_if(ArgCStrRemovals.cbegin(), ArgCStrRemovals.cend(),
765 [ArgStartPos = Args[ValueArgIndex]->getBeginLoc()](
766 const BoundNodes &Match) {
769 const Expr *CStrArg = Match.getNodeAs<Expr>(
"arg");
770 return ArgStartPos == CStrArg->getBeginLoc();
772 CStrRemovalMatch != ArgCStrRemovals.end()) {
773 const std::string ArgText =
775 assert(!ArgText.empty());
777 Diag << FixItHint::CreateReplacement(
778 Args[ValueArgIndex - ArgCount]->getSourceRange(), ArgText);
781 ArgCStrRemovals.erase(CStrRemovalMatch);
783 Diag << tooling::fixit::createReplacement(*Args[ValueArgIndex - ArgCount],
784 *Args[ValueArgIndex], *Context);
788 for (
size_t Offset = 0; Offset < ArgCount; ++Offset)
789 Diag << tooling::fixit::createReplacement(
790 *Args[ValueArgIndex - Offset], *Args[ValueArgIndex - Offset - 1],
796 for (
auto &ArgFix : ArgFixes) {
797 if (ArgFix.ArgIndex == ValueArgIndex)
798 ArgFix.ArgIndex = ValueArgIndex - ArgCount;
802 for (
const auto &[ArgIndex, Replacement] : ArgFixes) {
803 const SourceLocation AfterOtherSide =
804 Lexer::findNextToken(Args[ArgIndex]->getEndLoc(), SM, LangOpts)
807 Diag << FixItHint::CreateInsertion(Args[ArgIndex]->getBeginLoc(),
809 << FixItHint::CreateInsertion(AfterOtherSide,
")",
true);
812 for (
const auto &Match : ArgCStrRemovals) {
813 const auto *Call = Match.getNodeAs<CallExpr>(
"call");
815 if (!ArgText.empty())
816 Diag << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText);
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
std::string formatDereference(const Expr &ExprNode, const ASTContext &Context)
static std::string withoutCStrReplacement(const BoundNodes &CStrRemovalMatch, ASTContext &Context)
static std::optional< std::string > getCorrespondingSignedTypeName(const clang::QualType &QT)
If possible, return the text name of the signed type that corresponds to the passed integer type.
static bool castMismatchedIntegerTypes(const CallExpr *Call, bool StrictMode)
static bool isRealCharType(const clang::QualType &Ty)
Is the passed type the actual "char" type, whether that be signed or unsigned, rather than explicit s...
static std::optional< std::string > castTypeForArgument(ConversionSpecifier::Kind ArgKind, const clang::QualType &QT)
static std::optional< std::string > getCorrespondingUnsignedTypeName(const clang::QualType &QT)
If possible, return the text name of the unsigned type that corresponds to the passed integer type.
static bool isMatchingSignedness(ConversionSpecifier::Kind ArgKind, const clang::QualType &ArgType)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static constexpr const char FuncDecl[]