Skip to content

Commit 471d7a1

Browse files
committed
[clang][Interp] Implement dynamic memory allocation handling
1 parent c70fa55 commit 471d7a1

19 files changed

+933
-13
lines changed

clang/lib/AST/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ add_clang_library(clangAST
7676
Interp/InterpBuiltin.cpp
7777
Interp/Floating.cpp
7878
Interp/EvaluationResult.cpp
79+
Interp/DynamicAllocator.cpp
7980
Interp/Interp.cpp
8081
Interp/InterpBlock.cpp
8182
Interp/InterpFrame.cpp

clang/lib/AST/Interp/ByteCodeExprGen.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2471,6 +2471,85 @@ bool ByteCodeExprGen<Emitter>::VisitCXXInheritedCtorInitExpr(
24712471
return this->emitCall(F, 0, E);
24722472
}
24732473

2474+
template <class Emitter>
2475+
bool ByteCodeExprGen<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
2476+
assert(classifyPrim(E->getType()) == PT_Ptr);
2477+
const Expr *Init = E->getInitializer();
2478+
QualType ElementType = E->getAllocatedType();
2479+
std::optional<PrimType> ElemT = classify(ElementType);
2480+
2481+
const Descriptor *Desc;
2482+
if (ElemT) {
2483+
if (E->isArray())
2484+
Desc = nullptr; // We're not going to use it in this case.
2485+
else
2486+
Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
2487+
/*IsConst=*/false, /*IsTemporary=*/false,
2488+
/*IsMutable=*/false);
2489+
} else {
2490+
Desc = P.createDescriptor(
2491+
E, ElementType.getTypePtr(),
2492+
E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
2493+
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
2494+
}
2495+
2496+
if (E->isArray()) {
2497+
std::optional<const Expr *> ArraySizeExpr = E->getArraySize();
2498+
if (!ArraySizeExpr)
2499+
return false;
2500+
assert(ArraySizeExpr);
2501+
PrimType SizeT = classifyPrim((*ArraySizeExpr)->getType());
2502+
2503+
if (!this->visit(*ArraySizeExpr))
2504+
return false;
2505+
2506+
if (ElemT) {
2507+
// N primitive elements.
2508+
if (!this->emitAllocN(SizeT, *ElemT, E, E))
2509+
return false;
2510+
} else {
2511+
// N Composite elements.
2512+
if (!this->emitAllocCN(SizeT, Desc, E))
2513+
return false;
2514+
}
2515+
2516+
} else {
2517+
// Allocate just one element.
2518+
if (!this->emitAlloc(Desc, E))
2519+
return false;
2520+
2521+
if (Init) {
2522+
if (ElemT) {
2523+
if (!this->visit(Init))
2524+
return false;
2525+
2526+
if (!this->emitInit(*ElemT, E))
2527+
return false;
2528+
} else {
2529+
// Composite.
2530+
if (!this->visitInitializer(Init))
2531+
return false;
2532+
}
2533+
}
2534+
}
2535+
2536+
if (DiscardResult)
2537+
return this->emitPopPtr(E);
2538+
2539+
return true;
2540+
}
2541+
2542+
template <class Emitter>
2543+
bool ByteCodeExprGen<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
2544+
const Expr *Arg = E->getArgument();
2545+
2546+
// Arg must be an lvalue.
2547+
if (!this->visit(Arg))
2548+
return false;
2549+
2550+
return this->emitFree(E->isArrayForm(), E);
2551+
}
2552+
24742553
template <class Emitter>
24752554
bool ByteCodeExprGen<Emitter>::VisitExpressionTraitExpr(
24762555
const ExpressionTraitExpr *E) {

clang/lib/AST/Interp/ByteCodeExprGen.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>,
128128
bool VisitConvertVectorExpr(const ConvertVectorExpr *E);
129129
bool VisitShuffleVectorExpr(const ShuffleVectorExpr *E);
130130
bool VisitObjCBoxedExpr(const ObjCBoxedExpr *E);
131+
bool VisitCXXNewExpr(const CXXNewExpr *E);
132+
bool VisitCXXDeleteExpr(const CXXDeleteExpr *E);
131133

132134
protected:
133135
bool visitExpr(const Expr *E) override;
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//==-------- DynamicAllocator.cpp - Dynamic allocations ----------*- C++ -*-==//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "DynamicAllocator.h"
10+
#include "InterpBlock.h"
11+
#include "InterpState.h"
12+
13+
using namespace clang;
14+
using namespace clang::interp;
15+
16+
DynamicAllocator::~DynamicAllocator() { cleanup(); }
17+
18+
void DynamicAllocator::cleanup() {
19+
// Invoke destructors of all the blocks and as a last restort,
20+
// reset all the pointers pointing to them to null pointees.
21+
// This should never show up in diagnostics, but it's necessary
22+
// for us to not cause use-after-free problems.
23+
for (auto &Iter : AllocationSites) {
24+
auto &AllocSite = Iter.second;
25+
for (auto &Alloc : AllocSite.Allocations) {
26+
Block *B = reinterpret_cast<Block *>(Alloc.Memory.get());
27+
B->invokeDtor();
28+
if (B->hasPointers()) {
29+
while (B->Pointers) {
30+
Pointer *Next = B->Pointers->Next;
31+
B->Pointers->PointeeStorage.BS.Pointee = nullptr;
32+
B->Pointers = Next;
33+
}
34+
B->Pointers = nullptr;
35+
}
36+
}
37+
}
38+
39+
AllocationSites.clear();
40+
}
41+
42+
Block *DynamicAllocator::allocate(const Expr *Source, PrimType T,
43+
size_t NumElements) {
44+
assert(NumElements > 0);
45+
// Create a new descriptor for an array of the specified size and
46+
// element type.
47+
const Descriptor *D = allocateDescriptor(
48+
Source, T, Descriptor::InlineDescMD, NumElements, /*IsConst=*/false,
49+
/*IsTemporary=*/false, /*IsMutable=*/false);
50+
return allocate(D);
51+
}
52+
53+
Block *DynamicAllocator::allocate(const Descriptor *ElementDesc,
54+
size_t NumElements) {
55+
assert(NumElements > 0);
56+
// Create a new descriptor for an array of the specified size and
57+
// element type.
58+
const Descriptor *D = allocateDescriptor(
59+
ElementDesc->asExpr(), ElementDesc, Descriptor::InlineDescMD, NumElements,
60+
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false);
61+
return allocate(D);
62+
}
63+
64+
Block *DynamicAllocator::allocate(const Descriptor *D) {
65+
assert(D->asExpr());
66+
67+
auto Memory =
68+
std::make_unique<std::byte[]>(sizeof(Block) + D->getAllocSize());
69+
auto *B = new (Memory.get()) Block(D, /*isStatic=*/false);
70+
B->invokeCtor();
71+
72+
InlineDescriptor *ID = reinterpret_cast<InlineDescriptor *>(B->rawData());
73+
ID->Desc = D;
74+
ID->IsActive = true;
75+
ID->Offset = sizeof(InlineDescriptor);
76+
ID->IsBase = false;
77+
ID->IsFieldMutable = false;
78+
ID->IsConst = false;
79+
ID->IsInitialized = false;
80+
assert(ID->Desc);
81+
82+
B->IsDynamic = true;
83+
84+
if (auto It = AllocationSites.find(D->asExpr()); It != AllocationSites.end())
85+
It->second.Allocations.emplace_back(std::move(Memory));
86+
else
87+
AllocationSites.insert(
88+
{D->asExpr(), AllocationSite(std::move(Memory), D->isArray())});
89+
return B;
90+
}
91+
92+
bool DynamicAllocator::deallocate(const Expr *Source,
93+
const Block *BlockToDelete, InterpState &S) {
94+
auto It = AllocationSites.find(Source);
95+
if (It == AllocationSites.end())
96+
return false;
97+
98+
auto &Site = It->second;
99+
assert(Site.size() > 0);
100+
101+
// Find the Block to delete.
102+
auto AllocIt = llvm::find_if(Site.Allocations, [&](const Allocation &A) {
103+
const Block *B = reinterpret_cast<const Block *>(A.Memory.get());
104+
return BlockToDelete == B;
105+
});
106+
107+
assert(AllocIt != Site.Allocations.end());
108+
109+
Block *B = reinterpret_cast<Block *>(AllocIt->Memory.get());
110+
B->invokeDtor();
111+
112+
S.deallocate(B);
113+
Site.Allocations.erase(AllocIt);
114+
115+
if (Site.size() == 0)
116+
AllocationSites.erase(It);
117+
118+
return true;
119+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//==--------- DynamicAllocator.h - Dynamic allocations ------------*- C++ -*-=//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H
10+
#define LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H
11+
12+
#include "Descriptor.h"
13+
#include "InterpBlock.h"
14+
#include "llvm/ADT/SmallVector.h"
15+
#include "llvm/ADT/iterator_range.h"
16+
#include "llvm/Support/Allocator.h"
17+
18+
namespace clang {
19+
class Expr;
20+
namespace interp {
21+
class Block;
22+
class InterpState;
23+
24+
/// Manages dynamic memory allocations done during bytecode interpretation.
25+
///
26+
/// We manage allocations as a map from their new-expression to a list
27+
/// of allocations. This is called an AllocationSite. For each site, we
28+
/// record whether it was allocated using new or new[], the
29+
/// IsArrayAllocation flag.
30+
///
31+
/// For all array allocations, we need to allocat new Descriptor instances,
32+
/// so the DynamicAllocator has a llvm::BumpPtrAllocator similar to Program.
33+
class DynamicAllocator final {
34+
struct Allocation {
35+
std::unique_ptr<std::byte[]> Memory;
36+
Allocation(std::unique_ptr<std::byte[]> Memory)
37+
: Memory(std::move(Memory)) {}
38+
};
39+
40+
struct AllocationSite {
41+
llvm::SmallVector<Allocation> Allocations;
42+
bool IsArrayAllocation = false;
43+
44+
AllocationSite(std::unique_ptr<std::byte[]> Memory, bool Array)
45+
: IsArrayAllocation(Array) {
46+
Allocations.push_back({std::move(Memory)});
47+
}
48+
49+
size_t size() const { return Allocations.size(); }
50+
};
51+
52+
public:
53+
DynamicAllocator() = default;
54+
~DynamicAllocator();
55+
56+
void cleanup();
57+
58+
unsigned getNumAllocations() const { return AllocationSites.size(); }
59+
60+
/// Allocate ONE element of the given descriptor.
61+
Block *allocate(const Descriptor *D);
62+
/// Allocate \p NumElements primitive elements of the given type.
63+
Block *allocate(const Expr *Source, PrimType T, size_t NumElements);
64+
/// Allocate \p NumElements elements of the given descriptor.
65+
Block *allocate(const Descriptor *D, size_t NumElements);
66+
67+
/// Deallocate the given source+block combination.
68+
/// Returns \c true if anything has been deallocatd, \c false otherwise.
69+
bool deallocate(const Expr *Source, const Block *BlockToDelete,
70+
InterpState &S);
71+
72+
/// Checks whether the allocation done at the given source is an array
73+
/// allocation.
74+
bool isArrayAllocation(const Expr *Source) const {
75+
if (auto It = AllocationSites.find(Source); It != AllocationSites.end())
76+
return It->second.IsArrayAllocation;
77+
return false;
78+
}
79+
80+
// FIXME: Public because I'm not sure how to expose an iterator to it.
81+
llvm::DenseMap<const Expr *, AllocationSite> AllocationSites;
82+
83+
private:
84+
using PoolAllocTy = llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator>;
85+
PoolAllocTy DescAllocator;
86+
87+
/// Allocates a new descriptor.
88+
template <typename... Ts> Descriptor *allocateDescriptor(Ts &&...Args) {
89+
return new (DescAllocator) Descriptor(std::forward<Ts>(Args)...);
90+
}
91+
};
92+
93+
} // namespace interp
94+
} // namespace clang
95+
#endif

clang/lib/AST/Interp/EvalEmitter.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,17 @@ bool EvalEmitter::fallthrough(const LabelTy &Label) {
124124
return true;
125125
}
126126

127+
static bool checkReturnState(InterpState &S) {
128+
return S.maybeDiagnoseDanglingAllocations();
129+
}
130+
127131
template <PrimType OpType> bool EvalEmitter::emitRet(const SourceInfo &Info) {
128132
if (!isActive())
129133
return true;
134+
135+
if (!checkReturnState(S))
136+
return false;
137+
130138
using T = typename PrimConv<OpType>::T;
131139
EvalResult.setValue(S.Stk.pop<T>().toAPValue());
132140
return true;
@@ -137,6 +145,12 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(const SourceInfo &Info) {
137145
return true;
138146

139147
const Pointer &Ptr = S.Stk.pop<Pointer>();
148+
149+
if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info))
150+
return false;
151+
if (!checkReturnState(S))
152+
return false;
153+
140154
// Implicitly convert lvalue to rvalue, if requested.
141155
if (ConvertResultToRValue) {
142156
if (std::optional<APValue> V = Ptr.toRValue(Ctx)) {
@@ -163,18 +177,30 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(const SourceInfo &Info) {
163177
template <> bool EvalEmitter::emitRet<PT_FnPtr>(const SourceInfo &Info) {
164178
if (!isActive())
165179
return true;
180+
181+
if (!checkReturnState(S))
182+
return false;
166183
// Function pointers cannot be converted to rvalues.
167184
EvalResult.setFunctionPointer(S.Stk.pop<FunctionPointer>());
168185
return true;
169186
}
170187

171188
bool EvalEmitter::emitRetVoid(const SourceInfo &Info) {
189+
if (!checkReturnState(S))
190+
return false;
172191
EvalResult.setValid();
173192
return true;
174193
}
175194

176195
bool EvalEmitter::emitRetValue(const SourceInfo &Info) {
177196
const auto &Ptr = S.Stk.pop<Pointer>();
197+
198+
if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info))
199+
return false;
200+
201+
if (!checkReturnState(S))
202+
return false;
203+
178204
if (std::optional<APValue> APV = Ptr.toRValue(S.getCtx())) {
179205
EvalResult.setValue(*APV);
180206
return true;

0 commit comments

Comments
 (0)