Skip to content

Conversation

@Fznamznon
Copy link
Contributor

@Fznamznon Fznamznon commented Oct 29, 2025

MSVC supports an extension allowing to delete an array of objects via pointer whose static type doesn't match its dynamic type. This is done via generation of special destructors - vector deleting destructors. MSVC's virtual tables always contain pointer to vector deleting destructor for classes with virtual destructors, so not having this extension not having this extension implemented causes clang to generate code that is not compatible with the code generated by MSVC, because clang always puts pointer to a scalar deleting destructor to the vtable. As a bonus the deletion of an array of polymorphic object will work just like it does with MSVC - no memory leaks and correct destructors are called.

This patch will cause clang to emit code that is compatible with code produced by MSVC but not compatible with code produced with clang of older versions, so the new behavior can be disabled via passing -fclang-abi-compat=21 (or lower).

This is yet another attempt to land vector deleting destructors support originally implemented by #133451.

This PR contains fixes for issues reported in the original PR as well as fixes for issues related to operator delete[] search reported in several issues like
#133950 (comment) #134265

Fixes #19772

MSVC supports and extension allowing to delete an array of objects
via pointer whose static type doesn't match its dynamic type. This is
done via generation of special destructors - vector deleting
destructors. MSVC's virtual tables always contain pointer to vector
deleting destructor for classes with virtual destructors, so not having
this extension not having this extension implemented causes clang to generate
code that is not compatible with the code generated by MSVC, because
clang always puts pointer to a scalar deleting destructor to the vtable.
As a bonus the deletion of an array of polymorphic object will work just
like it does with MSVC - no memory leaks and correct destructors are
called.

This patch will cause clang to emit code that is compatible with code
produced by MSVC but not compatible with code produced with clang of
older versions, so the new behavior can be disabled via passing
-fclang-abi-compat=21 (or lower).

This is yet another attempt to land vector deleting destructors support
originally implemented by llvm#133451.

This PR contains fixes for issues reported in the original PR as well as
fixes for issues related to operator delete[] search reported in
several issues like
llvm#133950 (comment)
llvm#134265

Fixes llvm#19772
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang:codegen IR generation bugs: mangling, exceptions, etc. debuginfo labels Oct 29, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 29, 2025

@llvm/pr-subscribers-debuginfo
@llvm/pr-subscribers-clang-modules

@llvm/pr-subscribers-clang

Author: Mariya Podchishchaeva (Fznamznon)

Changes

MSVC supports and extension allowing to delete an array of objects via pointer whose static type doesn't match its dynamic type. This is done via generation of special destructors - vector deleting destructors. MSVC's virtual tables always contain pointer to vector deleting destructor for classes with virtual destructors, so not having this extension not having this extension implemented causes clang to generate code that is not compatible with the code generated by MSVC, because clang always puts pointer to a scalar deleting destructor to the vtable. As a bonus the deletion of an array of polymorphic object will work just like it does with MSVC - no memory leaks and correct destructors are called.

This patch will cause clang to emit code that is compatible with code produced by MSVC but not compatible with code produced with clang of older versions, so the new behavior can be disabled via passing -fclang-abi-compat=21 (or lower).

This is yet another attempt to land vector deleting destructors support originally implemented by #133451.

This PR contains fixes for issues reported in the original PR as well as fixes for issues related to operator delete[] search reported in several issues like
#133950 (comment) #134265

Fixes #19772


Patch is 120.14 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/165598.diff

