| //== PrintfFormatString.cpp - Analysis of printf format strings --*- 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Handling of format string in printf and friends. The structure of format |
| // strings for fprintf() are described in C99 7.19.6.1. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "FormatStringParsing.h" |
| #include "clang/AST/FormatString.h" |
| #include "clang/AST/OSLog.h" |
| #include "clang/Basic/TargetInfo.h" |
| #include "llvm/Support/Regex.h" |
| |
| using clang::analyze_format_string::ArgType; |
| using clang::analyze_format_string::FormatStringHandler; |
| using clang::analyze_format_string::LengthModifier; |
| using clang::analyze_format_string::OptionalAmount; |
| using clang::analyze_format_string::ConversionSpecifier; |
| using clang::analyze_printf::PrintfSpecifier; |
| |
| using namespace clang; |
| |
| typedef clang::analyze_format_string::SpecifierResult<PrintfSpecifier> |
| PrintfSpecifierResult; |
| |
| //===----------------------------------------------------------------------===// |
| // Methods for parsing format strings. |
| //===----------------------------------------------------------------------===// |
| |
| using analyze_format_string::ParseNonPositionAmount; |
| |
| static bool ParsePrecision(FormatStringHandler &H, PrintfSpecifier &FS, |
| const char *Start, const char *&Beg, const char *E, |
| unsigned *argIndex) { |
| if (argIndex) { |
| FS.setPrecision(ParseNonPositionAmount(Beg, E, *argIndex)); |
| } else { |
| const OptionalAmount Amt = ParsePositionAmount(H, Start, Beg, E, |
| analyze_format_string::PrecisionPos); |
| if (Amt.isInvalid()) |
| return true; |
| FS.setPrecision(Amt); |
| } |
| return false; |
| } |
| |
| static bool ParseObjCFlags(FormatStringHandler &H, PrintfSpecifier &FS, |
| const char *FlagBeg, const char *E, bool Warn) { |
| StringRef Flag(FlagBeg, E - FlagBeg); |
| // Currently there is only one flag. |
| if (Flag == "tt") { |
| FS.setHasObjCTechnicalTerm(FlagBeg); |
| return false; |
| } |
| // Handle either the case of no flag or an invalid flag. |
| if (Warn) { |
| if (Flag == "") |
| H.HandleEmptyObjCModifierFlag(FlagBeg, E - FlagBeg); |
| else |
| H.HandleInvalidObjCModifierFlag(FlagBeg, E - FlagBeg); |
| } |
| return true; |
| } |
| |
| static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H, |
| const char *&Beg, |
| const char *E, |
| unsigned &argIndex, |
| const LangOptions &LO, |
| const TargetInfo &Target, |
| bool Warn, |
| bool isFreeBSDKPrintf) { |
| |
| using namespace clang::analyze_format_string; |
| using namespace clang::analyze_printf; |
| |
| const char *I = Beg; |
| const char *Start = nullptr; |
| UpdateOnReturn <const char*> UpdateBeg(Beg, I); |
| |
| // Look for a '%' character that indicates the start of a format specifier. |
| for ( ; I != E ; ++I) { |
| char c = *I; |
| if (c == '\0') { |
| // Detect spurious null characters, which are likely errors. |
| H.HandleNullChar(I); |
| return true; |
| } |
| if (c == '%') { |
| Start = I++; // Record the start of the format specifier. |
| break; |
| } |
| } |
| |
| // No format specifier found? |
| if (!Start) |
| return false; |
| |
| if (I == E) { |
| // No more characters left? |
| if (Warn) |
| H.HandleIncompleteSpecifier(Start, E - Start); |
| return true; |
| } |
| |
| PrintfSpecifier FS; |
| if (ParseArgPosition(H, FS, Start, I, E)) |
| return true; |
| |
| if (I == E) { |
| // No more characters left? |
| if (Warn) |
| H.HandleIncompleteSpecifier(Start, E - Start); |
| return true; |
| } |
| |
| if (*I == '{') { |
| ++I; |
| unsigned char PrivacyFlags = 0; |
| StringRef MatchedStr; |
| |
| do { |
| StringRef Str(I, E - I); |
| std::string Match = "^[[:space:]]*" |
| "(private|public|sensitive|mask\\.[^[:space:],}]*)" |
| "[[:space:]]*(,|})"; |
| llvm::Regex R(Match); |
| SmallVector<StringRef, 2> Matches; |
| |
| if (R.match(Str, &Matches)) { |
| MatchedStr = Matches[1]; |
| I += Matches[0].size(); |
| |
| // Set the privacy flag if the privacy annotation in the |
| // comma-delimited segment is at least as strict as the privacy |
| // annotations in previous comma-delimited segments. |
| if (MatchedStr.startswith("mask")) { |
| StringRef MaskType = MatchedStr.substr(sizeof("mask.") - 1); |
| unsigned Size = MaskType.size(); |
| if (Warn && (Size == 0 || Size > 8)) |
| H.handleInvalidMaskType(MaskType); |
| FS.setMaskType(MaskType); |
| } else if (MatchedStr.equals("sensitive")) |
| PrivacyFlags = clang::analyze_os_log::OSLogBufferItem::IsSensitive; |
| else if (PrivacyFlags != |
| clang::analyze_os_log::OSLogBufferItem::IsSensitive && |
| MatchedStr.equals("private")) |
| PrivacyFlags = clang::analyze_os_log::OSLogBufferItem::IsPrivate; |
| else if (PrivacyFlags == 0 && MatchedStr.equals("public")) |
| PrivacyFlags = clang::analyze_os_log::OSLogBufferItem::IsPublic; |
| } else { |
| size_t CommaOrBracePos = |
| Str.find_if([](char c) { return c == ',' || c == '}'; }); |
| |
| if (CommaOrBracePos == StringRef::npos) { |
| // Neither a comma nor the closing brace was found. |
| if (Warn) |
| H.HandleIncompleteSpecifier(Start, E - Start); |
| return true; |
| } |
| |
| I += CommaOrBracePos + 1; |
| } |
| // Continue until the closing brace is found. |
| } while (*(I - 1) == ','); |
| |
| // Set the privacy flag. |
| switch (PrivacyFlags) { |
| case 0: |
| break; |
| case clang::analyze_os_log::OSLogBufferItem::IsPrivate: |
| FS.setIsPrivate(MatchedStr.data()); |
| break; |
| case clang::analyze_os_log::OSLogBufferItem::IsPublic: |
| FS.setIsPublic(MatchedStr.data()); |
| break; |
| case clang::analyze_os_log::OSLogBufferItem::IsSensitive: |
| FS.setIsSensitive(MatchedStr.data()); |
| break; |
| default: |
| llvm_unreachable("Unexpected privacy flag value"); |
| } |
| } |
| |
| // Look for flags (if any). |
| bool hasMore = true; |
| for ( ; I != E; ++I) { |
| switch (*I) { |
| default: hasMore = false; break; |
| case '\'': |
| // FIXME: POSIX specific. Always accept? |
| FS.setHasThousandsGrouping(I); |
| break; |
| case '-': FS.setIsLeftJustified(I); break; |
| case '+': FS.setHasPlusPrefix(I); break; |
| case ' ': FS.setHasSpacePrefix(I); break; |
| case '#': FS.setHasAlternativeForm(I); break; |
| case '0': FS.setHasLeadingZeros(I); break; |
| } |
| if (!hasMore) |
| break; |
| } |
| |
| if (I == E) { |
| // No more characters left? |
| if (Warn) |
| H.HandleIncompleteSpecifier(Start, E - Start); |
| return true; |
| } |
| |
| // Look for the field width (if any). |
| if (ParseFieldWidth(H, FS, Start, I, E, |
| FS.usesPositionalArg() ? nullptr : &argIndex)) |
| return true; |
| |
| if (I == E) { |
| // No more characters left? |
| if (Warn) |
| H.HandleIncompleteSpecifier(Start, E - Start); |
| return true; |
| } |
| |
| // Look for the precision (if any). |
| if (*I == '.') { |
| ++I; |
| if (I == E) { |
| if (Warn) |
| H.HandleIncompleteSpecifier(Start, E - Start); |
| return true; |
| } |
| |
| if (ParsePrecision(H, FS, Start, I, E, |
| FS.usesPositionalArg() ? nullptr : &argIndex)) |
| return true; |
| |
| if (I == E) { |
| // No more characters left? |
| if (Warn) |
| H.HandleIncompleteSpecifier(Start, E - Start); |
| return true; |
| } |
| } |
| |
| if (ParseVectorModifier(H, FS, I, E, LO)) |
| return true; |
| |
| // Look for the length modifier. |
| if (ParseLengthModifier(FS, I, E, LO) && I == E) { |
| // No more characters left? |
| if (Warn) |
| H.HandleIncompleteSpecifier(Start, E - Start); |
| return true; |
| } |
| |
| // Look for the Objective-C modifier flags, if any. |
| // We parse these here, even if they don't apply to |
| // the conversion specifier, and then emit an error |
| // later if the conversion specifier isn't '@'. This |
| // enables better recovery, and we don't know if |
| // these flags are applicable until later. |
| const char *ObjCModifierFlagsStart = nullptr, |
| *ObjCModifierFlagsEnd = nullptr; |
| if (*I == '[') { |
| ObjCModifierFlagsStart = I; |
| ++I; |
| auto flagStart = I; |
| for (;; ++I) { |
| ObjCModifierFlagsEnd = I; |
| if (I == E) { |
| if (Warn) |
| H.HandleIncompleteSpecifier(Start, E - Start); |
| return true; |
| } |
| // Did we find the closing ']'? |
| if (*I == ']') { |
| if (ParseObjCFlags(H, FS, flagStart, I, Warn)) |
| return true; |
| ++I; |
| break; |
| } |
| // There are no separators defined yet for multiple |
| // Objective-C modifier flags. When those are |
| // defined, this is the place to check. |
| } |
| } |
| |
| if (*I == '\0') { |
| // Detect spurious null characters, which are likely errors. |
| H.HandleNullChar(I); |
| return true; |
| } |
| |
| // Finally, look for the conversion specifier. |
| const char *conversionPosition = I++; |
| ConversionSpecifier::Kind k = ConversionSpecifier::InvalidSpecifier; |
| switch (*conversionPosition) { |
| default: |
| break; |
| // C99: 7.19.6.1 (section 8). |
| case '%': k = ConversionSpecifier::PercentArg; break; |
| case 'A': k = ConversionSpecifier::AArg; break; |
| case 'E': k = ConversionSpecifier::EArg; break; |
| case 'F': k = ConversionSpecifier::FArg; break; |
| case 'G': k = ConversionSpecifier::GArg; break; |
| case 'X': k = ConversionSpecifier::XArg; break; |
| case 'a': k = ConversionSpecifier::aArg; break; |
| case 'c': k = ConversionSpecifier::cArg; break; |
| case 'd': k = ConversionSpecifier::dArg; break; |
| case 'e': k = ConversionSpecifier::eArg; break; |
| case 'f': k = ConversionSpecifier::fArg; break; |
| case 'g': k = ConversionSpecifier::gArg; break; |
| case 'i': k = ConversionSpecifier::iArg; break; |
| case 'n': |
| // Not handled, but reserved in OpenCL. |
| if (!LO.OpenCL) |
| k = ConversionSpecifier::nArg; |
| break; |
| case 'o': k = ConversionSpecifier::oArg; break; |
| case 'p': k = ConversionSpecifier::pArg; break; |
| case 's': k = ConversionSpecifier::sArg; break; |
| case 'u': k = ConversionSpecifier::uArg; break; |
| case 'x': k = ConversionSpecifier::xArg; break; |
| // POSIX specific. |
| case 'C': k = ConversionSpecifier::CArg; break; |
| case 'S': k = ConversionSpecifier::SArg; break; |
| // Apple extension for os_log |
| case 'P': |
| k = ConversionSpecifier::PArg; |
| break; |
| // Objective-C. |
| case '@': k = ConversionSpecifier::ObjCObjArg; break; |
| // Glibc specific. |
| case 'm': k = ConversionSpecifier::PrintErrno; break; |
| // FreeBSD kernel specific. |
| case 'b': |
| if (isFreeBSDKPrintf) |
| k = ConversionSpecifier::FreeBSDbArg; // int followed by char * |
| break; |
| case 'r': |
| if (isFreeBSDKPrintf) |
| k = ConversionSpecifier::FreeBSDrArg; // int |
| break; |
| case 'y': |
| if (isFreeBSDKPrintf) |
| k = ConversionSpecifier::FreeBSDyArg; // int |
| break; |
| // Apple-specific. |
| case 'D': |
| if (isFreeBSDKPrintf) |
| k = ConversionSpecifier::FreeBSDDArg; // void * followed by char * |
| else if (Target.getTriple().isOSDarwin()) |
| k = ConversionSpecifier::DArg; |
| break; |
| case 'O': |
| if (Target.getTriple().isOSDarwin()) |
| k = ConversionSpecifier::OArg; |
| break; |
| case 'U': |
| if (Target.getTriple().isOSDarwin()) |
| k = ConversionSpecifier::UArg; |
| break; |
| // MS specific. |
| case 'Z': |
| if (Target.getTriple().isOSMSVCRT()) |
| k = ConversionSpecifier::ZArg; |
| break; |
| } |
| |
| // Check to see if we used the Objective-C modifier flags with |
| // a conversion specifier other than '@'. |
| if (k != ConversionSpecifier::ObjCObjArg && |
| k != ConversionSpecifier::InvalidSpecifier && |
| ObjCModifierFlagsStart) { |
| H.HandleObjCFlagsWithNonObjCConversion(ObjCModifierFlagsStart, |
| ObjCModifierFlagsEnd + 1, |
| conversionPosition); |
| return true; |
| } |
| |
| PrintfConversionSpecifier CS(conversionPosition, k); |
| FS.setConversionSpecifier(CS); |
| if (CS.consumesDataArgument() && !FS.usesPositionalArg()) |
| FS.setArgIndex(argIndex++); |
| // FreeBSD kernel specific. |
| if (k == ConversionSpecifier::FreeBSDbArg || |
| k == ConversionSpecifier::FreeBSDDArg) |
| argIndex++; |
| |
| if (k == ConversionSpecifier::InvalidSpecifier) { |
| unsigned Len = I - Start; |
| if (ParseUTF8InvalidSpecifier(Start, E, Len)) { |
| CS.setEndScanList(Start + Len); |
| FS.setConversionSpecifier(CS); |
| } |
| // Assume the conversion takes one argument. |
| return !H.HandleInvalidPrintfConversionSpecifier(FS, Start, Len); |
| } |
| return PrintfSpecifierResult(Start, FS); |
| } |
| |
| bool clang::analyze_format_string::ParsePrintfString(FormatStringHandler &H, |
| const char *I, |
| const char *E, |
| const LangOptions &LO, |
| const TargetInfo &Target, |
| bool isFreeBSDKPrintf) { |
| |
| unsigned argIndex = 0; |
| |
| // Keep looking for a format specifier until we have exhausted the string. |
| while (I != E) { |
| const PrintfSpecifierResult &FSR = ParsePrintfSpecifier(H, I, E, argIndex, |
| LO, Target, true, |
| isFreeBSDKPrintf); |
| // Did a fail-stop error of any kind occur when parsing the specifier? |
| // If so, don't do any more processing. |
| if (FSR.shouldStop()) |
| return true; |
| // Did we exhaust the string or encounter an error that |
| // we can recover from? |
| if (!FSR.hasValue()) |
| continue; |
| // We have a format specifier. Pass it to the callback. |
| if (!H.HandlePrintfSpecifier(FSR.getValue(), FSR.getStart(), |
| I - FSR.getStart())) |
| return true; |
| } |
| assert(I == E && "Format string not exhausted"); |
| return false; |
| } |
| |
| bool clang::analyze_format_string::ParseFormatStringHasSArg(const char *I, |
| const char *E, |
| const LangOptions &LO, |
| const TargetInfo &Target) { |
| |
| unsigned argIndex = 0; |
| |
| // Keep looking for a %s format specifier until we have exhausted the string. |
| FormatStringHandler H; |
| while (I != E) { |
| const PrintfSpecifierResult &FSR = ParsePrintfSpecifier(H, I, E, argIndex, |
| LO, Target, false, |
| false); |
| // Did a fail-stop error of any kind occur when parsing the specifier? |
| // If so, don't do any more processing. |
| if (FSR.shouldStop()) |
| return false; |
| // Did we exhaust the string or encounter an error that |
| // we can recover from? |
| if (!FSR.hasValue()) |
| continue; |
| const analyze_printf::PrintfSpecifier &FS = FSR.getValue(); |
| // Return true if this a %s format specifier. |
| if (FS.getConversionSpecifier().getKind() == ConversionSpecifier::Kind::sArg) |
| return true; |
| } |
| return false; |
| } |
| |
| bool clang::analyze_format_string::parseFormatStringHasFormattingSpecifiers( |
| const char *Begin, const char *End, const LangOptions &LO, |
| const TargetInfo &Target) { |
| unsigned ArgIndex = 0; |
| // Keep looking for a formatting specifier until we have exhausted the string. |
| FormatStringHandler H; |
| while (Begin != End) { |
| const PrintfSpecifierResult &FSR = |
| ParsePrintfSpecifier(H, Begin, End, ArgIndex, LO, Target, false, false); |
| if (FSR.shouldStop()) |
| break; |
| if (FSR.hasValue()) |
| return true; |
| } |
| return false; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Methods on PrintfSpecifier. |
| //===----------------------------------------------------------------------===// |
| |
| ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx, |
| bool IsObjCLiteral) const { |
| if (CS.getKind() == ConversionSpecifier::cArg) |
| switch (LM.getKind()) { |
| case LengthModifier::None: |
| return Ctx.IntTy; |
| case LengthModifier::AsLong: |
| case LengthModifier::AsWide: |
| return ArgType(ArgType::WIntTy, "wint_t"); |
| case LengthModifier::AsShort: |
| if (Ctx.getTargetInfo().getTriple().isOSMSVCRT()) |
| return Ctx.IntTy; |
| LLVM_FALLTHROUGH; |
| default: |
| return ArgType::Invalid(); |
| } |
| |
| if (CS.isIntArg()) |
| switch (LM.getKind()) { |
| case LengthModifier::AsLongDouble: |
| // GNU extension. |
| return Ctx.LongLongTy; |
| case LengthModifier::None: |
| case LengthModifier::AsShortLong: |
| return Ctx.IntTy; |
| case LengthModifier::AsInt32: |
| return ArgType(Ctx.IntTy, "__int32"); |
| case LengthModifier::AsChar: |
| return ArgType::AnyCharTy; |
| case LengthModifier::AsShort: return Ctx.ShortTy; |
| case LengthModifier::AsLong: return Ctx.LongTy; |
| case LengthModifier::AsLongLong: |
| case LengthModifier::AsQuad: |
| return Ctx.LongLongTy; |
| case LengthModifier::AsInt64: |
| return ArgType(Ctx.LongLongTy, "__int64"); |
| case LengthModifier::AsIntMax: |
| return ArgType(Ctx.getIntMaxType(), "intmax_t"); |
| case LengthModifier::AsSizeT: |
| return ArgType::makeSizeT(ArgType(Ctx.getSignedSizeType(), "ssize_t")); |
| case LengthModifier::AsInt3264: |
| return Ctx.getTargetInfo().getTriple().isArch64Bit() |
| ? ArgType(Ctx.LongLongTy, "__int64") |
| : ArgType(Ctx.IntTy, "__int32"); |
| case LengthModifier::AsPtrDiff: |
| return ArgType::makePtrdiffT( |
| ArgType(Ctx.getPointerDiffType(), "ptrdiff_t")); |
| case LengthModifier::AsAllocate: |
| case LengthModifier::AsMAllocate: |
| case LengthModifier::AsWide: |
| return ArgType::Invalid(); |
| } |
| |
| if (CS.isUIntArg()) |
| switch (LM.getKind()) { |
| case LengthModifier::AsLongDouble: |
| // GNU extension. |
| return Ctx.UnsignedLongLongTy; |
| case LengthModifier::None: |
| case LengthModifier::AsShortLong: |
| return Ctx.UnsignedIntTy; |
| case LengthModifier::AsInt32: |
| return ArgType(Ctx.UnsignedIntTy, "unsigned __int32"); |
| case LengthModifier::AsChar: return Ctx.UnsignedCharTy; |
| case LengthModifier::AsShort: return Ctx.UnsignedShortTy; |
| case LengthModifier::AsLong: return Ctx.UnsignedLongTy; |
| case LengthModifier::AsLongLong: |
| case LengthModifier::AsQuad: |
| return Ctx.UnsignedLongLongTy; |
| case LengthModifier::AsInt64: |
| return ArgType(Ctx.UnsignedLongLongTy, "unsigned __int64"); |
| case LengthModifier::AsIntMax: |
| return ArgType(Ctx.getUIntMaxType(), "uintmax_t"); |
| case LengthModifier::AsSizeT: |
| return ArgType::makeSizeT(ArgType(Ctx.getSizeType(), "size_t")); |
| case LengthModifier::AsInt3264: |
| return Ctx.getTargetInfo().getTriple().isArch64Bit() |
| ? ArgType(Ctx.UnsignedLongLongTy, "unsigned __int64") |
| : ArgType(Ctx.UnsignedIntTy, "unsigned __int32"); |
| case LengthModifier::AsPtrDiff: |
| return ArgType::makePtrdiffT( |
| ArgType(Ctx.getUnsignedPointerDiffType(), "unsigned ptrdiff_t")); |
| case LengthModifier::AsAllocate: |
| case LengthModifier::AsMAllocate: |
| case LengthModifier::AsWide: |
| return ArgType::Invalid(); |
| } |
| |
| if (CS.isDoubleArg()) { |
| if (!VectorNumElts.isInvalid()) { |
| switch (LM.getKind()) { |
| case LengthModifier::AsShort: |
| return Ctx.HalfTy; |
| case LengthModifier::AsShortLong: |
| return Ctx.FloatTy; |
| case LengthModifier::AsLong: |
| default: |
| return Ctx.DoubleTy; |
| } |
| } |
| |
| if (LM.getKind() == LengthModifier::AsLongDouble) |
| return Ctx.LongDoubleTy; |
| return Ctx.DoubleTy; |
| } |
| |
| if (CS.getKind() == ConversionSpecifier::nArg) { |
| switch (LM.getKind()) { |
| case LengthModifier::None: |
| return ArgType::PtrTo(Ctx.IntTy); |
| case LengthModifier::AsChar: |
| return ArgType::PtrTo(Ctx.SignedCharTy); |
| case LengthModifier::AsShort: |
| return ArgType::PtrTo(Ctx.ShortTy); |
| case LengthModifier::AsLong: |
| return ArgType::PtrTo(Ctx.LongTy); |
| case LengthModifier::AsLongLong: |
| case LengthModifier::AsQuad: |
| return ArgType::PtrTo(Ctx.LongLongTy); |
| case LengthModifier::AsIntMax: |
| return ArgType::PtrTo(ArgType(Ctx.getIntMaxType(), "intmax_t")); |
| case LengthModifier::AsSizeT: |
| return ArgType::PtrTo(ArgType(Ctx.getSignedSizeType(), "ssize_t")); |
| case LengthModifier::AsPtrDiff: |
| return ArgType::PtrTo(ArgType(Ctx.getPointerDiffType(), "ptrdiff_t")); |
| case LengthModifier::AsLongDouble: |
| return ArgType(); // FIXME: Is this a known extension? |
| case LengthModifier::AsAllocate: |
| case LengthModifier::AsMAllocate: |
| case LengthModifier::AsInt32: |
| case LengthModifier::AsInt3264: |
| case LengthModifier::AsInt64: |
| case LengthModifier::AsWide: |
| return ArgType::Invalid(); |
| case LengthModifier::AsShortLong: |
| llvm_unreachable("only used for OpenCL which doesn not handle nArg"); |
| } |
| } |
| |
| switch (CS.getKind()) { |
| case ConversionSpecifier::sArg: |
| if (LM.getKind() == LengthModifier::AsWideChar) { |
| if (IsObjCLiteral) |
| return ArgType(Ctx.getPointerType(Ctx.UnsignedShortTy.withConst()), |
| "const unichar *"); |
| return ArgType(ArgType::WCStrTy, "wchar_t *"); |
| } |
| if (LM.getKind() == LengthModifier::AsWide) |
| return ArgType(ArgType::WCStrTy, "wchar_t *"); |
| return ArgType::CStrTy; |
| case ConversionSpecifier::SArg: |
| if (IsObjCLiteral) |
| return ArgType(Ctx.getPointerType(Ctx.UnsignedShortTy.withConst()), |
| "const unichar *"); |
| if (Ctx.getTargetInfo().getTriple().isOSMSVCRT() && |
| LM.getKind() == LengthModifier::AsShort) |
| return ArgType::CStrTy; |
| return ArgType(ArgType::WCStrTy, "wchar_t *"); |
| case ConversionSpecifier::CArg: |
| if (IsObjCLiteral) |
| return ArgType(Ctx.UnsignedShortTy, "unichar"); |
| if (Ctx.getTargetInfo().getTriple().isOSMSVCRT() && |
| LM.getKind() == LengthModifier::AsShort) |
| return Ctx.IntTy; |
| return ArgType(Ctx.WideCharTy, "wchar_t"); |
| case ConversionSpecifier::pArg: |
| case ConversionSpecifier::PArg: |
| return ArgType::CPointerTy; |
| case ConversionSpecifier::ObjCObjArg: |
| return ArgType::ObjCPointerTy; |
| default: |
| break; |
| } |
| |
| // FIXME: Handle other cases. |
| return ArgType(); |
| } |
| |
| |
| ArgType PrintfSpecifier::getArgType(ASTContext &Ctx, |
| bool IsObjCLiteral) const { |
| const PrintfConversionSpecifier &CS = getConversionSpecifier(); |
| |
| if (!CS.consumesDataArgument()) |
| return ArgType::Invalid(); |
| |
| ArgType ScalarTy = getScalarArgType(Ctx, IsObjCLiteral); |
| if (!ScalarTy.isValid() || VectorNumElts.isInvalid()) |
| return ScalarTy; |
| |
| return ScalarTy.makeVectorType(Ctx, VectorNumElts.getConstantAmount()); |
| } |
| |
| bool PrintfSpecifier::fixType(QualType QT, const LangOptions &LangOpt, |
| ASTContext &Ctx, bool IsObjCLiteral) { |
| // %n is different from other conversion specifiers; don't try to fix it. |
| if (CS.getKind() == ConversionSpecifier::nArg) |
| return false; |
| |
| // Handle Objective-C objects first. Note that while the '%@' specifier will |
| // not warn for structure pointer or void pointer arguments (because that's |
| // how CoreFoundation objects are implemented), we only show a fixit for '%@' |
| // if we know it's an object (block, id, class, or __attribute__((NSObject))). |
| if (QT->isObjCRetainableType()) { |
| if (!IsObjCLiteral) |
| return false; |
| |
| CS.setKind(ConversionSpecifier::ObjCObjArg); |
| |
| // Disable irrelevant flags |
| HasThousandsGrouping = false; |
| HasPlusPrefix = false; |
| HasSpacePrefix = false; |
| HasAlternativeForm = false; |
| HasLeadingZeroes = false; |
| Precision.setHowSpecified(OptionalAmount::NotSpecified); |
| LM.setKind(LengthModifier::None); |
| |
| return true; |
| } |
| |
| // Handle strings next (char *, wchar_t *) |
| if (QT->isPointerType() && (QT->getPointeeType()->isAnyCharacterType())) { |
| CS.setKind(ConversionSpecifier::sArg); |
| |
| // Disable irrelevant flags |
| HasAlternativeForm = 0; |
| HasLeadingZeroes = 0; |
| |
| // Set the long length modifier for wide characters |
| if (QT->getPointeeType()->isWideCharType()) |
| LM.setKind(LengthModifier::AsWideChar); |
| else |
| LM.setKind(LengthModifier::None); |
| |
| return true; |
| } |
| |
| // If it's an enum, get its underlying type. |
| if (const EnumType *ETy = QT->getAs<EnumType>()) |
| QT = ETy->getDecl()->getIntegerType(); |
| |
| const BuiltinType *BT = QT->getAs<BuiltinType>(); |
| if (!BT) { |
| const VectorType *VT = QT->getAs<VectorType>(); |
| if (VT) { |
| QT = VT->getElementType(); |
| BT = QT->getAs<BuiltinType>(); |
| VectorNumElts = OptionalAmount(VT->getNumElements()); |
| } |
| } |
| |
| // We can only work with builtin types. |
| if (!BT) |
| return false; |
| |
| // Set length modifier |
| switch (BT->getKind()) { |
| case BuiltinType::Bool: |
| case BuiltinType::WChar_U: |
| case BuiltinType::WChar_S: |
| case BuiltinType::Char8: // FIXME: Treat like 'char'? |
| case BuiltinType::Char16: |
| case BuiltinType::Char32: |
| case BuiltinType::UInt128: |
| case BuiltinType::Int128: |
| case BuiltinType::Half: |
| case BuiltinType::BFloat16: |
| case BuiltinType::Float16: |
| case BuiltinType::Float128: |
| case BuiltinType::Ibm128: |
| case BuiltinType::ShortAccum: |
| case BuiltinType::Accum: |
| case BuiltinType::LongAccum: |
| case BuiltinType::UShortAccum: |
| case BuiltinType::UAccum: |
| case BuiltinType::ULongAccum: |
| case BuiltinType::ShortFract: |
| case BuiltinType::Fract: |
| case BuiltinType::LongFract: |
| case BuiltinType::UShortFract: |
| case BuiltinType::UFract: |
| case BuiltinType::ULongFract: |
| case BuiltinType::SatShortAccum: |
| case BuiltinType::SatAccum: |
| case BuiltinType::SatLongAccum: |
| case BuiltinType::SatUShortAccum: |
| case BuiltinType::SatUAccum: |
| case BuiltinType::SatULongAccum: |
| case BuiltinType::SatShortFract: |
| case BuiltinType::SatFract: |
| case BuiltinType::SatLongFract: |
| case BuiltinType::SatUShortFract: |
| case BuiltinType::SatUFract: |
| case BuiltinType::SatULongFract: |
| // Various types which are non-trivial to correct. |
| return false; |
| |
| #define IMAGE_TYPE(ImgType, Id, SingletonId, Access, Suffix) \ |
| case BuiltinType::Id: |
| #include "clang/Basic/OpenCLImageTypes.def" |
| #define EXT_OPAQUE_TYPE(ExtType, Id, Ext) \ |
| case BuiltinType::Id: |
| #include "clang/Basic/OpenCLExtensionTypes.def" |
| #define SVE_TYPE(Name, Id, SingletonId) \ |
| case BuiltinType::Id: |
| #include "clang/Basic/AArch64SVEACLETypes.def" |
| #define PPC_VECTOR_TYPE(Name, Id, Size) \ |
| case BuiltinType::Id: |
| #include "clang/Basic/PPCTypes.def" |
| #define RVV_TYPE(Name, Id, SingletonId) case BuiltinType::Id: |
| #include "clang/Basic/RISCVVTypes.def" |
| #define SIGNED_TYPE(Id, SingletonId) |
| #define UNSIGNED_TYPE(Id, SingletonId) |
| #define FLOATING_TYPE(Id, SingletonId) |
| #define BUILTIN_TYPE(Id, SingletonId) \ |
| case BuiltinType::Id: |
| #include "clang/AST/BuiltinTypes.def" |
| // Misc other stuff which doesn't make sense here. |
| return false; |
| |
| case BuiltinType::UInt: |
| case BuiltinType::Int: |
| case BuiltinType::Float: |
| LM.setKind(VectorNumElts.isInvalid() ? |
| LengthModifier::None : LengthModifier::AsShortLong); |
| break; |
| case BuiltinType::Double: |
| LM.setKind(VectorNumElts.isInvalid() ? |
| LengthModifier::None : LengthModifier::AsLong); |
| break; |
| case BuiltinType::Char_U: |
| case BuiltinType::UChar: |
| case BuiltinType::Char_S: |
| case BuiltinType::SChar: |
| LM.setKind(LengthModifier::AsChar); |
| break; |
| |
| case BuiltinType::Short: |
| case BuiltinType::UShort: |
| LM.setKind(LengthModifier::AsShort); |
| break; |
| |
| case BuiltinType::Long: |
| case BuiltinType::ULong: |
| LM.setKind(LengthModifier::AsLong); |
| break; |
| |
| case BuiltinType::LongLong: |
| case BuiltinType::ULongLong: |
| LM.setKind(LengthModifier::AsLongLong); |
| break; |
| |
| case BuiltinType::LongDouble: |
| LM.setKind(LengthModifier::AsLongDouble); |
| break; |
| } |
| |
| // Handle size_t, ptrdiff_t, etc. that have dedicated length modifiers in C99. |
| if (isa<TypedefType>(QT) && (LangOpt.C99 || LangOpt.CPlusPlus11)) |
| namedTypeToLengthModifier(QT, LM); |
| |
| // If fixing the length modifier was enough, we might be done. |
| if (hasValidLengthModifier(Ctx.getTargetInfo(), LangOpt)) { |
| // If we're going to offer a fix anyway, make sure the sign matches. |
| switch (CS.getKind()) { |
| case ConversionSpecifier::uArg: |
| case ConversionSpecifier::UArg: |
| if (QT->isSignedIntegerType()) |
| CS.setKind(clang::analyze_format_string::ConversionSpecifier::dArg); |
| break; |
| case ConversionSpecifier::dArg: |
| case ConversionSpecifier::DArg: |
| case ConversionSpecifier::iArg: |
| if (QT->isUnsignedIntegerType() && !HasPlusPrefix) |
| CS.setKind(clang::analyze_format_string::ConversionSpecifier::uArg); |
| break; |
| default: |
| // Other specifiers do not have signed/unsigned variants. |
| break; |
| } |
| |
| const analyze_printf::ArgType &ATR = getArgType(Ctx, IsObjCLiteral); |
| if (ATR.isValid() && ATR.matchesType(Ctx, QT)) |
| return true; |
| } |
| |
| // Set conversion specifier and disable any flags which do not apply to it. |
| // Let typedefs to char fall through to int, as %c is silly for uint8_t. |
| if (!isa<TypedefType>(QT) && QT->isCharType()) { |
| CS.setKind(ConversionSpecifier::cArg); |
| LM.setKind(LengthModifier::None); |
| Precision.setHowSpecified(OptionalAmount::NotSpecified); |
| HasAlternativeForm = 0; |
| HasLeadingZeroes = 0; |
| HasPlusPrefix = 0; |
| } |
| // Test for Floating type first as LongDouble can pass isUnsignedIntegerType |
| else if (QT->isRealFloatingType()) { |
| CS.setKind(ConversionSpecifier::fArg); |
| } |
| else if (QT->isSignedIntegerType()) { |
| CS.setKind(ConversionSpecifier::dArg); |
| HasAlternativeForm = 0; |
| } |
| else if (QT->isUnsignedIntegerType()) { |
| CS.setKind(ConversionSpecifier::uArg); |
| HasAlternativeForm = 0; |
| HasPlusPrefix = 0; |
| } else { |
| llvm_unreachable("Unexpected type"); |
| } |
| |
| return true; |
| } |
| |
| void PrintfSpecifier::toString(raw_ostream &os) const { |
| // Whilst some features have no defined order, we are using the order |
| // appearing in the C99 standard (ISO/IEC 9899:1999 (E) 7.19.6.1) |
| os << "%"; |
| |
| // Positional args |
| if (usesPositionalArg()) { |
| os << getPositionalArgIndex() << "$"; |
| } |
| |
| // Conversion flags |
| if (IsLeftJustified) os << "-"; |
| if (HasPlusPrefix) os << "+"; |
| if (HasSpacePrefix) os << " "; |
| if (HasAlternativeForm) os << "#"; |
| if (HasLeadingZeroes) os << "0"; |
| |
| // Minimum field width |
| FieldWidth.toString(os); |
| // Precision |
| Precision.toString(os); |
| |
| // Vector modifier |
| if (!VectorNumElts.isInvalid()) |
| os << 'v' << VectorNumElts.getConstantAmount(); |
| |
| // Length modifier |
| os << LM.toString(); |
| // Conversion specifier |
| os << CS.toString(); |
| } |
| |
| bool PrintfSpecifier::hasValidPlusPrefix() const { |
| if (!HasPlusPrefix) |
| return true; |
| |
| // The plus prefix only makes sense for signed conversions |
| switch (CS.getKind()) { |
| case ConversionSpecifier::dArg: |
| case ConversionSpecifier::DArg: |
| case ConversionSpecifier::iArg: |
| case ConversionSpecifier::fArg: |
| case ConversionSpecifier::FArg: |
| case ConversionSpecifier::eArg: |
| case ConversionSpecifier::EArg: |
| case ConversionSpecifier::gArg: |
| case ConversionSpecifier::GArg: |
| case ConversionSpecifier::aArg: |
| case ConversionSpecifier::AArg: |
| case ConversionSpecifier::FreeBSDrArg: |
| case ConversionSpecifier::FreeBSDyArg: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| bool PrintfSpecifier::hasValidAlternativeForm() const { |
| if (!HasAlternativeForm) |
| return true; |
| |
| // Alternate form flag only valid with the oxXaAeEfFgG conversions |
| switch (CS.getKind()) { |
| case ConversionSpecifier::oArg: |
| case ConversionSpecifier::OArg: |
| case ConversionSpecifier::xArg: |
| case ConversionSpecifier::XArg: |
| case ConversionSpecifier::aArg: |
| case ConversionSpecifier::AArg: |
| case ConversionSpecifier::eArg: |
| case ConversionSpecifier::EArg: |
| case ConversionSpecifier::fArg: |
| case ConversionSpecifier::FArg: |
| case ConversionSpecifier::gArg: |
| case ConversionSpecifier::GArg: |
| case ConversionSpecifier::FreeBSDrArg: |
| case ConversionSpecifier::FreeBSDyArg: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| bool PrintfSpecifier::hasValidLeadingZeros() const { |
| if (!HasLeadingZeroes) |
| return true; |
| |
| // Leading zeroes flag only valid with the diouxXaAeEfFgG conversions |
| switch (CS.getKind()) { |
| case ConversionSpecifier::dArg: |
| case ConversionSpecifier::DArg: |
| case ConversionSpecifier::iArg: |
| case ConversionSpecifier::oArg: |
| case ConversionSpecifier::OArg: |
| case ConversionSpecifier::uArg: |
| case ConversionSpecifier::UArg: |
| case ConversionSpecifier::xArg: |
| case ConversionSpecifier::XArg: |
| case ConversionSpecifier::aArg: |
| case ConversionSpecifier::AArg: |
| case ConversionSpecifier::eArg: |
| case ConversionSpecifier::EArg: |
| case ConversionSpecifier::fArg: |
| case ConversionSpecifier::FArg: |
| case ConversionSpecifier::gArg: |
| case ConversionSpecifier::GArg: |
| case ConversionSpecifier::FreeBSDrArg: |
| case ConversionSpecifier::FreeBSDyArg: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| bool PrintfSpecifier::hasValidSpacePrefix() const { |
| if (!HasSpacePrefix) |
| return true; |
| |
| // The space prefix only makes sense for signed conversions |
| switch (CS.getKind()) { |
| case ConversionSpecifier::dArg: |
| case ConversionSpecifier::DArg: |
| case ConversionSpecifier::iArg: |
| case ConversionSpecifier::fArg: |
| case ConversionSpecifier::FArg: |
| case ConversionSpecifier::eArg: |
| case ConversionSpecifier::EArg: |
| case ConversionSpecifier::gArg: |
| case ConversionSpecifier::GArg: |
| case ConversionSpecifier::aArg: |
| case ConversionSpecifier::AArg: |
| case ConversionSpecifier::FreeBSDrArg: |
| case ConversionSpecifier::FreeBSDyArg: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| bool PrintfSpecifier::hasValidLeftJustified() const { |
| if (!IsLeftJustified) |
| return true; |
| |
| // The left justified flag is valid for all conversions except n |
| switch (CS.getKind()) { |
| case ConversionSpecifier::nArg: |
| return false; |
| |
| default: |
| return true; |
| } |
| } |
| |
| bool PrintfSpecifier::hasValidThousandsGroupingPrefix() const { |
| if (!HasThousandsGrouping) |
| return true; |
| |
| switch (CS.getKind()) { |
| case ConversionSpecifier::dArg: |
| case ConversionSpecifier::DArg: |
| case ConversionSpecifier::iArg: |
| case ConversionSpecifier::uArg: |
| case ConversionSpecifier::UArg: |
| case ConversionSpecifier::fArg: |
| case ConversionSpecifier::FArg: |
| case ConversionSpecifier::gArg: |
| case ConversionSpecifier::GArg: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool PrintfSpecifier::hasValidPrecision() const { |
| if (Precision.getHowSpecified() == OptionalAmount::NotSpecified) |
| return true; |
| |
| // Precision is only valid with the diouxXaAeEfFgGsP conversions |
| switch (CS.getKind()) { |
| case ConversionSpecifier::dArg: |
| case ConversionSpecifier::DArg: |
| case ConversionSpecifier::iArg: |
| case ConversionSpecifier::oArg: |
| case ConversionSpecifier::OArg: |
| case ConversionSpecifier::uArg: |
| case ConversionSpecifier::UArg: |
| case ConversionSpecifier::xArg: |
| case ConversionSpecifier::XArg: |
| case ConversionSpecifier::aArg: |
| case ConversionSpecifier::AArg: |
| case ConversionSpecifier::eArg: |
| case ConversionSpecifier::EArg: |
| case ConversionSpecifier::fArg: |
| case ConversionSpecifier::FArg: |
| case ConversionSpecifier::gArg: |
| case ConversionSpecifier::GArg: |
| case ConversionSpecifier::sArg: |
| case ConversionSpecifier::FreeBSDrArg: |
| case ConversionSpecifier::FreeBSDyArg: |
| case ConversionSpecifier::PArg: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| bool PrintfSpecifier::hasValidFieldWidth() const { |
| if (FieldWidth.getHowSpecified() == OptionalAmount::NotSpecified) |
| return true; |
| |
| // The field width is valid for all conversions except n |
| switch (CS.getKind()) { |
| case ConversionSpecifier::nArg: |
| return false; |
| |
| default: |
| return true; |
| } |
| } |