blob: d4d92d62c014688d4218ff655a51dc64d51059ec [file] [edit]
//===--- Pointer.h - 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
//
//===----------------------------------------------------------------------===//
//
// Defines the classes responsible for pointer tracking.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_AST_INTERP_POINTER_H
#define LLVM_CLANG_AST_INTERP_POINTER_H
#include "Descriptor.h"
#include "Function.h"
#include "InitMap.h"
#include "InterpBlock.h"
#include "clang/AST/ComparisonCategories.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "llvm/Support/raw_ostream.h"
namespace clang {
namespace interp {
class Block;
class DeadBlock;
class Pointer;
class Context;
class Pointer;
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Pointer &P);
struct PtrView {
static constexpr unsigned PastEndMark = ~0u;
Block *Pointee;
unsigned Base;
uint64_t Offset;
bool isZero() const { return !Pointee; }
bool isLive() const { return Pointee && !Pointee->isDead(); }
bool isDummy() const { return Pointee && Pointee->isDummy(); }
bool isActive() const { return isRoot() || getInlineDesc()->IsActive; }
bool isArrayRoot() const { return inArray() && Offset == Base; }
bool isElementPastEnd() const { return Offset == PastEndMark; }
bool isZeroSizeArray() const { return getFieldDesc()->isZeroSizeArray(); }
bool isMutable() const {
return !isRoot() && getInlineDesc()->IsFieldMutable;
}
bool inUnion() const { return getInlineDesc()->InUnion; };
bool inArray() const { return getFieldDesc()->IsArray; }
bool inPrimitiveArray() const { return getFieldDesc()->isPrimitiveArray(); }
const Block *block() const { return Pointee; }
unsigned getEvalID() { return Pointee->getEvalID(); }
bool isRoot() const {
return Base == Pointee->getDescriptor()->getMetadataSize();
}
InlineDescriptor *getInlineDesc() const {
assert(Base != sizeof(GlobalInlineDescriptor));
assert(Base <= Pointee->getSize());
assert(Base >= sizeof(InlineDescriptor));
return getDescriptor(Base);
}
InlineDescriptor *getDescriptor(unsigned Offset) const {
assert(Offset != 0 && "Not a nested pointer");
return reinterpret_cast<InlineDescriptor *>(Pointee->rawData() + Offset) -
1;
}
const Descriptor *getFieldDesc() const {
if (isRoot())
return Pointee->getDescriptor();
return getInlineDesc()->Desc;
}
const Descriptor *getDeclDesc() const { return Pointee->getDescriptor(); }
size_t elemSize() const { return getFieldDesc()->getElemSize(); }
[[nodiscard]] PtrView narrow() const {
// Null pointers cannot be narrowed.
if (isZero() || isUnknownSizeArray())
return *this;
if (inArray()) {
// Pointer is one past end - magic offset marks that.
if (isOnePastEnd())
return PtrView{Pointee, Base, PastEndMark};
if (Offset != Base) {
// If we're pointing to a primitive array element, there's nothing to
// do.
if (inPrimitiveArray())
return *this;
// Pointer is to a composite array element - enter it.
return PtrView{Pointee, static_cast<unsigned>(Offset), Offset};
}
}
// Otherwise, we're pointing to a non-array element or
// are already narrowed to a composite array element. Nothing to do.
return *this;
}
[[nodiscard]] PtrView expand() const {
if (isElementPastEnd()) {
// Revert to an outer one-past-end pointer.
unsigned Adjust;
if (inPrimitiveArray())
Adjust = sizeof(InitMapPtr);
else
Adjust = sizeof(InlineDescriptor);
return PtrView{Pointee, Base, Base + getSize() + Adjust};
}
// Do not step out of array elements.
if (Base != Offset)
return *this;
if (isRoot())
return PtrView{Pointee, Base, Base};
// Step into the containing array, if inside one.
unsigned Next = Base - getInlineDesc()->Offset;
const Descriptor *Desc =
(Next == Pointee->getDescriptor()->getMetadataSize())
? getDeclDesc()
: getDescriptor(Next)->Desc;
if (!Desc->IsArray)
return *this;
return PtrView{Pointee, Next, Offset};
}
[[nodiscard]] PtrView getArray() const {
assert(Offset != Base && "not an array element");
return PtrView{Pointee, Base, Base};
}
const Record *getRecord() const { return getFieldDesc()->ElemRecord; }
const Record *getElemRecord() const {
const Descriptor *ElemDesc = getFieldDesc()->ElemDesc;
return ElemDesc ? ElemDesc->ElemRecord : nullptr;
}
const FieldDecl *getField() const { return getFieldDesc()->asFieldDecl(); }
bool isField() const {
return !isZero() && !isRoot() && getFieldDesc()->asDecl();
}
bool isBaseClass() const { return isField() && getInlineDesc()->IsBase; }
bool isVirtualBaseClass() const {
return isField() && getInlineDesc()->IsVirtualBase;
}
bool isUnknownSizeArray() const {
return getFieldDesc()->isUnknownSizeArray();
}
bool isPastEnd() const { return Offset > Pointee->getSize(); }
unsigned getOffset() const {
assert(Offset != PastEndMark);
unsigned Adjust = 0;
if (Offset != Base) {
if (getFieldDesc()->ElemDesc)
Adjust = sizeof(InlineDescriptor);
else
Adjust = sizeof(InitMapPtr);
}
return Offset - Base - Adjust;
}
size_t getSize() const { return getFieldDesc()->getSize(); }
bool isOnePastEnd() const {
if (!Pointee)
return false;
if (isUnknownSizeArray())
return false;
return isPastEnd() || (getSize() == getOffset());
}
PtrView atIndex(unsigned Idx) const {
unsigned Off = Idx * elemSize();
if (getFieldDesc()->ElemDesc)
Off += sizeof(InlineDescriptor);
else
Off += sizeof(InitMapPtr);
return PtrView{Pointee, Base, Base + Off};
}
int64_t getIndex() const {
if (isZero())
return 0;
// narrow()ed element in a composite array.
if (Base > sizeof(InlineDescriptor) && Base == Offset)
return 0;
if (auto ElemSize = elemSize())
return getOffset() / ElemSize;
return 0;
}
unsigned getNumElems() const { return getSize() / elemSize(); }
bool isArrayElement() const {
if (inArray() && Base != Offset)
return true;
// Might be a narrow()'ed element in a composite array.
// Check the inline descriptor.
if (Base >= sizeof(InlineDescriptor) && getInlineDesc()->IsArrayElement)
return true;
return false;
}
template <typename T> T &deref() const {
assert(isLive() && "Invalid pointer");
assert(Pointee);
if (isArrayRoot())
return *reinterpret_cast<T *>(Pointee->rawData() + Base +
sizeof(InitMapPtr));
return *reinterpret_cast<T *>(Pointee->rawData() + Offset);
}
template <typename T> T &elem(unsigned I) const {
assert(isLive() && "Invalid pointer");
assert(Pointee);
assert(getFieldDesc()->isPrimitiveArray());
assert(I < getFieldDesc()->getNumElems());
unsigned ElemByteOffset = I * getFieldDesc()->getElemSize();
unsigned ReadOffset = Base + sizeof(InitMapPtr) + ElemByteOffset;
assert(ReadOffset + sizeof(T) <= Pointee->getDescriptor()->getAllocSize());
return *reinterpret_cast<T *>(Pointee->rawData() + ReadOffset);
}
[[nodiscard]] PtrView getBase() const {
unsigned NewBase = Base - getInlineDesc()->Offset;
return PtrView{Pointee, NewBase, NewBase};
}
[[nodiscard]] PtrView atField(unsigned Offset) const {
unsigned F = this->Offset + Offset;
return PtrView{Pointee, F, F};
}
QualType getType() const {
if (isRoot() && Base == Offset) {
// If this pointer points to the root of a declaration, try to consult
// the ValueDecl directly, since that has a type with more information,
// e.g. the correct ElaboratedTypeKeyword.
if (const ValueDecl *VD = getDeclDesc()->asValueDecl())
return VD->getType();
return getDeclDesc()->getType();
}
if (inPrimitiveArray() && Offset != Base) {
// Unfortunately, complex and vector types are not array types in clang,
// but they are for us.
if (const auto *AT = getFieldDesc()->getType()->getAsArrayTypeUnsafe())
return AT->getElementType();
if (const auto *CT = getFieldDesc()->getType()->getAs<ComplexType>())
return CT->getElementType();
if (const auto *CT = getFieldDesc()->getType()->getAs<VectorType>())
return CT->getElementType();
}
return getFieldDesc()->getType();
}
bool isInitialized() const {
if (isRoot() && Base == sizeof(GlobalInlineDescriptor) && Offset == Base) {
const auto &GD = Pointee->getBlockDesc<GlobalInlineDescriptor>();
return GD.InitState == GlobalInitState::Initialized;
}
assert(Pointee && "Cannot check if null pointer was initialized");
const Descriptor *Desc = getFieldDesc();
assert(Desc);
if (Desc->isPrimitiveArray())
return isElementInitialized(getIndex());
if (Base == 0)
return true;
// Field has its bit in an inline descriptor.
return getInlineDesc()->IsInitialized;
}
void initializeElement(unsigned Index) const;
bool allElementsInitialized() const;
bool isElementInitialized(unsigned Index) const;
InitMapPtr &getInitMap() const {
return *reinterpret_cast<InitMapPtr *>(Pointee->rawData() + Base);
}
void initialize() const;
void activate() const;
void setLifeState(Lifetime L) const;
Lifetime getLifetime() const;
void startLifetime() const { setLifeState(Lifetime::Started); }
void endLifetime() const { setLifeState(Lifetime::Ended); }
bool operator==(const PtrView &Other) const {
return Other.Pointee == Pointee && Base == Other.Base &&
Offset == Other.Offset;
}
bool operator!=(const PtrView &Other) const { return !(Other == *this); }
};
struct BlockPointer {
/// The block the pointer is pointing to.
Block *Pointee;
/// Start of the current subfield.
unsigned Base;
/// Previous link in the pointer chain.
Pointer *Prev;
/// Next link in the pointer chain.
Pointer *Next;
};
struct IntPointer {
const Type *Ty;
uint64_t Value;
std::optional<IntPointer> atOffset(const Context &Ctx, unsigned Offset) const;
IntPointer baseCast(const Context &Ctx, unsigned BaseOffset) const;
QualType getPointeeType() const {
if (!Ty)
return QualType();
QualType QT(Ty, 0);
if (QT->isPointerOrReferenceType())
QT = QT->getPointeeType();
else if (QT->isArrayType())
QT = QT->getAsArrayTypeUnsafe()->getElementType();
return QT.IgnoreParens();
}
};
struct FunctionPointer {
const Function *Func;
};
struct TypeidPointer {
const Type *TypePtr;
const Type *TypeInfoType;
};
enum class Storage { Int, Block, Fn, Typeid };
/// A pointer to a memory block, live or dead.
///
/// This object can be allocated into interpreter stack frames. If pointing to
/// a live block, it is a link in the chain of pointers pointing to the block.
///
/// In the simplest form, a Pointer has a Block* (the pointee) and both Base
/// and Offset are 0, which means it will point to raw data.
///
/// The Base field is used to access metadata about the data. For primitive
/// arrays, the Base is followed by an InitMap. In a variety of cases, the
/// Base is preceded by an InlineDescriptor, which is used to track the
/// initialization state, among other things.
///
/// The Offset field is used to access the actual data. In other words, the
/// data the pointer decribes can be found at
/// Pointee->rawData() + Pointer.Offset.
///
/// \verbatim
/// Pointee Offset
/// │ │
/// │ │
/// ▼ ▼
/// ┌───────┬────────────┬─────────┬────────────────────────────┐
/// │ Block │ InlineDesc │ InitMap │ Actual Data │
/// └───────┴────────────┴─────────┴────────────────────────────┘
/// ▲
/// │
/// │
/// Base
/// \endverbatim
class Pointer {
public:
Pointer() : StorageKind(Storage::Int), Int{nullptr, 0} {}
Pointer(IntPointer &&IntPtr)
: StorageKind(Storage::Int), Int(std::move(IntPtr)) {}
Pointer(Block *B);
Pointer(Block *B, uint64_t BaseAndOffset);
Pointer(const Pointer &P);
Pointer(Pointer &&P);
Pointer(uint64_t Address, const Type *Ty, uint64_t Offset = 0)
: Offset(Offset), StorageKind(Storage::Int), Int{Ty, Address} {}
Pointer(const Function *F, uint64_t Offset = 0)
: Offset(Offset), StorageKind(Storage::Fn), Fn{F} {}
Pointer(const Type *TypePtr, const Type *TypeInfoType, uint64_t Offset = 0)
: Offset(Offset), StorageKind(Storage::Typeid) {
Typeid.TypePtr = TypePtr;
Typeid.TypeInfoType = TypeInfoType;
}
Pointer(Block *Pointee, unsigned Base, uint64_t Offset);
explicit Pointer(PtrView V) : Pointer(V.Pointee, V.Base, V.Offset) {}
~Pointer();
Pointer &operator=(const Pointer &P);
Pointer &operator=(Pointer &&P);
/// Equality operators are just for tests.
bool operator==(const Pointer &P) const {
if (P.StorageKind != StorageKind)
return false;
if (isIntegralPointer())
return P.Int.Value == Int.Value && P.Int.Ty == Int.Ty &&
P.Offset == Offset;
if (isFunctionPointer())
return P.Fn.Func == Fn.Func && P.Offset == Offset;
return P.view() == view();
}
bool operator!=(const Pointer &P) const { return !(P == *this); }
/// Converts the pointer to an APValue.
APValue toAPValue(const ASTContext &ASTCtx) const;
/// Converts the pointer to a string usable in diagnostics.
std::string toDiagnosticString(const ASTContext &Ctx) const;
uint64_t getIntegerRepresentation() const {
if (isIntegralPointer())
return Int.Value + (Offset * elemSize());
if (isFunctionPointer())
return reinterpret_cast<uint64_t>(Fn.Func) + Offset;
return reinterpret_cast<uint64_t>(BS.Pointee) + Offset;
}
PtrView view() const {
assert(isBlockPointer());
return PtrView{BS.Pointee, BS.Base, Offset};
}
/// Converts the pointer to an APValue that is an rvalue.
std::optional<APValue> toRValue(const Context &Ctx,
QualType ResultType) const;
/// Offsets a pointer inside an array.
[[nodiscard]] Pointer atIndex(uint64_t Idx) const {
if (isIntegralPointer())
return Pointer(Int.Value, Int.Ty, Idx);
if (isFunctionPointer())
return Pointer(Fn.Func, Idx);
return Pointer(view().atIndex(Idx));
}
/// Creates a pointer to a field.
[[nodiscard]] Pointer atField(unsigned Off) const {
return Pointer(view().atField(Off));
}
/// Subtract the given offset from the current Base and Offset
/// of the pointer.
[[nodiscard]] Pointer atFieldSub(unsigned Off) const {
assert(Offset >= Off);
unsigned O = Offset - Off;
return Pointer(BS.Pointee, O, O);
}
/// Restricts the scope of an array element pointer.
[[nodiscard]] Pointer narrow() const {
if (!isBlockPointer())
return *this;
return Pointer(view().narrow());
}
/// Expands a pointer to the containing array, undoing narrowing.
[[nodiscard]] Pointer expand() const {
if (!isBlockPointer())
return *this;
return Pointer(view().expand());
}
/// Checks if the pointer is null.
bool isZero() const {
switch (StorageKind) {
case Storage::Int:
return Int.Value == 0 && Offset == 0;
case Storage::Block:
return BS.Pointee == nullptr;
case Storage::Fn:
return !Fn.Func;
case Storage::Typeid:
return false;
}
llvm_unreachable("Unknown clang::interp::Storage enum");
}
/// Checks if the pointer is live.
bool isLive() const {
if (!isBlockPointer())
return true;
return view().isLive();
}
/// Checks if the item is a field in an object.
bool isField() const {
if (!isBlockPointer())
return false;
return view().isField();
}
/// Accessor for information about the declaration site.
const Descriptor *getDeclDesc() const {
if (!isBlockPointer())
return nullptr;
assert(isBlockPointer());
assert(BS.Pointee);
return BS.Pointee->Desc;
}
SourceLocation getDeclLoc() const { return getDeclDesc()->getLocation(); }
/// Returns the expression or declaration the pointer has been created for.
DeclTy getSource() const {
if (isBlockPointer())
return getDeclDesc()->getSource();
if (isFunctionPointer()) {
const Function *F = Fn.Func;
return F ? F->getDecl() : DeclTy();
}
llvm_unreachable("Unsupported pointer type in getSource()");
return DeclTy();
}
/// Returns a pointer to the object of which this pointer is a field.
[[nodiscard]] Pointer getBase() const { return Pointer(view().getBase()); }
/// Returns the parent array.
[[nodiscard]] Pointer getArray() const { return Pointer(view().getArray()); }
/// Accessors for information about the innermost field.
const Descriptor *getFieldDesc() const {
if (isIntegralPointer())
return nullptr;
if (isRoot())
return getDeclDesc();
return getInlineDesc()->Desc;
}
/// Returns the type of the innermost field.
QualType getType() const {
switch (StorageKind) {
case Storage::Int:
return Int.getPointeeType();
case Storage::Block:
return view().getType();
case Storage::Fn:
return Fn.Func->getDecl()->getType();
case Storage::Typeid:
return QualType(Typeid.TypeInfoType, 0);
}
llvm_unreachable("Unhandled StorageKind");
}
const VarDecl *getRootVarDecl() const;
[[nodiscard]] Pointer getDeclPtr() const { return Pointer(BS.Pointee); }
/// Returns the element size of the innermost field.
size_t elemSize() const {
if (isIntegralPointer()) {
// FIXME: Remove this and handle int ptrs specially?
return 1;
}
return view().elemSize();
}
/// Returns the total size of the innermost field.
size_t getSize() const {
assert(isBlockPointer());
return getFieldDesc()->getSize();
}
/// Returns the offset into an array.
unsigned getOffset() const {
assert(Offset != PtrView::PastEndMark && "invalid offset");
return view().getOffset();
}
/// Whether this array refers to an array, but not
/// to the first element.
bool isArrayRoot() const { return view().isArrayRoot(); }
/// Checks if the innermost field is an array.
bool inArray() const {
if (isBlockPointer())
return view().inArray();
return false;
}
bool inUnion() const {
if (isBlockPointer() && BS.Base >= sizeof(InlineDescriptor))
return view().inUnion();
return false;
};
/// Checks if the structure is a primitive array.
bool inPrimitiveArray() const {
if (isBlockPointer())
return view().inPrimitiveArray();
return false;
}
/// Checks if the structure is an array of unknown size.
bool isUnknownSizeArray() const {
if (!isBlockPointer())
return false;
return getFieldDesc()->isUnknownSizeArray();
}
/// Checks if the pointer points to an array.
bool isArrayElement() const {
if (!isBlockPointer())
return false;
return view().isArrayElement();
}
/// Pointer points directly to a block.
bool isRoot() const {
if (isZero() || !isBlockPointer())
return true;
return view().isRoot();
}
/// If this pointer has an InlineDescriptor we can use to initialize.
bool canBeInitialized() const {
if (!isBlockPointer())
return false;
return BS.Pointee && BS.Base > 0;
}
[[nodiscard]] const BlockPointer &asBlockPointer() const {
assert(isBlockPointer());
return BS;
}
[[nodiscard]] const IntPointer &asIntPointer() const {
assert(isIntegralPointer());
return Int;
}
[[nodiscard]] const FunctionPointer &asFunctionPointer() const {
assert(isFunctionPointer());
return Fn;
}
[[nodiscard]] const TypeidPointer &asTypeidPointer() const {
assert(isTypeidPointer());
return Typeid;
}
bool isBlockPointer() const { return StorageKind == Storage::Block; }
bool isIntegralPointer() const { return StorageKind == Storage::Int; }
bool isFunctionPointer() const { return StorageKind == Storage::Fn; }
bool isTypeidPointer() const { return StorageKind == Storage::Typeid; }
/// Returns the record descriptor of a class.
const Record *getRecord() const {
if (!isBlockPointer())
return nullptr;
return view().getRecord();
}
/// Returns the element record type, if this is a non-primive array.
const Record *getElemRecord() const { return view().getElemRecord(); }
/// Returns the field information.
const FieldDecl *getField() const {
if (const Descriptor *FD = getFieldDesc())
return FD->asFieldDecl();
return nullptr;
}
/// Checks if the storage is extern.
bool isExtern() const {
if (isBlockPointer())
return BS.Pointee && BS.Pointee->isExtern();
return false;
}
/// Checks if the storage is static.
bool isStatic() const {
if (!isBlockPointer())
return true;
assert(BS.Pointee);
return BS.Pointee->isStatic();
}
/// Checks if the storage is temporary.
bool isTemporary() const {
if (isBlockPointer()) {
assert(BS.Pointee);
return BS.Pointee->isTemporary();
}
return false;
}
/// Checks if the storage has been dynamically allocated.
bool isDynamic() const {
if (isBlockPointer()) {
assert(BS.Pointee);
return BS.Pointee->isDynamic();
}
return false;
}
/// Checks if the storage is a static temporary.
bool isStaticTemporary() const { return isStatic() && isTemporary(); }
/// Checks if the field is mutable.
bool isMutable() const {
if (!isBlockPointer())
return false;
return view().isMutable();
}
bool isWeak() const {
if (isFunctionPointer()) {
if (!Fn.Func || !Fn.Func->getDecl())
return false;
return Fn.Func->getDecl()->isWeak();
}
if (!isBlockPointer())
return false;
assert(isBlockPointer());
return BS.Pointee->isWeak();
}
/// Checks if the object is active.
bool isActive() const {
if (!isBlockPointer())
return true;
return view().isActive();
}
/// Checks if a structure is a base class.
bool isBaseClass() const { return view().isBaseClass(); }
bool isVirtualBaseClass() const { return view().isVirtualBaseClass(); }
/// Checks if the pointer points to a dummy value.
bool isDummy() const {
if (!isBlockPointer())
return false;
return view().isDummy();
}
/// Checks if an object or a subfield is mutable.
bool isConst() const {
if (isIntegralPointer())
return true;
return isRoot() ? getDeclDesc()->IsConst : getInlineDesc()->IsConst;
}
bool isConstInMutable() const {
if (!isBlockPointer())
return false;
return isRoot() ? false : getInlineDesc()->IsConstInMutable;
}
/// Checks if an object or a subfield is volatile.
bool isVolatile() const {
if (!isBlockPointer())
return false;
return isRoot() ? getDeclDesc()->IsVolatile : getInlineDesc()->IsVolatile;
}
/// Returns the declaration ID.
UnsignedOrNone getDeclID() const {
if (isBlockPointer()) {
assert(BS.Pointee);
return BS.Pointee->getDeclID();
}
return std::nullopt;
}
/// Returns the byte offset from the start.
uint64_t getByteOffset() const {
if (isIntegralPointer())
return Int.Value + Offset;
if (isTypeidPointer())
return reinterpret_cast<uintptr_t>(Typeid.TypePtr) + Offset;
if (isOnePastEnd())
return PtrView::PastEndMark;
return Offset;
}
/// Returns the number of elements.
unsigned getNumElems() const {
if (!isBlockPointer())
return ~0u;
return view().getNumElems();
}
const Block *block() const { return BS.Pointee; }
/// If backed by actual data (i.e. a block pointer), return
/// an address to that data.
const std::byte *getRawAddress() const {
assert(isBlockPointer());
return BS.Pointee->rawData() + Offset;
}
/// Returns the index into an array.
int64_t getIndex() const {
if (!isBlockPointer())
return getIntegerRepresentation();
return view().getIndex();
}
/// Checks if the index is one past end.
bool isOnePastEnd() const {
if (!isBlockPointer())
return false;
if (!BS.Pointee)
return false;
if (isUnknownSizeArray())
return false;
return isPastEnd() || (getSize() == getOffset());
}
/// Checks if the pointer points past the end of the object.
bool isPastEnd() const {
if (isIntegralPointer())
return false;
return !isZero() && Offset > BS.Pointee->getSize();
}
/// Checks if the pointer is an out-of-bounds element pointer.
bool isElementPastEnd() const { return Offset == PtrView::PastEndMark; }
/// Checks if the pointer is pointing to a zero-size array.
bool isZeroSizeArray() const {
if (isFunctionPointer())
return false;
if (const auto *Desc = getFieldDesc())
return Desc->isZeroSizeArray();
return false;
}
/// Checks whether the pointer can be dereferenced to the given PrimType.
bool canDeref(PrimType T) const {
if (const Descriptor *FieldDesc = getFieldDesc()) {
return (FieldDesc->isPrimitive() || FieldDesc->isPrimitiveArray()) &&
FieldDesc->getPrimType() == T;
}
return false;
}
/// Dereferences the pointer, if it's live.
template <typename T> T &deref() const {
assert(isLive() && "Invalid pointer");
assert(isBlockPointer());
assert(BS.Pointee);
assert(isDereferencable());
assert(Offset + sizeof(T) <= BS.Pointee->getDescriptor()->getAllocSize());
return view().deref<T>();
}
/// Dereferences the element at index \p I.
/// This is equivalent to atIndex(I).deref<T>().
template <typename T> T &elem(unsigned I) const {
assert(isLive() && "Invalid pointer");
assert(isBlockPointer());
assert(BS.Pointee);
assert(isDereferencable());
assert(getFieldDesc()->isPrimitiveArray());
assert(I < getFieldDesc()->getNumElems());
return view().elem<T>(I);
}
bool isConstexprUnknown() const {
if (!isBlockPointer())
return false;
return getDeclDesc()->IsConstexprUnknown;
}
/// Whether this block can be read from at all. This is only true for
/// block pointers that point to a valid location inside that block.
bool isDereferencable() const {
if (!isBlockPointer())
return false;
if (isDummy())
return false;
if (isConstexprUnknown())
return false;
if (isPastEnd())
return false;
return true;
}
/// Initializes a field.
void initialize() const {
if (!isBlockPointer())
return;
view().initialize();
}
/// Initialized the given element of a primitive array.
void initializeElement(unsigned Index) const {
view().initializeElement(Index);
}
/// Initialize all elements of a primitive array at once. This can be
/// used in situations where we *know* we have initialized *all* elements
/// of a primtive array.
void initializeAllElements() const;
/// Checks if an object was initialized.
bool isInitialized() const;
/// Like isInitialized(), but for primitive arrays.
bool isElementInitialized(unsigned Index) const {
if (!isBlockPointer())
return true;
return view().isElementInitialized(Index);
}
bool allElementsInitialized() const {
assert(getFieldDesc()->isPrimitiveArray());
assert(isArrayRoot());
return view().allElementsInitialized();
}
bool allElementsAlive() const;
bool isElementAlive(unsigned Index) const;
/// Activates a field.
void activate() const { view().activate(); }
/// Deactivates an entire strurcutre.
void deactivate() const {
// TODO: this only appears in constructors, so nothing to deactivate.
}
Lifetime getLifetime() const {
if (!isBlockPointer())
return Lifetime::Started;
if (BS.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;
}
/// Start the lifetime of this pointer. This works for pointer with an
/// InlineDescriptor as well as primitive array elements. Pointers are usually
/// alive by default, unless the underlying object has been allocated with
/// std::allocator. This function is used by std::construct_at.
void startLifetime() const { setLifeState(Lifetime::Started); }
/// Ends the lifetime of the pointer. This works for pointer with an
/// InlineDescriptor as well as primitive array elements. This function is
/// used by std::destroy_at.
void endLifetime() const { setLifeState(Lifetime::Ended); }
void setLifeState(Lifetime L) const {
if (!isBlockPointer())
return;
view().setLifeState(L);
};
/// Strip base casts from this Pointer.
/// The result is either a root pointer or something
/// that isn't a base class anymore.
[[nodiscard]] Pointer stripBaseCasts() const {
PtrView V = view();
while (V.isBaseClass())
V = V.getBase();
return Pointer(V);
}
/// Compare two pointers.
ComparisonCategoryResult compare(const Pointer &Other) const {
if (!hasSameBase(*this, Other))
return ComparisonCategoryResult::Unordered;
if (Offset < Other.Offset)
return ComparisonCategoryResult::Less;
if (Offset > Other.Offset)
return ComparisonCategoryResult::Greater;
return ComparisonCategoryResult::Equal;
}
/// Checks if two pointers are comparable.
static bool hasSameBase(const Pointer &A, const Pointer &B);
/// Checks if two pointers can be subtracted.
static bool hasSameArray(const Pointer &A, const Pointer &B);
/// Checks if both given pointers point to the same block.
static bool pointToSameBlock(const Pointer &A, const Pointer &B);
static std::optional<std::pair<PtrView, PtrView>>
computeSplitPoint(const Pointer &A, const Pointer &B);
/// Whether this points to a block that's been created for a "literal lvalue",
/// i.e. a non-MaterializeTemporaryExpr Expr.
bool pointsToLiteral() const;
bool pointsToStringLiteral() const;
/// Whether this points to a block created for an AddrLabelExpr.
bool pointsToLabel() const;
/// Returns the AddrLabelExpr the Pointer points to, if any.
const AddrLabelExpr *getPointedToLabel() const {
if (const Descriptor *Desc = getDeclDesc())
return dyn_cast_if_present<AddrLabelExpr>(Desc->asExpr());
return nullptr;
}
/// Prints the pointer.
void print(llvm::raw_ostream &OS) const;
/// Compute an integer that can be used to compare this pointer to
/// another one. This is usually NOT the same as the pointer offset
/// regarding the AST record layout.
std::optional<size_t>
computeOffsetForComparison(const ASTContext &ASTCtx) const;
private:
friend class Block;
friend class DeadBlock;
friend class MemberPointer;
friend class InterpState;
friend class DynamicAllocator;
friend class Program;
/// Returns the embedded descriptor preceding a field.
InlineDescriptor *getInlineDesc() const {
assert(isBlockPointer());
assert(BS.Base != sizeof(GlobalInlineDescriptor));
assert(BS.Base <= BS.Pointee->getSize());
assert(BS.Base >= sizeof(InlineDescriptor));
return getDescriptor(BS.Base);
}
/// Returns a descriptor at a given offset.
InlineDescriptor *getDescriptor(unsigned Offset) const {
assert(Offset != 0 && "Not a nested pointer");
assert(isBlockPointer());
assert(!isZero());
return view().getDescriptor(Offset);
}
/// Returns a reference to the InitMapPtr which stores the initialization map.
InitMapPtr &getInitMap() const {
assert(isBlockPointer());
assert(!isZero());
return view().getInitMap();
}
/// Offset into the storage.
uint64_t Offset = 0;
Storage StorageKind = Storage::Int;
union {
IntPointer Int;
BlockPointer BS;
FunctionPointer Fn;
TypeidPointer Typeid;
};
};
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Pointer &P) {
P.print(OS);
OS << ' ';
if (P.isZero())
return OS;
if (const Descriptor *D = P.getFieldDesc())
D->dump(OS);
if (P.isArrayElement()) {
if (P.isOnePastEnd())
OS << " one-past-the-end";
else {
OS << ' ';
std::string Indices;
llvm::raw_string_ostream SS(Indices);
Pointer K = P;
while (K.isArrayElement()) {
SS << ']' << K.expand().getIndex() << '[';
K = K.expand().getArray();
}
std::reverse(Indices.begin(), Indices.end());
OS << Indices;
}
} else if (P.isBlockPointer() && P.isArrayRoot())
OS << " arrayroot";
if (P.isBlockPointer() && P.block() && P.block()->isDummy())
OS << " dummy";
if (!P.isLive())
OS << " dead";
if (P.isBlockPointer() && P.isBaseClass())
OS << " base-class";
return OS;
}
} // namespace interp
} // namespace clang
#endif