Files
llvm-project/clang/lib/AST/FormatString.cpp
Justin Stitt 7f631bb523 [Clang] Introduce OverflowBehaviorType for fine-grained overflow control (#148914)
Introduce `OverflowBehaviorType` (OBT), a new type attribute in Clang
that provides developers with fine-grained control over the overflow
behavior of integer types. This feature allows for a more nuanced
approach to integer safety, achieving better granularity than global
compiler flags like `-fwrapv` and `-ftrapv`. Type specifiers are also
available as keywords `__ob_wrap` and `__ob_trap`.

These can be applied to integer types (both signed and unsigned) as well
as typedef declarations, where the behavior is one of the following:

* `wrap`: Guarantees that arithmetic operations on the type will wrap on
overflow, similar to `-fwrapv`. This suppresses UBSan's integer overflow
checks for the attributed type and prevents eager compiler
optimizations.
* `trap`: Enforces overflow checking for the type, even when global
flags like `-fwrapv` would otherwise suppress it.

A key aspect of this feature is its interaction with existing
mechanisms. `OverflowBehaviorType` takes precedence over global flags
and, notably, over entries in the Sanitizer Special Case List (SSCL).
This allows developers to "allowlist" critical types for overflow
instrumentation, even if they are disabled by a broad rule in an SSCL.


Signed-off-by: Justin Stitt <justinstitt@google.com>
2026-02-19 13:54:33 -08:00

1290 lines
39 KiB
C++

// FormatString.cpp - Common stuff for handling printf/scanf formats -*- 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
//
//===----------------------------------------------------------------------===//
//
// Shared details for processing format strings of printf and scanf
// (and friends).
//
//===----------------------------------------------------------------------===//
#include "FormatStringParsing.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/Support/ConvertUTF.h"
#include <optional>
using clang::analyze_format_string::ArgType;
using clang::analyze_format_string::FormatStringHandler;
using clang::analyze_format_string::FormatSpecifier;
using clang::analyze_format_string::LengthModifier;
using clang::analyze_format_string::OptionalAmount;
using clang::analyze_format_string::ConversionSpecifier;
using namespace clang;
// Key function to FormatStringHandler.
FormatStringHandler::~FormatStringHandler() {}
//===----------------------------------------------------------------------===//
// Functions for parsing format strings components in both printf and
// scanf format strings.
//===----------------------------------------------------------------------===//
OptionalAmount
clang::analyze_format_string::ParseAmount(const char *&Beg, const char *E) {
const char *I = Beg;
UpdateOnReturn <const char*> UpdateBeg(Beg, I);
unsigned accumulator = 0;
bool hasDigits = false;
for ( ; I != E; ++I) {
char c = *I;
if (c >= '0' && c <= '9') {
hasDigits = true;
accumulator = (accumulator * 10) + (c - '0');
continue;
}
if (hasDigits)
return OptionalAmount(OptionalAmount::Constant, accumulator, Beg, I - Beg,
false);
break;
}
return OptionalAmount();
}
OptionalAmount
clang::analyze_format_string::ParseNonPositionAmount(const char *&Beg,
const char *E,
unsigned &argIndex) {
if (*Beg == '*') {
++Beg;
return OptionalAmount(OptionalAmount::Arg, argIndex++, Beg, 0, false);
}
return ParseAmount(Beg, E);
}
OptionalAmount
clang::analyze_format_string::ParsePositionAmount(FormatStringHandler &H,
const char *Start,
const char *&Beg,
const char *E,
PositionContext p) {
if (*Beg == '*') {
const char *I = Beg + 1;
const OptionalAmount &Amt = ParseAmount(I, E);
if (Amt.getHowSpecified() == OptionalAmount::NotSpecified) {
H.HandleInvalidPosition(Beg, I - Beg, p);
return OptionalAmount(false);
}
if (I == E) {
// No more characters left?
H.HandleIncompleteSpecifier(Start, E - Start);
return OptionalAmount(false);
}
assert(Amt.getHowSpecified() == OptionalAmount::Constant);
if (*I == '$') {
// Handle positional arguments
// Special case: '*0$', since this is an easy mistake.
if (Amt.getConstantAmount() == 0) {
H.HandleZeroPosition(Beg, I - Beg + 1);
return OptionalAmount(false);
}
const char *Tmp = Beg;
Beg = ++I;
return OptionalAmount(OptionalAmount::Arg, Amt.getConstantAmount() - 1,
Tmp, 0, true);
}
H.HandleInvalidPosition(Beg, I - Beg, p);
return OptionalAmount(false);
}
return ParseAmount(Beg, E);
}
bool
clang::analyze_format_string::ParseFieldWidth(FormatStringHandler &H,
FormatSpecifier &CS,
const char *Start,
const char *&Beg, const char *E,
unsigned *argIndex) {
// FIXME: Support negative field widths.
if (argIndex) {
CS.setFieldWidth(ParseNonPositionAmount(Beg, E, *argIndex));
}
else {
const OptionalAmount Amt =
ParsePositionAmount(H, Start, Beg, E,
analyze_format_string::FieldWidthPos);
if (Amt.isInvalid())
return true;
CS.setFieldWidth(Amt);
}
return false;
}
bool
clang::analyze_format_string::ParseArgPosition(FormatStringHandler &H,
FormatSpecifier &FS,
const char *Start,
const char *&Beg,
const char *E) {
const char *I = Beg;
const OptionalAmount &Amt = ParseAmount(I, E);
if (I == E) {
// No more characters left?
H.HandleIncompleteSpecifier(Start, E - Start);
return true;
}
if (Amt.getHowSpecified() == OptionalAmount::Constant && *(I++) == '$') {
// Warn that positional arguments are non-standard.
H.HandlePosition(Start, I - Start);
// Special case: '%0$', since this is an easy mistake.
if (Amt.getConstantAmount() == 0) {
H.HandleZeroPosition(Start, I - Start);
return true;
}
FS.setArgIndex(Amt.getConstantAmount() - 1);
FS.setUsesPositionalArg();
// Update the caller's pointer if we decided to consume
// these characters.
Beg = I;
return false;
}
return false;
}
bool
clang::analyze_format_string::ParseVectorModifier(FormatStringHandler &H,
FormatSpecifier &FS,
const char *&I,
const char *E,
const LangOptions &LO) {
if (!LO.OpenCL)
return false;
const char *Start = I;
if (*I == 'v') {
++I;
if (I == E) {
H.HandleIncompleteSpecifier(Start, E - Start);
return true;
}
OptionalAmount NumElts = ParseAmount(I, E);
if (NumElts.getHowSpecified() != OptionalAmount::Constant) {
H.HandleIncompleteSpecifier(Start, E - Start);
return true;
}
FS.setVectorNumElts(NumElts);
}
return false;
}
bool
clang::analyze_format_string::ParseLengthModifier(FormatSpecifier &FS,
const char *&I,
const char *E,
const LangOptions &LO,
bool IsScanf) {
LengthModifier::Kind lmKind = LengthModifier::None;
const char *lmPosition = I;
switch (*I) {
default:
return false;
case 'h':
++I;
if (I != E && *I == 'h') {
++I;
lmKind = LengthModifier::AsChar;
} else if (I != E && *I == 'l' && LO.OpenCL) {
++I;
lmKind = LengthModifier::AsShortLong;
} else {
lmKind = LengthModifier::AsShort;
}
break;
case 'l':
++I;
if (I != E && *I == 'l') {
++I;
lmKind = LengthModifier::AsLongLong;
} else {
lmKind = LengthModifier::AsLong;
}
break;
case 'j': lmKind = LengthModifier::AsIntMax; ++I; break;
case 'z': lmKind = LengthModifier::AsSizeT; ++I; break;
case 't': lmKind = LengthModifier::AsPtrDiff; ++I; break;
case 'L': lmKind = LengthModifier::AsLongDouble; ++I; break;
case 'q': lmKind = LengthModifier::AsQuad; ++I; break;
case 'a':
if (IsScanf && !LO.C99 && !LO.CPlusPlus11) {
// For scanf in C90, look at the next character to see if this should
// be parsed as the GNU extension 'a' length modifier. If not, this
// will be parsed as a conversion specifier.
++I;
if (I != E && (*I == 's' || *I == 'S' || *I == '[')) {
lmKind = LengthModifier::AsAllocate;
break;
}
--I;
}
return false;
case 'm':
if (IsScanf) {
lmKind = LengthModifier::AsMAllocate;
++I;
break;
}
return false;
// printf: AsInt64, AsInt32, AsInt3264
// scanf: AsInt64
case 'I':
if (I + 1 != E && I + 2 != E) {
if (I[1] == '6' && I[2] == '4') {
I += 3;
lmKind = LengthModifier::AsInt64;
break;
}
if (IsScanf)
return false;
if (I[1] == '3' && I[2] == '2') {
I += 3;
lmKind = LengthModifier::AsInt32;
break;
}
}
++I;
lmKind = LengthModifier::AsInt3264;
break;
case 'w':
lmKind = LengthModifier::AsWide; ++I; break;
}
LengthModifier lm(lmPosition, lmKind);
FS.setLengthModifier(lm);
return true;
}
bool clang::analyze_format_string::ParseUTF8InvalidSpecifier(
const char *SpecifierBegin, const char *FmtStrEnd, unsigned &Len) {
if (SpecifierBegin + 1 >= FmtStrEnd)
return false;
const llvm::UTF8 *SB =
reinterpret_cast<const llvm::UTF8 *>(SpecifierBegin + 1);
const llvm::UTF8 *SE = reinterpret_cast<const llvm::UTF8 *>(FmtStrEnd);
const char FirstByte = *SB;
// If the invalid specifier is a multibyte UTF-8 string, return the
// total length accordingly so that the conversion specifier can be
// properly updated to reflect a complete UTF-8 specifier.
unsigned NumBytes = llvm::getNumBytesForUTF8(FirstByte);
if (NumBytes == 1)
return false;
if (SB + NumBytes > SE)
return false;
Len = NumBytes + 1;
return true;
}
//===----------------------------------------------------------------------===//
// Methods on ArgType.
//===----------------------------------------------------------------------===//
static bool namedTypeToLengthModifierKind(ASTContext &Ctx, QualType QT,
LengthModifier::Kind &K) {
if (!Ctx.getLangOpts().C99 && !Ctx.getLangOpts().CPlusPlus)
return false;
for (/**/; const auto *TT = QT->getAs<TypedefType>(); QT = TT->desugar()) {
const auto *TD = TT->getDecl();
const auto *DC = TT->getDecl()->getDeclContext();
if (DC->isTranslationUnit() || DC->isStdNamespace()) {
StringRef Name = TD->getIdentifier()->getName();
if (Name == "size_t") {
K = LengthModifier::AsSizeT;
return true;
} else if (Name == "ssize_t" /*Not C99, but common in Unix.*/) {
K = LengthModifier::AsSizeT;
return true;
} else if (Name == "ptrdiff_t") {
K = LengthModifier::AsPtrDiff;
return true;
} else if (Name == "intmax_t") {
K = LengthModifier::AsIntMax;
return true;
} else if (Name == "uintmax_t") {
K = LengthModifier::AsIntMax;
return true;
}
}
}
if (const auto *PST = QT->getAs<PredefinedSugarType>()) {
using Kind = PredefinedSugarType::Kind;
switch (PST->getKind()) {
case Kind::SizeT:
case Kind::SignedSizeT:
K = LengthModifier::AsSizeT;
return true;
case Kind::PtrdiffT:
K = LengthModifier::AsPtrDiff;
return true;
}
llvm_unreachable("unexpected kind");
}
return false;
}
// Check whether T and E are compatible size_t/ptrdiff_t types. E must be
// consistent with LE.
// T is the type of the actual expression in the code to be checked, and E is
// the expected type parsed from the format string.
static clang::analyze_format_string::ArgType::MatchKind
matchesSizeTPtrdiffT(ASTContext &C, QualType T, QualType E) {
using MatchKind = clang::analyze_format_string::ArgType::MatchKind;
if (!T->isIntegerType() || T->isBooleanType())
return MatchKind::NoMatch;
if (C.hasSameType(T, E))
return MatchKind::Match;
if (C.getCorrespondingSignedType(T.getCanonicalType()) !=
C.getCorrespondingSignedType(E.getCanonicalType()))
return MatchKind::NoMatch;
return MatchKind::NoMatchSignedness;
}
clang::analyze_format_string::ArgType::MatchKind
ArgType::matchesType(ASTContext &C, QualType argTy) const {
// When using the format attribute in C++, you can receive a function or an
// array that will necessarily decay to a pointer when passed to the final
// format consumer. Apply decay before type comparison.
if (argTy->canDecayToPointerType())
argTy = C.getDecayedType(argTy);
if (Ptr) {
// It has to be a pointer.
const PointerType *PT = argTy->getAs<PointerType>();
if (!PT)
return NoMatch;
// We cannot write through a const qualified pointer.
if (PT->getPointeeType().isConstQualified())
return NoMatch;
argTy = PT->getPointeeType();
}
if (const auto *OBT = argTy->getAs<OverflowBehaviorType>())
argTy = OBT->getUnderlyingType();
switch (K) {
case InvalidTy:
llvm_unreachable("ArgType must be valid");
case UnknownTy:
return Match;
case AnyCharTy: {
if (const auto *ED = argTy->getAsEnumDecl()) {
// If the enum is incomplete we know nothing about the underlying type.
// Assume that it's 'int'. Do not use the underlying type for a scoped
// enumeration.
if (!ED->isComplete())
return NoMatch;
if (!ED->isScoped())
argTy = ED->getIntegerType();
}
if (const auto *BT = argTy->getAs<BuiltinType>()) {
// The types are perfectly matched?
switch (BT->getKind()) {
default:
break;
case BuiltinType::Char_S:
case BuiltinType::SChar:
case BuiltinType::UChar:
case BuiltinType::Char_U:
return Match;
case BuiltinType::Bool:
if (!Ptr)
return Match;
break;
}
// "Partially matched" because of promotions?
if (!Ptr) {
switch (BT->getKind()) {
default:
break;
case BuiltinType::Int:
case BuiltinType::UInt:
return MatchPromotion;
case BuiltinType::Short:
case BuiltinType::UShort:
case BuiltinType::WChar_S:
case BuiltinType::WChar_U:
return NoMatchPromotionTypeConfusion;
}
}
}
return NoMatch;
}
case SpecificTy: {
if (TK != TypeKind::DontCare) {
return matchesSizeTPtrdiffT(C, argTy, T);
}
if (const auto *ED = argTy->getAsEnumDecl()) {
// If the enum is incomplete we know nothing about the underlying type.
// Assume that it's 'int'. Do not use the underlying type for a scoped
// enumeration as that needs an exact match.
if (!ED->isComplete())
argTy = C.IntTy;
else if (!ED->isScoped())
argTy = ED->getIntegerType();
}
if (argTy->isSaturatedFixedPointType())
argTy = C.getCorrespondingUnsaturatedType(argTy);
argTy = C.getCanonicalType(argTy).getUnqualifiedType();
if (T == argTy)
return Match;
if (const auto *BT = argTy->getAs<BuiltinType>()) {
// Check if the only difference between them is signed vs unsigned
// if true, return match signedness.
switch (BT->getKind()) {
default:
break;
case BuiltinType::Bool:
if (Ptr && (T == C.UnsignedCharTy || T == C.SignedCharTy))
return NoMatch;
[[fallthrough]];
case BuiltinType::Char_S:
case BuiltinType::SChar:
if (T == C.UnsignedShortTy || T == C.ShortTy)
return NoMatchTypeConfusion;
if (T == C.UnsignedCharTy)
return NoMatchSignedness;
if (T == C.SignedCharTy)
return Match;
break;
case BuiltinType::Char_U:
case BuiltinType::UChar:
if (T == C.UnsignedShortTy || T == C.ShortTy)
return NoMatchTypeConfusion;
if (T == C.UnsignedCharTy)
return Match;
if (T == C.SignedCharTy)
return NoMatchSignedness;
break;
case BuiltinType::Short:
if (T == C.UnsignedShortTy)
return NoMatchSignedness;
break;
case BuiltinType::UShort:
if (T == C.ShortTy)
return NoMatchSignedness;
break;
case BuiltinType::Int:
if (T == C.UnsignedIntTy)
return NoMatchSignedness;
break;
case BuiltinType::UInt:
if (T == C.IntTy)
return NoMatchSignedness;
break;
case BuiltinType::Long:
if (T == C.UnsignedLongTy)
return NoMatchSignedness;
break;
case BuiltinType::ULong:
if (T == C.LongTy)
return NoMatchSignedness;
break;
case BuiltinType::LongLong:
if (T == C.UnsignedLongLongTy)
return NoMatchSignedness;
break;
case BuiltinType::ULongLong:
if (T == C.LongLongTy)
return NoMatchSignedness;
break;
}
// "Partially matched" because of promotions?
if (!Ptr) {
switch (BT->getKind()) {
default:
break;
case BuiltinType::Bool:
if (T == C.IntTy || T == C.UnsignedIntTy)
return MatchPromotion;
break;
case BuiltinType::Int:
case BuiltinType::UInt:
if (T == C.SignedCharTy || T == C.UnsignedCharTy ||
T == C.ShortTy || T == C.UnsignedShortTy || T == C.WCharTy ||
T == C.WideCharTy)
return MatchPromotion;
break;
case BuiltinType::Char_U:
if (T == C.UnsignedIntTy)
return MatchPromotion;
if (T == C.UnsignedShortTy)
return NoMatchPromotionTypeConfusion;
break;
case BuiltinType::Char_S:
if (T == C.IntTy)
return MatchPromotion;
if (T == C.ShortTy)
return NoMatchPromotionTypeConfusion;
break;
case BuiltinType::Half:
case BuiltinType::Float:
if (T == C.DoubleTy)
return MatchPromotion;
break;
case BuiltinType::Short:
case BuiltinType::UShort:
if (T == C.SignedCharTy || T == C.UnsignedCharTy)
return NoMatchPromotionTypeConfusion;
break;
case BuiltinType::WChar_U:
case BuiltinType::WChar_S:
if (T != C.WCharTy && T != C.WideCharTy)
return NoMatchPromotionTypeConfusion;
}
}
}
return NoMatch;
}
case CStrTy:
if (const auto *PT = argTy->getAs<PointerType>();
PT && PT->getPointeeType()->isCharType())
return Match;
return NoMatch;
case WCStrTy:
if (const auto *PT = argTy->getAs<PointerType>();
PT &&
C.hasSameUnqualifiedType(PT->getPointeeType(), C.getWideCharType()))
return Match;
return NoMatch;
case WIntTy: {
QualType WInt = C.getCanonicalType(C.getWIntType()).getUnqualifiedType();
if (C.getCanonicalType(argTy).getUnqualifiedType() == WInt)
return Match;
QualType PromoArg = C.isPromotableIntegerType(argTy)
? C.getPromotedIntegerType(argTy)
: argTy;
PromoArg = C.getCanonicalType(PromoArg).getUnqualifiedType();
// If the promoted argument is the corresponding signed type of the
// wint_t type, then it should match.
if (PromoArg->hasSignedIntegerRepresentation() &&
C.getCorrespondingUnsignedType(PromoArg) == WInt)
return Match;
return WInt == PromoArg ? Match : NoMatch;
}
case CPointerTy:
if (const auto *PT = argTy->getAs<PointerType>()) {
QualType PointeeTy = PT->getPointeeType();
if (PointeeTy->isVoidType() || (!Ptr && PointeeTy->isCharType()))
return Match;
return NoMatchPedantic;
}
// nullptr_t* is not a double pointer, so reject when something like
// void** is expected.
// In C++, nullptr is promoted to void*. In C23, va_arg(ap, void*) is not
// undefined when the next argument is of type nullptr_t.
if (!Ptr && argTy->isNullPtrType())
return C.getLangOpts().CPlusPlus ? MatchPromotion : Match;
if (argTy->isObjCObjectPointerType() || argTy->isBlockPointerType())
return NoMatchPedantic;
return NoMatch;
case ObjCPointerTy: {
if (argTy->getAs<ObjCObjectPointerType>() ||
argTy->getAs<BlockPointerType>())
return Match;
// Handle implicit toll-free bridging.
if (const PointerType *PT = argTy->getAs<PointerType>()) {
// Things such as CFTypeRef are really just opaque pointers
// to C structs representing CF types that can often be bridged
// to Objective-C objects. Since the compiler doesn't know which
// structs can be toll-free bridged, we just accept them all.
QualType pointee = PT->getPointeeType();
if (pointee->isStructureType() || pointee->isVoidType())
return Match;
}
return NoMatch;
}
}
llvm_unreachable("Invalid ArgType Kind!");
}
static analyze_format_string::ArgType::MatchKind
integerTypeMatch(ASTContext &C, QualType A, QualType B, bool CheckSign) {
using MK = analyze_format_string::ArgType::MatchKind;
uint64_t IntSize = C.getTypeSize(C.IntTy);
uint64_t ASize = C.getTypeSize(A);
uint64_t BSize = C.getTypeSize(B);
if (std::max(ASize, IntSize) != std::max(BSize, IntSize))
return MK::NoMatch;
if (CheckSign && A->isSignedIntegerType() != B->isSignedIntegerType())
return MK::NoMatchSignedness;
if (ASize != BSize)
return MK::MatchPromotion;
return MK::Match;
}
analyze_format_string::ArgType::MatchKind
ArgType::matchesArgType(ASTContext &C, const ArgType &Other) const {
using AK = analyze_format_string::ArgType::Kind;
// Per matchesType.
if (K == AK::InvalidTy || Other.K == AK::InvalidTy)
return NoMatch;
if (K == AK::UnknownTy || Other.K == AK::UnknownTy)
return Match;
// Handle whether either (or both, or neither) sides has Ptr set,
// in addition to whether either (or both, or neither) sides is a SpecificTy
// that is a pointer.
ArgType Left = *this;
bool LeftWasPointer = false;
ArgType Right = Other;
bool RightWasPointer = false;
if (Left.Ptr) {
Left.Ptr = false;
LeftWasPointer = true;
} else if (Left.K == AK::SpecificTy && Left.T->isPointerType()) {
Left.T = Left.T->getPointeeType();
LeftWasPointer = true;
}
if (Right.Ptr) {
Right.Ptr = false;
RightWasPointer = true;
} else if (Right.K == AK::SpecificTy && Right.T->isPointerType()) {
Right.T = Right.T->getPointeeType();
RightWasPointer = true;
}
if (LeftWasPointer != RightWasPointer)
return NoMatch;
// Ensure that if at least one side is a SpecificTy, then Left is a
// SpecificTy.
if (Right.K == AK::SpecificTy)
std::swap(Left, Right);
if (Left.K == AK::SpecificTy) {
if (Right.K == AK::SpecificTy) {
if (Left.TK != TypeKind::DontCare) {
return matchesSizeTPtrdiffT(C, Right.T, Left.T);
} else if (Right.TK != TypeKind::DontCare) {
return matchesSizeTPtrdiffT(C, Left.T, Right.T);
}
auto Canon1 = C.getCanonicalType(Left.T);
auto Canon2 = C.getCanonicalType(Right.T);
if (Canon1 == Canon2)
return Match;
auto *BT1 = QualType(Canon1)->getAs<BuiltinType>();
auto *BT2 = QualType(Canon2)->getAs<BuiltinType>();
if (BT1 == nullptr || BT2 == nullptr)
return NoMatch;
if (BT1 == BT2)
return Match;
if (!LeftWasPointer && BT1->isInteger() && BT2->isInteger())
return integerTypeMatch(C, Canon1, Canon2, true);
return NoMatch;
} else if (Right.K == AK::AnyCharTy) {
if (!LeftWasPointer && Left.T->isIntegerType())
return integerTypeMatch(C, Left.T, C.CharTy, false);
return NoMatch;
} else if (Right.K == AK::WIntTy) {
if (!LeftWasPointer && Left.T->isIntegerType())
return integerTypeMatch(C, Left.T, C.WIntTy, true);
return NoMatch;
}
// It's hypothetically possible to create an AK::SpecificTy ArgType
// that matches another kind of ArgType, but in practice Clang doesn't
// do that, so ignore that case.
return NoMatch;
}
return Left.K == Right.K ? Match : NoMatch;
}
ArgType ArgType::makeVectorType(ASTContext &C, unsigned NumElts) const {
// Check for valid vector element types.
if (T.isNull())
return ArgType::Invalid();
QualType Vec = C.getExtVectorType(T, NumElts);
return ArgType(Vec, Name);
}
QualType ArgType::getRepresentativeType(ASTContext &C) const {
QualType Res;
switch (K) {
case InvalidTy:
llvm_unreachable("No representative type for Invalid ArgType");
case UnknownTy:
llvm_unreachable("No representative type for Unknown ArgType");
case AnyCharTy:
Res = C.CharTy;
break;
case SpecificTy:
if (TK == TypeKind::PtrdiffT || TK == TypeKind::SizeT)
// Using Name as name, so no need to show the uglified name.
Res = T->getCanonicalTypeInternal();
else
Res = T;
break;
case CStrTy:
Res = C.getPointerType(C.CharTy);
break;
case WCStrTy:
Res = C.getPointerType(C.getWideCharType());
break;
case ObjCPointerTy:
Res = C.ObjCBuiltinIdTy;
break;
case CPointerTy:
Res = C.VoidPtrTy;
break;
case WIntTy: {
Res = C.getWIntType();
break;
}
}
if (Ptr)
Res = C.getPointerType(Res);
return Res;
}
std::string ArgType::getRepresentativeTypeName(ASTContext &C) const {
std::string S = getRepresentativeType(C).getAsString(C.getPrintingPolicy());
std::string Alias;
if (Name) {
// Use a specific name for this type, e.g. "size_t".
Alias = Name;
if (Ptr) {
// If ArgType is actually a pointer to T, append an asterisk.
Alias += (Alias[Alias.size()-1] == '*') ? "*" : " *";
}
// If Alias is the same as the underlying type, e.g. wchar_t, then drop it.
if (S == Alias)
Alias.clear();
}
if (!Alias.empty())
return std::string("'") + Alias + "' (aka '" + S + "')";
return std::string("'") + S + "'";
}
//===----------------------------------------------------------------------===//
// Methods on OptionalAmount.
//===----------------------------------------------------------------------===//
ArgType
analyze_format_string::OptionalAmount::getArgType(ASTContext &Ctx) const {
return Ctx.IntTy;
}
//===----------------------------------------------------------------------===//
// Methods on LengthModifier.
//===----------------------------------------------------------------------===//
const char *
analyze_format_string::LengthModifier::toString() const {
switch (kind) {
case AsChar:
return "hh";
case AsShort:
return "h";
case AsShortLong:
return "hl";
case AsLong: // or AsWideChar
return "l";
case AsLongLong:
return "ll";
case AsQuad:
return "q";
case AsIntMax:
return "j";
case AsSizeT:
return "z";
case AsPtrDiff:
return "t";
case AsInt32:
return "I32";
case AsInt3264:
return "I";
case AsInt64:
return "I64";
case AsLongDouble:
return "L";
case AsAllocate:
return "a";
case AsMAllocate:
return "m";
case AsWide:
return "w";
case None:
return "";
}
return nullptr;
}
//===----------------------------------------------------------------------===//
// Methods on ConversionSpecifier.
//===----------------------------------------------------------------------===//
const char *ConversionSpecifier::toString() const {
switch (kind) {
case bArg: return "b";
case BArg: return "B";
case dArg: return "d";
case DArg: return "D";
case iArg: return "i";
case oArg: return "o";
case OArg: return "O";
case uArg: return "u";
case UArg: return "U";
case xArg: return "x";
case XArg: return "X";
case fArg: return "f";
case FArg: return "F";
case eArg: return "e";
case EArg: return "E";
case gArg: return "g";
case GArg: return "G";
case aArg: return "a";
case AArg: return "A";
case cArg: return "c";
case sArg: return "s";
case pArg: return "p";
case PArg:
return "P";
case nArg: return "n";
case PercentArg: return "%";
case ScanListArg: return "[";
case InvalidSpecifier: return nullptr;
// POSIX unicode extensions.
case CArg: return "C";
case SArg: return "S";
// Objective-C specific specifiers.
case ObjCObjArg: return "@";
// FreeBSD kernel specific specifiers.
case FreeBSDbArg: return "b";
case FreeBSDDArg: return "D";
case FreeBSDrArg: return "r";
case FreeBSDyArg: return "y";
// GlibC specific specifiers.
case PrintErrno: return "m";
// MS specific specifiers.
case ZArg: return "Z";
// ISO/IEC TR 18037 (fixed-point) specific specifiers.
case rArg:
return "r";
case RArg:
return "R";
case kArg:
return "k";
case KArg:
return "K";
}
return nullptr;
}
std::optional<ConversionSpecifier>
ConversionSpecifier::getStandardSpecifier() const {
ConversionSpecifier::Kind NewKind;
switch (getKind()) {
default:
return std::nullopt;
case DArg:
NewKind = dArg;
break;
case UArg:
NewKind = uArg;
break;
case OArg:
NewKind = oArg;
break;
}
ConversionSpecifier FixedCS(*this);
FixedCS.setKind(NewKind);
return FixedCS;
}
//===----------------------------------------------------------------------===//
// Methods on OptionalAmount.
//===----------------------------------------------------------------------===//
void OptionalAmount::toString(raw_ostream &os) const {
switch (hs) {
case Invalid:
case NotSpecified:
return;
case Arg:
if (UsesDotPrefix)
os << ".";
if (usesPositionalArg())
os << "*" << getPositionalArgIndex() << "$";
else
os << "*";
break;
case Constant:
if (UsesDotPrefix)
os << ".";
os << amt;
break;
}
}
bool FormatSpecifier::hasValidLengthModifier(const TargetInfo &Target,
const LangOptions &LO) const {
switch (LM.getKind()) {
case LengthModifier::None:
return true;
// Handle most integer flags
case LengthModifier::AsShort:
// Length modifier only applies to FP vectors.
if (LO.OpenCL && CS.isDoubleArg())
return !VectorNumElts.isInvalid();
if (CS.isFixedPointArg())
return true;
if (Target.getTriple().isOSMSVCRT()) {
switch (CS.getKind()) {
case ConversionSpecifier::cArg:
case ConversionSpecifier::CArg:
case ConversionSpecifier::sArg:
case ConversionSpecifier::SArg:
case ConversionSpecifier::ZArg:
return true;
default:
break;
}
}
[[fallthrough]];
case LengthModifier::AsChar:
case LengthModifier::AsLongLong:
case LengthModifier::AsQuad:
case LengthModifier::AsIntMax:
case LengthModifier::AsSizeT:
case LengthModifier::AsPtrDiff:
switch (CS.getKind()) {
case ConversionSpecifier::bArg:
case ConversionSpecifier::BArg:
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::nArg:
return true;
case ConversionSpecifier::FreeBSDrArg:
case ConversionSpecifier::FreeBSDyArg:
return Target.getTriple().isOSFreeBSD() || Target.getTriple().isPS();
default:
return false;
}
case LengthModifier::AsShortLong:
return LO.OpenCL && !VectorNumElts.isInvalid();
// Handle 'l' flag
case LengthModifier::AsLong: // or AsWideChar
if (CS.isDoubleArg()) {
// Invalid for OpenCL FP scalars.
if (LO.OpenCL && VectorNumElts.isInvalid())
return false;
return true;
}
if (CS.isFixedPointArg())
return true;
switch (CS.getKind()) {
case ConversionSpecifier::bArg:
case ConversionSpecifier::BArg:
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::nArg:
case ConversionSpecifier::cArg:
case ConversionSpecifier::sArg:
case ConversionSpecifier::ScanListArg:
case ConversionSpecifier::ZArg:
return true;
case ConversionSpecifier::FreeBSDrArg:
case ConversionSpecifier::FreeBSDyArg:
return Target.getTriple().isOSFreeBSD() || Target.getTriple().isPS();
default:
return false;
}
case LengthModifier::AsLongDouble:
switch (CS.getKind()) {
case ConversionSpecifier::aArg:
case ConversionSpecifier::AArg:
case ConversionSpecifier::fArg:
case ConversionSpecifier::FArg:
case ConversionSpecifier::eArg:
case ConversionSpecifier::EArg:
case ConversionSpecifier::gArg:
case ConversionSpecifier::GArg:
return true;
// GNU libc extension.
case ConversionSpecifier::dArg:
case ConversionSpecifier::iArg:
case ConversionSpecifier::oArg:
case ConversionSpecifier::uArg:
case ConversionSpecifier::xArg:
case ConversionSpecifier::XArg:
return !Target.getTriple().isOSDarwin() &&
!Target.getTriple().isOSWindows();
default:
return false;
}
case LengthModifier::AsAllocate:
switch (CS.getKind()) {
case ConversionSpecifier::sArg:
case ConversionSpecifier::SArg:
case ConversionSpecifier::ScanListArg:
return true;
default:
return false;
}
case LengthModifier::AsMAllocate:
switch (CS.getKind()) {
case ConversionSpecifier::cArg:
case ConversionSpecifier::CArg:
case ConversionSpecifier::sArg:
case ConversionSpecifier::SArg:
case ConversionSpecifier::ScanListArg:
return true;
default:
return false;
}
case LengthModifier::AsInt32:
case LengthModifier::AsInt3264:
case LengthModifier::AsInt64:
switch (CS.getKind()) {
case ConversionSpecifier::dArg:
case ConversionSpecifier::iArg:
case ConversionSpecifier::oArg:
case ConversionSpecifier::uArg:
case ConversionSpecifier::xArg:
case ConversionSpecifier::XArg:
return Target.getTriple().isOSMSVCRT();
default:
return false;
}
case LengthModifier::AsWide:
switch (CS.getKind()) {
case ConversionSpecifier::cArg:
case ConversionSpecifier::CArg:
case ConversionSpecifier::sArg:
case ConversionSpecifier::SArg:
case ConversionSpecifier::ZArg:
return Target.getTriple().isOSMSVCRT();
default:
return false;
}
}
llvm_unreachable("Invalid LengthModifier Kind!");
}
bool FormatSpecifier::hasStandardLengthModifier() const {
switch (LM.getKind()) {
case LengthModifier::None:
case LengthModifier::AsChar:
case LengthModifier::AsShort:
case LengthModifier::AsLong:
case LengthModifier::AsLongLong:
case LengthModifier::AsIntMax:
case LengthModifier::AsSizeT:
case LengthModifier::AsPtrDiff:
case LengthModifier::AsLongDouble:
return true;
case LengthModifier::AsAllocate:
case LengthModifier::AsMAllocate:
case LengthModifier::AsQuad:
case LengthModifier::AsInt32:
case LengthModifier::AsInt3264:
case LengthModifier::AsInt64:
case LengthModifier::AsWide:
case LengthModifier::AsShortLong: // ???
return false;
}
llvm_unreachable("Invalid LengthModifier Kind!");
}
bool FormatSpecifier::hasStandardConversionSpecifier(
const LangOptions &LangOpt) const {
switch (CS.getKind()) {
case ConversionSpecifier::bArg:
case ConversionSpecifier::BArg:
case ConversionSpecifier::cArg:
case ConversionSpecifier::dArg:
case ConversionSpecifier::iArg:
case ConversionSpecifier::oArg:
case ConversionSpecifier::uArg:
case ConversionSpecifier::xArg:
case ConversionSpecifier::XArg:
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::sArg:
case ConversionSpecifier::pArg:
case ConversionSpecifier::nArg:
case ConversionSpecifier::ObjCObjArg:
case ConversionSpecifier::ScanListArg:
case ConversionSpecifier::PercentArg:
case ConversionSpecifier::PArg:
return true;
case ConversionSpecifier::CArg:
case ConversionSpecifier::SArg:
return LangOpt.ObjC;
case ConversionSpecifier::InvalidSpecifier:
case ConversionSpecifier::FreeBSDbArg:
case ConversionSpecifier::FreeBSDDArg:
case ConversionSpecifier::FreeBSDrArg:
case ConversionSpecifier::FreeBSDyArg:
case ConversionSpecifier::PrintErrno:
case ConversionSpecifier::DArg:
case ConversionSpecifier::OArg:
case ConversionSpecifier::UArg:
case ConversionSpecifier::ZArg:
return false;
case ConversionSpecifier::rArg:
case ConversionSpecifier::RArg:
case ConversionSpecifier::kArg:
case ConversionSpecifier::KArg:
return LangOpt.FixedPoint;
}
llvm_unreachable("Invalid ConversionSpecifier Kind!");
}
bool FormatSpecifier::hasStandardLengthConversionCombination() const {
if (LM.getKind() == LengthModifier::AsLongDouble) {
switch(CS.getKind()) {
case ConversionSpecifier::dArg:
case ConversionSpecifier::iArg:
case ConversionSpecifier::oArg:
case ConversionSpecifier::uArg:
case ConversionSpecifier::xArg:
case ConversionSpecifier::XArg:
return false;
default:
return true;
}
}
return true;
}
std::optional<LengthModifier>
FormatSpecifier::getCorrectedLengthModifier() const {
if (CS.isAnyIntArg() || CS.getKind() == ConversionSpecifier::nArg) {
if (LM.getKind() == LengthModifier::AsLongDouble ||
LM.getKind() == LengthModifier::AsQuad) {
LengthModifier FixedLM(LM);
FixedLM.setKind(LengthModifier::AsLongLong);
return FixedLM;
}
}
return std::nullopt;
}
bool FormatSpecifier::namedTypeToLengthModifier(ASTContext &Ctx, QualType QT,
LengthModifier &LM) {
if (LengthModifier::Kind Out = LengthModifier::Kind::None;
namedTypeToLengthModifierKind(Ctx, QT, Out)) {
LM.setKind(Out);
return true;
}
return false;
}