56 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+9)
  • (modified) clang/include/clang/AST/ASTContext.h (+27)
  • (modified) clang/include/clang/AST/ASTMutationListener.h (+9)
  • (modified) clang/include/clang/AST/DeclCXX.h (+6-10)
  • (modified) clang/include/clang/AST/VTableBuilder.h (+4-2)
  • (modified) clang/include/clang/Basic/ABI.h (+1)
  • (modified) clang/include/clang/Basic/TargetInfo.h (+5)
  • (modified) clang/include/clang/Sema/Sema.h (+2-1)
  • (modified) clang/include/clang/Serialization/ASTWriter.h (+4)
  • (modified) clang/lib/AST/ASTContext.cpp (+70)
  • (modified) clang/lib/AST/DeclCXX.cpp (+63-10)
  • (modified) clang/lib/AST/Expr.cpp (+3)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+2)
  • (modified) clang/lib/AST/MicrosoftMangle.cpp (+13-9)
  • (modified) clang/lib/AST/VTableBuilder.cpp (+16-6)
  • (modified) clang/lib/Basic/TargetInfo.cpp (+7)
  • (modified) clang/lib/CodeGen/CGCXX.cpp (+36-1)
  • (modified) clang/lib/CodeGen/CGCXXABI.cpp (+14)
  • (modified) clang/lib/CodeGen/CGCXXABI.h (+6)
  • (modified) clang/lib/CodeGen/CGClass.cpp (+95-2)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+7-1)
  • (modified) clang/lib/CodeGen/CGExprCXX.cpp (+41-4)
  • (modified) clang/lib/CodeGen/CGVTables.cpp (+3-1)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+50)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+6)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+4-1)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+56-14)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+29-2)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+7-6)
  • (modified) clang/lib/Serialization/ASTCommon.h (+3-1)
  • (modified) clang/lib/Serialization/ASTReaderDecl.cpp (+53-13)
  • (modified) clang/lib/Serialization/ASTWriter.cpp (+36)
  • (modified) clang/lib/Serialization/ASTWriterDecl.cpp (+2)
  • (modified) clang/test/CodeGenCXX/dllexport.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-extern-template.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-structors.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-thunks.cpp (+1-2)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vftables.cpp (+10-10)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-virtual-inheritance.cpp (+9-8)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-multiple-nonvirtual-inheritance-vdtors.cpp (+9-9)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-return-thunks.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-single-inheritance.cpp (+10-10)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-virtual-inheritance-vtordisps.cpp (+15-15)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-virtual-inheritance.cpp (+9-9)
  • (modified) clang/test/CodeGenCXX/microsoft-no-rtti-data.cpp (+1-1)
  • (added) clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp (+336)
  • (modified) clang/test/CodeGenCXX/vtable-consteval.cpp (+2-2)
  • (modified) clang/test/DebugInfo/CXX/windows-dtor.cpp (+1-1)
  • (added) clang/test/Modules/Inputs/msvc-vector-deleting-dtors/module.modulemap (+1)
  • (added) clang/test/Modules/Inputs/msvc-vector-deleting-dtors/msvc-vector-deleting-dtors.h (+16)
  • (added) clang/test/Modules/msvc-vector-deleting-destructors.cpp (+30)
  • (modified) clang/test/Modules/vtable-windows.cppm (+1-1)
  • (added) clang/test/PCH/Inputs/msvc-vector-deleting-dtors.h (+16)
  • (added) clang/test/PCH/msvc-vector-deleting-destructors.cpp (+34)
  • (modified) clang/test/Profile/cxx-abc-deleting-dtor.cpp (+4-5)
  • (added) clang/test/SemaCXX/gh134265.cpp (+62)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index add1582344a0e..844bb3d20244a 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -69,6 +69,13 @@ Potentially Breaking Changes
   call the member ``operator delete`` instead of the expected global
   delete operator. The old behavior is retained under ``-fclang-abi-compat=21``
   flag.
+- Clang now supports MSVC vector deleting destructors when targeting Windows.
+  This means that vtables of classes with virtual destructors will contain a
+  pointer to vector deleting destructor (instead of scalar deleting destructor)
+  which in fact is a different symbol with different name and linkage. This
+  may cause runtime failures if two binaries using the same class defining a
+  virtual destructor are compiled with different versions of clang.
+
 
 C/C++ Language Potentially Breaking Changes
 -------------------------------------------
@@ -549,6 +556,8 @@ Android Support
 Windows Support
 ^^^^^^^^^^^^^^^
 
