Skip to content

Commit 87209d2

Browse files
committed
[clang][bytecode] Check dtor instance pointers for active-ness
And diagnose if we're trying to destroy an inactive member of a union.
1 parent d21b2e6 commit 87209d2

File tree

5 files changed

+123
-64
lines changed

5 files changed

+123
-64
lines changed

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4733,8 +4733,12 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
47334733
}
47344734
// Explicit calls to trivial destructors
47354735
if (const auto *DD = dyn_cast_if_present<CXXDestructorDecl>(FuncDecl);
4736-
DD && DD->isTrivial())
4737-
return true;
4736+
DD && DD->isTrivial()) {
4737+
const auto *MemberCall = cast<CXXMemberCallExpr>(E);
4738+
if (!this->visit(MemberCall->getImplicitObjectArgument()))
4739+
return false;
4740+
return this->emitCheckDestruction(E) && this->emitPopPtr(E);
4741+
}
47384742

47394743
QualType ReturnType = E->getCallReturnType(Ctx.getASTContext());
47404744
std::optional<PrimType> T = classify(ReturnType);
@@ -5753,6 +5757,9 @@ bool Compiler<Emitter>::compileDestructor(const CXXDestructorDecl *Dtor) {
57535757
if (!this->emitThis(Dtor))
57545758
return false;
57555759

5760+
if (!this->emitCheckDestruction(Dtor))
5761+
return false;
5762+
57565763
assert(R);
57575764
if (!R->isUnion()) {
57585765
// First, destroy all fields.

clang/lib/AST/ByteCode/Interp.cpp

Lines changed: 69 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -129,68 +129,6 @@ static void diagnoseNonConstVariable(InterpState &S, CodePtr OpPC,
129129
S.Note(VD->getLocation(), diag::note_declared_at);
130130
}
131131

132-
static bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
133-
AccessKinds AK) {
134-
if (Ptr.isActive())
135-
return true;
136-
137-
assert(Ptr.inUnion());
138-
assert(Ptr.isField() && Ptr.getField());
139-
140-
Pointer U = Ptr.getBase();
141-
Pointer C = Ptr;
142-
while (!U.isRoot() && !U.isActive()) {
143-
// A little arbitrary, but this is what the current interpreter does.
144-
// See the AnonymousUnion test in test/AST/ByteCode/unions.cpp.
145-
// GCC's output is more similar to what we would get without
146-
// this condition.
147-
if (U.getRecord() && U.getRecord()->isAnonymousUnion())
148-
break;
149-
150-
C = U;
151-
U = U.getBase();
152-
}
153-
assert(C.isField());
154-
155-
// Consider:
156-
// union U {
157-
// struct {
158-
// int x;
159-
// int y;
160-
// } a;
161-
// }
162-
//
163-
// When activating x, we will also activate a. If we now try to read
164-
// from y, we will get to CheckActive, because y is not active. In that
165-
// case, our U will be a (not a union). We return here and let later code
166-
// handle this.
167-
if (!U.getFieldDesc()->isUnion())
168-
return true;
169-
170-
// Get the inactive field descriptor.
171-
assert(!C.isActive());
172-
const FieldDecl *InactiveField = C.getField();
173-
assert(InactiveField);
174-
175-
// Find the active field of the union.
176-
const Record *R = U.getRecord();
177-
assert(R && R->isUnion() && "Not a union");
178-
179-
const FieldDecl *ActiveField = nullptr;
180-
for (const Record::Field &F : R->fields()) {
181-
const Pointer &Field = U.atField(F.Offset);
182-
if (Field.isActive()) {
183-
ActiveField = Field.getField();
184-
break;
185-
}
186-
}
187-
188-
const SourceInfo &Loc = S.Current->getSource(OpPC);
189-
S.FFDiag(Loc, diag::note_constexpr_access_inactive_union_member)
190-
<< AK << InactiveField << !ActiveField << ActiveField;
191-
return false;
192-
}
193-
194132
static bool CheckTemporary(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
195133
AccessKinds AK) {
196134
if (auto ID = Ptr.getDeclID()) {
@@ -290,6 +228,68 @@ void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC,
290228
TYPE_SWITCH(Ty, S.Stk.discard<T>());
291229
}
292230

231+
bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
232+
AccessKinds AK) {
233+
if (Ptr.isActive())
234+
return true;
235+
236+
assert(Ptr.inUnion());
237+
assert(Ptr.isField() && Ptr.getField());
238+
239+
Pointer U = Ptr.getBase();
240+
Pointer C = Ptr;
241+
while (!U.isRoot() && !U.isActive()) {
242+
// A little arbitrary, but this is what the current interpreter does.
243+
// See the AnonymousUnion test in test/AST/ByteCode/unions.cpp.
244+
// GCC's output is more similar to what we would get without
245+
// this condition.
246+
if (U.getRecord() && U.getRecord()->isAnonymousUnion())
247+
break;
248+
249+
C = U;
250+
U = U.getBase();
251+
}
252+
assert(C.isField());
253+
254+
// Consider:
255+
// union U {
256+
// struct {
257+
// int x;
258+
// int y;
259+
// } a;
260+
// }
261+
//
262+
// When activating x, we will also activate a. If we now try to read
263+
// from y, we will get to CheckActive, because y is not active. In that
264+
// case, our U will be a (not a union). We return here and let later code
265+
// handle this.
266+
if (!U.getFieldDesc()->isUnion())
267+
return true;
268+
269+
// Get the inactive field descriptor.
270+
assert(!C.isActive());
271+
const FieldDecl *InactiveField = C.getField();
272+
assert(InactiveField);
273+
274+
// Find the active field of the union.
275+
const Record *R = U.getRecord();
276+
assert(R && R->isUnion() && "Not a union");
277+
278+
const FieldDecl *ActiveField = nullptr;
279+
for (const Record::Field &F : R->fields()) {
280+
const Pointer &Field = U.atField(F.Offset);
281+
if (Field.isActive()) {
282+
ActiveField = Field.getField();
283+
break;
284+
}
285+
}
286+
287+
const SourceInfo &Loc = S.Current->getSource(OpPC);
288+
S.FFDiag(Loc, diag::note_constexpr_access_inactive_union_member)
289+
<< AK << InactiveField << !ActiveField << ActiveField;
290+
return false;
291+
}
292+
293293
bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
294294
if (!Ptr.isExtern())
295295
return true;
@@ -1265,6 +1265,11 @@ static bool checkConstructor(InterpState &S, CodePtr OpPC, const Function *Func,
12651265
return false;
12661266
}
12671267

1268+
static bool checkDestructor(InterpState &S, CodePtr OpPC, const Function *Func,
1269+
const Pointer &ThisPtr) {
1270+
return CheckActive(S, OpPC, ThisPtr, AK_Destroy);
1271+
}
1272+
12681273
bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func,
12691274
uint32_t VarArgSize) {
12701275
if (Func->hasThisPointer()) {
@@ -1347,6 +1352,8 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
13471352

13481353
if (Func->isConstructor() && !checkConstructor(S, OpPC, Func, ThisPtr))
13491354
return false;
1355+
if (Func->isDestructor() && !checkDestructor(S, OpPC, Func, ThisPtr))
1356+
return false;
13501357
}
13511358

13521359
if (!CheckCallable(S, OpPC, Func))

clang/lib/AST/ByteCode/Interp.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC,
137137
bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
138138
const Pointer &Ptr);
139139

