blob: 30976fcf1d52132a456743b2cbc5c665663e5d6d [file]
//===----- EvaluationResult.cpp - Result class for the 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 "EvaluationResult.h"
#include "../ExprConstShared.h"
#include "InterpState.h"
#include "Pointer.h"
#include "Record.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/ExprObjC.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include <iterator>
namespace clang {
namespace interp {
QualType EvaluationResult::getStorageType() const {
if (const auto *E = Source.asExpr()) {
if (E->isPRValue())
return E->getType();
return Ctx.getASTContext().getLValueReferenceType(E->getType());
}
if (const auto *D = Source.asValueDecl())
return D->getType();
return QualType();
}
static void DiagnoseUninitializedSubobject(InterpState &S, SourceLocation Loc,
const FieldDecl *SubObjDecl) {
assert(SubObjDecl && "Subobject declaration does not exist");
S.FFDiag(Loc, diag::note_constexpr_uninitialized)
<< /*(name)*/ 1 << SubObjDecl;
S.Note(SubObjDecl->getLocation(),
diag::note_constexpr_subobject_declared_here);
}
static bool CheckFieldsInitialized(InterpState &S, SourceLocation Loc,
PtrView BasePtr, const Record *R,
bool IsCompleteClass = true);
static bool CheckArrayInitialized(InterpState &S, SourceLocation Loc,
PtrView BasePtr) {
const Descriptor *BaseDesc = BasePtr.getFieldDesc();
assert(BaseDesc->isArray());
size_t NumElems = BaseDesc->getNumElems();
if (NumElems == 0)
return true;
bool Result = true;
if (BaseDesc->isPrimitiveArray()) {
if (BasePtr.allElementsInitialized())
return true;
DiagnoseUninitializedSubobject(S, Loc, BasePtr.getField());
return false;
}
const Descriptor *ElemDesc = BaseDesc->ElemDesc;
if (ElemDesc->isRecord()) {
const Record *R = ElemDesc->ElemRecord;
for (size_t I = 0; I != NumElems; ++I) {
PtrView ElemPtr = BasePtr.atIndex(I).narrow();
Result &= CheckFieldsInitialized(S, Loc, ElemPtr, R);
}
} else if (ElemDesc->isArray()) {
for (size_t I = 0; I != NumElems; ++I) {
PtrView ElemPtr = BasePtr.atIndex(I).narrow();
Result &= CheckArrayInitialized(S, Loc, ElemPtr);
}
}
return Result;
}
static bool CheckFieldsInitialized(InterpState &S, SourceLocation Loc,
PtrView BasePtr, const Record *R,
bool IsCompleteClass) {
assert(R);
bool Result = true;
// Check all fields of this record are initialized.
for (const Record::Field &F : R->fields()) {
PtrView FieldPtr = BasePtr.atField(F.Offset);
// Don't check inactive union members.
if (R->isUnion() && !FieldPtr.isActive())
continue;
QualType FieldType = F.Decl->getType();
const Descriptor *FieldDesc = FieldPtr.getFieldDesc();
if (FieldDesc->isRecord()) {
Result &= CheckFieldsInitialized(S, Loc, FieldPtr, FieldPtr.getRecord());
} else if (FieldType->isIncompleteArrayType()) {
// Nothing to do here.
} else if (F.Decl->isUnnamedBitField()) {
// Nothing do do here.
} else if (FieldDesc->isArray()) {
Result &= CheckArrayInitialized(S, Loc, FieldPtr);
} else if (!FieldPtr.isInitialized()) {
DiagnoseUninitializedSubobject(S, Loc, F.Decl);
Result = false;
}
}
auto diagnoseBase = [&](const Record::Base &B, unsigned Index) -> bool {
const Descriptor *Desc = BasePtr.getDeclDesc();
if (const auto *CD = dyn_cast_if_present<CXXRecordDecl>(R->getDecl())) {
const auto &BS = *std::next(CD->bases_begin(), Index);
SourceLocation TypeBeginLoc = BS.getBaseTypeLoc();
S.FFDiag(TypeBeginLoc, diag::note_constexpr_uninitialized_base)
<< B.Desc->getType() << SourceRange(TypeBeginLoc, BS.getEndLoc());
} else {
S.FFDiag(Desc->getLocation(), diag::note_constexpr_uninitialized_base)
<< B.Desc->getType();
}
return false;
};
// Check Fields in all bases.
for (auto [I, B] : llvm::enumerate(R->bases())) {
PtrView P = BasePtr.atField(B.Offset);
if (!P.isInitialized())
return diagnoseBase(B, I);
Result &= CheckFieldsInitialized(S, Loc, P, B.R, /*IsCompleteClass=*/false);
}
// And virtual bases.
if (IsCompleteClass) {
for (auto [I, B] : llvm::enumerate(R->virtual_bases())) {
PtrView P = BasePtr.atField(B.Offset);
if (!P.isInitialized())
return diagnoseBase(B, I);
Result &=
CheckFieldsInitialized(S, Loc, P, B.R, /*IsCompleteClass=*/false);
}
}
return Result;
}
bool EvaluationResult::checkFullyInitialized(InterpState &S,
const Pointer &Ptr) const {
assert(Source);
assert(empty());
if (Ptr.isZero())
return true;
if (!Ptr.isBlockPointer())
return true;
// We can't inspect dead pointers at all. Return true here so we can
// diagnose them later.
if (!Ptr.isLive())
return true;
SourceLocation InitLoc;
if (const auto *D = Source.asDecl())
InitLoc = cast<VarDecl>(D)->getAnyInitializer()->getExprLoc();
else if (const auto *E = Source.asExpr())
InitLoc = E->getExprLoc();
if (const Record *R = Ptr.getRecord())
return CheckFieldsInitialized(S, InitLoc, Ptr.view(), R);
if (isa_and_nonnull<ConstantArrayType>(Ptr.getType()->getAsArrayTypeUnsafe()))
return CheckArrayInitialized(S, InitLoc, Ptr.view());
return true;
}
static bool isOrHasPtr(const Descriptor *D) {
if ((D->isPrimitive() || D->isPrimitiveArray()) && D->getPrimType() == PT_Ptr)
return true;
if (D->ElemRecord)
return D->ElemRecord->hasPtrField();
return false;
}
static void collectBlocks(PtrView Ptr,
llvm::SmallPtrSet<const Block *, 4> &Blocks,
bool IsCompleteClass = true) {
auto isUsefulPtr = [](const Pointer &P) -> bool {
return P.isLive() && P.isBlockPointer() && !P.isZero() && !P.isDummy() &&
P.isDereferencable() && !P.isUnknownSizeArray() && !P.isOnePastEnd();
};
if (!Ptr.isLive() || Ptr.isZero() || Ptr.isUnknownSizeArray() ||
Ptr.isOnePastEnd())
return;
Blocks.insert(Ptr.Pointee);
const Descriptor *Desc = Ptr.getFieldDesc();
if (!Desc)
return;
if (const Record *R = Desc->ElemRecord) {
if (!R->hasPtrField())
return;
for (const Record::Base &B : R->bases()) {
if (!B.R->hasPtrField())
continue;
PtrView BasePtr = Ptr.atField(B.Offset);
collectBlocks(BasePtr, Blocks, /*IsCompleteClass=*/false);
}
for (const Record::Field &F : R->fields()) {
if (!isOrHasPtr(F.Desc))
continue;
PtrView FieldPtr = Ptr.atField(F.Offset);
collectBlocks(FieldPtr, Blocks);
}
if (IsCompleteClass) {
for (const Record::Base &B : R->virtual_bases()) {
if (!B.R->hasPtrField())
continue;
PtrView BasePtr = Ptr.atField(B.Offset);
collectBlocks(BasePtr, Blocks, /*IsCompleteClass=*/false);
}
}
return;
}
if (Desc->isPrimitive() && Desc->getPrimType() == PT_Ptr) {
Pointer Pointee = Ptr.deref<Pointer>();
if (isUsefulPtr(Pointee) && !Blocks.contains(Pointee.block()))
collectBlocks(Pointee.view(), Blocks);
return;
}
if (Desc->isPrimitiveArray() && Desc->getPrimType() == PT_Ptr) {
for (unsigned I = 0; I != Desc->getNumElems(); ++I) {
Pointer ElemPointee = Ptr.elem<Pointer>(I);
if (isUsefulPtr(ElemPointee) && !Blocks.contains(ElemPointee.block()))
collectBlocks(ElemPointee.view(), Blocks);
}
return;
}
if (Desc->isCompositeArray() && isOrHasPtr(Desc->ElemDesc)) {
for (unsigned I = 0; I != Desc->getNumElems(); ++I) {
PtrView ElemPtr = Ptr.atIndex(I).narrow();
collectBlocks(ElemPtr, Blocks);
}
}
}
bool EvaluationResult::checkDynamicAllocations(InterpState &S,
const Pointer &Ptr,
SourceInfo Info) const {
if (!Ptr.isBlockPointer())
return true;
// Collect all blocks that this pointer (transitively) points to and
// return false if any of them is a dynamic block.
llvm::SmallPtrSet<const Block *, 4> Blocks;
collectBlocks(Ptr.view(), Blocks);
for (const Block *B : Blocks) {
if (B->isDynamic()) {
assert(B->getDescriptor());
assert(B->getDescriptor()->asExpr());
bool IsSubobj = !Ptr.isRoot() || Ptr.isArrayElement();
S.FFDiag(Info, diag::note_constexpr_dynamic_alloc)
<< Ptr.getType()->isReferenceType() << IsSubobj;
S.Note(B->getDescriptor()->asExpr()->getExprLoc(),
diag::note_constexpr_dynamic_alloc_here);
return false;
}
}
return true;
}
static bool isGlobalLValue(const Pointer &Ptr) {
if (Ptr.isDynamic())
return true;
if (Ptr.isTypeidPointer())
return true;
return ::isGlobalLValue(Ptr.getRootValueDecl(), Ptr.getRootExpr());
}
/// Check if the given function pointer can be returned from an evaluation.
static bool checkFunctionPtr(InterpState &S, const Pointer &Ptr,
QualType PtrType, SourceInfo Info,
ConstantExprKind ConstexprKind) {
assert(Ptr.isFunctionPointer());
const FunctionPointer &FuncPtr = Ptr.asFunctionPointer();
if (!FuncPtr.Func)
return true;
const FunctionDecl *FD = FuncPtr.Func->getDecl();
// E.g. ObjC block pointers.
if (!FD)
return true;
if (FD->isImmediateFunction()) {
S.FFDiag(Info, diag::note_consteval_address_accessible)
<< !PtrType->isAnyPointerType();
S.Note(FD->getLocation(), diag::note_declared_at);
return false;
}
// __declspec(dllimport) must be handled very carefully:
// We must never initialize an expression with the thunk in C++.
// Doing otherwise would allow the same id-expression to yield
// different addresses for the same function in different translation
// units. However, this means that we must dynamically initialize the
// expression with the contents of the import address table at runtime.
//
// The C language has no notion of ODR; furthermore, it has no notion of
// dynamic initialization. This means that we are permitted to
// perform initialization with the address of the thunk.
if (S.getLangOpts().CPlusPlus && !isForManglingOnly(ConstexprKind) &&
FD->hasAttr<DLLImportAttr>())
// FIXME: Diagnostic!
return false;
return true;
}
static bool lvalFields(InterpState &S, const ASTContext &Ctx, PtrView Ptr,
QualType PtrType, SourceInfo Info,
ConstantExprKind ConstexprKind,
llvm::SmallPtrSet<const Block *, 4> &CheckedBlocks);
static bool lval(InterpState &S, const ASTContext &Ctx, const Pointer &Ptr,
QualType PtrType, SourceInfo Info,
ConstantExprKind ConstexprKind,
llvm::SmallPtrSet<const Block *, 4> &CheckedBlocks) {
if (Ptr.isFunctionPointer())
return checkFunctionPtr(S, Ptr, PtrType, Info, ConstexprKind);
if (!Ptr.isBlockPointer() && !Ptr.isOpaquePointer())
return true;
const Expr *BaseE = Ptr.getRootExpr();
const ValueDecl *BaseVD = Ptr.getRootValueDecl();
assert(BaseE || BaseVD);
bool IsReferenceType = PtrType->isReferenceType();
bool IsSubObj = !Ptr.isRoot() || (Ptr.inArray() && !Ptr.isArrayRoot());
if (!isGlobalLValue(Ptr)) {
if (S.getLangOpts().CPlusPlus11) {
S.FFDiag(Info, diag::note_constexpr_non_global, 1)
<< IsReferenceType << IsSubObj << !!BaseVD << BaseVD;
const VarDecl *VarD = dyn_cast_if_present<VarDecl>(BaseVD);
if (VarD && VarD->isConstexpr()) {
// Non-static local constexpr variables have unintuitive semantics:
// constexpr int a = 1;
// constexpr const int *p = &a;
// ... is invalid because the address of 'a' is not constant. Suggest
// adding a 'static' in this case.
S.Note(VarD->getLocation(), diag::note_constexpr_not_static)
<< VarD
<< FixItHint::CreateInsertion(VarD->getBeginLoc(), "static ");
} else {
if (BaseVD)
S.Note(BaseVD->getLocation(), diag::note_declared_at);
else if (BaseE)
S.Note(BaseE->getExprLoc(), diag::note_constexpr_temporary_here);
}
} else {
S.FFDiag(Info);
}
return false;
}
if (const auto *VD = dyn_cast_if_present<VarDecl>(BaseVD)) {
// Check if this is a thread-local variable.
if (VD->getTLSKind()) {
// FIXME: Diagnostic!
return false;
}
// A dllimport variable never acts like a constant, unless we're
// evaluating a value for use only in name mangling, and unless it's a
// static local. For the latter case, we'd still need to evaluate the
// constant expression in case we're inside a (inlined) function.
if (!isForManglingOnly(ConstexprKind) && VD->hasAttr<DLLImportAttr>() &&
!VD->isStaticLocal())
return false;
// Address of a managed variable is never a constant expression.
if (S.getLangOpts().CUDA && VD->hasAttr<HIPManagedAttr>())
return false;
// In CUDA/HIP device compilation, only device side variables have
// constant addresses.
if (S.getLangOpts().CUDA && S.getLangOpts().CUDAIsDevice &&
Ctx.CUDAConstantEvalCtx.NoWrongSidedVars) {
if ((!VD->hasAttr<CUDADeviceAttr>() && !VD->hasAttr<CUDAConstantAttr>() &&
!VD->getType()->isCUDADeviceBuiltinSurfaceType() &&
!VD->getType()->isCUDADeviceBuiltinTextureType()))
return false;
}
return true;
}
if (const auto *MTE = dyn_cast_if_present<MaterializeTemporaryExpr>(BaseE)) {
QualType TempType = Ptr.getType();
if (TempType.isDestructedType()) {
S.FFDiag(MTE->getExprLoc(),
diag::note_constexpr_unsupported_temporary_nontrivial_dtor)
<< TempType;
return false;
}
if (Ptr.getFieldDesc()->isPrimitive() &&
Ptr.getFieldDesc()->getPrimType() == PT_Ptr) {
// Recurse!
Pointer Pointee = Ptr.deref<Pointer>();
if (!Pointee.isBlockPointer() ||
CheckedBlocks.insert(Pointee.block()).second) {
if (!lval(S, Ctx, Pointee, Pointee.getType(),
Ptr.getDeclDesc()->getLoc(), ConstexprKind, CheckedBlocks))
return false;
}
} else {
if (!lvalFields(S, Ctx, Ptr.view(), TempType, Info, ConstexprKind,
CheckedBlocks))
return false;
}
}
return true;
}
static bool lvalFields(InterpState &S, const ASTContext &Ctx, PtrView Ptr,
QualType PtrType, SourceInfo Info,
ConstantExprKind ConstexprKind,
llvm::SmallPtrSet<const Block *, 4> &CheckedBlocks) {
const Descriptor *FieldDesc = Ptr.getFieldDesc();
if (const Record *R = Ptr.getRecord()) {
if (!R->hasPtrField())
return true;
for (const Record::Base &B : R->bases()) {
if (!B.R->hasPtrField())
continue;
PtrView BasePtr = Ptr.atField(B.Offset);
if (!lvalFields(S, Ctx, BasePtr, B.Desc->getType(), Info, ConstexprKind,
CheckedBlocks))
return false;
}
for (const Record::Field &F : R->fields()) {
PtrView FieldPtr = Ptr.atField(F.Offset);
if (!isOrHasPtr(F.Desc))
continue;
if (F.Desc->isPrimitive() && F.Desc->getPrimType() == PT_Ptr) {
if (!FieldPtr.isLive())
return false;
Pointer Pointee = FieldPtr.deref<Pointer>();
if (!Pointee.isBlockPointer() ||
CheckedBlocks.insert(Pointee.block()).second) {
QualType FieldType = F.Decl->getType();
if (!lval(S, Ctx, Pointee, FieldType, Info, ConstexprKind,
CheckedBlocks))
return false;
}
} else {
if (!lvalFields(S, Ctx, FieldPtr, F.Decl->getType(), Info,
ConstexprKind, CheckedBlocks))
return false;
}
}
for (const Record::Base &B : R->virtual_bases()) {
if (!B.R->hasPtrField())
continue;
PtrView BasePtr = Ptr.atField(B.Offset);
if (!lvalFields(S, Ctx, BasePtr, B.Desc->getType(), Info, ConstexprKind,
CheckedBlocks))
return false;
}
return true;
}
if (FieldDesc->isPrimitiveArray() && FieldDesc->getPrimType() == PT_Ptr) {
for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) {
if (!Ptr.isLive())
return false;
Pointer Pointee = Ptr.elem<Pointer>(I);
if (!Pointee.isBlockPointer() ||
CheckedBlocks.insert(Pointee.block()).second) {
if (!lval(S, Ctx, Pointee, FieldDesc->getElemQualType(), Info,
ConstexprKind, CheckedBlocks))
return false;
}
}
return true;
}
if (FieldDesc->isCompositeArray()) {
if (FieldDesc->ElemRecord && !FieldDesc->ElemRecord->hasPtrField())
return true;
for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) {
PtrView Elem = Ptr.atIndex(I).narrow();
if (!lvalFields(S, Ctx, Elem, FieldDesc->getElemQualType(), Info,
ConstexprKind, CheckedBlocks))
return false;
}
return true;
}
if (FieldDesc->isPrimitive() && FieldDesc->getPrimType() == PT_MemberPtr) {
MemberPointer MP = Ptr.deref<MemberPointer>();
if (!EvaluationResult::checkMemberPointer(S, MP, Info, ConstexprKind))
return false;
}
return true;
}
/// Toplevel accessor to check all lvalue fields.
bool EvaluationResult::checkLValueFields(InterpState &S, const Pointer &Ptr,
SourceInfo Info,
ConstantExprKind ConstexprKind) const {
if (!Ptr.isBlockPointer())
return true;
QualType SourceType = getStorageType();
llvm::SmallPtrSet<const Block *, 4> CheckedBlocks;
return lvalFields(S, Ctx.getASTContext(), Ptr.view(), SourceType, Info,
ConstexprKind, CheckedBlocks);
}
bool EvaluationResult::checkLValue(InterpState &S, const Pointer &Ptr,
SourceInfo Info,
ConstantExprKind ConstexprKind) const {
QualType SourceType = getStorageType();
if (Ptr.isFunctionPointer())
return checkFunctionPtr(S, Ptr, SourceType, Info, ConstexprKind);
if (Ptr.isZero())
return true;
bool IsReferenceType = SourceType->isReferenceType();
if (Ptr.isTypeidPointer()) {
if (isTemplateArgument(ConstexprKind)) {
S.FFDiag(Info, diag::note_constexpr_invalid_template_arg)
<< IsReferenceType << /*IsSubObj=*/false << /*InvalidBaseKind=*/0;
return false;
}
return true;
}
if (Ptr.isStringPointer()) {
// Additional restrictions apply in a template argument. We only enforce the
// C++20 restrictions here; additional syntactic and semantic restrictions
// are applied elsewhere.
if (isTemplateArgument(ConstexprKind)) {
bool IsSubObj = Ptr.asStringPointer().Decayed || Ptr.getIndex() != 0;
int InvalidBaseKind = -1;
StringRef Ident;
const Expr *BaseE = Ptr.asStringPointer().Base;
if (isa_and_nonnull<StringLiteral>(BaseE))
InvalidBaseKind = 1;
else if (const auto *PE = dyn_cast_if_present<PredefinedExpr>(BaseE)) {
InvalidBaseKind = 3;
Ident = PE->getIdentKindName();
}
if (InvalidBaseKind != -1) {
S.FFDiag(Info, diag::note_constexpr_invalid_template_arg)
<< IsReferenceType << IsSubObj << InvalidBaseKind << Ident;
return false;
}
}
return true;
}
if (!Ptr.isBlockPointer() && !Ptr.isOpaquePointer())
return true;
// Additional restrictions apply in a template argument. We only enforce the
// C++20 restrictions here; additional syntactic and semantic restrictions
// are applied elsewhere.
if (isTemplateArgument(ConstexprKind)) {
const Expr *BaseE = Ptr.getRootExpr();
const ValueDecl *BaseVD = Ptr.getRootValueDecl();
assert(BaseE || BaseVD);
if (isa_and_nonnull<MaterializeTemporaryExpr>(BaseE) ||
isa_and_nonnull<LifetimeExtendedTemporaryDecl>(BaseVD)) {
bool IsSubObj = !Ptr.isRoot() || (Ptr.inArray() && !Ptr.isArrayRoot());
S.FFDiag(Info, diag::note_constexpr_invalid_template_arg)
<< IsReferenceType << IsSubObj << 2;
return false;
}
}
llvm::SmallPtrSet<const Block *, 4> CheckedBlocks;
if (!lval(S, Ctx.getASTContext(), Ptr, SourceType, Info, ConstexprKind,
CheckedBlocks)) {
return false;
}
return true;
}
bool EvaluationResult::checkMemberPointer(InterpState &S,
const MemberPointer &MemberPtr,
SourceInfo Info,
ConstantExprKind ConstexprKind) {
const CXXMethodDecl *MD = MemberPtr.getMemberFunction();
if (!MD)
return true;
if (MD->isImmediateFunction()) {
S.FFDiag(Info, diag::note_consteval_address_accessible)
<< /*pointer=*/false;
S.Note(MD->getLocation(), diag::note_declared_at);
return false;
}
if (isForManglingOnly(ConstexprKind) || MD->isVirtual() ||
!MD->hasAttr<DLLImportAttr>()) {
return true;
}
return false;
}
bool EvaluationResult::checkFunctionPointer(
InterpState &S, const Pointer &Ptr, SourceInfo Info,
ConstantExprKind ConstexprKind) const {
return checkFunctionPtr(S, Ptr, getStorageType(), Info, ConstexprKind);
}
} // namespace interp
} // namespace clang