Skip to content

Reland [MS][clang] Add support for vector deleting destructors #133451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 31, 2025

Conversation

Fznamznon
Copy link
Contributor

Whereas it is UB in terms of the standard to delete an array of objects
via pointer whose static type doesn't match its dynamic type, MSVC
supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case,
currently not having this extension implemented causes clang to generate
code that is not compatible with the code generated by MSVC, because
clang always puts scalar deleting destructor to the vftable. This PR
aims to resolve these problems.

It was reverted due to link time errors in chromium with sanitizer coverage enabled,
which is fixed by #131929 .

The second commit of this PR also contains a fix for a runtime failure in chromium reported
in #126240 (comment) .

Fixes #19772

@Fznamznon Fznamznon requested review from rnk, zmodem and tahonermann March 28, 2025 15:03
@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 Mar 28, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 28, 2025

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

@llvm/pr-subscribers-clang-modules

Author: Mariya Podchishchaeva (Fznamznon)

Changes

Whereas it is UB in terms of the standard to delete an array of objects
via pointer whose static type doesn't match its dynamic type, MSVC
supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case,
currently not having this extension implemented causes clang to generate
code that is not compatible with the code generated by MSVC, because
clang always puts scalar deleting destructor to the vftable. This PR
aims to resolve these problems.

It was reverted due to link time errors in chromium with sanitizer coverage enabled,
which is fixed by #131929 .

The second commit of this PR also contains a fix for a runtime failure in chromium reported
in #126240 (comment) .

Fixes #19772


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

34 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/include/clang/AST/VTableBuilder.h (+4-2)
  • (modified) clang/include/clang/Basic/ABI.h (+5-4)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+2)
  • (modified) clang/lib/AST/MicrosoftMangle.cpp (+13-9)
  • (modified) clang/lib/AST/VTableBuilder.cpp (+13-6)
  • (modified) clang/lib/CodeGen/CGCXX.cpp (+36-1)
  • (modified) clang/lib/CodeGen/CGCXXABI.cpp (+14)
  • (modified) clang/lib/CodeGen/CGCXXABI.h (+7)
  • (modified) clang/lib/CodeGen/CGClass.cpp (+73-4)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CGExprCXX.cpp (+38-4)
  • (modified) clang/lib/CodeGen/CGVTables.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+48)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+6)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+5-1)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+35-14)
  • (modified) clang/test/CodeGenCXX/debug-info-windows-dtor.cpp (+1-1)
  • (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 (+3-2)
  • (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 (+129)
  • (modified) clang/test/CodeGenCXX/vtable-consteval.cpp (+2-2)
  • (modified) clang/test/Modules/vtable-windows.cppm (+1-1)
  • (modified) clang/test/Profile/cxx-abc-deleting-dtor.cpp (+4-5)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index eaecead0e6b9d..fc1c4b571ddbc 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -432,6 +432,7 @@ Windows Support
 - Clang now can process the `i128` and `ui128` integeral suffixes when MSVC
   extensions are enabled. This allows for properly processing ``intsafe.h`` in
   the Windows SDK.
+- Clang now supports MSVC vector deleting destructors (GH19772).
 
 LoongArch Support
 ^^^^^^^^^^^^^^^^^
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 231bad799a42c..48969e4f295c3 100644
--- a/clang/include/clang/Basic/ABI.h
+++ b/clang/include/clang/Basic/ABI.h
@@ -31,10 +31,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_Deleting,      ///< Deleting dtor
+  Dtor_Complete,      ///< Complete object dtor
+  Dtor_Base,          ///< Base object dtor
+  Dtor_Comdat,        ///< The COMDAT used for dtors
+  Dtor_VectorDeleting ///< Vector deleting dtor
 };
 
 } // end namespace clang
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 981cdb3c806b1..49a04861ae25d 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -6004,6 +6004,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 15de407e122d8..7e964124a9fec 100644
--- a/clang/lib/AST/MicrosoftMangle.cpp
+++ b/clang/lib/AST/MicrosoftMangle.cpp
@@ -1484,8 +1484,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");
   }
@@ -2886,9 +2887,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 << (PointersAre64Bit ? "PEAXI@Z" : "PAXI@Z");
         return;
       }
