mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-19 01:46:49 +00:00
Reapply "[clang][Interp] Implement dynamic memory allocation handling (#70306)"
This reverts commit 48d703e7f56282ce5d690e45a129a4a7fd040ee6.
This commit is contained in:
parent
e9b2a25e90
commit
e94e72a0c2
@ -75,6 +75,7 @@ add_clang_library(clangAST
|
||||
Interp/InterpBuiltin.cpp
|
||||
Interp/Floating.cpp
|
||||
Interp/EvaluationResult.cpp
|
||||
Interp/DynamicAllocator.cpp
|
||||
Interp/Interp.cpp
|
||||
Interp/InterpBlock.cpp
|
||||
Interp/InterpFrame.cpp
|
||||
|
@ -2771,6 +2771,117 @@ bool Compiler<Emitter>::VisitCXXInheritedCtorInitExpr(
|
||||
return this->emitCall(F, 0, E);
|
||||
}
|
||||
|
||||
template <class Emitter>
|
||||
bool Compiler<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
|
||||
assert(classifyPrim(E->getType()) == PT_Ptr);
|
||||
const Expr *Init = E->getInitializer();
|
||||
QualType ElementType = E->getAllocatedType();
|
||||
std::optional<PrimType> ElemT = classify(ElementType);
|
||||
unsigned PlacementArgs = E->getNumPlacementArgs();
|
||||
bool IsNoThrow = false;
|
||||
|
||||
// FIXME: Better diagnostic. diag::note_constexpr_new_placement
|
||||
if (PlacementArgs != 0) {
|
||||
// The only new-placement list we support is of the form (std::nothrow).
|
||||
//
|
||||
// FIXME: There is no restriction on this, but it's not clear that any
|
||||
// other form makes any sense. We get here for cases such as:
|
||||
//
|
||||
// new (std::align_val_t{N}) X(int)
|
||||
//
|
||||
// (which should presumably be valid only if N is a multiple of
|
||||
// alignof(int), and in any case can't be deallocated unless N is
|
||||
// alignof(X) and X has new-extended alignment).
|
||||
if (PlacementArgs != 1 || !E->getPlacementArg(0)->getType()->isNothrowT())
|
||||
return this->emitInvalid(E);
|
||||
|
||||
if (!this->discard(E->getPlacementArg(0)))
|
||||
return false;
|
||||
IsNoThrow = true;
|
||||
}
|
||||
|
||||
const Descriptor *Desc;
|
||||
if (ElemT) {
|
||||
if (E->isArray())
|
||||
Desc = nullptr; // We're not going to use it in this case.
|
||||
else
|
||||
Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
|
||||
/*IsConst=*/false, /*IsTemporary=*/false,
|
||||
/*IsMutable=*/false);
|
||||
} else {
|
||||
Desc = P.createDescriptor(
|
||||
E, ElementType.getTypePtr(),
|
||||
E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
|
||||
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
|
||||
}
|
||||
|
||||
if (E->isArray()) {
|
||||
std::optional<const Expr *> ArraySizeExpr = E->getArraySize();
|
||||
if (!ArraySizeExpr)
|
||||
return false;
|
||||
|
||||
const Expr *Stripped = *ArraySizeExpr;
|
||||
for (; auto *ICE = dyn_cast<ImplicitCastExpr>(Stripped);
|
||||
Stripped = ICE->getSubExpr())
|
||||
if (ICE->getCastKind() != CK_NoOp &&
|
||||
ICE->getCastKind() != CK_IntegralCast)
|
||||
break;
|
||||
|
||||
PrimType SizeT = classifyPrim(Stripped->getType());
|
||||
|
||||
if (!this->visit(Stripped))
|
||||
return false;
|
||||
|
||||
if (ElemT) {
|
||||
// N primitive elements.
|
||||
if (!this->emitAllocN(SizeT, *ElemT, E, IsNoThrow, E))
|
||||
return false;
|
||||
} else {
|
||||
// N Composite elements.
|
||||
if (!this->emitAllocCN(SizeT, Desc, IsNoThrow, E))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Init && !this->visitInitializer(Init))
|
||||
return false;
|
||||
|
||||
} else {
|
||||
// Allocate just one element.
|
||||
if (!this->emitAlloc(Desc, E))
|
||||
return false;
|
||||
|
||||
if (Init) {
|
||||
if (ElemT) {
|
||||
if (!this->visit(Init))
|
||||
return false;
|
||||
|
||||
if (!this->emitInit(*ElemT, E))
|
||||
return false;
|
||||
} else {
|
||||
// Composite.
|
||||
if (!this->visitInitializer(Init))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (DiscardResult)
|
||||
return this->emitPopPtr(E);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Emitter>
|
||||
bool Compiler<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
|
||||
const Expr *Arg = E->getArgument();
|
||||
|
||||
// Arg must be an lvalue.
|
||||
if (!this->visit(Arg))
|
||||
return false;
|
||||
|
||||
return this->emitFree(E->isArrayForm(), E);
|
||||
}
|
||||
|
||||
template <class Emitter>
|
||||
bool Compiler<Emitter>::VisitExpressionTraitExpr(const ExpressionTraitExpr *E) {
|
||||
assert(Ctx.getLangOpts().CPlusPlus);
|
||||
|
@ -190,6 +190,8 @@ public:
|
||||
bool VisitObjCBoxedExpr(const ObjCBoxedExpr *E);
|
||||
bool VisitCXXStdInitializerListExpr(const CXXStdInitializerListExpr *E);
|
||||
bool VisitStmtExpr(const StmtExpr *E);
|
||||
bool VisitCXXNewExpr(const CXXNewExpr *E);
|
||||
bool VisitCXXDeleteExpr(const CXXDeleteExpr *E);
|
||||
|
||||
// Statements.
|
||||
bool visitCompoundStmt(const CompoundStmt *S);
|
||||
|
118
clang/lib/AST/Interp/DynamicAllocator.cpp
Normal file
118
clang/lib/AST/Interp/DynamicAllocator.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
//==-------- DynamicAllocator.cpp - Dynamic allocations ----------*- 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 "DynamicAllocator.h"
|
||||
#include "InterpBlock.h"
|
||||
#include "InterpState.h"
|
||||
|
||||
using namespace clang;
|
||||
using namespace clang::interp;
|
||||
|
||||
DynamicAllocator::~DynamicAllocator() { cleanup(); }
|
||||
|
||||
void DynamicAllocator::cleanup() {
|
||||
// Invoke destructors of all the blocks and as a last restort,
|
||||
// reset all the pointers pointing to them to null pointees.
|
||||
// This should never show up in diagnostics, but it's necessary
|
||||
// for us to not cause use-after-free problems.
|
||||
for (auto &Iter : AllocationSites) {
|
||||
auto &AllocSite = Iter.second;
|
||||
for (auto &Alloc : AllocSite.Allocations) {
|
||||
Block *B = reinterpret_cast<Block *>(Alloc.Memory.get());
|
||||
B->invokeDtor();
|
||||
if (B->hasPointers()) {
|
||||
while (B->Pointers) {
|
||||
Pointer *Next = B->Pointers->Next;
|
||||
B->Pointers->PointeeStorage.BS.Pointee = nullptr;
|
||||
B->Pointers = Next;
|
||||
}
|
||||
B->Pointers = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AllocationSites.clear();
|
||||
}
|
||||
|
||||
Block *DynamicAllocator::allocate(const Expr *Source, PrimType T,
|
||||
size_t NumElements, unsigned EvalID) {
|
||||
// Create a new descriptor for an array of the specified size and
|
||||
// element type.
|
||||
const Descriptor *D = allocateDescriptor(
|
||||
Source, T, Descriptor::InlineDescMD, NumElements, /*IsConst=*/false,
|
||||
/*IsTemporary=*/false, /*IsMutable=*/false);
|
||||
|
||||
return allocate(D, EvalID);
|
||||
}
|
||||
|
||||
Block *DynamicAllocator::allocate(const Descriptor *ElementDesc,
|
||||
size_t NumElements, unsigned EvalID) {
|
||||
// Create a new descriptor for an array of the specified size and
|
||||
// element type.
|
||||
const Descriptor *D = allocateDescriptor(
|
||||
ElementDesc->asExpr(), ElementDesc, Descriptor::InlineDescMD, NumElements,
|
||||
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false);
|
||||
return allocate(D, EvalID);
|
||||
}
|
||||
|
||||
Block *DynamicAllocator::allocate(const Descriptor *D, unsigned EvalID) {
|
||||
assert(D);
|
||||
assert(D->asExpr());
|
||||
|
||||
auto Memory =
|
||||
std::make_unique<std::byte[]>(sizeof(Block) + D->getAllocSize());
|
||||
auto *B = new (Memory.get()) Block(EvalID, D, /*isStatic=*/false);
|
||||
B->invokeCtor();
|
||||
|
||||
InlineDescriptor *ID = reinterpret_cast<InlineDescriptor *>(B->rawData());
|
||||
ID->Desc = D;
|
||||
ID->IsActive = true;
|
||||
ID->Offset = sizeof(InlineDescriptor);
|
||||
ID->IsBase = false;
|
||||
ID->IsFieldMutable = false;
|
||||
ID->IsConst = false;
|
||||
ID->IsInitialized = false;
|
||||
|
||||
B->IsDynamic = true;
|
||||
|
||||
if (auto It = AllocationSites.find(D->asExpr()); It != AllocationSites.end())
|
||||
It->second.Allocations.emplace_back(std::move(Memory));
|
||||
else
|
||||
AllocationSites.insert(
|
||||
{D->asExpr(), AllocationSite(std::move(Memory), D->isArray())});
|
||||
return B;
|
||||
}
|
||||
|
||||
bool DynamicAllocator::deallocate(const Expr *Source,
|
||||
const Block *BlockToDelete, InterpState &S) {
|
||||
auto It = AllocationSites.find(Source);
|
||||
if (It == AllocationSites.end())
|
||||
return false;
|
||||
|
||||
auto &Site = It->second;
|
||||
assert(Site.size() > 0);
|
||||
|
||||
// Find the Block to delete.
|
||||
auto AllocIt = llvm::find_if(Site.Allocations, [&](const Allocation &A) {
|
||||
const Block *B = reinterpret_cast<const Block *>(A.Memory.get());
|
||||
return BlockToDelete == B;
|
||||
});
|
||||
|
||||
assert(AllocIt != Site.Allocations.end());
|
||||
|
||||
Block *B = reinterpret_cast<Block *>(AllocIt->Memory.get());
|
||||
B->invokeDtor();
|
||||
|
||||
S.deallocate(B);
|
||||
Site.Allocations.erase(AllocIt);
|
||||
|
||||
if (Site.size() == 0)
|
||||
AllocationSites.erase(It);
|
||||
|
||||
return true;
|
||||
}
|
102
clang/lib/AST/Interp/DynamicAllocator.h
Normal file
102
clang/lib/AST/Interp/DynamicAllocator.h
Normal file
@ -0,0 +1,102 @@
|
||||
//==--------- DynamicAllocator.h - Dynamic allocations ------------*- 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H
|
||||
#define LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H
|
||||
|
||||
#include "Descriptor.h"
|
||||
#include "InterpBlock.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/iterator_range.h"
|
||||
#include "llvm/Support/Allocator.h"
|
||||
|
||||
namespace clang {
|
||||
class Expr;
|
||||
namespace interp {
|
||||
class Block;
|
||||
class InterpState;
|
||||
|
||||
/// Manages dynamic memory allocations done during bytecode interpretation.
|
||||
///
|
||||
/// We manage allocations as a map from their new-expression to a list
|
||||
/// of allocations. This is called an AllocationSite. For each site, we
|
||||
/// record whether it was allocated using new or new[], the
|
||||
/// IsArrayAllocation flag.
|
||||
///
|
||||
/// For all array allocations, we need to allocate new Descriptor instances,
|
||||
/// so the DynamicAllocator has a llvm::BumpPtrAllocator similar to Program.
|
||||
class DynamicAllocator final {
|
||||
struct Allocation {
|
||||
std::unique_ptr<std::byte[]> Memory;
|
||||
Allocation(std::unique_ptr<std::byte[]> Memory)
|
||||
: Memory(std::move(Memory)) {}
|
||||
};
|
||||
|
||||
struct AllocationSite {
|
||||
llvm::SmallVector<Allocation> Allocations;
|
||||
bool IsArrayAllocation = false;
|
||||
|
||||
AllocationSite(std::unique_ptr<std::byte[]> Memory, bool Array)
|
||||
: IsArrayAllocation(Array) {
|
||||
Allocations.push_back({std::move(Memory)});
|
||||
}
|
||||
|
||||
size_t size() const { return Allocations.size(); }
|
||||
};
|
||||
|
||||
public:
|
||||
DynamicAllocator() = default;
|
||||
~DynamicAllocator();
|
||||
|
||||
void cleanup();
|
||||
|
||||
unsigned getNumAllocations() const { return AllocationSites.size(); }
|
||||
|
||||
/// Allocate ONE element of the given descriptor.
|
||||
Block *allocate(const Descriptor *D, unsigned EvalID);
|
||||
/// Allocate \p NumElements primitive elements of the given type.
|
||||
Block *allocate(const Expr *Source, PrimType T, size_t NumElements,
|
||||
unsigned EvalID);
|
||||
/// Allocate \p NumElements elements of the given descriptor.
|
||||
Block *allocate(const Descriptor *D, size_t NumElements, unsigned EvalID);
|
||||
|
||||
/// Deallocate the given source+block combination.
|
||||
/// Returns \c true if anything has been deallocatd, \c false otherwise.
|
||||
bool deallocate(const Expr *Source, const Block *BlockToDelete,
|
||||
InterpState &S);
|
||||
|
||||
/// Checks whether the allocation done at the given source is an array
|
||||
/// allocation.
|
||||
bool isArrayAllocation(const Expr *Source) const {
|
||||
if (auto It = AllocationSites.find(Source); It != AllocationSites.end())
|
||||
return It->second.IsArrayAllocation;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Allocation site iterator.
|
||||
using const_virtual_iter =
|
||||
llvm::DenseMap<const Expr *, AllocationSite>::const_iterator;
|
||||
llvm::iterator_range<const_virtual_iter> allocation_sites() const {
|
||||
return llvm::make_range(AllocationSites.begin(), AllocationSites.end());
|
||||
}
|
||||
|
||||
private:
|
||||
llvm::DenseMap<const Expr *, AllocationSite> AllocationSites;
|
||||
|
||||
using PoolAllocTy = llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator>;
|
||||
PoolAllocTy DescAllocator;
|
||||
|
||||
/// Allocates a new descriptor.
|
||||
template <typename... Ts> Descriptor *allocateDescriptor(Ts &&...Args) {
|
||||
return new (DescAllocator) Descriptor(std::forward<Ts>(Args)...);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace interp
|
||||
} // namespace clang
|
||||
#endif
|
@ -133,9 +133,17 @@ bool EvalEmitter::fallthrough(const LabelTy &Label) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool checkReturnState(InterpState &S) {
|
||||
return S.maybeDiagnoseDanglingAllocations();
|
||||
}
|
||||
|
||||
template <PrimType OpType> bool EvalEmitter::emitRet(const SourceInfo &Info) {
|
||||
if (!isActive())
|
||||
return true;
|
||||
|
||||
if (!checkReturnState(S))
|
||||
return false;
|
||||
|
||||
using T = typename PrimConv<OpType>::T;
|
||||
EvalResult.setValue(S.Stk.pop<T>().toAPValue());
|
||||
return true;
|
||||
@ -147,9 +155,14 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(const SourceInfo &Info) {
|
||||
|
||||
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
||||
|
||||
if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info))
|
||||
return false;
|
||||
if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr))
|
||||
return false;
|
||||
|
||||
if (!checkReturnState(S))
|
||||
return false;
|
||||
|
||||
// Implicitly convert lvalue to rvalue, if requested.
|
||||
if (ConvertResultToRValue) {
|
||||
if (!Ptr.isZero() && !Ptr.isDereferencable())
|
||||
@ -174,12 +187,17 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(const SourceInfo &Info) {
|
||||
template <> bool EvalEmitter::emitRet<PT_FnPtr>(const SourceInfo &Info) {
|
||||
if (!isActive())
|
||||
return true;
|
||||
|
||||
if (!checkReturnState(S))
|
||||
return false;
|
||||
// Function pointers cannot be converted to rvalues.
|
||||
EvalResult.setFunctionPointer(S.Stk.pop<FunctionPointer>());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EvalEmitter::emitRetVoid(const SourceInfo &Info) {
|
||||
if (!checkReturnState(S))
|
||||
return false;
|
||||
EvalResult.setValid();
|
||||
return true;
|
||||
}
|
||||
@ -187,9 +205,14 @@ bool EvalEmitter::emitRetVoid(const SourceInfo &Info) {
|
||||
bool EvalEmitter::emitRetValue(const SourceInfo &Info) {
|
||||
const auto &Ptr = S.Stk.pop<Pointer>();
|
||||
|
||||
if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info))
|
||||
return false;
|
||||
if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr))
|
||||
return false;
|
||||
|
||||
if (!checkReturnState(S))
|
||||
return false;
|
||||
|
||||
if (std::optional<APValue> APV =
|
||||
Ptr.toRValue(S.getCtx(), EvalResult.getSourceType())) {
|
||||
EvalResult.setValue(*APV);
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "InterpState.h"
|
||||
#include "Record.h"
|
||||
#include "clang/AST/ExprCXX.h"
|
||||
#include "llvm/ADT/SetVector.h"
|
||||
|
||||
namespace clang {
|
||||
namespace interp {
|
||||
@ -152,6 +153,11 @@ bool EvaluationResult::checkFullyInitialized(InterpState &S,
|
||||
if (Ptr.isZero())
|
||||
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.dyn_cast<const Decl *>())
|
||||
InitLoc = cast<VarDecl>(D)->getAnyInitializer()->getExprLoc();
|
||||
@ -168,5 +174,71 @@ bool EvaluationResult::checkFullyInitialized(InterpState &S,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void collectBlocks(const Pointer &Ptr,
|
||||
llvm::SetVector<const Block *> &Blocks) {
|
||||
auto isUsefulPtr = [](const Pointer &P) -> bool {
|
||||
return P.isLive() && !P.isZero() && !P.isDummy() &&
|
||||
!P.isUnknownSizeArray() && !P.isOnePastEnd() && P.isBlockPointer();
|
||||
};
|
||||
|
||||
if (!isUsefulPtr(Ptr))
|
||||
return;
|
||||
|
||||
Blocks.insert(Ptr.block());
|
||||
|
||||
const Descriptor *Desc = Ptr.getFieldDesc();
|
||||
if (!Desc)
|
||||
return;
|
||||
|
||||
if (const Record *R = Desc->ElemRecord) {
|
||||
for (const Record::Field &F : R->fields()) {
|
||||
const Pointer &FieldPtr = Ptr.atField(F.Offset);
|
||||
assert(FieldPtr.block() == Ptr.block());
|
||||
collectBlocks(FieldPtr, Blocks);
|
||||
}
|
||||
} else if (Desc->isPrimitive() && Desc->getPrimType() == PT_Ptr) {
|
||||
const Pointer &Pointee = Ptr.deref<Pointer>();
|
||||
if (isUsefulPtr(Pointee) && !Blocks.contains(Pointee.block()))
|
||||
collectBlocks(Pointee, Blocks);
|
||||
|
||||
} else if (Desc->isPrimitiveArray() && Desc->getPrimType() == PT_Ptr) {
|
||||
for (unsigned I = 0; I != Desc->getNumElems(); ++I) {
|
||||
const Pointer &ElemPointee = Ptr.atIndex(I).deref<Pointer>();
|
||||
if (isUsefulPtr(ElemPointee) && !Blocks.contains(ElemPointee.block()))
|
||||
collectBlocks(ElemPointee, Blocks);
|
||||
}
|
||||
} else if (Desc->isCompositeArray()) {
|
||||
for (unsigned I = 0; I != Desc->getNumElems(); ++I) {
|
||||
const Pointer &ElemPtr = Ptr.atIndex(I).narrow();
|
||||
collectBlocks(ElemPtr, Blocks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool EvaluationResult::checkReturnValue(InterpState &S, const Context &Ctx,
|
||||
const Pointer &Ptr,
|
||||
const SourceInfo &Info) {
|
||||
// Collect all blocks that this pointer (transitively) points to and
|
||||
// return false if any of them is a dynamic block.
|
||||
llvm::SetVector<const Block *> Blocks;
|
||||
|
||||
collectBlocks(Ptr, Blocks);
|
||||
|
||||
for (const Block *B : Blocks) {
|
||||
if (B->isDynamic()) {
|
||||
assert(B->getDescriptor());
|
||||
assert(B->getDescriptor()->asExpr());
|
||||
|
||||
S.FFDiag(Info, diag::note_constexpr_dynamic_alloc)
|
||||
<< Ptr.getType()->isReferenceType() << !Ptr.isRoot();
|
||||
S.Note(B->getDescriptor()->asExpr()->getExprLoc(),
|
||||
diag::note_constexpr_dynamic_alloc_here);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace interp
|
||||
} // namespace clang
|
||||
|
@ -98,7 +98,12 @@ public:
|
||||
/// LValue and we can't read from it.
|
||||
std::optional<APValue> toRValue() const;
|
||||
|
||||
/// Check that all subobjects of the given pointer have been initialized.
|
||||
bool checkFullyInitialized(InterpState &S, const Pointer &Ptr) const;
|
||||
/// Check that none of the blocks the given pointer (transitively) points
|
||||
/// to are dynamically allocated.
|
||||
bool checkReturnValue(InterpState &S, const Context &Ctx, const Pointer &Ptr,
|
||||
const SourceInfo &Info);
|
||||
|
||||
QualType getSourceType() const {
|
||||
if (const auto *D =
|
||||
@ -113,6 +118,7 @@ public:
|
||||
void dump() const;
|
||||
|
||||
friend class EvalEmitter;
|
||||
friend class InterpState;
|
||||
};
|
||||
|
||||
} // namespace interp
|
||||
|
@ -717,6 +717,58 @@ bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC) {
|
||||
if (S.getLangOpts().CPlusPlus20)
|
||||
return true;
|
||||
|
||||
const SourceInfo &E = S.Current->getSource(OpPC);
|
||||
S.FFDiag(E, diag::note_constexpr_new);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray,
|
||||
bool DeleteIsArray, const Descriptor *D,
|
||||
const Expr *NewExpr) {
|
||||
if (NewWasArray == DeleteIsArray)
|
||||
return true;
|
||||
|
||||
QualType TypeToDiagnose;
|
||||
// We need to shuffle things around a bit here to get a better diagnostic,
|
||||
// because the expression we allocated the block for was of type int*,
|
||||
// but we want to get the array size right.
|
||||
if (D->isArray()) {
|
||||
QualType ElemQT = D->getType()->getPointeeType();
|
||||
TypeToDiagnose = S.getCtx().getConstantArrayType(
|
||||
ElemQT, APInt(64, static_cast<uint64_t>(D->getNumElems()), false),
|
||||
nullptr, ArraySizeModifier::Normal, 0);
|
||||
} else
|
||||
TypeToDiagnose = D->getType()->getPointeeType();
|
||||
|
||||
const SourceInfo &E = S.Current->getSource(OpPC);
|
||||
S.FFDiag(E, diag::note_constexpr_new_delete_mismatch)
|
||||
<< DeleteIsArray << 0 << TypeToDiagnose;
|
||||
S.Note(NewExpr->getExprLoc(), diag::note_constexpr_dynamic_alloc_here)
|
||||
<< NewExpr->getSourceRange();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
|
||||
const Pointer &Ptr) {
|
||||
if (Source && isa<CXXNewExpr>(Source))
|
||||
return true;
|
||||
|
||||
// Whatever this is, we didn't heap allocate it.
|
||||
const SourceInfo &Loc = S.Current->getSource(OpPC);
|
||||
S.FFDiag(Loc, diag::note_constexpr_delete_not_heap_alloc)
|
||||
<< Ptr.toDiagnosticString(S.getCtx());
|
||||
|
||||
if (Ptr.isTemporary())
|
||||
S.Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here);
|
||||
else
|
||||
S.Note(Ptr.getDeclLoc(), diag::note_declared_at);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// We aleady know the given DeclRefExpr is invalid for some reason,
|
||||
/// now figure out why and print appropriate diagnostics.
|
||||
bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR) {
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
#include "../ExprConstShared.h"
|
||||
#include "Boolean.h"
|
||||
#include "DynamicAllocator.h"
|
||||
#include "Floating.h"
|
||||
#include "Function.h"
|
||||
#include "FunctionPointer.h"
|
||||
@ -122,6 +123,20 @@ bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD);
|
||||
bool CheckNonNullArgs(InterpState &S, CodePtr OpPC, const Function *F,
|
||||
const CallExpr *CE, unsigned ArgSize);
|
||||
|
||||
/// Checks if dynamic memory allocation is available in the current
|
||||
/// language mode.
|
||||
bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC);
|
||||
|
||||
/// Diagnose mismatched new[]/delete or new/delete[] pairs.
|
||||
bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray,
|
||||
bool DeleteIsArray, const Descriptor *D,
|
||||
const Expr *NewExpr);
|
||||
|
||||
/// Check the source of the pointer passed to delete/delete[] has actually
|
||||
/// been heap allocated by us.
|
||||
bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
|
||||
const Pointer &Ptr);
|
||||
|
||||
/// Sets the given integral value to the pointer, which is of
|
||||
/// a std::{weak,partial,strong}_ordering type.
|
||||
bool SetThreeWayComparisonField(InterpState &S, CodePtr OpPC,
|
||||
@ -189,6 +204,30 @@ bool CheckDivRem(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename SizeT>
|
||||
bool CheckArraySize(InterpState &S, CodePtr OpPC, SizeT *NumElements,
|
||||
unsigned ElemSize, bool IsNoThrow) {
|
||||
// FIXME: Both the SizeT::from() as well as the
|
||||
// NumElements.toAPSInt() in this function are rather expensive.
|
||||
|
||||
// FIXME: GH63562
|
||||
// APValue stores array extents as unsigned,
|
||||
// so anything that is greater that unsigned would overflow when
|
||||
// constructing the array, we catch this here.
|
||||
SizeT MaxElements = SizeT::from(Descriptor::MaxArrayElemBytes / ElemSize);
|
||||
if (NumElements->toAPSInt().getActiveBits() >
|
||||
ConstantArrayType::getMaxSizeBits(S.getCtx()) ||
|
||||
*NumElements > MaxElements) {
|
||||
if (!IsNoThrow) {
|
||||
const SourceInfo &Loc = S.Current->getSource(OpPC);
|
||||
S.FFDiag(Loc, diag::note_constexpr_new_too_large)
|
||||
<< NumElements->toDiagnosticString(S.getCtx());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Checks if the result of a floating-point operation is valid
|
||||
/// in the current context.
|
||||
bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result,
|
||||
@ -2766,6 +2805,119 @@ inline bool CheckDecl(InterpState &S, CodePtr OpPC, const VarDecl *VD) {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool Alloc(InterpState &S, CodePtr OpPC, const Descriptor *Desc) {
|
||||
assert(Desc);
|
||||
|
||||
if (!CheckDynamicMemoryAllocation(S, OpPC))
|
||||
return false;
|
||||
|
||||
DynamicAllocator &Allocator = S.getAllocator();
|
||||
Block *B = Allocator.allocate(Desc, S.Ctx.getEvalID());
|
||||
assert(B);
|
||||
|
||||
S.Stk.push<Pointer>(B, sizeof(InlineDescriptor));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <PrimType Name, class SizeT = typename PrimConv<Name>::T>
|
||||
inline bool AllocN(InterpState &S, CodePtr OpPC, PrimType T, const Expr *Source,
|
||||
bool IsNoThrow) {
|
||||
if (!CheckDynamicMemoryAllocation(S, OpPC))
|
||||
return false;
|
||||
|
||||
SizeT NumElements = S.Stk.pop<SizeT>();
|
||||
if (!CheckArraySize(S, OpPC, &NumElements, primSize(T), IsNoThrow)) {
|
||||
if (!IsNoThrow)
|
||||
return false;
|
||||
|
||||
// If this failed and is nothrow, just return a null ptr.
|
||||
S.Stk.push<Pointer>(0, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
DynamicAllocator &Allocator = S.getAllocator();
|
||||
Block *B = Allocator.allocate(Source, T, static_cast<size_t>(NumElements),
|
||||
S.Ctx.getEvalID());
|
||||
assert(B);
|
||||
S.Stk.push<Pointer>(B, sizeof(InlineDescriptor));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <PrimType Name, class SizeT = typename PrimConv<Name>::T>
|
||||
inline bool AllocCN(InterpState &S, CodePtr OpPC, const Descriptor *ElementDesc,
|
||||
bool IsNoThrow) {
|
||||
if (!CheckDynamicMemoryAllocation(S, OpPC))
|
||||
return false;
|
||||
|
||||
SizeT NumElements = S.Stk.pop<SizeT>();
|
||||
if (!CheckArraySize(S, OpPC, &NumElements, ElementDesc->getSize(),
|
||||
IsNoThrow)) {
|
||||
if (!IsNoThrow)
|
||||
return false;
|
||||
|
||||
// If this failed and is nothrow, just return a null ptr.
|
||||
S.Stk.push<Pointer>(0, ElementDesc);
|
||||
return true;
|
||||
}
|
||||
|
||||
DynamicAllocator &Allocator = S.getAllocator();
|
||||
Block *B = Allocator.allocate(ElementDesc, static_cast<size_t>(NumElements),
|
||||
S.Ctx.getEvalID());
|
||||
assert(B);
|
||||
|
||||
S.Stk.push<Pointer>(B, sizeof(InlineDescriptor));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm) {
|
||||
|
||||
if (!CheckDynamicMemoryAllocation(S, OpPC))
|
||||
return false;
|
||||
|
||||
const Expr *Source = nullptr;
|
||||
const Block *BlockToDelete = nullptr;
|
||||
{
|
||||
// Extra scope for this so the block doesn't have this pointer
|
||||
// pointing to it when we destroy it.
|
||||
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
||||
|
||||
// Deleteing nullptr is always fine.
|
||||
if (Ptr.isZero())
|
||||
return true;
|
||||
|
||||
if (!Ptr.isRoot() || Ptr.isOnePastEnd() || Ptr.isArrayElement()) {
|
||||
const SourceInfo &Loc = S.Current->getSource(OpPC);
|
||||
S.FFDiag(Loc, diag::note_constexpr_delete_subobject)
|
||||
<< Ptr.toDiagnosticString(S.getCtx()) << Ptr.isOnePastEnd();
|
||||
return false;
|
||||
}
|
||||
|
||||
Source = Ptr.getDeclDesc()->asExpr();
|
||||
BlockToDelete = Ptr.block();
|
||||
|
||||
if (!CheckDeleteSource(S, OpPC, Source, Ptr))
|
||||
return false;
|
||||
}
|
||||
assert(Source);
|
||||
assert(BlockToDelete);
|
||||
|
||||
DynamicAllocator &Allocator = S.getAllocator();
|
||||
bool WasArrayAlloc = Allocator.isArrayAllocation(Source);
|
||||
const Descriptor *BlockDesc = BlockToDelete->getDescriptor();
|
||||
|
||||
if (!Allocator.deallocate(Source, BlockToDelete, S)) {
|
||||
// Nothing has been deallocated, this must be a double-delete.
|
||||
const SourceInfo &Loc = S.Current->getSource(OpPC);
|
||||
S.FFDiag(Loc, diag::note_constexpr_double_delete);
|
||||
return false;
|
||||
}
|
||||
return CheckNewDeleteForms(S, OpPC, WasArrayAlloc, DeleteIsArrayForm,
|
||||
BlockDesc, Source);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Read opcode arguments
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -52,14 +52,14 @@ public:
|
||||
Block(unsigned EvalID, const std::optional<unsigned> &DeclID,
|
||||
const Descriptor *Desc, bool IsStatic = false, bool IsExtern = false)
|
||||
: EvalID(EvalID), DeclID(DeclID), IsStatic(IsStatic), IsExtern(IsExtern),
|
||||
Desc(Desc) {
|
||||
IsDynamic(false), Desc(Desc) {
|
||||
assert(Desc);
|
||||
}
|
||||
|
||||
Block(unsigned EvalID, const Descriptor *Desc, bool IsStatic = false,
|
||||
bool IsExtern = false)
|
||||
: EvalID(EvalID), DeclID((unsigned)-1), IsStatic(IsStatic),
|
||||
IsExtern(IsExtern), Desc(Desc) {
|
||||
IsExtern(IsExtern), IsDynamic(false), Desc(Desc) {
|
||||
assert(Desc);
|
||||
}
|
||||
|
||||
@ -73,6 +73,7 @@ public:
|
||||
bool isStatic() const { return IsStatic; }
|
||||
/// Checks if the block is temporary.
|
||||
bool isTemporary() const { return Desc->IsTemporary; }
|
||||
bool isDynamic() const { return IsDynamic; }
|
||||
/// Returns the size of the block.
|
||||
unsigned getSize() const { return Desc->getAllocSize(); }
|
||||
/// Returns the declaration ID.
|
||||
@ -130,11 +131,12 @@ private:
|
||||
friend class Pointer;
|
||||
friend class DeadBlock;
|
||||
friend class InterpState;
|
||||
friend class DynamicAllocator;
|
||||
|
||||
Block(unsigned EvalID, const Descriptor *Desc, bool IsExtern, bool IsStatic,
|
||||
bool IsDead)
|
||||
: EvalID(EvalID), IsStatic(IsStatic), IsExtern(IsExtern), IsDead(true),
|
||||
Desc(Desc) {
|
||||
IsDynamic(false), Desc(Desc) {
|
||||
assert(Desc);
|
||||
}
|
||||
|
||||
@ -164,6 +166,9 @@ private:
|
||||
/// Flag indicating if the block contents have been initialized
|
||||
/// via invokeCtor.
|
||||
bool IsInitialized = false;
|
||||
/// Flag indicating if this block has been allocated via dynamic
|
||||
/// memory allocation (e.g. malloc).
|
||||
bool IsDynamic = false;
|
||||
/// Pointer to the stack slot descriptor.
|
||||
const Descriptor *Desc;
|
||||
};
|
||||
|
@ -41,6 +41,8 @@ void InterpState::cleanup() {
|
||||
P->PointeeStorage.BS.Pointee = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Alloc.cleanup();
|
||||
}
|
||||
|
||||
Frame *InterpState::getCurrentFrame() {
|
||||
@ -81,3 +83,18 @@ void InterpState::deallocate(Block *B) {
|
||||
B->invokeDtor();
|
||||
}
|
||||
}
|
||||
|
||||
bool InterpState::maybeDiagnoseDanglingAllocations() {
|
||||
bool NoAllocationsLeft = (Alloc.getNumAllocations() == 0);
|
||||
|
||||
if (!checkingPotentialConstantExpression()) {
|
||||
for (const auto &It : Alloc.allocation_sites()) {
|
||||
assert(It.second.size() > 0);
|
||||
|
||||
const Expr *Source = It.first;
|
||||
CCEDiag(Source->getExprLoc(), diag::note_constexpr_memory_leak)
|
||||
<< (It.second.size() - 1) << Source->getSourceRange();
|
||||
}
|
||||
}
|
||||
return NoAllocationsLeft;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
#define LLVM_CLANG_AST_INTERP_INTERPSTATE_H
|
||||
|
||||
#include "Context.h"
|
||||
#include "DynamicAllocator.h"
|
||||
#include "Function.h"
|
||||
#include "InterpFrame.h"
|
||||
#include "InterpStack.h"
|
||||
@ -102,13 +103,23 @@ public:
|
||||
|
||||
void setEvalLocation(SourceLocation SL) { this->EvalLocation = SL; }
|
||||
|
||||
DynamicAllocator &getAllocator() { return Alloc; }
|
||||
|
||||
/// Diagnose any dynamic allocations that haven't been freed yet.
|
||||
/// Will return \c false if there were any allocations to diagnose,
|
||||
/// \c true otherwise.
|
||||
bool maybeDiagnoseDanglingAllocations();
|
||||
|
||||
private:
|
||||
friend class EvaluationResult;
|
||||
/// AST Walker state.
|
||||
State &Parent;
|
||||
/// Dead block chain.
|
||||
DeadBlock *DeadBlocks = nullptr;
|
||||
/// Reference to the offset-source mapping.
|
||||
SourceMapper *M;
|
||||
/// Allocator used for dynamic allocations performed via the program.
|
||||
DynamicAllocator Alloc;
|
||||
|
||||
public:
|
||||
/// Reference to the module containing all bytecode.
|
||||
|
@ -58,12 +58,14 @@ def ArgRoundingMode : ArgType { let Name = "llvm::RoundingMode"; }
|
||||
def ArgLETD: ArgType { let Name = "const LifetimeExtendedTemporaryDecl *"; }
|
||||
def ArgCastKind : ArgType { let Name = "CastKind"; }
|
||||
def ArgCallExpr : ArgType { let Name = "const CallExpr *"; }
|
||||
def ArgExpr : ArgType { let Name = "const Expr *"; }
|
||||
def ArgOffsetOfExpr : ArgType { let Name = "const OffsetOfExpr *"; }
|
||||
def ArgDeclRef : ArgType { let Name = "const DeclRefExpr *"; }
|
||||
def ArgDesc : ArgType { let Name = "const Descriptor *"; }
|
||||
def ArgCCI : ArgType { let Name = "const ComparisonCategoryInfo *"; }
|
||||
def ArgDecl : ArgType { let Name = "const Decl*"; }
|
||||
def ArgVarDecl : ArgType { let Name = "const VarDecl*"; }
|
||||
def ArgDesc : ArgType { let Name = "const Descriptor *"; }
|
||||
def ArgPrimType : ArgType { let Name = "PrimType"; }
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Classes of types instructions operate on.
|
||||
@ -747,3 +749,23 @@ def GetMemberPtrDecl : Opcode;
|
||||
// Debugging.
|
||||
//===----------------------------------------------------------------------===//
|
||||
def Dump : Opcode;
|
||||
|
||||
def Alloc : Opcode {
|
||||
let Args = [ArgDesc];
|
||||
}
|
||||
|
||||
def AllocN : Opcode {
|
||||
let Types = [IntegerTypeClass];
|
||||
let Args = [ArgPrimType, ArgExpr, ArgBool];
|
||||
let HasGroup = 1;
|
||||
}
|
||||
|
||||
def AllocCN : Opcode {
|
||||
let Types = [IntegerTypeClass];
|
||||
let Args = [ArgDesc, ArgBool];
|
||||
let HasGroup = 1;
|
||||
}
|
||||
|
||||
def Free : Opcode {
|
||||
let Args = [ArgBool];
|
||||
}
|
||||
|
@ -649,6 +649,7 @@ private:
|
||||
friend class MemberPointer;
|
||||
friend class InterpState;
|
||||
friend struct InitMap;
|
||||
friend class DynamicAllocator;
|
||||
|
||||
Pointer(Block *Pointee, unsigned Base, uint64_t Offset);
|
||||
|
||||
|
490
clang/test/AST/Interp/new-delete.cpp
Normal file
490
clang/test/AST/Interp/new-delete.cpp
Normal file
@ -0,0 +1,490 @@
|
||||
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify=expected,both %s
|
||||
// RUN: %clang_cc1 -std=c++20 -fexperimental-new-constant-interpreter -verify=expected,both %s
|
||||
// RUN: %clang_cc1 -triple=i686-linux-gnu -std=c++20 -fexperimental-new-constant-interpreter -verify=expected,both %s
|
||||
// RUN: %clang_cc1 -verify=ref,both %s
|
||||
// RUN: %clang_cc1 -std=c++20 -verify=ref,both %s
|
||||
// RUN: %clang_cc1 -triple=i686-linux-gnu -std=c++20 -verify=ref,both %s
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
|
||||
constexpr int *Global = new int(12); // both-error {{must be initialized by a constant expression}} \
|
||||
// both-note {{pointer to heap-allocated object}} \
|
||||
// both-note {{heap allocation performed here}}
|
||||
|
||||
static_assert(*(new int(12)) == 12); // both-error {{not an integral constant expression}} \
|
||||
// both-note {{allocation performed here was not deallocated}}
|
||||
|
||||
|
||||
constexpr int a() {
|
||||
new int(12); // both-note {{allocation performed here was not deallocated}}
|
||||
return 1;
|
||||
}
|
||||
static_assert(a() == 1, ""); // both-error {{not an integral constant expression}}
|
||||
|
||||
constexpr int b() {
|
||||
int *i = new int(12);
|
||||
int m = *i;
|
||||
delete(i);
|
||||
return m;
|
||||
}
|
||||
static_assert(b() == 12, "");
|
||||
|
||||
|
||||
struct S {
|
||||
int a;
|
||||
int b;
|
||||
|
||||
static constexpr S *create(int a, int b) {
|
||||
return new S(a, b);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr int c() {
|
||||
S *s = new S(12, 13);
|
||||
|
||||
int i = s->a;
|
||||
delete s;
|
||||
|
||||
return i;
|
||||
}
|
||||
static_assert(c() == 12, "");
|
||||
|
||||
/// Dynamic allocation in function ::create(), freed in function d().
|
||||
constexpr int d() {
|
||||
S* s = S::create(12, 14);
|
||||
|
||||
int sum = s->a + s->b;
|
||||
delete s;
|
||||
return sum;
|
||||
}
|
||||
static_assert(d() == 26);
|
||||
|
||||
|
||||
/// Test we emit the right diagnostic for several allocations done on
|
||||
/// the same site.
|
||||
constexpr int loop() {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
int *a = new int[10]; // both-note {{not deallocated (along with 9 other memory leaks)}}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
static_assert(loop() == 1, ""); // both-error {{not an integral constant expression}}
|
||||
|
||||
/// No initializer.
|
||||
constexpr int noInit() {
|
||||
int *i = new int;
|
||||
delete i;
|
||||
return 0;
|
||||
}
|
||||
static_assert(noInit() == 0, "");
|
||||
|
||||
/// Try to delete a pointer that hasn't been heap allocated.
|
||||
constexpr int notHeapAllocated() { // both-error {{never produces a constant expression}}
|
||||
int A = 0; // both-note 2{{declared here}}
|
||||
delete &A; // ref-note 2{{delete of pointer '&A' that does not point to a heap-allocated object}} \
|
||||
// expected-note 2{{delete of pointer '&A' that does not point to a heap-allocated object}}
|
||||
|
||||
return 1;
|
||||
}
|
||||
static_assert(notHeapAllocated() == 1, ""); // both-error {{not an integral constant expression}} \
|
||||
// both-note {{in call to 'notHeapAllocated()'}}
|
||||
|
||||
consteval int deleteNull() {
|
||||
int *A = nullptr;
|
||||
delete A;
|
||||
return 1;
|
||||
}
|
||||
static_assert(deleteNull() == 1, "");
|
||||
|
||||
consteval int doubleDelete() { // both-error {{never produces a constant expression}}
|
||||
int *A = new int;
|
||||
delete A;
|
||||
delete A; // both-note 2{{delete of pointer that has already been deleted}}
|
||||
return 1;
|
||||
}
|
||||
static_assert(doubleDelete() == 1); // both-error {{not an integral constant expression}} \
|
||||
// both-note {{in call to 'doubleDelete()'}}
|
||||
|
||||
constexpr int AutoArray() {
|
||||
auto array = new int[]{0, 1, 2, 3};
|
||||
int ret = array[3];
|
||||
delete [] array;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static_assert(AutoArray() == 3);
|
||||
|
||||
#if 0
|
||||
consteval int largeArray1(bool b) {
|
||||
if (b) {
|
||||
int *a = new int[1ull<<32]; // both-note {{cannot allocate array; evaluated array bound 4294967296 is too large}}
|
||||
delete[] a;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
static_assert(largeArray1(false) == 1, "");
|
||||
static_assert(largeArray1(true) == 1, ""); // both-error {{not an integral constant expression}} \
|
||||
// both-note {{in call to 'largeArray1(true)'}}
|
||||
|
||||
consteval int largeArray2(bool b) {
|
||||
if (b) {
|
||||
S *a = new S[1ull<<32]; // both-note {{cannot allocate array; evaluated array bound 4294967296 is too large}}
|
||||
delete[] a;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
static_assert(largeArray2(false) == 1, "");
|
||||
static_assert(largeArray2(true) == 1, ""); // both-error {{not an integral constant expression}} \
|
||||
// both-note {{in call to 'largeArray2(true)'}}
|
||||
#endif
|
||||
namespace Arrays {
|
||||
constexpr int d() {
|
||||
int *Arr = new int[12];
|
||||
|
||||
Arr[0] = 1;
|
||||
Arr[1] = 5;
|
||||
|
||||
int sum = Arr[0] + Arr[1];
|
||||
delete[] Arr;
|
||||
return sum;
|
||||
}
|
||||
static_assert(d() == 6);
|
||||
|
||||
|
||||
constexpr int mismatch1() { // both-error {{never produces a constant expression}}
|
||||
int *i = new int(12); // both-note {{allocated with 'new' here}} \
|
||||
// both-note 2{{heap allocation performed here}}
|
||||
delete[] i; // both-warning {{'delete[]' applied to a pointer that was allocated with 'new'}} \
|
||||
// both-note 2{{array delete used to delete pointer to non-array object of type 'int'}}
|
||||
return 6;
|
||||
}
|
||||
static_assert(mismatch1() == 6); // both-error {{not an integral constant expression}} \
|
||||
// both-note {{in call to 'mismatch1()'}}
|
||||
|
||||
constexpr int mismatch2() { // both-error {{never produces a constant expression}}
|
||||
int *i = new int[12]; // both-note {{allocated with 'new[]' here}} \
|
||||
// both-note 2{{heap allocation performed here}}
|
||||
delete i; // both-warning {{'delete' applied to a pointer that was allocated with 'new[]'}} \
|
||||
// both-note 2{{non-array delete used to delete pointer to array object of type 'int[12]'}}
|
||||
return 6;
|
||||
}
|
||||
static_assert(mismatch2() == 6); // both-error {{not an integral constant expression}} \
|
||||
// both-note {{in call to 'mismatch2()'}}
|
||||
/// Array of composite elements.
|
||||
constexpr int foo() {
|
||||
S *ss = new S[12];
|
||||
|
||||
ss[0].a = 12;
|
||||
|
||||
int m = ss[0].a;
|
||||
|
||||
delete[] ss;
|
||||
return m;
|
||||
}
|
||||
static_assert(foo() == 12);
|
||||
|
||||
|
||||
|
||||
constexpr int ArrayInit() {
|
||||
auto array = new int[4]{0, 1, 2, 3};
|
||||
int ret = array[0];
|
||||
delete [] array;
|
||||
return ret;
|
||||
}
|
||||
static_assert(ArrayInit() == 0, "");
|
||||
|
||||
struct S {
|
||||
float F;
|
||||
};
|
||||
constexpr float ArrayInit2() {
|
||||
auto array = new S[4]{};
|
||||
float ret = array[0].F;
|
||||
delete [] array;
|
||||
return ret;
|
||||
}
|
||||
static_assert(ArrayInit2() == 0.0f, "");
|
||||
}
|
||||
|
||||
namespace std {
|
||||
struct type_info;
|
||||
struct destroying_delete_t {
|
||||
explicit destroying_delete_t() = default;
|
||||
} inline constexpr destroying_delete{};
|
||||
struct nothrow_t {
|
||||
explicit nothrow_t() = default;
|
||||
} inline constexpr nothrow{};
|
||||
using size_t = decltype(sizeof(0));
|
||||
enum class align_val_t : size_t {};
|
||||
};
|
||||
|
||||
[[nodiscard]] void *operator new(std::size_t, const std::nothrow_t&) noexcept;
|
||||
[[nodiscard]] void *operator new(std::size_t, std::align_val_t, const std::nothrow_t&) noexcept;
|
||||
[[nodiscard]] void *operator new[](std::size_t, const std::nothrow_t&) noexcept;
|
||||
[[nodiscard]] void *operator new[](std::size_t, std::align_val_t, const std::nothrow_t&) noexcept;
|
||||
[[nodiscard]] void *operator new[](std::size_t, std::align_val_t);
|
||||
void operator delete(void*, const std::nothrow_t&) noexcept;
|
||||
void operator delete(void*, std::align_val_t, const std::nothrow_t&) noexcept;
|
||||
void operator delete[](void*, const std::nothrow_t&) noexcept;
|
||||
void operator delete[](void*, std::align_val_t, const std::nothrow_t&) noexcept;
|
||||
|
||||
struct placement_new_arg {};
|
||||
void *operator new(std::size_t, placement_new_arg);
|
||||
void operator delete(void*, placement_new_arg);
|
||||
|
||||
|
||||
constexpr void *operator new(std::size_t, void *p) { return p; }
|
||||
namespace std {
|
||||
template<typename T> constexpr T *construct(T *p) { return new (p) T; }
|
||||
template<typename T> constexpr void destroy(T *p) { p->~T(); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// FIXME: The new interpreter produces the wrong diagnostic.
|
||||
namespace PlacementNew {
|
||||
constexpr int foo() { // both-error {{never produces a constant expression}}
|
||||
char c[sizeof(int)];
|
||||
new (c) int{12}; // ref-note {{call to placement 'operator new'}} \
|
||||
// expected-note {{subexpression not valid in a constant expression}}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace NowThrowNew {
|
||||
constexpr bool erroneous_array_bound_nothrow(long long n) {
|
||||
int *p = new (std::nothrow) int[n];
|
||||
bool result = p != nullptr;
|
||||
delete[] p;
|
||||
return result;
|
||||
}
|
||||
static_assert(erroneous_array_bound_nothrow(3));
|
||||
static_assert(erroneous_array_bound_nothrow(0));
|
||||
static_assert(erroneous_array_bound_nothrow(-1) == 0);
|
||||
static_assert(!erroneous_array_bound_nothrow(1LL << 62));
|
||||
|
||||
struct S { int a; };
|
||||
constexpr bool erroneous_array_bound_nothrow2(long long n) {
|
||||
S *p = new (std::nothrow) S[n];
|
||||
bool result = p != nullptr;
|
||||
delete[] p;
|
||||
return result;
|
||||
}
|
||||
/// This needs support for CXXConstrucExprs with non-constant array sizes.
|
||||
static_assert(erroneous_array_bound_nothrow2(3)); // expected-error {{not an integral constant expression}}
|
||||
static_assert(erroneous_array_bound_nothrow2(0));// expected-error {{not an integral constant expression}}
|
||||
static_assert(erroneous_array_bound_nothrow2(-1) == 0);// expected-error {{not an integral constant expression}}
|
||||
static_assert(!erroneous_array_bound_nothrow2(1LL << 62));// expected-error {{not an integral constant expression}}
|
||||
|
||||
constexpr bool evaluate_nothrow_arg() {
|
||||
bool ok = false;
|
||||
delete new ((ok = true, std::nothrow)) int;
|
||||
return ok;
|
||||
}
|
||||
static_assert(evaluate_nothrow_arg());
|
||||
}
|
||||
|
||||
namespace placement_new_delete {
|
||||
struct ClassSpecificNew {
|
||||
void *operator new(std::size_t);
|
||||
};
|
||||
struct ClassSpecificDelete {
|
||||
void operator delete(void*);
|
||||
};
|
||||
struct DestroyingDelete {
|
||||
void operator delete(DestroyingDelete*, std::destroying_delete_t);
|
||||
};
|
||||
struct alignas(64) Overaligned {};
|
||||
|
||||
constexpr bool ok() {
|
||||
delete new Overaligned;
|
||||
delete ::new ClassSpecificNew;
|
||||
::delete new ClassSpecificDelete;
|
||||
::delete new DestroyingDelete;
|
||||
return true;
|
||||
}
|
||||
static_assert(ok());
|
||||
|
||||
/// FIXME: Diagnosting placement new.
|
||||
constexpr bool bad(int which) {
|
||||
switch (which) {
|
||||
case 0:
|
||||
delete new (placement_new_arg{}) int; // ref-note {{call to placement 'operator new'}} \
|
||||
// expected-note {{subexpression not valid in a constant expression}}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
delete new ClassSpecificNew; // ref-note {{call to class-specific 'operator new'}}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
delete new ClassSpecificDelete; // ref-note {{call to class-specific 'operator delete'}}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
delete new DestroyingDelete; // ref-note {{call to class-specific 'operator delete'}}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
// FIXME: This technically follows the standard's rules, but it seems
|
||||
// unreasonable to expect implementations to support this.
|
||||
delete new (std::align_val_t{64}) Overaligned; // ref-note {{placement new expression is not yet supported}} \
|
||||
// expected-note {{subexpression not valid in a constant expression}}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
static_assert(bad(0)); // both-error {{constant expression}} \
|
||||
// both-note {{in call}}
|
||||
static_assert(bad(1)); // ref-error {{constant expression}} ref-note {{in call}}
|
||||
static_assert(bad(2)); // ref-error {{constant expression}} ref-note {{in call}}
|
||||
static_assert(bad(3)); // ref-error {{constant expression}} ref-note {{in call}}
|
||||
static_assert(bad(4)); // both-error {{constant expression}} \
|
||||
// both-note {{in call}}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
namespace delete_random_things {
|
||||
static_assert((delete new int, true));
|
||||
static_assert((delete (int*)0, true));
|
||||
int n; // both-note {{declared here}}
|
||||
static_assert((delete &n, true)); // both-error {{}} \
|
||||
// both-note {{delete of pointer '&n' that does not point to a heap-allocated object}}
|
||||
struct A { int n; };
|
||||
static_assert((delete &(new A)->n, true)); // both-error {{}} \
|
||||
// both-note {{delete of pointer to subobject }}
|
||||
static_assert((delete (new int + 1), true)); // both-error {{}} \
|
||||
// ref-note {{delete of pointer '&{*new int#0} + 1' that does not point to complete object}} \
|
||||
// expected-note {{delete of pointer '&new int + 1' that does not point to complete object}}
|
||||
static_assert((delete[] (new int[3] + 1), true)); // both-error {{}} \
|
||||
// both-note {{delete of pointer to subobject}}
|
||||
static_assert((delete &(int&)(int&&)0, true)); // both-error {{}} \
|
||||
// both-note {{delete of pointer '&0' that does not point to a heap-allocated object}} \
|
||||
// both-note {{temporary created here}}
|
||||
}
|
||||
|
||||
namespace value_dependent_delete {
|
||||
template<typename T> void f(T *p) {
|
||||
int arr[(delete p, 0)];
|
||||
}
|
||||
}
|
||||
|
||||
namespace memory_leaks {
|
||||
static_assert(*new bool(true)); // both-error {{}} both-note {{allocation performed here was not deallocated}}
|
||||
|
||||
constexpr bool *f() { return new bool(true); } // both-note {{allocation performed here was not deallocated}}
|
||||
static_assert(*f()); // both-error {{}}
|
||||
|
||||
struct UP {
|
||||
bool *p;
|
||||
constexpr ~UP() { delete p; }
|
||||
constexpr bool &operator*() { return *p; }
|
||||
};
|
||||
constexpr UP g() { return {new bool(true)}; }
|
||||
static_assert(*g()); // ok
|
||||
|
||||
constexpr bool h(UP p) { return *p; }
|
||||
static_assert(h({new bool(true)})); // ok
|
||||
}
|
||||
|
||||
/// From test/SemaCXX/cxx2a-consteval.cpp
|
||||
|
||||
namespace std {
|
||||
template <typename T> struct remove_reference { using type = T; };
|
||||
template <typename T> struct remove_reference<T &> { using type = T; };
|
||||
template <typename T> struct remove_reference<T &&> { using type = T; };
|
||||
template <typename T>
|
||||
constexpr typename std::remove_reference<T>::type&& move(T &&t) noexcept {
|
||||
return static_cast<typename std::remove_reference<T>::type &&>(t);
|
||||
}
|
||||
}
|
||||
|
||||
namespace cxx2a {
|
||||
struct A {
|
||||
int* p = new int(42); // both-note 7{{heap allocation performed here}}
|
||||
consteval int ret_i() const { return p ? *p : 0; }
|
||||
consteval A ret_a() const { return A{}; }
|
||||
constexpr ~A() { delete p; }
|
||||
};
|
||||
|
||||
consteval int by_value_a(A a) { return a.ret_i(); }
|
||||
|
||||
consteval int const_a_ref(const A &a) {
|
||||
return a.ret_i();
|
||||
}
|
||||
|
||||
consteval int rvalue_ref(const A &&a) {
|
||||
return a.ret_i();
|
||||
}
|
||||
|
||||
consteval const A &to_lvalue_ref(const A &&a) {
|
||||
return a;
|
||||
}
|
||||
|
||||
void test() {
|
||||
constexpr A a{ nullptr };
|
||||
{ int k = A().ret_i(); }
|
||||
|
||||
{ A k = A().ret_a(); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
|
||||
// both-note {{heap-allocated object is not a constant expression}}
|
||||
{ A k = to_lvalue_ref(A()); } // both-error {{'cxx2a::to_lvalue_ref' is not a constant expression}} \
|
||||
// both-note {{reference to temporary is not a constant expression}} \
|
||||
// both-note {{temporary created here}}
|
||||
{ A k = to_lvalue_ref(A().ret_a()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
|
||||
// both-note {{heap-allocated object is not a constant expression}} \
|
||||
// both-error {{'cxx2a::to_lvalue_ref' is not a constant expression}} \
|
||||
// both-note {{reference to temporary is not a constant expression}} \
|
||||
// both-note {{temporary created here}}
|
||||
{ int k = A().ret_a().ret_i(); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
|
||||
// both-note {{heap-allocated object is not a constant expression}}
|
||||
{ int k = by_value_a(A()); }
|
||||
{ int k = const_a_ref(A()); }
|
||||
{ int k = const_a_ref(a); }
|
||||
{ int k = rvalue_ref(A()); }
|
||||
{ int k = rvalue_ref(std::move(a)); }
|
||||
{ int k = const_a_ref(A().ret_a()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
|
||||
// both-note {{is not a constant expression}}
|
||||
{ int k = const_a_ref(to_lvalue_ref(A().ret_a())); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
|
||||
// both-note {{is not a constant expression}}
|
||||
{ int k = const_a_ref(to_lvalue_ref(std::move(a))); }
|
||||
{ int k = by_value_a(A().ret_a()); }
|
||||
{ int k = by_value_a(to_lvalue_ref(static_cast<const A&&>(a))); }
|
||||
{ int k = (A().ret_a(), A().ret_i()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
|
||||
// both-note {{is not a constant expression}} \
|
||||
// both-warning {{left operand of comma operator has no effect}}
|
||||
{ int k = (const_a_ref(A().ret_a()), A().ret_i()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \
|
||||
// both-note {{is not a constant expression}} \
|
||||
// both-warning {{left operand of comma operator has no effect}}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int *const &p = new int; // both-error {{must be initialized by a constant expression}} \
|
||||
// both-note {{pointer to heap-allocated object}} \
|
||||
// both-note {{allocation performed here}}
|
||||
|
||||
constexpr const int *A[] = {nullptr, nullptr, new int{12}}; // both-error {{must be initialized by a constant expression}} \
|
||||
// both-note {{pointer to heap-allocated object}} \
|
||||
// both-note {{allocation performed here}}
|
||||
|
||||
struct Sp {
|
||||
const int *p;
|
||||
};
|
||||
constexpr Sp ss[] = {Sp{new int{154}}}; // both-error {{must be initialized by a constant expression}} \
|
||||
// both-note {{pointer to heap-allocated object}} \
|
||||
// both-note {{allocation performed here}}
|
||||
|
||||
|
||||
|
||||
|
||||
#else
|
||||
/// Make sure we reject this prior to C++20
|
||||
constexpr int a() { // both-error {{never produces a constant expression}}
|
||||
delete new int(12); // both-note 2{{dynamic memory allocation is not permitted in constant expressions until C++20}}
|
||||
return 1;
|
||||
}
|
||||
static_assert(a() == 1, ""); // both-error {{not an integral constant expression}} \
|
||||
// both-note {{in call to 'a()'}}
|
||||
#endif
|
@ -1,4 +1,4 @@
|
||||
// RUN: %clang_cc1 -x objective-c -Wno-return-type -fblocks -fms-extensions -rewrite-objc %s -o %t-rw.cpp
|
||||
// RUN: %clang_cc1 -x objective-c -Wno-return-type -fblocks -fms-extensions -rewrite-objc %s -o %t-rw.cpp -fexperimental-new-constant-interpreter
|
||||
// RUN: %clang_cc1 -fsyntax-only -fcxx-exceptions -fexceptions -Wno-address-of-temporary -D"id=void*" -D"SEL=void*" -D"__declspec(X)=" %t-rw.cpp
|
||||
|
||||
void foo(id arg);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Test without PCH
|
||||
// RUN: %clang_cc1 -fsyntax-only -include %S/delete-mismatch.h -fdiagnostics-parseable-fixits -std=c++11 %s 2>&1 | FileCheck %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -include %S/delete-mismatch.h -fdiagnostics-parseable-fixits -std=c++11 %s 2>&1 -fexperimental-new-constant-interpreter | FileCheck %s
|
||||
|
||||
// Test with PCH
|
||||
// RUN: %clang_cc1 -x c++-header -std=c++11 -emit-pch -o %t %S/delete-mismatch.h
|
||||
|
@ -6,6 +6,14 @@
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++23
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=expected,since-cxx26,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++2c
|
||||
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++98 -fexperimental-new-constant-interpreter -DNEW_INTERP
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++11 -fexperimental-new-constant-interpreter -DNEW_INTERP
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++14 -fexperimental-new-constant-interpreter -DNEW_INTERP
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++17 -fexperimental-new-constant-interpreter -DNEW_INTERP
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++20 -fexperimental-new-constant-interpreter -DNEW_INTERP
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++23 -fexperimental-new-constant-interpreter -DNEW_INTERP
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify=expected,since-cxx26,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++2c -fexperimental-new-constant-interpreter -DNEW_INTERP
|
||||
|
||||
// FIXME Location is (frontend)
|
||||
// cxx17-note@*:* {{candidate function not viable: requires 2 arguments, but 3 were provided}}
|
||||
|
||||
@ -653,10 +661,22 @@ int *fail = dependent_array_size("hello"); // expected-note {{instantiation of}}
|
||||
// FIXME: Our behavior here is incredibly inconsistent. GCC allows
|
||||
// constant-folding in array bounds in new-expressions.
|
||||
int (*const_fold)[12] = new int[3][&const_fold + 12 - &const_fold];
|
||||
#if __cplusplus >= 201402L
|
||||
#if __cplusplus >= 201402L && !defined(NEW_INTERP)
|
||||
// expected-error@-2 {{array size is not a constant expression}}
|
||||
// expected-note@-3 {{cannot refer to element 12 of non-array}}
|
||||
#elif __cplusplus < 201103L
|
||||
#elif __cplusplus < 201103L && !defined(NEW_INTERP)
|
||||
// expected-error@-5 {{cannot allocate object of variably modified type}}
|
||||
// expected-warning@-6 {{variable length arrays in C++ are a Clang extension}}
|
||||
#endif
|
||||
#ifdef NEW_INTERP
|
||||
#if __cplusplus >= 201402L
|
||||
// expected-error@-10 {{array size is not a constant expression}}
|
||||
// expected-note@-11 {{cannot refer to element 12 of non-array}}
|
||||
#elif __cplusplus >= 201103L
|
||||
// expected-error@-13 {{only the first dimension of an allocated array may have dynamic size}}
|
||||
// expected-note@-14 {{cannot refer to element 12 of non-array}}
|
||||
#else
|
||||
// expected-error@-16 {{only the first dimension of an allocated array may have dynamic size}}
|
||||
// expected-note@-17 {{cannot refer to element 12 of non-array}}
|
||||
#endif
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user