Skip to content

Commit b19f0dd

Browse files
committed
[clang][Interp] Implement dynamic memory allocation handling
1 parent 5563d91 commit b19f0dd

19 files changed

+928
-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
@@ -2544,6 +2544,85 @@ bool ByteCodeExprGen<Emitter>::VisitCXXInheritedCtorInitExpr(
25442544
return this->emitCall(F, 0, E);
25452545
}
25462546

2547+
template <class Emitter>
2548+
bool ByteCodeExprGen<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
2549+
assert(classifyPrim(E->getType()) == PT_Ptr);
2550+
const Expr *Init = E->getInitializer();
2551+
QualType ElementType = E->getAllocatedType();
2552+
std::optional<PrimType> ElemT = classify(ElementType);
2553+
2554+
const Descriptor *Desc;
2555+
if (ElemT) {
2556+
if (E->isArray())
2557+
Desc = nullptr; // We're not going to use it in this case.
2558+
else
2559+
Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
2560+
/*IsConst=*/false, /*IsTemporary=*/false,
2561+
/*IsMutable=*/false);
2562+
} else {
2563+
Desc = P.createDescriptor(
2564+
E, ElementType.getTypePtr(),
2565+
E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
2566+
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
2567+
}
2568+
2569+
if (E->isArray()) {
2570+
std::optional<const Expr *> ArraySizeExpr = E->getArraySize();
2571+
if (!ArraySizeExpr)
2572+
return false;
2573+
assert(ArraySizeExpr);
2574+
PrimType SizeT = classifyPrim((*ArraySizeExpr)->getType());
2575+
2576+
if (!this->visit(*ArraySizeExpr))
2577+
return false;
2578+
2579+
if (ElemT) {
2580+
// N primitive elements.
2581+
if (!this->emitAllocN(SizeT, *ElemT, E, E))
2582+
return false;
2583+
} else {
2584+
// N Composite elements.
2585+
if (!this->emitAllocCN(SizeT, Desc, E))
2586+
return false;
2587+
}
2588+
2589+
} else {
2590+
// Allocate just one element.
2591+
if (!this->emitAlloc(Desc, E))
2592+
return false;
2593+
2594+
if (Init) {
2595+
if (ElemT) {
2596+
if (!this->visit(Init))
2597+
return false;
2598+
2599+
if (!this->emitInit(*ElemT, E))
2600+
return false;
2601+
} else {
2602+
// Composite.
2603+
if (!this->visitInitializer(Init))
2604+
return false;
2605+
}
2606+
}
2607+
}
2608+
2609+
if (DiscardResult)
2610+
return this->emitPopPtr(E);
2611+
2612+
return true;
2613+
}
2614+
2615+
template <class Emitter>
2616+
bool ByteCodeExprGen<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
2617+
const Expr *Arg = E->getArgument();
2618+
2619+
// Arg must be an lvalue.
2620+
if (!this->visit(Arg))
2621+
return false;
2622+
2623+
return this->emitFree(E->isArrayForm(), E);
2624+
}
2625+
25472626
template <class Emitter>
25482627
bool ByteCodeExprGen<Emitter>::VisitExpressionTraitExpr(
25492628
const ExpressionTraitExpr *E) {

clang/lib/AST/Interp/ByteCodeExprGen.h

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

133135
protected:
134136
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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,17 @@ bool EvalEmitter::fallthrough(const LabelTy &Label) {
129129
return true;
130130
}
131131

132+
static bool checkReturnState(InterpState &S) {
133+
return S.maybeDiagnoseDanglingAllocations();
134+
}
135+
132136
template <PrimType OpType> bool EvalEmitter::emitRet(const SourceInfo &Info) {
133137
if (!isActive())
134138
return true;
139+
140+
if (!checkReturnState(S))
141+
return false;
142+
135143
using T = typename PrimConv<OpType>::T;
136144
EvalResult.setValue(S.Stk.pop<T>().toAPValue());
137145
return true;
@@ -145,6 +153,10 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(const SourceInfo &Info) {
145153

146154
if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr))
147155
return false;
156+
if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info))
157+
return false;
158+
if (!checkReturnState(S))
159+
return false;
148160

149161
// Implicitly convert lvalue to rvalue, if requested.
150162
if (ConvertResultToRValue) {
@@ -162,12 +174,17 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(const SourceInfo &Info) {
162174
template <> bool EvalEmitter::emitRet<PT_FnPtr>(const SourceInfo &Info) {
163175
if (!isActive())
164176
return true;
177+
178+
if (!checkReturnState(S))
179+
return false;
165180
// Function pointers cannot be converted to rvalues.
166181
EvalResult.setFunctionPointer(S.Stk.pop<FunctionPointer>());
167182
return true;
168183
}
169184

170185
bool EvalEmitter::emitRetVoid(const SourceInfo &Info) {
186+
if (!checkReturnState(S))
187+
return false;
171188
EvalResult.setValid();
172189
return true;
173190
}
@@ -177,6 +194,10 @@ bool EvalEmitter::emitRetValue(const SourceInfo &Info) {
177194

178195
if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr))
179196
return false;
197+
if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info))
198+
return false;
199+
if (!checkReturnState(S))
200+
return false;
180201

181202
if (std::optional<APValue> APV = Ptr.toRValue(S.getCtx())) {
182203
EvalResult.setValue(*APV);

0 commit comments

Comments
 (0)