blob: c7e00cc964f82a8fbe841efd1f8f3fad7c86cedb [file]
//===--- Pointer.cpp - Types for the constexpr VM ---------------*- 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
//
//===----------------------------------------------------------------------===//
#include "Pointer.h"
#include "Boolean.h"
#include "Char.h"
#include "Context.h"
#include "Floating.h"
#include "Function.h"
#include "InitMap.h"
#include "Integral.h"
#include "InterpBlock.h"
#include "MemberPointer.h"
#include "PrimType.h"
#include "Record.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/RecordLayout.h"
using namespace clang;
using namespace clang::interp;
// Helper to check if a Type can be passed to
// ASTContext::getTypeSize().
static bool validType(QualType T) {
if (const RecordDecl *RD = T->getAsRecordDecl())
return ASTContext::hasLayout(RD);
return !T->isDependentType() && !T->isUndeducedAutoType() &&
!T->isSpecificBuiltinType(BuiltinType::UnknownAny) &&
!T->isIncompleteType();
}
Pointer::Pointer(Block *Pointee)
: Pointer(Pointee, Pointee->getMetadataSize(), Pointee->getMetadataSize()) {
}
Pointer::Pointer(Block *Pointee, uint64_t BaseAndOffset)
: Pointer(Pointee, BaseAndOffset, BaseAndOffset) {}
Pointer::Pointer(Block *Pointee, unsigned Base, uint64_t Offset)
: Offset(Offset), StorageKind(Storage::Block) {
assert(Pointee);
assert(Base % alignof(void *) == 0 && "wrong base");
assert(Base >= Pointee->getMetadataSize());
BS = {Pointee, Base, nullptr, nullptr};
Pointee->addPointer(this);
}
Pointer::Pointer(const Pointer &P)
: Offset(P.Offset), StorageKind(P.StorageKind) {
switch (StorageKind) {
case Storage::Int:
Int = P.Int;
break;
case Storage::Block:
BS = P.BS;
if (BS.Pointee)
BS.Pointee->addPointer(this);
break;
case Storage::Fn:
Fn = P.Fn;
break;
case Storage::Typeid:
Typeid = P.Typeid;
break;
case Storage::String:
Str = P.Str;
break;
case Storage::Opaque:
Opaque = P.Opaque;
break;
}
}
Pointer::Pointer(Pointer &&P) : Offset(P.Offset), StorageKind(P.StorageKind) {
switch (StorageKind) {
case Storage::Int:
Int = P.Int;
break;
case Storage::Block:
BS = P.BS;
if (BS.Pointee)
BS.Pointee->replacePointer(&P, this);
break;
case Storage::Fn:
Fn = P.Fn;
break;
case Storage::Typeid:
Typeid = P.Typeid;
break;
case Storage::String:
Str = P.Str;
break;
case Storage::Opaque:
Opaque = P.Opaque;
break;
}
}
Pointer::~Pointer() {
if (!isBlockPointer())
return;
if (Block *Pointee = BS.Pointee) {
Pointee->removePointer(this);
BS.Pointee = nullptr;
Pointee->cleanup();
}
}
Pointer &Pointer::operator=(const Pointer &P) {
// If the current storage type is Block, we need to remove
// this pointer from the block.
if (isBlockPointer()) {
if (P.isBlockPointer() && this->block() == P.block()) {
Offset = P.Offset;
BS.Base = P.BS.Base;
return *this;
}
if (Block *Pointee = BS.Pointee) {
Pointee->removePointer(this);
BS.Pointee = nullptr;
Pointee->cleanup();
}
}
StorageKind = P.StorageKind;
Offset = P.Offset;
switch (StorageKind) {
case Storage::Int:
Int = P.Int;
break;
case Storage::Block:
BS = P.BS;
if (BS.Pointee)
BS.Pointee->addPointer(this);
break;
case Storage::Fn:
Fn = P.Fn;
break;
case Storage::Typeid:
Typeid = P.Typeid;
break;
case Storage::String:
Str = P.Str;
break;
case Storage::Opaque:
Opaque = P.Opaque;
break;
}
return *this;
}
Pointer &Pointer::operator=(Pointer &&P) {
// If the current storage type is Block, we need to remove
// this pointer from the block.
if (isBlockPointer()) {
if (P.isBlockPointer() && this->block() == P.block()) {
Offset = P.Offset;
BS.Base = P.BS.Base;
return *this;
}
if (Block *Pointee = BS.Pointee) {
Pointee->removePointer(this);
BS.Pointee = nullptr;
Pointee->cleanup();
}
}
StorageKind = P.StorageKind;
Offset = P.Offset;
switch (StorageKind) {
case Storage::Int:
Int = P.Int;
break;
case Storage::Block:
BS = P.BS;
if (BS.Pointee)
BS.Pointee->addPointer(this);
break;
case Storage::Fn:
Fn = P.Fn;
break;
case Storage::Typeid:
Typeid = P.Typeid;
break;
case Storage::String:
Str = P.Str;
break;
case Storage::Opaque:
Opaque = P.Opaque;
break;
}
return *this;
}
bool Pointer::operator==(const Pointer &P) const {
if (StorageKind != P.StorageKind)
return false;
switch (StorageKind) {
case Storage::Int:
return P.Int.Value == Int.Value && P.Int.getType() == Int.getType() &&
P.Offset == Offset;
case Storage::Block:
return P.view() == view();
case Storage::Fn:
return P.Fn.Func == Fn.Func && P.Offset == Offset;
case Storage::Typeid:
llvm_unreachable("typeid in operator==?");
case Storage::String:
return Str.Base == P.Str.Base && Offset == P.Offset;
case Storage::Opaque:
if (P.Opaque.Base != Opaque.Base ||
P.Opaque.PathLength != Opaque.PathLength || P.Offset != Offset)
return false;
for (unsigned I = 0; I != Opaque.PathLength; ++I) {
if (Opaque.Path[I].Kind != P.Opaque.Path[I].Kind)
return false;
switch (Opaque.Path[I].Kind) {
case PointerPathEntry::Base:
if (Opaque.Path[I].RD != P.Opaque.Path[I].RD)
return false;
break;
case PointerPathEntry::Array:
case PointerPathEntry::NegativeArray:
if (Opaque.Path[I].Index != P.Opaque.Path[I].Index)
return false;
break;
case PointerPathEntry::Field:
if (Opaque.Path[I].FD != P.Opaque.Path[I].FD)
return false;
break;
}
}
}
return true;
}
APValue Pointer::toAPValue(const ASTContext &ASTCtx) const {
if (isZero())
return APValue(APValue::LValueBase(), CharUnits::Zero(), {},
/*IsOnePastEnd=*/false, /*IsNullPtr=*/true);
switch (StorageKind) {
case Storage::Int:
return APValue(static_cast<const Expr *>(nullptr),
CharUnits::fromQuantity(asIntPointer().Value + this->Offset),
{},
/*IsOnePastEnd=*/false, /*IsNullPtr=*/false);
case Storage::Block:
// See below.
break;
case Storage::Fn: {
const FunctionPointer &FP = asFunctionPointer();
if (const FunctionDecl *FD = FP.Func->getDecl())
return APValue(FD, CharUnits::fromQuantity(Offset), {},
/*OnePastTheEnd=*/false, /*IsNull=*/false);
return APValue(FP.Func->getExpr(), CharUnits::fromQuantity(Offset), {},
/*OnePastTheEnd=*/false, /*IsNull=*/false);
} break;
case Storage::Typeid: {
TypeInfoLValue TypeInfo(Typeid.TypePtr);
return APValue(APValue::LValueBase::getTypeInfo(
TypeInfo, QualType(Typeid.TypeInfoType, 0)),
CharUnits::Zero(), {},
/*OnePastTheEnd=*/false, /*IsNull=*/false);
} break;
case Storage::String: {
llvm::SmallVector<APValue::LValuePathEntry, 1> Path;
if (Offset != 0 || Str.Decayed)
Path.push_back(APValue::LValuePathEntry::ArrayIndex(Offset));
return APValue(APValue::LValueBase(Str.Base),
CharUnits::fromQuantity(Offset * elemSize()), Path,
/*OnePastTheEnd=*/false, /*IsNull=*/false);
}
case Storage::Opaque: {
bool ValidBase = Opaque.hasValidBase() || this->Offset <= 1;
size_t LayoutOffset = Opaque.computeLayoutOffset(ASTCtx).value_or(0);
size_t ElemSize = 0;
if (validType(Opaque.getFieldType()))
ElemSize = ASTCtx.getTypeSizeInChars(Opaque.getFieldType()).getQuantity();
auto LValueOffset =
CharUnits::fromQuantity(LayoutOffset + (this->Offset * ElemSize));
APValue::LValueBase Base;
if (const Expr *E = Opaque.Base.asExpr())
Base = E;
else
Base = Opaque.Base.asValueDecl();
// For valid bases, assemble the LValuePath.
APValue Result;
if (ValidBase) {
llvm::SmallVector<APValue::LValuePathEntry, 5> Path;
for (const PointerPathEntry &Entry : Opaque.path()) {
switch (Entry.Kind) {
case PointerPathEntry::Field:
Path.push_back(APValue::LValuePathEntry({Entry.FD, false}));
break;
case PointerPathEntry::Base:
Path.push_back(APValue::LValuePathEntry(
{Entry.RD.getPointer(), Entry.RD.getInt()}));
break;
case PointerPathEntry::Array:
Path.push_back(APValue::LValuePathEntry::ArrayIndex(Entry.Index));
break;
case PointerPathEntry::NegativeArray:
Path.push_back(APValue::LValuePathEntry::ArrayIndex(-Entry.Index));
break;
}
}
Result = APValue(Base, LValueOffset, Path, Opaque.isOnePastEnd(),
/*IsNullPtr=*/false);
} else {
Result = APValue(Base, LValueOffset, APValue::NoLValuePath{});
}
Result.setConstexprUnknown(Opaque.isConstexprUnknown());
return Result;
}
}
assert(isBlockPointer());
// Build the lvalue base from the block.
const Descriptor *Desc = getDeclDesc();
APValue::LValueBase Base;
if (const auto *VD = Desc->asValueDecl())
Base = VD;
else if (const auto *E = Desc->asExpr()) {
if (block()->isDynamic()) {
QualType AllocatedType = getDeclPtr().getFieldDesc()->getDataType(ASTCtx);
DynamicAllocLValue DA(*block()->DynAllocId);
Base = APValue::LValueBase::getDynamicAlloc(DA, AllocatedType);
} else {
Base = E;
}
} else
llvm_unreachable("Invalid allocation type");
CharUnits Offset = CharUnits::Zero();
auto getFieldOffset = [&](const FieldDecl *FD) -> std::optional<CharUnits> {
if (!ASTContext::hasLayout(FD->getParent()))
return std::nullopt;
// This shouldn't happen, but if it does, don't crash inside
// getASTRecordLayout.
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(FD->getParent());
unsigned FieldIndex = FD->getFieldIndex();
return ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FieldIndex));
};
// Build the path into the object.
bool OnePastEnd = isOnePastEnd() && !isZeroSizeArray();
llvm::SmallVector<APValue::LValuePathEntry, 5> Path;
PtrView Ptr = view();
while (Ptr.isField() || Ptr.isArrayElement()) {
if (Ptr.isArrayRoot()) {
// An array root may still be an array element itself.
if (Ptr.isArrayElement()) {
Ptr = Ptr.expand();
const Descriptor *Desc = Ptr.getFieldDesc();
unsigned Index = Ptr.getIndex();
QualType ElemType = Desc->getElemQualType();
Offset += (Index * ASTCtx.getTypeSizeInChars(ElemType));
if (Ptr.getArray().getFieldDesc()->IsArray)
Path.push_back(APValue::LValuePathEntry::ArrayIndex(Index));
Ptr = Ptr.getArray();
} else {
const Descriptor *Desc = Ptr.getFieldDesc();
const auto *Dcl = Desc->asDecl();
Path.push_back(APValue::LValuePathEntry({Dcl, /*IsVirtual=*/false}));
if (const auto *FD = dyn_cast_if_present<FieldDecl>(Dcl)) {
if (std::optional<CharUnits> FieldOffset = getFieldOffset(FD))
Offset += *FieldOffset;
else
return APValue();
}
Ptr = Ptr.getBase();
}
} else if (Ptr.isArrayElement()) {
Ptr = Ptr.expand();
const Descriptor *Desc = Ptr.getFieldDesc();
unsigned Index;
if (Ptr.isOnePastEnd()) {
Index = Ptr.getArray().getNumElems();
OnePastEnd = false;
} else
Index = Ptr.getIndex();
QualType ElemType = Desc->getElemQualType();
if (const auto *RD = ElemType->getAsRecordDecl();
RD && !RD->getDefinition()) {
// Ignore this for the offset.
} else {
Offset += (Index * ASTCtx.getTypeSizeInChars(ElemType));
}
if (Ptr.getArray().getFieldDesc()->IsArray)
Path.push_back(APValue::LValuePathEntry::ArrayIndex(Index));
Ptr = Ptr.getArray();
} else {
const Descriptor *Desc = Ptr.getFieldDesc();
// Create a path entry for the field.
if (const auto *BaseOrMember = Desc->asDecl()) {
bool IsVirtual = false;
if (const auto *FD = dyn_cast<FieldDecl>(BaseOrMember)) {
Ptr = Ptr.getBase();
if (std::optional<CharUnits> FieldOffset = getFieldOffset(FD))
Offset += *FieldOffset;
else
return APValue();
} else if (const auto *RD = dyn_cast<CXXRecordDecl>(BaseOrMember)) {
IsVirtual = Ptr.isVirtualBaseClass();
Ptr = Ptr.getBase();
const Record *BaseRecord = Ptr.getRecord();
if (!ASTContext::hasLayout(BaseRecord->getDecl()))
return APValue();
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(
cast<CXXRecordDecl>(BaseRecord->getDecl()));
if (IsVirtual)
Offset += Layout.getVBaseClassOffset(RD);
else
Offset += Layout.getBaseClassOffset(RD);
} else {
Ptr = Ptr.getBase();
}
Path.push_back(APValue::LValuePathEntry({BaseOrMember, IsVirtual}));
continue;
}
llvm_unreachable("Invalid field type");
}
}
// We assemble the LValuePath starting from the innermost pointer to the
// outermost one. SO in a.b.c, the first element in Path will refer to
// the field 'c', while later code expects it to refer to 'a'.
// Just invert the order of the elements.
std::reverse(Path.begin(), Path.end());
auto Result = APValue(Base, Offset, Path, OnePastEnd);
Result.setConstexprUnknown(isConstexprUnknown());
return Result;
}
void Pointer::print(llvm::raw_ostream &OS) const {
switch (StorageKind) {
case Storage::Block: {
const Block *B = BS.Pointee;
OS << "(Block) " << B << " {";
if (isRoot())
OS << "rootptr(" << BS.Base << "), ";
else
OS << BS.Base << ", ";
if (isElementPastEnd())
OS << "pastend, ";
else
OS << Offset << ", ";
if (B)
OS << B->getSize();
else
OS << "nullptr";
OS << "}";
} break;
case Storage::Int:
OS << "(Int) {" << Int.Value << " + " << Offset << ", " << Int.getType()
<< ", " << (Int.isNull() ? "null" : "nonnull") << '}';
break;
case Storage::Fn:
OS << "(Fn) { " << Fn.Func << " + " << Offset << " }";
break;
case Storage::Typeid:
OS << "(Typeid) { " << (const void *)asTypeidPointer().TypePtr << ", "
<< (const void *)asTypeidPointer().TypeInfoType << " + " << Offset
<< "}";
break;
case Storage::String:
OS << "(String) { " << (const void *)Str.getLiteral() << ' ';
Str.getLiteral()->outputString(OS);
OS << ". ID: " << Str.ID << " + " << Offset << "}";
break;
case Storage::Opaque:
OS << "(Opaque) { Base: " << Opaque.Base << ", "
<< Opaque.FieldType.getPointer() << " Length: " << Opaque.PathLength
<< ". PastEnd: " << Opaque.isOnePastEnd();
OS << "} + " << Offset;
break;
}
}
/// Compute an offset that can be used to compare the pointer to another one
/// with the same base. To get accurate results, we basically _have to_ compute
/// the lvalue offset using the ASTRecordLayout.
///
/// This function will fail if we're trying to get the type size of a forward
/// declaration.
///
// FIXME: We're still mixing values from the record layout with our internal
// offsets, which will inevitably lead to cryptic errors.
std::optional<size_t>
Pointer::computeOffsetForComparison(const ASTContext &ASTCtx) const {
switch (StorageKind) {
case Storage::Int:
return Int.Value + Offset;
case Storage::Block:
// See below.
break;
case Storage::Fn:
return getIntegerRepresentation();
case Storage::Typeid:
return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset;
case Storage::String:
return reinterpret_cast<uintptr_t>(Str.getLiteral()) + Offset;
case Storage::Opaque:
return computeLayoutOffset(ASTCtx);
}
auto getTypeSize = [&](QualType T) -> std::optional<size_t> {
if (!validType(T))
return std::nullopt;
return ASTCtx.getTypeSizeInChars(T).getQuantity();
};
size_t Result = 0;
PtrView P = view();
while (true) {
if (P.isVirtualBaseClass()) {
Result += getInlineDesc()->Offset;
P = P.getBase();
continue;
}
if (P.isBaseClass()) {
Result += P.getInlineDesc()->Offset - sizeof(InlineDescriptor);
P = P.getBase();
continue;
}
if (P.isArrayElement()) {
P = P.expand();
Result += (P.getIndex() * P.elemSize());
P = P.getArray();
continue;
}
if (P.isRoot()) {
if (P.isOnePastEnd()) {
if (auto Size = getTypeSize(P.getDeclDesc()->getType()))
Result += *Size;
else
return std::nullopt;
}
break;
}
assert(P.getField());
const Record *R = P.getBase().getRecord();
assert(R);
if (!ASTContext::hasLayout(R->getDecl()))
return std::nullopt;
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(R->getDecl());
Result += ASTCtx
.toCharUnitsFromBits(
Layout.getFieldOffset(P.getField()->getFieldIndex()))
.getQuantity();
if (P.isOnePastEnd()) {
if (auto Size = getTypeSize(P.getField()->getType()))
Result += *Size;
else
return std::nullopt;
}
P = P.getBase();
if (P.isRoot())
break;
}
return Result;
}
std::optional<size_t>
Pointer::computeLayoutOffset(const ASTContext &ASTCtx) const {
switch (StorageKind) {
case Storage::Int:
return Int.Value + Offset;
case Storage::Block:
// See below.
break;
case Storage::Fn:
return getIntegerRepresentation();
case Storage::Typeid:
return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset;
case Storage::String:
return Offset * Str.getLiteral()->getCharByteWidth();
case Storage::Opaque:
if (auto O = Opaque.computeLayoutOffset(ASTCtx)) {
size_t TypeSize = 0;
if (QualType FT = Opaque.getFieldType(); validType(FT))
TypeSize = ASTCtx.getTypeSizeInChars(FT).getQuantity();
return *O + (Offset * TypeSize);
}
return std::nullopt;
}
auto getTypeSize = [&](QualType T) -> std::optional<size_t> {
if (!validType(T))
return std::nullopt;
return ASTCtx.getTypeSizeInChars(T).getQuantity();
};
auto getRecordDecl = [&](PtrView P) -> const CXXRecordDecl * {
if (const Record *R = P.getRecord())
return cast<CXXRecordDecl>(R->getDecl());
return cast<CXXRecordDecl>(P.getFieldDesc()->asDecl());
};
auto getRecordSize = [&](const RecordDecl *RD) -> unsigned {
CanQualType RecordTy = ASTCtx.getCanonicalTagType(RD);
return ASTCtx.getTypeSizeInChars(RecordTy).getQuantity();
};
size_t Result = 0;
PtrView P = view();
while (true) {
if (P.isBaseClass()) {
const CXXRecordDecl *BaseRD = getRecordDecl(P.getBase());
if (!ASTContext::hasLayout(BaseRD))
return std::nullopt;
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(BaseRD);
const CXXRecordDecl *RD = getRecordDecl(P);
if (P.isVirtualBaseClass())
Result += Layout.getVBaseClassOffset(RD).getQuantity();
else
Result += Layout.getBaseClassOffset(RD).getQuantity();
if (P.isOnePastEnd())
Result += getRecordSize(RD);
P = P.getBase();
continue;
}
if (P.isArrayElement()) {
P = P.expand();
assert(P.getFieldDesc()->isArray());
if (std::optional<size_t> ElemSize =
getTypeSize(P.getFieldDesc()->getElemQualType()))
Result += *ElemSize * P.getIndex();
else
return std::nullopt;
P = P.getArray();
continue;
}
if (P.isRoot()) {
if (P.isPastEnd() || P.isOnePastEnd()) {
if (std::optional<size_t> Size =
getTypeSize(P.getDeclDesc()->getType()))
Result += *Size * P.getIndex();
else
return std::nullopt;
}
break;
}
assert(P.getField());
const FieldDecl *F = P.getField();
if (!ASTContext::hasLayout(F->getParent()))
return std::nullopt;
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(F->getParent());
Result +=
ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(F->getFieldIndex()))
.getQuantity();
if (P.isPastEnd() || P.isOnePastEnd()) {
if (std::optional<size_t> Size = getTypeSize(F->getType()))
Result += *Size * P.getIndex();
else
return std::nullopt;
}
P = P.getBase();
if (P.isRoot())
break;
}
return Result;
}
std::string Pointer::toDiagnosticString(const ASTContext &Ctx) const {
if (isZero())
return "nullptr";
if (isIntegralPointer())
return (Twine("&(") + Twine(asIntPointer().Value + Offset) + ")").str();
QualType Ty = getType();
if (Ty->isLValueReferenceType())
Ty = Ty->getPointeeType();
return toAPValue(Ctx).getAsString(Ctx, Ty);
}
bool PtrView::isElementInitialized(unsigned Index) const {
const Descriptor *Desc = getFieldDesc();
assert(Desc);
if (Pointee->isStatic() && Base == 0)
return true;
if (isRoot() && Base == sizeof(GlobalInlineDescriptor) && Offset == Base) {
const auto &GD = Pointee->getBlockDesc<GlobalInlineDescriptor>();
return GD.InitState == GlobalInitState::Initialized;
}
if (Desc->isPrimitiveArray()) {
InitMapPtr IM = getInitMap();
if (IM.allInitialized())
return true;
if (!IM.hasInitMap())
return false;
return IM->isElementInitialized(Index);
}
return isInitialized();
}
bool Pointer::isElementAlive(unsigned Index) const {
assert(getFieldDesc()->isPrimitiveArray());
InitMapPtr &IM = getInitMap();
if (!IM.hasInitMap())
return true;
if (IM.allInitialized())
return true;
return IM->isElementAlive(Index);
}
Lifetime PtrView::getLifetime() const {
if (Base < sizeof(InlineDescriptor))
return Lifetime::Started;
if (inArray() && !isArrayRoot()) {
InitMapPtr &IM = getInitMap();
if (!IM.hasInitMap()) {
if (IM.allInitialized())
return Lifetime::Started;
return getArray().getLifetime();
}
return IM->isElementAlive(getIndex()) ? Lifetime::Started : Lifetime::Ended;
}
return getInlineDesc()->LifeState;
}
void PtrView::setLifeState(Lifetime L) const {
if (Base < sizeof(InlineDescriptor))
return;
if (inArray() && !isArrayRoot()) {
assert(L == Lifetime::Started || L == Lifetime::Ended);
const Descriptor *Desc = getFieldDesc();
InitMapPtr &IM = getInitMap();
if (!IM.hasInitMap())
IM.setInitMap(new InitMap(Desc->getNumElems(), IM.allInitialized()));
if (L == Lifetime::Ended)
IM->endElementLifetime(getIndex());
else if (L == Lifetime::Started)
IM->startElementLifetime(getIndex());
assert(isArrayRoot() || (this->getLifetime() == L));
return;
}
getInlineDesc()->LifeState = L;
}
void PtrView::initialize() const {
// FIXME: This happens when the control flow jumps right into a scope, e.g. in
// switch_into_init_stmt in constant-expression-cxx2a.cpp. I.e. we have never
// initialized the scope via an InitScope op.
if (LLVM_UNLIKELY(!Pointee->isInitialized()))
Pointee->invokeCtor();
if (isRoot() && Base == sizeof(GlobalInlineDescriptor) && Offset == Base) {
auto &GD = Pointee->getBlockDesc<GlobalInlineDescriptor>();
GD.InitState = GlobalInitState::Initialized;
return;
}
const Descriptor *Desc = getFieldDesc();
assert(Desc);
if (Desc->isPrimitiveArray()) {
if (Desc->getNumElems() != 0)
initializeElement(getIndex());
return;
}
// Field has its bit in an inline descriptor.
assert(Base != 0 && "Only composite fields can be initialised");
getInlineDesc()->IsInitialized = true;
getInlineDesc()->LifeState = Lifetime::Started;
}
void PtrView::initializeElement(unsigned Index) const {
// Primitive global arrays don't have an initmap.
if (Pointee->isStatic() && Base == 0)
return;
assert(Index < getFieldDesc()->getNumElems());
// FIXME: This happens when the control flow jumps right into a scope, e.g. in
// switch_into_init_stmt in constant-expression-cxx2a.cpp. I.e. we have never
// initialized the scope via an InitScope op.
if (LLVM_UNLIKELY(!Pointee->isInitialized()))
Pointee->invokeCtor();
InitMapPtr &IM = getInitMap();
if (IM.allInitialized())
return;
if (!IM.hasInitMap()) {
const Descriptor *Desc = getFieldDesc();
IM.setInitMap(new InitMap(Desc->getNumElems()));
}
assert(IM.hasInitMap());
if (IM->initializeElement(Index))
IM.noteAllInitialized();
}
void Pointer::initializeAllElements() const {
assert(getFieldDesc()->isPrimitiveArray());
assert(isArrayRoot());
getInitMap().noteAllInitialized();
}
bool PtrView::allElementsInitialized() const {
assert(getFieldDesc()->isPrimitiveArray());
assert(isArrayRoot());
if (Pointee->isStatic() && Base == 0)
return true;
if (isRoot() && Base == sizeof(GlobalInlineDescriptor) && Offset == Base) {
const auto &GD = Pointee->getBlockDesc<GlobalInlineDescriptor>();
return GD.InitState == GlobalInitState::Initialized;
}
InitMapPtr IM = getInitMap();
return IM.allInitialized();
}
bool Pointer::allElementsAlive() const {
assert(getFieldDesc()->isPrimitiveArray());
assert(isArrayRoot());
if (isStatic() && BS.Base == 0)
return true;
if (isRoot() && BS.Base == sizeof(GlobalInlineDescriptor) &&
Offset == BS.Base) {
const auto &GD = block()->getBlockDesc<GlobalInlineDescriptor>();
return GD.InitState == GlobalInitState::Initialized;
}
InitMapPtr &IM = getInitMap();
return IM.allInitialized() || (IM.hasInitMap() && IM->allElementsAlive());
}
void PtrView::activate() const {
// Field has its bit in an inline descriptor.
assert(Base != 0 && "Only composite fields can be activated");
if (isRoot() && Base == sizeof(GlobalInlineDescriptor))
return;
if (!getInlineDesc()->InUnion)
return;
std::function<void(PtrView P)> activate;
activate = [&activate](PtrView P) -> void {
P.getInlineDesc()->IsActive = true;
P.startLifetime();
if (const Record *R = P.getRecord(); R && !R->isUnion()) {
for (const Record::Field &F : R->fields()) {
PtrView FieldPtr = P.atField(F.Offset);
if (!FieldPtr.getInlineDesc()->IsActive)
activate(FieldPtr);
}
// FIXME: Bases?
}
};
std::function<void(PtrView &)> deactivate;
deactivate = [&deactivate](PtrView &P) -> void {
P.getInlineDesc()->IsActive = false;
if (const Record *R = P.getRecord()) {
for (const Record::Field &F : R->fields()) {
PtrView FieldPtr = P.atField(F.Offset);
if (FieldPtr.getInlineDesc()->IsActive)
deactivate(FieldPtr);
}
// FIXME: Bases?
}
};
PtrView B = *this;
// Primitive array elements can't be activated individually, so
// look at the array root instead.
if (B.getFieldDesc()->isPrimitiveArray() && B.isArrayElement())
B = B.getArray();
while (!B.isRoot() && B.inUnion()) {
activate(B);
// When walking up the pointer chain, deactivate
// all union child pointers that aren't on our path.
PtrView Cur = B;
B = B.getBase();
if (const Record *BR = B.getRecord(); BR && BR->isUnion()) {
for (const Record::Field &F : BR->fields()) {
PtrView FieldPtr = B.atField(F.Offset);
if (FieldPtr != Cur)
deactivate(FieldPtr);
}
}
}
}
bool Pointer::hasSameBase(const Pointer &A, const Pointer &B) {
// Two null pointers always have the same base.
if (A.isZero() && B.isZero())
return true;
// We allow comparisons between opaque pointers and block pointers, provided
// they have the same declaration as base.
if (A.StorageKind != B.StorageKind) {
if (A.isOpaquePointer() && A.Opaque.Base.isVarDecl() &&
B.isBlockPointer()) {
if (const VarDecl *BDecl = B.block()->getDescriptor()->asVarDecl())
return BDecl == A.Opaque.Base.asVarDecl()->getMostRecentDecl();
return false;
}
if (B.isOpaquePointer() && B.Opaque.Base.isVarDecl() &&
A.isBlockPointer()) {
if (const VarDecl *ADecl = A.block()->getDescriptor()->asVarDecl())
return ADecl == B.Opaque.Base.asVarDecl()->getMostRecentDecl();
return false;
}
return false;
}
switch (A.StorageKind) {
case Storage::Int:
return true;
case Storage::Block:
return A.BS.Pointee == B.BS.Pointee;
case Storage::Fn:
return true;
case Storage::Typeid:
return A.asTypeidPointer().TypePtr == B.asTypeidPointer().TypePtr;
case Storage::String:
return A.Str.ID == B.Str.ID && A.Str.getLiteral() == B.Str.getLiteral();
case Storage::Opaque:
if (A.Opaque.Base.isExpr())
return B.Opaque.Base.isExpr() && A.Opaque.Base == B.Opaque.Base;
if (A.Opaque.Base.isVarDecl())
return B.Opaque.Base.isVarDecl() &&
A.Opaque.Base.asVarDecl()->getMostRecentDecl() ==
B.Opaque.Base.asVarDecl()->getMostRecentDecl();
return false;
}
llvm_unreachable("should have been handled by the fully covered switch");
}
bool Pointer::pointToSameBlock(const Pointer &A, const Pointer &B) {
if (!A.isBlockPointer() || !B.isBlockPointer())
return false;
return A.block() == B.block();
}
bool Pointer::elemsOfSameArray(const Pointer &A, const Pointer &B) {
assert(hasSameBase(A, B));
assert(A.isBlockPointer());
assert(B.isBlockPointer());
if (A.BS.Base == B.BS.Base)
return true;
if (A.isBaseClass() || B.isBaseClass())
return false;
if (A.getField() || B.getField())
return false;
auto closestArray = [](const Pointer &P) -> PtrView {
if (P.isArrayRoot())
return P.view();
PtrView V = P.view();
if (V.isArrayElement() || V.isOnePastEnd())
V = V.expand().getArray();
if (P.isRoot())
return P.view();
while (!V.isRoot() && !V.getFieldDesc()->IsArray) {
if (V.isArrayElement()) {
V = V.expand().getArray();
break;
}
V = V.getBase();
}
return V;
};
if (closestArray(A) != closestArray(B))
return false;
return true;
}
// FIXME: This should return true for string pointers.
bool Pointer::pointsToLiteral() const {
if (isZero())
return false;
if (isDynamic())
return false;
const Expr *E = getRootExpr();
return E && !isa<MaterializeTemporaryExpr, StringLiteral>(E);
}
bool Pointer::pointsToLabel() const {
if (isZero())
return false;
if (isOpaquePointer())
return isa_and_nonnull<AddrLabelExpr>(Opaque.Base.asExpr());
return false;
}
std::optional<std::pair<PtrView, PtrView>>
Pointer::computeSplitPoint(const Pointer &A, const Pointer &B) {
if (!A.isBlockPointer() || !B.isBlockPointer())
return std::nullopt;
if (A.asBlockPointer().Pointee != B.asBlockPointer().Pointee)
return std::nullopt;
if (A.isRoot() && B.isRoot())
return std::nullopt;
if (A == B)
return std::make_pair(A.view(), B.view());
auto getBase = [](PtrView P) -> PtrView {
if (P.isArrayElement())
return P.expand().getArray();
return P.getBase();
};
PtrView IterA = A.view();
PtrView IterB = B.view();
PtrView CurA = IterA;
PtrView CurB = IterB;
for (;;) {
if (IterA.Base > IterB.Base) {
CurA = IterA;
IterA = getBase(IterA);
} else {
CurB = IterB;
IterB = getBase(IterB);
}
if (IterA == IterB) {
// If the Iter is an array, CurA and CurB are both elements of the same
// array. That is fine, so return nullopt.
if (IterA.getFieldDesc()->isArray())
return std::nullopt;
return std::make_pair(CurA, CurB);
}
if (IterA.isRoot() && IterB.isRoot())
return std::nullopt;
}
llvm_unreachable("The loop above should've returned.");
}
/// Convert a pointer to a composite value to an rvalue.
static bool toRValue(const Context &Ctx, QualType Ty, PtrView Ptr, APValue &R) {
const ASTContext &ASTCtx = Ctx.getASTContext();
if (const auto *AT = Ty->getAs<AtomicType>())
Ty = AT->getValueType();
// Invalid pointers.
if (!Ptr.isLive() || Ptr.isPastEnd())
return false;
// Primitives should never end up here.
assert(!Ctx.canClassify(Ty));
const Descriptor *FieldDesc = Ptr.getFieldDesc();
assert(FieldDesc);
if (const auto *RT = Ty->getAsCanonical<RecordType>()) {
if (!FieldDesc->isRecord())
return false;
const auto *Record = Ptr.getRecord();
assert(Record && "Missing record descriptor");
bool Ok = true;
if (RT->getDecl()->isUnion()) {
const FieldDecl *ActiveField = nullptr;
APValue Value;
for (const auto &F : Record->fields()) {
PtrView FP = Ptr.atField(F.Offset);
if (FP.isActive()) {
const Descriptor *Desc = F.Desc;
if (Desc->isPrimitive()) {
TYPE_SWITCH(Desc->getPrimType(),
Value = FP.deref<T>().toAPValue(ASTCtx));
} else {
QualType FieldTy = F.Decl->getType();
Ok &= toRValue(Ctx, FieldTy, FP, Value);
}
ActiveField = FP.getFieldDesc()->asFieldDecl();
break;
}
}
R = APValue(ActiveField, Value);
} else {
unsigned NF = Record->getNumFields();
unsigned NB = Record->getNumBases();
unsigned NV = Ptr.isBaseClass() ? 0 : Record->getNumVirtualBases();
R = APValue(APValue::UninitStruct(), NB, NF, NV);
for (unsigned I = 0; I != NF; ++I) {
const Record::Field *FD = Record->getField(I);
const Descriptor *Desc = FD->Desc;
PtrView FP = Ptr.atField(FD->Offset);
APValue &Value = R.getStructField(I);
if (Desc->isPrimitive()) {
TYPE_SWITCH(Desc->getPrimType(),
Value = FP.deref<T>().toAPValue(ASTCtx));
} else {
QualType FieldTy = FD->Decl->getType();
Ok &= toRValue(Ctx, FieldTy, FP, Value);
}
}
for (unsigned I = 0; I != NB; ++I) {
const Record::Base *BD = Record->getBase(I);
QualType BaseTy = Ctx.getASTContext().getCanonicalTagType(BD->Decl);
PtrView BP = Ptr.atField(BD->Offset);
Ok &= toRValue(Ctx, BaseTy, BP, R.getStructBase(I));
}
for (unsigned I = 0; I != NV; ++I) {
const Record::Base *VD = Record->getVirtualBase(I);
assert(VD);
QualType VirtBaseTy = Ctx.getASTContext().getCanonicalTagType(VD->Decl);
PtrView VP = Ptr.atField(VD->Offset);
Ok &= toRValue(Ctx, VirtBaseTy, VP, R.getStructVirtualBase(I));
}
}
return Ok;
}
if (Ty->isIncompleteArrayType()) {
R = APValue(APValue::UninitArray(), 0, 0);
return true;
}
if (const auto *AT = Ty->getAsArrayTypeUnsafe()) {
if (!FieldDesc->isArray())
return false;
const size_t NumElems = Ptr.getNumElems();
QualType ElemTy = AT->getElementType();
R = APValue(APValue::UninitArray{}, NumElems, NumElems);
bool Ok = true;
OptPrimType ElemT = Ctx.classify(ElemTy);
for (unsigned I = 0; I != NumElems; ++I) {
APValue &Slot = R.getArrayInitializedElt(I);
if (ElemT) {
TYPE_SWITCH(*ElemT, Slot = Ptr.elem<T>(I).toAPValue(ASTCtx));
} else {
Ok &= toRValue(Ctx, ElemTy, Ptr.atIndex(I).narrow(), Slot);
}
}
return Ok;
}
// Complex types.
if (Ty->isAnyComplexType()) {
// Can happen via C casts.
if (!FieldDesc->getType()->isAnyComplexType())
return false;
PrimType ElemT = FieldDesc->getPrimType();
if (isIntegerOrBoolType(ElemT)) {
INT_TYPE_SWITCH(ElemT, {
auto V1 = Ptr.elem<T>(0);
auto V2 = Ptr.elem<T>(1);
R = APValue(V1.toAPSInt(), V2.toAPSInt());
return true;
});
} else if (ElemT == PT_Float) {
R = APValue(Ptr.elem<Floating>(0).getAPFloat(),
Ptr.elem<Floating>(1).getAPFloat());
return true;
}
return false;
}
// Vector types.
if (const auto *VT = Ty->getAs<VectorType>()) {
if (!FieldDesc->isPrimitiveArray())
return false;
PrimType ElemT = FieldDesc->getPrimType();
SmallVector<APValue> Values;
Values.reserve(VT->getNumElements());
for (unsigned I = 0; I != VT->getNumElements(); ++I) {
TYPE_SWITCH(ElemT,
{ Values.push_back(Ptr.elem<T>(I).toAPValue(ASTCtx)); });
}
assert(Values.size() == VT->getNumElements());
R = APValue(Values.data(), Values.size());
return true;
}
// Constant Matrix types.
if (const auto *MT = Ty->getAs<ConstantMatrixType>()) {
if (!FieldDesc->isPrimitiveArray())
return false;
PrimType ElemT = FieldDesc->getPrimType();
unsigned NumElems = MT->getNumElementsFlattened();
SmallVector<APValue> Values;
Values.reserve(NumElems);
for (unsigned I = 0; I != NumElems; ++I) {
TYPE_SWITCH(ElemT,
{ Values.push_back(Ptr.elem<T>(I).toAPValue(ASTCtx)); });
}
R = APValue(Values.data(), MT->getNumRows(), MT->getNumColumns());
return true;
}
llvm_unreachable("invalid value to return");
}
std::optional<APValue> Pointer::toRValue(const Context &Ctx,
QualType ResultType) const {
const ASTContext &ASTCtx = Ctx.getASTContext();
assert(!ResultType.isNull());
// Can't return functions as rvalues.
if (ResultType->isFunctionType())
return std::nullopt;
// Invalid to read from.
if (isDummy() || !isLive() || isPastEnd() ||
(isOnePastEnd() && !isZeroSizeArray()))
return std::nullopt;
// We can return these as rvalues, but we can't deref() them.
if (isZero() || isIntegralPointer())
return toAPValue(ASTCtx);
// Just load primitive types.
if (OptPrimType T = Ctx.classify(ResultType)) {
if (!canDeref(*T))
return std::nullopt;
TYPE_SWITCH(*T, return this->load<T>().toAPValue(ASTCtx));
}
if (!isBlockPointer())
return std::nullopt;
// Return the composite type.
APValue Result;
if (!::toRValue(Ctx, ResultType, view(), Result))
return std::nullopt;
return Result;
}
const VarDecl *Pointer::getRootVarDecl() const {
return dyn_cast_if_present<VarDecl>(getRootValueDecl());
}
const ValueDecl *Pointer::getRootValueDecl() const {
if (isBlockPointer())
return getDeclDesc()->asValueDecl();
if (isOpaquePointer())
return Opaque.getBaseDecl();
return nullptr;
}
const Expr *Pointer::getRootExpr() const {
if (isBlockPointer())
return getDeclDesc()->asExpr();
if (isStringPointer())
return Str.getLiteral();
if (isOpaquePointer())
return Opaque.getBaseExpr();
return nullptr;
}
std::optional<IntPointer> IntPointer::atOffset(const interp::Context &Ctx,
unsigned Offset) const {
QualType CurType = getPointeeType();
if (CurType.isNull() || !CurType->isRecordType())
return std::nullopt;
const Record *R = Ctx.getRecord(CurType->getAsRecordDecl());
if (!R)
return *this;
const Record::Field *F = R->findField(Offset);
if (!F)
return *this;
const FieldDecl *FD = F->Decl;
if (FD->getParent()->isInvalidDecl())
return std::nullopt;
const ASTContext &ASTCtx = Ctx.getASTContext();
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(FD->getParent());
unsigned FieldIndex = FD->getFieldIndex();
uint64_t FieldOffset =
ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FieldIndex))
.getQuantity();
uint64_t NewValue = this->Value + FieldOffset;
return IntPointer{{FD->getType().getTypePtr(), NewValue == 0}, NewValue};
}
IntPointer IntPointer::baseCast(const interp::Context &Ctx,
unsigned BaseOffset) const {
if (!getType())
return *this;
QualType CurType = getPointeeType();
if (CurType.isNull() || !CurType->isRecordType())
return *this;
// null pointers stay null during a cast, per conv.ptr
if (Value == 0)
return *this;
const Record *R = Ctx.getRecord(CurType->getAsRecordDecl());
// This iterates over bases and checks for the proper offset. That's
// potentially slow but this case really shouldn't happen a lot.
const Record::Base *B = R->findBase(BaseOffset);
if (!B)
return *this;
const Descriptor *BaseDesc = B->Desc;
// Adjust the offset value based on the information from the record layout.
const ASTContext &ASTCtx = Ctx.getASTContext();
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(R->getDecl());
CharUnits BaseLayoutOffset =
Layout.getBaseClassOffset(cast<CXXRecordDecl>(BaseDesc->asDecl()));
const RecordDecl *RD = BaseDesc->ElemRecord->getDecl();
QualType T = RD->getASTContext().getTagType(ElaboratedTypeKeyword::None,
std::nullopt, RD, false);
uint64_t NewValue = Value + BaseLayoutOffset.getQuantity();
return {{T.getTypePtr(), NewValue == 0}, NewValue};
}
std::optional<size_t>
OpaquePointer::computeLayoutOffset(const ASTContext &ASTCtx) const {
size_t Offset = 0;
QualType CurType = getObjectType();
for (const PointerPathEntry &Entry : path()) {
switch (Entry.Kind) {
case PointerPathEntry::Base: {
const RecordDecl *RD = CurType->getAsRecordDecl();
if (!ASTContext::hasLayout(RD))
return std::nullopt;
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
if (Entry.RD.getInt())
Offset +=
Layout.getVBaseClassOffset(Entry.RD.getPointer()).getQuantity();
else
Offset +=
Layout.getBaseClassOffset(Entry.RD.getPointer()).getQuantity();
CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer());
} break;
case PointerPathEntry::Field: {
const FieldDecl *FD = Entry.FD;
const RecordDecl *RD = FD->getParent();
if (!ASTContext::hasLayout(RD))
return std::nullopt;
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
Offset +=
ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex()))
.getQuantity();
CurType = FD->getType();
} break;
case PointerPathEntry::Array:
case PointerPathEntry::NegativeArray: {
bool Add = (Entry.Kind == PointerPathEntry::Array);
uint64_t Index = Entry.Index;
if (!CurType->isArrayType()) {
if (Add)
Offset += Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
else
Offset -= Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
continue;
}
const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
assert(AT);
QualType ElemTy = AT->getElementType();
if (!validType(ElemTy) || isa<VariableArrayType>(AT))
return std::nullopt;
if (Add)
Offset += Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity();
else
Offset -= Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity();
CurType = AT->getElementType();
} break;
}
}
return Offset;
}
QualType OpaquePointer::getSurroundingArray() const {
if (PathLength == 0)
return getObjectType();
if (Path[PathLength - 1].Kind != PointerPathEntry::Array)
return getFieldType();
assert(Path[PathLength - 1].Kind == PointerPathEntry::Array);
assert(isArrayElement());
QualType CurType = getObjectType();
for (const PointerPathEntry &Entry : path().drop_back(1)) {
switch (Entry.Kind) {
case PointerPathEntry::Base:
CurType = Entry.RD.getPointer()->getASTContext().getCanonicalTagType(
Entry.RD.getPointer());
break;
case PointerPathEntry::Field:
CurType = Entry.FD->getType();
break;
case PointerPathEntry::Array:
case PointerPathEntry::NegativeArray:
if (!CurType->isArrayType())
break;
CurType = CurType->getAsArrayTypeUnsafe()->getElementType();
}
}
return CurType;
}
/// Check if the pointer has offset 0.
// As an optimization, don't actually compute the offset.
bool OpaquePointer::isRoot() const {
QualType CurType = getObjectType();
for (const PointerPathEntry &Entry : path()) {
switch (Entry.Kind) {
case PointerPathEntry::Base:
if (Entry.RD.getInt())
return false;
CurType = Entry.RD.getPointer()->getASTContext().getCanonicalTagType(
Entry.RD.getPointer());
break;
case PointerPathEntry::Field:
if (!Entry.FD->getParent()->isUnion() && Entry.FD->getFieldIndex() != 0)
return false;
CurType = Entry.FD->getType();
break;
case PointerPathEntry::Array:
if (Entry.Index != 0)
return false;
if (!CurType->isArrayType())
continue;
CurType = CurType->getAsArrayTypeUnsafe()->getElementType();
break;
case PointerPathEntry::NegativeArray:
return false;
}
}
return true;
}
bool OpaquePointer::isUnknownSizeArray() const {
QualType FieldType = getFieldType();
if (isArrayElement())
FieldType = getSurroundingArray();
bool Result = false;
// If the field type is an IncompleteArrayType, we still need to check the
// base to see if this array is a flexible array member _and_ has actually
// been initialized by data we know the size of.
if (isa<IncompleteArrayType>(FieldType)) {
const VarDecl *Base = this->Base.asVarDecl();
if (!Base || !Base->getType()->isRecordType() || !Base->hasInit())
Result = true;
else
Result = !Base->hasFlexibleArrayInit(Base->getASTContext());
} else if (isa<VariableArrayType>(FieldType))
Result = true;
return Result;
}
/// This is used in Pointer::isOnePastEnd(). We cannot read from such pointers.
/// We can of course never read from opaque pointers anyway but we diagnose
/// one-past-the-end pointers differently.
///
/// In contrast, OpaquePointer::isOnePastEnd() only uses the past-end bit. That
/// is used for the APValue conversion.
bool OpaquePointer::isOnePastEndOrElementPastEnd() const {
if (isOnePastEnd())
return true;
if (PathLength == 0)
return false;
if (Path[PathLength - 1].Kind != PointerPathEntry::Array)
return false;
QualType ArrTy = getSurroundingArray();
if (!ArrTy->isArrayType())
return false;
// FIXME: Flexible array members?
if (const auto *CAT =
dyn_cast<ConstantArrayType>(ArrTy->getAsArrayTypeUnsafe())) {
if (Path[PathLength - 1].Index >= CAT->getZExtSize())
return true;
}
return false;
}
bool OpaquePointer::hasValidBase() const {
if (const VarDecl *VD = Base.asVarDecl())
return !VD->hasExternalStorage();
return !Base.getType()->isPointerType();
}