Skip to content

Commit f93258e

Browse files
authored
[clang][bytecode] Diagnose class-specific operator delete calls (llvm#111700)
1 parent c15611a commit f93258e

File tree

5 files changed

+112
-63
lines changed

5 files changed

+112
-63
lines changed

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3406,7 +3406,7 @@ bool Compiler<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
34063406
if (!this->visit(Arg))
34073407
return false;
34083408

3409-
return this->emitFree(E->isArrayForm(), E);
3409+
return this->emitFree(E->isArrayForm(), E->isGlobalDelete(), E);
34103410
}
34113411

34123412
template <class Emitter>

clang/lib/AST/ByteCode/Interp.cpp

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,7 @@ static bool runRecordDestructor(InterpState &S, CodePtr OpPC,
964964
return true;
965965
}
966966

967-
bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B) {
967+
static bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B) {
968968
assert(B);
969969
const Descriptor *Desc = B->getDescriptor();
970970

@@ -989,6 +989,89 @@ bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B) {
989989
return runRecordDestructor(S, OpPC, Pointer(const_cast<Block *>(B)), Desc);
990990
}
991991

992+
bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm,
993+
bool IsGlobalDelete) {
994+
if (!CheckDynamicMemoryAllocation(S, OpPC))
995+
return false;
996+
997+
const Expr *Source = nullptr;
998+
const Block *BlockToDelete = nullptr;
999+
{
1000+
// Extra scope for this so the block doesn't have this pointer
1001+
// pointing to it when we destroy it.
1002+
Pointer Ptr = S.Stk.pop<Pointer>();
1003+
1004+
// Deleteing nullptr is always fine.
1005+
if (Ptr.isZero())
1006+
return true;
1007+
1008+
// Remove base casts.
1009+
while (Ptr.isBaseClass())
1010+
Ptr = Ptr.getBase();
1011+
1012+
if (!Ptr.isRoot() || Ptr.isOnePastEnd() || Ptr.isArrayElement()) {
1013+
const SourceInfo &Loc = S.Current->getSource(OpPC);
1014+
S.FFDiag(Loc, diag::note_constexpr_delete_subobject)
1015+
<< Ptr.toDiagnosticString(S.getASTContext()) << Ptr.isOnePastEnd();
1016+
return false;
1017+
}
1018+
1019+
Source = Ptr.getDeclDesc()->asExpr();
1020+
BlockToDelete = Ptr.block();
1021+
1022+
if (!CheckDeleteSource(S, OpPC, Source, Ptr))
1023+
return false;
1024+
1025+
// For a class type with a virtual destructor, the selected operator delete
1026+
// is the one looked up when building the destructor.
1027+
QualType AllocType = Ptr.getType();
1028+
if (!DeleteIsArrayForm && !IsGlobalDelete) {
1029+
auto getVirtualOperatorDelete = [](QualType T) -> const FunctionDecl * {
1030+
if (const CXXRecordDecl *RD = T->getAsCXXRecordDecl())
1031+
if (const CXXDestructorDecl *DD = RD->getDestructor())
1032+
return DD->isVirtual() ? DD->getOperatorDelete() : nullptr;
1033+
return nullptr;
1034+
};
1035+
1036+
AllocType->dump();
1037+
if (const FunctionDecl *VirtualDelete =
1038+
getVirtualOperatorDelete(AllocType);
1039+
VirtualDelete &&
1040+
!VirtualDelete->isReplaceableGlobalAllocationFunction()) {
1041+
S.FFDiag(S.Current->getSource(OpPC),
1042+
diag::note_constexpr_new_non_replaceable)
1043+
<< isa<CXXMethodDecl>(VirtualDelete) << VirtualDelete;
1044+
return false;
1045+
}
1046+
}
1047+
}
1048+
assert(Source);
1049+
assert(BlockToDelete);
1050+
1051+
// Invoke destructors before deallocating the memory.
1052+
if (!RunDestructors(S, OpPC, BlockToDelete))
1053+
return false;
1054+
1055+
DynamicAllocator &Allocator = S.getAllocator();
1056+
const Descriptor *BlockDesc = BlockToDelete->getDescriptor();
1057+
std::optional<DynamicAllocator::Form> AllocForm =
1058+
Allocator.getAllocationForm(Source);
1059+
1060+
if (!Allocator.deallocate(Source, BlockToDelete, S)) {
1061+
// Nothing has been deallocated, this must be a double-delete.
1062+
const SourceInfo &Loc = S.Current->getSource(OpPC);
1063+
S.FFDiag(Loc, diag::note_constexpr_double_delete);
1064+
return false;
1065+
}
1066+
1067+
assert(AllocForm);
1068+
DynamicAllocator::Form DeleteForm = DeleteIsArrayForm
1069+
? DynamicAllocator::Form::Array
1070+
: DynamicAllocator::Form::NonArray;
1071+
return CheckNewDeleteForms(S, OpPC, *AllocForm, DeleteForm, BlockDesc,
1072+
Source);
1073+
}
1074+
9921075
void diagnoseEnumValue(InterpState &S, CodePtr OpPC, const EnumDecl *ED,
9931076
const APSInt &Value) {
9941077
llvm::APInt Min;

clang/lib/AST/ByteCode/Interp.h

Lines changed: 2 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2976,61 +2976,8 @@ inline bool AllocCN(InterpState &S, CodePtr OpPC, const Descriptor *ElementDesc,
29762976
return true;
29772977
}
29782978

2979-
bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B);
2980-
static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm) {
2981-
if (!CheckDynamicMemoryAllocation(S, OpPC))
2982-
return false;
2983-
2984-
const Expr *Source = nullptr;
2985-
const Block *BlockToDelete = nullptr;
2986-
{
2987-
// Extra scope for this so the block doesn't have this pointer
2988-
// pointing to it when we destroy it.
2989-
const Pointer &Ptr = S.Stk.pop<Pointer>();
2990-
2991-
// Deleteing nullptr is always fine.
2992-
if (Ptr.isZero())
2993-
return true;
2994-
2995-
if (!Ptr.isRoot() || Ptr.isOnePastEnd() || Ptr.isArrayElement()) {
2996-
const SourceInfo &Loc = S.Current->getSource(OpPC);
2997-
S.FFDiag(Loc, diag::note_constexpr_delete_subobject)
2998-
<< Ptr.toDiagnosticString(S.getASTContext()) << Ptr.isOnePastEnd();
2999-
return false;
3000-
}
3001-
3002-
Source = Ptr.getDeclDesc()->asExpr();
3003-
BlockToDelete = Ptr.block();
3004-
3005-
if (!CheckDeleteSource(S, OpPC, Source, Ptr))
3006-
return false;
3007-
}
3008-
assert(Source);
3009-
assert(BlockToDelete);
3010-
3011-
// Invoke destructors before deallocating the memory.
3012-
if (!RunDestructors(S, OpPC, BlockToDelete))
3013-
return false;
3014-
3015-
DynamicAllocator &Allocator = S.getAllocator();
3016-
const Descriptor *BlockDesc = BlockToDelete->getDescriptor();
3017-
std::optional<DynamicAllocator::Form> AllocForm =
3018-
Allocator.getAllocationForm(Source);
3019-
3020-
if (!Allocator.deallocate(Source, BlockToDelete, S)) {
3021-
// Nothing has been deallocated, this must be a double-delete.
3022-
const SourceInfo &Loc = S.Current->getSource(OpPC);
3023-
S.FFDiag(Loc, diag::note_constexpr_double_delete);
3024-
return false;
3025-
}
3026-
3027-
assert(AllocForm);
3028-
DynamicAllocator::Form DeleteForm = DeleteIsArrayForm
3029-
? DynamicAllocator::Form::Array
3030-
: DynamicAllocator::Form::NonArray;
3031-
return CheckNewDeleteForms(S, OpPC, *AllocForm, DeleteForm, BlockDesc,
3032-
Source);
3033-
}
2979+
bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm,
2980+
bool IsGlobalDelete);
30342981

