]> granicus.if.org Git - clang/commitdiff
Support for destroying operator delete, per C++2a proposal P0722.
authorRichard Smith <richard-llvm@metafoo.co.uk>
Fri, 13 Oct 2017 01:55:36 +0000 (01:55 +0000)
committerRichard Smith <richard-llvm@metafoo.co.uk>
Fri, 13 Oct 2017 01:55:36 +0000 (01:55 +0000)
This feature is not (yet) approved by the C++ committee, so this is liable to
be reverted or significantly modified based on committee feedback.

No functionality change intended for existing code (a new type must be defined
in namespace std to take advantage of this feature).

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@315662 91177308-0d34-0410-b5e6-96231b3b80d8

19 files changed:
include/clang/AST/ASTMutationListener.h
include/clang/AST/Decl.h
include/clang/AST/DeclCXX.h
include/clang/Basic/DiagnosticSemaKinds.td
include/clang/Serialization/ASTWriter.h
lib/AST/Decl.cpp
lib/AST/DeclCXX.cpp
lib/AST/ExprCXX.cpp
lib/CodeGen/CGClass.cpp
lib/CodeGen/CGExprCXX.cpp
lib/CodeGen/ItaniumCXXABI.cpp
lib/Frontend/MultiplexConsumer.cpp
lib/Sema/SemaDeclCXX.cpp
lib/Sema/SemaExprCXX.cpp
lib/Serialization/ASTReaderDecl.cpp
lib/Serialization/ASTWriter.cpp
lib/Serialization/ASTWriterDecl.cpp
test/CodeGenCXX/cxx2a-destroying-delete.cpp [new file with mode: 0644]
test/SemaCXX/cxx2a-destroying-delete.cpp [new file with mode: 0644]

index a8eff1a2fcbb52bb51ccda5f5aced481f7a9a48a..ed82b32342722893ea3618b51b916f15efad660c 100644 (file)
@@ -22,6 +22,7 @@ namespace clang {
   class CXXRecordDecl;
   class Decl;
   class DeclContext;
+  class Expr;
   class FieldDecl;
   class FunctionDecl;
   class FunctionTemplateDecl;
@@ -80,7 +81,8 @@ public:
 
   /// \brief A virtual destructor's operator delete has been resolved.
   virtual void ResolvedOperatorDelete(const CXXDestructorDecl *DD,
-                                      const FunctionDecl *Delete) {}
+                                      const FunctionDecl *Delete,
+                                      Expr *ThisArg) {}
 
   /// \brief An implicit member got a definition.
   virtual void CompletedImplicitDefinition(const FunctionDecl *D) {}
index 3dcd7a89a7a456a5f524701f6a8e48958f97fd1e..51d53a4c59d5fa98a7094361c84f322f8814b515 100644 (file)
@@ -2036,6 +2036,9 @@ public:
   /// true through IsAligned.
   bool isReplaceableGlobalAllocationFunction(bool *IsAligned = nullptr) const;
 
+  /// \brief Determine whether this is a destroying operator delete.
+  bool isDestroyingOperatorDelete() const;
+
   /// Compute the language linkage.
   LanguageLinkage getLanguageLinkage() const;
 
