[clang] f93258e - [clang][bytecode] Diagnose class-specific operator delete calls (#111700)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Oct 9 23:40:23 PDT 2024
Author: Timm Baeder
Date: 2024-10-10T08:40:14+02:00
New Revision: f93258e4aca518cba3d48db59ed6143ca19ca99b
URL: https://github.com/llvm/llvm-project/commit/f93258e4aca518cba3d48db59ed6143ca19ca99b
DIFF: https://github.com/llvm/llvm-project/commit/f93258e4aca518cba3d48db59ed6143ca19ca99b.diff
LOG: [clang][bytecode] Diagnose class-specific operator delete calls (#111700)
Added:
Modified:
clang/lib/AST/ByteCode/Compiler.cpp
clang/lib/AST/ByteCode/Interp.cpp
clang/lib/AST/ByteCode/Interp.h
clang/lib/AST/ByteCode/Opcodes.td
clang/test/AST/ByteCode/new-delete.cpp
Removed:
################################################################################
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index f270de1054c61b..fe44238ea11869 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -3406,7 +3406,7 @@ bool Compiler<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
if (!this->visit(Arg))
return false;
- return this->emitFree(E->isArrayForm(), E);
+ return this->emitFree(E->isArrayForm(), E->isGlobalDelete(), E);
}
template <class Emitter>
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 82e11743cc5296..95715655cc9bbd 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -964,7 +964,7 @@ static bool runRecordDestructor(InterpState &S, CodePtr OpPC,
return true;
}
-bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B) {
+static bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B) {
assert(B);
const Descriptor *Desc = B->getDescriptor();
@@ -989,6 +989,89 @@ bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B) {
return runRecordDestructor(S, OpPC, Pointer(const_cast<Block *>(B)), Desc);
}
+bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm,
+ bool IsGlobalDelete) {
+ if (!CheckDynamicMemoryAllocation(S, OpPC))
+ return false;
+
+ const Expr *Source = nullptr;
+ const Block *BlockToDelete = nullptr;
+ {
+ // Extra scope for this so the block doesn't have this pointer
+ // pointing to it when we destroy it.
+ Pointer Ptr = S.Stk.pop<Pointer>();
+
+ // Deleteing nullptr is always fine.
+ if (Ptr.isZero())
+ return true;
+
+ // Remove base casts.
+ while (Ptr.isBaseClass())
+ Ptr = Ptr.getBase();
+
+ if (!Ptr.isRoot() || Ptr.isOnePastEnd() || Ptr.isArrayElement()) {
+ const SourceInfo &Loc = S.Current->getSource(OpPC);
+ S.FFDiag(Loc, diag::note_constexpr_delete_subobject)
+ << Ptr.toDiagnosticString(S.getASTContext()) << Ptr.isOnePastEnd();
+ return false;
+ }
+
+ Source = Ptr.getDeclDesc()->asExpr();
+ BlockToDelete = Ptr.block();
+
+ if (!CheckDeleteSource(S, OpPC, Source, Ptr))
+ return false;
+
+ // For a class type with a virtual destructor, the selected operator delete
+ // is the one looked up when building the destructor.
+ QualType AllocType = Ptr.getType();
+ if (!DeleteIsArrayForm && !IsGlobalDelete) {
+ auto getVirtualOperatorDelete = [](QualType T) -> const FunctionDecl * {
+ if (const CXXRecordDecl *RD = T->getAsCXXRecordDecl())
+ if (const CXXDestructorDecl *DD = RD->getDestructor())
+ return DD->isVirtual() ? DD->getOperatorDelete() : nullptr;
+ return nullptr;
+ };
+
+ AllocType->dump();
+ if (const FunctionDecl *VirtualDelete =
+ getVirtualOperatorDelete(AllocType);
+ VirtualDelete &&
+ !VirtualDelete->isReplaceableGlobalAllocationFunction()) {
+ S.FFDiag(S.Current->getSource(OpPC),
+ diag::note_constexpr_new_non_replaceable)
+ << isa<CXXMethodDecl>(VirtualDelete) << VirtualDelete;
+ return false;
+ }
+ }
+ }
+ assert(Source);
+ assert(BlockToDelete);
+
+ // Invoke destructors before deallocating the memory.
+ if (!RunDestructors(S, OpPC, BlockToDelete))
+ return false;
+
+ DynamicAllocator &Allocator = S.getAllocator();
+ const Descriptor *BlockDesc = BlockToDelete->getDescriptor();
+ std::optional<DynamicAllocator::Form> AllocForm =
+ Allocator.getAllocationForm(Source);
+
+ if (!Allocator.deallocate(Source, BlockToDelete, S)) {
+ // Nothing has been deallocated, this must be a double-delete.
+ const SourceInfo &Loc = S.Current->getSource(OpPC);
+ S.FFDiag(Loc, diag::note_constexpr_double_delete);
+ return false;
+ }
+
+ assert(AllocForm);
+ DynamicAllocator::Form DeleteForm = DeleteIsArrayForm
+ ? DynamicAllocator::Form::Array
+ : DynamicAllocator::Form::NonArray;
+ return CheckNewDeleteForms(S, OpPC, *AllocForm, DeleteForm, BlockDesc,
+ Source);
+}
+
void diagnoseEnumValue(InterpState &S, CodePtr OpPC, const EnumDecl *ED,
const APSInt &Value) {
llvm::APInt Min;
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 41708910024476..dece95971b7617 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -2976,61 +2976,8 @@ inline bool AllocCN(InterpState &S, CodePtr OpPC, const Descriptor *ElementDesc,
return true;
}
-bool RunDestructors(InterpState &S, CodePtr OpPC, const Block *B);
-static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm) {
- if (!CheckDynamicMemoryAllocation(S, OpPC))
- return false;
-
- const Expr *Source = nullptr;
- const Block *BlockToDelete = nullptr;
- {
- // Extra scope for this so the block doesn't have this pointer
- // pointing to it when we destroy it.
- const Pointer &Ptr = S.Stk.pop<Pointer>();
-
- // Deleteing nullptr is always fine.
- if (Ptr.isZero())
- return true;
-
- if (!Ptr.isRoot() || Ptr.isOnePastEnd() || Ptr.isArrayElement()) {
- const SourceInfo &Loc = S.Current->getSource(OpPC);
- S.FFDiag(Loc, diag::note_constexpr_delete_subobject)
- << Ptr.toDiagnosticString(S.getASTContext()) << Ptr.isOnePastEnd();
- return false;
- }
-
- Source = Ptr.getDeclDesc()->asExpr();
- BlockToDelete = Ptr.block();
-
- if (!CheckDeleteSource(S, OpPC, Source, Ptr))
- return false;
- }
- assert(Source);
- assert(BlockToDelete);
-
- // Invoke destructors before deallocating the memory.
- if (!RunDestructors(S, OpPC, BlockToDelete))
- return false;
-
- DynamicAllocator &Allocator = S.getAllocator();
- const Descriptor *BlockDesc = BlockToDelete->getDescriptor();
- std::optional<DynamicAllocator::Form> AllocForm =
- Allocator.getAllocationForm(Source);
-
- if (!Allocator.deallocate(Source, BlockToDelete, S)) {
- // Nothing has been deallocated, this must be a double-delete.
- const SourceInfo &Loc = S.Current->getSource(OpPC);
- S.FFDiag(Loc, diag::note_constexpr_double_delete);
- return false;
- }
-
- assert(AllocForm);
- DynamicAllocator::Form DeleteForm = DeleteIsArrayForm
- ? DynamicAllocator::Form::Array
- : DynamicAllocator::Form::NonArray;
- return CheckNewDeleteForms(S, OpPC, *AllocForm, DeleteForm, BlockDesc,
- Source);
-}
+bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm,
+ bool IsGlobalDelete);
static inline bool IsConstantContext(InterpState &S, CodePtr OpPC) {
S.Stk.push<Boolean>(Boolean::from(S.inConstantContext()));
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 7b65138e5a3c94..4fa9b6d61d5ab9 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -818,7 +818,7 @@ def AllocCN : Opcode {
}
def Free : Opcode {
- let Args = [ArgBool];
+ let Args = [ArgBool, ArgBool];
}
def CheckNewTypeMismatch : Opcode {
diff --git a/clang/test/AST/ByteCode/new-delete.cpp b/clang/test/AST/ByteCode/new-delete.cpp
index 8c9d5d9c9b1d7c..8bcbed1aba21e9 100644
--- a/clang/test/AST/ByteCode/new-delete.cpp
+++ b/clang/test/AST/ByteCode/new-delete.cpp
@@ -552,8 +552,6 @@ namespace DeleteThis {
// both-note {{in call to 'super_secret_double_delete()'}}
}
-/// FIXME: This is currently diagnosed, but should work.
-/// If the destructor for S is _not_ virtual however, it should fail.
namespace CastedDelete {
struct S {
constexpr S(int *p) : p(p) {}
@@ -567,11 +565,10 @@ namespace CastedDelete {
constexpr int vdtor_1() {
int a;
- delete (S*)new T(&a); // expected-note {{delete of pointer to subobject}}
+ delete (S*)new T(&a);
return a;
}
- static_assert(vdtor_1() == 1); // expected-error {{not an integral constant expression}} \
- // expected-note {{in call to}}
+ static_assert(vdtor_1() == 1);
}
constexpr void use_after_free_2() { // both-error {{never produces a constant expression}}
@@ -778,6 +775,28 @@ namespace Placement {
static_assert(ok2()== 0);
}
+constexpr bool virt_delete(bool global) {
+ struct A {
+ virtual constexpr ~A() {}
+ };
+ struct B : A {
+ void operator delete(void *);
+ constexpr ~B() {}
+ };
+
+ A *p = new B;
+ if (global)
+ ::delete p;
+ else
+ delete p; // both-note {{call to class-specific 'operator delete'}}
+ return true;
+}
+static_assert(virt_delete(true));
+static_assert(virt_delete(false)); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to}}
+
+
+
#else
/// Make sure we reject this prior to C++20
constexpr int a() { // both-error {{never produces a constant expression}}
More information about the cfe-commits
mailing list