@@ -3861,10 +3865,10 @@ void MicrosoftMangleContextImpl::mangleCXXDtorThunk(const CXXDestructorDecl *DD,
                                                     const ThunkInfo &Thunk,
                                                     bool /*ElideOverrideInfo*/,
                                                     raw_ostream &Out) {
-  // FIXME: Actually, the dtor thunk should be emitted for vector deleting
-  // dtors rather than scalar deleting dtors. Just use the vector deleting dtor
-  // mangling manually until we support both deleting dtor types.
-  assert(Type == Dtor_Deleting);
+  // The dtor thunk should use vector deleting dtor mangling, however as an
+  // optimization we may end up emitting only scalar deleting dtor body, so just
+  // use the vector deleting dtor mangling manually.
+  assert(Type == Dtor_Deleting || Type == Dtor_VectorDeleting);
   msvc_hashing_ostream MHO(Out);
   MicrosoftCXXNameMangler Mangler(*this, MHO, DD, Type);
   Mangler.getStream() << "??_E";
diff --git a/clang/lib/AST/VTableBuilder.cpp b/clang/lib/AST/VTableBuilder.cpp
index 18893b996b5d6..b7c6ad85b8889 100644
--- a/clang/lib/AST/VTableBuilder.cpp
+++ b/clang/lib/AST/VTableBuilder.cpp
@@ -1735,8 +1735,8 @@ void ItaniumVTableBuilder::LayoutPrimaryAndSecondaryVTables(
       const CXXMethodDecl *MD = I.first;
       const MethodInfo &MI = I.second;
       if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
-        MethodVTableIndices[GlobalDecl(DD, Dtor_Complete)]
-            = MI.VTableIndex - AddressPoint;
+        MethodVTableIndices[GlobalDecl(DD, Dtor_Complete)] =
+            MI.VTableIndex - AddressPoint;
         MethodVTableIndices[GlobalDecl(DD, Dtor_Deleting)]
             = MI.VTableIndex + 1 - AddressPoint;
       } else {
@@ -2657,7 +2657,11 @@ class VFTableBuilder {
       MethodVFTableLocation Loc(MI.VBTableIndex, WhichVFPtr.getVBaseWithVPtr(),
                                 WhichVFPtr.NonVirtualOffset, MI.VFTableIndex);
       if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
-        MethodVFTableLocations[GlobalDecl(DD, Dtor_Deleting)] = Loc;
+        // In Microsoft ABI vftable always references vector deleting dtor.
+        CXXDtorType DtorTy = Context.getTargetInfo().getCXXABI().isMicrosoft()
+                                 ? Dtor_VectorDeleting
+                                 : Dtor_Deleting;
+        MethodVFTableLocations[GlobalDecl(DD, DtorTy)] = Loc;
       } else {
         MethodVFTableLocations[MD] = Loc;
       }
@@ -3287,7 +3291,10 @@ void VFTableBuilder::dumpLayout(raw_ostream &Out) {
       const CXXDestructorDecl *DD = Component.getDestructorDecl();
 
       DD->printQualifiedName(Out);
-      Out << "() [scalar deleting]";
+      if (Context.getTargetInfo().getCXXABI().isMicrosoft())
+        Out << "() [vector deleting]";
+      else
+        Out << "() [scalar deleting]";
 
       if (DD->isPureVirtual())
         Out << " [pure]";
@@ -3758,7 +3765,7 @@ void MicrosoftVTableContext::dumpMethodLocations(
         PredefinedIdentKind::PrettyFunctionNoVirtual, MD);
 
     if (isa<CXXDestructorDecl>(MD)) {
-      IndicesMap[I.second] = MethodName + " [scalar deleting]";
+      IndicesMap[I.second] = MethodName + " [vector deleting]";
     } else {
       IndicesMap[I.second] = MethodName;
     }
@@ -3875,7 +3882,7 @@ MicrosoftVTableContext::getMethodVFTableLocation(GlobalDecl GD) {
   assert(hasVtableSlot(cast<CXXMethodDecl>(GD.getDecl())) &&
          "Only use this method for virtual methods or dtors");
   if (isa<CXXDestructorDecl>(GD.getDecl()))
-    assert(GD.getDtorType() == Dtor_Deleting);
+    assert(GD.getDtorType() == Dtor_VectorDeleting);
 
   GD = GD.getCanonicalDecl();
 
diff --git a/clang/lib/CodeGen/CGCXX.cpp b/clang/lib/CodeGen/CGCXX.cpp
index 78a7b021855b7..6f47e24eed5b3 100644
--- a/clang/lib/CodeGen/CGCXX.cpp
+++ b/clang/lib/CodeGen/CGCXX.cpp
@@ -175,7 +175,6 @@ bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
   // requires explicit comdat support in the IL.
   if (llvm::GlobalValue::isWeakForLinker(TargetLinkage))
     return true;
-
   // Create the alias with no name.
   auto *Alias = llvm::GlobalAlias::create(AliasValueType, 0, Linkage, "",
                                           Aliasee, &getModule());
@@ -201,6 +200,42 @@ bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
   return false;
 }
 
+/// Emit a definition as a global alias for another definition, unconditionally.
+void CodeGenModule::EmitDefinitionAsAlias(GlobalDecl AliasDecl,
+                                          GlobalDecl TargetDecl) {
+
+  llvm::Type *AliasValueType = getTypes().GetFunctionType(AliasDecl);
+
+  StringRef MangledName = getMangledName(AliasDecl);
+  llvm::GlobalValue *Entry = GetGlobalValue(MangledName);
+  if (Entry && !Entry->isDeclaration())
+    return;
+  auto *Aliasee = cast<llvm::GlobalValue>(GetAddrOfGlobal(TargetDecl));
+
+  // Determine the linkage type for the alias.
+  llvm::GlobalValue::LinkageTypes Linkage = getFunctionLinkage(AliasDecl);
+
+  // Create the alias with no name.
+  auto *Alias = llvm::GlobalAlias::create(AliasValueType, 0, Linkage, "",
+                                          Aliasee, &getModule());
+  // Destructors are always unnamed_addr.
+  Alias->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
+
+  if (Entry) {
+    assert(Entry->getValueType() == AliasValueType &&
+           Entry->getAddressSpace() == Alias->getAddressSpace() &&
+           "declaration exists with different type");
+    Alias->takeName(Entry);
+    Entry->replaceAllUsesWith(Alias);
+    Entry->eraseFromParent();
+  } else {
+    Alias->setName(MangledName);
+  }
+
+  // Set any additional necessary attributes for the alias.
+  SetCommonAttributes(AliasDecl, Alias);
+}
+
 llvm::Function *CodeGenModule::codegenCXXStructor(GlobalDecl GD) {
   const CGFunctionInfo &FnInfo = getTypes().arrangeCXXStructorDeclaration(GD);
   auto *Fn = cast<llvm::Function>(
diff --git a/clang/lib/CodeGen/CGCXXABI.cpp b/clang/lib/CodeGen/CGCXXABI.cpp
index fd35f2adfa2d2..9f77fbec21380 100644
--- a/clang/lib/CodeGen/CGCXXABI.cpp
+++ b/clang/lib/CodeGen/CGCXXABI.cpp
@@ -272,6 +272,20 @@ void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, Address ptr,
   numElements = readArrayCookieImpl(CGF, allocAddr, cookieSize);
 }
 
+void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, Address ptr,
+                               QualType eltTy, llvm::Value *&numElements,
+                               llvm::Value *&allocPtr, CharUnits &cookieSize) {
+  assert(eltTy.isDestructedType());
+
+  // Derive a char* in the same address space as the pointer.
+  ptr = ptr.withElementType(CGF.Int8Ty);
+
+  cookieSize = getArrayCookieSizeImpl(eltTy);
+  Address allocAddr = CGF.Builder.CreateConstInBoundsByteGEP(ptr, -cookieSize);
+  allocPtr = allocAddr.emitRawPointer(CGF);
+  numElements = readArrayCookieImpl(CGF, allocAddr, cookieSize);
+}
+
 llvm::Value *CGCXXABI::readArrayCookieImpl(CodeGenFunction &CGF,
                                            Address ptr,
                                            CharUnits cookieSize) {
diff --git a/clang/lib/CodeGen/CGCXXABI.h b/clang/lib/CodeGen/CGCXXABI.h
index 687ff7fb84444..148a7ba6df7e6 100644
--- a/clang/lib/CodeGen/CGCXXABI.h
+++ b/clang/lib/CodeGen/CGCXXABI.h
@@ -275,6 +275,7 @@ class CGCXXABI {
   virtual CatchTypeInfo getCatchAllTypeInfo();
 
   virtual bool shouldTypeidBeNullChecked(QualType SrcRecordTy) = 0;
+  virtual bool hasVectorDeletingDtors() = 0;
   virtual void EmitBadTypeidCall(CodeGenFunction &CGF) = 0;
   virtual llvm::Value *EmitTypeid(CodeGenFunction &CGF, QualType SrcRecordTy,
                                   Address ThisPtr,
@@ -575,6 +576,12 @@ class CGCXXABI {
                                QualType ElementType, llvm::Value *&NumElements,
                                llvm::Value *&AllocPtr, CharUnits &CookieSize);
 
+  /// Reads the array cookie associated with the given pointer,
+  /// that should have one.
+  void ReadArrayCookie(CodeGenFunction &CGF, Address Ptr, QualType ElementType,
+                       llvm::Value *&NumElements, llvm::Value *&AllocPtr,
+                       CharUnits &CookieSize);
+
   /// Return whether the given global decl needs a VTT parameter.
   virtual bool NeedsVTTParameter(GlobalDecl GD);
 
diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp
index 98c93b5bb4883..f508930cc9f2b 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -1432,6 +1432,70 @@ static bool CanSkipVTablePointerInitialization(CodeGenFunction &CGF,
   return true;
 }
 
+static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD,
+                                         CodeGenFunction &CGF,
+                                         llvm::Value *ShouldDeleteCondition) {
+  Address ThisPtr = CGF.LoadCXXThisAddress();
+  llvm::BasicBlock *ScalarBB = CGF.createBasicBlock("dtor.scalar");
+  llvm::BasicBlock *callDeleteBB =
+      CGF.createBasicBlock("dtor.call_delete_after_array_destroy");
+  llvm::BasicBlock *VectorBB = CGF.createBasicBlock("dtor.vector");
+  auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
+  llvm::Value *CheckTheBitForArrayDestroy = CGF.Builder.CreateAnd(
+      ShouldDeleteCondition, llvm::ConstantInt::get(CondTy, 2));
+  llvm::Value *ShouldDestroyArray =
+      CGF.Builder.CreateIsNull(CheckTheBitForArrayDestroy);
+  CGF.Builder.CreateCondBr(ShouldDestroyArray, ScalarBB, VectorBB);
+
+  CGF.EmitBlock(VectorBB);
+
+  llvm::Value *numElements = nullptr;
+  llvm::Value *allocatedPtr = nullptr;
+  CharUnits cookieSize;
+  QualType EltTy = DD->getThisType()->getPointeeType();
+  CGF.CGM.getCXXABI().ReadArrayCookie(CGF, ThisPtr, EltTy, numElements,
+                                      allocatedPtr, cookieSize);
+
+  // Destroy the elements.
+  QualType::DestructionKind dtorKind = EltTy.isDestructedType();
+
+  assert(dtorKind);
+  assert(numElements && "no element count for a type with a destructor!");
+
+  CharUnits elementSize = CGF.getContext().getTypeSizeInChars(EltTy);
+  CharUnits elementAlign =
+      ThisPtr.getAlignment().alignmentOfArrayElement(elementSize);
+
+  llvm::Value *arrayBegin = ThisPtr.emitRawPointer(CGF);
+  llvm::Value *arrayEnd = CGF.Builder.CreateInBoundsGEP(
+      ThisPtr.getElementType(), arrayBegin, numElements, "delete.end");
+
+  // We already checked that the array is not 0-length before entering vector
+  // deleting dtor.
+  CGF.emitArrayDestroy(arrayBegin, arrayEnd, EltTy, elementAlign,
+                       CGF.getDestroyer(dtorKind),
+                       /*checkZeroLength*/ false, CGF.needsEHCleanup(dtorKind));
+
+  llvm::BasicBlock *VectorBBCont = CGF.createBasicBlock("dtor.vector.cont");
+  CGF.EmitBlock(VectorBBCont);
+
+  llvm::Value *CheckTheBitForDeleteCall = CGF.Builder.CreateAnd(
+      ShouldDeleteCondition, llvm::ConstantInt::get(CondTy, 1));
+
+  llvm::Value *ShouldCallDelete =
+      CGF.Builder.CreateIsNull(CheckTheBitForDeleteCall);
+  CGF.Builder.CreateCondBr(ShouldCallDelete, CGF.ReturnBlock.getBlock(),
+                           callDeleteBB);
+  CGF.EmitBlock(callDeleteBB);
+  const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CGF.CurCodeDecl);
+  const CXXRecordDecl *ClassDecl = Dtor->getParent();
+  CGF.EmitDeleteCall(Dtor->getOperatorDelete(), allocatedPtr,
+                     CGF.getContext().getTagDeclType(ClassDecl));
+
+  CGF.EmitBranchThroughCleanup(CGF.ReturnBlock);
+  CGF.EmitBlock(ScalarBB);
+}
+
 /// EmitDestructorBody - Emits the body of the current destructor.
 void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CurGD.getDecl());
@@ -1461,7 +1525,9 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   // outside of the function-try-block, which means it's always
   // possible to delegate the destructor body to the complete
   // destructor.  Do so.
-  if (DtorType == Dtor_Deleting) {
+  if (DtorType == Dtor_Deleting || DtorType == Dtor_VectorDeleting) {
+    if (CXXStructorImplicitParamValue && DtorType == Dtor_VectorDeleting)
+      EmitConditionalArrayDtorCall(Dtor, *this, CXXStructorImplicitParamValue);
     RunCleanupsScope DtorEpilogue(*this);
     EnterDtorCleanups(Dtor, Dtor_Deleting);
     if (HaveInsertPoint()) {
@@ -1490,6 +1556,8 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   switch (DtorType) {
   case Dtor_Comdat: llvm_unreachable("not expecting a COMDAT");
   case Dtor_Deleting: llvm_unreachable("already handled deleting case");
+  case Dtor_VectorDeleting:
+    llvm_unreachable("already handled vector deleting case");
 
   case Dtor_Complete:
     assert((Body || getTarget().getCXXABI().isMicrosoft()) &&
@@ -1572,7 +1640,6 @@ namespace {
       return CGF.EmitScalarExpr(ThisArg);
     return CGF.LoadCXXThis();
   }
-
   /// Call the operator delete associated with the current destructor.
   struct CallDtorDelete final : EHScopeStack::Cleanup {
     CallDtorDelete() {}
@@ -1591,8 +1658,10 @@ namespace {
                                      bool ReturnAfterDelete) {
     llvm::BasicBlock *callDeleteBB = CGF.createBasicBlock("dtor.call_delete");
     llvm::BasicBlock *continueBB = CGF.createBasicBlock("dtor.continue");
-    llvm::Value *ShouldCallDelete
-      = CGF.Builder.CreateIsNull(ShouldDeleteCondition);
+    auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
+    llvm::Value *CheckTheBit = CGF.Builder.CreateAnd(
+        ShouldDeleteCondition, llvm::ConstantInt::get(CondTy, 1));
+    llvm::Value *ShouldCallDelete = CGF.Builder.CreateIsNull(CheckTheBit);
     CGF.Builder.CreateCondBr(ShouldCallDelete, continueBB, callDeleteBB);
 
     CGF.EmitBlock(callDeleteBB);
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 54025b767dc81..286007b30d7c8 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -2119,7 +2119,8 @@ llvm::DISubprogram *CGDebugInfo::CreateCXXMemberFunction(
       // Emit MS ABI vftable information.  There is only one entry for the
       // deleting dtor.
       const auto *DD = dyn_cast<CXXDestructorDecl>(Method);
-      GlobalDecl GD = DD ? GlobalDecl(DD, Dtor_Deleting) : GlobalDecl(Method);
+      GlobalDecl GD =
+          DD ? GlobalDecl(DD, Dtor_VectorDeleting) : GlobalDecl(Method);
       MethodVFTableLocation ML =
           CGM.getMicrosoftVTableContext().getMethodVFTableLocation(GD);
       VIndex = ML.Index;
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 5d96959065dd9..5c11c0bceade7 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1209,6 +1209,8 @@ void CodeGenFunction::EmitNewArrayInitializer(
     EmitCXXAggrConstructorCall(Ctor, NumElements, CurPtr, CCE,
                                /*NewPointerIsChecked*/true,
                                CCE->requiresZeroInitialization());
+    if (CGM.getCXXABI().hasVectorDeletingDtors())
+      CGM.requireVectorDestructorDefinition(Ctor->getParent());
     return;
   }
 
@@ -1912,10 +1914,8 @@ static void EmitDestroyingObjectDelete(CodeGenFunction &CGF,
 /// Emit the code for deleting a single object.
 /// \return \c true if we started emitting UnconditionalDeleteBlock, \c false
 /// if not.
-static bool EmitObjectDelete(CodeGenFunction &CGF,
-                             const CXXDeleteExpr *DE,
-                             Address Ptr,
-                             QualType ElementType,
+static bool EmitObjectDelete(CodeGenFunction &CGF, const CXXDeleteExpr *DE,
+                             Address Ptr, QualType ElementType,
                              llvm::BasicBlock *UnconditionalDeleteBlock) {
   // C++11 [expr.delete]p3:
   //   If the static type of the object to be deleted is different from its
@@ -2131,6 +2131,40 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) {
 
   assert(ConvertTypeForMem(DeleteTy) == Ptr.getElementType());
 
+  if (E->isArrayForm() && CGM.getCXXABI().hasVectorDeletingDtors...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Mar 28, 2025

@llvm/pr-subscribers-clang-codegen

Author: Mariya Podchishchaeva (Fznamznon)

Changes

Whereas it is UB in terms of the standard to delete an array of objects
via pointer whose static type doesn't match its dynamic type, MSVC
supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case,
currently not having this extension implemented causes clang to generate
code that is not compatible with the code generated by MSVC, because
clang always puts scalar deleting destructor to the vftable. This PR
aims to resolve these problems.

It was reverted due to link time errors in chromium with sanitizer coverage enabled,
which is fixed by #131929 .

The second commit of this PR also contains a fix for a runtime failure in chromium reported
in #126240 (comment) .

Fixes #19772


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

34 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/include/clang/AST/VTableBuilder.h (+4-2)
  • (modified) clang/include/clang/Basic/ABI.h (+5-4)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+2)
  • (modified) clang/lib/AST/MicrosoftMangle.cpp (+13-9)
  • (modified) clang/lib/AST/VTableBuilder.cpp (+13-6)
  • (modified) clang/lib/CodeGen/CGCXX.cpp (+36-1)
  • (modified) clang/lib/CodeGen/CGCXXABI.cpp (+14)
  • (modified) clang/lib/CodeGen/CGCXXABI.h (+7)
  • (modified) clang/lib/CodeGen/CGClass.cpp (+73-4)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CGExprCXX.cpp (+38-4)
  • (modified) clang/lib/CodeGen/CGVTables.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+48)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+6)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+5-1)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+35-14)
  • (modified) clang/test/CodeGenCXX/debug-info-windows-dtor.cpp (+1-1)
  • (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 (+3-2)
  • (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 (+129)
  • (modified) clang/test/CodeGenCXX/vtable-consteval.cpp (+2-2)
  • (modified) clang/test/Modules/vtable-windows.cppm (+1-1)
  • (modified) clang/test/Profile/cxx-abc-deleting-dtor.cpp (+4-5)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index eaecead0e6b9d..fc1c4b571ddbc 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -432,6 +432,7 @@ Windows Support
 - Clang now can process the `i128` and `ui128` integeral suffixes when MSVC
   extensions are enabled. This allows for properly processing ``intsafe.h`` in
   the Windows SDK.
+- Clang now supports MSVC vector deleting destructors (GH19772).
 
 LoongArch Support
 ^^^^^^^^^^^^^^^^^
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 231bad799a42c..48969e4f295c3 100644
--- a/clang/include/clang/Basic/ABI.h
+++ b/clang/include/clang/Basic/ABI.h
@@ -31,10 +31,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_Deleting,      ///< Deleting dtor
+  Dtor_Complete,      ///< Complete object dtor
+  Dtor_Base,          ///< Base object dtor
+  Dtor_Comdat,        ///< The COMDAT used for dtors
+  Dtor_VectorDeleting ///< Vector deleting dtor
 };
 
 } // end namespace clang
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 981cdb3c806b1..49a04861ae25d 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -6004,6 +6004,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 15de407e122d8..7e964124a9fec 100644
--- a/clang/lib/AST/MicrosoftMangle.cpp
+++ b/clang/lib/AST/MicrosoftMangle.cpp
@@ -1484,8 +1484,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");
   }
@@ -2886,9 +2887,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 << (PointersAre64Bit ? "PEAXI@Z" : "PAXI@Z");
         return;
       }
@@ -3861,10 +3865,10 @@ void MicrosoftMangleContextImpl::mangleCXXDtorThunk(const CXXDestructorDecl *DD,
                                                     const ThunkInfo &Thunk,
                                                     bool /*ElideOverrideInfo*/,
                                                     raw_ostream &Out) {
-  // FIXME: Actually, the dtor thunk should be emitted for vector deleting
-  // dtors rather than scalar deleting dtors. Just use the vector deleting dtor
-  // mangling manually until we support both deleting dtor types.
-  assert(Type == Dtor_Deleting);
+  // The dtor thunk should use vector deleting dtor mangling, however as an
+  // optimization we may end up emitting only scalar deleting dtor body, so just
+  // use the vector deleting dtor mangling manually.
+  assert(Type == Dtor_Deleting || Type == Dtor_VectorDeleting);
   msvc_hashing_ostream MHO(Out);
   MicrosoftCXXNameMangler Mangler(*this, MHO, DD, Type);
   Mangler.getStream() << "??_E";
diff --git a/clang/lib/AST/VTableBuilder.cpp b/clang/lib/AST/VTableBuilder.cpp
index 18893b996b5d6..b7c6ad85b8889 100644
--- a/clang/lib/AST/VTableBuilder.cpp
+++ b/clang/lib/AST/VTableBuilder.cpp
@@ -1735,8 +1735,8 @@ void ItaniumVTableBuilder::LayoutPrimaryAndSecondaryVTables(
       const CXXMethodDecl *MD = I.first;
       const MethodInfo &MI = I.second;
       if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
-        MethodVTableIndices[GlobalDecl(DD, Dtor_Complete)]
-            = MI.VTableIndex - AddressPoint;
+        MethodVTableIndices[GlobalDecl(DD, Dtor_Complete)] =
+            MI.VTableIndex - AddressPoint;
         MethodVTableIndices[GlobalDecl(DD, Dtor_Deleting)]
             = MI.VTableIndex + 1 - AddressPoint;
       } else {
@@ -2657,7 +2657,11 @@ class VFTableBuilder {
       MethodVFTableLocation Loc(MI.VBTableIndex, WhichVFPtr.getVBaseWithVPtr(),
                                 WhichVFPtr.NonVirtualOffset, MI.VFTableIndex);
       if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
-        MethodVFTableLocations[GlobalDecl(DD, Dtor_Deleting)] = Loc;
+        // In Microsoft ABI vftable always references vector deleting dtor.
+        CXXDtorType DtorTy = Context.getTargetInfo().getCXXABI().isMicrosoft()
+                                 ? Dtor_VectorDeleting
+                                 : Dtor_Deleting;
+        MethodVFTableLocations[GlobalDecl(DD, DtorTy)] = Loc;
       } else {
         MethodVFTableLocations[MD] = Loc;
       }
@@ -3287,7 +3291,10 @@ void VFTableBuilder::dumpLayout(raw_ostream &Out) {
       const CXXDestructorDecl *DD = Component.getDestructorDecl();
 
       DD->printQualifiedName(Out);
-      Out << "() [scalar deleting]";
+      if (Context.getTargetInfo().getCXXABI().isMicrosoft())
+        Out << "() [vector deleting]";
+      else
+        Out << "() [scalar deleting]";
 
       if (DD->isPureVirtual())
         Out << " [pure]";
@@ -3758,7 +3765,7 @@ void MicrosoftVTableContext::dumpMethodLocations(
         PredefinedIdentKind::PrettyFunctionNoVirtual, MD);
 
     if (isa<CXXDestructorDecl>(MD)) {
-      IndicesMap[I.second] = MethodName + " [scalar deleting]";
+      IndicesMap[I.second] = MethodName + " [vector deleting]";
     } else {
       IndicesMap[I.second] = MethodName;
     }
@@ -3875,7 +3882,7 @@ MicrosoftVTableContext::getMethodVFTableLocation(GlobalDecl GD) {
   assert(hasVtableSlot(cast<CXXMethodDecl>(GD.getDecl())) &&
          "Only use this method for virtual methods or dtors");
   if (isa<CXXDestructorDecl>(GD.getDecl()))
-    assert(GD.getDtorType() == Dtor_Deleting);
+    assert(GD.getDtorType() == Dtor_VectorDeleting);
 
   GD = GD.getCanonicalDecl();
 
diff --git a/clang/lib/CodeGen/CGCXX.cpp b/clang/lib/CodeGen/CGCXX.cpp
index 78a7b021855b7..6f47e24eed5b3 100644
--- a/clang/lib/CodeGen/CGCXX.cpp
+++ b/clang/lib/CodeGen/CGCXX.cpp
@@ -175,7 +175,6 @@ bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
   // requires explicit comdat support in the IL.
   if (llvm::GlobalValue::isWeakForLinker(TargetLinkage))
     return true;
-
   // Create the alias with no name.
   auto *Alias = llvm::GlobalAlias::create(AliasValueType, 0, Linkage, "",
                                           Aliasee, &getModule());
@@ -201,6 +200,42 @@ bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
   return false;
 }
 
+/// Emit a definition as a global alias for another definition, unconditionally.
+void CodeGenModule::EmitDefinitionAsAlias(GlobalDecl AliasDecl,
+                                          GlobalDecl TargetDecl) {
+
+  llvm::Type *AliasValueType = getTypes().GetFunctionType(AliasDecl);
+
+  StringRef MangledName = getMangledName(AliasDecl);
+  llvm::GlobalValue *Entry = GetGlobalValue(MangledName);
+  if (Entry && !Entry->isDeclaration())
+    return;
+  auto *Aliasee = cast<llvm::GlobalValue>(GetAddrOfGlobal(TargetDecl));
+
+  // Determine the linkage type for the alias.
+  llvm::GlobalValue::LinkageTypes Linkage = getFunctionLinkage(AliasDecl);
+
+  // Create the alias with no name.
+  auto *Alias = llvm::GlobalAlias::create(AliasValueType, 0, Linkage, "",
+                                          Aliasee, &getModule());
+  // Destructors are always unnamed_addr.
+  Alias->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
+
+  if (Entry) {
+    assert(Entry->getValueType() == AliasValueType &&
+           Entry->getAddressSpace() == Alias->getAddressSpace() &&
+           "declaration exists with different type");
+    Alias->takeName(Entry);
+    Entry->replaceAllUsesWith(Alias);
+    Entry->eraseFromParent();
+  } else {
+    Alias->setName(MangledName);
+  }
+
+  // Set any additional necessary attributes for the alias.
+  SetCommonAttributes(AliasDecl, Alias);
+}
+
 llvm::Function *CodeGenModule::codegenCXXStructor(GlobalDecl GD) {
   const CGFunctionInfo &FnInfo = getTypes().arrangeCXXStructorDeclaration(GD);
   auto *Fn = cast<llvm::Function>(
diff --git a/clang/lib/CodeGen/CGCXXABI.cpp b/clang/lib/CodeGen/CGCXXABI.cpp
index fd35f2adfa2d2..9f77fbec21380 100644
--- a/clang/lib/CodeGen/CGCXXABI.cpp
+++ b/clang/lib/CodeGen/CGCXXABI.cpp
@@ -272,6 +272,20 @@ void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, Address ptr,
   numElements = readArrayCookieImpl(CGF, allocAddr, cookieSize);
 }
 
+void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, Address ptr,
+                               QualType eltTy, llvm::Value *&numElements,
+                               llvm::Value *&allocPtr, CharUnits &cookieSize) {
+  assert(eltTy.isDestructedType());
+
+  // Derive a char* in the same address space as the pointer.
+  ptr = ptr.withElementType(CGF.Int8Ty);
+
+  cookieSize = getArrayCookieSizeImpl(eltTy);
+  Address allocAddr = CGF.Builder.CreateConstInBoundsByteGEP(ptr, -cookieSize);
+  allocPtr = allocAddr.emitRawPointer(CGF);
+  numElements = readArrayCookieImpl(CGF, allocAddr, cookieSize);
+}
+
 llvm::Value *CGCXXABI::readArrayCookieImpl(CodeGenFunction &CGF,
                                            Address ptr,
                                            CharUnits cookieSize) {
diff --git a/clang/lib/CodeGen/CGCXXABI.h b/clang/lib/CodeGen/CGCXXABI.h
index 687ff7fb84444..148a7ba6df7e6 100644
--- a/clang/lib/CodeGen/CGCXXABI.h
+++ b/clang/lib/CodeGen/CGCXXABI.h
@@ -275,6 +275,7 @@ class CGCXXABI {
   virtual CatchTypeInfo getCatchAllTypeInfo();
 
   virtual bool shouldTypeidBeNullChecked(QualType SrcRecordTy) = 0;
+  virtual bool hasVectorDeletingDtors() = 0;
   virtual void EmitBadTypeidCall(CodeGenFunction &CGF) = 0;
   virtual llvm::Value *EmitTypeid(CodeGenFunction &CGF, QualType SrcRecordTy,
                                   Address ThisPtr,
@@ -575,6 +576,12 @@ class CGCXXABI {
                                QualType ElementType, llvm::Value *&NumElements,
                                llvm::Value *&AllocPtr, CharUnits &CookieSize);
 
+  /// Reads the array cookie associated with the given pointer,
+  /// that should have one.
+  void ReadArrayCookie(CodeGenFunction &CGF, Address Ptr, QualType ElementType,
+                       llvm::Value *&NumElements, llvm::Value *&AllocPtr,
+                       CharUnits &CookieSize);
+
   /// Return whether the given global decl needs a VTT parameter.
   virtual bool NeedsVTTParameter(GlobalDecl GD);
 
diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp
index 98c93b5bb4883..f508930cc9f2b 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -1432,6 +1432,70 @@ static bool CanSkipVTablePointerInitialization(CodeGenFunction &CGF,
   return true;
 }
 
+static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD,
+                                         CodeGenFunction &CGF,
+                                         llvm::Value *ShouldDeleteCondition) {
+  Address ThisPtr = CGF.LoadCXXThisAddress();
+  llvm::BasicBlock *ScalarBB = CGF.createBasicBlock("dtor.scalar");
+  llvm::BasicBlock *callDeleteBB =
+      CGF.createBasicBlock("dtor.call_delete_after_array_destroy");
+  llvm::BasicBlock *VectorBB = CGF.createBasicBlock("dtor.vector");
+  auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
+  llvm::Value *CheckTheBitForArrayDestroy = CGF.Builder.CreateAnd(
+      ShouldDeleteCondition, llvm::ConstantInt::get(CondTy, 2));
+  llvm::Value *ShouldDestroyArray =
+      CGF.Builder.CreateIsNull(CheckTheBitForArrayDestroy);
+  CGF.Builder.CreateCondBr(ShouldDestroyArray, ScalarBB, VectorBB);
+
+  CGF.EmitBlock(VectorBB);
+
+  llvm::Value *numElements = nullptr;
+  llvm::Value *allocatedPtr = nullptr;
+  CharUnits cookieSize;
+  QualType EltTy = DD->getThisType()->getPointeeType();
+  CGF.CGM.getCXXABI().ReadArrayCookie(CGF, ThisPtr, EltTy, numElements,
+                                      allocatedPtr, cookieSize);
+
+  // Destroy the elements.
+  QualType::DestructionKind dtorKind = EltTy.isDestructedType();
+
+  assert(dtorKind);
+  assert(numElements && "no element count for a type with a destructor!");
+
+  CharUnits elementSize = CGF.getContext().getTypeSizeInChars(EltTy);
+  CharUnits elementAlign =
+      ThisPtr.getAlignment().alignmentOfArrayElement(elementSize);
+
+  llvm::Value *arrayBegin = ThisPtr.emitRawPointer(CGF);
+  llvm::Value *arrayEnd = CGF.Builder.CreateInBoundsGEP(
+      ThisPtr.getElementType(), arrayBegin, numElements, "delete.end");
+
+  // We already checked that the array is not 0-length before entering vector
+  // deleting dtor.
+  CGF.emitArrayDestroy(arrayBegin, arrayEnd, EltTy, elementAlign,
+                       CGF.getDestroyer(dtorKind),
+                       /*checkZeroLength*/ false, CGF.needsEHCleanup(dtorKind));
+
+  llvm::BasicBlock *VectorBBCont = CGF.createBasicBlock("dtor.vector.cont");
+  CGF.EmitBlock(VectorBBCont);
+
+  llvm::Value *CheckTheBitForDeleteCall = CGF.Builder.CreateAnd(
+      ShouldDeleteCondition, llvm::ConstantInt::get(CondTy, 1));
+
+  llvm::Value *ShouldCallDelete =
+      CGF.Builder.CreateIsNull(CheckTheBitForDeleteCall);
+  CGF.Builder.CreateCondBr(ShouldCallDelete, CGF.ReturnBlock.getBlock(),
+                           callDeleteBB);
+  CGF.EmitBlock(callDeleteBB);
+  const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CGF.CurCodeDecl);
+  const CXXRecordDecl *ClassDecl = Dtor->getParent();
+  CGF.EmitDeleteCall(Dtor->getOperatorDelete(), allocatedPtr,
+                     CGF.getContext().getTagDeclType(ClassDecl));
+
+  CGF.EmitBranchThroughCleanup(CGF.ReturnBlock);
+  CGF.EmitBlock(ScalarBB);
+}
+
 /// EmitDestructorBody - Emits the body of the current destructor.
 void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CurGD.getDecl());
@@ -1461,7 +1525,9 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   // outside of the function-try-block, which means it's always
   // possible to delegate the destructor body to the complete
   // destructor.  Do so.
-  if (DtorType == Dtor_Deleting) {
+  if (DtorType == Dtor_Deleting || DtorType == Dtor_VectorDeleting) {
+    if (CXXStructorImplicitParamValue && DtorType == Dtor_VectorDeleting)
+      EmitConditionalArrayDtorCall(Dtor, *this, CXXStructorImplicitParamValue);
     RunCleanupsScope DtorEpilogue(*this);
     EnterDtorCleanups(Dtor, Dtor_Deleting);
     if (HaveInsertPoint()) {
@@ -1490,6 +1556,8 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   switch (DtorType) {
   case Dtor_Comdat: llvm_unreachable("not expecting a COMDAT");
   case Dtor_Deleting: llvm_unreachable("already handled deleting case");
+  case Dtor_VectorDeleting:
+    llvm_unreachable("already handled vector deleting case");
 
   case Dtor_Complete:
     assert((Body || getTarget().getCXXABI().isMicrosoft()) &&
@@ -1572,7 +1640,6 @@ namespace {
       return CGF.EmitScalarExpr(ThisArg);
     return CGF.LoadCXXThis();
   }
-
   /// Call the operator delete associated with the current destructor.
   struct CallDtorDelete final : EHScopeStack::Cleanup {
     CallDtorDelete() {}
@@ -1591,8 +1658,10 @@ namespace {
                                      bool ReturnAfterDelete) {
     llvm::BasicBlock *callDeleteBB = CGF.createBasicBlock("dtor.call_delete");
     llvm::BasicBlock *continueBB = CGF.createBasicBlock("dtor.continue");
-    llvm::Value *ShouldCallDelete
-      = CGF.Builder.CreateIsNull(ShouldDeleteCondition);
+    auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
+    llvm::Value *CheckTheBit = CGF.Builder.CreateAnd(
+        ShouldDeleteCondition, llvm::ConstantInt::get(CondTy, 1));
+    llvm::Value *ShouldCallDelete = CGF.Builder.CreateIsNull(CheckTheBit);
     CGF.Builder.CreateCondBr(ShouldCallDelete, continueBB, callDeleteBB);
 
     CGF.EmitBlock(callDeleteBB);
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 54025b767dc81..286007b30d7c8 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -2119,7 +2119,8 @@ llvm::DISubprogram *CGDebugInfo::CreateCXXMemberFunction(
       // Emit MS ABI vftable information.  There is only one entry for the
       // deleting dtor.
       const auto *DD = dyn_cast<CXXDestructorDecl>(Method);
-      GlobalDecl GD = DD ? GlobalDecl(DD, Dtor_Deleting) : GlobalDecl(Method);
+      GlobalDecl GD =
+          DD ? GlobalDecl(DD, Dtor_VectorDeleting) : GlobalDecl(Method);
       MethodVFTableLocation ML =
           CGM.getMicrosoftVTableContext().getMethodVFTableLocation(GD);
       VIndex = ML.Index;
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 5d96959065dd9..5c11c0bceade7 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -1209,6 +1209,8 @@ void CodeGenFunction::EmitNewArrayInitializer(
     EmitCXXAggrConstructorCall(Ctor, NumElements, CurPtr, CCE,
                                /*NewPointerIsChecked*/true,
                                CCE->requiresZeroInitialization());
+    if (CGM.getCXXABI().hasVectorDeletingDtors())
+      CGM.requireVectorDestructorDefinition(Ctor->getParent());
     return;
   }
 
@@ -1912,10 +1914,8 @@ static void EmitDestroyingObjectDelete(CodeGenFunction &CGF,
 /// Emit the code for deleting a single object.
 /// \return \c true if we started emitting UnconditionalDeleteBlock, \c false
 /// if not.
-static bool EmitObjectDelete(CodeGenFunction &CGF,
-                             const CXXDeleteExpr *DE,
-                             Address Ptr,
-                             QualType ElementType,
+static bool EmitObjectDelete(CodeGenFunction &CGF, const CXXDeleteExpr *DE,
+                             Address Ptr, QualType ElementType,
                              llvm::BasicBlock *UnconditionalDeleteBlock) {
   // C++11 [expr.delete]p3:
   //   If the static type of the object to be deleted is different from its
@@ -2131,6 +2131,40 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) {
 
   assert(ConvertTypeForMem(DeleteTy) == Ptr.getElementType());
 
+  if (E->isArrayForm() && CGM.getCXXABI().hasVectorDeletingDtors...
[truncated]

@@ -7976,6 +7976,8 @@ void CodeGenModule::requireVectorDestructorDefinition(const CXXRecordDecl *RD) {
auto *NewFn = llvm::Function::Create(
cast<llvm::FunctionType>(VDEntry->getValueType()),
llvm::Function::ExternalLinkage, VDName, &getModule());
SetFunctionAttributes(VectorDtorGD, NewFn, /*IsIncompleteFunction*/ false,
/*IsThunk*/ false);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice find!

Can you add a test that covers this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I actually added a test for that, just forgot to hit git add. Here it is dd4f396

Copy link
Collaborator

@zmodem zmodem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't look at the original patch, but the fix and test update lgtm.

@Fznamznon
Copy link
Contributor Author

The original patch has been reviewed before, so I'll proceed with the merge. Feel free to do any post commit comments that I'll be happy to apply.

@Fznamznon Fznamznon merged commit 842b57b into llvm:main Mar 31, 2025
12 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Mar 31, 2025

LLVM Buildbot has detected a new failure on builder clang-ppc64-aix running on aix-ppc64 while building clang at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/64/builds/2702

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'Clang :: ClangScanDeps/verbose.test' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
rm -rf /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp # RUN: at line 1
+ rm -rf /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp
split-file /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/llvm-project/clang/test/ClangScanDeps/verbose.test /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp # RUN: at line 2
+ split-file /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/llvm-project/clang/test/ClangScanDeps/verbose.test /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp
sed -e "s|DIR|/home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp|g" /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp/cdb.json.in > /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp/cdb.json # RUN: at line 3
+ sed -e 's|DIR|/home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp|g' /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp/cdb.json.in
/home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/bin/clang-scan-deps -compilation-database /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp/cdb.json -v -o /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp/result.json 2>&1 | /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/bin/FileCheck /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/llvm-project/clang/test/ClangScanDeps/verbose.test # RUN: at line 5
+ /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/bin/clang-scan-deps -compilation-database /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp/cdb.json -v -o /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/tools/clang/test/ClangScanDeps/Output/verbose.test.tmp/result.json
+ /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/bin/FileCheck /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/llvm-project/clang/test/ClangScanDeps/verbose.test
/home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/llvm-project/clang/test/ClangScanDeps/verbose.test:6:11: error: CHECK: expected string not found in input
// CHECK: *** Virtual File System Stats:
          ^
<stdin>:1:1: note: scanning from here
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.
^
<stdin>:1:8: note: possible intended match here
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.
       ^

Input file: <stdin>
Check file: /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/llvm-project/clang/test/ClangScanDeps/verbose.test

-dump-input=help explains the following input dump.

Input was:
<<<<<<
           1: PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. 
check:6'0     X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
check:6'1            ?                                                                                                     possible intended match
>>>>>>

--

********************


@llvm-ci
Copy link
Collaborator

llvm-ci commented Mar 31, 2025

LLVM Buildbot has detected a new failure on builder clang-aarch64-sve2-vla-2stage running on linaro-g4-02 while building clang at step 12 "ninja check 2".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/199/builds/2486

Here is the relevant piece of the build log for the reference
Step 12 (ninja check 2) failure: stage 2 checked (failure)
...
PASS: Flang :: Driver/override-triple.ll (24741 of 96445)
PASS: Flang :: Driver/predefined-macros-compiler-version.F90 (24742 of 96445)
PASS: Flang :: Driver/print-resource-dir.F90 (24743 of 96445)
PASS: Flang :: Driver/parse-error.ll (24744 of 96445)
PASS: Flang :: Driver/phases.f90 (24745 of 96445)
PASS: Flang :: Driver/linker-flags.f90 (24746 of 96445)
PASS: Flang :: Driver/lto-bc.f90 (24747 of 96445)
PASS: Flang :: Driver/print-pipeline-passes.f90 (24748 of 96445)
PASS: Flang :: Driver/parse-fir-error.ll (24749 of 96445)
UNRESOLVED: Flang :: Driver/slp-vectorize.ll (24750 of 96445)
******************** TEST 'Flang :: Driver/slp-vectorize.ll' FAILED ********************
Test has no 'RUN:' line
********************
PASS: Flang :: Driver/print-target-triple.f90 (24751 of 96445)
PASS: Clangd Unit Tests :: ./ClangdTests/72/81 (24752 of 96445)
PASS: Flang :: Driver/mlir-pass-pipeline.f90 (24753 of 96445)
PASS: Flang :: Driver/include-header.f90 (24754 of 96445)
PASS: Flang :: Driver/parse-ir-error.f95 (24755 of 96445)
PASS: Flang :: Driver/mlink-builtin-bc.f90 (24756 of 96445)
PASS: Flang :: Driver/pthread.f90 (24757 of 96445)
PASS: Flang :: Driver/pass-plugin-not-found.f90 (24758 of 96445)
PASS: Flang :: Driver/missing-arg.f90 (24759 of 96445)
PASS: Flang :: Driver/scanning-error.f95 (24760 of 96445)
PASS: Flang :: Driver/std2018-wrong.f90 (24761 of 96445)
PASS: Flang :: Driver/fixed-line-length.f90 (24762 of 96445)
PASS: Flang :: Driver/supported-suffices/f03-suffix.f03 (24763 of 96445)
PASS: Flang :: Driver/pp-fixed-form.f90 (24764 of 96445)
PASS: Flang :: Driver/fsave-optimization-record.f90 (24765 of 96445)
PASS: Flang :: Driver/supported-suffices/f08-suffix.f08 (24766 of 96445)
PASS: Flang :: Driver/tco-code-gen-llvm.fir (24767 of 96445)
PASS: Flang :: Driver/target-gpu-features.f90 (24768 of 96445)
PASS: Flang :: Driver/target.f90 (24769 of 96445)
PASS: Flang :: Driver/lto-flags.f90 (24770 of 96445)
PASS: Flang :: Driver/multiple-input-files.f90 (24771 of 96445)
PASS: Flang :: Driver/mllvm.f90 (24772 of 96445)
PASS: Flang :: Driver/unsupported-vscale-max-min.f90 (24773 of 96445)
PASS: Flang :: Driver/no-duplicate-main.f90 (24774 of 96445)
PASS: Flang :: Driver/q-unused-arguments.f90 (24775 of 96445)
PASS: Flang :: Driver/unparse-with-modules.f90 (24776 of 96445)
PASS: Flang :: Driver/prescanner-diag.f90 (24777 of 96445)
PASS: Flang :: Driver/target-machine-error.f90 (24778 of 96445)
PASS: Flang :: Driver/save-temps.f90 (24779 of 96445)
PASS: Flang :: Driver/input-from-stdin/input-from-stdin.f90 (24780 of 96445)
PASS: Flang :: Driver/unparse-use-analyzed.f95 (24781 of 96445)
PASS: Flang :: Driver/optimization-remark-invalid.f90 (24782 of 96445)
PASS: Flang :: Driver/std2018.f90 (24783 of 96445)
PASS: Flang :: Driver/save-temps-use-module.f90 (24784 of 96445)
PASS: Flang :: Driver/falias-analysis.f90 (24785 of 96445)
PASS: Flang :: Driver/target-cpu-features-invalid.f90 (24786 of 96445)

@llvm-ci
Copy link
Collaborator

llvm-ci commented Mar 31, 2025

LLVM Buildbot has detected a new failure on builder sanitizer-aarch64-linux-bootstrap-msan running on sanitizer-buildbot10 while building clang at step 2 "annotate".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/94/builds/5774

Here is the relevant piece of the build log for the reference
Step 2 (annotate) failure: 'python ../sanitizer_buildbot/sanitizers/zorg/buildbot/builders/sanitizers/buildbot_selector.py' (failure)
...
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using lld-link: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/lld-link
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using ld64.lld: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/ld64.lld
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using wasm-ld: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/wasm-ld
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using ld.lld: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/ld.lld
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using lld-link: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/lld-link
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using ld64.lld: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/ld64.lld
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using wasm-ld: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/wasm-ld
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/main.py:72: note: The test suite configuration requested an individual test timeout of 0 seconds but a timeout of 900 seconds was requested on the command line. Forcing timeout to be 900 seconds.
-- Testing: 84372 tests, 72 workers --
Testing:  0.. 10.. 20.. 30.. 40.. 50.. 60.. 70.. 80.. 90.
FAIL: lit :: allow-retries.py (81367 of 84372)
******************** TEST 'lit :: allow-retries.py' FAILED ********************
Exit Code: 2

Command Output (stdout):
--
# RUN: at line 6
rm -f /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter
# executed command: rm -f /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter
# note: command had no output on stdout or stderr
# RUN: at line 7
env -u FILECHECK_OPTS "/usr/bin/python3" /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/succeeds-within-limit.py -Dcounter=/home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter -Dpython="/usr/bin/python3" | FileCheck --check-prefix=CHECK-TEST1 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# executed command: env -u FILECHECK_OPTS /usr/bin/python3 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/succeeds-within-limit.py -Dcounter=/home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter -Dpython=/usr/bin/python3
# note: command had no output on stdout or stderr
# executed command: FileCheck --check-prefix=CHECK-TEST1 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# note: command had no output on stdout or stderr
# RUN: at line 12
rm -f /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter
# executed command: rm -f /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter
# note: command had no output on stdout or stderr
# RUN: at line 13
env -u FILECHECK_OPTS "/usr/bin/python3" /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/succeeds-within-limit.py -Dtest_retry_attempts=2 -Dcounter=/home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter -Dpython="/usr/bin/python3" | FileCheck --check-prefix=CHECK-TEST2 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# executed command: env -u FILECHECK_OPTS /usr/bin/python3 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/succeeds-within-limit.py -Dtest_retry_attempts=2 -Dcounter=/home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter -Dpython=/usr/bin/python3
# note: command had no output on stdout or stderr
# executed command: FileCheck --check-prefix=CHECK-TEST2 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# note: command had no output on stdout or stderr
# RUN: at line 22
not env -u FILECHECK_OPTS "/usr/bin/python3" /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/does-not-succeed-within-limit.py -v |   FileCheck --check-prefix=CHECK-TEST3 -match-full-lines /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# executed command: not env -u FILECHECK_OPTS /usr/bin/python3 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/does-not-succeed-within-limit.py -v
# note: command had no output on stdout or stderr
# executed command: FileCheck --check-prefix=CHECK-TEST3 -match-full-lines /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# note: command had no output on stdout or stderr
# RUN: at line 44
not env -u FILECHECK_OPTS "/usr/bin/python3" /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/more-than-one-allow-retries-lines.py | FileCheck --check-prefix=CHECK-TEST4 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# executed command: not env -u FILECHECK_OPTS /usr/bin/python3 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/more-than-one-allow-retries-lines.py
# note: command had no output on stdout or stderr
# executed command: FileCheck --check-prefix=CHECK-TEST4 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# .---command stderr------------
# | FileCheck error: '<stdin>' is empty.
Step 13 (stage3/msan check) failure: stage3/msan check (failure)
...
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using lld-link: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/lld-link
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using ld64.lld: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/ld64.lld
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using wasm-ld: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/wasm-ld
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using ld.lld: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/ld.lld
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using lld-link: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/lld-link
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using ld64.lld: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/ld64.lld
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:520: note: using wasm-ld: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/bin/wasm-ld
llvm-lit: /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit/main.py:72: note: The test suite configuration requested an individual test timeout of 0 seconds but a timeout of 900 seconds was requested on the command line. Forcing timeout to be 900 seconds.
-- Testing: 84372 tests, 72 workers --
Testing:  0.. 10.. 20.. 30.. 40.. 50.. 60.. 70.. 80.. 90.
FAIL: lit :: allow-retries.py (81367 of 84372)
******************** TEST 'lit :: allow-retries.py' FAILED ********************
Exit Code: 2

Command Output (stdout):
--
# RUN: at line 6
rm -f /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter
# executed command: rm -f /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter
# note: command had no output on stdout or stderr
# RUN: at line 7
env -u FILECHECK_OPTS "/usr/bin/python3" /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/succeeds-within-limit.py -Dcounter=/home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter -Dpython="/usr/bin/python3" | FileCheck --check-prefix=CHECK-TEST1 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# executed command: env -u FILECHECK_OPTS /usr/bin/python3 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/succeeds-within-limit.py -Dcounter=/home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter -Dpython=/usr/bin/python3
# note: command had no output on stdout or stderr
# executed command: FileCheck --check-prefix=CHECK-TEST1 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# note: command had no output on stdout or stderr
# RUN: at line 12
rm -f /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter
# executed command: rm -f /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter
# note: command had no output on stdout or stderr
# RUN: at line 13
env -u FILECHECK_OPTS "/usr/bin/python3" /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/succeeds-within-limit.py -Dtest_retry_attempts=2 -Dcounter=/home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter -Dpython="/usr/bin/python3" | FileCheck --check-prefix=CHECK-TEST2 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# executed command: env -u FILECHECK_OPTS /usr/bin/python3 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/succeeds-within-limit.py -Dtest_retry_attempts=2 -Dcounter=/home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/Output/allow-retries.py.tmp.counter -Dpython=/usr/bin/python3
# note: command had no output on stdout or stderr
# executed command: FileCheck --check-prefix=CHECK-TEST2 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# note: command had no output on stdout or stderr
# RUN: at line 22
not env -u FILECHECK_OPTS "/usr/bin/python3" /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/does-not-succeed-within-limit.py -v |   FileCheck --check-prefix=CHECK-TEST3 -match-full-lines /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# executed command: not env -u FILECHECK_OPTS /usr/bin/python3 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/does-not-succeed-within-limit.py -v
# note: command had no output on stdout or stderr
# executed command: FileCheck --check-prefix=CHECK-TEST3 -match-full-lines /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# note: command had no output on stdout or stderr
# RUN: at line 44
not env -u FILECHECK_OPTS "/usr/bin/python3" /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/more-than-one-allow-retries-lines.py | FileCheck --check-prefix=CHECK-TEST4 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# executed command: not env -u FILECHECK_OPTS /usr/bin/python3 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/allow-retries/more-than-one-allow-retries-lines.py
# note: command had no output on stdout or stderr
# executed command: FileCheck --check-prefix=CHECK-TEST4 /home/b/sanitizer-aarch64-linux-bootstrap-msan/build/llvm_build2_msan/utils/lit/tests/allow-retries.py
# .---command stderr------------
# | FileCheck error: '<stdin>' is empty.

SchrodingerZhu pushed a commit to SchrodingerZhu/llvm-project that referenced this pull request Mar 31, 2025
…133451)

Whereas it is UB in terms of the standard to delete an array of objects
via pointer whose static type doesn't match its dynamic type, MSVC
supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case,
currently not having this extension implemented causes clang to generate
code that is not compatible with the code generated by MSVC, because
clang always puts scalar deleting destructor to the vftable. This PR
aims to resolve these problems.

It was reverted due to link time errors in chromium with sanitizer
coverage enabled,
which is fixed by llvm#131929 .

The second commit of this PR also contains a fix for a runtime failure
in chromium reported
in
llvm#126240 (comment)
.

Fixes llvm#19772
Fznamznon added a commit to Fznamznon/llvm-project that referenced this pull request Apr 14, 2025
zmodem pushed a commit that referenced this pull request Apr 14, 2025
Finding operator delete[] is still problematic, without it the extension
is a security hazard, so reverting until the problem with operator
delete[] is figured out.

This reverts the following PRs:
Reland [MS][clang] Add support for vector deleting destructors (#133451)
[MS][clang] Make sure vector deleting dtor calls correct operator delete (#133950)
[MS][clang] Fix crash on deletion of array of pointers (#134088)
[clang] Do not diagnose unused deleted operator delete[] (#134357)
[MS][clang] Error about ambiguous operator delete[] only when required (#135041)
var-const pushed a commit to ldionne/llvm-project that referenced this pull request Apr 17, 2025
Finding operator delete[] is still problematic, without it the extension
is a security hazard, so reverting until the problem with operator
delete[] is figured out.

This reverts the following PRs:
Reland [MS][clang] Add support for vector deleting destructors (llvm#133451)
[MS][clang] Make sure vector deleting dtor calls correct operator delete (llvm#133950)
[MS][clang] Fix crash on deletion of array of pointers (llvm#134088)
[clang] Do not diagnose unused deleted operator delete[] (llvm#134357)
[MS][clang] Error about ambiguous operator delete[] only when required (llvm#135041)
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
4 participants