30352982
static inline bool IsConstantContext(InterpState &S, CodePtr OpPC) {
30362983
S.Stk.push<Boolean>(Boolean::from(S.inConstantContext()));

clang/lib/AST/ByteCode/Opcodes.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ def AllocCN : Opcode {
818818
}
819819

820820
def Free : Opcode {
821-
let Args = [ArgBool];
821+
let Args = [ArgBool, ArgBool];
822822
}
823823

824824
def CheckNewTypeMismatch : Opcode {

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -552,8 +552,6 @@ namespace DeleteThis {
552552
// both-note {{in call to 'super_secret_double_delete()'}}
553553
}
554554

555-
/// FIXME: This is currently diagnosed, but should work.
556-
/// If the destructor for S is _not_ virtual however, it should fail.
557555
namespace CastedDelete {
558556
struct S {
559557
constexpr S(int *p) : p(p) {}
@@ -567,11 +565,10 @@ namespace CastedDelete {
567565

568566
constexpr int vdtor_1() {
569567
int a;
570-
delete (S*)new T(&a); // expected-note {{delete of pointer to subobject}}
568+
delete (S*)new T(&a);
571569
return a;
572570
}
573-
static_assert(vdtor_1() == 1); // expected-error {{not an integral constant expression}} \
574-
// expected-note {{in call to}}
571+
static_assert(vdtor_1() == 1);
575572
}
576573

577574
constexpr void use_after_free_2() { // both-error {{never produces a constant expression}}
@@ -778,6 +775,28 @@ namespace Placement {
778775
static_assert(ok2()== 0);
779776
}
780777

778+
constexpr bool virt_delete(bool global) {
779+
struct A {
780+
virtual constexpr ~A() {}
781+
};
782+
struct B : A {
783+
void operator delete(void *);
784+
constexpr ~B() {}
785+
};
786+
787+
A *p = new B;
788+
if (global)
789+
::delete p;
790+
else
791+
delete p; // both-note {{call to class-specific 'operator delete'}}
792+
return true;
793+
}
794+
static_assert(virt_delete(true));
795+
static_assert(virt_delete(false)); // both-error {{not an integral constant expression}} \
796+
// both-note {{in call to}}
797+
798+
799+
781800
#else
782801
/// Make sure we reject this prior to C++20
783802
constexpr int a() { // both-error {{never produces a constant expression}}

0 commit comments

Comments
 (0)