+- Clang now supports MSVC vector deleting destructors (GH19772).
+
 LoongArch Support
 ^^^^^^^^^^^^^^^^^
 - Enable linker relaxation by default for loongarch64.
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 33aa2d343aa7a..9723e84f9a42c 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -370,6 +370,18 @@ class ASTContext : public RefCountedBase<ASTContext> {
   mutable llvm::DenseSet<const FunctionDecl *> DestroyingOperatorDeletes;
   mutable llvm::DenseSet<const FunctionDecl *> TypeAwareOperatorNewAndDeletes;
 
+  /// Global and array operators delete are only required for MSVC deleting
+  /// destructors support. Store them here to avoid keeping 4 pointers that are
+  /// not always used in each redeclaration of the destructor.
+  mutable llvm::DenseMap<const CXXDestructorDecl *, FunctionDecl *>
+      OperatorDeletesForVirtualDtor;
+  mutable llvm::DenseMap<const CXXDestructorDecl *, FunctionDecl *>
+      GlobalOperatorDeletesForVirtualDtor;
+  mutable llvm::DenseMap<const CXXDestructorDecl *, FunctionDecl *>
+      ArrayOperatorDeletesForVirtualDtor;
+  mutable llvm::DenseMap<const CXXDestructorDecl *, FunctionDecl *>
+      GlobalArrayOperatorDeletesForVirtualDtor;
+
   /// The next string literal "version" to allocate during constant evaluation.
   /// This is used to distinguish between repeated evaluations of the same
   /// string literal.
@@ -3473,6 +3485,21 @@ class ASTContext : public RefCountedBase<ASTContext> {
                                          bool IsTypeAware);
   bool isTypeAwareOperatorNewOrDelete(const FunctionDecl *FD) const;
 
+  enum OperatorDeleteKind {
+    Regular,
+    GlobalRegular,
+    Array,
+    ArrayGlobal
+  };
+
+  void addOperatorDeleteForVDtor(const CXXDestructorDecl *Dtor,
+                                 FunctionDecl *OperatorDelete,
+                                 OperatorDeleteKind K) const;
+  FunctionDecl *getOperatorDeleteForVDtor(const CXXDestructorDecl *Dtor,
+                                          OperatorDeleteKind K) const;
+  bool dtorHasOperatorDelete(const CXXDestructorDecl *Dtor,
+                             OperatorDeleteKind K) const;
+
   /// Retrieve the context for computing mangling numbers in the given
   /// DeclContext.
   MangleNumberingContext &getManglingNumberContext(const DeclContext *DC);
diff --git a/clang/include/clang/AST/ASTMutationListener.h b/clang/include/clang/AST/ASTMutationListener.h
index 352af42391782..96d72b4a5c04e 100644
--- a/clang/include/clang/AST/ASTMutationListener.h
+++ b/clang/include/clang/AST/ASTMutationListener.h
@@ -90,6 +90,15 @@ class ASTMutationListener {
   virtual void ResolvedOperatorGlobDelete(const CXXDestructorDecl *DD,
                                           const FunctionDecl *GlobDelete) {}
 
+  /// A virtual destructor's operator array delete has been resolved.
+  virtual void ResolvedOperatorArrayDelete(const CXXDestructorDecl *DD,
+                                          const FunctionDecl *ArrayDelete) {}
+
+  /// A virtual destructor's operator global array delete has been resolved.
+  virtual void
+  ResolvedOperatorGlobArrayDelete(const CXXDestructorDecl *DD,
+                                  const FunctionDecl *GlobArrayDelete) {}
+
   /// An implicit member got a definition.
   virtual void CompletedImplicitDefinition(const FunctionDecl *D) {}
 
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index dfa3befb27dd0..5c4ad3c45da19 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -2872,8 +2872,6 @@ class CXXDestructorDecl : public CXXMethodDecl {
 
   // FIXME: Don't allocate storage for these except in the first declaration
   // of a virtual destructor.
-  FunctionDecl *OperatorDelete = nullptr;
-  FunctionDecl *OperatorGlobalDelete = nullptr;
   Expr *OperatorDeleteThisArg = nullptr;
 
   CXXDestructorDecl(ASTContext &C, CXXRecordDecl *RD, SourceLocation StartLoc,
@@ -2900,14 +2898,12 @@ class CXXDestructorDecl : public CXXMethodDecl {
 
   void setOperatorDelete(FunctionDecl *OD, Expr *ThisArg);
   void setOperatorGlobalDelete(FunctionDecl *OD);
-
-  const FunctionDecl *getOperatorDelete() const {
-    return getCanonicalDecl()->OperatorDelete;
-  }
-
-  const FunctionDecl *getOperatorGlobalDelete() const {
-    return getCanonicalDecl()->OperatorGlobalDelete;
-  }
+  void setOperatorArrayDelete(FunctionDecl *OD);
+  void setGlobalOperatorArrayDelete(FunctionDecl *OD);
+  const FunctionDecl *getOperatorDelete() const;
+  const FunctionDecl *getOperatorGlobalDelete() const;
+  const FunctionDecl *getArrayOperatorDelete() const;
+  const FunctionDecl *getGlobalArrayOperatorDelete() const;
 
   Expr *getOperatorDeleteThisArg() const {
     return getCanonicalDecl()->OperatorDeleteThisArg;
diff --git a/clang/include/clang/AST/VTableBuilder.h b/clang/include/clang/AST/VTableBuilder.h
index a5de41dbc22f1..e1efe8cddcc5e 100644
--- a/clang/include/clang/AST/VTableBuilder.h
+++ b/clang/include/clang/AST/VTableBuilder.h
@@ -150,7 +150,7 @@ class VTableComponent {
 
   bool isRTTIKind() const { return isRTTIKind(getKind()); }
 
-  GlobalDecl getGlobalDecl() const {
+  GlobalDecl getGlobalDecl(bool HasVectorDeletingDtors) const {
     assert(isUsedFunctionPointerKind() &&
            "GlobalDecl can be created only from virtual function");
 
@@ -161,7 +161,9 @@ class VTableComponent {
     case CK_CompleteDtorPointer:
       return GlobalDecl(DtorDecl, CXXDtorType::Dtor_Complete);
     case CK_DeletingDtorPointer:
-      return GlobalDecl(DtorDecl, CXXDtorType::Dtor_Deleting);
+      return GlobalDecl(DtorDecl, (HasVectorDeletingDtors)
+                                      ? CXXDtorType::Dtor_VectorDeleting
+                                      : CXXDtorType::Dtor_Deleting);
     case CK_VCallOffset:
     case CK_VBaseOffset:
     case CK_OffsetToTop:
diff --git a/clang/include/clang/Basic/ABI.h b/clang/include/clang/Basic/ABI.h
index 8279529c316cf..ba1beb04a3260 100644
--- a/clang/include/clang/Basic/ABI.h
+++ b/clang/include/clang/Basic/ABI.h
@@ -37,6 +37,7 @@ enum CXXDtorType {
   Dtor_Base,     ///< Base object dtor
   Dtor_Comdat,   ///< The COMDAT used for dtors
   Dtor_Unified,  ///< GCC-style unified dtor
+  Dtor_VectorDeleting, ///< Vector deleting dtor
 };
 
 } // end namespace clang
diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h
index ea73ed915bf03..14d74488244b3 100644
--- a/clang/include/clang/Basic/TargetInfo.h
+++ b/clang/include/clang/Basic/TargetInfo.h
@@ -1793,6 +1793,11 @@ class TargetInfo : public TransferrableTargetInfo,
   /// destructor body.
   virtual bool callGlobalDeleteInDeletingDtor(const LangOptions &) const;
 
+  /// Controls whether to emit MSVC vector deleting destructors. The support for
+  /// vector deleting affects vtable layout and therefore is an ABI breaking
+  /// change. The support was only implemented at Clang 22 timeframe.
+  virtual bool emitVectorDeletingDtors(const LangOptions &) const;
+
   /// Controls if __builtin_longjmp / __builtin_setjmp can be lowered to
   /// llvm.eh.sjlj.longjmp / llvm.eh.sjlj.setjmp.
   virtual bool hasSjLjLowering() const {
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 52904c72d1cfc..f932ef0417fb0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -8565,7 +8565,8 @@ class Sema final : public SemaBase {
   FunctionDecl *FindDeallocationFunctionForDestructor(SourceLocation StartLoc,
                                                       CXXRecordDecl *RD,
                                                       bool Diagnose,
-                                                      bool LookForGlobal);
+                                                      bool LookForGlobal,
+                                                      DeclarationName Name);
 
   /// ActOnCXXDelete - Parsed a C++ 'delete' expression (C++ 5.3.5), as in:
   /// @code ::delete ptr; @endcode
diff --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h
index 28c3e55864057..d49572ce7439e 100644
--- a/clang/include/clang/Serialization/ASTWriter.h
+++ b/clang/include/clang/Serialization/ASTWriter.h
@@ -951,6 +951,10 @@ class ASTWriter : public ASTDeserializationListener,
                               Expr *ThisArg) override;
   void ResolvedOperatorGlobDelete(const CXXDestructorDecl *DD,
                                   const FunctionDecl *Delete) override;
+  void ResolvedOperatorArrayDelete(const CXXDestructorDecl *DD,
+                                   const FunctionDecl *Delete) override;
+  void ResolvedOperatorGlobArrayDelete(const CXXDestructorDecl *DD,
+                                       const FunctionDecl *Delete) override;
   void CompletedImplicitDefinition(const FunctionDecl *D) override;
   void InstantiationRequested(const ValueDecl *D) override;
   void VariableDefinitionInstantiated(const VarDecl *D) override;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 687cd46773f43..3c32a005a634d 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13328,6 +13328,76 @@ bool ASTContext::isTypeAwareOperatorNewOrDelete(const FunctionDecl *FD) const {
   return TypeAwareOperatorNewAndDeletes.contains(FD->getCanonicalDecl());
 }
 
+void ASTContext::addOperatorDeleteForVDtor(const CXXDestructorDecl *Dtor,
+                                           FunctionDecl *OperatorDelete,
+                                           OperatorDeleteKind K) const {
+  switch (K) {
+  case OperatorDeleteKind::Regular:
+    OperatorDeletesForVirtualDtor[Dtor->getCanonicalDecl()] = OperatorDelete;
+    break;
+  case OperatorDeleteKind::GlobalRegular:
+    GlobalOperatorDeletesForVirtualDtor[Dtor->getCanonicalDecl()] =
+        OperatorDelete;
+    break;
+  case OperatorDeleteKind::Array:
+    ArrayOperatorDeletesForVirtualDtor[Dtor->getCanonicalDecl()] =
+        OperatorDelete;
+    break;
+  case OperatorDeleteKind::ArrayGlobal:
+    GlobalArrayOperatorDeletesForVirtualDtor[Dtor->getCanonicalDecl()] =
+        OperatorDelete;
+    break;
+  default:
+    llvm_unreachable("Unknown operator delete kind");
+  }
+}
+
+bool ASTContext::dtorHasOperatorDelete(const CXXDestructorDecl *Dtor,
+                                       OperatorDeleteKind K) const {
+  switch (K) {
+  case OperatorDeleteKind::Regular:
+    return OperatorDeletesForVirtualDtor.contains(Dtor->getCanonicalDecl());
+  case OperatorDeleteKind::GlobalRegular:
+    return GlobalOperatorDeletesForVirtualDtor.contains(
+        Dtor->getCanonicalDecl());
+  case OperatorDeleteKind::Array:
+    return ArrayOperatorDeletesForVirtualDtor.contains(
+        Dtor->getCanonicalDecl());
+  case OperatorDeleteKind::ArrayGlobal:
+    return GlobalArrayOperatorDeletesForVirtualDtor.contains(
+        Dtor->getCanonicalDecl());
+  default:
+    llvm_unreachable("Unknown operator delete kind");
+  }
+  return false;
+}
+
+FunctionDecl *ASTContext::getOperatorDeleteForVDtor(const CXXDestructorDecl *Dtor,
+                                        OperatorDeleteKind K) const {
+  const CXXDestructorDecl *Canon = Dtor->getCanonicalDecl();
+  switch (K) {
+  case OperatorDeleteKind::Regular:
+    if (OperatorDeletesForVirtualDtor.contains(Canon))
+      return OperatorDeletesForVirtualDtor[Canon];
+    return nullptr;
+  case OperatorDeleteKind::GlobalRegular:
+    if (GlobalOperatorDeletesForVirtualDtor.contains(Canon))
+      return GlobalOperatorDeletesForVirtualDtor[Canon];
+    return nullptr;
+  case OperatorDeleteKind::Array:
+    if (ArrayOperatorDeletesForVirtualDtor.contains(Canon))
+      return ArrayOperatorDeletesForVirtualDtor[Canon];
+    return nullptr;
+  case OperatorDeleteKind::ArrayGlobal:
+    if (GlobalArrayOperatorDeletesForVirtualDtor.contains(Canon))
+      return GlobalArrayOperatorDeletesForVirtualDtor[Canon];
+    return nullptr;
+  default:
+    llvm_unreachable("Unknown operator delete kind");
+  }
+  return nullptr;
+}
+
 MangleNumberingContext &
 ASTContext::getManglingNumberContext(const DeclContext *DC) {
   assert(LangOpts.CPlusPlus);  // We don't need mangling numbers for plain C.
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 24e4f189cbe4a..344ba7b9b29fb 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -3110,12 +3110,15 @@ CXXDestructorDecl *CXXDestructorDecl::Create(
 }
 
 void CXXDestructorDecl::setOperatorDelete(FunctionDecl *OD, Expr *ThisArg) {
-  auto *First = cast<CXXDestructorDecl>(getFirstDecl());
-  if (OD && !First->OperatorDelete) {
-    First->OperatorDelete = OD;
-    First->OperatorDeleteThisArg = ThisArg;
+  assert(!OD || (OD->getDeclName().getCXXOverloadedOperator() == OO_Delete));
+  if (OD && !getASTContext().dtorHasOperatorDelete(
+          this, ASTContext::OperatorDeleteKind::Regular)) {
+    getASTContext().addOperatorDeleteForVDtor(
+        this, OD, ASTContext::OperatorDeleteKind::Regular);
+    getCanonicalDecl()->OperatorDeleteThisArg = ThisArg;
     if (auto *L = getASTMutationListener())
-      L->ResolvedOperatorDelete(First, OD, ThisArg);
+      L->ResolvedOperatorDelete(cast<CXXDestructorDecl>(getCanonicalDecl()), OD,
+                                ThisArg);
   }
 }
 
@@ -3127,14 +3130,63 @@ void CXXDestructorDecl::setOperatorGlobalDelete(FunctionDecl *OD) {
   assert(!OD ||
          (OD->getDeclName().getCXXOverloadedOperator() == OO_Delete &&
           OD->getDeclContext()->getRedeclContext()->isTranslationUnit()));
-  auto *Canonical = cast<CXXDestructorDecl>(getCanonicalDecl());
-  if (!Canonical->OperatorGlobalDelete) {
-    Canonical->OperatorGlobalDelete = OD;
+  if (OD && !getASTContext().dtorHasOperatorDelete(
+          this, ASTContext::OperatorDeleteKind::GlobalRegular)) {
+    getASTContext().addOperatorDeleteForVDtor(
+        this, OD, ASTContext::OperatorDeleteKind::GlobalRegular);
     if (auto *L = getASTMutationListener())
-      L->ResolvedOperatorGlobDelete(Canonical, OD);
+      L->ResolvedOperatorGlobDelete(cast<CXXDestructorDecl>(getCanonicalDecl()),
+                                    OD);
   }
 }
 
+void CXXDestructorDecl::setOperatorArrayDelete(FunctionDecl *OD) {
+  assert(!OD ||
+         (OD->getDeclName().getCXXOverloadedOperator() == OO_Array_Delete));
+  if (OD && !getASTContext().dtorHasOperatorDelete(
+          this, ASTContext::OperatorDeleteKind::Array)) {
+    getASTContext().addOperatorDeleteForVDtor(
+        this, OD, ASTContext::OperatorDeleteKind::Array);
+    if (auto *L = getASTMutationListener())
+      L->ResolvedOperatorArrayDelete(
+          cast<CXXDestructorDecl>(getCanonicalDecl()), OD);
+  }
+}
+
+void CXXDestructorDecl::setGlobalOperatorArrayDelete(FunctionDecl *OD) {
+  assert(!OD ||
+         (OD->getDeclName().getCXXOverloadedOperator() == OO_Array_Delete &&
+          OD->getDeclContext()->getRedeclContext()->isTranslationUnit()));
+  if (OD && !getASTContext().dtorHasOperatorDelete(
+          this, ASTContext::OperatorDeleteKind::ArrayGlobal)) {
+    getASTContext().addOperatorDeleteForVDtor(
+        this, OD, ASTContext::OperatorDeleteKind::ArrayGlobal);
+    if (auto *L = getASTMutationListener())
+      L->ResolvedOperatorGlobArrayDelete(
+          cast<CXXDestructorDecl>(getCanonicalDecl()), OD);
+  }
+}
+
+const FunctionDecl *CXXDestructorDecl::getOperatorDelete() const {
+  return getASTContext().getOperatorDeleteForVDtor(
+      this, ASTContext::OperatorDeleteKind::Regular);
+}
+
+const FunctionDecl *CXXDestructorDecl::getOperatorGlobalDelete() const {
+  return getASTContext().getOperatorDeleteForVDtor(
+      this, ASTContext::OperatorDeleteKind::GlobalRegular);
+}
+
+const FunctionDecl *CXXDestructorDecl::getArrayOperatorDelete() const {
+  return getASTContext().getOperatorDeleteForVDtor(
+      this, ASTContext::OperatorDeleteKind::Array);
+}
+
+const FunctionDecl *CXXDestructorDecl::getGlobalArrayOperatorDelete() const {
+  return getASTContext().getOperatorDeleteForVDtor(
+      this, ASTContext::OperatorDeleteKind::ArrayGlobal);
+}
+
 bool CXXDestructorDecl::isCalledByDelete(const FunctionDecl *OpDel) const {
   // C++20 [expr.delete]p6: If the value of the operand of the delete-
   // expression is not a null pointer value and the selected deallocation
@@ -3146,7 +3198,8 @@ bool CXXDestructorDecl::isCalledByDelete(const FunctionDecl *OpDel) const {
   // delete operator, as that destructor is never called, unless the
   // destructor is virtual (see [expr.delete]p8.1) because then the
   // selected operator depends on the dynamic type of the pointer.
-  const FunctionDecl *SelectedOperatorDelete = OpDel ? OpDel : OperatorDelete;
+  const FunctionDecl *SelectedOperatorDelete =
+      OpDel ? OpDel : getOperatorDelete();
   if (!SelectedOperatorDelete)
     return true;
 
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 340bb4b2ed6a3..1d914fa876759 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -71,6 +71,9 @@ const CXXRecordDecl *Expr::getBestDynamicClassType() const {
   if (const PointerType *PTy = DerivedType->getAs<PointerType>())
     DerivedType = PTy->getPointeeType();
 
+  while (const ArrayType *ATy = DerivedType->getAsArrayTypeUnsafe())
+    DerivedType = ATy->getElementType();
+
   if (DerivedType->isDependentType())
     return nullptr;
 
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 5572e0a7ae59c..a5bcf5c97e837 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -6040,6 +6040,8 @@ void CXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
   case Dtor_Comdat:
     Out << "D5";
     break;
+  case Dtor_VectorDeleting:
+    llvm_unreachable("Itanium ABI does not use vector deleting dtors");
   }
 }
 
diff --git a/clang/lib/AST/MicrosoftMangle.cpp b/clang/lib/AST/MicrosoftMangle.cpp
index f1baf9f49384b..551aa7bf3321c 100644
--- a/clang/lib/AST/MicrosoftMangle.cpp
+++ b/clang/lib/AST/MicrosoftMangle.cpp
@@ -1492,8 +1492,9 @@ void MicrosoftCXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
   // <operator-name> ::= ?_G # scalar deleting destructor
   case Dtor_Deleting: Out << "?_G"; return;
   // <operator-name> ::= ?_E # vector deleting destructor
-  // FIXME: Add a vector deleting dtor type.  It goes in the vtable, so we need
-  // it.
+  case Dtor_VectorDeleting:
+    Out << "?_E";
+    return;
   case Dtor_Comdat:
     llvm_unreachable("not expecting a COMDAT");
   case Dtor_Unified:
@@ -2913,9 +2914,12 @@ void MicrosoftCXXNameMangler::mangleFunctionType(const FunctionType *T,
   //               ::= @ # structors (they have no declared return type)
   if (IsStructor) {
     if (isa<CXXDestructorDecl>(D) && isStructorDecl(D)) {
-      // The scalar deleting destructor takes an extra int argument which is not
-      // reflected in the AST.
-      if (StructorType == Dtor_Deleting) {
+      // The deleting destructors take an extra argument of type int that
+      // indicates whether the storage for the object should be deleted and
+      // whether a single object or an array of objects is being destroyed. This
+      // extra argument is not reflected in the AST.
+      if (StructorType == Dtor_Deleting ||
+          StructorType == Dtor_VectorDeleting) {
         Out << (Pointe...
[truncated]

@github-actions
Copy link

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff origin/main HEAD --extensions cpp,h,cppm -- clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp clang/test/Modules/Inputs/msvc-vector-deleting-dtors/msvc-vector-deleting-dtors.h clang/test/Modules/msvc-vector-deleting-destructors.cpp clang/test/PCH/Inputs/msvc-vector-deleting-dtors.h clang/test/PCH/msvc-vector-deleting-destructors.cpp clang/test/SemaCXX/gh134265.cpp clang/include/clang/AST/ASTContext.h clang/include/clang/AST/ASTMutationListener.h clang/include/clang/AST/DeclCXX.h clang/include/clang/AST/VTableBuilder.h clang/include/clang/Basic/ABI.h clang/include/clang/Basic/TargetInfo.h clang/include/clang/Sema/Sema.h clang/include/clang/Serialization/ASTWriter.h clang/lib/AST/ASTContext.cpp clang/lib/AST/DeclCXX.cpp clang/lib/AST/Expr.cpp clang/lib/AST/ItaniumMangle.cpp clang/lib/AST/MicrosoftMangle.cpp clang/lib/AST/VTableBuilder.cpp clang/lib/Basic/TargetInfo.cpp clang/lib/CodeGen/CGCXX.cpp clang/lib/CodeGen/CGCXXABI.cpp clang/lib/CodeGen/CGCXXABI.h clang/lib/CodeGen/CGClass.cpp clang/lib/CodeGen/CGDebugInfo.cpp clang/lib/CodeGen/CGExprCXX.cpp clang/lib/CodeGen/CGVTables.cpp clang/lib/CodeGen/CodeGenModule.cpp clang/lib/CodeGen/CodeGenModule.h clang/lib/CodeGen/ItaniumCXXABI.cpp clang/lib/CodeGen/MicrosoftCXXABI.cpp clang/lib/Sema/SemaDeclCXX.cpp clang/lib/Sema/SemaExprCXX.cpp clang/lib/Serialization/ASTCommon.h clang/lib/Serialization/ASTReaderDecl.cpp clang/lib/Serialization/ASTWriter.cpp clang/lib/Serialization/ASTWriterDecl.cpp clang/test/CodeGenCXX/dllexport.cpp clang/test/CodeGenCXX/microsoft-abi-extern-template.cpp clang/test/CodeGenCXX/microsoft-abi-structors.cpp clang/test/CodeGenCXX/microsoft-abi-thunks.cpp clang/test/CodeGenCXX/microsoft-abi-vftables.cpp clang/test/CodeGenCXX/microsoft-abi-virtual-inheritance.cpp clang/test/CodeGenCXX/microsoft-abi-vtables-multiple-nonvirtual-inheritance-vdtors.cpp clang/test/CodeGenCXX/microsoft-abi-vtables-return-thunks.cpp clang/test/CodeGenCXX/microsoft-abi-vtables-single-inheritance.cpp clang/test/CodeGenCXX/microsoft-abi-vtables-virtual-inheritance-vtordisps.cpp clang/test/CodeGenCXX/microsoft-abi-vtables-virtual-inheritance.cpp clang/test/CodeGenCXX/microsoft-no-rtti-data.cpp clang/test/CodeGenCXX/vtable-consteval.cpp clang/test/DebugInfo/CXX/windows-dtor.cpp clang/test/Modules/vtable-windows.cppm clang/test/Profile/cxx-abc-deleting-dtor.cpp --diff_from_common_commit

⚠️
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing origin/main to the base branch/commit you want to compare against.
⚠️

View the diff from clang-format here.
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 9723e84f9..6e9e737dc 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3485,12 +3485,7 @@ public:
                                          bool IsTypeAware);
   bool isTypeAwareOperatorNewOrDelete(const FunctionDecl *FD) const;
 
-  enum OperatorDeleteKind {
-    Regular,
-    GlobalRegular,
-    Array,
-    ArrayGlobal
-  };
+  enum OperatorDeleteKind { Regular, GlobalRegular, Array, ArrayGlobal };
 
   void addOperatorDeleteForVDtor(const CXXDestructorDecl *Dtor,
                                  FunctionDecl *OperatorDelete,
diff --git a/clang/include/clang/AST/ASTMutationListener.h b/clang/include/clang/AST/ASTMutationListener.h
index 96d72b4a5..c8448a25c 100644
--- a/clang/include/clang/AST/ASTMutationListener.h
+++ b/clang/include/clang/AST/ASTMutationListener.h
@@ -92,7 +92,7 @@ public:
 
   /// A virtual destructor's operator array delete has been resolved.
   virtual void ResolvedOperatorArrayDelete(const CXXDestructorDecl *DD,
-                                          const FunctionDecl *ArrayDelete) {}
+                                           const FunctionDecl *ArrayDelete) {}
 
   /// A virtual destructor's operator global array delete has been resolved.
   virtual void
diff --git a/clang/include/clang/Basic/ABI.h b/clang/include/clang/Basic/ABI.h
index ba1beb04a..be3edccbf 100644
--- a/clang/include/clang/Basic/ABI.h
+++ b/clang/include/clang/Basic/ABI.h
@@ -32,11 +32,11 @@ enum CXXCtorType {
 
 /// C++ destructor types.
 enum CXXDtorType {
-  Dtor_Deleting, ///< Deleting dtor
-  Dtor_Complete, ///< Complete object dtor
-  Dtor_Base,     ///< Base object dtor
-  Dtor_Comdat,   ///< The COMDAT used for dtors
-  Dtor_Unified,  ///< GCC-style unified dtor
+  Dtor_Deleting,       ///< Deleting dtor
+  Dtor_Complete,       ///< Complete object dtor
+  Dtor_Base,           ///< Base object dtor
+  Dtor_Comdat,         ///< The COMDAT used for dtors
+  Dtor_Unified,        ///< GCC-style unified dtor
   Dtor_VectorDeleting, ///< Vector deleting dtor
 };
 
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 3c32a005a..04a2cfa15 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13372,8 +13372,9 @@ bool ASTContext::dtorHasOperatorDelete(const CXXDestructorDecl *Dtor,
   return false;
 }
 
-FunctionDecl *ASTContext::getOperatorDeleteForVDtor(const CXXDestructorDecl *Dtor,
-                                        OperatorDeleteKind K) const {
+FunctionDecl *
+ASTContext::getOperatorDeleteForVDtor(const CXXDestructorDecl *Dtor,
+                                      OperatorDeleteKind K) const {
   const CXXDestructorDecl *Canon = Dtor->getCanonicalDecl();
   switch (K) {
   case OperatorDeleteKind::Regular:
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 344ba7b9b..c16b1bb7a 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -3112,7 +3112,7 @@ CXXDestructorDecl *CXXDestructorDecl::Create(
 void CXXDestructorDecl::setOperatorDelete(FunctionDecl *OD, Expr *ThisArg) {
   assert(!OD || (OD->getDeclName().getCXXOverloadedOperator() == OO_Delete));
   if (OD && !getASTContext().dtorHasOperatorDelete(
-          this, ASTContext::OperatorDeleteKind::Regular)) {
+                this, ASTContext::OperatorDeleteKind::Regular)) {
     getASTContext().addOperatorDeleteForVDtor(
         this, OD, ASTContext::OperatorDeleteKind::Regular);
     getCanonicalDecl()->OperatorDeleteThisArg = ThisArg;
@@ -3131,7 +3131,7 @@ void CXXDestructorDecl::setOperatorGlobalDelete(FunctionDecl *OD) {
          (OD->getDeclName().getCXXOverloadedOperator() == OO_Delete &&
           OD->getDeclContext()->getRedeclContext()->isTranslationUnit()));
   if (OD && !getASTContext().dtorHasOperatorDelete(
-          this, ASTContext::OperatorDeleteKind::GlobalRegular)) {
+                this, ASTContext::OperatorDeleteKind::GlobalRegular)) {
     getASTContext().addOperatorDeleteForVDtor(
         this, OD, ASTContext::OperatorDeleteKind::GlobalRegular);
     if (auto *L = getASTMutationListener())
@@ -3144,7 +3144,7 @@ void CXXDestructorDecl::setOperatorArrayDelete(FunctionDecl *OD) {
   assert(!OD ||
          (OD->getDeclName().getCXXOverloadedOperator() == OO_Array_Delete));
   if (OD && !getASTContext().dtorHasOperatorDelete(
-          this, ASTContext::OperatorDeleteKind::Array)) {
+                this, ASTContext::OperatorDeleteKind::Array)) {
     getASTContext().addOperatorDeleteForVDtor(
         this, OD, ASTContext::OperatorDeleteKind::Array);
     if (auto *L = getASTMutationListener())
@@ -3158,7 +3158,7 @@ void CXXDestructorDecl::setGlobalOperatorArrayDelete(FunctionDecl *OD) {
          (OD->getDeclName().getCXXOverloadedOperator() == OO_Array_Delete &&
           OD->getDeclContext()->getRedeclContext()->isTranslationUnit()));
   if (OD && !getASTContext().dtorHasOperatorDelete(
-          this, ASTContext::OperatorDeleteKind::ArrayGlobal)) {
+                this, ASTContext::OperatorDeleteKind::ArrayGlobal)) {
     getASTContext().addOperatorDeleteForVDtor(
         this, OD, ASTContext::OperatorDeleteKind::ArrayGlobal);
     if (auto *L = getASTMutationListener())
diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp
index 42cf31288..51fbacf0a 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -1516,8 +1516,7 @@ static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD,
         CGF.createBasicBlock("dtor.call_glob_delete_after_array_destroy");
     llvm::BasicBlock *ClassDelete =
         CGF.createBasicBlock("dtor.call_class_delete_after_array_destroy");
-    CGF.Builder.CreateCondBr(ShouldCallGlobDelete, ClassDelete,
-                             GlobDelete);
+    CGF.Builder.CreateCondBr(ShouldCallGlobDelete, ClassDelete, GlobDelete);
     CGF.EmitBlock(ClassDelete);
     CGF.EmitDeleteCall(Dtor->getArrayOperatorDelete(), allocatedPtr,
                        CGF.getContext().getCanonicalTagType(ClassDecl));
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 573456e66..e51853670 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -11210,10 +11210,9 @@ bool Sema::CheckDestructor(CXXDestructorDecl *Destructor) {
                                                     VDeleteName);
           Destructor->setGlobalOperatorArrayDelete(GlobalArrOperatorDelete);
         } else if (!ArrOperatorDelete) {
-          ArrOperatorDelete =
-              FindDeallocationFunctionForDestructor(Loc, RD, /*Diagnose*/ false,
-                                                    /*LookForGlobal*/ true,
-                                                    VDeleteName);
+          ArrOperatorDelete = FindDeallocationFunctionForDestructor(
+              Loc, RD, /*Diagnose*/ false,
+              /*LookForGlobal*/ true, VDeleteName);
         }
         assert(ArrOperatorDelete &&
                "Should've found at least global array delete");
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index cbc0d9ece..0ee8c3511 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -4897,7 +4897,7 @@ void ASTDeclReader::UpdateDecl(Decl *D) {
       ASTContext &C = Reader.getContext();
       if (!C.dtorHasOperatorDelete(Dtor, ASTContext::OperatorDeleteKind::Array))
         C.addOperatorDeleteForVDtor(Dtor, Del,
-                                   ASTContext::OperatorDeleteKind::Array);
+                                    ASTContext::OperatorDeleteKind::Array);
       break;
     }
     case DeclUpdateKind::CXXResolvedDtorGlobArrayDelete: {

@Fznamznon Fznamznon requested a review from tahonermann October 29, 2025 17:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category debuginfo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement vector deleting destructors

2 participants