140+
bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
141+
AccessKinds AK);
142+
140143
/// Sets the given integral value to the pointer, which is of
141144
/// a std::{weak,partial,strong}_ordering type.
142145
bool SetThreeWayComparisonField(InterpState &S, CodePtr OpPC,
@@ -3063,6 +3066,11 @@ bool GetTypeid(InterpState &S, CodePtr OpPC, const Type *TypePtr,
30633066
bool GetTypeidPtr(InterpState &S, CodePtr OpPC, const Type *TypeInfoType);
30643067
bool DiagTypeid(InterpState &S, CodePtr OpPC);
30653068

3069+
inline bool CheckDestruction(InterpState &S, CodePtr OpPC) {
3070+
const auto &Ptr = S.Stk.peek<Pointer>();
3071+
return CheckActive(S, OpPC, Ptr, AK_Destroy);
3072+
}
3073+
30663074
//===----------------------------------------------------------------------===//
30673075
// Read opcode arguments
30683076
//===----------------------------------------------------------------------===//

clang/lib/AST/ByteCode/Opcodes.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,3 +857,5 @@ def BitCast : Opcode;
857857
def GetTypeid : Opcode { let Args = [ArgTypePtr, ArgTypePtr]; }
858858
def GetTypeidPtr : Opcode { let Args = [ArgTypePtr]; }
859859
def DiagTypeid : Opcode;
860+
861+
def CheckDestruction : Opcode;

clang/test/AST/ByteCode/unions.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,4 +504,39 @@ namespace AnonymousUnion {
504504
static_assert(return_init_all().a.p == 7); // both-error {{}} \
505505
// both-note {{read of member 'p' of union with no active member}}
506506
}
507+
508+
namespace InactiveDestroy {
509+
struct A {
510+
constexpr ~A() {}
511+
};
512+
union U {
513+
A a;
514+
constexpr ~U() {
515+
}
516+
};
517+
518+
constexpr bool foo() { // both-error {{never produces a constant expression}}
519+
U u;
520+
u.a.~A(); // both-note 2{{destruction of member 'a' of union with no active member}}
521+
return true;
522+
}
523+
static_assert(foo()); // both-error {{not an integral constant expression}} \
524+
// both-note {{in call to}}
525+
}
526+
527+
namespace InactiveTrivialDestroy {
528+
struct A {};
529+
union U {
530+
A a;
531+
};
532+
533+
constexpr bool foo() { // both-error {{never produces a constant expression}}
534+
U u;
535+
u.a.~A(); // both-note 2{{destruction of member 'a' of union with no active member}}
536+
return true;
537+
}
538+
static_assert(foo()); // both-error {{not an integral constant expression}} \
539+
// both-note {{in call to}}
540+
}
541+
507542
#endif

0 commit comments

Comments
 (0)