Add an `EvaluationResult` class. This contains the result either as a `Pointer` or as a `APValue`. This way, we can inspect the result of the evaluation and diagnose problems with it (e.g. uninitialized fields in global initializers or pointers pointing to things they shouldn't point to).
374 lines
10 KiB
C++
374 lines
10 KiB
C++
//===--- 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 "Context.h"
|
|
#include "Floating.h"
|
|
#include "Function.h"
|
|
#include "Integral.h"
|
|
#include "InterpBlock.h"
|
|
#include "PrimType.h"
|
|
#include "Record.h"
|
|
|
|
using namespace clang;
|
|
using namespace clang::interp;
|
|
|
|
Pointer::Pointer(Block *Pointee) : Pointer(Pointee, 0, 0) {}
|
|
|
|
Pointer::Pointer(Block *Pointee, unsigned BaseAndOffset)
|
|
: Pointer(Pointee, BaseAndOffset, BaseAndOffset) {}
|
|
|
|
Pointer::Pointer(const Pointer &P) : Pointer(P.Pointee, P.Base, P.Offset) {}
|
|
|
|
Pointer::Pointer(Pointer &&P)
|
|
: Pointee(P.Pointee), Base(P.Base), Offset(P.Offset) {
|
|
if (Pointee)
|
|
Pointee->replacePointer(&P, this);
|
|
}
|
|
|
|
Pointer::Pointer(Block *Pointee, unsigned Base, unsigned Offset)
|
|
: Pointee(Pointee), Base(Base), Offset(Offset) {
|
|
assert((Base == RootPtrMark || Base % alignof(void *) == 0) && "wrong base");
|
|
if (Pointee)
|
|
Pointee->addPointer(this);
|
|
}
|
|
|
|
Pointer::~Pointer() {
|
|
if (Pointee) {
|
|
Pointee->removePointer(this);
|
|
Pointee->cleanup();
|
|
}
|
|
}
|
|
|
|
void Pointer::operator=(const Pointer &P) {
|
|
Block *Old = Pointee;
|
|
|
|
if (Pointee)
|
|
Pointee->removePointer(this);
|
|
|
|
Offset = P.Offset;
|
|
Base = P.Base;
|
|
|
|
Pointee = P.Pointee;
|
|
if (Pointee)
|
|
Pointee->addPointer(this);
|
|
|
|
if (Old)
|
|
Old->cleanup();
|
|
}
|
|
|
|
void Pointer::operator=(Pointer &&P) {
|
|
Block *Old = Pointee;
|
|
|
|
if (Pointee)
|
|
Pointee->removePointer(this);
|
|
|
|
Offset = P.Offset;
|
|
Base = P.Base;
|
|
|
|
Pointee = P.Pointee;
|
|
if (Pointee)
|
|
Pointee->replacePointer(&P, this);
|
|
|
|
if (Old)
|
|
Old->cleanup();
|
|
}
|
|
|
|
APValue Pointer::toAPValue() const {
|
|
APValue::LValueBase Base;
|
|
llvm::SmallVector<APValue::LValuePathEntry, 5> Path;
|
|
CharUnits Offset;
|
|
bool IsNullPtr;
|
|
bool IsOnePastEnd;
|
|
|
|
if (isZero()) {
|
|
Base = static_cast<const Expr *>(nullptr);
|
|
IsNullPtr = true;
|
|
IsOnePastEnd = false;
|
|
Offset = CharUnits::Zero();
|
|
} else {
|
|
// Build the lvalue base from the block.
|
|
const Descriptor *Desc = getDeclDesc();
|
|
if (auto *VD = Desc->asValueDecl())
|
|
Base = VD;
|
|
else if (auto *E = Desc->asExpr())
|
|
Base = E;
|
|
else
|
|
llvm_unreachable("Invalid allocation type");
|
|
|
|
// Not a null pointer.
|
|
IsNullPtr = false;
|
|
|
|
if (isUnknownSizeArray()) {
|
|
IsOnePastEnd = false;
|
|
Offset = CharUnits::Zero();
|
|
} else if (Desc->asExpr()) {
|
|
// Pointer pointing to a an expression.
|
|
IsOnePastEnd = false;
|
|
Offset = CharUnits::Zero();
|
|
} else {
|
|
// TODO: compute the offset into the object.
|
|
Offset = CharUnits::Zero();
|
|
|
|
// Build the path into the object.
|
|
Pointer Ptr = *this;
|
|
while (Ptr.isField() || Ptr.isArrayElement()) {
|
|
if (Ptr.isArrayElement()) {
|
|
Path.push_back(APValue::LValuePathEntry::ArrayIndex(Ptr.getIndex()));
|
|
Ptr = Ptr.getArray();
|
|
} else {
|
|
// TODO: figure out if base is virtual
|
|
bool IsVirtual = false;
|
|
|
|
// Create a path entry for the field.
|
|
const Descriptor *Desc = Ptr.getFieldDesc();
|
|
if (const auto *BaseOrMember = Desc->asDecl()) {
|
|
Path.push_back(APValue::LValuePathEntry({BaseOrMember, IsVirtual}));
|
|
Ptr = Ptr.getBase();
|
|
continue;
|
|
}
|
|
llvm_unreachable("Invalid field type");
|
|
}
|
|
}
|
|
|
|
IsOnePastEnd = isOnePastEnd();
|
|
}
|
|
}
|
|
|
|
// 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());
|
|
|
|
return APValue(Base, Offset, Path, IsOnePastEnd, IsNullPtr);
|
|
}
|
|
|
|
std::string Pointer::toDiagnosticString(const ASTContext &Ctx) const {
|
|
if (!Pointee)
|
|
return "nullptr";
|
|
|
|
return toAPValue().getAsString(Ctx, getType());
|
|
}
|
|
|
|
bool Pointer::isInitialized() const {
|
|
assert(Pointee && "Cannot check if null pointer was initialized");
|
|
const Descriptor *Desc = getFieldDesc();
|
|
assert(Desc);
|
|
if (Desc->isPrimitiveArray()) {
|
|
if (isStatic() && Base == 0)
|
|
return true;
|
|
|
|
InitMapPtr &IM = getInitMap();
|
|
|
|
if (!IM)
|
|
return false;
|
|
|
|
if (IM->first)
|
|
return true;
|
|
|
|
return IM->second->isElementInitialized(getIndex());
|
|
}
|
|
|
|
// Field has its bit in an inline descriptor.
|
|
return Base == 0 || getInlineDesc()->IsInitialized;
|
|
}
|
|
|
|
void Pointer::initialize() const {
|
|
assert(Pointee && "Cannot initialize null pointer");
|
|
const Descriptor *Desc = getFieldDesc();
|
|
|
|
assert(Desc);
|
|
if (Desc->isPrimitiveArray()) {
|
|
// Primitive global arrays don't have an initmap.
|
|
if (isStatic() && Base == 0)
|
|
return;
|
|
|
|
InitMapPtr &IM = getInitMap();
|
|
if (!IM)
|
|
IM =
|
|
std::make_pair(false, std::make_shared<InitMap>(Desc->getNumElems()));
|
|
|
|
assert(IM);
|
|
|
|
// All initialized.
|
|
if (IM->first)
|
|
return;
|
|
|
|
if (IM->second->initializeElement(getIndex())) {
|
|
IM->first = true;
|
|
IM->second.reset();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Field has its bit in an inline descriptor.
|
|
assert(Base != 0 && "Only composite fields can be initialised");
|
|
getInlineDesc()->IsInitialized = true;
|
|
}
|
|
|
|
void Pointer::activate() const {
|
|
// Field has its bit in an inline descriptor.
|
|
assert(Base != 0 && "Only composite fields can be initialised");
|
|
getInlineDesc()->IsActive = true;
|
|
}
|
|
|
|
void Pointer::deactivate() const {
|
|
// TODO: this only appears in constructors, so nothing to deactivate.
|
|
}
|
|
|
|
bool Pointer::hasSameBase(const Pointer &A, const Pointer &B) {
|
|
return A.Pointee == B.Pointee;
|
|
}
|
|
|
|
bool Pointer::hasSameArray(const Pointer &A, const Pointer &B) {
|
|
return hasSameBase(A, B) && A.Base == B.Base && A.getFieldDesc()->IsArray;
|
|
}
|
|
|
|
std::optional<APValue> Pointer::toRValue(const Context &Ctx) const {
|
|
// Method to recursively traverse composites.
|
|
std::function<bool(QualType, const Pointer &, APValue &)> Composite;
|
|
Composite = [&Composite, &Ctx](QualType Ty, const Pointer &Ptr, APValue &R) {
|
|
if (const auto *AT = Ty->getAs<AtomicType>())
|
|
Ty = AT->getValueType();
|
|
|
|
// Invalid pointers.
|
|
if (Ptr.isDummy() || !Ptr.isLive() ||
|
|
(!Ptr.isUnknownSizeArray() && Ptr.isOnePastEnd()))
|
|
return false;
|
|
|
|
// Primitive values.
|
|
if (std::optional<PrimType> T = Ctx.classify(Ty)) {
|
|
if (T == PT_Ptr || T == PT_FnPtr) {
|
|
R = Ptr.toAPValue();
|
|
} else {
|
|
TYPE_SWITCH(*T, R = Ptr.deref<T>().toAPValue());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (const auto *RT = Ty->getAs<RecordType>()) {
|
|
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()) {
|
|
const Pointer &FP = Ptr.atField(F.Offset);
|
|
QualType FieldTy = F.Decl->getType();
|
|
if (FP.isActive()) {
|
|
if (std::optional<PrimType> T = Ctx.classify(FieldTy)) {
|
|
TYPE_SWITCH(*T, Value = FP.deref<T>().toAPValue());
|
|
} else {
|
|
Ok &= Composite(FieldTy, FP, Value);
|
|
}
|
|
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);
|
|
|
|
for (unsigned I = 0; I < NF; ++I) {
|
|
const Record::Field *FD = Record->getField(I);
|
|
QualType FieldTy = FD->Decl->getType();
|
|
const Pointer &FP = Ptr.atField(FD->Offset);
|
|
APValue &Value = R.getStructField(I);
|
|
|
|
if (std::optional<PrimType> T = Ctx.classify(FieldTy)) {
|
|
TYPE_SWITCH(*T, Value = FP.deref<T>().toAPValue());
|
|
} else {
|
|
Ok &= Composite(FieldTy, FP, Value);
|
|
}
|
|
}
|
|
|
|
for (unsigned I = 0; I < NB; ++I) {
|
|
const Record::Base *BD = Record->getBase(I);
|
|
QualType BaseTy = Ctx.getASTContext().getRecordType(BD->Decl);
|
|
const Pointer &BP = Ptr.atField(BD->Offset);
|
|
Ok &= Composite(BaseTy, BP, R.getStructBase(I));
|
|
}
|
|
|
|
for (unsigned I = 0; I < NV; ++I) {
|
|
const Record::Base *VD = Record->getVirtualBase(I);
|
|
QualType VirtBaseTy = Ctx.getASTContext().getRecordType(VD->Decl);
|
|
const Pointer &VP = Ptr.atField(VD->Offset);
|
|
Ok &= Composite(VirtBaseTy, VP, R.getStructBase(NB + I));
|
|
}
|
|
}
|
|
return Ok;
|
|
}
|
|
|
|
if (Ty->isIncompleteArrayType()) {
|
|
R = APValue(APValue::UninitArray(), 0, 0);
|
|
return true;
|
|
}
|
|
|
|
if (const auto *AT = Ty->getAsArrayTypeUnsafe()) {
|
|
const size_t NumElems = Ptr.getNumElems();
|
|
QualType ElemTy = AT->getElementType();
|
|
R = APValue(APValue::UninitArray{}, NumElems, NumElems);
|
|
|
|
bool Ok = true;
|
|
for (unsigned I = 0; I < NumElems; ++I) {
|
|
APValue &Slot = R.getArrayInitializedElt(I);
|
|
const Pointer &EP = Ptr.atIndex(I);
|
|
if (std::optional<PrimType> T = Ctx.classify(ElemTy)) {
|
|
TYPE_SWITCH(*T, Slot = EP.deref<T>().toAPValue());
|
|
} else {
|
|
Ok &= Composite(ElemTy, EP.narrow(), Slot);
|
|
}
|
|
}
|
|
return Ok;
|
|
}
|
|
|
|
// Complex types.
|
|
if (const auto *CT = Ty->getAs<ComplexType>()) {
|
|
QualType ElemTy = CT->getElementType();
|
|
std::optional<PrimType> ElemT = Ctx.classify(ElemTy);
|
|
assert(ElemT);
|
|
|
|
if (ElemTy->isIntegerType()) {
|
|
INT_TYPE_SWITCH(*ElemT, {
|
|
auto V1 = Ptr.atIndex(0).deref<T>();
|
|
auto V2 = Ptr.atIndex(1).deref<T>();
|
|
R = APValue(V1.toAPSInt(), V2.toAPSInt());
|
|
return true;
|
|
});
|
|
} else if (ElemTy->isFloatingType()) {
|
|
R = APValue(Ptr.atIndex(0).deref<Floating>().getAPFloat(),
|
|
Ptr.atIndex(1).deref<Floating>().getAPFloat());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
llvm_unreachable("invalid value to return");
|
|
};
|
|
|
|
if (isZero())
|
|
return APValue(static_cast<Expr *>(nullptr), CharUnits::Zero(), {}, false,
|
|
true);
|
|
|
|
if (isDummy() || !isLive())
|
|
return std::nullopt;
|
|
|
|
// Return the composite type.
|
|
APValue Result;
|
|
if (!Composite(getType(), *this, Result))
|
|
return std::nullopt;
|
|
return Result;
|
|
}
|