Skip to content

Commit 294643e

Browse files
authored
[clang][bytecode] Check lifetime of all ptr bases in placement-new (#141272)
placement-new'ing an object with a dead base object is not allowed, so we need to check all the pointer bases.
1 parent 7cfdd74 commit 294643e

File tree

7 files changed

+84
-25
lines changed

7 files changed

+84
-25
lines changed

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4927,7 +4927,8 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
49274927
const auto *MemberCall = cast<CXXMemberCallExpr>(E);
49284928
if (!this->visit(MemberCall->getImplicitObjectArgument()))
49294929
return false;
4930-
return this->emitCheckDestruction(E) && this->emitPopPtr(E);
4930+
return this->emitCheckDestruction(E) && this->emitEndLifetime(E) &&
4931+
this->emitPopPtr(E);
49314932
}
49324933
}
49334934

@@ -5016,7 +5017,7 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
50165017
return this->discard(Base);
50175018
if (!this->visit(Base))
50185019
return false;
5019-
return this->emitKill(E);
5020+
return this->emitEndLifetimePop(E);
50205021
} else if (!FuncDecl) {
50215022
const Expr *Callee = E->getCallee();
50225023
CalleeOffset =

clang/lib/AST/ByteCode/DynamicAllocator.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ Block *DynamicAllocator::allocate(const Descriptor *D, unsigned EvalID,
8686
ID->IsInitialized = false;
8787
ID->IsVolatile = false;
8888

89+
ID->LifeState =
90+
AllocForm == Form::Operator ? Lifetime::Ended : Lifetime::Started;
91+
;
92+
8993
B->IsDynamic = true;
9094

9195
if (auto It = AllocationSites.find(D->asExpr()); It != AllocationSites.end())

clang/lib/AST/ByteCode/Interp.cpp

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,6 +1699,50 @@ bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
16991699
return Call(S, OpPC, F, VarArgSize);
17001700
}
17011701

1702+
bool StartLifetime(InterpState &S, CodePtr OpPC) {
1703+
const auto &Ptr = S.Stk.peek<Pointer>();
1704+
if (!CheckDummy(S, OpPC, Ptr, AK_Destroy))
1705+
return false;
1706+
1707+
Ptr.startLifetime();
1708+
return true;
1709+
}
1710+
1711+
// FIXME: It might be better to the recursing as part of the generated code for
1712+
// a destructor?
1713+
static void endLifetimeRecurse(const Pointer &Ptr) {
1714+
Ptr.endLifetime();
1715+
if (const Record *R = Ptr.getRecord()) {
1716+
for (const Record::Field &Fi : R->fields())
1717+
endLifetimeRecurse(Ptr.atField(Fi.Offset));
1718+
return;
1719+
}
1720+
1721+
if (const Descriptor *FieldDesc = Ptr.getFieldDesc();
1722+
FieldDesc->isCompositeArray()) {
1723+
for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I)
1724+
endLifetimeRecurse(Ptr.atIndex(I).narrow());
1725+
}
1726+
}
1727+
1728+
/// Ends the lifetime of the peek'd pointer.
1729+
bool EndLifetime(InterpState &S, CodePtr OpPC) {
1730+
const auto &Ptr = S.Stk.peek<Pointer>();
1731+
if (!CheckDummy(S, OpPC, Ptr, AK_Destroy))
1732+
return false;
1733+
endLifetimeRecurse(Ptr);
1734+
return true;
1735+
}
1736+
1737+
/// Ends the lifetime of the pop'd pointer.
1738+
bool EndLifetimePop(InterpState &S, CodePtr OpPC) {
1739+
const auto &Ptr = S.Stk.pop<Pointer>();
1740+
if (!CheckDummy(S, OpPC, Ptr, AK_Destroy))
1741+
return false;
1742+
endLifetimeRecurse(Ptr);
1743+
return true;
1744+
}
1745+
17021746
bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E,
17031747
std::optional<uint64_t> ArraySize) {
17041748
const Pointer &Ptr = S.Stk.peek<Pointer>();
@@ -1711,8 +1755,16 @@ bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E,
17111755
return false;
17121756
if (!CheckDummy(S, OpPC, Ptr, AK_Construct))
17131757
return false;
1714-
if (!CheckLifetime(S, OpPC, Ptr, AK_Construct))
1715-
return false;
1758+
1759+
// CheckLifetime for this and all base pointers.
1760+
for (Pointer P = Ptr;;) {
1761+
if (!CheckLifetime(S, OpPC, P, AK_Construct)) {
1762+
return false;
1763+
}
1764+
if (P.isRoot())
1765+
break;
1766+
P = P.getBase();
1767+
}
17161768
if (!CheckExtern(S, OpPC, Ptr))
17171769
return false;
17181770
if (!CheckRange(S, OpPC, Ptr, AK_Construct))

clang/lib/AST/ByteCode/Interp.h

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,21 +1326,9 @@ bool GetLocal(InterpState &S, CodePtr OpPC, uint32_t I) {
13261326
return true;
13271327
}
13281328

1329-
static inline bool Kill(InterpState &S, CodePtr OpPC) {
1330-
const auto &Ptr = S.Stk.pop<Pointer>();
1331-
if (!CheckDummy(S, OpPC, Ptr, AK_Destroy))
1332-
return false;
1333-
Ptr.endLifetime();
1334-
return true;
1335-
}
1336-
1337-
static inline bool StartLifetime(InterpState &S, CodePtr OpPC) {
1338-
const auto &Ptr = S.Stk.peek<Pointer>();
1339-
if (!CheckDummy(S, OpPC, Ptr, AK_Destroy))
1340-
return false;
1341-
Ptr.startLifetime();
1342-
return true;
1343-
}
1329+
bool EndLifetime(InterpState &S, CodePtr OpPC);
1330+
bool EndLifetimePop(InterpState &S, CodePtr OpPC);
1331+
bool StartLifetime(InterpState &S, CodePtr OpPC);
13441332

13451333
/// 1) Pops the value from the stack.
13461334
/// 2) Writes the value to the local variable with the

clang/lib/AST/ByteCode/Opcodes.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,8 @@ def GetLocal : AccessOpcode { let HasCustomEval = 1; }
395395
// [] -> [Pointer]
396396
def SetLocal : AccessOpcode { let HasCustomEval = 1; }
397397

398-
def Kill : Opcode;
398+
def EndLifetimePop : Opcode;
399+
def EndLifetime : Opcode;
399400
def StartLifetime : Opcode;
400401

401402
def CheckDecl : Opcode {

clang/test/AST/ByteCode/new-delete.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -682,17 +682,16 @@ namespace OperatorNewDelete {
682682
}
683683
static_assert(zeroAlloc());
684684

685-
/// FIXME: This is broken in the current interpreter.
686685
constexpr int arrayAlloc() {
687686
int *F = std::allocator<int>().allocate(2);
688-
F[0] = 10; // ref-note {{assignment to object outside its lifetime is not allowed in a constant expression}}
687+
F[0] = 10; // both-note {{assignment to object outside its lifetime is not allowed in a constant expression}}
689688
F[1] = 13;
690689
int Res = F[1] + F[0];
691690
std::allocator<int>().deallocate(F);
692691
return Res;
693692
}
694-
static_assert(arrayAlloc() == 23); // ref-error {{not an integral constant expression}} \
695-
// ref-note {{in call to}}
693+
static_assert(arrayAlloc() == 23); // both-error {{not an integral constant expression}} \
694+
// both-note {{in call to}}
696695

697696
struct S {
698697
int i;

clang/test/AST/ByteCode/placement-new.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@ namespace std {
1717
// both-note {{placement new would change type of storage from 'int' to 'float'}} \
1818
// both-note {{construction of subobject of member 'x' of union with active member 'a' is not allowed in a constant expression}} \
1919
// both-note {{construction of temporary is not allowed}} \
20-
// both-note {{construction of heap allocated object that has been deleted}}
20+
// both-note {{construction of heap allocated object that has been deleted}} \
21+
// both-note {{construction of subobject of object outside its lifetime is not allowed in a constant expression}}
2122
}
2223
}
2324

2425
void *operator new(std::size_t, void *p) { return p; }
2526
void* operator new[] (std::size_t, void* p) {return p;}
2627

28+
constexpr int no_lifetime_start = (*std::allocator<int>().allocate(1) = 1); // both-error {{constant expression}} \
29+
// both-note {{assignment to object outside its lifetime}}
2730

2831
consteval auto ok1() {
2932
bool b;
@@ -409,3 +412,14 @@ namespace PlacementNewAfterDelete {
409412
static_assert(construct_after_lifetime()); // both-error {{}} \
410413
// both-note {{in call}}
411414
}
415+
416+
namespace SubObj {
417+
constexpr bool construct_after_lifetime_2() {
418+
struct A { struct B {} b; };
419+
A a;
420+
a.~A();
421+
std::construct_at<A::B>(&a.b); // both-note {{in call}}
422+
return true;
423+
}
424+
static_assert(construct_after_lifetime_2()); // both-error {{}} both-note {{in call}}
425+
}

0 commit comments

Comments
 (0)