Support for destroying operator delete, per C++2a proposal P0722.
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).
llvm-svn: 315662
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index a01f9d5..750e062 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7896,8 +7896,34 @@
// 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 @@
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;
}
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 08c4f50..d8036c7 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -1407,14 +1407,20 @@
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 @@
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 @@
DeclAccessPair Found;
FunctionDecl *FD;
- bool HasSizeT, HasAlignValT;
+ bool Destroying, HasSizeT, HasAlignValT;
Sema::CUDAFunctionPreference CUDAPref;
};
}
@@ -3261,16 +3273,30 @@
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(