index d85ace9dda06bc50387f155e0b01749178a867bd..1b9458fcfc55e38c157116662fa6e096f2cea24d 100644 (file)
@@ -2566,7 +2566,10 @@ public:
 class CXXDestructorDecl : public CXXMethodDecl {
   void anchor() override;
 
+  // FIXME: Don't allocate storage for these except in the first declaration
+  // of a virtual destructor.
   FunctionDecl *OperatorDelete;
+  Expr *OperatorDeleteThisArg;
 
   CXXDestructorDecl(ASTContext &C, CXXRecordDecl *RD, SourceLocation StartLoc,
                     const DeclarationNameInfo &NameInfo,
@@ -2574,7 +2577,7 @@ class CXXDestructorDecl : public CXXMethodDecl {
                     bool isInline, bool isImplicitlyDeclared)
     : CXXMethodDecl(CXXDestructor, C, RD, StartLoc, NameInfo, T, TInfo,
                     SC_None, isInline, /*isConstexpr=*/false, SourceLocation()),
-      OperatorDelete(nullptr) {
+      OperatorDelete(nullptr), OperatorDeleteThisArg(nullptr) {
     setImplicit(isImplicitlyDeclared);
   }
 
@@ -2587,10 +2590,13 @@ public:
                                    bool isImplicitlyDeclared);
   static CXXDestructorDecl *CreateDeserialized(ASTContext & C, unsigned ID);
 
-  void setOperatorDelete(FunctionDecl *OD);
+  void setOperatorDelete(FunctionDecl *OD, Expr *ThisArg);
   const FunctionDecl *getOperatorDelete() const {
     return getCanonicalDecl()->OperatorDelete;
   }
+  Expr *getOperatorDeleteThisArg() const {
+    return getCanonicalDecl()->OperatorDeleteThisArg;
+  }
 
   CXXDestructorDecl *getCanonicalDecl() override {
     return cast<CXXDestructorDecl>(FunctionDecl::getCanonicalDecl());
index 7f20be91df87b59a92be950537a22377055cb16f..141b96598ab1e826a37d6c9b219928f2d9b8b94b 100644 (file)
@@ -7612,6 +7612,11 @@ def err_operator_delete_dependent_param_type : Error<
   "%0 cannot take a dependent type as first parameter; use %1 instead">;
 def err_operator_delete_param_type : Error<
   "first parameter of %0 must have type %1">;
+def err_destroying_operator_delete_not_usual : Error<
+  "destroying operator delete can have only an optional size and optional "
+  "alignment parameter">;
+def note_implicit_delete_this_in_destructor_here : Note<
+  "while checking implicit 'delete this' for virtual destructor">;
 
 // C++ literal operators
 def err_literal_operator_outside_namespace : Error<
index f14dfc73baa98a4236819c43a4e89801f4836143..392a4652d60f7932e0e6ab01597a06d73878943e 100644 (file)
@@ -698,7 +698,8 @@ private:
   void ResolvedExceptionSpec(const FunctionDecl *FD) override;
   void DeducedReturnType(const FunctionDecl *FD, QualType ReturnType) override;
   void ResolvedOperatorDelete(const CXXDestructorDecl *DD,
-                              const FunctionDecl *Delete) override;
+                              const FunctionDecl *Delete,
+                              Expr *ThisArg) override;
   void CompletedImplicitDefinition(const FunctionDecl *D) override;
   void StaticDataMemberInstantiated(const VarDecl *D) override;
   void DefaultArgumentInstantiated(const ParmVarDecl *D) override;
index 4a9b9bea8306bbdf02b428c744def28d1f7b1096..9b0d976ebb28fff6e9e688a28aad8fa4cae2f19a 100644 (file)
@@ -2732,6 +2732,20 @@ bool FunctionDecl::isReplaceableGlobalAllocationFunction(bool *IsAligned) const
   return Params == FPT->getNumParams();
 }
 
+bool FunctionDecl::isDestroyingOperatorDelete() const {
+  // C++ P0722:
+  //   Within a class C, a single object deallocation function with signature
+  //     (T, std::destroying_delete_t, <more params>)
+  //   is a destroying operator delete.
+  if (!isa<CXXMethodDecl>(this) || getOverloadedOperator() != OO_Delete ||
+      getNumParams() < 2)
+    return false;
+
+  auto *RD = getParamDecl(1)->getType()->getAsCXXRecordDecl();
+  return RD && RD->isInStdNamespace() && RD->getIdentifier() &&
+         RD->getIdentifier()->isStr("destroying_delete_t");
+}
+
 LanguageLinkage FunctionDecl::getLanguageLinkage() const {
   return getDeclLanguageLinkage(*this);
 }
index 26e5c4ffbedcec6cc8c57171743c89edcb06a849..bd8a150cf1e2b3bb196953e1c8d58dfb93e71895 100644 (file)
@@ -1788,6 +1788,14 @@ bool CXXMethodDecl::isUsualDeallocationFunction() const {
     return true;
   unsigned UsualParams = 1;
 
+  // C++ P0722:
+  //   A destroying operator delete is a usual deallocation function if
+  //   removing the std::destroying_delete_t parameter and changing the
+  //   first parameter type from T* to void* results in the signature of
+  //   a usual deallocation function.
+  if (isDestroyingOperatorDelete())
+    ++UsualParams;
+
   // C++ <=14 [basic.stc.dynamic.deallocation]p2:
   //   [...] If class T does not declare such an operator delete but does 
   //   declare a member deallocation function named operator delete with 
@@ -2187,12 +2195,13 @@ CXXDestructorDecl::Create(ASTContext &C, CXXRecordDecl *RD,
                                        isInline, isImplicitlyDeclared);
 }
 
-void CXXDestructorDecl::setOperatorDelete(FunctionDecl *OD) {
+void CXXDestructorDecl::setOperatorDelete(FunctionDecl *OD, Expr *ThisArg) {
   auto *First = cast<CXXDestructorDecl>(getFirstDecl());
   if (OD && !First->OperatorDelete) {
     First->OperatorDelete = OD;
+    First->OperatorDeleteThisArg = ThisArg;
     if (auto *L = getASTMutationListener())
-      L->ResolvedOperatorDelete(First, OD);
+      L->ResolvedOperatorDelete(First, OD, ThisArg);
   }
 }
 
index fe45b5e47f36dc32a84fe975978534ef88e4c532..182a8b415a4d9204240822affd19fba6ccac4efb 100644 (file)
@@ -160,6 +160,22 @@ bool CXXNewExpr::shouldNullCheckAllocation(const ASTContext &Ctx) const {
 // CXXDeleteExpr
 QualType CXXDeleteExpr::getDestroyedType() const {
   const Expr *Arg = getArgument();
+
+  // For a destroying operator delete, we may have implicitly converted the
+  // pointer type to the type of the parameter of the 'operator delete'
+  // function.
+  while (auto *ICE = dyn_cast<ImplicitCastExpr>(Arg)) {
+    if (ICE->getCastKind() == CK_DerivedToBase ||
+        ICE->getCastKind() == CK_UncheckedDerivedToBase ||
+        ICE->getCastKind() == CK_NoOp) {
+      assert((ICE->getCastKind() == CK_NoOp ||
+              getOperatorDelete()->isDestroyingOperatorDelete()) &&
+             "only a destroying operator delete can have a converted arg");
+      Arg = ICE->getSubExpr();
+    } else
+      break;
+  }
+
   // The type-to-delete may not be a pointer if it's a dependent type.
   const QualType ArgType = Arg->getType();
 
index e86c8dcc8a626c8b28dcd2fc3cc66cf9d369c1c1..60a99367532e6113e6b12e6b30b49e25f641c355 100644 (file)
@@ -1413,10 +1413,11 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   // possible to delegate the destructor body to the complete
   // destructor.  Do so.
   if (DtorType == Dtor_Deleting) {
+    RunCleanupsScope DtorEpilogue(*this);
     EnterDtorCleanups(Dtor, Dtor_Deleting);
-    EmitCXXDestructorCall(Dtor, Dtor_Complete, /*ForVirtualBase=*/false,
-                          /*Delegating=*/false, LoadCXXThisAddress());
-    PopCleanupBlock();
+    if (HaveInsertPoint())
+      EmitCXXDestructorCall(Dtor, Dtor_Complete, /*ForVirtualBase=*/false,
+                            /*Delegating=*/false, LoadCXXThisAddress());
     return;
   }
 
@@ -1512,6 +1513,13 @@ void CodeGenFunction::emitImplicitAssignmentOperatorBody(FunctionArgList &Args)
 }
 
 namespace {
+  llvm::Value *LoadThisForDtorDelete(CodeGenFunction &CGF,
+                                     const CXXDestructorDecl *DD) {
+    if (Expr *ThisArg = DD->getOperatorDeleteThisArg())
+      return CGF.EmitScalarExpr(DD->getOperatorDeleteThisArg());
+    return CGF.LoadCXXThis();
+  }
+
   /// Call the operator delete associated with the current destructor.
   struct CallDtorDelete final : EHScopeStack::Cleanup {
     CallDtorDelete() {}
@@ -1519,11 +1527,38 @@ namespace {
     void Emit(CodeGenFunction &CGF, Flags flags) override {
       const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CGF.CurCodeDecl);
       const CXXRecordDecl *ClassDecl = Dtor->getParent();
-      CGF.EmitDeleteCall(Dtor->getOperatorDelete(), CGF.LoadCXXThis(),
+      CGF.EmitDeleteCall(Dtor->getOperatorDelete(),
+                         LoadThisForDtorDelete(CGF, Dtor),
                          CGF.getContext().getTagDeclType(ClassDecl));
     }
   };
 
+  void EmitConditionalDtorDeleteCall(CodeGenFunction &CGF,
+                                     llvm::Value *ShouldDeleteCondition,
+                                     bool ReturnAfterDelete) {
+    llvm::BasicBlock *callDeleteBB = CGF.createBasicBlock("dtor.call_delete");
+    llvm::BasicBlock *continueBB = CGF.createBasicBlock("dtor.continue");
+    llvm::Value *ShouldCallDelete
+      = CGF.Builder.CreateIsNull(ShouldDeleteCondition);
+    CGF.Builder.CreateCondBr(ShouldCallDelete, continueBB, callDeleteBB);
+
+    CGF.EmitBlock(callDeleteBB);
+    const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CGF.CurCodeDecl);
+    const CXXRecordDecl *ClassDecl = Dtor->getParent();
+    CGF.EmitDeleteCall(Dtor->getOperatorDelete(),
+                       LoadThisForDtorDelete(CGF, Dtor),
+                       CGF.getContext().getTagDeclType(ClassDecl));
+    assert(Dtor->getOperatorDelete()->isDestroyingOperatorDelete() ==
+               ReturnAfterDelete &&
+           "unexpected value for ReturnAfterDelete");
+    if (ReturnAfterDelete)
+      CGF.EmitBranchThroughCleanup(CGF.ReturnBlock);
+    else
+      CGF.Builder.CreateBr(continueBB);
+
+    CGF.EmitBlock(continueBB);
+  }
+
   struct CallDtorDeleteConditional final : EHScopeStack::Cleanup {
     llvm::Value *ShouldDeleteCondition;
 
@@ -1534,20 +1569,8 @@ namespace {
     }
 
     void Emit(CodeGenFunction &CGF, Flags flags) override {
-      llvm::BasicBlock *callDeleteBB = CGF.createBasicBlock("dtor.call_delete");
-      llvm::BasicBlock *continueBB = CGF.createBasicBlock("dtor.continue");
-      llvm::Value *ShouldCallDelete
-        = CGF.Builder.CreateIsNull(ShouldDeleteCondition);
-      CGF.Builder.CreateCondBr(ShouldCallDelete, continueBB, callDeleteBB);
-
-      CGF.EmitBlock(callDeleteBB);
-      const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CGF.CurCodeDecl);
-      const CXXRecordDecl *ClassDecl = Dtor->getParent();
-      CGF.EmitDeleteCall(Dtor->getOperatorDelete(), CGF.LoadCXXThis(),
-                         CGF.getContext().getTagDeclType(ClassDecl));
-      CGF.Builder.CreateBr(continueBB);
-
-      CGF.EmitBlock(continueBB);
+      EmitConditionalDtorDeleteCall(CGF, ShouldDeleteCondition,
+                                    /*ReturnAfterDelete*/false);
     }
   };
 
@@ -1706,6 +1729,9 @@ namespace {
 /// \brief Emit all code that comes at the end of class's
 /// destructor. This is to call destructors on members and base classes
 /// in reverse order of their construction.
+///
+/// For a deleting destructor, this also handles the case where a destroying
+/// operator delete completely overrides the definition.
 void CodeGenFunction::EnterDtorCleanups(const CXXDestructorDecl *DD,
                                         CXXDtorType DtorType) {
   assert((!DD->isTrivial() || DD->hasAttr<DLLExportAttr>()) &&
@@ -1718,11 +1744,23 @@ void CodeGenFunction::EnterDtorCleanups(const CXXDestructorDecl *DD,
            "operator delete missing - EnterDtorCleanups");
     if (CXXStructorImplicitParamValue) {
       // If there is an implicit param to the deleting dtor, it's a boolean
-      // telling whether we should call delete at the end of the dtor.
-      EHStack.pushCleanup<CallDtorDeleteConditional>(
-          NormalAndEHCleanup, CXXStructorImplicitParamValue);
+      // telling whether this is a deleting destructor.
+      if (DD->getOperatorDelete()->isDestroyingOperatorDelete())
+        EmitConditionalDtorDeleteCall(*this, CXXStructorImplicitParamValue,
+                                      /*ReturnAfterDelete*/true);
+      else
+        EHStack.pushCleanup<CallDtorDeleteConditional>(
+            NormalAndEHCleanup, CXXStructorImplicitParamValue);
     } else {
-      EHStack.pushCleanup<CallDtorDelete>(NormalAndEHCleanup);
+      if (DD->getOperatorDelete()->isDestroyingOperatorDelete()) {
+        const CXXRecordDecl *ClassDecl = DD->getParent();
+        EmitDeleteCall(DD->getOperatorDelete(),
+                       LoadThisForDtorDelete(*this, DD),
+                       getContext().getTagDeclType(ClassDecl));
+        EmitBranchThroughCleanup(ReturnBlock);
+      } else {
+        EHStack.pushCleanup<CallDtorDelete>(NormalAndEHCleanup);
+      }
     }
     return;
   }
index 8ee236ddd86b13484dfc931098052ad5f0e116e7..3eb4bd6b901eb95eb886377fc0bb830e9dac1d46 100644 (file)
@@ -1311,29 +1311,44 @@ RValue CodeGenFunction::EmitBuiltinNewDeleteCall(const FunctionProtoType *Type,
   llvm_unreachable("predeclared global operator new/delete is missing");
 }
 
-static std::pair<bool, bool>
-shouldPassSizeAndAlignToUsualDelete(const FunctionProtoType *FPT) {
+namespace {
+/// The parameters to pass to a usual operator delete.
+struct UsualDeleteParams {
+  bool DestroyingDelete = false;
+  bool Size = false;
+  bool Alignment = false;
+};
+}
+
+static UsualDeleteParams getUsualDeleteParams(const FunctionDecl *FD) {
+  UsualDeleteParams Params;
+
+  const FunctionProtoType *FPT = FD->getType()->castAs<FunctionProtoType>();
   auto AI = FPT->param_type_begin(), AE = FPT->param_type_end();
 
   // The first argument is always a void*.
   ++AI;
 
-  // Figure out what other parameters we should be implicitly passing.
-  bool PassSize = false;
-  bool PassAlignment = false;
+  // The next parameter may be a std::destroying_delete_t.
+  if (FD->isDestroyingOperatorDelete()) {
+    Params.DestroyingDelete = true;
+    assert(AI != AE);
+    ++AI;
+  }
 
+  // Figure out what other parameters we should be implicitly passing.
   if (AI != AE && (*AI)->isIntegerType()) {
-    PassSize = true;
+    Params.Size = true;
     ++AI;
   }
 
   if (AI != AE && (*AI)->isAlignValT()) {
-    PassAlignment = true;
+    Params.Alignment = true;
     ++AI;
   }
 
   assert(AI == AE && "unexpected usual deallocation function parameter");
-  return {PassSize, PassAlignment};
+  return Params;
 }
 
 namespace {
@@ -1386,25 +1401,27 @@ namespace {
           OperatorDelete->getType()->getAs<FunctionProtoType>();
       CallArgList DeleteArgs;
 
-      // The first argument is always a void*.
+      // The first argument is always a void* (or C* for a destroying operator
+      // delete for class type C).
       DeleteArgs.add(Traits::get(CGF, Ptr), FPT->getParamType(0));
 
       // Figure out what other parameters we should be implicitly passing.
-      bool PassSize = false;
-      bool PassAlignment = false;
+      UsualDeleteParams Params;
       if (NumPlacementArgs) {
         // A placement deallocation function is implicitly passed an alignment
         // if the placement allocation function was, but is never passed a size.
-        PassAlignment = PassAlignmentToPlacementDelete;
+        Params.Alignment = PassAlignmentToPlacementDelete;
       } else {
         // For a non-placement new-expression, 'operator delete' can take a
         // size and/or an alignment if it has the right parameters.
-        std::tie(PassSize, PassAlignment) =
-            shouldPassSizeAndAlignToUsualDelete(FPT);
+        Params = getUsualDeleteParams(OperatorDelete);
       }
 
+      assert(!Params.DestroyingDelete &&
+             "should not call destroying delete in a new-expression");
+
       // The second argument can be a std::size_t (for non-placement delete).
-      if (PassSize)
+      if (Params.Size)
         DeleteArgs.add(Traits::get(CGF, AllocSize),
                        CGF.getContext().getSizeType());
 
@@ -1412,7 +1429,7 @@ namespace {
       // is an enum whose underlying type is std::size_t.
       // FIXME: Use the right type as the parameter type. Note that in a call
       // to operator delete(size_t, ...), we may not have it available.
-      if (PassAlignment)
+      if (Params.Alignment)
         DeleteArgs.add(RValue::get(llvm::ConstantInt::get(
                            CGF.SizeTy, AllocAlign.getQuantity())),
                        CGF.getContext().getSizeType());
@@ -1715,9 +1732,7 @@ void CodeGenFunction::EmitDeleteCall(const FunctionDecl *DeleteFD,
 
   CallArgList DeleteArgs;
 
-  std::pair<bool, bool> PassSizeAndAlign =
-      shouldPassSizeAndAlignToUsualDelete(DeleteFTy);
-
+  auto Params = getUsualDeleteParams(DeleteFD);
   auto ParamTypeIt = DeleteFTy->param_type_begin();
 
   // Pass the pointer itself.
@@ -1725,8 +1740,16 @@ void CodeGenFunction::EmitDeleteCall(const FunctionDecl *DeleteFD,
   llvm::Value *DeletePtr = Builder.CreateBitCast(Ptr, ConvertType(ArgTy));
   DeleteArgs.add(RValue::get(DeletePtr), ArgTy);
 
+  // Pass the std::destroying_delete tag if present.
+  if (Params.DestroyingDelete) {
+    QualType DDTag = *ParamTypeIt++;
+    // Just pass an 'undef'. We expect the tag type to be an empty struct.
+    auto *V = llvm::UndefValue::get(getTypes().ConvertType(DDTag));
+    DeleteArgs.add(RValue::get(V), DDTag);
+  }
+
   // Pass the size if the delete function has a size_t parameter.
-  if (PassSizeAndAlign.first) {
+  if (Params.Size) {
     QualType SizeType = *ParamTypeIt++;
     CharUnits DeleteTypeSize = getContext().getTypeSizeInChars(DeleteTy);
     llvm::Value *Size = llvm::ConstantInt::get(ConvertType(SizeType),
@@ -1745,7 +1768,7 @@ void CodeGenFunction::EmitDeleteCall(const FunctionDecl *DeleteFD,
   }
 
   // Pass the alignment if the delete function has an align_val_t parameter.
-  if (PassSizeAndAlign.second) {
+  if (Params.Alignment) {
     QualType AlignValType = *ParamTypeIt++;
     CharUnits DeleteTypeAlign = getContext().toCharUnitsFromBits(
         getContext().getTypeAlignIfKnown(DeleteTy));
@@ -1787,6 +1810,21 @@ CodeGenFunction::pushCallObjectDeleteCleanup(const FunctionDecl *OperatorDelete,
                                         OperatorDelete, ElementType);
 }
 
+/// Emit the code for deleting a single object with a destroying operator
+/// delete. If the element type has a non-virtual destructor, Ptr has already
+/// been converted to the type of the parameter of 'operator delete'. Otherwise
+/// Ptr points to an object of the static type.
+static void EmitDestroyingObjectDelete(CodeGenFunction &CGF,
+                                       const CXXDeleteExpr *DE, Address Ptr,
+                                       QualType ElementType) {
+  auto *Dtor = ElementType->getAsCXXRecordDecl()->getDestructor();
+  if (Dtor && Dtor->isVirtual())
+    CGF.CGM.getCXXABI().emitVirtualObjectDelete(CGF, DE, Ptr, ElementType,
+                                                Dtor);
+  else
+    CGF.EmitDeleteCall(DE->getOperatorDelete(), Ptr.getPointer(), ElementType);
+}
+
 /// Emit the code for deleting a single object.
 static void EmitObjectDelete(CodeGenFunction &CGF,
                              const CXXDeleteExpr *DE,
@@ -1801,6 +1839,9 @@ static void EmitObjectDelete(CodeGenFunction &CGF,
                     DE->getExprLoc(), Ptr.getPointer(),
                     ElementType);
 
+  const FunctionDecl *OperatorDelete = DE->getOperatorDelete();
+  assert(!OperatorDelete->isDestroyingOperatorDelete());
+
   // Find the destructor for the type, if applicable.  If the
   // destructor is virtual, we'll just emit the vcall and return.
   const CXXDestructorDecl *Dtor = nullptr;
@@ -1820,7 +1861,6 @@ static void EmitObjectDelete(CodeGenFunction &CGF,
   // Make sure that we call delete even if the dtor throws.
   // This doesn't have to a conditional cleanup because we're going
   // to pop it off in a second.
-  const FunctionDecl *OperatorDelete = DE->getOperatorDelete();
   CGF.EHStack.pushCleanup<CallObjectDelete>(NormalAndEHCleanup,
                                             Ptr.getPointer(),
                                             OperatorDelete, ElementType);
@@ -1932,10 +1972,19 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) {
   Builder.CreateCondBr(IsNull, DeleteEnd, DeleteNotNull);
   EmitBlock(DeleteNotNull);
 
+  QualType DeleteTy = E->getDestroyedType();
+
+  // A destroying operator delete overrides the entire operation of the
+  // delete expression.
+  if (E->getOperatorDelete()->isDestroyingOperatorDelete()) {
+    EmitDestroyingObjectDelete(*this, E, Ptr, DeleteTy);
+    EmitBlock(DeleteEnd);
+    return;
+  }
+
   // We might be deleting a pointer to array.  If so, GEP down to the
   // first non-array element.
   // (this assumes that A(*)[3][7] is converted to [3 x [7 x %A]]*)
-  QualType DeleteTy = Arg->getType()->getAs<PointerType>()->getPointeeType();
   if (DeleteTy->isConstantArrayType()) {
     llvm::Value *Zero = Builder.getInt32(0);
     SmallVector<llvm::Value*,8> GEP;
index 6e55a67418341a9c2c01e77bc6db467e77d7f1f9..173bc4d8df3498e33b6435dcb1ac8c2878476bc9 100644 (file)
@@ -3664,6 +3664,18 @@ void ItaniumCXXABI::emitCXXStructor(const CXXMethodDecl *MD,
       !CGM.TryEmitBaseDestructorAsAlias(DD))
     return;
 
+  // FIXME: The deleting destructor is equivalent to the selected operator
+  // delete if:
+  //  * either the delete is a destroying operator delete or the destructor
+  //    would be trivial if it weren't virtual,
+  //  * the conversion from the 'this' parameter to the first parameter of the
+  //    destructor is equivalent to a bitcast,
+  //  * the destructor does not have an implicit "this" return, and
+  //  * the operator delete has the same calling convention and IR function type
+  //    as the destructor.
+  // In such cases we should try to emit the deleting dtor as an alias to the
+  // selected 'operator delete'.
+
   llvm::Function *Fn = CGM.codegenCXXStructor(MD, Type);
 
   if (CGType == StructorCodegen::COMDAT) {
index 8ef6df5e740da2a5c4e29308471f0003e3e14be0..16000bc159dab3f901e70bd5472c02e8383adc74 100644 (file)
@@ -116,7 +116,8 @@ public:
   void ResolvedExceptionSpec(const FunctionDecl *FD) override;
   void DeducedReturnType(const FunctionDecl *FD, QualType ReturnType) override;
   void ResolvedOperatorDelete(const CXXDestructorDecl *DD,
-                              const FunctionDecl *Delete) override;
+                              const FunctionDecl *Delete,
+                              Expr *ThisArg) override;
   void CompletedImplicitDefinition(const FunctionDecl *D) override;
   void StaticDataMemberInstantiated(const VarDecl *D) override;
   void DefaultArgumentInstantiated(const ParmVarDecl *D) override;
@@ -183,9 +184,9 @@ void MultiplexASTMutationListener::DeducedReturnType(const FunctionDecl *FD,
     Listeners[i]->DeducedReturnType(FD, ReturnType);
 }
 void MultiplexASTMutationListener::ResolvedOperatorDelete(
-    const CXXDestructorDecl *DD, const FunctionDecl *Delete) {
+    const CXXDestructorDecl *DD, const FunctionDecl *Delete, Expr *ThisArg) {
   for (auto *L : Listeners)
-    L->ResolvedOperatorDelete(DD, Delete);
+    L->ResolvedOperatorDelete(DD, Delete, ThisArg);
 }
 void MultiplexASTMutationListener::CompletedImplicitDefinition(
                                                         const FunctionDecl *D) {
index a01f9d5bab9afe1fae6dad6f2a840b50e67a0db9..750e0623c5bfd5adfe6787b7ead0ffe5bf51ce83 100644 (file)
@@ -7896,8 +7896,34 @@ bool Sema::CheckDestructor(CXXDestructorDecl *Destructor) {
     // If we have a virtual destructor, look up the deallocation function
     if (FunctionDecl *OperatorDelete =
             FindDeallocationFunctionForDestructor(Loc, RD)) {
+      Expr *ThisArg = nullptr;
+
+      // If the notional 'delete this' expression requires a non-trivial
+      // conversion from 'this' to the type of a destroying operator delete's
+      // first parameter, perform that conversion now.
+      if (OperatorDelete->isDestroyingOperatorDelete()) {
+        QualType ParamType = OperatorDelete->getParamDecl(0)->getType();
+        if (!declaresSameEntity(ParamType->getAsCXXRecordDecl(), RD)) {
+          // C++ [class.dtor]p13:
+          //   ... as if for the expression 'delete this' appearing in a
+          //   non-virtual destructor of the destructor's class.
+          ContextRAII SwitchContext(*this, Destructor);
+          ExprResult This =
+              ActOnCXXThis(OperatorDelete->getParamDecl(0)->getLocation());
+          assert(!This.isInvalid() && "couldn't form 'this' expr in dtor?");
+          This = PerformImplicitConversion(This.get(), ParamType, AA_Passing);
+          if (This.isInvalid()) {
+            // FIXME: Register this as a context note so that it comes out
+            // in the right order.
+            Diag(Loc, diag::note_implicit_delete_this_in_destructor_here);
+            return true;
+          }
+          ThisArg = This.get();
+        }
+      }
+
       MarkFunctionReferenced(Loc, OperatorDelete);
-      Destructor->setOperatorDelete(OperatorDelete);
+      Destructor->setOperatorDelete(OperatorDelete, ThisArg);
     }
   }
 
@@ -12664,15 +12690,35 @@ CheckOperatorDeleteDeclaration(Sema &SemaRef, FunctionDecl *FnDecl) {
   if (CheckOperatorNewDeleteDeclarationScope(SemaRef, FnDecl))
     return true;
 
+  auto *MD = dyn_cast<CXXMethodDecl>(FnDecl);
+
+  // C++ P0722:
+  //   Within a class C, the first parameter of a destroying operator delete
+  //   shall be of type C *. The first parameter of any other deallocation
+  //   function shall be of type void *.
+  CanQualType ExpectedFirstParamType =
+      MD && MD->isDestroyingOperatorDelete()
+          ? SemaRef.Context.getCanonicalType(SemaRef.Context.getPointerType(
+                SemaRef.Context.getRecordType(MD->getParent())))
+          : SemaRef.Context.VoidPtrTy;
+
   // C++ [basic.stc.dynamic.deallocation]p2:
-  //   Each deallocation function shall return void and its first parameter
-  //   shall be void*.
-  if (CheckOperatorNewDeleteTypes(SemaRef, FnDecl, SemaRef.Context.VoidTy,
-                                  SemaRef.Context.VoidPtrTy,
-                                 diag::err_operator_delete_dependent_param_type,
-                                 diag::err_operator_delete_param_type))
+  //   Each deallocation function shall return void
+  if (CheckOperatorNewDeleteTypes(
+          SemaRef, FnDecl, SemaRef.Context.VoidTy, ExpectedFirstParamType,
+          diag::err_operator_delete_dependent_param_type,
+          diag::err_operator_delete_param_type))
     return true;
 
+  // C++ P0722:
+  //   A destroying operator delete shall be a usual deallocation function.
+  if (MD && !MD->getParent()->isDependentContext() &&
+      MD->isDestroyingOperatorDelete() && !MD->isUsualDeallocationFunction()) {
+    SemaRef.Diag(MD->getLocation(),
+                 diag::err_destroying_operator_delete_not_usual);
+    return true;
+  }
+
   return false;
 }
 
index 08c4f50091f7ec8495f1750926a427122755e358..d8036c7ef2c149ff994562837d233d5d816a11ef 100644 (file)
@@ -1407,14 +1407,20 @@ namespace {
     UsualDeallocFnInfo() : Found(), FD(nullptr) {}
     UsualDeallocFnInfo(Sema &S, DeclAccessPair Found)
         : Found(Found), FD(dyn_cast<FunctionDecl>(Found->getUnderlyingDecl())),
-          HasSizeT(false), HasAlignValT(false), CUDAPref(Sema::CFP_Native) {
+          Destroying(false), HasSizeT(false), HasAlignValT(false),
+          CUDAPref(Sema::CFP_Native) {
       // A function template declaration is never a usual deallocation function.
       if (!FD)
         return;
-      if (FD->getNumParams() == 3)
+      unsigned NumBaseParams = 1;
+      if (FD->isDestroyingOperatorDelete()) {
+        Destroying = true;
+        ++NumBaseParams;
+      }
+      if (FD->getNumParams() == NumBaseParams + 2)
         HasAlignValT = HasSizeT = true;
-      else if (FD->getNumParams() == 2) {
-        HasSizeT = FD->getParamDecl(1)->getType()->isIntegerType();
+      else if (FD->getNumParams() == NumBaseParams + 1) {
+        HasSizeT = FD->getParamDecl(NumBaseParams)->getType()->isIntegerType();
         HasAlignValT = !HasSizeT;
       }
 
@@ -1428,6 +1434,12 @@ namespace {
 
     bool isBetterThan(const UsualDeallocFnInfo &Other, bool WantSize,
                       bool WantAlign) const {
+      // C++ P0722:
+      //   A destroying operator delete is preferred over a non-destroying
+      //   operator delete.
+      if (Destroying != Other.Destroying)
+        return Destroying;
+
       // C++17 [expr.delete]p10:
       //   If the type has new-extended alignment, a function with a parameter
       //   of type std::align_val_t is preferred; otherwise a function without
@@ -1444,7 +1456,7 @@ namespace {
 
     DeclAccessPair Found;
     FunctionDecl *FD;
-    bool HasSizeT, HasAlignValT;
+    bool Destroying, HasSizeT, HasAlignValT;
     Sema::CUDAFunctionPreference CUDAPref;
   };
 }
@@ -3261,16 +3273,30 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal,
 
     MarkFunctionReferenced(StartLoc, OperatorDelete);
 
-    // Check access and ambiguity of operator delete and destructor.
+    // Check access and ambiguity of destructor if we're going to call it.
+    // Note that this is required even for a virtual delete.
+    bool IsVirtualDelete = false;
     if (PointeeRD) {
       if (CXXDestructorDecl *Dtor = LookupDestructor(PointeeRD)) {
-          CheckDestructorAccess(Ex.get()->getExprLoc(), Dtor,
-                      PDiag(diag::err_access_dtor) << PointeeElem);
+        CheckDestructorAccess(Ex.get()->getExprLoc(), Dtor,
+                              PDiag(diag::err_access_dtor) << PointeeElem);
+        IsVirtualDelete = Dtor->isVirtual();
       }
     }
 
     diagnoseUnavailableAlignedAllocation(*OperatorDelete, StartLoc, true,
                                          *this);
+
+    // Convert the operand to the type of the first parameter of operator
+    // delete. This is only necessary if we selected a destroying operator
+    // delete that we are going to call (non-virtually); converting to void*
+    // is trivial and left to AST consumers to handle.
+    QualType ParamType = OperatorDelete->getParamDecl(0)->getType();
+    if (!IsVirtualDelete && !ParamType->getPointeeType()->isVoidType()) {
+      Ex = PerformImplicitConversion(Ex.get(), ParamType, AA_Passing);
+      if (Ex.isInvalid())
+        return ExprError();
+    }
   }
 
   CXXDeleteExpr *Result = new (Context) CXXDeleteExpr(
index 1283b006dfb305b0dfd964d4f14c61e939ae9881..c3c6c3e3d885e3ee4da297e86b877e8f514afca6 100644 (file)
@@ -1901,9 +1901,12 @@ void ASTDeclReader::VisitCXXDestructorDecl(CXXDestructorDecl *D) {
 
   if (auto *OperatorDelete = ReadDeclAs<FunctionDecl>()) {
     auto *Canon = cast<CXXDestructorDecl>(D->getCanonicalDecl());
+    auto *ThisArg = Record.readExpr();
     // FIXME: Check consistency if we have an old and new operator delete.
-    if (!Canon->OperatorDelete)
+    if (!Canon->OperatorDelete) {
       Canon->OperatorDelete = OperatorDelete;
+      Canon->OperatorDeleteThisArg = ThisArg;
+    }
   }
 }
 
@@ -4113,9 +4116,12 @@ void ASTDeclReader::UpdateDecl(Decl *D,
       // record.
       auto *Del = ReadDeclAs<FunctionDecl>();
       auto *First = cast<CXXDestructorDecl>(D->getCanonicalDecl());
+      auto *ThisArg = Record.readExpr();
       // FIXME: Check consistency if we have an old and new operator delete.
-      if (!First->OperatorDelete)
+      if (!First->OperatorDelete) {
         First->OperatorDelete = Del;
+        First->OperatorDeleteThisArg = ThisArg;
+      }
       break;
     }
 
index 166db2c1af385aa54be39bd123076c77027acf1b..7e88c59b358e0f4ae5c046d9ab86a15c86b85e10 100644 (file)
@@ -5151,6 +5151,7 @@ void ASTWriter::WriteDeclUpdatesBlocks(RecordDataImpl &OffsetsRecord) {
 
       case UPD_CXX_RESOLVED_DTOR_DELETE:
         Record.AddDeclRef(Update.getDecl());
+        Record.AddStmt(cast<CXXDestructorDecl>(D)->getOperatorDeleteThisArg());
         break;
 
       case UPD_CXX_RESOLVED_EXCEPTION_SPEC:
@@ -6178,7 +6179,8 @@ void ASTWriter::DeducedReturnType(const FunctionDecl *FD, QualType ReturnType) {
 }
 
 void ASTWriter::ResolvedOperatorDelete(const CXXDestructorDecl *DD,
-                                       const FunctionDecl *Delete) {
+                                       const FunctionDecl *Delete,
+                                       Expr *ThisArg) {
   if (Chain && Chain->isProcessingUpdateRecords()) return;
   assert(!WritingAST && "Already writing the AST!");
   assert(Delete && "Not given an operator delete");
index bdf3fca49a7f1a4a6c914bac12dcfea6fd934bc2..7a3f2e34bb4411d3d2cc25fcc37f3c8cb0663001 100644 (file)
@@ -1309,6 +1309,8 @@ void ASTDeclWriter::VisitCXXDestructorDecl(CXXDestructorDecl *D) {
   VisitCXXMethodDecl(D);
 
   Record.AddDeclRef(D->getOperatorDelete());
+  if (D->getOperatorDelete())
+    Record.AddStmt(D->getOperatorDeleteThisArg());
 
   Code = serialization::DECL_CXX_DESTRUCTOR;
 }
diff --git a/test/CodeGenCXX/cxx2a-destroying-delete.cpp b/test/CodeGenCXX/cxx2a-destroying-delete.cpp
new file mode 100644 (file)
index 0000000..2e4d407
--- /dev/null
@@ -0,0 +1,161 @@
+// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -triple x86_64-linux-gnu -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-ITANIUM
+// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -triple x86_64-windows -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-MSABI
+
+namespace std {
+  using size_t = decltype(sizeof(0));
+  enum class align_val_t : size_t;
+  struct destroying_delete_t {};
+}
+
+struct A {
+  void *data;
+  ~A();
+  void operator delete(A*, std::destroying_delete_t);
+};
+void delete_A(A *a) { delete a; }
+// CHECK-LABEL: define {{.*}}delete_A
+// CHECK: %[[a:.*]] = load
+// CHECK: icmp eq %{{.*}} %[[a]], null
+// CHECK: br i1
+//
+// Ensure that we call the destroying delete and not the destructor.
+// CHECK-NOT: call
+// CHECK-ITANIUM: call void @_ZN1AdlEPS_St19destroying_delete_t(%{{.*}}* %[[a]])
+// CHECK-MSABI: call void @"\01??3A@@SAXPEAU0@Udestroying_delete_t@std@@@Z"(%{{.*}}* %[[a]], i8
+// CHECK-NOT: call
+// CHECK: }
+
+struct B {
+  virtual ~B();
+  void operator delete(B*, std::destroying_delete_t);
+};
+void delete_B(B *b) { delete b; }
+// CHECK-LABEL: define {{.*}}delete_B
+// CHECK: %[[b:.*]] = load
+// CHECK: icmp eq %{{.*}} %[[b]], null
+// CHECK: br i1
+//
+// Ensure that we call the virtual destructor and not the operator delete.
+// CHECK-NOT: call
+// CHECK: %[[VTABLE:.*]] = load
+// CHECK: %[[DTOR:.*]] = load
+// CHECK: call {{void|i8\*}} %[[DTOR]](%{{.*}}* %[[b]]
+// CHECK-MSABI-SAME: , i32 1)
+// CHECK-NOT: call
+// CHECK: }
+
+struct Padding {
+  virtual void f();
+};
+
+struct C : Padding, A {};
+void delete_C(C *c) { delete c; }
+// Check that we perform a derived-to-base conversion on the parameter to 'operator delete'.
+// CHECK-LABEL: define {{.*}}delete_C
+// CHECK: %[[c:.*]] = load
+// CHECK: icmp eq %{{.*}} %[[c]], null
+// CHECK: br i1
+//
+// CHECK: %[[base:.*]] = getelementptr {{.*}}, i64 8
+// CHECK: %[[castbase:.*]] = bitcast {{.*}} %[[base]]
+//
+// CHECK: %[[a:.*]] = phi {{.*}} %[[castbase]]
+// CHECK: icmp eq %{{.*}} %[[a]], null
+// CHECK: br i1
+//
+// CHECK-NOT: call
+// CHECK-ITANIUM: call void @_ZN1AdlEPS_St19destroying_delete_t(%{{.*}}* %[[a]])
+// CHECK-MSABI: call void @"\01??3A@@SAXPEAU0@Udestroying_delete_t@std@@@Z"(%{{.*}}* %[[a]], i8
+// CHECK-NOT: call
+// CHECK: }
+
+struct VDel { virtual ~VDel(); };
+struct D : Padding, VDel, B {};
+void delete_D(D *d) { delete d; }
+// CHECK-LABEL: define {{.*}}delete_D
+// CHECK: %[[d:.*]] = load
+// CHECK: icmp eq %{{.*}} %[[d]], null
+// CHECK: br i1
+//
+// CHECK-NOT: call
+// CHECK: %[[VTABLE:.*]] = load
+// CHECK: %[[DTOR:.*]] = load
+//
+// For MS, we don't add a new vtable slot to the primary vtable for the virtual
+// destructor. Instead we cast to the VDel base class.
+// CHECK-MSABI: bitcast {{.*}} %[[d]]
+// CHECK-MSABI-NEXT: getelementptr {{.*}}, i64 8
+// CHECK-MSABI-NEXT: %[[d:.*]] = bitcast i8*
+//
+// CHECK: call {{void|i8\*}} %[[DTOR]](%{{.*}}* %[[d]]
+// CHECK-MSABI-SAME: , i32 1)
+// CHECK-NOT: call
+// CHECK: }
+
+struct E { void *data; };
+struct F { void operator delete(F *, std::destroying_delete_t, std::size_t, std::align_val_t); void *data; };
+struct alignas(16) G : E, F { void *data; };
+
+void delete_G(G *g) { delete g; }
+// CHECK-LABEL: define {{.*}}delete_G
+// CHECK-NOT: call
+// CHECK-ITANIUM: call void @_ZN1FdlEPS_St19destroying_delete_tmSt11align_val_t(%{{.*}}* %[[a]], i64 32, i64 16)
+// CHECK-MSABI: call void @"\01??3F@@SAXPEAU0@Udestroying_delete_t@std@@_KW4align_val_t@2@@Z"(%{{.*}}* %[[a]], i8 {{[^,]*}}, i64 32, i64 16)
+// CHECK-NOT: call
+// CHECK: }
+
+void call_in_dtor();
+
+struct H : G { virtual ~H(); } h;
+H::~H() { call_in_dtor(); }
+// CHECK-ITANIUM-LABEL: define void @_ZN1HD0Ev(
+// CHECK-ITANIUM-NOT: call
+// CHECK-ITANIUM: getelementptr {{.*}}, i64 24
+// CHECK-ITANIUM-NOT: call
+// CHECK-ITANIUM: call void @_ZN1FdlEPS_St19destroying_delete_tmSt11align_val_t({{.*}}, i64 48, i64 16)
+// CHECK-ITANIUM-NOT: call
+// CHECK-ITANIUM: }
+
+// CHECK-MSABI: define {{.*}} @"\01??_GH@@UEAAPEAXI@Z"(
+// CHECK-MSABI-NOT: call{{ }}
+// CHECK-MSABI: load i32
+// CHECK-MSABI: icmp eq i32 {{.*}}, 0
+// CHECK-MSABI: br i1
+//
+// CHECK-MSABI-NOT: call{{ }}
+// CHECK-MSABI: getelementptr {{.*}}, i64 24
+// CHECK-MSABI-NOT: call{{ }}
+// CHECK-MSABI: call void @"\01??3F@@SAXPEAU0@Udestroying_delete_t@std@@_KW4align_val_t@2@@Z"({{.*}}, i64 48, i64 16)
+// CHECK-MSABI: br label %[[RETURN:.*]]
+//
+// CHECK-MSABI: call void @"\01??_DH@@QEAAXXZ"(
+// CHECK-MSABI: br label %[[RETURN]]
+//
+// CHECK-MSABI: }
+
+struct I : H { virtual ~I(); alignas(32) char buffer[32]; } i;
+I::~I() { call_in_dtor(); }
+// CHECK-ITANIUM-LABEL: define void @_ZN1ID0Ev(
+// CHECK-ITANIUM-NOT: call
+// CHECK-ITANIUM: getelementptr {{.*}}, i64 24
+// CHECK-ITANIUM-NOT: call
+// CHECK-ITANIUM: call void @_ZN1FdlEPS_St19destroying_delete_tmSt11align_val_t({{.*}}, i64 96, i64 32)
+// CHECK-ITANIUM-NOT: call
+// CHECK-ITANIUM: }
+
+// CHECK-MSABI: define {{.*}} @"\01??_GI@@UEAAPEAXI@Z"(
+// CHECK-MSABI-NOT: call{{ }}
+// CHECK-MSABI: load i32
+// CHECK-MSABI: icmp eq i32 {{.*}}, 0
+// CHECK-MSABI: br i1
+//
+// CHECK-MSABI-NOT: call{{ }}
+// CHECK-MSABI: getelementptr {{.*}}, i64 24
+// CHECK-MSABI-NOT: call{{ }}
+// CHECK-MSABI: call void @"\01??3F@@SAXPEAU0@Udestroying_delete_t@std@@_KW4align_val_t@2@@Z"({{.*}}, i64 96, i64 32)
+// CHECK-MSABI: br label %[[RETURN:.*]]
+//
+// CHECK-MSABI: call void @"\01??_DI@@QEAAXXZ"(
+// CHECK-MSABI: br label %[[RETURN]]
+//
+// CHECK-MSABI: }
diff --git a/test/SemaCXX/cxx2a-destroying-delete.cpp b/test/SemaCXX/cxx2a-destroying-delete.cpp
new file mode 100644 (file)
index 0000000..a085c11
--- /dev/null
@@ -0,0 +1,103 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+namespace std {
+  using size_t = decltype(sizeof(0));
+  enum class align_val_t : size_t;
+
+  struct destroying_delete_t {
+    struct __construct { explicit __construct() = default; };
+    explicit destroying_delete_t(__construct) {}
+  };
+
+  inline constexpr destroying_delete_t destroying_delete(destroying_delete_t::__construct());
+}
+
+void operator delete(void*, std::destroying_delete_t); // ok, just a placement delete
+
+struct A;
+void operator delete(A*, std::destroying_delete_t); // expected-error {{first parameter of 'operator delete' must have type 'void *'}}
+
+struct A {
+  void operator delete(A*, std::destroying_delete_t);
+  void operator delete(A*, std::destroying_delete_t, std::size_t);
+  void operator delete(A*, std::destroying_delete_t, std::align_val_t);
+  void operator delete(A*, std::destroying_delete_t, std::size_t, std::align_val_t);
+  void operator delete(A*, std::destroying_delete_t, int); // expected-error {{destroying operator delete can have only an optional size and optional alignment parameter}}
+  // FIXME: It's probably a language defect that we permit usual operator delete to be variadic.
+  void operator delete(A*, std::destroying_delete_t, std::size_t, ...);
+
+  void operator delete(struct X*, std::destroying_delete_t, std::size_t, ...); // expected-error {{first parameter of 'operator delete' must have type 'A *'}}
+
+  void operator delete(void*, std::size_t);
+};
+
+void delete_A(A *a) { delete a; }
+
+namespace convert_param {
+  struct A {
+    void operator delete(
+        A*,
+        std::destroying_delete_t);
+  };
+  struct B : private A { using A::operator delete; }; // expected-note 2{{declared private here}}
+  struct C : B {};
+  void delete_C(C *c) { delete c; } // expected-error {{cannot cast 'convert_param::C' to its private base class 'convert_param::A'}}
+
+  // expected-error@-7 {{cannot cast 'convert_param::D' to its private base class 'convert_param::A'}}
+  struct D : B { virtual ~D() {} }; // expected-note {{while checking implicit 'delete this' for virtual destructor}}
+}
+
+namespace delete_selection {
+  struct B {
+    void operator delete(void*) = delete;
+    void operator delete(B *, std::destroying_delete_t) = delete; // expected-note {{deleted}}
+  };
+  void delete_B(B *b) { delete b; } // expected-error {{deleted}}
+
+  struct C {
+    C();
+    void *operator new(std::size_t);
+    void operator delete(void*) = delete;
+    void operator delete(C *, std::destroying_delete_t) = delete;
+  };
+  // FIXME: This should be ill-formed, but we incorrectly decide that overload
+  // resolution failed (because it selected a deleted function) and thus no
+  // 'operator delete' should be called.
+  C *new_C() { return new C; }
+
+  struct D {
+    void operator delete(D *, std::destroying_delete_t) = delete; // expected-note {{deleted}}
+    void operator delete(D *, std::destroying_delete_t, std::align_val_t) = delete;
+  };
+  void delete_D(D *d) { delete d; } // expected-error {{deleted}}
+
+  struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) E {
+    void operator delete(E *, std::destroying_delete_t) = delete;
+    void operator delete(E *, std::destroying_delete_t, std::align_val_t) = delete; // expected-note {{deleted}}
+  };
+  void delete_E(E *e) { delete e; } // expected-error {{deleted}}
+
+  struct F {
+    void operator delete(F *, std::destroying_delete_t) = delete; // expected-note {{deleted}}
+    void operator delete(F *, std::destroying_delete_t, std::size_t) = delete;
+  };
+  void delete_F(F *f) { delete f; } // expected-error {{deleted}}
+
+  struct G {
+    void operator delete(G *, std::destroying_delete_t, std::align_val_t) = delete;
+    void operator delete(G *, std::destroying_delete_t, std::size_t) = delete; // expected-note {{deleted}}
+  };
+  void delete_G(G *g) { delete g; } // expected-error {{deleted}}
+
+  struct H {
+    void operator delete(H *, std::destroying_delete_t, std::align_val_t) = delete; // expected-note {{deleted}}
+    void operator delete(H *, std::destroying_delete_t, std::size_t, std::align_val_t) = delete;
+  };
+  void delete_H(H *h) { delete h; } // expected-error {{deleted}}
+
+  struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) I {
+    void operator delete(I *, std::destroying_delete_t, std::size_t) = delete;
+    void operator delete(I *, std::destroying_delete_t, std::size_t, std::align_val_t) = delete; // expected-note {{deleted}}
+  };
+  void delete_I(I *i) { delete i; } // expected-error {{deleted}}
+}