diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 77bf3355af9d..beab7600e2c3 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -93,6 +93,8 @@ C++2c Feature Support - Implemented `P0963R3 Structured binding declaration as a condition `_. +- Implemented `P2719R4 Type-aware allocation and deallocation functions `_. + C++23 Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index c339bb698e09..b8ea2af9215d 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -329,6 +329,9 @@ class ASTContext : public RefCountedBase { /// This is lazily created. This is intentionally not serialized. mutable llvm::StringMap StringLiteralCache; + mutable llvm::DenseSet DestroyingOperatorDeletes; + mutable llvm::DenseSet TypeAwareOperatorNewAndDeletes; + /// The next string literal "version" to allocate during constant evaluation. /// This is used to distinguish between repeated evaluations of the same /// string literal. @@ -3356,6 +3359,15 @@ public: void setStaticLocalNumber(const VarDecl *VD, unsigned Number); unsigned getStaticLocalNumber(const VarDecl *VD) const; + bool hasSeenTypeAwareOperatorNewOrDelete() const { + return !TypeAwareOperatorNewAndDeletes.empty(); + } + void setIsDestroyingOperatorDelete(const FunctionDecl *FD, bool IsDestroying); + bool isDestroyingOperatorDelete(const FunctionDecl *FD) const; + void setIsTypeAwareOperatorNewOrDelete(const FunctionDecl *FD, + bool IsTypeAware); + bool isTypeAwareOperatorNewOrDelete(const FunctionDecl *FD) const; + /// Retrieve the context for computing mangling numbers in the given /// DeclContext. MangleNumberingContext &getManglingNumberContext(const DeclContext *DC); diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 798f112ce720..3faf63e395a0 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -2540,6 +2540,38 @@ public: /// If this function is an allocation/deallocation function that takes /// the `std::nothrow_t` tag, return true through IsNothrow, bool isReplaceableGlobalAllocationFunction( + UnsignedOrNone *AlignmentParam = nullptr, + bool *IsNothrow = nullptr) const { + if (isTypeAwareOperatorNewOrDelete()) + return false; + return isUsableAsGlobalAllocationFunctionInConstantEvaluation( + AlignmentParam, IsNothrow); + } + + /// Determines whether this function is one of the replaceable global + /// allocation functions described in isReplaceableGlobalAllocationFunction, + /// or is a function that may be treated as such during constant evaluation. + /// This adds support for potentially templated type aware global allocation + /// functions of the form: + /// void *operator new(type-identity, std::size_t, std::align_val_t) + /// void *operator new(type-identity, std::size_t, std::align_val_t, + /// const std::nothrow_t &) noexcept; + /// void *operator new[](type-identity, std::size_t, std::align_val_t) + /// void *operator new[](type-identity, std::size_t, std::align_val_t, + /// const std::nothrow_t &) noexcept; + /// void operator delete(type-identity, void*, std::size_t, + /// std::align_val_t) noexcept; + /// void operator delete(type-identity, void*, std::size_t, + /// std::align_val_t, const std::nothrow_t&) noexcept; + /// void operator delete[](type-identity, void*, std::size_t, + /// std::align_val_t) noexcept; + /// void operator delete[](type-identity, void*, std::size_t, + /// std::align_val_t, const std::nothrow_t&) noexcept; + /// Where `type-identity` is a specialization of std::type_identity. If the + /// declaration is a templated function, it may not include a parameter pack + /// in the argument list, the type-identity parameter is required to be + /// dependent, and is the only permitted dependent parameter. + bool isUsableAsGlobalAllocationFunctionInConstantEvaluation( UnsignedOrNone *AlignmentParam = nullptr, bool *IsNothrow = nullptr) const; @@ -2548,6 +2580,20 @@ public: /// Determine whether this is a destroying operator delete. bool isDestroyingOperatorDelete() const; + void setIsDestroyingOperatorDelete(bool IsDestroyingDelete); + + /// Count of mandatory parameters for type aware operator new + static constexpr unsigned RequiredTypeAwareNewParameterCount = + /* type-identity */ 1 + /* size */ 1 + /* alignment */ 1; + + /// Count of mandatory parameters for type aware operator delete + static constexpr unsigned RequiredTypeAwareDeleteParameterCount = + /* type-identity */ 1 + /* address */ 1 + /* size */ 1 + + /* alignment */ 1; + + /// Determine whether this is a type aware operator new or delete. + bool isTypeAwareOperatorNewOrDelete() const; + void setIsTypeAwareOperatorNewOrDelete(bool IsTypeAwareOperator = true); /// Compute the language linkage. LanguageLinkage getLanguageLinkage() const; diff --git a/clang/include/clang/AST/DeclarationName.h b/clang/include/clang/AST/DeclarationName.h index c9b01dc53964..9bf740b3bf7c 100644 --- a/clang/include/clang/AST/DeclarationName.h +++ b/clang/include/clang/AST/DeclarationName.h @@ -477,6 +477,34 @@ public: return OO_None; } + bool isAnyOperatorNew() const { + if (getNameKind() != DeclarationName::CXXOperatorName) + return false; + switch (getCXXOverloadedOperator()) { + case OO_New: + case OO_Array_New: + return true; + default: + return false; + } + } + + bool isAnyOperatorDelete() const { + if (getNameKind() != DeclarationName::CXXOperatorName) + return false; + switch (getCXXOverloadedOperator()) { + case OO_Delete: + case OO_Array_Delete: + return true; + default: + return false; + } + } + + bool isAnyOperatorNewOrDelete() const { + return isAnyOperatorNew() || isAnyOperatorDelete(); + } + /// If this name is the name of a literal operator, /// retrieve the identifier associated with it. const IdentifierInfo *getCXXLiteralIdentifier() const { diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index c613ce162a6a..844f6dd90ae1 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -2235,6 +2235,98 @@ enum class CXXNewInitializationStyle { Braces }; +enum class TypeAwareAllocationMode : unsigned { No, Yes }; + +inline bool isTypeAwareAllocation(TypeAwareAllocationMode Mode) { + return Mode == TypeAwareAllocationMode::Yes; +} + +inline TypeAwareAllocationMode +typeAwareAllocationModeFromBool(bool IsTypeAwareAllocation) { + return IsTypeAwareAllocation ? TypeAwareAllocationMode::Yes + : TypeAwareAllocationMode::No; +} + +enum class AlignedAllocationMode : unsigned { No, Yes }; + +inline bool isAlignedAllocation(AlignedAllocationMode Mode) { + return Mode == AlignedAllocationMode::Yes; +} + +inline AlignedAllocationMode alignedAllocationModeFromBool(bool IsAligned) { + return IsAligned ? AlignedAllocationMode::Yes : AlignedAllocationMode::No; +} + +enum class SizedDeallocationMode : unsigned { No, Yes }; + +inline bool isSizedDeallocation(SizedDeallocationMode Mode) { + return Mode == SizedDeallocationMode::Yes; +} + +inline SizedDeallocationMode sizedDeallocationModeFromBool(bool IsSized) { + return IsSized ? SizedDeallocationMode::Yes : SizedDeallocationMode::No; +} + +struct ImplicitAllocationParameters { + ImplicitAllocationParameters(QualType AllocType, + TypeAwareAllocationMode PassTypeIdentity, + AlignedAllocationMode PassAlignment) + : Type(AllocType), PassTypeIdentity(PassTypeIdentity), + PassAlignment(PassAlignment) { + if (!Type.isNull()) + Type = Type.getUnqualifiedType(); + } + explicit ImplicitAllocationParameters(AlignedAllocationMode PassAlignment) + : PassTypeIdentity(TypeAwareAllocationMode::No), + PassAlignment(PassAlignment) {} + + unsigned getNumImplicitArgs() const { + unsigned Count = 1; // Size + if (isTypeAwareAllocation(PassTypeIdentity)) + ++Count; + if (isAlignedAllocation(PassAlignment)) + ++Count; + return Count; + } + + QualType Type; + TypeAwareAllocationMode PassTypeIdentity; + AlignedAllocationMode PassAlignment; +}; + +struct ImplicitDeallocationParameters { + ImplicitDeallocationParameters(QualType DeallocType, + TypeAwareAllocationMode PassTypeIdentity, + AlignedAllocationMode PassAlignment, + SizedDeallocationMode PassSize) + : Type(DeallocType), PassTypeIdentity(PassTypeIdentity), + PassAlignment(PassAlignment), PassSize(PassSize) { + if (!Type.isNull()) + Type = Type.getUnqualifiedType(); + } + + ImplicitDeallocationParameters(AlignedAllocationMode PassAlignment, + SizedDeallocationMode PassSize) + : PassTypeIdentity(TypeAwareAllocationMode::No), + PassAlignment(PassAlignment), PassSize(PassSize) {} + + unsigned getNumImplicitArgs() const { + unsigned Count = 1; // Size + if (isTypeAwareAllocation(PassTypeIdentity)) + ++Count; + if (isAlignedAllocation(PassAlignment)) + ++Count; + if (isSizedDeallocation(PassSize)) + ++Count; + return Count; + } + + QualType Type; + TypeAwareAllocationMode PassTypeIdentity; + AlignedAllocationMode PassAlignment; + SizedDeallocationMode PassSize; +}; + /// Represents a new-expression for memory allocation and constructor /// calls, e.g: "new CXXNewExpr(foo)". class CXXNewExpr final @@ -2290,7 +2382,8 @@ class CXXNewExpr final /// Build a c++ new expression. CXXNewExpr(bool IsGlobalNew, FunctionDecl *OperatorNew, - FunctionDecl *OperatorDelete, bool ShouldPassAlignment, + FunctionDecl *OperatorDelete, + const ImplicitAllocationParameters &IAP, bool UsualArrayDeleteWantsSize, ArrayRef PlacementArgs, SourceRange TypeIdParens, std::optional ArraySize, CXXNewInitializationStyle InitializationStyle, Expr *Initializer, @@ -2305,7 +2398,7 @@ public: /// Create a c++ new expression. static CXXNewExpr * Create(const ASTContext &Ctx, bool IsGlobalNew, FunctionDecl *OperatorNew, - FunctionDecl *OperatorDelete, bool ShouldPassAlignment, + FunctionDecl *OperatorDelete, const ImplicitAllocationParameters &IAP, bool UsualArrayDeleteWantsSize, ArrayRef PlacementArgs, SourceRange TypeIdParens, std::optional ArraySize, CXXNewInitializationStyle InitializationStyle, Expr *Initializer, @@ -2394,6 +2487,10 @@ public: return const_cast(this)->getPlacementArg(I); } + unsigned getNumImplicitArgs() const { + return implicitAllocationParameters().getNumImplicitArgs(); + } + bool isParenTypeId() const { return CXXNewExprBits.IsParenTypeId; } SourceRange getTypeIdParens() const { return isParenTypeId() ? getTrailingObjects()[0] @@ -2439,6 +2536,15 @@ public: return CXXNewExprBits.UsualArrayDeleteWantsSize; } + /// Provides the full set of information about expected implicit + /// parameters in this call + ImplicitAllocationParameters implicitAllocationParameters() const { + return ImplicitAllocationParameters{ + getAllocatedType(), + typeAwareAllocationModeFromBool(CXXNewExprBits.ShouldPassTypeIdentity), + alignedAllocationModeFromBool(CXXNewExprBits.ShouldPassAlignment)}; + } + using arg_iterator = ExprIterator; using const_arg_iterator = ConstExprIterator; diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 1ab951a005fd..336eb6d3df7e 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -891,6 +891,10 @@ protected: LLVM_PREFERRED_TYPE(bool) unsigned ShouldPassAlignment : 1; + /// Should the type identity be passed to the allocation function? + LLVM_PREFERRED_TYPE(bool) + unsigned ShouldPassTypeIdentity : 1; + /// If this is an array allocation, does the usual deallocation /// function for the allocated type want to know the allocated size? LLVM_PREFERRED_TYPE(bool) diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 09f423ad4b4b..d97bbfee2e4d 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -64,8 +64,11 @@ def AlwaysInlineCoroutine : DiagGroup<"always-inline-coroutine">; def CoroNonAlignedAllocationFunction : DiagGroup<"coro-non-aligned-allocation-function">; +def CoroTypeAwareAllocationFunction : + DiagGroup<"coro-type-aware-allocation-function">; def Coroutine : DiagGroup<"coroutine", [CoroutineMissingUnhandledException, DeprecatedCoroutine, - AlwaysInlineCoroutine, CoroNonAlignedAllocationFunction]>; + AlwaysInlineCoroutine, CoroNonAlignedAllocationFunction, + CoroTypeAwareAllocationFunction]>; def ObjCBoolConstantConversion : DiagGroup<"objc-bool-constant-conversion">; def ConstantConversion : DiagGroup<"constant-conversion", [BitFieldConstantConversion, diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 3cb2731488fa..b0972541cb4a 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2595,8 +2595,8 @@ def err_auto_non_deduced_not_alone : Error< def err_implied_std_initializer_list_not_found : Error< "cannot deduce type of initializer list because std::initializer_list was " "not found; include ">; -def err_malformed_std_initializer_list : Error< - "std::initializer_list must be a class template with a single type parameter">; +def err_malformed_std_class_template : Error< + "std::%0 must be a class template with a single type parameter">; def err_auto_init_list_from_c : Error< "cannot use %select{'auto'||'__auto_type'}0 with " "%select{initializer list|array}1 in C">; @@ -9757,7 +9757,7 @@ def err_operator_new_delete_invalid_result_type : Error< def err_operator_new_delete_dependent_result_type : Error< "%0 cannot have a dependent return type; use %1 instead">; def err_operator_new_delete_too_few_parameters : Error< - "%0 must have at least one parameter">; + "%select{|type aware }0%select{|destroying }1%2 must have at least %select{|one|two|three|four|five}3 parameter%s3">; def err_operator_new_delete_template_too_few_parameters : Error< "%0 template must have at least two parameters">; def warn_operator_new_returns_null : Warning< @@ -9765,19 +9765,38 @@ def warn_operator_new_returns_null : Warning< "%select{| or 'noexcept'}1">, InGroup; def err_operator_new_dependent_param_type : Error< - "%0 cannot take a dependent type as first parameter; " - "use size_t (%1) instead">; + "%select{|type aware }0%select{|destroying }1%2 cannot take a dependent type as its %ordinal3 parameter; " + "use %5 (%4) instead">; def err_operator_new_param_type : Error< - "%0 takes type size_t (%1) as first parameter">; + "%select{|type aware }0%select{|destroying }1%2 takes type %5 (%4) as %ordinal3 parameter">; def err_operator_new_default_arg: Error< "parameter of %0 cannot have a default argument">; def err_operator_delete_dependent_param_type : Error< - "%0 cannot take a dependent type as first parameter; use %1 instead">; + "%select{|type aware }0%select{|destroying }1%2 cannot take a dependent type as its %ordinal3 parameter; " + "use %4 instead">; def err_operator_delete_param_type : Error< - "first parameter of %0 must have type %1">; + "%ordinal3 parameter of%select{| type aware}0%select{| destroying}1 %2 must have type %4">; def err_destroying_operator_delete_not_usual : Error< "destroying operator delete can have only an optional size and optional " "alignment parameter">; + +def err_type_aware_destroying_operator_delete : Error< + "destroying delete is not permitted to be type aware">; + +def ext_cxx26_type_aware_allocators : ExtWarn< + "type aware allocators are a C++2c extension">, InGroup; +def warn_cxx26_type_aware_allocators : Warning< + "type aware allocators are incompatible with C++ standards before C++2c">, + DefaultIgnore, InGroup; +def err_type_aware_allocator_missing_matching_operator : Error< + "declaration of type aware %0 in %1 must have matching type aware %2" +>; +def note_unmatched_type_aware_allocator_declared : Note< + "unmatched type aware %0 declared here">; +def err_mismatching_type_aware_cleanup_deallocator : Error< + "type aware %0 requires a matching type aware %select{|placement }1%2 to be declared in the same scope">; +def note_type_aware_operator_declared : Note< + "%select{non-|}0type aware %1 declared here in %2">; def note_implicit_delete_this_in_destructor_here : Note< "while checking implicit 'delete this' for virtual destructor">; def err_builtin_operator_new_delete_not_usual : Error< @@ -12149,6 +12168,9 @@ def warn_always_inline_coroutine : Warning< def err_coroutine_unusable_new : Error< "'operator new' provided by %0 is not usable with the function signature of %1" >; +def note_coroutine_unusable_type_aware_allocators : Note< + "type aware %0 will not be used for coroutine allocation" +>; def err_coroutine_unfound_nothrow_new : Error < "unable to find %select{'::operator new(size_t, nothrow_t)'|" "'::operator new(size_t, align_val_t, nothrow_t)'}1 for %0" @@ -12168,6 +12190,9 @@ def err_coroutine_return_type : Error< "function returns a type %0 marked with [[clang::coro_return_type]] but is neither a coroutine nor a coroutine wrapper; " "non-coroutines should be marked with [[clang::coro_wrapper]] to allow returning coroutine return type" >; +def warn_coroutine_type_aware_allocator_ignored : Warning < + "type aware %0 will not be used for coroutine allocation">, + InGroup; } // end of coroutines issue category let CategoryName = "Documentation Issue" in { diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index c42887deebde..2242269c30b0 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -624,6 +624,7 @@ defvar cpp14 = LangOpts<"CPlusPlus14">; defvar cpp17 = LangOpts<"CPlusPlus17">; defvar cpp20 = LangOpts<"CPlusPlus20">; defvar cpp23 = LangOpts<"CPlusPlus23">; +defvar cpp26 = LangOpts<"CPlusPlus26">; defvar c99 = LangOpts<"C99">; defvar c23 = LangOpts<"C23">; defvar lang_std = LangOpts<"LangStd">; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index f65dd0191c66..4c70ad38db25 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4891,6 +4891,10 @@ public: CXXRecordDecl *getStdBadAlloc() const; EnumDecl *getStdAlignValT() const; + TypeAwareAllocationMode ShouldUseTypeAwareOperatorNewOrDelete() const; + FunctionDecl *BuildTypeAwareUsualDelete(FunctionTemplateDecl *FnDecl, + QualType AllocType, SourceLocation); + ValueDecl *tryLookupUnambiguousFieldDecl(RecordDecl *ClassDecl, const IdentifierInfo *MemberOrBase); @@ -4918,12 +4922,27 @@ public: /// it is and Element is not NULL, assigns the element type to Element. bool isStdInitializerList(QualType Ty, QualType *Element); + /// Tests whether Ty is an instance of std::type_identity and, if + /// it is and TypeArgument is not NULL, assigns the element type to Element. + /// If MalformedDecl is not null, and type_identity was ruled out due to being + /// incorrectly structured despite having the correct name, the faulty Decl + /// will be assigned to MalformedDecl. + bool isStdTypeIdentity(QualType Ty, QualType *TypeArgument, + const Decl **MalformedDecl = nullptr); + /// Looks for the std::initializer_list template and instantiates it /// with Element, or emits an error if it's not found. /// /// \returns The instantiated template, or null on error. QualType BuildStdInitializerList(QualType Element, SourceLocation Loc); + /// Looks for the std::type_identity template and instantiates it + /// with Type, or returns a null type if type_identity has not been declared + /// + /// \returns The instantiated template, or null if std::type_identity is not + /// declared + QualType tryBuildStdTypeIdentity(QualType Type, SourceLocation Loc); + /// Determine whether Ctor is an initializer-list constructor, as /// defined in [dcl.init.list]p2. bool isInitListConstructor(const FunctionDecl *Ctor); @@ -6172,6 +6191,10 @@ public: /// \. ClassTemplateDecl *StdInitializerList; + /// The C++ "std::type_identity" template, which is defined in + /// \. + ClassTemplateDecl *StdTypeIdentity; + // Contains the locations of the beginning of unparsed default // argument locations. llvm::DenseMap UnparsedDefaultArgLocs; @@ -8298,14 +8321,12 @@ public: /// Finds the overloads of operator new and delete that are appropriate /// for the allocation. - bool FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, - AllocationFunctionScope NewScope, - AllocationFunctionScope DeleteScope, - QualType AllocType, bool IsArray, - bool &PassAlignment, MultiExprArg PlaceArgs, - FunctionDecl *&OperatorNew, - FunctionDecl *&OperatorDelete, - bool Diagnose = true); + bool FindAllocationFunctions( + SourceLocation StartLoc, SourceRange Range, + AllocationFunctionScope NewScope, AllocationFunctionScope DeleteScope, + QualType AllocType, bool IsArray, ImplicitAllocationParameters &IAP, + MultiExprArg PlaceArgs, FunctionDecl *&OperatorNew, + FunctionDecl *&OperatorDelete, bool Diagnose = true); /// DeclareGlobalNewDelete - Declare the global forms of operator new and /// delete. These are: @@ -8336,11 +8357,10 @@ public: bool FindDeallocationFunction(SourceLocation StartLoc, CXXRecordDecl *RD, DeclarationName Name, FunctionDecl *&Operator, - bool Diagnose = true, bool WantSize = false, - bool WantAligned = false); + ImplicitDeallocationParameters, + bool Diagnose = true); FunctionDecl *FindUsualDeallocationFunction(SourceLocation StartLoc, - bool CanProvideSize, - bool Overaligned, + ImplicitDeallocationParameters, DeclarationName Name); FunctionDecl *FindDeallocationFunctionForDestructor(SourceLocation StartLoc, CXXRecordDecl *RD, diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h index 4552df2a2a31..f6a43bf5f493 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -1146,7 +1146,7 @@ public: /// C++17 aligned operator new() calls that have alignment implicitly /// passed as the second argument, and to 1 for other operator new() calls. unsigned getNumImplicitArgs() const { - return getOriginExpr()->passAlignment() ? 2 : 1; + return getOriginExpr()->getNumImplicitArgs(); } unsigned getNumArgs() const override { diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 6ad49c27b34d..b8e624523047 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13104,6 +13104,32 @@ unsigned ASTContext::getStaticLocalNumber(const VarDecl *VD) const { return I != StaticLocalNumbers.end() ? I->second : 1; } +void ASTContext::setIsDestroyingOperatorDelete(const FunctionDecl *FD, + bool IsDestroying) { + if (!IsDestroying) { + assert(!DestroyingOperatorDeletes.contains(FD->getCanonicalDecl())); + return; + } + DestroyingOperatorDeletes.insert(FD->getCanonicalDecl()); +} + +bool ASTContext::isDestroyingOperatorDelete(const FunctionDecl *FD) const { + return DestroyingOperatorDeletes.contains(FD->getCanonicalDecl()); +} + +void ASTContext::setIsTypeAwareOperatorNewOrDelete(const FunctionDecl *FD, + bool IsTypeAware) { + if (!IsTypeAware) { + assert(!TypeAwareOperatorNewAndDeletes.contains(FD->getCanonicalDecl())); + return; + } + TypeAwareOperatorNewAndDeletes.insert(FD->getCanonicalDecl()); +} + +bool ASTContext::isTypeAwareOperatorNewOrDelete(const FunctionDecl *FD) const { + return TypeAwareOperatorNewAndDeletes.contains(FD->getCanonicalDecl()); +} + MangleNumberingContext & ASTContext::getManglingNumberContext(const DeclContext *DC) { assert(LangOpts.CPlusPlus); // We don't need mangling numbers for plain C. diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 1528fb9021ae..742ff1803133 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -4042,6 +4042,9 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { ToFunction->setDeletedAsWritten(D->isDeletedAsWritten()); ToFunction->setFriendConstraintRefersToEnclosingTemplate( D->FriendConstraintRefersToEnclosingTemplate()); + ToFunction->setIsDestroyingOperatorDelete(D->isDestroyingOperatorDelete()); + ToFunction->setIsTypeAwareOperatorNewOrDelete( + D->isTypeAwareOperatorNewOrDelete()); ToFunction->setRangeEnd(ToEndLoc); ToFunction->setDefaultLoc(ToDefaultLoc); @@ -8338,10 +8341,10 @@ ExpectedStmt ASTNodeImporter::VisitCXXNewExpr(CXXNewExpr *E) { return CXXNewExpr::Create( Importer.getToContext(), E->isGlobalNew(), ToOperatorNew, - ToOperatorDelete, E->passAlignment(), E->doesUsualArrayDeleteWantSize(), - ToPlacementArgs, ToTypeIdParens, ToArraySize, E->getInitializationStyle(), - ToInitializer, ToType, ToAllocatedTypeSourceInfo, ToSourceRange, - ToDirectInitRange); + ToOperatorDelete, E->implicitAllocationParameters(), + E->doesUsualArrayDeleteWantSize(), ToPlacementArgs, ToTypeIdParens, + ToArraySize, E->getInitializationStyle(), ToInitializer, ToType, + ToAllocatedTypeSourceInfo, ToSourceRange, ToDirectInitRange); } ExpectedStmt ASTNodeImporter::VisitCXXDeleteExpr(CXXDeleteExpr *E) { diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 197c079aaf3e..eafe735c2dce 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -3380,7 +3380,8 @@ bool Compiler::VisitCXXNewExpr(const CXXNewExpr *E) { // Always invalid. return this->emitInvalid(E); } - } else if (!OperatorNew->isReplaceableGlobalAllocationFunction()) + } else if (!OperatorNew + ->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) return this->emitInvalidNewDeleteExpr(E, E); const Descriptor *Desc; @@ -3600,7 +3601,7 @@ bool Compiler::VisitCXXDeleteExpr(const CXXDeleteExpr *E) { const FunctionDecl *OperatorDelete = E->getOperatorDelete(); - if (!OperatorDelete->isReplaceableGlobalAllocationFunction()) + if (!OperatorDelete->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) return this->emitInvalidNewDeleteExpr(E, E); // Arg must be an lvalue. @@ -4820,9 +4821,9 @@ bool Compiler::VisitCallExpr(const CallExpr *E) { const FunctionDecl *FuncDecl = E->getDirectCallee(); // Calls to replaceable operator new/operator delete. - if (FuncDecl && FuncDecl->isReplaceableGlobalAllocationFunction()) { - if (FuncDecl->getDeclName().getCXXOverloadedOperator() == OO_New || - FuncDecl->getDeclName().getCXXOverloadedOperator() == OO_Array_New) { + if (FuncDecl && + FuncDecl->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { + if (FuncDecl->getDeclName().isAnyOperatorNew()) { return VisitBuiltinCallExpr(E, Builtin::BI__builtin_operator_new); } else { assert(FuncDecl->getDeclName().getCXXOverloadedOperator() == OO_Delete); diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 4a300c02d007..768ae6e49b13 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1205,7 +1205,8 @@ bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm, if (const FunctionDecl *VirtualDelete = getVirtualOperatorDelete(AllocType); VirtualDelete && - !VirtualDelete->isReplaceableGlobalAllocationFunction()) { + !VirtualDelete + ->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_new_non_replaceable) << isa(VirtualDelete) << VirtualDelete; @@ -1723,7 +1724,9 @@ bool InvalidNewDeleteExpr(InterpState &S, CodePtr OpPC, const Expr *E) { return true; S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_new_placement) << /*C++26 feature*/ 1 << E->getSourceRange(); - } else if (!OperatorNew->isReplaceableGlobalAllocationFunction()) { + } else if ( + !OperatorNew + ->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_new_non_replaceable) << isa(OperatorNew) << OperatorNew; @@ -1741,7 +1744,8 @@ bool InvalidNewDeleteExpr(InterpState &S, CodePtr OpPC, const Expr *E) { } else { const auto *DeleteExpr = cast(E); const FunctionDecl *OperatorDelete = DeleteExpr->getOperatorDelete(); - if (!OperatorDelete->isReplaceableGlobalAllocationFunction()) { + if (!OperatorDelete + ->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_new_non_replaceable) << isa(OperatorDelete) << OperatorDelete; diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 83116ecc0f47..ad1cb01592e9 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -3082,6 +3082,7 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC, static_cast(DeductionCandidate::Normal); FunctionDeclBits.HasODRHash = false; FunctionDeclBits.FriendConstraintRefersToEnclosingTemplate = false; + if (TrailingRequiresClause) setTrailingRequiresClause(TrailingRequiresClause); } @@ -3361,17 +3362,15 @@ bool FunctionDecl::isMSVCRTEntryPoint() const { } bool FunctionDecl::isReservedGlobalPlacementOperator() const { - if (getDeclName().getNameKind() != DeclarationName::CXXOperatorName) - return false; - if (getDeclName().getCXXOverloadedOperator() != OO_New && - getDeclName().getCXXOverloadedOperator() != OO_Delete && - getDeclName().getCXXOverloadedOperator() != OO_Array_New && - getDeclName().getCXXOverloadedOperator() != OO_Array_Delete) + if (!getDeclName().isAnyOperatorNewOrDelete()) return false; if (!getDeclContext()->getRedeclContext()->isTranslationUnit()) return false; + if (isTypeAwareOperatorNewOrDelete()) + return false; + const auto *proto = getType()->castAs(); if (proto->getNumParams() != 2 || proto->isVariadic()) return false; @@ -3385,14 +3384,9 @@ bool FunctionDecl::isReservedGlobalPlacementOperator() const { return (proto->getParamType(1).getCanonicalType() == Context.VoidPtrTy); } -bool FunctionDecl::isReplaceableGlobalAllocationFunction( +bool FunctionDecl::isUsableAsGlobalAllocationFunctionInConstantEvaluation( UnsignedOrNone *AlignmentParam, bool *IsNothrow) const { - if (getDeclName().getNameKind() != DeclarationName::CXXOperatorName) - return false; - if (getDeclName().getCXXOverloadedOperator() != OO_New && - getDeclName().getCXXOverloadedOperator() != OO_Delete && - getDeclName().getCXXOverloadedOperator() != OO_Array_New && - getDeclName().getCXXOverloadedOperator() != OO_Array_Delete) + if (!getDeclName().isAnyOperatorNewOrDelete()) return false; if (isa(getDeclContext())) @@ -3402,8 +3396,31 @@ bool FunctionDecl::isReplaceableGlobalAllocationFunction( if (!getDeclContext()->getRedeclContext()->isTranslationUnit()) return false; + if (isVariadic()) + return false; + + if (isTypeAwareOperatorNewOrDelete()) { + bool IsDelete = getDeclName().isAnyOperatorDelete(); + unsigned RequiredParameterCount = + IsDelete ? FunctionDecl::RequiredTypeAwareDeleteParameterCount + : FunctionDecl::RequiredTypeAwareNewParameterCount; + if (AlignmentParam) + *AlignmentParam = + /* type identity */ 1U + /* address */ IsDelete + /* size */ 1U; + if (RequiredParameterCount == getNumParams()) + return true; + if (getNumParams() > RequiredParameterCount + 1) + return false; + if (!getParamDecl(RequiredParameterCount)->getType()->isNothrowT()) + return false; + + if (IsNothrow) + *IsNothrow = true; + return true; + } + const auto *FPT = getType()->castAs(); - if (FPT->getNumParams() == 0 || FPT->getNumParams() > 4 || FPT->isVariadic()) + if (FPT->getNumParams() == 0 || FPT->getNumParams() > 4) return false; // If this is a single-parameter function, it must be a replaceable global @@ -3423,8 +3440,7 @@ bool FunctionDecl::isReplaceableGlobalAllocationFunction( // In C++14, the next parameter can be a 'std::size_t' for sized delete. bool IsSizedDelete = false; if (Ctx.getLangOpts().SizedDeallocation && - (getDeclName().getCXXOverloadedOperator() == OO_Delete || - getDeclName().getCXXOverloadedOperator() == OO_Array_Delete) && + getDeclName().isAnyOperatorDelete() && Ctx.hasSameType(Ty, Ctx.getSizeType())) { IsSizedDelete = true; Consume(); @@ -3494,17 +3510,19 @@ bool FunctionDecl::isInlineBuiltinDeclaration() const { } bool FunctionDecl::isDestroyingOperatorDelete() const { - // C++ P0722: - // Within a class C, a single object deallocation function with signature - // (T, std::destroying_delete_t, ) - // is a destroying operator delete. - if (!isa(this) || getOverloadedOperator() != OO_Delete || - getNumParams() < 2) - return false; + return getASTContext().isDestroyingOperatorDelete(this); +} - auto *RD = getParamDecl(1)->getType()->getAsCXXRecordDecl(); - return RD && RD->isInStdNamespace() && RD->getIdentifier() && - RD->getIdentifier()->isStr("destroying_delete_t"); +void FunctionDecl::setIsDestroyingOperatorDelete(bool IsDestroyingDelete) { + getASTContext().setIsDestroyingOperatorDelete(this, IsDestroyingDelete); +} + +bool FunctionDecl::isTypeAwareOperatorNewOrDelete() const { + return getASTContext().isTypeAwareOperatorNewOrDelete(this); +} + +void FunctionDecl::setIsTypeAwareOperatorNewOrDelete(bool IsTypeAware) { + getASTContext().setIsTypeAwareOperatorNewOrDelete(this, IsTypeAware); } LanguageLinkage FunctionDecl::getLanguageLinkage() const { diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index fffc50eb0b07..d11225285c57 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -2530,13 +2530,46 @@ CXXMethodDecl *CXXMethodDecl::getDevirtualizedMethod(const Expr *Base, bool CXXMethodDecl::isUsualDeallocationFunction( SmallVectorImpl &PreventedBy) const { assert(PreventedBy.empty() && "PreventedBy is expected to be empty"); - if (getOverloadedOperator() != OO_Delete && - getOverloadedOperator() != OO_Array_Delete) + if (!getDeclName().isAnyOperatorDelete()) return false; + if (isTypeAwareOperatorNewOrDelete()) { + // A variadic type aware allocation function is not a usual deallocation + // function + if (isVariadic()) + return false; + + // Type aware deallocation functions are only usual if they only accept the + // mandatory arguments + if (getNumParams() != FunctionDecl::RequiredTypeAwareDeleteParameterCount) + return false; + + FunctionTemplateDecl *PrimaryTemplate = getPrimaryTemplate(); + if (!PrimaryTemplate) + return true; + + // A template instance is is only a usual deallocation function if it has a + // type-identity parameter, the type-identity parameter is a dependent type + // (i.e. the type-identity parameter is of type std::type_identity where + // U shall be a dependent type), and the type-identity parameter is the only + // dependent parameter, and there are no template packs in the parameter + // list. + FunctionDecl *SpecializedDecl = PrimaryTemplate->getTemplatedDecl(); + if (!SpecializedDecl->getParamDecl(0)->getType()->isDependentType()) + return false; + for (unsigned Idx = 1; Idx < getNumParams(); ++Idx) { + if (SpecializedDecl->getParamDecl(Idx)->getType()->isDependentType()) + return false; + } + return true; + } + // C++ [basic.stc.dynamic.deallocation]p2: // A template instance is never a usual deallocation function, // regardless of its signature. + // Post-P2719 adoption: + // A template instance is is only a usual deallocation function if it has a + // type-identity parameter if (getPrimaryTemplate()) return false; diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp index b12f655c4b38..169f11b61106 100644 --- a/clang/lib/AST/ExprCXX.cpp +++ b/clang/lib/AST/ExprCXX.cpp @@ -226,7 +226,8 @@ SourceLocation CXXScalarValueInitExpr::getBeginLoc() const { // CXXNewExpr CXXNewExpr::CXXNewExpr(bool IsGlobalNew, FunctionDecl *OperatorNew, - FunctionDecl *OperatorDelete, bool ShouldPassAlignment, + FunctionDecl *OperatorDelete, + const ImplicitAllocationParameters &IAP, bool UsualArrayDeleteWantsSize, ArrayRef PlacementArgs, SourceRange TypeIdParens, std::optional ArraySize, @@ -245,7 +246,9 @@ CXXNewExpr::CXXNewExpr(bool IsGlobalNew, FunctionDecl *OperatorNew, CXXNewExprBits.IsGlobalNew = IsGlobalNew; CXXNewExprBits.IsArray = ArraySize.has_value(); - CXXNewExprBits.ShouldPassAlignment = ShouldPassAlignment; + CXXNewExprBits.ShouldPassAlignment = isAlignedAllocation(IAP.PassAlignment); + CXXNewExprBits.ShouldPassTypeIdentity = + isTypeAwareAllocation(IAP.PassTypeIdentity); CXXNewExprBits.UsualArrayDeleteWantsSize = UsualArrayDeleteWantsSize; CXXNewExprBits.HasInitializer = Initializer != nullptr; CXXNewExprBits.StoredInitializationStyle = @@ -290,7 +293,7 @@ CXXNewExpr::CXXNewExpr(EmptyShell Empty, bool IsArray, CXXNewExpr *CXXNewExpr::Create( const ASTContext &Ctx, bool IsGlobalNew, FunctionDecl *OperatorNew, - FunctionDecl *OperatorDelete, bool ShouldPassAlignment, + FunctionDecl *OperatorDelete, const ImplicitAllocationParameters &IAP, bool UsualArrayDeleteWantsSize, ArrayRef PlacementArgs, SourceRange TypeIdParens, std::optional ArraySize, CXXNewInitializationStyle InitializationStyle, Expr *Initializer, @@ -304,11 +307,10 @@ CXXNewExpr *CXXNewExpr::Create( Ctx.Allocate(totalSizeToAlloc( IsArray + HasInit + NumPlacementArgs, IsParenTypeId), alignof(CXXNewExpr)); - return new (Mem) - CXXNewExpr(IsGlobalNew, OperatorNew, OperatorDelete, ShouldPassAlignment, - UsualArrayDeleteWantsSize, PlacementArgs, TypeIdParens, - ArraySize, InitializationStyle, Initializer, Ty, - AllocatedTypeInfo, Range, DirectInitRange); + return new (Mem) CXXNewExpr( + IsGlobalNew, OperatorNew, OperatorDelete, IAP, UsualArrayDeleteWantsSize, + PlacementArgs, TypeIdParens, ArraySize, InitializationStyle, Initializer, + Ty, AllocatedTypeInfo, Range, DirectInitRange); } CXXNewExpr *CXXNewExpr::CreateEmpty(const ASTContext &Ctx, bool IsArray, diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 80ece3c4ed7e..d1cc722fb794 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -8385,9 +8385,8 @@ public: FD = CorrespondingCallOpSpecialization; } else FD = LambdaCallOp; - } else if (FD->isReplaceableGlobalAllocationFunction()) { - if (FD->getDeclName().getCXXOverloadedOperator() == OO_New || - FD->getDeclName().getCXXOverloadedOperator() == OO_Array_New) { + } else if (FD->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { + if (FD->getDeclName().isAnyOperatorNew()) { LValue Ptr; if (!HandleOperatorNewCall(Info, E, Ptr)) return false; @@ -10319,7 +10318,8 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) { Info.FFDiag(E, diag::note_constexpr_new_placement) << /*Unsupported*/ 0 << E->getSourceRange(); return false; - } else if (!OperatorNew->isReplaceableGlobalAllocationFunction()) { + } else if (!OperatorNew + ->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { Info.FFDiag(E, diag::note_constexpr_new_non_replaceable) << isa(OperatorNew) << OperatorNew; return false; @@ -16524,7 +16524,8 @@ bool VoidExprEvaluator::VisitCXXDeleteExpr(const CXXDeleteExpr *E) { return false; FunctionDecl *OperatorDelete = E->getOperatorDelete(); - if (!OperatorDelete->isReplaceableGlobalAllocationFunction()) { + if (!OperatorDelete + ->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { Info.FFDiag(E, diag::note_constexpr_new_non_replaceable) << isa(OperatorDelete) << OperatorDelete; return false; @@ -16568,7 +16569,8 @@ bool VoidExprEvaluator::VisitCXXDeleteExpr(const CXXDeleteExpr *E) { if (!E->isArrayForm() && !E->isGlobalDelete()) { const FunctionDecl *VirtualDelete = getVirtualOperatorDelete(AllocType); if (VirtualDelete && - !VirtualDelete->isReplaceableGlobalAllocationFunction()) { + !VirtualDelete + ->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { Info.FFDiag(E, diag::note_constexpr_new_non_replaceable) << isa(VirtualDelete) << VirtualDelete; return false; diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp index 5c11c0bceade..a0d21a4de469 100644 --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -1382,9 +1382,10 @@ RValue CodeGenFunction::EmitBuiltinNewDeleteCall(const FunctionProtoType *Type, namespace { /// The parameters to pass to a usual operator delete. struct UsualDeleteParams { + TypeAwareAllocationMode TypeAwareDelete = TypeAwareAllocationMode::No; bool DestroyingDelete = false; bool Size = false; - bool Alignment = false; + AlignedAllocationMode Alignment = AlignedAllocationMode::No; }; } @@ -1394,11 +1395,20 @@ static UsualDeleteParams getUsualDeleteParams(const FunctionDecl *FD) { const FunctionProtoType *FPT = FD->getType()->castAs(); auto AI = FPT->param_type_begin(), AE = FPT->param_type_end(); - // The first argument is always a void*. + if (FD->isTypeAwareOperatorNewOrDelete()) { + Params.TypeAwareDelete = TypeAwareAllocationMode::Yes; + assert(AI != AE); + ++AI; + } + + // The first argument after the type-identity parameter (if any) is + // always a void* (or C* for a destroying operator delete for class + // type C). ++AI; // The next parameter may be a std::destroying_delete_t. if (FD->isDestroyingOperatorDelete()) { + assert(!isTypeAwareAllocation(Params.TypeAwareDelete)); Params.DestroyingDelete = true; assert(AI != AE); ++AI; @@ -1408,12 +1418,14 @@ static UsualDeleteParams getUsualDeleteParams(const FunctionDecl *FD) { if (AI != AE && (*AI)->isIntegerType()) { Params.Size = true; ++AI; - } + } else + assert(!isTypeAwareAllocation(Params.TypeAwareDelete)); if (AI != AE && (*AI)->isAlignValT()) { - Params.Alignment = true; + Params.Alignment = AlignedAllocationMode::Yes; ++AI; - } + } else + assert(!isTypeAwareAllocation(Params.TypeAwareDelete)); assert(AI == AE && "unexpected usual deallocation function parameter"); return Params; @@ -1434,10 +1446,13 @@ namespace { QualType ArgType; }; - unsigned NumPlacementArgs : 31; - LLVM_PREFERRED_TYPE(bool) + unsigned NumPlacementArgs : 30; + LLVM_PREFERRED_TYPE(AlignedAllocationMode) unsigned PassAlignmentToPlacementDelete : 1; + LLVM_PREFERRED_TYPE(TypeAwareAllocationMode) + unsigned PassTypeToPlacementDelete : 1; const FunctionDecl *OperatorDelete; + RValueTy TypeIdentity; ValueTy Ptr; ValueTy AllocSize; CharUnits AllocAlign; @@ -1452,13 +1467,15 @@ namespace { } CallDeleteDuringNew(size_t NumPlacementArgs, - const FunctionDecl *OperatorDelete, ValueTy Ptr, - ValueTy AllocSize, bool PassAlignmentToPlacementDelete, + const FunctionDecl *OperatorDelete, + RValueTy TypeIdentity, ValueTy Ptr, ValueTy AllocSize, + const ImplicitAllocationParameters &IAP, CharUnits AllocAlign) - : NumPlacementArgs(NumPlacementArgs), - PassAlignmentToPlacementDelete(PassAlignmentToPlacementDelete), - OperatorDelete(OperatorDelete), Ptr(Ptr), AllocSize(AllocSize), - AllocAlign(AllocAlign) {} + : NumPlacementArgs(NumPlacementArgs), + PassAlignmentToPlacementDelete( + isAlignedAllocation(IAP.PassAlignment)), + OperatorDelete(OperatorDelete), TypeIdentity(TypeIdentity), Ptr(Ptr), + AllocSize(AllocSize), AllocAlign(AllocAlign) {} void setPlacementArg(unsigned I, RValueTy Arg, QualType Type) { assert(I < NumPlacementArgs && "index out of range"); @@ -1468,17 +1485,28 @@ namespace { void Emit(CodeGenFunction &CGF, Flags flags) override { const auto *FPT = OperatorDelete->getType()->castAs(); CallArgList DeleteArgs; - - // 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)); + unsigned FirstNonTypeArg = 0; + TypeAwareAllocationMode TypeAwareDeallocation = + TypeAwareAllocationMode::No; + if (OperatorDelete->isTypeAwareOperatorNewOrDelete()) { + TypeAwareDeallocation = TypeAwareAllocationMode::Yes; + QualType SpecializedTypeIdentity = FPT->getParamType(0); + ++FirstNonTypeArg; + DeleteArgs.add(Traits::get(CGF, TypeIdentity), SpecializedTypeIdentity); + } + // The first argument after type-identity parameter (if any) is always + // a void* (or C* for a destroying operator delete for class type C). + DeleteArgs.add(Traits::get(CGF, Ptr), FPT->getParamType(FirstNonTypeArg)); // Figure out what other parameters we should be implicitly passing. 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. - Params.Alignment = PassAlignmentToPlacementDelete; + Params.Alignment = + alignedAllocationModeFromBool(PassAlignmentToPlacementDelete); + Params.TypeAwareDelete = TypeAwareDeallocation; + Params.Size = isTypeAwareAllocation(Params.TypeAwareDelete); } else { // For a non-placement new-expression, 'operator delete' can take a // size and/or an alignment if it has the right parameters. @@ -1497,7 +1525,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 (Params.Alignment) + if (isAlignedAllocation(Params.Alignment)) DeleteArgs.add(RValue::get(llvm::ConstantInt::get( CGF.SizeTy, AllocAlign.getQuantity())), CGF.getContext().getSizeType()); @@ -1516,13 +1544,11 @@ namespace { /// Enter a cleanup to call 'operator delete' if the initializer in a /// new-expression throws. -static void EnterNewDeleteCleanup(CodeGenFunction &CGF, - const CXXNewExpr *E, - Address NewPtr, - llvm::Value *AllocSize, - CharUnits AllocAlign, +static void EnterNewDeleteCleanup(CodeGenFunction &CGF, const CXXNewExpr *E, + RValue TypeIdentity, Address NewPtr, + llvm::Value *AllocSize, CharUnits AllocAlign, const CallArgList &NewArgs) { - unsigned NumNonPlacementArgs = E->passAlignment() ? 2 : 1; + unsigned NumNonPlacementArgs = E->getNumImplicitArgs(); // If we're not inside a conditional branch, then the cleanup will // dominate and we can do the easier (and more efficient) thing. @@ -1538,7 +1564,8 @@ static void EnterNewDeleteCleanup(CodeGenFunction &CGF, DirectCleanup *Cleanup = CGF.EHStack.pushCleanupWithExtra( EHCleanup, E->getNumPlacementArgs(), E->getOperatorDelete(), - NewPtr.emitRawPointer(CGF), AllocSize, E->passAlignment(), AllocAlign); + TypeIdentity, NewPtr.emitRawPointer(CGF), AllocSize, + E->implicitAllocationParameters(), AllocAlign); for (unsigned I = 0, N = E->getNumPlacementArgs(); I != N; ++I) { auto &Arg = NewArgs[I + NumNonPlacementArgs]; Cleanup->setPlacementArg(I, Arg.getRValue(CGF), Arg.Ty); @@ -1552,7 +1579,8 @@ static void EnterNewDeleteCleanup(CodeGenFunction &CGF, DominatingValue::save(CGF, RValue::get(NewPtr, CGF)); DominatingValue::saved_type SavedAllocSize = DominatingValue::save(CGF, RValue::get(AllocSize)); - + DominatingValue::saved_type SavedTypeIdentity = + DominatingValue::save(CGF, TypeIdentity); struct ConditionalCleanupTraits { typedef DominatingValue::saved_type ValueTy; typedef DominatingValue::saved_type RValueTy; @@ -1562,14 +1590,11 @@ static void EnterNewDeleteCleanup(CodeGenFunction &CGF, }; typedef CallDeleteDuringNew ConditionalCleanup; - ConditionalCleanup *Cleanup = CGF.EHStack - .pushCleanupWithExtra(EHCleanup, - E->getNumPlacementArgs(), - E->getOperatorDelete(), - SavedNewPtr, - SavedAllocSize, - E->passAlignment(), - AllocAlign); + ConditionalCleanup *Cleanup = + CGF.EHStack.pushCleanupWithExtra( + EHCleanup, E->getNumPlacementArgs(), E->getOperatorDelete(), + SavedTypeIdentity, SavedNewPtr, SavedAllocSize, + E->implicitAllocationParameters(), AllocAlign); for (unsigned I = 0, N = E->getNumPlacementArgs(); I != N; ++I) { auto &Arg = NewArgs[I + NumNonPlacementArgs]; Cleanup->setPlacementArg( @@ -1589,6 +1614,7 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) { // If there is a brace-initializer or C++20 parenthesized initializer, cannot // allocate fewer elements than inits. unsigned minElements = 0; + unsigned IndexOfAlignArg = 1; if (E->isArray() && E->hasInitializer()) { const Expr *Init = E->getInitializer(); const InitListExpr *ILE = dyn_cast(Init); @@ -1615,6 +1641,7 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) { // operator, just "inline" it directly. Address allocation = Address::invalid(); CallArgList allocatorArgs; + RValue TypeIdentityArg; if (allocator->isReservedGlobalPlacementOperator()) { assert(E->getNumPlacementArgs() == 1); const Expr *arg = *E->placement_arguments().begin(); @@ -1639,8 +1666,17 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) { } else { const FunctionProtoType *allocatorType = allocator->getType()->castAs(); + ImplicitAllocationParameters IAP = E->implicitAllocationParameters(); unsigned ParamsToSkip = 0; - + if (isTypeAwareAllocation(IAP.PassTypeIdentity)) { + QualType SpecializedTypeIdentity = allocatorType->getParamType(0); + CXXScalarValueInitExpr TypeIdentityParam(SpecializedTypeIdentity, nullptr, + SourceLocation()); + TypeIdentityArg = EmitAnyExprToTemp(&TypeIdentityParam); + allocatorArgs.add(TypeIdentityArg, SpecializedTypeIdentity); + ++ParamsToSkip; + ++IndexOfAlignArg; + } // The allocation size is the first argument. QualType sizeType = getContext().getSizeType(); allocatorArgs.add(RValue::get(allocSize), sizeType); @@ -1652,10 +1688,10 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) { } // The allocation alignment may be passed as the second argument. - if (E->passAlignment()) { + if (isAlignedAllocation(IAP.PassAlignment)) { QualType AlignValT = sizeType; - if (allocatorType->getNumParams() > 1) { - AlignValT = allocatorType->getParamType(1); + if (allocatorType->getNumParams() > IndexOfAlignArg) { + AlignValT = allocatorType->getParamType(IndexOfAlignArg); assert(getContext().hasSameUnqualifiedType( AlignValT->castAs()->getDecl()->getIntegerType(), sizeType) && @@ -1732,8 +1768,8 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) { llvm::Instruction *cleanupDominator = nullptr; if (E->getOperatorDelete() && !E->getOperatorDelete()->isReservedGlobalPlacementOperator()) { - EnterNewDeleteCleanup(*this, E, allocation, allocSize, allocAlign, - allocatorArgs); + EnterNewDeleteCleanup(*this, E, TypeIdentityArg, allocation, allocSize, + allocAlign, allocatorArgs); operatorDeleteCleanup = EHStack.stable_begin(); cleanupDominator = Builder.CreateUnreachable(); } @@ -1811,21 +1847,29 @@ void CodeGenFunction::EmitDeleteCall(const FunctionDecl *DeleteFD, auto Params = getUsualDeleteParams(DeleteFD); auto ParamTypeIt = DeleteFTy->param_type_begin(); + std::optional TagAlloca; + auto EmitTag = [&](QualType TagType, const char *TagName) { + assert(!TagAlloca); + llvm::Type *Ty = getTypes().ConvertType(TagType); + CharUnits Align = CGM.getNaturalTypeAlignment(TagType); + llvm::AllocaInst *TagAllocation = CreateTempAlloca(Ty, TagName); + TagAllocation->setAlignment(Align.getAsAlign()); + DeleteArgs.add(RValue::getAggregate(Address(TagAllocation, Ty, Align)), + TagType); + TagAlloca = TagAllocation; + }; + + // Pass std::type_identity tag if present + if (isTypeAwareAllocation(Params.TypeAwareDelete)) + EmitTag(*ParamTypeIt++, "typeaware.delete.tag"); + // Pass the pointer itself. QualType ArgTy = *ParamTypeIt++; DeleteArgs.add(RValue::get(DeletePtr), ArgTy); // Pass the std::destroying_delete tag if present. - llvm::AllocaInst *DestroyingDeleteTag = nullptr; - if (Params.DestroyingDelete) { - QualType DDTag = *ParamTypeIt++; - llvm::Type *Ty = getTypes().ConvertType(DDTag); - CharUnits Align = CGM.getNaturalTypeAlignment(DDTag); - DestroyingDeleteTag = CreateTempAlloca(Ty, "destroying.delete.tag"); - DestroyingDeleteTag->setAlignment(Align.getAsAlign()); - DeleteArgs.add( - RValue::getAggregate(Address(DestroyingDeleteTag, Ty, Align)), DDTag); - } + if (Params.DestroyingDelete) + EmitTag(*ParamTypeIt++, "destroying.delete.tag"); // Pass the size if the delete function has a size_t parameter. if (Params.Size) { @@ -1847,7 +1891,7 @@ void CodeGenFunction::EmitDeleteCall(const FunctionDecl *DeleteFD, } // Pass the alignment if the delete function has an align_val_t parameter. - if (Params.Alignment) { + if (isAlignedAllocation(Params.Alignment)) { QualType AlignValType = *ParamTypeIt++; CharUnits DeleteTypeAlign = getContext().toCharUnitsFromBits(getContext().getTypeAlignIfKnown( @@ -1863,12 +1907,11 @@ void CodeGenFunction::EmitDeleteCall(const FunctionDecl *DeleteFD, // Emit the call to delete. EmitNewDeleteCall(*this, DeleteFD, DeleteFTy, DeleteArgs); - // If call argument lowering didn't use the destroying_delete_t alloca, - // remove it again. - if (DestroyingDeleteTag && DestroyingDeleteTag->use_empty()) - DestroyingDeleteTag->eraseFromParent(); + // If call argument lowering didn't use a generated tag argument alloca we + // remove them + if (TagAlloca && (*TagAlloca)->use_empty()) + (*TagAlloca)->eraseFromParent(); } - namespace { /// Calls the given 'operator delete' on a single object. struct CallObjectDelete final : EHScopeStack::Cleanup { diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 90d0f5c8c9d1..659af4247899 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -776,6 +776,9 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, if (LangOpts.Char8) Builder.defineMacro("__cpp_char8_t", "202207L"); Builder.defineMacro("__cpp_impl_destroying_delete", "201806L"); + + // TODO: Final number? + Builder.defineMacro("__cpp_type_aware_allocators", "202500L"); } /// InitializeOpenCLFeatureTestMacros - Define OpenCL macros based on target diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 32d7744be922..d2da9cd1201c 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -258,6 +258,7 @@ Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer, VisContext(nullptr), PragmaAttributeCurrentTargetDecl(nullptr), StdCoroutineTraitsCache(nullptr), IdResolver(pp), OriginalLexicalContext(nullptr), StdInitializerList(nullptr), + StdTypeIdentity(nullptr), FullyCheckedComparisonCategories( static_cast(ComparisonCategoryType::Last) + 1), StdSourceLocationImplDecl(nullptr), CXXTypeInfoDecl(nullptr), diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp index 6f873cafa98f..d631ad11fc9f 100644 --- a/clang/lib/Sema/SemaCoroutine.cpp +++ b/clang/lib/Sema/SemaCoroutine.cpp @@ -1097,12 +1097,39 @@ static TypeSourceInfo *getTypeSourceInfoForStdAlignValT(Sema &S, return S.Context.getTrivialTypeSourceInfo(StdAlignValDecl); } +// When searching for custom allocators on the PromiseType we want to +// warn that we will ignore type aware allocators. +static bool DiagnoseTypeAwareAllocators(Sema &S, SourceLocation Loc, + unsigned DiagnosticID, + DeclarationName Name, + QualType PromiseType) { + assert(PromiseType->isRecordType()); + + LookupResult R(S, Name, Loc, Sema::LookupOrdinaryName); + S.LookupQualifiedName(R, PromiseType->getAsCXXRecordDecl()); + bool HaveIssuedWarning = false; + for (auto Decl : R) { + if (!Decl->getAsFunction()->isTypeAwareOperatorNewOrDelete()) + continue; + if (!HaveIssuedWarning) { + S.Diag(Loc, DiagnosticID) << Name; + HaveIssuedWarning = true; + } + S.Diag(Decl->getLocation(), diag::note_type_aware_operator_declared) + << /* isTypeAware=*/1 << Decl << Decl->getDeclContext(); + } + R.suppressDiagnostics(); + return HaveIssuedWarning; +} + // Find an appropriate delete for the promise. static bool findDeleteForPromise(Sema &S, SourceLocation Loc, QualType PromiseType, FunctionDecl *&OperatorDelete) { DeclarationName DeleteName = S.Context.DeclarationNames.getCXXOperatorName(OO_Delete); - + DiagnoseTypeAwareAllocators(S, Loc, + diag::warn_coroutine_type_aware_allocator_ignored, + DeleteName, PromiseType); auto *PointeeRD = PromiseType->getAsCXXRecordDecl(); assert(PointeeRD && "PromiseType must be a CxxRecordDecl type"); @@ -1112,9 +1139,10 @@ static bool findDeleteForPromise(Sema &S, SourceLocation Loc, QualType PromiseTy // The deallocation function's name is looked up by searching for it in the // scope of the promise type. If nothing is found, a search is performed in // the global scope. + ImplicitDeallocationParameters IDP = { + alignedAllocationModeFromBool(Overaligned), SizedDeallocationMode::Yes}; if (S.FindDeallocationFunction(Loc, PointeeRD, DeleteName, OperatorDelete, - /*Diagnose*/ true, /*WantSize*/ true, - /*WantAligned*/ Overaligned)) + IDP, /*Diagnose=*/true)) return false; // [dcl.fct.def.coroutine]p12 @@ -1125,18 +1153,18 @@ static bool findDeleteForPromise(Sema &S, SourceLocation Loc, QualType PromiseTy // shall be the function with one parameter. if (!OperatorDelete) { // Look for a global declaration. - // Coroutines can always provide their required size. - const bool CanProvideSize = true; // Sema::FindUsualDeallocationFunction will try to find the one with two // parameters first. It will return the deallocation function with one // parameter if failed. - OperatorDelete = S.FindUsualDeallocationFunction(Loc, CanProvideSize, - Overaligned, DeleteName); + // Coroutines can always provide their required size. + IDP.PassSize = SizedDeallocationMode::Yes; + OperatorDelete = S.FindUsualDeallocationFunction(Loc, IDP, DeleteName); if (!OperatorDelete) return false; } + assert(!OperatorDelete->isTypeAwareOperatorNewOrDelete()); S.MarkFunctionReferenced(Loc, OperatorDelete); return true; } @@ -1411,10 +1439,10 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() { FunctionDecl *OperatorNew = nullptr; SmallVector PlacementArgs; + DeclarationName NewName = + S.getASTContext().DeclarationNames.getCXXOperatorName(OO_New); - const bool PromiseContainsNew = [this, &PromiseType]() -> bool { - DeclarationName NewName = - S.getASTContext().DeclarationNames.getCXXOperatorName(OO_New); + const bool PromiseContainsNew = [this, &PromiseType, NewName]() -> bool { LookupResult R(S, NewName, Loc, Sema::LookupOrdinaryName); if (PromiseType->isRecordType()) @@ -1425,7 +1453,8 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() { // Helper function to indicate whether the last lookup found the aligned // allocation function. - bool PassAlignment = S.getLangOpts().CoroAlignedAllocation; + ImplicitAllocationParameters IAP( + alignedAllocationModeFromBool(S.getLangOpts().CoroAlignedAllocation)); auto LookupAllocationFunction = [&](Sema::AllocationFunctionScope NewScope = Sema::AFS_Both, bool WithoutPlacementArgs = false, @@ -1439,14 +1468,19 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() { if (NewScope == Sema::AFS_Both) NewScope = PromiseContainsNew ? Sema::AFS_Class : Sema::AFS_Global; - PassAlignment = !ForceNonAligned && S.getLangOpts().CoroAlignedAllocation; + bool ShouldUseAlignedAlloc = + !ForceNonAligned && S.getLangOpts().CoroAlignedAllocation; + IAP = ImplicitAllocationParameters( + alignedAllocationModeFromBool(ShouldUseAlignedAlloc)); + FunctionDecl *UnusedResult = nullptr; S.FindAllocationFunctions(Loc, SourceRange(), NewScope, - /*DeleteScope*/ Sema::AFS_Both, PromiseType, - /*isArray*/ false, PassAlignment, + /*DeleteScope=*/Sema::AFS_Both, PromiseType, + /*isArray=*/false, IAP, WithoutPlacementArgs ? MultiExprArg{} : PlacementArgs, - OperatorNew, UnusedResult, /*Diagnose*/ false); + OperatorNew, UnusedResult, /*Diagnose=*/false); + assert(!OperatorNew || !OperatorNew->isTypeAwareOperatorNewOrDelete()); }; // We don't expect to call to global operator new with (size, p0, …, pn). @@ -1470,8 +1504,8 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() { // by passing the amount of space requested as an argument of type // std::size_t as the first argument, and the requested alignment as // an argument of type std:align_val_t as the second argument. - if (!OperatorNew || - (S.getLangOpts().CoroAlignedAllocation && !PassAlignment)) + if (!OperatorNew || (S.getLangOpts().CoroAlignedAllocation && + !isAlignedAllocation(IAP.PassAlignment))) LookupAllocationFunction(/*NewScope*/ Sema::AFS_Class, /*WithoutPlacementArgs*/ true); } @@ -1496,7 +1530,7 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() { // Helper variable to emit warnings. bool FoundNonAlignedInPromise = false; if (PromiseContainsNew && S.getLangOpts().CoroAlignedAllocation) - if (!OperatorNew || !PassAlignment) { + if (!OperatorNew || !isAlignedAllocation(IAP.PassAlignment)) { FoundNonAlignedInPromise = OperatorNew; LookupAllocationFunction(/*NewScope*/ Sema::AFS_Class, @@ -1533,14 +1567,22 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() { } if (!OperatorNew) { - if (PromiseContainsNew) + if (PromiseContainsNew) { S.Diag(Loc, diag::err_coroutine_unusable_new) << PromiseType << &FD; - else if (RequiresNoThrowAlloc) + DiagnoseTypeAwareAllocators( + S, Loc, diag::note_coroutine_unusable_type_aware_allocators, NewName, + PromiseType); + } else if (RequiresNoThrowAlloc) S.Diag(Loc, diag::err_coroutine_unfound_nothrow_new) << &FD << S.getLangOpts().CoroAlignedAllocation; return false; } + assert(!OperatorNew->isTypeAwareOperatorNewOrDelete()); + + DiagnoseTypeAwareAllocators(S, Loc, + diag::warn_coroutine_type_aware_allocator_ignored, + NewName, PromiseType); if (RequiresNoThrowAlloc) { const auto *FT = OperatorNew->getType()->castAs(); @@ -1562,6 +1604,8 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() { return false; } + assert(!OperatorDelete->isTypeAwareOperatorNewOrDelete()); + Expr *FramePtr = S.BuildBuiltinCallExpr(Loc, Builtin::BI__builtin_coro_frame, {}); @@ -1591,7 +1635,8 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() { return false; SmallVector NewArgs(1, FrameSize); - if (S.getLangOpts().CoroAlignedAllocation && PassAlignment) + if (S.getLangOpts().CoroAlignedAllocation && + isAlignedAllocation(IAP.PassAlignment)) NewArgs.push_back(FrameAlignment); if (OperatorNew->getNumParams() > NewArgs.size()) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 08bb74190a76..e9805c345b6a 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -10169,10 +10169,7 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC, // deallocation function shall not be declared with the consteval // specifier. if (ConstexprKind == ConstexprSpecKind::Consteval && - (NewFD->getOverloadedOperator() == OO_New || - NewFD->getOverloadedOperator() == OO_Array_New || - NewFD->getOverloadedOperator() == OO_Delete || - NewFD->getOverloadedOperator() == OO_Array_Delete)) { + NewFD->getDeclName().isAnyOperatorNewOrDelete()) { Diag(D.getDeclSpec().getConstexprSpecLoc(), diag::err_invalid_consteval_decl_kind) << NewFD; @@ -10276,9 +10273,8 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC, // A deallocation function with no exception-specification is treated // as if it were specified with noexcept(true). const FunctionProtoType *FPT = R->getAs(); - if ((Name.getCXXOverloadedOperator() == OO_Delete || - Name.getCXXOverloadedOperator() == OO_Array_Delete) && - getLangOpts().CPlusPlus11 && FPT && !FPT->hasExceptionSpec()) + if (Name.isAnyOperatorDelete() && getLangOpts().CPlusPlus11 && FPT && + !FPT->hasExceptionSpec()) NewFD->setType(Context.getFunctionType( FPT->getReturnType(), FPT->getParamTypes(), FPT->getExtProtoInfo().withExceptionSpec(EST_BasicNoexcept))); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 721f61ca409a..e4f09ca60d7d 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -46,6 +46,7 @@ #include "clang/Sema/SemaObjC.h" #include "clang/Sema/SemaOpenMP.h" #include "clang/Sema/Template.h" +#include "clang/Sema/TemplateDeduction.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLForwardCompat.h" @@ -7322,6 +7323,48 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) { else if (Record->hasAttr()) checkCUDADeviceBuiltinTextureClassTemplate(*this, Record); } + + llvm::SmallDenseMap, 4> + TypeAwareDecls{{OO_New, {}}, + {OO_Array_New, {}}, + {OO_Delete, {}}, + {OO_Array_New, {}}}; + for (auto *D : Record->decls()) { + const FunctionDecl *FnDecl = D->getAsFunction(); + if (!FnDecl || !FnDecl->isTypeAwareOperatorNewOrDelete()) + continue; + assert(FnDecl->getDeclName().isAnyOperatorNewOrDelete()); + TypeAwareDecls[FnDecl->getOverloadedOperator()].push_back(FnDecl); + } + auto CheckMismatchedTypeAwareAllocators = + [this, &TypeAwareDecls, Record](OverloadedOperatorKind NewKind, + OverloadedOperatorKind DeleteKind) { + auto &NewDecls = TypeAwareDecls[NewKind]; + auto &DeleteDecls = TypeAwareDecls[DeleteKind]; + if (NewDecls.empty() == DeleteDecls.empty()) + return; + DeclarationName FoundOperator = + Context.DeclarationNames.getCXXOperatorName( + NewDecls.empty() ? DeleteKind : NewKind); + DeclarationName MissingOperator = + Context.DeclarationNames.getCXXOperatorName( + NewDecls.empty() ? NewKind : DeleteKind); + Diag(Record->getLocation(), + diag::err_type_aware_allocator_missing_matching_operator) + << FoundOperator << Context.getRecordType(Record) + << MissingOperator; + for (auto MD : NewDecls) + Diag(MD->getLocation(), + diag::note_unmatched_type_aware_allocator_declared) + << MD; + for (auto MD : DeleteDecls) + Diag(MD->getLocation(), + diag::note_unmatched_type_aware_allocator_declared) + << MD; + }; + CheckMismatchedTypeAwareAllocators(OO_New, OO_Delete); + CheckMismatchedTypeAwareAllocators(OO_Array_New, OO_Array_Delete); } /// Look up the special member function that would be called by a special @@ -9849,10 +9892,15 @@ bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD, // results in an ambiguity or in a function that is deleted or inaccessible if (CSM == CXXSpecialMemberKind::Destructor && MD->isVirtual()) { FunctionDecl *OperatorDelete = nullptr; + QualType DeallocType = Context.getRecordType(RD); DeclarationName Name = Context.DeclarationNames.getCXXOperatorName(OO_Delete); + ImplicitDeallocationParameters IDP = { + DeallocType, ShouldUseTypeAwareOperatorNewOrDelete(), + AlignedAllocationMode::No, SizedDeallocationMode::No}; if (FindDeallocationFunction(MD->getLocation(), MD->getParent(), Name, - OperatorDelete, /*Diagnose*/false)) { + OperatorDelete, IDP, + /*Diagnose=*/false)) { if (Diagnose) Diag(RD->getLocation(), diag::note_deleted_dtor_no_operator_delete); return true; @@ -11021,14 +11069,18 @@ bool Sema::CheckDestructor(CXXDestructorDecl *Destructor) { // 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(); + unsigned AddressParamIndex = 0; + if (OperatorDelete->isTypeAwareOperatorNewOrDelete()) + ++AddressParamIndex; + QualType ParamType = + OperatorDelete->getParamDecl(AddressParamIndex)->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()); + ExprResult This = ActOnCXXThis( + OperatorDelete->getParamDecl(AddressParamIndex)->getLocation()); assert(!This.isInvalid() && "couldn't form 'this' expr in dtor?"); This = PerformImplicitConversion(This.get(), ParamType, AssignmentAction::Passing); @@ -11902,6 +11954,7 @@ NamespaceDecl *Sema::getStdNamespace() const { return cast_or_null( StdNamespace.get(Context.getExternalSource())); } + namespace { enum UnsupportedSTLSelect { @@ -12058,25 +12111,40 @@ NamespaceDecl *Sema::getOrCreateStdNamespace() { return getStdNamespace(); } -bool Sema::isStdInitializerList(QualType Ty, QualType *Element) { - assert(getLangOpts().CPlusPlus && - "Looking for std::initializer_list outside of C++."); - +static bool isStdClassTemplate(Sema &S, QualType SugaredType, QualType *TypeArg, + const char *ClassName, + ClassTemplateDecl **CachedDecl, + const Decl **MalformedDecl) { // We're looking for implicit instantiations of - // template class std::initializer_list. + // template class std::{ClassName}. - if (!StdNamespace) // If we haven't seen namespace std yet, this can't be it. + if (!S.StdNamespace) // If we haven't seen namespace std yet, this can't be + // it. return false; + auto ReportMatchingNameAsMalformed = [&](NamedDecl *D) { + if (!MalformedDecl) + return; + if (!D) + D = SugaredType->getAsTagDecl(); + if (!D || !D->isInStdNamespace()) + return; + IdentifierInfo *II = D->getDeclName().getAsIdentifierInfo(); + if (II && II == &S.PP.getIdentifierTable().get(ClassName)) + *MalformedDecl = D; + }; + ClassTemplateDecl *Template = nullptr; const TemplateArgument *Arguments = nullptr; + QualType Ty = S.Context.getCanonicalType(SugaredType); if (const RecordType *RT = Ty->getAs()) { - ClassTemplateSpecializationDecl *Specialization = dyn_cast(RT->getDecl()); - if (!Specialization) + if (!Specialization) { + ReportMatchingNameAsMalformed(RT->getDecl()); return false; + } Template = Specialization->getSpecializedTemplate(); Arguments = Specialization->getTemplateArgs().data(); @@ -12092,91 +12160,146 @@ bool Sema::isStdInitializerList(QualType Ty, QualType *Element) { Arguments = TST->template_arguments().begin(); } } - if (!Template) + if (!Template) { + ReportMatchingNameAsMalformed(Ty->getAsTagDecl()); return false; - - if (!StdInitializerList) { - // Haven't recognized std::initializer_list yet, maybe this is it. - CXXRecordDecl *TemplateClass = Template->getTemplatedDecl(); - if (TemplateClass->getIdentifier() != - &PP.getIdentifierTable().get("initializer_list") || - !getStdNamespace()->InEnclosingNamespaceSetOf( - TemplateClass->getNonTransparentDeclContext())) - return false; - // This is a template called std::initializer_list, but is it the right - // template? - TemplateParameterList *Params = Template->getTemplateParameters(); - if (Params->getMinRequiredArguments() != 1) - return false; - if (!isa(Params->getParam(0))) - return false; - - // It's the right template. - StdInitializerList = Template; } - if (Template->getCanonicalDecl() != StdInitializerList->getCanonicalDecl()) + if (!*CachedDecl) { + // Haven't recognized std::{ClassName} yet, maybe this is it. + // FIXME: It seems we should just reuse LookupStdClassTemplate but the + // semantics of this are slightly different, most notably the existing + // "lookup" semantics explicitly diagnose an invalid definition as an + // error. + CXXRecordDecl *TemplateClass = Template->getTemplatedDecl(); + if (TemplateClass->getIdentifier() != + &S.PP.getIdentifierTable().get(ClassName) || + !S.getStdNamespace()->InEnclosingNamespaceSetOf( + TemplateClass->getNonTransparentDeclContext())) + return false; + // This is a template called std::{ClassName}, but is it the right + // template? + TemplateParameterList *Params = Template->getTemplateParameters(); + if (Params->getMinRequiredArguments() != 1 || + !isa(Params->getParam(0))) { + if (MalformedDecl) + *MalformedDecl = TemplateClass; + return false; + } + + // It's the right template. + *CachedDecl = Template; + } + + if (Template->getCanonicalDecl() != (*CachedDecl)->getCanonicalDecl()) return false; - // This is an instance of std::initializer_list. Find the argument type. - if (Element) - *Element = Arguments[0].getAsType(); + // This is an instance of std::{ClassName}. Find the argument type. + if (TypeArg) + *TypeArg = Arguments[0].getAsType(); + return true; } -static ClassTemplateDecl *LookupStdInitializerList(Sema &S, SourceLocation Loc){ - NamespaceDecl *Std = S.getStdNamespace(); - if (!Std) { - S.Diag(Loc, diag::err_implied_std_initializer_list_not_found); - return nullptr; - } +bool Sema::isStdInitializerList(QualType Ty, QualType *Element) { + assert(getLangOpts().CPlusPlus && + "Looking for std::initializer_list outside of C++."); - LookupResult Result(S, &S.PP.getIdentifierTable().get("initializer_list"), - Loc, Sema::LookupOrdinaryName); - if (!S.LookupQualifiedName(Result, Std)) { - S.Diag(Loc, diag::err_implied_std_initializer_list_not_found); + // We're looking for implicit instantiations of + // template class std::initializer_list. + + return isStdClassTemplate(*this, Ty, Element, "initializer_list", + &StdInitializerList, /*MalformedDecl=*/nullptr); +} + +bool Sema::isStdTypeIdentity(QualType Ty, QualType *Element, + const Decl **MalformedDecl) { + assert(getLangOpts().CPlusPlus && + "Looking for std::type_identity outside of C++."); + + // We're looking for implicit instantiations of + // template struct std::type_identity. + + return isStdClassTemplate(*this, Ty, Element, "type_identity", + &StdTypeIdentity, MalformedDecl); +} + +static ClassTemplateDecl *LookupStdClassTemplate(Sema &S, SourceLocation Loc, + const char *ClassName, + bool *WasMalformed) { + if (!S.StdNamespace) return nullptr; - } + + LookupResult Result(S, &S.PP.getIdentifierTable().get(ClassName), Loc, + Sema::LookupOrdinaryName); + if (!S.LookupQualifiedName(Result, S.getStdNamespace())) + return nullptr; + ClassTemplateDecl *Template = Result.getAsSingle(); if (!Template) { Result.suppressDiagnostics(); // We found something weird. Complain about the first thing we found. NamedDecl *Found = *Result.begin(); - S.Diag(Found->getLocation(), diag::err_malformed_std_initializer_list); + S.Diag(Found->getLocation(), diag::err_malformed_std_class_template) + << ClassName; + if (WasMalformed) + *WasMalformed = true; return nullptr; } - // We found some template called std::initializer_list. Now verify that it's + // We found some template with the correct name. Now verify that it's // correct. TemplateParameterList *Params = Template->getTemplateParameters(); if (Params->getMinRequiredArguments() != 1 || !isa(Params->getParam(0))) { - S.Diag(Template->getLocation(), diag::err_malformed_std_initializer_list); + S.Diag(Template->getLocation(), diag::err_malformed_std_class_template) + << ClassName; + if (WasMalformed) + *WasMalformed = true; return nullptr; } return Template; } -QualType Sema::BuildStdInitializerList(QualType Element, SourceLocation Loc) { - if (!StdInitializerList) { - StdInitializerList = LookupStdInitializerList(*this, Loc); - if (!StdInitializerList) - return QualType(); - } - +static QualType BuildStdClassTemplate(Sema &S, ClassTemplateDecl *CTD, + QualType TypeParam, SourceLocation Loc) { + assert(S.getStdNamespace()); TemplateArgumentListInfo Args(Loc, Loc); - Args.addArgument(TemplateArgumentLoc(TemplateArgument(Element), - Context.getTrivialTypeSourceInfo(Element, - Loc))); + auto TSI = S.Context.getTrivialTypeSourceInfo(TypeParam, Loc); + Args.addArgument(TemplateArgumentLoc(TemplateArgument(TypeParam), TSI)); - QualType T = CheckTemplateIdType(TemplateName(StdInitializerList), Loc, Args); + QualType T = S.CheckTemplateIdType(TemplateName(CTD), Loc, Args); if (T.isNull()) return QualType(); - return Context.getElaboratedType( + return S.Context.getElaboratedType( ElaboratedTypeKeyword::None, - NestedNameSpecifier::Create(Context, nullptr, getStdNamespace()), T); + NestedNameSpecifier::Create(S.Context, nullptr, S.getStdNamespace()), T); +} + +QualType Sema::BuildStdInitializerList(QualType Element, SourceLocation Loc) { + if (!StdInitializerList) { + bool WasMalformed = false; + StdInitializerList = + LookupStdClassTemplate(*this, Loc, "initializer_list", &WasMalformed); + if (!StdInitializerList) { + if (!WasMalformed) + Diag(Loc, diag::err_implied_std_initializer_list_not_found); + return QualType(); + } + } + return BuildStdClassTemplate(*this, StdInitializerList, Element, Loc); +} + +QualType Sema::tryBuildStdTypeIdentity(QualType Type, SourceLocation Loc) { + if (!StdTypeIdentity) { + StdTypeIdentity = LookupStdClassTemplate(*this, Loc, "type_identity", + /*WasMalformed=*/nullptr); + if (!StdTypeIdentity) + return QualType(); + } + return BuildStdClassTemplate(*this, StdTypeIdentity, Type, Loc); } bool Sema::isInitListConstructor(const FunctionDecl *Ctor) { @@ -16288,6 +16411,65 @@ bool Sema::CompleteConstructorCall(CXXConstructorDecl *Constructor, return Invalid; } +TypeAwareAllocationMode Sema::ShouldUseTypeAwareOperatorNewOrDelete() const { + bool SeenTypedOperators = Context.hasSeenTypeAwareOperatorNewOrDelete(); + return typeAwareAllocationModeFromBool(SeenTypedOperators); +} + +FunctionDecl * +Sema::BuildTypeAwareUsualDelete(FunctionTemplateDecl *FnTemplateDecl, + QualType DeallocType, SourceLocation Loc) { + if (DeallocType.isNull()) + return nullptr; + + FunctionDecl *FnDecl = FnTemplateDecl->getTemplatedDecl(); + if (!FnDecl->isTypeAwareOperatorNewOrDelete()) + return nullptr; + + if (FnDecl->isVariadic()) + return nullptr; + + unsigned NumParams = FnDecl->getNumParams(); + constexpr unsigned RequiredParameterCount = + FunctionDecl::RequiredTypeAwareDeleteParameterCount; + // A usual deallocation function has no placement parameters + if (NumParams != RequiredParameterCount) + return nullptr; + + // A type aware allocation is only usual if the only dependent parameter is + // the first parameter. + if (llvm::any_of(FnDecl->parameters().drop_front(), + [](const ParmVarDecl *ParamDecl) { + return ParamDecl->getType()->isDependentType(); + })) + return nullptr; + + QualType SpecializedTypeIdentity = tryBuildStdTypeIdentity(DeallocType, Loc); + if (SpecializedTypeIdentity.isNull()) + return nullptr; + + SmallVector ArgTypes; + ArgTypes.reserve(NumParams); + + // The first parameter to a type aware operator delete is by definition the + // type-identity argument, so we explicitly set this to the target + // type-identity type, the remaining usual parameters should then simply match + // the type declared in the function template. + ArgTypes.push_back(SpecializedTypeIdentity); + for (unsigned ParamIdx = 1; ParamIdx < RequiredParameterCount; ++ParamIdx) + ArgTypes.push_back(FnDecl->getParamDecl(ParamIdx)->getType()); + + FunctionProtoType::ExtProtoInfo EPI; + QualType ExpectedFunctionType = + Context.getFunctionType(Context.VoidTy, ArgTypes, EPI); + sema::TemplateDeductionInfo Info(Loc); + FunctionDecl *Result; + if (DeduceTemplateArguments(FnTemplateDecl, nullptr, ExpectedFunctionType, + Result, Info) != TemplateDeductionResult::Success) + return nullptr; + return Result; +} + static inline bool CheckOperatorNewDeleteDeclarationScope(Sema &SemaRef, const FunctionDecl *FnDecl) { @@ -16317,79 +16499,184 @@ static CanQualType RemoveAddressSpaceFromPtr(Sema &SemaRef, PtrTy->getPointeeType().getUnqualifiedType(), PtrQuals))); } -static inline bool -CheckOperatorNewDeleteTypes(Sema &SemaRef, const FunctionDecl *FnDecl, - CanQualType ExpectedResultType, - CanQualType ExpectedFirstParamType, - unsigned DependentParamTypeDiag, - unsigned InvalidParamTypeDiag) { - QualType ResultType = - FnDecl->getType()->castAs()->getReturnType(); +enum class AllocationOperatorKind { New, Delete }; - if (SemaRef.getLangOpts().OpenCLCPlusPlus) { - // The operator is valid on any address space for OpenCL. - // Drop address space from actual and expected result types. - if (const auto *PtrTy = ResultType->getAs()) - ResultType = RemoveAddressSpaceFromPtr(SemaRef, PtrTy); +static bool IsPotentiallyTypeAwareOperatorNewOrDelete(Sema &SemaRef, + const FunctionDecl *FD, + bool *WasMalformed) { + const Decl *MalformedDecl = nullptr; + if (FD->getNumParams() > 0 && + SemaRef.isStdTypeIdentity(FD->getParamDecl(0)->getType(), + /*TypeArgument=*/nullptr, &MalformedDecl)) + return true; - if (auto ExpectedPtrTy = ExpectedResultType->getAs()) - ExpectedResultType = RemoveAddressSpaceFromPtr(SemaRef, ExpectedPtrTy); + if (!MalformedDecl) + return false; + + if (WasMalformed) + *WasMalformed = true; + + return true; +} + +static bool isDestroyingDeleteT(QualType Type) { + auto *RD = Type->getAsCXXRecordDecl(); + return RD && RD->isInStdNamespace() && RD->getIdentifier() && + RD->getIdentifier()->isStr("destroying_delete_t"); +} + +static bool IsPotentiallyDestroyingOperatorDelete(Sema &SemaRef, + const FunctionDecl *FD) { + // C++ P0722: + // Within a class C, a single object deallocation function with signature + // (T, std::destroying_delete_t, ) + // is a destroying operator delete. + bool IsPotentiallyTypeAware = IsPotentiallyTypeAwareOperatorNewOrDelete( + SemaRef, FD, /*WasMalformed=*/nullptr); + unsigned DestroyingDeleteIdx = IsPotentiallyTypeAware + /* address */ 1; + return isa(FD) && FD->getOverloadedOperator() == OO_Delete && + FD->getNumParams() > DestroyingDeleteIdx && + isDestroyingDeleteT(FD->getParamDecl(DestroyingDeleteIdx)->getType()); +} + +static inline bool CheckOperatorNewDeleteTypes( + Sema &SemaRef, FunctionDecl *FnDecl, AllocationOperatorKind OperatorKind, + CanQualType ExpectedResultType, CanQualType ExpectedSizeOrAddressParamType, + unsigned DependentParamTypeDiag, unsigned InvalidParamTypeDiag) { + auto NormalizeType = [&SemaRef](QualType T) { + if (SemaRef.getLangOpts().OpenCLCPlusPlus) { + // The operator is valid on any address space for OpenCL. + // Drop address space from actual and expected result types. + if (const auto PtrTy = T->template getAs()) + T = RemoveAddressSpaceFromPtr(SemaRef, PtrTy); + } + return SemaRef.Context.getCanonicalType(T); + }; + + const unsigned NumParams = FnDecl->getNumParams(); + unsigned FirstNonTypeParam = 0; + bool MalformedTypeIdentity = false; + bool IsPotentiallyTypeAware = IsPotentiallyTypeAwareOperatorNewOrDelete( + SemaRef, FnDecl, &MalformedTypeIdentity); + unsigned MinimumMandatoryArgumentCount = 1; + unsigned SizeParameterIndex = 0; + if (IsPotentiallyTypeAware) { + // We don't emit this diagnosis for template instantiations as we will + // have already emitted it for the original template declaration. + if (!FnDecl->isTemplateInstantiation()) { + unsigned DiagID = SemaRef.getLangOpts().CPlusPlus26 + ? diag::warn_cxx26_type_aware_allocators + : diag::ext_cxx26_type_aware_allocators; + SemaRef.Diag(FnDecl->getLocation(), DiagID); + } + + if (OperatorKind == AllocationOperatorKind::New) { + SizeParameterIndex = 1; + MinimumMandatoryArgumentCount = + FunctionDecl::RequiredTypeAwareNewParameterCount; + } else { + SizeParameterIndex = 2; + MinimumMandatoryArgumentCount = + FunctionDecl::RequiredTypeAwareDeleteParameterCount; + } + FirstNonTypeParam = 1; } + bool IsPotentiallyDestroyingDelete = + IsPotentiallyDestroyingOperatorDelete(SemaRef, FnDecl); + + if (IsPotentiallyDestroyingDelete) { + ++MinimumMandatoryArgumentCount; + ++SizeParameterIndex; + } + + if (NumParams < MinimumMandatoryArgumentCount) + return SemaRef.Diag(FnDecl->getLocation(), + diag::err_operator_new_delete_too_few_parameters) + << IsPotentiallyTypeAware << IsPotentiallyDestroyingDelete + << FnDecl->getDeclName() << MinimumMandatoryArgumentCount; + + for (unsigned Idx = 0; Idx < MinimumMandatoryArgumentCount; ++Idx) { + const ParmVarDecl *ParamDecl = FnDecl->getParamDecl(Idx); + if (ParamDecl->hasDefaultArg()) + return SemaRef.Diag(FnDecl->getLocation(), + diag::err_operator_new_default_arg) + << FnDecl->getDeclName() << Idx << ParamDecl->getDefaultArgRange(); + } + + auto *FnType = FnDecl->getType()->castAs(); + QualType CanResultType = NormalizeType(FnType->getReturnType()); + QualType CanExpectedResultType = NormalizeType(ExpectedResultType); + QualType CanExpectedSizeOrAddressParamType = + NormalizeType(ExpectedSizeOrAddressParamType); + // Check that the result type is what we expect. - if (SemaRef.Context.getCanonicalType(ResultType) != ExpectedResultType) { + if (CanResultType != CanExpectedResultType) { // Reject even if the type is dependent; an operator delete function is // required to have a non-dependent result type. return SemaRef.Diag( FnDecl->getLocation(), - ResultType->isDependentType() + CanResultType->isDependentType() ? diag::err_operator_new_delete_dependent_result_type : diag::err_operator_new_delete_invalid_result_type) << FnDecl->getDeclName() << ExpectedResultType; } // A function template must have at least 2 parameters. - if (FnDecl->getDescribedFunctionTemplate() && FnDecl->getNumParams() < 2) + if (FnDecl->getDescribedFunctionTemplate() && NumParams < 2) return SemaRef.Diag(FnDecl->getLocation(), diag::err_operator_new_delete_template_too_few_parameters) << FnDecl->getDeclName(); - // The function decl must have at least 1 parameter. - if (FnDecl->getNumParams() == 0) - return SemaRef.Diag(FnDecl->getLocation(), - diag::err_operator_new_delete_too_few_parameters) - << FnDecl->getDeclName(); - - QualType FirstParamType = FnDecl->getParamDecl(0)->getType(); - if (SemaRef.getLangOpts().OpenCLCPlusPlus) { - // The operator is valid on any address space for OpenCL. - // Drop address space from actual and expected first parameter types. - if (const auto *PtrTy = - FnDecl->getParamDecl(0)->getType()->getAs()) - FirstParamType = RemoveAddressSpaceFromPtr(SemaRef, PtrTy); - - if (auto ExpectedPtrTy = ExpectedFirstParamType->getAs()) - ExpectedFirstParamType = - RemoveAddressSpaceFromPtr(SemaRef, ExpectedPtrTy); - } + auto CheckType = [&](unsigned ParamIdx, QualType ExpectedType, + auto FallbackType) -> bool { + const ParmVarDecl *ParamDecl = FnDecl->getParamDecl(ParamIdx); + if (ExpectedType.isNull()) { + return SemaRef.Diag(FnDecl->getLocation(), InvalidParamTypeDiag) + << IsPotentiallyTypeAware << IsPotentiallyDestroyingDelete + << FnDecl->getDeclName() << (1 + ParamIdx) << FallbackType + << ParamDecl->getSourceRange(); + } + CanQualType CanExpectedTy = + NormalizeType(SemaRef.Context.getCanonicalType(ExpectedType)); + auto ActualParamType = + NormalizeType(ParamDecl->getType().getUnqualifiedType()); + if (ActualParamType == CanExpectedTy) + return false; + unsigned Diagnostic = ActualParamType->isDependentType() + ? DependentParamTypeDiag + : InvalidParamTypeDiag; + return SemaRef.Diag(FnDecl->getLocation(), Diagnostic) + << IsPotentiallyTypeAware << IsPotentiallyDestroyingDelete + << FnDecl->getDeclName() << (1 + ParamIdx) << ExpectedType + << FallbackType << ParamDecl->getSourceRange(); + }; // Check that the first parameter type is what we expect. - if (SemaRef.Context.getCanonicalType(FirstParamType).getUnqualifiedType() != - ExpectedFirstParamType) { - // The first parameter type is not allowed to be dependent. As a tentative - // DR resolution, we allow a dependent parameter type if it is the right - // type anyway, to allow destroying operator delete in class templates. - return SemaRef.Diag(FnDecl->getLocation(), FirstParamType->isDependentType() - ? DependentParamTypeDiag - : InvalidParamTypeDiag) - << FnDecl->getDeclName() << ExpectedFirstParamType; - } + if (CheckType(FirstNonTypeParam, CanExpectedSizeOrAddressParamType, "size_t")) + return true; - return false; + FnDecl->setIsDestroyingOperatorDelete(IsPotentiallyDestroyingDelete); + + // If the first parameter type is not a type-identity we're done, otherwise + // we need to ensure the size and alignment parameters have the correct type + if (!IsPotentiallyTypeAware) + return false; + + if (CheckType(SizeParameterIndex, SemaRef.Context.getSizeType(), "size_t")) + return true; + TypeDecl *StdAlignValTDecl = SemaRef.getStdAlignValT(); + QualType StdAlignValT = + StdAlignValTDecl ? SemaRef.Context.getTypeDeclType(StdAlignValTDecl) + : QualType(); + if (CheckType(SizeParameterIndex + 1, StdAlignValT, "std::align_val_t")) + return true; + + FnDecl->setIsTypeAwareOperatorNewOrDelete(); + return MalformedTypeIdentity; } -static bool -CheckOperatorNewDeclaration(Sema &SemaRef, const FunctionDecl *FnDecl) { +static bool CheckOperatorNewDeclaration(Sema &SemaRef, FunctionDecl *FnDecl) { // C++ [basic.stc.dynamic.allocation]p1: // A program is ill-formed if an allocation function is declared in a // namespace scope other than global scope or declared static in global @@ -16403,20 +16690,10 @@ CheckOperatorNewDeclaration(Sema &SemaRef, const FunctionDecl *FnDecl) { // C++ [basic.stc.dynamic.allocation]p1: // The return type shall be void*. The first parameter shall have type // std::size_t. - if (CheckOperatorNewDeleteTypes(SemaRef, FnDecl, SemaRef.Context.VoidPtrTy, - SizeTy, - diag::err_operator_new_dependent_param_type, - diag::err_operator_new_param_type)) - return true; - - // C++ [basic.stc.dynamic.allocation]p1: - // The first parameter shall not have an associated default argument. - if (FnDecl->getParamDecl(0)->hasDefaultArg()) - return SemaRef.Diag(FnDecl->getLocation(), - diag::err_operator_new_default_arg) - << FnDecl->getDeclName() << FnDecl->getParamDecl(0)->getDefaultArgRange(); - - return false; + return CheckOperatorNewDeleteTypes( + SemaRef, FnDecl, AllocationOperatorKind::New, SemaRef.Context.VoidPtrTy, + SizeTy, diag::err_operator_new_dependent_param_type, + diag::err_operator_new_param_type); } static bool @@ -16429,13 +16706,48 @@ CheckOperatorDeleteDeclaration(Sema &SemaRef, FunctionDecl *FnDecl) { return true; auto *MD = dyn_cast(FnDecl); + auto ConstructDestroyingDeleteAddressType = [&]() { + assert(MD); + return SemaRef.Context.getCanonicalType(SemaRef.Context.getPointerType( + SemaRef.Context.getRecordType(MD->getParent()))); + }; + + // C++ P2719: A destroying operator delete cannot be type aware + // so for QoL we actually check for this explicitly by considering + // an destroying-delete appropriate address type and the presence of + // any parameter of type destroying_delete_t as an erroneous attempt + // to declare a type aware destroying delete, rather than emitting a + // pile of incorrect parameter type errors. + if (MD && IsPotentiallyTypeAwareOperatorNewOrDelete( + SemaRef, MD, /*WasMalformed=*/nullptr)) { + QualType AddressParamType = + SemaRef.Context.getCanonicalType(MD->getParamDecl(1)->getType()); + if (AddressParamType != SemaRef.Context.VoidPtrTy && + AddressParamType == ConstructDestroyingDeleteAddressType()) { + // The address parameter type implies an author trying to construct a + // type aware destroying delete, so we'll see if we can find a parameter + // of type `std::destroying_delete_t`, and if we find it we'll report + // this as being an attempt at a type aware destroying delete just stop + // here. If we don't do this, the resulting incorrect parameter ordering + // results in a pile mismatched argument type errors that don't explain + // the core problem. + for (auto Param : MD->parameters()) { + if (isDestroyingDeleteT(Param->getType())) { + SemaRef.Diag(MD->getLocation(), + diag::err_type_aware_destroying_operator_delete) + << Param->getSourceRange(); + return true; + } + } + } + } // 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() + CanQualType ExpectedAddressParamType = + MD && IsPotentiallyDestroyingOperatorDelete(SemaRef, MD) ? SemaRef.Context.getCanonicalType(SemaRef.Context.getPointerType( SemaRef.Context.getRecordType(MD->getParent()))) : SemaRef.Context.VoidPtrTy; @@ -16443,7 +16755,8 @@ CheckOperatorDeleteDeclaration(Sema &SemaRef, FunctionDecl *FnDecl) { // C++ [basic.stc.dynamic.deallocation]p2: // Each deallocation function shall return void if (CheckOperatorNewDeleteTypes( - SemaRef, FnDecl, SemaRef.Context.VoidTy, ExpectedFirstParamType, + SemaRef, FnDecl, AllocationOperatorKind::Delete, + SemaRef.Context.VoidTy, ExpectedAddressParamType, diag::err_operator_delete_dependent_param_type, diag::err_operator_delete_param_type)) return true; @@ -16451,11 +16764,12 @@ CheckOperatorDeleteDeclaration(Sema &SemaRef, FunctionDecl *FnDecl) { // C++ P0722: // A destroying operator delete shall be a usual deallocation function. if (MD && !MD->getParent()->isDependentContext() && - MD->isDestroyingOperatorDelete() && - !SemaRef.isUsualDeallocationFunction(MD)) { - SemaRef.Diag(MD->getLocation(), - diag::err_destroying_operator_delete_not_usual); - return true; + MD->isDestroyingOperatorDelete()) { + if (!SemaRef.isUsualDeallocationFunction(MD)) { + 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 247cd02b2352..a87cf6087b71 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -1728,12 +1728,14 @@ static bool isNonPlacementDeallocationFunction(Sema &S, FunctionDecl *FD) { if (CXXMethodDecl *Method = dyn_cast(FD)) return S.isUsualDeallocationFunction(Method); - if (FD->getOverloadedOperator() != OO_Delete && - FD->getOverloadedOperator() != OO_Array_Delete) + if (!FD->getDeclName().isAnyOperatorDelete()) return false; - unsigned UsualParams = 1; + if (FD->isTypeAwareOperatorNewOrDelete()) + return FunctionDecl::RequiredTypeAwareDeleteParameterCount == + FD->getNumParams(); + unsigned UsualParams = 1; if (S.getLangOpts().SizedDeallocation && UsualParams < FD->getNumParams() && S.Context.hasSameUnqualifiedType( FD->getParamDecl(UsualParams)->getType(), @@ -1751,15 +1753,56 @@ static bool isNonPlacementDeallocationFunction(Sema &S, FunctionDecl *FD) { namespace { struct UsualDeallocFnInfo { - UsualDeallocFnInfo() : Found(), FD(nullptr) {} - UsualDeallocFnInfo(Sema &S, DeclAccessPair Found) + UsualDeallocFnInfo() + : Found(), FD(nullptr), + IDP(AlignedAllocationMode::No, SizedDeallocationMode::No) {} + UsualDeallocFnInfo(Sema &S, DeclAccessPair Found, QualType AllocType, + SourceLocation Loc) : Found(Found), FD(dyn_cast(Found->getUnderlyingDecl())), - Destroying(false), HasSizeT(false), HasAlignValT(false), + Destroying(false), + IDP({AllocType, TypeAwareAllocationMode::No, + AlignedAllocationMode::No, SizedDeallocationMode::No}), CUDAPref(SemaCUDA::CFP_Native) { - // A function template declaration is never a usual deallocation function. - if (!FD) - return; + // A function template declaration is only a usual deallocation function + // if it is a typed delete. + if (!FD) { + if (AllocType.isNull()) + return; + auto *FTD = dyn_cast(Found->getUnderlyingDecl()); + if (!FTD) + return; + FunctionDecl *InstantiatedDecl = + S.BuildTypeAwareUsualDelete(FTD, AllocType, Loc); + if (!InstantiatedDecl) + return; + FD = InstantiatedDecl; + } unsigned NumBaseParams = 1; + if (FD->isTypeAwareOperatorNewOrDelete()) { + // If this is a type aware operator delete we instantiate an appropriate + // specialization of std::type_identity<>. If we do not know the + // type being deallocated, or if the type-identity parameter of the + // deallocation function does not match the constructed type_identity + // specialization we reject the declaration. + if (AllocType.isNull()) { + FD = nullptr; + return; + } + QualType TypeIdentityTag = FD->getParamDecl(0)->getType(); + QualType ExpectedTypeIdentityTag = + S.tryBuildStdTypeIdentity(AllocType, Loc); + if (ExpectedTypeIdentityTag.isNull()) { + FD = nullptr; + return; + } + if (!S.Context.hasSameType(TypeIdentityTag, ExpectedTypeIdentityTag)) { + FD = nullptr; + return; + } + IDP.PassTypeIdentity = TypeAwareAllocationMode::Yes; + ++NumBaseParams; + } + if (FD->isDestroyingOperatorDelete()) { Destroying = true; ++NumBaseParams; @@ -1770,13 +1813,13 @@ namespace { FD->getParamDecl(NumBaseParams)->getType(), S.Context.getSizeType())) { ++NumBaseParams; - HasSizeT = true; + IDP.PassSize = SizedDeallocationMode::Yes; } if (NumBaseParams < FD->getNumParams() && FD->getParamDecl(NumBaseParams)->getType()->isAlignValT()) { ++NumBaseParams; - HasAlignValT = true; + IDP.PassAlignment = AlignedAllocationMode::Yes; } // In CUDA, determine how much we'd like / dislike to call this. @@ -1787,31 +1830,69 @@ namespace { explicit operator bool() const { return FD; } - bool isBetterThan(const UsualDeallocFnInfo &Other, bool WantSize, - bool WantAlign) const { + int Compare(Sema &S, const UsualDeallocFnInfo &Other, + ImplicitDeallocationParameters TargetIDP) const { + assert(!TargetIDP.Type.isNull() || + !isTypeAwareAllocation(Other.IDP.PassTypeIdentity)); + // C++ P0722: // A destroying operator delete is preferred over a non-destroying // operator delete. if (Destroying != Other.Destroying) - return Destroying; + return Destroying ? 1 : -1; + + const ImplicitDeallocationParameters &OtherIDP = Other.IDP; + // Selection for type awareness has priority over alignment and size + if (IDP.PassTypeIdentity != OtherIDP.PassTypeIdentity) + return IDP.PassTypeIdentity == TargetIDP.PassTypeIdentity ? 1 : -1; // 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 // such a parameter is preferred - if (HasAlignValT != Other.HasAlignValT) - return HasAlignValT == WantAlign; + if (IDP.PassAlignment != OtherIDP.PassAlignment) + return IDP.PassAlignment == TargetIDP.PassAlignment ? 1 : -1; - if (HasSizeT != Other.HasSizeT) - return HasSizeT == WantSize; + if (IDP.PassSize != OtherIDP.PassSize) + return IDP.PassSize == TargetIDP.PassSize ? 1 : -1; + + if (isTypeAwareAllocation(IDP.PassTypeIdentity)) { + // Type aware allocation involves templates so we need to choose + // the best type + FunctionTemplateDecl *PrimaryTemplate = FD->getPrimaryTemplate(); + FunctionTemplateDecl *OtherPrimaryTemplate = + Other.FD->getPrimaryTemplate(); + if ((!PrimaryTemplate) != (!OtherPrimaryTemplate)) + return OtherPrimaryTemplate ? 1 : -1; + + if (PrimaryTemplate && OtherPrimaryTemplate) { + const auto *DC = dyn_cast(Found->getDeclContext()); + const auto *OtherDC = + dyn_cast(Other.Found->getDeclContext()); + unsigned ImplicitArgCount = Destroying + IDP.getNumImplicitArgs(); + if (FunctionTemplateDecl *Best = S.getMoreSpecializedTemplate( + PrimaryTemplate, OtherPrimaryTemplate, SourceLocation(), + TPOC_Call, ImplicitArgCount, + DC ? QualType(DC->getTypeForDecl(), 0) : QualType{}, + OtherDC ? QualType(OtherDC->getTypeForDecl(), 0) : QualType{}, + false)) { + return Best == PrimaryTemplate ? 1 : -1; + } + } + } // Use CUDA call preference as a tiebreaker. - return CUDAPref > Other.CUDAPref; + if (CUDAPref > Other.CUDAPref) + return 1; + if (CUDAPref == Other.CUDAPref) + return 0; + return -1; } DeclAccessPair Found; FunctionDecl *FD; - bool Destroying, HasSizeT, HasAlignValT; + bool Destroying; + ImplicitDeallocationParameters IDP; SemaCUDA::CUDAFunctionPreference CUDAPref; }; } @@ -1826,32 +1907,62 @@ static bool hasNewExtendedAlignment(Sema &S, QualType AllocType) { S.getASTContext().getTargetInfo().getNewAlign(); } +static bool CheckDeleteOperator(Sema &S, SourceLocation StartLoc, + SourceRange Range, bool Diagnose, + CXXRecordDecl *NamingClass, DeclAccessPair Decl, + FunctionDecl *Operator) { + if (Operator->isTypeAwareOperatorNewOrDelete()) { + QualType SelectedTypeIdentityParameter = + Operator->getParamDecl(0)->getType(); + if (S.RequireCompleteType(StartLoc, SelectedTypeIdentityParameter, + diag::err_incomplete_type)) + return true; + } + + // FIXME: DiagnoseUseOfDecl? + if (Operator->isDeleted()) { + if (Diagnose) { + StringLiteral *Msg = Operator->getDeletedMessage(); + S.Diag(StartLoc, diag::err_deleted_function_use) + << (Msg != nullptr) << (Msg ? Msg->getString() : StringRef()); + S.NoteDeletedFunction(Operator); + } + return true; + } + + return S.CheckAllocationAccess(StartLoc, Range, NamingClass, Decl, Diagnose); +} + /// Select the correct "usual" deallocation function to use from a selection of /// deallocation functions (either global or class-scope). static UsualDeallocFnInfo resolveDeallocationOverload( - Sema &S, LookupResult &R, bool WantSize, bool WantAlign, + Sema &S, LookupResult &R, const ImplicitDeallocationParameters &IDP, + SourceLocation Loc, llvm::SmallVectorImpl *BestFns = nullptr) { - UsualDeallocFnInfo Best; + UsualDeallocFnInfo Best; for (auto I = R.begin(), E = R.end(); I != E; ++I) { - UsualDeallocFnInfo Info(S, I.getPair()); + UsualDeallocFnInfo Info(S, I.getPair(), IDP.Type, Loc); if (!Info || !isNonPlacementDeallocationFunction(S, Info.FD) || Info.CUDAPref == SemaCUDA::CFP_Never) continue; + if (!isTypeAwareAllocation(IDP.PassTypeIdentity) && + isTypeAwareAllocation(Info.IDP.PassTypeIdentity)) + continue; if (!Best) { Best = Info; if (BestFns) BestFns->push_back(Info); continue; } - - if (Best.isBetterThan(Info, WantSize, WantAlign)) + int ComparisonResult = Best.Compare(S, Info, IDP); + if (ComparisonResult > 0) continue; // If more than one preferred function is found, all non-preferred // functions are eliminated from further consideration. - if (BestFns && Info.isBetterThan(Best, WantSize, WantAlign)) + if (BestFns && ComparisonResult < 0) BestFns->clear(); Best = Info; @@ -1867,6 +1978,7 @@ static UsualDeallocFnInfo resolveDeallocationOverload( /// we need to store the array size (even if the type is /// trivially-destructible). static bool doesUsualArrayDeleteWantSize(Sema &S, SourceLocation loc, + TypeAwareAllocationMode PassType, QualType allocType) { const RecordType *record = allocType->getBaseElementTypeUnsafe()->getAs(); @@ -1892,10 +2004,12 @@ static bool doesUsualArrayDeleteWantSize(Sema &S, SourceLocation loc, // C++17 [expr.delete]p10: // If the deallocation functions have class scope, the one without a // parameter of type std::size_t is selected. - auto Best = resolveDeallocationOverload( - S, ops, /*WantSize*/false, - /*WantAlign*/hasNewExtendedAlignment(S, allocType)); - return Best && Best.HasSizeT; + ImplicitDeallocationParameters IDP = { + allocType, PassType, + alignedAllocationModeFromBool(hasNewExtendedAlignment(S, allocType)), + SizedDeallocationMode::No}; + auto Best = resolveDeallocationOverload(S, ops, IDP, loc); + return Best && isSizedDeallocation(Best.IDP.PassSize); } ExprResult @@ -2012,8 +2126,7 @@ void Sema::diagnoseUnavailableAlignedAllocation(const FunctionDecl &FD, getASTContext().getTargetInfo().getPlatformName()); VersionTuple OSVersion = alignedAllocMinVersion(T.getOS()); - OverloadedOperatorKind Kind = FD.getDeclName().getCXXOverloadedOperator(); - bool IsDelete = Kind == OO_Delete || Kind == OO_Array_Delete; + bool IsDelete = FD.getDeclName().isAnyOperatorDelete(); Diag(Loc, diag::err_aligned_allocation_unavailable) << IsDelete << FD.getType().getAsString() << OSName << OSVersion.getAsString() << OSVersion.empty(); @@ -2307,27 +2420,31 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, unsigned Alignment = AllocType->isDependentType() ? 0 : Context.getTypeAlign(AllocType); unsigned NewAlignment = Context.getTargetInfo().getNewAlign(); - bool PassAlignment = getLangOpts().AlignedAllocation && - Alignment > NewAlignment; + ImplicitAllocationParameters IAP = { + AllocType, ShouldUseTypeAwareOperatorNewOrDelete(), + alignedAllocationModeFromBool(getLangOpts().AlignedAllocation && + Alignment > NewAlignment)}; if (CheckArgsForPlaceholders(PlacementArgs)) return ExprError(); AllocationFunctionScope Scope = UseGlobal ? AFS_Global : AFS_Both; + SourceRange AllocationParameterRange = Range; + if (PlacementLParen.isValid() && PlacementRParen.isValid()) + AllocationParameterRange = SourceRange(PlacementLParen, PlacementRParen); if (!AllocType->isDependentType() && !Expr::hasAnyTypeDependentArguments(PlacementArgs) && - FindAllocationFunctions( - StartLoc, SourceRange(PlacementLParen, PlacementRParen), Scope, Scope, - AllocType, ArraySize.has_value(), PassAlignment, PlacementArgs, - OperatorNew, OperatorDelete)) + FindAllocationFunctions(StartLoc, AllocationParameterRange, Scope, Scope, + AllocType, ArraySize.has_value(), IAP, + PlacementArgs, OperatorNew, OperatorDelete)) return ExprError(); // If this is an array allocation, compute whether the usual array // deallocation function for the type has a size_t parameter. bool UsualArrayDeleteWantsSize = false; if (ArraySize && !AllocType->isDependentType()) - UsualArrayDeleteWantsSize = - doesUsualArrayDeleteWantSize(*this, StartLoc, AllocType); + UsualArrayDeleteWantsSize = doesUsualArrayDeleteWantSize( + *this, StartLoc, IAP.PassTypeIdentity, AllocType); SmallVector AllPlaceArgs; if (OperatorNew) { @@ -2339,10 +2456,16 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, // arguments. Skip the first parameter because we don't have a corresponding // argument. Skip the second parameter too if we're passing in the // alignment; we've already filled it in. - unsigned NumImplicitArgs = PassAlignment ? 2 : 1; - if (GatherArgumentsForCall(PlacementLParen, OperatorNew, Proto, - NumImplicitArgs, PlacementArgs, AllPlaceArgs, - CallType)) + unsigned NumImplicitArgs = 1; + if (isTypeAwareAllocation(IAP.PassTypeIdentity)) { + assert(OperatorNew->isTypeAwareOperatorNewOrDelete()); + NumImplicitArgs++; + } + if (isAlignedAllocation(IAP.PassAlignment)) + NumImplicitArgs++; + if (GatherArgumentsForCall(AllocationParameterRange.getBegin(), OperatorNew, + Proto, NumImplicitArgs, PlacementArgs, + AllPlaceArgs, CallType)) return ExprError(); if (!AllPlaceArgs.empty()) @@ -2377,10 +2500,10 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, IntegerLiteral AllocationSizeLiteral( Context, AllocationSize.value_or(llvm::APInt::getZero(SizeTyWidth)), - SizeTy, SourceLocation()); + SizeTy, StartLoc); // Otherwise, if we failed to constant-fold the allocation size, we'll // just give up and pass-in something opaque, that isn't a null pointer. - OpaqueValueExpr OpaqueAllocationSize(SourceLocation(), SizeTy, VK_PRValue, + OpaqueValueExpr OpaqueAllocationSize(StartLoc, SizeTy, VK_PRValue, OK_Ordinary, /*SourceExpr=*/nullptr); // Let's synthesize the alignment argument in case we will need it. @@ -2393,7 +2516,7 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, Context, llvm::APInt(Context.getTypeSize(SizeTy), Alignment / Context.getCharWidth()), - SizeTy, SourceLocation()); + SizeTy, StartLoc); ImplicitCastExpr DesiredAlignment(ImplicitCastExpr::OnStack, AlignValT, CK_IntegralCast, &AlignmentLiteral, VK_PRValue, FPOptionsOverride()); @@ -2404,7 +2527,7 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, CallArgs.emplace_back(AllocationSize ? static_cast(&AllocationSizeLiteral) : &OpaqueAllocationSize); - if (PassAlignment) + if (isAlignedAllocation(IAP.PassAlignment)) CallArgs.emplace_back(&DesiredAlignment); CallArgs.insert(CallArgs.end(), PlacementArgs.begin(), PlacementArgs.end()); @@ -2415,7 +2538,7 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, // Warn if the type is over-aligned and is being allocated by (unaligned) // global operator new. - if (PlacementArgs.empty() && !PassAlignment && + if (PlacementArgs.empty() && !isAlignedAllocation(IAP.PassAlignment) && (OperatorNew->isImplicit() || (OperatorNew->getBeginLoc().isValid() && getSourceManager().isInSystemHeader(OperatorNew->getBeginLoc())))) { @@ -2502,10 +2625,9 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, } return CXXNewExpr::Create(Context, UseGlobal, OperatorNew, OperatorDelete, - PassAlignment, UsualArrayDeleteWantsSize, - PlacementArgs, TypeIdParens, ArraySize, InitStyle, - Initializer, ResultType, AllocTypeInfo, Range, - DirectInitRange); + IAP, UsualArrayDeleteWantsSize, PlacementArgs, + TypeIdParens, ArraySize, InitStyle, Initializer, + ResultType, AllocTypeInfo, Range, DirectInitRange); } bool Sema::CheckAllocatedType(QualType AllocType, SourceLocation Loc, @@ -2546,10 +2668,17 @@ bool Sema::CheckAllocatedType(QualType AllocType, SourceLocation Loc, return false; } -static bool resolveAllocationOverload( - Sema &S, LookupResult &R, SourceRange Range, SmallVectorImpl &Args, - bool &PassAlignment, FunctionDecl *&Operator, - OverloadCandidateSet *AlignedCandidates, Expr *AlignArg, bool Diagnose) { +enum class ResolveMode { Typed, Untyped }; +static bool resolveAllocationOverloadInterior( + Sema &S, LookupResult &R, SourceRange Range, ResolveMode Mode, + SmallVectorImpl &Args, AlignedAllocationMode &PassAlignment, + FunctionDecl *&Operator, OverloadCandidateSet *AlignedCandidates, + Expr *AlignArg, bool Diagnose) { + unsigned NonTypeArgumentOffset = 0; + if (Mode == ResolveMode::Typed) { + ++NonTypeArgumentOffset; + } + OverloadCandidateSet Candidates(R.getNameLoc(), OverloadCandidateSet::CSK_Normal); for (LookupResult::iterator Alloc = R.begin(), AllocEnd = R.end(); @@ -2557,6 +2686,9 @@ static bool resolveAllocationOverload( // Even member operator new/delete are implicitly treated as // static, so don't use AddMemberCandidate. NamedDecl *D = (*Alloc)->getUnderlyingDecl(); + bool IsTypeAware = D->getAsFunction()->isTypeAwareOperatorNewOrDelete(); + if (IsTypeAware == (Mode != ResolveMode::Typed)) + continue; if (FunctionTemplateDecl *FnTemplate = dyn_cast(D)) { S.AddTemplateOverloadCandidate(FnTemplate, Alloc.getPair(), @@ -2590,13 +2722,13 @@ static bool resolveAllocationOverload( // If no matching function is found and the allocated object type has // new-extended alignment, the alignment argument is removed from the // argument list, and overload resolution is performed again. - if (PassAlignment) { - PassAlignment = false; - AlignArg = Args[1]; - Args.erase(Args.begin() + 1); - return resolveAllocationOverload(S, R, Range, Args, PassAlignment, - Operator, &Candidates, AlignArg, - Diagnose); + if (isAlignedAllocation(PassAlignment)) { + PassAlignment = AlignedAllocationMode::No; + AlignArg = Args[NonTypeArgumentOffset + 1]; + Args.erase(Args.begin() + NonTypeArgumentOffset + 1); + return resolveAllocationOverloadInterior(S, R, Range, Mode, Args, + PassAlignment, Operator, + &Candidates, AlignArg, Diagnose); } // MSVC will fall back on trying to find a matching global operator new @@ -2606,16 +2738,22 @@ static bool resolveAllocationOverload( // FIXME: Find out how this interacts with the std::align_val_t fallback // once MSVC implements it. if (R.getLookupName().getCXXOverloadedOperator() == OO_Array_New && - S.Context.getLangOpts().MSVCCompat) { + S.Context.getLangOpts().MSVCCompat && Mode != ResolveMode::Typed) { R.clear(); R.setLookupName(S.Context.DeclarationNames.getCXXOperatorName(OO_New)); S.LookupQualifiedName(R, S.Context.getTranslationUnitDecl()); // FIXME: This will give bad diagnostics pointing at the wrong functions. - return resolveAllocationOverload(S, R, Range, Args, PassAlignment, - Operator, /*Candidates=*/nullptr, - /*AlignArg=*/nullptr, Diagnose); + return resolveAllocationOverloadInterior(S, R, Range, Mode, Args, + PassAlignment, Operator, + /*Candidates=*/nullptr, + /*AlignArg=*/nullptr, Diagnose); + } + if (Mode == ResolveMode::Typed) { + // If we can't find a matching type aware operator we don't consider this + // a failure. + Operator = nullptr; + return false; } - if (Diagnose) { // If this is an allocation of the form 'new (p) X' for some object // pointer p (or an expression that will decay to such a pointer), @@ -2639,16 +2777,21 @@ static bool resolveAllocationOverload( SmallVector AlignedCands; llvm::SmallVector AlignedArgs; if (AlignedCandidates) { - auto IsAligned = [](OverloadCandidate &C) { - return C.Function->getNumParams() > 1 && - C.Function->getParamDecl(1)->getType()->isAlignValT(); + auto IsAligned = [NonTypeArgumentOffset](OverloadCandidate &C) { + auto AlignArgOffset = NonTypeArgumentOffset + 1; + return C.Function->getNumParams() > AlignArgOffset && + C.Function->getParamDecl(AlignArgOffset) + ->getType() + ->isAlignValT(); }; auto IsUnaligned = [&](OverloadCandidate &C) { return !IsAligned(C); }; - AlignedArgs.reserve(Args.size() + 1); - AlignedArgs.push_back(Args[0]); + AlignedArgs.reserve(Args.size() + NonTypeArgumentOffset + 1); + for (unsigned Idx = 0; Idx < NonTypeArgumentOffset + 1; ++Idx) + AlignedArgs.push_back(Args[Idx]); AlignedArgs.push_back(AlignArg); - AlignedArgs.append(Args.begin() + 1, Args.end()); + AlignedArgs.append(Args.begin() + NonTypeArgumentOffset + 1, + Args.end()); AlignedCands = AlignedCandidates->CompleteCandidates( S, OCD_AllCandidates, AlignedArgs, R.getNameLoc(), IsAligned); @@ -2688,14 +2831,75 @@ static bool resolveAllocationOverload( llvm_unreachable("Unreachable, bad result from BestViableFunction"); } -bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, - AllocationFunctionScope NewScope, - AllocationFunctionScope DeleteScope, - QualType AllocType, bool IsArray, - bool &PassAlignment, MultiExprArg PlaceArgs, - FunctionDecl *&OperatorNew, - FunctionDecl *&OperatorDelete, - bool Diagnose) { +enum class DeallocLookupMode { Untyped, OptionallyTyped }; + +static void LookupGlobalDeallocationFunctions(Sema &S, SourceLocation Loc, + LookupResult &FoundDelete, + DeallocLookupMode Mode, + DeclarationName Name) { + S.LookupQualifiedName(FoundDelete, S.Context.getTranslationUnitDecl()); + if (Mode != DeallocLookupMode::OptionallyTyped) { + // We're going to remove either the typed or the non-typed + bool RemoveTypedDecl = Mode == DeallocLookupMode::Untyped; + LookupResult::Filter Filter = FoundDelete.makeFilter(); + while (Filter.hasNext()) { + FunctionDecl *FD = Filter.next()->getUnderlyingDecl()->getAsFunction(); + if (FD->isTypeAwareOperatorNewOrDelete() == RemoveTypedDecl) + Filter.erase(); + } + Filter.done(); + } +} + +static bool resolveAllocationOverload( + Sema &S, LookupResult &R, SourceRange Range, SmallVectorImpl &Args, + ImplicitAllocationParameters &IAP, FunctionDecl *&Operator, + OverloadCandidateSet *AlignedCandidates, Expr *AlignArg, bool Diagnose) { + Operator = nullptr; + if (isTypeAwareAllocation(IAP.PassTypeIdentity)) { + assert(S.isStdTypeIdentity(Args[0]->getType(), nullptr)); + // The internal overload resolution work mutates the argument list + // in accordance with the spec. We may want to change that in future, + // but for now we deal with this by making a copy of the non-type-identity + // arguments. + SmallVector UntypedParameters; + UntypedParameters.reserve(Args.size() - 1); + UntypedParameters.push_back(Args[1]); + // Type aware allocation implicitly includes the alignment parameter so + // only include it in the untyped parameter list if alignment was explicitly + // requested + if (isAlignedAllocation(IAP.PassAlignment)) + UntypedParameters.push_back(Args[2]); + UntypedParameters.append(Args.begin() + 3, Args.end()); + + AlignedAllocationMode InitialAlignmentMode = IAP.PassAlignment; + IAP.PassAlignment = AlignedAllocationMode::Yes; + if (resolveAllocationOverloadInterior( + S, R, Range, ResolveMode::Typed, Args, IAP.PassAlignment, Operator, + AlignedCandidates, AlignArg, Diagnose)) + return true; + if (Operator) + return false; + + // If we got to this point we could not find a matching typed operator + // so we update the IAP flags, and revert to our stored copy of the + // type-identity-less argument list. + IAP.PassTypeIdentity = TypeAwareAllocationMode::No; + IAP.PassAlignment = InitialAlignmentMode; + Args = UntypedParameters; + } + assert(!S.isStdTypeIdentity(Args[0]->getType(), nullptr)); + return resolveAllocationOverloadInterior( + S, R, Range, ResolveMode::Untyped, Args, IAP.PassAlignment, Operator, + AlignedCandidates, AlignArg, Diagnose); +} + +bool Sema::FindAllocationFunctions( + SourceLocation StartLoc, SourceRange Range, + AllocationFunctionScope NewScope, AllocationFunctionScope DeleteScope, + QualType AllocType, bool IsArray, ImplicitAllocationParameters &IAP, + MultiExprArg PlaceArgs, FunctionDecl *&OperatorNew, + FunctionDecl *&OperatorDelete, bool Diagnose) { // --- Choosing an allocation function --- // C++ 5.3.4p8 - 14 & 18 // 1) If looking in AFS_Global scope for allocation functions, only look in @@ -2707,28 +2911,7 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, // placement form. SmallVector AllocArgs; - AllocArgs.reserve((PassAlignment ? 2 : 1) + PlaceArgs.size()); - - // We don't care about the actual value of these arguments. - // FIXME: Should the Sema create the expression and embed it in the syntax - // tree? Or should the consumer just recalculate the value? - // FIXME: Using a dummy value will interact poorly with attribute enable_if. - QualType SizeTy = Context.getSizeType(); - unsigned SizeTyWidth = Context.getTypeSize(SizeTy); - IntegerLiteral Size(Context, llvm::APInt::getZero(SizeTyWidth), SizeTy, - SourceLocation()); - AllocArgs.push_back(&Size); - - QualType AlignValT = Context.VoidTy; - if (PassAlignment) { - DeclareGlobalNewDelete(); - AlignValT = Context.getTypeDeclType(getStdAlignValT()); - } - CXXScalarValueInitExpr Align(AlignValT, nullptr, SourceLocation()); - if (PassAlignment) - AllocArgs.push_back(&Align); - - AllocArgs.insert(AllocArgs.end(), PlaceArgs.begin(), PlaceArgs.end()); + AllocArgs.reserve(IAP.getNumImplicitArgs() + PlaceArgs.size()); // C++ [expr.new]p8: // If the allocated type is a non-array type, the allocation @@ -2741,6 +2924,50 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, QualType AllocElemType = Context.getBaseElementType(AllocType); + // We don't care about the actual value of these arguments. + // FIXME: Should the Sema create the expression and embed it in the syntax + // tree? Or should the consumer just recalculate the value? + // FIXME: Using a dummy value will interact poorly with attribute enable_if. + + // We use size_t as a stand in so that we can construct the init + // expr on the stack + QualType TypeIdentity = Context.getSizeType(); + if (isTypeAwareAllocation(IAP.PassTypeIdentity)) { + QualType SpecializedTypeIdentity = + tryBuildStdTypeIdentity(IAP.Type, StartLoc); + if (!SpecializedTypeIdentity.isNull()) { + TypeIdentity = SpecializedTypeIdentity; + if (RequireCompleteType(StartLoc, TypeIdentity, + diag::err_incomplete_type)) + return true; + } else + IAP.PassTypeIdentity = TypeAwareAllocationMode::No; + } + TypeAwareAllocationMode OriginalTypeAwareState = IAP.PassTypeIdentity; + + CXXScalarValueInitExpr TypeIdentityParam(TypeIdentity, nullptr, StartLoc); + if (isTypeAwareAllocation(IAP.PassTypeIdentity)) + AllocArgs.push_back(&TypeIdentityParam); + + QualType SizeTy = Context.getSizeType(); + unsigned SizeTyWidth = Context.getTypeSize(SizeTy); + IntegerLiteral Size(Context, llvm::APInt::getZero(SizeTyWidth), SizeTy, + SourceLocation()); + AllocArgs.push_back(&Size); + + QualType AlignValT = Context.VoidTy; + bool IncludeAlignParam = isAlignedAllocation(IAP.PassAlignment) || + isTypeAwareAllocation(IAP.PassTypeIdentity); + if (IncludeAlignParam) { + DeclareGlobalNewDelete(); + AlignValT = Context.getTypeDeclType(getStdAlignValT()); + } + CXXScalarValueInitExpr Align(AlignValT, nullptr, SourceLocation()); + if (IncludeAlignParam) + AllocArgs.push_back(&Align); + + AllocArgs.insert(AllocArgs.end(), PlaceArgs.begin(), PlaceArgs.end()); + // Find the allocation function. { LookupResult R(*this, NewName, StartLoc, LookupOrdinaryName); @@ -2783,8 +3010,8 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, // We do our own custom access checks below. R.suppressDiagnostics(); - if (resolveAllocationOverload(*this, R, Range, AllocArgs, PassAlignment, - OperatorNew, /*Candidates=*/nullptr, + if (resolveAllocationOverload(*this, R, Range, AllocArgs, IAP, OperatorNew, + /*Candidates=*/nullptr, /*AlignArg=*/nullptr, Diagnose)) return true; } @@ -2846,6 +3073,23 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, } bool FoundGlobalDelete = FoundDelete.empty(); + bool IsClassScopedTypeAwareNew = + isTypeAwareAllocation(IAP.PassTypeIdentity) && + OperatorNew->getDeclContext()->isRecord(); + auto DiagnoseMissingTypeAwareCleanupOperator = [&](bool IsPlacementOperator) { + assert(isTypeAwareAllocation(IAP.PassTypeIdentity)); + if (Diagnose) { + Diag(StartLoc, diag::err_mismatching_type_aware_cleanup_deallocator) + << OperatorNew->getDeclName() << IsPlacementOperator << DeleteName; + Diag(OperatorNew->getLocation(), diag::note_type_aware_operator_declared) + << OperatorNew->isTypeAwareOperatorNewOrDelete() + << OperatorNew->getDeclName() << OperatorNew->getDeclContext(); + } + }; + if (IsClassScopedTypeAwareNew && FoundDelete.empty()) { + DiagnoseMissingTypeAwareCleanupOperator(/*isPlacementNew=*/false); + return true; + } if (FoundDelete.empty()) { FoundDelete.clear(LookupOrdinaryName); @@ -2853,7 +3097,11 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, return true; DeclareGlobalNewDelete(); - LookupQualifiedName(FoundDelete, Context.getTranslationUnitDecl()); + DeallocLookupMode LookupMode = isTypeAwareAllocation(OriginalTypeAwareState) + ? DeallocLookupMode::OptionallyTyped + : DeallocLookupMode::Untyped; + LookupGlobalDeallocationFunctions(*this, StartLoc, FoundDelete, LookupMode, + DeleteName); } FoundDelete.suppressDiagnostics(); @@ -2873,7 +3121,13 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, // FIXME: Should (size_t, std::align_val_t) also be considered non-placement? // This affects whether an exception from the constructor of an overaligned // type uses the sized or non-sized form of aligned operator delete. - bool isPlacementNew = !PlaceArgs.empty() || OperatorNew->param_size() != 1 || + + unsigned NonPlacementNewArgCount = 1; // size parameter + if (isTypeAwareAllocation(IAP.PassTypeIdentity)) + NonPlacementNewArgCount = + /* type-identity */ 1 + /* size */ 1 + /* alignment */ 1; + bool isPlacementNew = !PlaceArgs.empty() || + OperatorNew->param_size() != NonPlacementNewArgCount || OperatorNew->isVariadic(); if (isPlacementNew) { @@ -2891,9 +3145,16 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, { auto *Proto = OperatorNew->getType()->castAs(); - SmallVector ArgTypes; + SmallVector ArgTypes; + int InitialParamOffset = 0; + if (isTypeAwareAllocation(IAP.PassTypeIdentity)) { + ArgTypes.push_back(TypeIdentity); + InitialParamOffset = 1; + } ArgTypes.push_back(Context.VoidPtrTy); - for (unsigned I = 1, N = Proto->getNumParams(); I < N; ++I) + for (unsigned I = ArgTypes.size() - InitialParamOffset, + N = Proto->getNumParams(); + I < N; ++I) ArgTypes.push_back(Proto->getParamType(I)); FunctionProtoType::ExtProtoInfo EPI; @@ -2929,6 +3190,10 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, if (getLangOpts().CUDA) CUDA().EraseUnwantedMatches(getCurFunctionDecl(/*AllowLambda=*/true), Matches); + if (Matches.empty() && isTypeAwareAllocation(IAP.PassTypeIdentity)) { + DiagnoseMissingTypeAwareCleanupOperator(isPlacementNew); + return true; + } } else { // C++1y [expr.new]p22: // For a non-placement allocation function, the normal deallocation @@ -2938,11 +3203,14 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, // without a size_t argument, but prefers a non-member operator delete // with a size_t where possible (which it always is in this case). llvm::SmallVector BestDeallocFns; + ImplicitDeallocationParameters IDP = { + AllocElemType, OriginalTypeAwareState, + alignedAllocationModeFromBool( + hasNewExtendedAlignment(*this, AllocElemType)), + sizedDeallocationModeFromBool(FoundGlobalDelete)}; UsualDeallocFnInfo Selected = resolveDeallocationOverload( - *this, FoundDelete, /*WantSize*/ FoundGlobalDelete, - /*WantAlign*/ hasNewExtendedAlignment(*this, AllocElemType), - &BestDeallocFns); - if (Selected) + *this, FoundDelete, IDP, StartLoc, &BestDeallocFns); + if (Selected && BestDeallocFns.empty()) Matches.push_back(std::make_pair(Selected.Found, Selected.FD)); else { // If we failed to select an operator, all remaining functions are viable @@ -2958,6 +3226,35 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, // deallocation function will be called. if (Matches.size() == 1) { OperatorDelete = Matches[0].second; + bool FoundTypeAwareOperator = + OperatorDelete->isTypeAwareOperatorNewOrDelete() || + OperatorNew->isTypeAwareOperatorNewOrDelete(); + if (Diagnose && FoundTypeAwareOperator) { + bool MismatchedTypeAwareness = + OperatorDelete->isTypeAwareOperatorNewOrDelete() != + OperatorNew->isTypeAwareOperatorNewOrDelete(); + bool MismatchedContext = + OperatorDelete->getDeclContext() != OperatorNew->getDeclContext(); + if (MismatchedTypeAwareness || MismatchedContext) { + FunctionDecl *Operators[] = {OperatorDelete, OperatorNew}; + bool TypeAwareOperatorIndex = + OperatorNew->isTypeAwareOperatorNewOrDelete(); + Diag(StartLoc, diag::err_mismatching_type_aware_cleanup_deallocator) + << Operators[TypeAwareOperatorIndex]->getDeclName() + << isPlacementNew + << Operators[!TypeAwareOperatorIndex]->getDeclName() + << Operators[TypeAwareOperatorIndex]->getDeclContext(); + Diag(OperatorNew->getLocation(), + diag::note_type_aware_operator_declared) + << OperatorNew->isTypeAwareOperatorNewOrDelete() + << OperatorNew->getDeclName() << OperatorNew->getDeclContext(); + Diag(OperatorDelete->getLocation(), + diag::note_type_aware_operator_declared) + << OperatorDelete->isTypeAwareOperatorNewOrDelete() + << OperatorDelete->getDeclName() + << OperatorDelete->getDeclContext(); + } + } // C++1z [expr.new]p23: // If the lookup finds a usual deallocation function (3.7.4.2) @@ -2968,22 +3265,26 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, if (getLangOpts().CPlusPlus11 && isPlacementNew && isNonPlacementDeallocationFunction(*this, OperatorDelete)) { UsualDeallocFnInfo Info(*this, - DeclAccessPair::make(OperatorDelete, AS_public)); + DeclAccessPair::make(OperatorDelete, AS_public), + AllocElemType, StartLoc); // Core issue, per mail to core reflector, 2016-10-09: // If this is a member operator delete, and there is a corresponding // non-sized member operator delete, this isn't /really/ a sized // deallocation function, it just happens to have a size_t parameter. - bool IsSizedDelete = Info.HasSizeT; + bool IsSizedDelete = isSizedDeallocation(Info.IDP.PassSize); if (IsSizedDelete && !FoundGlobalDelete) { - auto NonSizedDelete = - resolveDeallocationOverload(*this, FoundDelete, /*WantSize*/false, - /*WantAlign*/Info.HasAlignValT); - if (NonSizedDelete && !NonSizedDelete.HasSizeT && - NonSizedDelete.HasAlignValT == Info.HasAlignValT) + ImplicitDeallocationParameters SizeTestingIDP = { + AllocElemType, Info.IDP.PassTypeIdentity, Info.IDP.PassAlignment, + SizedDeallocationMode::No}; + auto NonSizedDelete = resolveDeallocationOverload( + *this, FoundDelete, SizeTestingIDP, StartLoc); + if (NonSizedDelete && + !isSizedDeallocation(NonSizedDelete.IDP.PassSize) && + NonSizedDelete.IDP.PassAlignment == Info.IDP.PassAlignment) IsSizedDelete = false; } - if (IsSizedDelete) { + if (IsSizedDelete && !isTypeAwareAllocation(IAP.PassTypeIdentity)) { SourceRange R = PlaceArgs.empty() ? SourceRange() : SourceRange(PlaceArgs.front()->getBeginLoc(), @@ -2994,9 +3295,11 @@ bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, << DeleteName; } } + if (CheckDeleteOperator(*this, StartLoc, Range, Diagnose, + FoundDelete.getNamingClass(), Matches[0].first, + Matches[0].second)) + return true; - CheckAllocationAccess(StartLoc, Range, FoundDelete.getNamingClass(), - Matches[0].first); } else if (!Matches.empty()) { // We found multiple suitable operators. Per [expr.new]p20, that means we // call no 'operator delete' function, but we should at least warn the user. @@ -3175,9 +3478,7 @@ void Sema::DeclareGlobalAllocationFunction(DeclarationName Name, /*IsVariadic=*/false, /*IsCXXMethod=*/false, /*IsBuiltin=*/true)); QualType BadAllocType; - bool HasBadAllocExceptionSpec - = (Name.getCXXOverloadedOperator() == OO_New || - Name.getCXXOverloadedOperator() == OO_Array_New); + bool HasBadAllocExceptionSpec = Name.isAnyOperatorNew(); if (HasBadAllocExceptionSpec) { if (!getLangOpts().CPlusPlus11) { BadAllocType = Context.getTypeDeclType(getStdBadAlloc()); @@ -3257,21 +3558,29 @@ void Sema::DeclareGlobalAllocationFunction(DeclarationName Name, } } -FunctionDecl *Sema::FindUsualDeallocationFunction(SourceLocation StartLoc, - bool CanProvideSize, - bool Overaligned, - DeclarationName Name) { +FunctionDecl * +Sema::FindUsualDeallocationFunction(SourceLocation StartLoc, + ImplicitDeallocationParameters IDP, + DeclarationName Name) { DeclareGlobalNewDelete(); LookupResult FoundDelete(*this, Name, StartLoc, LookupOrdinaryName); - LookupQualifiedName(FoundDelete, Context.getTranslationUnitDecl()); + LookupGlobalDeallocationFunctions(*this, StartLoc, FoundDelete, + DeallocLookupMode::OptionallyTyped, Name); // FIXME: It's possible for this to result in ambiguity, through a // user-declared variadic operator delete or the enable_if attribute. We // should probably not consider those cases to be usual deallocation // functions. But for now we just make an arbitrary choice in that case. - auto Result = resolveDeallocationOverload(*this, FoundDelete, CanProvideSize, - Overaligned); + auto Result = resolveDeallocationOverload(*this, FoundDelete, IDP, StartLoc); + if (!Result) + return nullptr; + + if (CheckDeleteOperator(*this, StartLoc, StartLoc, /*Diagnose=*/true, + FoundDelete.getNamingClass(), Result.Found, + Result.FD)) + return nullptr; + assert(Result.FD && "operator delete missing from global scope?"); return Result.FD; } @@ -3282,21 +3591,30 @@ FunctionDecl *Sema::FindDeallocationFunctionForDestructor(SourceLocation Loc, bool Diagnose) { FunctionDecl *OperatorDelete = nullptr; - if (FindDeallocationFunction(Loc, RD, Name, OperatorDelete, Diagnose)) + QualType DeallocType = Context.getRecordType(RD); + ImplicitDeallocationParameters IDP = { + DeallocType, ShouldUseTypeAwareOperatorNewOrDelete(), + AlignedAllocationMode::No, SizedDeallocationMode::No}; + + if (FindDeallocationFunction(Loc, RD, Name, OperatorDelete, IDP, Diagnose)) return nullptr; + if (OperatorDelete) return OperatorDelete; // If there's no class-specific operator delete, look up the global delete. - return FindUsualDeallocationFunction( - Loc, true, hasNewExtendedAlignment(*this, Context.getRecordType(RD)), - Name); + QualType RecordType = Context.getRecordType(RD); + IDP.PassAlignment = + alignedAllocationModeFromBool(hasNewExtendedAlignment(*this, RecordType)); + IDP.PassSize = SizedDeallocationMode::Yes; + return FindUsualDeallocationFunction(Loc, IDP, Name); } bool Sema::FindDeallocationFunction(SourceLocation StartLoc, CXXRecordDecl *RD, DeclarationName Name, - FunctionDecl *&Operator, bool Diagnose, - bool WantSize, bool WantAligned) { + FunctionDecl *&Operator, + ImplicitDeallocationParameters IDP, + bool Diagnose) { LookupResult Found(*this, Name, StartLoc, LookupOrdinaryName); // Try to find operator delete/operator delete[] in class scope. LookupQualifiedName(Found, RD); @@ -3309,36 +3627,22 @@ bool Sema::FindDeallocationFunction(SourceLocation StartLoc, CXXRecordDecl *RD, Found.suppressDiagnostics(); - bool Overaligned = - WantAligned || hasNewExtendedAlignment(*this, Context.getRecordType(RD)); + if (!isAlignedAllocation(IDP.PassAlignment) && + hasNewExtendedAlignment(*this, Context.getRecordType(RD))) + IDP.PassAlignment = AlignedAllocationMode::Yes; // C++17 [expr.delete]p10: // If the deallocation functions have class scope, the one without a // parameter of type std::size_t is selected. llvm::SmallVector Matches; - resolveDeallocationOverload(*this, Found, /*WantSize*/ WantSize, - /*WantAlign*/ Overaligned, &Matches); + resolveDeallocationOverload(*this, Found, IDP, StartLoc, &Matches); // If we could find an overload, use it. if (Matches.size() == 1) { Operator = cast(Matches[0].FD); - - // FIXME: DiagnoseUseOfDecl? - if (Operator->isDeleted()) { - if (Diagnose) { - StringLiteral *Msg = Operator->getDeletedMessage(); - Diag(StartLoc, diag::err_deleted_function_use) - << (Msg != nullptr) << (Msg ? Msg->getString() : StringRef()); - NoteDeletedFunction(Operator); - } - return true; - } - - if (CheckAllocationAccess(StartLoc, SourceRange(), Found.getNamingClass(), - Matches[0].Found, Diagnose) == AR_inaccessible) - return true; - - return false; + return CheckDeleteOperator(*this, StartLoc, StartLoc, Diagnose, + Found.getNamingClass(), Matches[0].Found, + Operator); } // We found multiple suitable operators; complain about the ambiguity. @@ -3762,9 +4066,12 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal, ArrayForm ? OO_Array_Delete : OO_Delete); if (PointeeRD) { + ImplicitDeallocationParameters IDP = { + Pointee, ShouldUseTypeAwareOperatorNewOrDelete(), + AlignedAllocationMode::No, SizedDeallocationMode::No}; if (!UseGlobal && FindDeallocationFunction(StartLoc, PointeeRD, DeleteName, - OperatorDelete)) + OperatorDelete, IDP)) return ExprError(); // If we're allocating an array of records, check whether the @@ -3773,16 +4080,17 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal, // If the user specifically asked to use the global allocator, // we'll need to do the lookup into the class. if (UseGlobal) - UsualArrayDeleteWantsSize = - doesUsualArrayDeleteWantSize(*this, StartLoc, PointeeElem); + UsualArrayDeleteWantsSize = doesUsualArrayDeleteWantSize( + *this, StartLoc, IDP.PassTypeIdentity, PointeeElem); // Otherwise, the usual operator delete[] should be the // function we just found. - else if (isa_and_nonnull(OperatorDelete)) - UsualArrayDeleteWantsSize = - UsualDeallocFnInfo(*this, - DeclAccessPair::make(OperatorDelete, AS_public)) - .HasSizeT; + else if (isa_and_nonnull(OperatorDelete)) { + UsualDeallocFnInfo UDFI( + *this, DeclAccessPair::make(OperatorDelete, AS_public), Pointee, + StartLoc); + UsualArrayDeleteWantsSize = isSizedDeallocation(UDFI.IDP.PassSize); + } } if (!PointeeRD->hasIrrelevantDestructor()) { @@ -3815,8 +4123,13 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal, bool Overaligned = hasNewExtendedAlignment(*this, Pointee); // Look for a global declaration. - OperatorDelete = FindUsualDeallocationFunction(StartLoc, CanProvideSize, - Overaligned, DeleteName); + ImplicitDeallocationParameters IDP = { + Pointee, ShouldUseTypeAwareOperatorNewOrDelete(), + alignedAllocationModeFromBool(Overaligned), + sizedDeallocationModeFromBool(CanProvideSize)}; + OperatorDelete = FindUsualDeallocationFunction(StartLoc, IDP, DeleteName); + if (!OperatorDelete) + return ExprError(); } if (OperatorDelete->isInvalidDecl()) @@ -3838,11 +4151,21 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal, DiagnoseUseOfDecl(OperatorDelete, StartLoc); + unsigned AddressParamIdx = 0; + if (OperatorDelete->isTypeAwareOperatorNewOrDelete()) { + QualType TypeIdentity = OperatorDelete->getParamDecl(0)->getType(); + if (RequireCompleteType(StartLoc, TypeIdentity, + diag::err_incomplete_type)) + return ExprError(); + AddressParamIdx = 1; + } + // 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(); + QualType ParamType = + OperatorDelete->getParamDecl(AddressParamIdx)->getType(); if (!IsVirtualDelete && !ParamType->getPointeeType()->isVoidType()) { Qualifiers Qs = Pointee.getQualifiers(); if (Qs.hasCVRQualifiers()) { diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 3f619b3fb3da..108d7e1dbaeb 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -2738,6 +2738,9 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl( LexicalDC = SemaRef.CurContext; } + Function->setIsDestroyingOperatorDelete(D->isDestroyingOperatorDelete()); + Function->setIsTypeAwareOperatorNewOrDelete( + D->isTypeAwareOperatorNewOrDelete()); Function->setLexicalDeclContext(LexicalDC); // Attach the parameters diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 22fe54b52643..715aaf645226 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -1927,6 +1927,7 @@ void ASTStmtReader::VisitCXXNewExpr(CXXNewExpr *E) { E->CXXNewExprBits.IsGlobalNew = Record.readInt(); E->CXXNewExprBits.ShouldPassAlignment = Record.readInt(); + E->CXXNewExprBits.ShouldPassTypeIdentity = Record.readInt(); E->CXXNewExprBits.UsualArrayDeleteWantsSize = Record.readInt(); E->CXXNewExprBits.HasInitializer = Record.readInt(); E->CXXNewExprBits.StoredInitializationStyle = Record.readInt(); diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index d0a0f843c754..939b37c54734 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -1932,7 +1932,9 @@ void ASTStmtWriter::VisitCXXNewExpr(CXXNewExpr *E) { Record.push_back(E->isParenTypeId()); Record.push_back(E->isGlobalNew()); - Record.push_back(E->passAlignment()); + ImplicitAllocationParameters IAP = E->implicitAllocationParameters(); + Record.push_back(isAlignedAllocation(IAP.PassAlignment)); + Record.push_back(isTypeAwareAllocation(IAP.PassTypeIdentity)); Record.push_back(E->doesUsualArrayDeleteWantSize()); Record.push_back(E->CXXNewExprBits.HasInitializer); Record.push_back(E->CXXNewExprBits.StoredInitializationStyle); diff --git a/clang/test/CXX/basic/basic.stc/basic.stc.dynamic/basic.stc.dynamic.allocation/p1.cpp b/clang/test/CXX/basic/basic.stc/basic.stc.dynamic/basic.stc.dynamic.allocation/p1.cpp index 3b77a62ce7d6..2ba212b2cedf 100644 --- a/clang/test/CXX/basic/basic.stc/basic.stc.dynamic/basic.stc.dynamic.allocation/p1.cpp +++ b/clang/test/CXX/basic/basic.stc/basic.stc.dynamic/basic.stc.dynamic.allocation/p1.cpp @@ -37,7 +37,7 @@ struct G { }; struct H { - template void *operator new(T, int); // expected-error {{'operator new' cannot take a dependent type as first parameter; use size_t}} + template void *operator new(T, int); // expected-error {{'operator new' cannot take a dependent type as its 1st parameter; use size_t}} }; struct I { diff --git a/clang/test/CodeGenCXX/Inputs/std-coroutine.h b/clang/test/CodeGenCXX/Inputs/std-coroutine.h new file mode 100644 index 000000000000..0bc459d48ccc --- /dev/null +++ b/clang/test/CodeGenCXX/Inputs/std-coroutine.h @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wno-unreachable-code -Wno-unused-value +#ifndef STD_COROUTINE_H +#define STD_COROUTINE_H + +namespace std { + +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; + +template +typename remove_reference::type &&move(T &&t) noexcept; + +struct input_iterator_tag {}; +struct forward_iterator_tag : public input_iterator_tag {}; + +template +struct coroutine_traits { using promise_type = typename Ret::promise_type; }; + +template +struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; + static coroutine_handle from_promise(Promise &promise); + constexpr void* address() const noexcept; +}; +template <> +struct coroutine_handle { + template + coroutine_handle(coroutine_handle) noexcept; + static coroutine_handle from_address(void *); + constexpr void* address() const noexcept; +}; + +struct suspend_always { + bool await_ready() noexcept { return false; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; + +struct suspend_never { + bool await_ready() noexcept { return true; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; + +} // namespace std + +#endif // STD_COROUTINE_H diff --git a/clang/test/CodeGenCXX/type-aware-allocators.cpp b/clang/test/CodeGenCXX/type-aware-allocators.cpp new file mode 100644 index 000000000000..cce9197ed0d1 --- /dev/null +++ b/clang/test/CodeGenCXX/type-aware-allocators.cpp @@ -0,0 +1,212 @@ +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -fsized-deallocation -faligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++23 -o - -Wno-c++26-extensions | FileCheck --check-prefixes=CHECK,CHECK_SIZED_ALIGNED %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -fno-sized-deallocation -faligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++23 -o - -Wno-c++26-extensions | FileCheck --check-prefixes=CHECK,CHECK_NO_SIZE_ALIGNED %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -fno-sized-deallocation -fno-aligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++23 -o - -Wno-c++26-extensions | FileCheck --check-prefixes=CHECK,CHECK_NO_SIZE_NO_ALIGN %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -fsized-deallocation -fno-aligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++23 -o - -Wno-c++26-extensions | FileCheck --check-prefixes=CHECK,CHECK_SIZED_NO_ALIGN %s +// Test default behaviour with c++26 +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -fsized-deallocation -faligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++26 -o - | FileCheck --check-prefixes=CHECK,CHECK_SIZED_ALIGNED %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -fno-sized-deallocation -faligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++26 -o - | FileCheck --check-prefixes=CHECK,CHECK_NO_SIZE_ALIGNED %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -fno-sized-deallocation -fno-aligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++26 -o - | FileCheck --check-prefixes=CHECK,CHECK_NO_SIZE_NO_ALIGN %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -fsized-deallocation -fno-aligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++26 -o - | FileCheck --check-prefixes=CHECK,CHECK_SIZED_NO_ALIGN %s + + +namespace std { + template struct type_identity { + typedef T type; + }; + enum class align_val_t : __SIZE_TYPE__ {}; + struct destroying_delete_t {}; +} + +using size_t = __SIZE_TYPE__; + +// Sanity check to esure the semantics of the selected compiler mode +// will trigger the exception handlers we are expecting, before +// involving type aware allocation. +// We duplicate the struct definitions so we don't trigger diagnostics +// for changing operator resolution on the same type, and we do the +// untyped test before specifying the typed operators rather than using +// template constraints so we don't have to deal with monstrous mangling. +struct S1 { + S1(); +}; + +struct __attribute__((aligned(128))) S2 { + S2(); +}; + +struct S3 { + S3(); +}; + +struct __attribute__((aligned(128))) S4 { + S4(); + char buffer[130]; +}; + +extern "C" void test_no_type_aware_allocator() { + S1 *s1 = new S1; + delete s1; + S2 *s2 = new S2; + delete s2; +} +// CHECK-LABEL: test_no_type_aware_allocator +// CHECK: [[ALLOC_RESULT:%.*]] = call {{.*}} @_Znwm( +// CHECK: @_ZN2S1C1Ev({{.*}} [[ALLOC_RESULT]]) +// CHECK-NEXT: unwind label %[[S1LPAD:lpad]] +// CHECK_SIZED_ALIGNED: @_ZdlPvm( +// CHECK_SIZED_NO_ALIGN: @_ZdlPvm( +// CHECK_NO_SIZE_ALIGNED: @_ZdlPv( +// CHECK_NO_SIZE_NO_ALIGN: @_ZdlPv( +// CHECK_SIZED_ALIGNED: [[ALIGNED_ALLOC_RESULT:%.*]] = call {{.*}} @_ZnwmSt11align_val_t( +// CHECK_NO_SIZE_ALIGNED: [[ALIGNED_ALLOC_RESULT:%.*]] = call {{.*}} @_ZnwmSt11align_val_t( +// CHECK_SIZED_NO_ALIGN: [[ALIGNED_ALLOC_RESULT:%.*]] = call {{.*}} @_Znwm( +// CHECK_NO_SIZE_NO_ALIGN: [[ALIGNED_ALLOC_RESULT:%.*]] = call {{.*}} @_Znwm( +// CHECK: _ZN2S2C1Ev({{.*}} [[ALIGNED_ALLOC_RESULT]]) +// CHECK-NEXT: unwind label %[[S2LPAD:lpad3]] +// CHECK_SIZED_ALIGNED: _ZdlPvmSt11align_val_t( +// CHECK_NO_SIZE_ALIGNED: _ZdlPvSt11align_val_t( +// CHECK_SIZED_NO_ALIGN: _ZdlPvm( +// CHECK_NO_SIZE_NO_ALIGN: _ZdlPv( +// CHECK: [[S1LPAD]]:{{.*}}; +// CHECK_SIZED_ALIGNED: @_ZdlPvm({{.*}}[[ALLOC_RESULT]], {{.*}}) +// CHECK_SIZED_NO_ALIGN: @_ZdlPvm({{.*}}[[ALLOC_RESULT]], {{.*}}) +// CHECK_NO_SIZE_ALIGNED: @_ZdlPv({{.*}}[[ALLOC_RESULT]]) +// CHECK_NO_SIZE_NO_ALIGN: @_ZdlPv({{.*}}[[ALLOC_RESULT]]) +// CHECK: [[S2LPAD]]: +// CHECK_SIZED_ALIGNED: _ZdlPvSt11align_val_t({{.*}}[[ALIGNED_ALLOC_RESULT]], {{.*}}) +// CHECK_NO_SIZE_ALIGNED: _ZdlPvSt11align_val_t({{.*}}[[ALIGNED_ALLOC_RESULT]], {{.*}}) +// CHECK_SIZED_NO_ALIGN: _ZdlPvm({{.*}}[[ALIGNED_ALLOC_RESULT]], {{.*}}) +// CHECK_NO_SIZE_NO_ALIGN: _ZdlPv({{.*}}[[ALIGNED_ALLOC_RESULT]]) + +template void *operator new(std::type_identity, size_t, std::align_val_t); +template void operator delete(std::type_identity, void *, size_t, std::align_val_t); + +extern "C" void test_free_type_aware_allocator() { + S3 *s3 = new S3; + delete s3; + S4 *s4 = new S4; + delete s4; +} +// CHECK-LABEL: test_free_type_aware_allocator +// CHECK: [[ALLOC_RESULT:%.*]] = call {{.*}} @_ZnwI2S3EPvSt13type_identityIT_EmSt11align_val_t( +// CHECK: @_ZN2S3C1Ev({{.*}}[[ALLOC_RESULT]]) +// CHECK-NEXT: unwind label %[[S3LPAD:.*]] +// CHECK: @_ZdlI2S3EvSt13type_identityIT_EPvmSt11align_val_t( +// CHECK: [[ALIGNED_ALLOC_RESULT:%.*]] = call {{.*}} @_ZnwI2S4EPvSt13type_identityIT_EmSt11align_val_t( +// CHECK: @_ZN2S4C1Ev({{.*}}[[ALIGNED_ALLOC_RESULT]]) +// CHECK-NEXT: unwind label %[[S4LPAD:.*]] +// CHECK: @_ZdlI2S4EvSt13type_identityIT_EPvmSt11align_val_t({{.*}}, {{.*}}, {{.*}} 256, {{.*}} 128) +// CHECK: [[S3LPAD]]: +// CHECK: @_ZdlI2S3EvSt13type_identityIT_EPvmSt11align_val_t({{.*}}, {{.*}}[[ALLOC_RESULT]], {{.*}} 1, {{.*}} 1) +// CHECK: [[S4LPAD]]: +// CHECK: @_ZdlI2S4EvSt13type_identityIT_EPvmSt11align_val_t({{.*}}, {{.*}}[[ALIGNED_ALLOC_RESULT]], {{.*}} 256, {{.*}} 128) + +struct S5 { + S5(); + template void *operator new(std::type_identity, size_t, std::align_val_t); + template void operator delete(std::type_identity, void*, size_t, std::align_val_t); + void operator delete(S5*, std::destroying_delete_t); +}; + +extern "C" void test_ensure_type_aware_cleanup() { + S5 *s5 = new S5; + delete s5; +} +// CHECK-LABEL: test_ensure_type_aware_cleanup +// CHECK: [[ALLOC_RESULT:%.*]] = call {{.*}} @_ZN2S5nwIS_EEPvSt13type_identityIT_EmSt11align_val_t( +// CHECK: @_ZN2S5C1Ev({{.*}}[[ALLOC_RESULT]]) +// CHECK-NEXT: unwind label %[[S5LPAD:.*]] +// CHECK: @_ZN2S5dlEPS_St19destroying_delete_t( +// CHECK: [[S5LPAD]]: +// CHECK: @_ZN2S5dlIS_EEvSt13type_identityIT_EPvmSt11align_val_t({{.*}}, {{.*}} [[ALLOC_RESULT]] + +struct S6 { + S6(); + virtual ~S6(); + template void *operator new(std::type_identity, size_t, std::align_val_t); + template void operator delete(std::type_identity, void*, size_t, std::align_val_t); +}; + +S6::~S6(){ +} +// CHECK-LABEL: _ZN2S6D0Ev +// CHECK: _ZN2S6dlIS_EEvSt13type_identityIT_EPvmSt11align_val_t( + +struct __attribute__((aligned(128))) S7 : S6 { + +}; + + +struct S8 : S6 { + S8(); + void *operator new(size_t); + void operator delete(void*); +}; + +S8::S8(){} + +extern "C" void test_ensure_type_aware_overrides() { + S6 *s6 = new S6; + delete s6; + S7 *s7 = new S7; + delete s7; + S8 *s8 = new S8; + delete s8; +} +// CHECK-LABEL: test_ensure_type_aware_overrides +// CHECK: [[S6_ALLOC:%.*]] = call {{.*}} @_ZN2S6nwIS_EEPvSt13type_identityIT_EmSt11align_val_t( +// CHECK: @_ZN2S6C1Ev({{.*}}[[S6_ALLOC]]) +// CHECK-NEXT: unwind label %[[S6LPAD:.*]] +// CHECK: [[S6_VTABLE:%vtable.*]] = load +// CHECK: [[S6_DFN_ADDR:%.*]] = getelementptr inbounds ptr, ptr [[S6_VTABLE]], i64 1 +// CHECK: [[S6_DFN:%.*]] = load ptr, ptr [[S6_DFN_ADDR]] +// CHECK: call void [[S6_DFN]]( +// CHECK: [[S7_ALLOC:%.*]] = call {{.*}} @_ZN2S6nwI2S7EEPvSt13type_identityIT_EmSt11align_val_t( +// CHECK: @_ZN2S7C1Ev({{.*}}[[S7_ALLOC]]) +// CHECK-NEXT: unwind label %[[S7LPAD:.*]] +// CHECK: [[S7_VTABLE:%vtable.*]] = load +// CHECK: [[S7_DFN_ADDR:%.*]] = getelementptr inbounds ptr, ptr [[S7_VTABLE]], i64 1 +// CHECK: [[S7_DFN:%.*]] = load ptr, ptr [[S7_DFN_ADDR]] +// CHECK: call void [[S7_DFN]]( +// CHECK: [[S8_ALLOC:%.*]] = call {{.*}} @_ZN2S8nwEm( +// CHECK: @_ZN2S8C1Ev({{.*}}[[S8_ALLOC]]) +// CHECK-NEXT: unwind label %[[S8LPAD:.*]] +// CHECK: [[S8_VTABLE:%vtable.*]] = load +// CHECK: [[S8_DFN_ADDR:%.*]] = getelementptr inbounds ptr, ptr [[S8_VTABLE]], i64 1 +// CHECK: [[S8_DFN:%.*]] = load ptr, ptr [[S8_DFN_ADDR]] +// CHECK: call void [[S8_DFN]]( +// CHECK: [[S6LPAD]]: +// CHECK: @_ZN2S6dlIS_EEvSt13type_identityIT_EPvmSt11align_val_t({{.*}}, {{.*}} [[S6_ALLOC]], {{.*}}, {{.*}}) +// CHECK: [[S7LPAD]]: +// CHECK: @_ZN2S6dlI2S7EEvSt13type_identityIT_EPvmSt11align_val_t({{.*}}, {{.*}} [[S7_ALLOC]], {{.*}}, {{.*}}) +// CHECK: [[S8LPAD]]: +// CHECK: @_ZN2S8dlEPv({{.*}} [[S8_ALLOC]]) + +struct __attribute__((aligned(128))) S11 { + S11(); + template void *operator new(std::type_identity, size_t, std::align_val_t); + template void operator delete(std::type_identity, void*, size_t, std::align_val_t); + void operator delete(S11*, std::destroying_delete_t, std::align_val_t); +}; + + +struct S12 { + template void *operator new(std::type_identity, size_t, std::align_val_t, unsigned line = __builtin_LINE()); + template void operator delete(std::type_identity, void*, size_t, std::align_val_t, unsigned line = __builtin_LINE()); + template void operator delete(std::type_identity, void*, size_t, std::align_val_t); +}; + +extern "C" void test_ensure_type_aware_resolution_includes_location() { + S12 *s12 = new S12(); // test line + delete s12; +} + +// CHECK-LABEL: test_ensure_type_aware_resolution_includes_location +// `180` in the next line is the line number from the test line in above +// CHECK: %call = call noundef ptr @_ZN3S12nwIS_EEPvSt13type_identityIT_EmSt11align_val_tj({{.*}}, {{.*}}, {{.*}}, {{.*}}) + +// CHECK-LABEL: @_ZN2S8D0Ev +// CHECK: @_ZN2S8dlEPv( + +// CHECK-LABEL: _ZN2S7D0Ev +// CHECK: _ZN2S6dlI2S7EEvSt13type_identityIT_EPvmSt11align_val_t( diff --git a/clang/test/CodeGenCXX/type-aware-coroutines.cpp b/clang/test/CodeGenCXX/type-aware-coroutines.cpp new file mode 100644 index 000000000000..0a19079d987e --- /dev/null +++ b/clang/test/CodeGenCXX/type-aware-coroutines.cpp @@ -0,0 +1,128 @@ +// RUN: %clang_cc1 -triple arm64-apple-macosx %s -std=c++23 -fcoroutines -fexceptions -emit-llvm -Wno-coro-type-aware-allocation-function -o - | FileCheck %s +// RUN: %clang_cc1 -triple arm64-apple-macosx %s -std=c++26 -fcoroutines -fexceptions -emit-llvm -Wno-coro-type-aware-allocation-function -o - | FileCheck %s + +#include "Inputs/std-coroutine.h" + +namespace std { + template struct type_identity { + typedef T type; + }; + typedef __SIZE_TYPE__ size_t; + enum class align_val_t {}; +} + +struct Allocator {}; + +struct resumable { + struct promise_type { + promise_type(); + void *operator new(std::size_t sz, int); + void *operator new(std::size_t sz, float); + void *operator new(std::type_identity, std::size_t sz, std::align_val_t, int); + void *operator new(std::type_identity, std::size_t sz, std::align_val_t, float); + void operator delete(std::type_identity, void *, std::size_t sz, std::align_val_t); + template void operator delete(std::type_identity, void *, std::size_t sz, std::align_val_t) = delete; + void operator delete(void *); + + resumable get_return_object() { return {}; } + auto initial_suspend() { return std::suspend_always(); } + auto final_suspend() noexcept { return std::suspend_always(); } + void unhandled_exception() {} + void return_void(){}; + std::suspend_always yield_value(int i); + }; +}; + +struct resumable2 { + struct promise_type { + promise_type(); + template void *operator new(std::size_t sz, Args...); + template void *operator new(std::type_identity, std::size_t sz, std::align_val_t, Args...); + void operator delete(std::type_identity, void *, std::size_t sz, std::align_val_t); + void operator delete(void *); + + resumable2 get_return_object() { return {}; } + auto initial_suspend() { return std::suspend_always(); } + auto final_suspend() noexcept { return std::suspend_always(); } + void unhandled_exception() {} + void return_void(){}; + std::suspend_always yield_value(int i); + }; +}; + +// CHECK-LABEL: void @f1 +extern "C" resumable f1(int) { + co_return; +// CHECK: coro.alloc: +// CHECK: _ZN9resumable12promise_typenwEmi +// CHECK: coro.free: +// CHECK: _ZN9resumable12promise_typedlEPv +} + +// CHECK-LABEL: void @f2 +extern "C" resumable f2(float) { + co_return; +// CHECK: coro.alloc: +// CHECK: _ZN9resumable12promise_typenwEmf +// CHECK: coro.free: +// CHECK: _ZN9resumable12promise_typedlEPv +} + +// CHECK-LABEL: void @f3 +extern "C" resumable2 f3(int, float, const char*, Allocator) { + co_yield 1; + co_return; +// CHECK: coro.alloc: +// CHECK: _ZN10resumable212promise_typenwIJifPKc9AllocatorEEEPvmDpT_ +// CHECK: coro.free: +// CHECK: _ZN10resumable212promise_typedlEPv +// CHECK: _ZN10resumable212promise_typedlEPv +} + +// CHECK-LABEL: void @f4 +extern "C" resumable f4(int n = 10) { + for (int i = 0; i < n; i++) co_yield i; +// CHECK: coro.alloc: +// CHECK: call {{.*}}@_ZN9resumable12promise_typenwEmi( +// CHECK: coro.free: +// CHECK: call void @_ZN9resumable12promise_typedlEPv( +// CHECK: call void @_ZN9resumable12promise_typedlEPv( +} + +struct resumable3 { + struct promise_type { + promise_type(); + resumable3 get_return_object() { return {}; } + auto initial_suspend() { return std::suspend_always(); } + auto final_suspend() noexcept { return std::suspend_always(); } + void unhandled_exception() {} + void return_void(){}; + std::suspend_always yield_value(int i); + }; +}; +template void *operator new(std::type_identity, std::size_t sz, std::align_val_t); +template void *operator new(std::type_identity, std::size_t sz, std::align_val_t, Args...); +template void operator delete(std::type_identity, void *, std::size_t sz, std::align_val_t); +template void operator delete(std::type_identity, void *, std::size_t sz, std::align_val_t, Args...); + +// CHECK-LABEL: void @f5 +extern "C" resumable3 f5(float) { + co_return; +// CHECK: coro.alloc: +// CHECK: call {{.*}}@_Znwm( +// CHECK: coro.free: +// CHECK: call void @_ZdlPvm( +// CHECK: call void @_ZdlPvm( +} + +// CHECK-LABEL: void @f4.resume +// CHECK: coro.free: +// CHECK: _ZN9resumable12promise_typedlEPv + +// CHECK-LABEL: void @f4.destroy +// CHECK: coro.free: +// CHECK: _ZN9resumable12promise_typedlEPv + +// CHECK-LABEL: void @f4.cleanup +// CHECK: coro.free: +// CHECK: _ZN9resumable12promise_typedlEPv diff --git a/clang/test/CodeGenCXX/type-aware-new-constexpr.cpp b/clang/test/CodeGenCXX/type-aware-new-constexpr.cpp new file mode 100644 index 000000000000..57eba8c7cb7f --- /dev/null +++ b/clang/test/CodeGenCXX/type-aware-new-constexpr.cpp @@ -0,0 +1,56 @@ +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -fsized-deallocation -faligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++26 -o - | FileCheck %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -fno-sized-deallocation -faligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++26 -o - | FileCheck %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -fno-sized-deallocation -fno-aligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++26 -o - | FileCheck %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -fsized-deallocation -fno-aligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++26 -o - | FileCheck %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -faligned-allocation -emit-llvm -fcxx-exceptions -fexceptions -std=c++26 -fexperimental-new-constant-interpreter -o - | FileCheck %s + +using size_t = __SIZE_TYPE__; + +namespace std { + template struct type_identity { + typedef T type; + }; + enum class align_val_t : size_t {}; +} + + +template void *operator new(std::type_identity, size_t, std::align_val_t); +template void operator delete(std::type_identity, void *, size_t, std::align_val_t); +struct S { + int i = 0; + constexpr S() __attribute__((noinline)) {} +}; + + constexpr int doSomething() { + S* s = new S; + int result = s->i; + delete s; + return result; +} + +static constexpr int force_doSomething = doSomething(); +template struct Tag {}; + +void test1(Tag){ +// CHECK-LABEL: define void @_Z5test13TagILi0EE +} + +void test2(Tag){ +// CHECK-LABEL: define void @_Z5test23TagILi1EE +} + +int main() { + // CHECK-LABEL: define noundef i32 @main() + return doSomething(); + // CHECK: call{{.*}}i32 @_Z11doSomethingv() +} + +// CHECK-LABEL: define linkonce_odr noundef i32 @_Z11doSomethingv() +// CHECK: [[ALLOC:%.*]] = call noundef ptr @_ZnwI1SEPvSt13type_identityIT_Em +// CHECK: invoke noundef ptr @_ZN1SC1Ev(ptr{{.*}} [[ALLOC]]) +// CHECK-NEXT: to label %[[CONT:.*]] unwind label %[[LPAD:[a-z0-9]+]] +// CHECK: [[CONT]]: +// CHECK: call void @_ZdlI1SEvSt13type_identityIT_EPv +// CHECK: ret +// CHECK: [[LPAD]]: +// call void @_ZdlI1SEvSt13type_identityIT_EPvmSt11align_val_t({{.*}} [[ALLOC]]) diff --git a/clang/test/CodeGenCXX/type-aware-placement-operators.cpp b/clang/test/CodeGenCXX/type-aware-placement-operators.cpp new file mode 100644 index 000000000000..858db62ffcfb --- /dev/null +++ b/clang/test/CodeGenCXX/type-aware-placement-operators.cpp @@ -0,0 +1,75 @@ +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -emit-llvm -fcxx-exceptions -fexceptions -std=c++23 -fsized-deallocation -faligned-allocation -o - | FileCheck %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -emit-llvm -fcxx-exceptions -fexceptions -std=c++23 -fno-sized-deallocation -faligned-allocation -o - | FileCheck %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -emit-llvm -fcxx-exceptions -fexceptions -std=c++23 -fsized-deallocation -fno-aligned-allocation -o - | FileCheck %s +// RUN: %clang_cc1 %s -triple arm64-apple-macosx -emit-llvm -fcxx-exceptions -fexceptions -std=c++23 -fno-aligned-allocation -fno-sized-deallocation -o - | FileCheck %s + +namespace std { + template struct type_identity {}; + enum class align_val_t : __SIZE_TYPE__ {}; +} + +using size_t = __SIZE_TYPE__; +struct Context; +struct S1 { + S1(); + int i; +}; + +void *operator new(std::type_identity, size_t, std::align_val_t, Context&); +void operator delete(std::type_identity, void*, size_t, std::align_val_t, Context&); // #1 +void operator delete(std::type_identity, void*, size_t, std::align_val_t); + +struct S2 { + S2(); + int i; + template void *operator new(std::type_identity, size_t, std::align_val_t, Context&); + template void operator delete(std::type_identity, void*, size_t, std::align_val_t, Context&); // #2 + template void operator delete(std::type_identity, void*, size_t, std::align_val_t); // #3 +}; + +struct S3 { + S3(); + int i; + template void *operator new(std::type_identity, size_t, std::align_val_t); + template void operator delete(std::type_identity, void*, size_t, std::align_val_t); // #4 +}; + +extern "C" void test_s1(Context& Ctx) { + S1 *s1 = new (Ctx) S1; + delete s1; +} + +// CHECK-LABEL: test_s1 +// CHECK: [[S1_NEW:%.*]] = call noundef ptr @_ZnwSt13type_identityI2S1EmSt11align_val_tR7Context({{.*}},{{.*}} 4,{{.*}} 4, ptr noundef nonnull align 1 [[S1_NEW_CONTEXT:%.*]]) +// CHECK: call void @_ZdlSt13type_identityI2S1EPvmSt11align_val_t({{.*}}, ptr noundef %2,{{.*}} 4,{{.*}} 4) +// CHECK: call void @_ZdlSt13type_identityI2S1EPvmSt11align_val_tR7Context({{.*}}, ptr noundef [[S1_NEW]],{{.*}} 4,{{.*}} 4, ptr noundef nonnull align 1 [[S1_NEW_CONTEXT]]) + +extern "C" void test_s2(Context& Ctx) { + S2 *s2_1 = new (Ctx) S2; + delete s2_1; + S2 *s2_2 = new (std::align_val_t(128), Ctx) S2; + delete s2_2; +} + +// CHECK-LABEL: test_s2 +// CHECK: [[S2_NEW1:%.*]] = call noundef ptr @_ZN2S2nwIS_EEPvSt13type_identityIT_EmSt11align_val_tR7Context({{.*}},{{.*}} 4,{{.*}} 4,{{.*}} [[S2_NEW1_CONTEXT:%.*]]) +// CHECK: call void @_ZN2S2dlIS_EEvSt13type_identityIT_EPvmSt11align_val_t({{.*}},{{.*}},{{.*}} 4,{{.*}} 4) +// CHECK: [[S2_NEW2:%.*]] = call noundef ptr @_ZN2S2nwIS_EEPvSt13type_identityIT_EmSt11align_val_tR7Context({{.*}},{{.*}} 4,{{.*}} 128,{{.*}} [[S2_NEW2_CONTEXT:%.*]]) +// CHECK: call void @_ZN2S2dlIS_EEvSt13type_identityIT_EPvmSt11align_val_t({{.*}},{{.*}},{{.*}} 4,{{.*}} 4) +// CHECK: call void @_ZN2S2dlIS_EEvSt13type_identityIT_EPvmSt11align_val_tR7Context({{.*}}, {{.*}} [[S2_NEW1]],{{.*}} 4,{{.*}} 4,{{.*}} [[S2_NEW1_CONTEXT]]) +// CHECK: call void @_ZN2S2dlIS_EEvSt13type_identityIT_EPvmSt11align_val_tR7Context({{.*}}, {{.*}} [[S2_NEW2]],{{.*}} 4,{{.*}} 128,{{.*}} [[S2_NEW2_CONTEXT]]) + +extern "C" void test_s3(Context& Ctx) { + S3 *s3_1 = new S3; + delete s3_1; + S3 *s3_2 = new (std::align_val_t(128)) S3; + delete s3_2; +} + +// CHECK-LABEL: test_s3 +// CHECK: [[S3_NEW1:%.*]] = call noundef ptr @_ZN2S3nwIS_EEPvSt13type_identityIT_EmSt11align_val_t({{.*}},{{.*}} 4,{{.*}} 4) +// CHECK: call void @_ZN2S3dlIS_EEvSt13type_identityIT_EPvmSt11align_val_t({{.*}},{{.*}},{{.*}} 4,{{.*}} 4) +// CHECK: [[S3_NEW2:%.*]] = call noundef ptr @_ZN2S3nwIS_EEPvSt13type_identityIT_EmSt11align_val_t({{.*}},{{.*}} 4,{{.*}} 128) +// CHECK: call void @_ZN2S3dlIS_EEvSt13type_identityIT_EPvmSt11align_val_t({{.*}},{{.*}},{{.*}} 4,{{.*}} 4) +// CHECK: call void @_ZN2S3dlIS_EEvSt13type_identityIT_EPvmSt11align_val_t({{.*}},{{.*}}[[S3_NEW1]],{{.*}} 4,{{.*}} 4) +// CHECK: call void @_ZN2S3dlIS_EEvSt13type_identityIT_EPvmSt11align_val_t({{.*}},{{.*}}[[S3_NEW2]],{{.*}} 4,{{.*}} 128) diff --git a/clang/test/CodeGenCoroutines/coro-alloc-2.cpp b/clang/test/CodeGenCoroutines/coro-alloc-2.cpp index 9c60c32a5c54..460f862deaba 100644 --- a/clang/test/CodeGenCoroutines/coro-alloc-2.cpp +++ b/clang/test/CodeGenCoroutines/coro-alloc-2.cpp @@ -1,5 +1,7 @@ // Tests that we wouldn't generate an allocation call in global scope with (std::size_t, p0, ..., pn) // RUN: %clang_cc1 %s -std=c++20 -triple x86_64 -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s +// RUN: %clang_cc1 %s -std=c++26 -triple x86_64 -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s + #include "Inputs/coroutine.h" namespace std { diff --git a/clang/test/Modules/new-delete.cpp b/clang/test/Modules/new-delete.cpp index 585a242b2247..438b72738695 100644 --- a/clang/test/Modules/new-delete.cpp +++ b/clang/test/Modules/new-delete.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -fmodules -verify %s +// RUN: %clang_cc1 -fmodules -std=c++26 -verify %s // expected-no-diagnostics #pragma clang module build M diff --git a/clang/test/SemaCXX/cxx2a-destroying-delete.cpp b/clang/test/SemaCXX/cxx2a-destroying-delete.cpp index bf0a64a77385..27ea6663bae6 100644 --- a/clang/test/SemaCXX/cxx2a-destroying-delete.cpp +++ b/clang/test/SemaCXX/cxx2a-destroying-delete.cpp @@ -16,7 +16,7 @@ namespace std { 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 *'}} +void operator delete(A*, std::destroying_delete_t); // expected-error {{1st parameter of 'operator delete' must have type 'void *'}} struct A { void operator delete(A*, std::destroying_delete_t); @@ -27,7 +27,7 @@ struct A { // 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(struct X*, std::destroying_delete_t, std::size_t, ...); // expected-error {{1st parameter of destroying 'operator delete' must have type 'A *'}} void operator delete(void*, std::size_t); }; @@ -191,7 +191,7 @@ namespace delete_from_new { namespace GH96191 { struct S {}; struct T { - void operator delete(S) { } // expected-error {{first parameter of 'operator delete' must have type 'void *'}} + void operator delete(S) { } // expected-error {{1st parameter of 'operator delete' must have type 'void *'}} }; void foo(T *t) { delete t; } diff --git a/clang/test/SemaCXX/delete.cpp b/clang/test/SemaCXX/delete.cpp index 7d1f51cb218c..f97b45de5214 100644 --- a/clang/test/SemaCXX/delete.cpp +++ b/clang/test/SemaCXX/delete.cpp @@ -5,6 +5,10 @@ // RUN: %clang_cc1 -x c++-header -std=c++11 -emit-pch -o %t %S/delete-mismatch.h // RUN: %clang_cc1 -std=c++11 -include-pch %t -DWITH_PCH -verify %s -ast-dump +// Test with PCH and type aware allocators +// RUN: %clang_cc1 -x c++-header -std=c++11 -emit-pch -o %t.taa.pch %S/delete-mismatch.h +// RUN: %clang_cc1 -std=c++11 -include-pch %t.taa.pch -DWITH_PCH -verify %s -ast-dump + void f(int a[10][20]) { delete a; // expected-warning {{'delete' applied to a pointer-to-array type}} // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:9}:"[]" diff --git a/clang/test/SemaCXX/new-delete.cpp b/clang/test/SemaCXX/new-delete.cpp index fb4810ad673a..9bbee32c58c3 100644 --- a/clang/test/SemaCXX/new-delete.cpp +++ b/clang/test/SemaCXX/new-delete.cpp @@ -251,7 +251,7 @@ void loadEngineFor() { } template struct TBase { - void* operator new(T size, int); // expected-error {{'operator new' cannot take a dependent type as first parameter; use size_t}} + void* operator new(T size, int); // expected-error {{'operator new' cannot take a dependent type as its 1st parameter; use size_t}} }; TBase t1; @@ -466,7 +466,7 @@ namespace TemplateDestructors { namespace DeleteParam { struct X { - void operator delete(X*); // expected-error{{first parameter of 'operator delete' must have type 'void *'}} + void operator delete(X*); // expected-error{{1st parameter of 'operator delete' must have type 'void *'}} }; struct Y { diff --git a/clang/test/SemaCXX/type-aware-class-scoped-mismatched-constraints.cpp b/clang/test/SemaCXX/type-aware-class-scoped-mismatched-constraints.cpp new file mode 100644 index 000000000000..57e6d953c2ad --- /dev/null +++ b/clang/test/SemaCXX/type-aware-class-scoped-mismatched-constraints.cpp @@ -0,0 +1,261 @@ +// RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -std=c++26 -fexceptions -fcxx-exceptions -fsized-deallocation -faligned-allocation -Wno-non-c-typedef-for-linkage -DDEFAULT_DELETE=1 +// RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -std=c++26 -fexceptions -fcxx-exceptions -fno-sized-deallocation -faligned-allocation -Wno-non-c-typedef-for-linkage -DDEFAULT_DELETE=0 +// RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -std=c++26 -fexceptions -fcxx-exceptions -fsized-deallocation -fno-aligned-allocation -Wno-non-c-typedef-for-linkage -DDEFAULT_DELETE=1 +// RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -std=c++26 -fexceptions -fcxx-exceptions -fno-sized-deallocation -fno-aligned-allocation -Wno-non-c-typedef-for-linkage -DDEFAULT_DELETE=0 + +namespace std { + template struct type_identity {}; + enum class align_val_t : __SIZE_TYPE__ {}; +} + +using size_t = __SIZE_TYPE__; + +void *operator new(size_t); // #default_operator_new + +#if DEFAULT_DELETE==0 +void operator delete(void*) noexcept; // #default_operator_delete +#elif DEFAULT_DELETE==1 +void operator delete(void*, size_t) noexcept; // #default_operator_delete +#elif DEFAULT_DELETE==2 +void operator delete(void*, std::align_val_t) noexcept; // #default_operator_delete +#elif DEFAULT_DELETE==3 +void operator delete(void*, size_t, std::align_val_t) noexcept; // #default_operator_delete +#endif + +struct Invalid1 { + // expected-error@-1 {{declaration of type aware 'operator new' in 'Invalid1' must have matching type aware 'operator delete'}} + void *operator new(std::type_identity, size_t, std::align_val_t); + // expected-note@-1 {{unmatched type aware 'operator new' declared here}} +}; +struct Invalid2 { + // expected-error@-1 {{declaration of type aware 'operator new' in 'Invalid2' must have matching type aware 'operator delete'}} + template void *operator new(std::type_identity, size_t, std::align_val_t); // #Invalid2_new + // expected-note@-1 {{unmatched type aware 'operator new' declared here}} +}; +struct Invalid3 { + // expected-error@-1 {{declaration of type aware 'operator delete' in 'Invalid3' must have matching type aware 'operator new'}} + void operator delete(std::type_identity, void*, size_t, std::align_val_t); + // expected-note@-1 {{unmatched type aware 'operator delete' declared here}} +}; +struct Invalid4 { + // expected-error@-1 {{declaration of type aware 'operator delete' in 'Invalid4' must have matching type aware 'operator new'}} + template void operator delete(std::type_identity, void*, size_t, std::align_val_t); // #Invalid4_delete + // expected-note@-1 {{unmatched type aware 'operator delete' declared here}} +}; +struct Invalid5 { + // expected-error@-1 {{declaration of type aware 'operator new[]' in 'Invalid5' must have matching type aware 'operator delete[]'}} + void *operator new[](std::type_identity, size_t, std::align_val_t); + // expected-note@-1 {{unmatched type aware 'operator new[]' declared here}} +}; +struct Invalid6 { + // expected-error@-1 {{declaration of type aware 'operator new[]' in 'Invalid6' must have matching type aware 'operator delete[]'}} + template void *operator new[](std::type_identity, size_t, std::align_val_t); + // expected-note@-1 {{unmatched type aware 'operator new[]' declared here}} +}; +struct Invalid7 { + // expected-error@-1 {{declaration of type aware 'operator delete[]' in 'Invalid7' must have matching type aware 'operator new[]'}} + void operator delete[](std::type_identity, void*, size_t, std::align_val_t); + // expected-note@-1 {{unmatched type aware 'operator delete[]' declared here}} +}; +struct Invalid8 { + // expected-error@-1 {{declaration of type aware 'operator delete[]' in 'Invalid8' must have matching type aware 'operator new[]'}} + template void operator delete[](std::type_identity, void*, size_t, std::align_val_t); + // expected-note@-1 {{unmatched type aware 'operator delete[]' declared here}} +}; + +// Invalid9 and Invalid10 will ensure we report the correct owner for the +// resolved, but unmatched, new and delete +struct Invalid9: Invalid2 {}; +struct Invalid10: Invalid4 {}; +// Invalid11 inherits a "matching" new and delete pair (so no inheritance ambiguity) +// but the resolved operators are from different scopes +struct Invalid11 : Invalid2, Invalid4 {}; +struct Invalid12 : Invalid2, Invalid4 { + using Invalid2::operator new; + using Invalid4::operator delete; +}; + +struct TestClass1 { + void *operator new(std::type_identity, size_t, std::align_val_t); // #TestClass1_new + void operator delete(std::type_identity, void *, size_t, std::align_val_t); // #TestClass1_delete +}; + +struct TestClass2 { + void *operator new(std::type_identity, size_t, std::align_val_t); // #TestClass2_new + void operator delete(std::type_identity, void *, size_t, std::align_val_t); // #TestClass2_delete +}; + +void basic_tests() { + TestClass1 * tc1 = new TestClass1; + delete tc1; + // expected-error@-1 {{no suitable member 'operator delete' in 'TestClass1'}} + // expected-note@#TestClass1_delete {{member 'operator delete' declared here}} + TestClass2 * tc2 = new TestClass2; + // expected-error@-1 {{no matching function for call to 'operator new'}} + delete tc2; + Invalid9 * i9 = new Invalid9; + // expected-error@-1 {{type aware 'operator new' requires a matching type aware 'operator delete' to be declared in the same scope}} + // expected-note@#Invalid2_new {{type aware 'operator new' declared here in 'Invalid2'}} + delete i9; + Invalid10 * i10 = new Invalid10; + // expected-error@-1 {{type aware 'operator delete' requires a matching type aware 'operator new' to be declared in the same scope}} + // expected-note@#Invalid4_delete {{type aware 'operator delete' declared here in 'Invalid4'}} + // expected-note@#default_operator_new {{non-type aware 'operator new' declared here in the global namespace}} + delete i10; + Invalid11 * i11 = new Invalid11; + // expected-error@-1 {{type aware 'operator new' requires a matching type aware 'operator delete' to be declared in the same scope}} + // expected-note@#Invalid2_new {{type aware 'operator new' declared here in 'Invalid2'}} + // expected-note@#Invalid4_delete {{type aware 'operator delete' declared here in 'Invalid4'}} + delete i11; + Invalid12 * i12 = new Invalid12; + // expected-error@-1 {{type aware 'operator new' requires a matching type aware 'operator delete' to be declared in the same scope}} + // expected-note@#Invalid2_new {{type aware 'operator new' declared here in 'Invalid2'}} + // expected-note@#Invalid4_delete {{type aware 'operator delete' declared here in 'Invalid4'}} + delete i12; +} + +struct Baseclass1 { + void *operator new(std::type_identity, size_t, std::align_val_t); + void operator delete(std::type_identity, void *, size_t, std::align_val_t); // #Baseclass1_delete +}; + +struct Subclass1 : Baseclass1 { + Subclass1(); +}; + +struct Baseclass2 { + template void *operator new(std::type_identity, size_t, std::align_val_t); + void operator delete(std::type_identity, void *, size_t, std::align_val_t); // #Baseclass2_delete +}; + +struct Subclass2 : Baseclass2 { + Subclass2(); +}; + +struct Baseclass3 { + template void *operator new(std::type_identity, size_t, std::align_val_t); + template void operator delete(std::type_identity, void *, size_t, std::align_val_t); +}; + +struct Subclass3_1 : Baseclass3 { + Subclass3_1(); + void *operator new(std::type_identity, size_t, std::align_val_t); + template void operator delete(std::type_identity, void *, size_t, std::align_val_t); +}; + +struct Subclass3_2 : Baseclass3 { + Subclass3_2(); + template void *operator new(std::type_identity, size_t, std::align_val_t); + void operator delete(std::type_identity, void *, size_t, std::align_val_t); // #Subclass3_2_delete +}; + + +void test_subclasses() { + Subclass1 * sc1 = new Subclass1; + // expected-error@-1 {{no matching function for call to 'operator new'}} + delete sc1; + // expected-error@-1 {{no suitable member 'operator delete' in 'Subclass1'}} + // expected-note@#Baseclass1_delete {{member 'operator delete' declared here}} + Subclass2 * sc2 = new Subclass2; + delete sc2; + // expected-error@-1 {{no suitable member 'operator delete' in 'Subclass2'}} + // expected-note@#Baseclass2_delete {{member 'operator delete' declared here}} + Subclass3_1 * sc3_1 = new Subclass3_1; + // expected-error@-1 {{no matching function for call to 'operator new'}} + delete sc3_1; + Subclass3_2 * sc3_2 = new Subclass3_2; + delete sc3_2; + // expected-error@-1 {{no suitable member 'operator delete' in 'Subclass3_2'}} + // expected-note@#Subclass3_2_delete {{member 'operator delete' declared here}} +} + +template constexpr bool same_type_v = false; +template constexpr bool same_type_v = true; + +template struct InvalidConstrainedOperator { + template void *operator new(std::type_identity, size_t, std::align_val_t); + template requires (same_type_v) void operator delete(std::type_identity, void *, size_t, std::align_val_t); // #InvalidConstrainedOperator_delete +}; + +struct Context; +template struct InvalidConstrainedCleanup { + template void *operator new(std::type_identity, size_t, std::align_val_t, Context&); // #InvalidConstrainedCleanup_placement_new + template requires (same_type_v) void operator delete(std::type_identity, void *, size_t, std::align_val_t, Context&); // #InvalidConstrainedCleanup_delete + template void operator delete(std::type_identity, void *, size_t, std::align_val_t); +}; + +void test_incompatible_constrained_operators(Context &Ctx) { + InvalidConstrainedOperator *ico1 = new InvalidConstrainedOperator; + delete ico1; + InvalidConstrainedOperator *ico2 = new InvalidConstrainedOperator; + delete ico2; + // expected-error@-1 {{no suitable member 'operator delete' in 'InvalidConstrainedOperator'}} + // expected-note@#InvalidConstrainedOperator_delete {{member 'operator delete' declared here}} + InvalidConstrainedCleanup *icc1 = new (Ctx) InvalidConstrainedCleanup; + delete icc1; + InvalidConstrainedCleanup *icc2 = new (Ctx) InvalidConstrainedCleanup; + // expected-error@-1 {{type aware 'operator new' requires a matching type aware placement 'operator delete' to be declared in the same scope}} + // expected-note@#InvalidConstrainedCleanup_placement_new {{type aware 'operator new' declared here in 'InvalidConstrainedCleanup'}} + delete icc2; +} + +typedef struct { + // expected-error@-1 {{declaration of type aware 'operator new' in '(unnamed struct}} + // expected-note@#AnonymousClass1_new {{unmatched type aware 'operator new' declared here}} + template void *operator new(std::type_identity, size_t, std::align_val_t); // #AnonymousClass1_new +} AnonymousClass1; + +typedef struct { + // expected-error@-1 {{declaration of type aware 'operator delete' in '(unnamed struct}} + template void operator delete(std::type_identity, void*, size_t, std::align_val_t); // #AnonymousClass2_delete +} AnonymousClass2; + +typedef struct { + template void *operator new(std::type_identity, size_t, std::align_val_t); + template void operator delete(std::type_identity, void*, size_t, std::align_val_t); +} AnonymousClass3; + +using AnonymousClass4 = struct {}; +using AnonymousClass5 = struct {}; +using AnonymousClass6 = struct {}; +using AnonymousClass7 = struct { + // expected-error@-1 {{declaration of type aware 'operator new' in}} + // expected-note@#AnonymousClass7_new {{unmatched type aware 'operator new' declared here}} + template void *operator new(std::type_identity, size_t, std::align_val_t, Context&); // #AnonymousClass7_new +}; + + +void *operator new(std::type_identity, size_t, std::align_val_t); // #AnonymousClass4_new +void operator delete(std::type_identity, void*, size_t, std::align_val_t); // #AnonymousClass5_delete +void *operator new(std::type_identity, size_t, std::align_val_t, Context&); // #AnonymousClass6_placement_new +void operator delete(std::type_identity, void*, size_t, std::align_val_t); +void test_anonymous_types(Context &Ctx) { + AnonymousClass1 *ac1 = new AnonymousClass1; + // expected-error@-1 {{type aware 'operator new' requires a matching type aware 'operator delete' to be declared in the same scope}} + // expected-note@#AnonymousClass1_new {{type aware 'operator new' declared here in 'AnonymousClass1'}} + + delete ac1; + AnonymousClass2 *ac2 = new AnonymousClass2; + // expected-error@-1 {{type aware 'operator delete' requires a matching type aware 'operator new' to be declared in the same scope}} + // expected-note@#AnonymousClass2_delete {{unmatched type aware 'operator delete' declared here}} + // expected-note@#AnonymousClass2_delete {{type aware 'operator delete' declared here in 'AnonymousClass2'}} + // expected-note@#default_operator_new {{non-type aware 'operator new' declared here in the global namespace}} + + delete ac2; + AnonymousClass3 *ac3 = new AnonymousClass3; + delete ac3; + AnonymousClass4 *ac4 = new AnonymousClass4; + // expected-error@-1 {{type aware 'operator new' requires a matching type aware 'operator delete' to be declared in the same scope}} + // expected-note@#AnonymousClass4_new {{type aware 'operator new' declared here in the global namespace}} + delete ac4; + AnonymousClass5 *ac5 = new AnonymousClass5; + // expected-error@-1 {{type aware 'operator delete' requires a matching type aware 'operator new' to be declared in the same scope}} + // expected-note@#AnonymousClass5_delete {{type aware 'operator delete' declared here}} + // expected-note@#default_operator_new {{non-type aware 'operator new' declared here in the global namespace}} + delete ac5; + AnonymousClass6 *ac6 = new (Ctx) AnonymousClass6; + // expected-error@-1 {{type aware 'operator new' requires a matching type aware placement 'operator delete' to be declared in the same scope}} + // expected-note@#AnonymousClass6_placement_new {{type aware 'operator new' declared here in the global namespace}} + // expected-note@#default_operator_delete {{non-type aware 'operator delete' declared here in the global namespace}} + delete ac6; +} \ No newline at end of file diff --git a/clang/test/SemaCXX/type-aware-coroutines.cpp b/clang/test/SemaCXX/type-aware-coroutines.cpp new file mode 100644 index 000000000000..a54d37c47dbd --- /dev/null +++ b/clang/test/SemaCXX/type-aware-coroutines.cpp @@ -0,0 +1,145 @@ +// RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -std=c++26 -fcoroutines -fexceptions -Wall -Wpedantic + + +#include "Inputs/std-coroutine.h" + +namespace std { + template struct type_identity { + typedef T type; + }; + typedef __SIZE_TYPE__ size_t; + enum class align_val_t : size_t {}; +} + +struct Allocator {}; + +struct resumable { + struct promise_type { + void *operator new(std::type_identity, std::size_t sz, std::align_val_t, int); // #resumable_tan1 + void *operator new(std::type_identity, std::size_t sz, std::align_val_t, float); // #resumable_tan2 + void operator delete(std::type_identity, void *, std::size_t sz, std::align_val_t); // #resumable_tad1 + template void operator delete(std::type_identity, void *, std::size_t sz, std::align_val_t) = delete; // #resumable_tad2 + + resumable get_return_object() { return {}; } + auto initial_suspend() { return std::suspend_always(); } + auto final_suspend() noexcept { return std::suspend_always(); } + void unhandled_exception() {} + void return_void(){}; + std::suspend_always yield_value(int i); + }; +}; + +struct resumable2 { + struct promise_type { + template void *operator new(std::type_identity, std::size_t sz, std::align_val_t, Args...); // #resumable2_tan1 + void operator delete(std::type_identity, void *, std::size_t sz, std::align_val_t); // #resumable2_tad2 + + resumable2 get_return_object() { return {}; } + auto initial_suspend() { return std::suspend_always(); } + auto final_suspend() noexcept { return std::suspend_always(); } + void unhandled_exception() {} + void return_void(){}; + std::suspend_always yield_value(int i); + }; +}; + + +struct resumable3 { + struct promise_type { + // expected-error@-1 {{declaration of type aware 'operator new' in 'resumable3::promise_type' must have matching type aware 'operator delete'}} + // expected-note@#resumable3_tan {{unmatched type aware 'operator new' declared here}} + void *operator new(std::size_t sz, float); + void *operator new(std::type_identity, std::size_t sz, std::align_val_t, float); // #resumable3_tan + void operator delete(void *); + + resumable3 get_return_object() { return {}; } + auto initial_suspend() { return std::suspend_always(); } + auto final_suspend() noexcept { return std::suspend_always(); } + void unhandled_exception() {} + void return_void(){}; + std::suspend_always yield_value(int i); + }; +}; +struct resumable4 { + struct promise_type { + // expected-error@-1 {{declaration of type aware 'operator delete' in 'resumable4::promise_type' must have matching type aware 'operator new'}} + // expected-note@#resumable4_tad {{unmatched type aware 'operator delete' declared here}} + void *operator new(std::size_t sz, float); + template void operator delete(std::type_identity, void *, std::size_t, std::align_val_t); // #resumable4_tad + + resumable4 get_return_object() { return {}; } + auto initial_suspend() { return std::suspend_always(); } + auto final_suspend() noexcept { return std::suspend_always(); } + void unhandled_exception() {} + void return_void(){}; + std::suspend_always yield_value(int i); + }; +}; +struct resumable5 { + struct promise_type { + // expected-error@-1 {{declaration of type aware 'operator delete' in 'resumable5::promise_type' must have matching type aware 'operator new'}} + // expected-note@#resumable5_tad {{unmatched type aware 'operator delete' declared here}} + void *operator new(std::size_t sz, float); + void operator delete(void *); + template void operator delete(std::type_identity, void *, std::size_t, std::align_val_t); // #resumable5_tad + + resumable5 get_return_object() { return {}; } + auto initial_suspend() { return std::suspend_always(); } + auto final_suspend() noexcept { return std::suspend_always(); } + void unhandled_exception() {} + void return_void(){}; + std::suspend_always yield_value(int i); + }; +}; + +resumable f1(int) { + // expected-error@-1 {{'operator new' provided by 'std::coroutine_traits::promise_type' (aka 'resumable::promise_type') is not usable with the function signature of 'f1'}} + // expected-note@-2 {{type aware 'operator new' will not be used for coroutine allocation}} + // expected-note@#resumable_tan1 {{type aware 'operator new' declared here}} + // expected-note@#resumable_tan2 {{type aware 'operator new' declared here}} + co_return; +} + +resumable f2(float) { + // expected-error@-1 {{'operator new' provided by 'std::coroutine_traits::promise_type' (aka 'resumable::promise_type') is not usable with the function signature of 'f2'}} + // expected-note@-2 {{type aware 'operator new' will not be used for coroutine allocation}} + // expected-note@#resumable_tan1 {{type aware 'operator new' declared here}} + // expected-note@#resumable_tan2 {{type aware 'operator new' declared here}} + co_return; +} + +resumable2 f3(int, float, const char*, Allocator) { + // expected-error@-1 {{'operator new' provided by 'std::coroutine_traits::promise_type' (aka 'resumable2::promise_type') is not usable with the function signature of 'f3'}} + // expected-note@-2 {{type aware 'operator new' will not be used for coroutine allocation}} + // expected-note@#resumable2_tan1 {{type aware 'operator new' declared here}} + co_yield 1; + co_return; +} + +resumable f4(int n = 10) { + // expected-error@-1 {{'operator new' provided by 'std::coroutine_traits::promise_type' (aka 'resumable::promise_type') is not usable with the function signature of 'f4'}} + // expected-note@-2 {{type aware 'operator new' will not be used for coroutine allocation}} + // expected-note@#resumable_tan1 {{type aware 'operator new' declared here}} + // expected-note@#resumable_tan2 {{type aware 'operator new' declared here}} + for (int i = 0; i < n; i++) + co_yield i; +} +resumable3 f5(float) { + // expected-warning@-1 {{type aware 'operator new' will not be used for coroutine allocation}} + // expected-note@#resumable3_tan {{type aware 'operator new' declared here}} + co_return; +} + +resumable4 f6(float) { + // expected-error@-1 {{no suitable member 'operator delete' in 'promise_type'}} + // expected-warning@-2 {{type aware 'operator delete' will not be used for coroutine allocation}} + // expected-note@#resumable4_tad {{type aware 'operator delete' declared here}} + // expected-note@#resumable4_tad {{member 'operator delete' declared here}} + co_return; +} + +resumable5 f7(float) { + // expected-warning@-1 {{type aware 'operator delete' will not be used for coroutine allocation}} + // expected-note@#resumable5_tad {{type aware 'operator delete' declared here}} + co_return; +} diff --git a/clang/test/SemaCXX/type-aware-new-constexpr.cpp b/clang/test/SemaCXX/type-aware-new-constexpr.cpp new file mode 100644 index 000000000000..105610cd103e --- /dev/null +++ b/clang/test/SemaCXX/type-aware-new-constexpr.cpp @@ -0,0 +1,127 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -fsized-deallocation -faligned-allocation +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -fno-sized-deallocation -faligned-allocation +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -fsized-deallocation -fno-aligned-allocation +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -fno-sized-deallocation -fno-aligned-allocation +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -fsized-deallocation -faligned-allocation -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -fno-sized-deallocation -faligned-allocation -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -fsized-deallocation -fno-aligned-allocation -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -fno-sized-deallocation -fno-aligned-allocation -fexperimental-new-constant-interpreter + + +namespace std { + template struct type_identity {}; + enum class align_val_t : __SIZE_TYPE__ {}; + struct destroying_delete_t { explicit destroying_delete_t() = default; }; +} + +using size_t = __SIZE_TYPE__; + +struct S1 { + constexpr explicit S1() : i(5) { } + const int i; +}; + +void *operator new(std::type_identity, size_t sz, std::align_val_t); // #1 +void operator delete(std::type_identity, void* ptr, size_t sz, std::align_val_t); // #2 + +constexpr int ensure_consteval_skips_typed_allocators() { + // Verify we dont resolve typed allocators in const contexts + auto * s = new S1(); + auto result = s->i; + delete s; + return result; +}; + +struct S2 { + constexpr explicit S2() : i(5) { } + const int i; +}; + +void *operator new(std::type_identity, size_t sz, std::align_val_t) = delete; // #3 +void operator delete(std::type_identity, void* ptr, size_t sz, std::align_val_t) = delete; // #4 + +constexpr int ensure_constexpr_retains_types_at_runtime() { + // Verify we dont resolve typed allocators in const contexts + S2 *s = new S2(); + // expected-error@-1 {{call to deleted function 'operator new'}} + // expected-note@#1 {{candidate function not viable: no known conversion from 'type_identity' to 'type_identity' for 1st argument}} + // expected-note@#3 {{candidate function has been explicitly deleted}} + auto result = s->i; + delete s; + // expected-error@-1 {{attempt to use a deleted function}} + // expected-note@#4 {{'operator delete' has been explicitly marked deleted here}} + return result; +}; + + +struct S3 { + constexpr explicit S3() : i(5) { } + const int i; + template void* operator new(std::type_identity, size_t sz, std::align_val_t) = delete; // #5 + template void operator delete(std::type_identity, void *, size_t sz, std::align_val_t) = delete; // #6 +}; + +template void* operator new(std::type_identity, size_t sz, std::align_val_t) = delete; // #7 +template void operator delete(std::type_identity, void *, size_t sz, std::align_val_t) = delete; // #8 + +constexpr int constexpr_vs_inclass_operators() { + S3 *s; + if consteval { + s = ::new S3(); + // expected-error@-1 {{call to deleted function 'operator new'}} + // expected-note@#1 {{candidate function not viable: no known conversion from 'type_identity' to 'type_identity' for 1st argument}} + // expected-note@#3 {{candidate function not viable: no known conversion from 'type_identity' to 'type_identity' for 1st argument}} + // expected-note@#7 {{candidate function [with T = S3] has been explicitly deleted}} + } else { + s = new S3(); + // expected-error@-1 {{call to deleted function 'operator new'}} + // expected-note@#5 {{candidate function [with T = S3] has been explicitly deleted}} + } + auto result = s->i; + if consteval { + ::delete s; + // expected-error@-1 {{attempt to use a deleted function}} + // expected-note@#8 {{'operator delete' has been explicitly marked deleted here}} + } else { + delete s; + // expected-error@-1 {{attempt to use a deleted function}} + // expected-note@#6 {{'operator delete' has been explicitly marked deleted here}} + } + return result; +}; + +// Test a variety of valid constant evaluation paths +struct S4 { + int i = 1; + constexpr S4() __attribute__((noinline)) {} +}; + +void* operator new(std::type_identity, size_t sz, std::align_val_t); +void operator delete(std::type_identity, void *, size_t sz, std::align_val_t); + +constexpr int do_dynamic_alloc(int n) { + S4* s = new S4; + int result = n * s->i; + delete s; + return result; +} + +template struct Tag { +}; + +static constexpr int force_do_dynamic_alloc = do_dynamic_alloc(5); + +constexpr int test_consteval_calling_constexpr(int i) { + if consteval { + return do_dynamic_alloc(2 * i); + } + return do_dynamic_alloc(3 * i); +} + +int test_consteval(int n, Tag, Tag) { + static const int t1 = test_consteval_calling_constexpr(4); + static const int t2 = do_dynamic_alloc(5); + int t3 = test_consteval_calling_constexpr(6); + int t4 = do_dynamic_alloc(7); + return t1 * t2 * t3 * t4; +} diff --git a/clang/test/SemaCXX/type-aware-new-delete-arrays.cpp b/clang/test/SemaCXX/type-aware-new-delete-arrays.cpp new file mode 100644 index 000000000000..b1c73236476c --- /dev/null +++ b/clang/test/SemaCXX/type-aware-new-delete-arrays.cpp @@ -0,0 +1,58 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -fsized-deallocation -faligned-allocation -Wall -Wpedantic +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -fno-sized-deallocation -faligned-allocation -Wall -Wpedantic +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -fno-sized-deallocation -fno-aligned-allocation -Wall -Wpedantic +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -fsized-deallocation -fno-aligned-allocation -Wall -Wpedantic + +namespace std { + template struct type_identity {}; + enum class align_val_t : __SIZE_TYPE__ {}; + struct destroying_delete_t { explicit destroying_delete_t() = default; }; +} + +using size_t = __SIZE_TYPE__; + +struct BasicTypeAwareArrayAllocator { + template void *operator new[](std::type_identity, size_t, std::align_val_t) = delete; // #1 + template void operator delete[](std::type_identity, void*, size_t, std::align_val_t) = delete; // #2 +}; +void *operator new[](std::type_identity, size_t, std::align_val_t); +void operator delete[](std::type_identity, void*, size_t, std::align_val_t); + +struct BasicTypeAwareNonArrayAllocator { + template void *operator new[](std::type_identity, size_t, std::align_val_t); + template void operator delete[](std::type_identity, void*, size_t, std::align_val_t); + void *operator new(size_t) = delete; + void operator delete(void*) = delete; +}; + +struct WorkingTypeAwareAllocator { + template void *operator new[](std::type_identity, size_t, std::align_val_t); + template void operator delete[](std::type_identity, void*, size_t, std::align_val_t); +}; + +void *operator new[](std::type_identity, size_t, std::align_val_t) = delete; +void operator delete[](std::type_identity, void*, size_t, std::align_val_t) = delete; + + +void test() { + BasicTypeAwareArrayAllocator *A0 = new BasicTypeAwareArrayAllocator[10]; + // expected-error@-1 {{call to deleted function 'operator new[]'}} + // expected-note@#1 {{candidate function [with T = BasicTypeAwareArrayAllocator] has been explicitly deleted}} + delete [] A0; + // expected-error@-1 {{attempt to use a deleted function}} + // expected-note@#2 {{'operator delete[]' has been explicitly marked deleted here}} + + BasicTypeAwareNonArrayAllocator *A1 = new BasicTypeAwareNonArrayAllocator[10]; + delete [] A1; + + WorkingTypeAwareAllocator *A2 = new WorkingTypeAwareAllocator[10]; + // expected-note@-1 {{allocated with 'new[]' here}} + delete A2; + // expected-warning@-1 {{'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'?}} + + WorkingTypeAwareAllocator *A3 = new WorkingTypeAwareAllocator; + // expected-note@-1 {{allocated with 'new' here}} + delete [] A3; + // expected-warning@-1 {{'delete[]' applied to a pointer that was allocated with 'new'; did you mean 'delete'?}} + +} diff --git a/clang/test/SemaCXX/type-aware-new-delete-basic-free-declarations.cpp b/clang/test/SemaCXX/type-aware-new-delete-basic-free-declarations.cpp new file mode 100644 index 000000000000..c85b92718479 --- /dev/null +++ b/clang/test/SemaCXX/type-aware-new-delete-basic-free-declarations.cpp @@ -0,0 +1,119 @@ +// RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -std=c++26 -fsized-deallocation -faligned-allocation +// RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -std=c++26 -fno-sized-deallocation -faligned-allocation +// RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -std=c++26 -fno-sized-deallocation -fno-aligned-allocation +// RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -std=c++26 -fsized-deallocation -fno-aligned-allocation + +namespace std { + template struct type_identity {}; + enum class align_val_t : __SIZE_TYPE__ {}; + struct destroying_delete_t { explicit destroying_delete_t() = default; }; +} + +using size_t = __SIZE_TYPE__; + +struct TestType {}; +template struct TemplateTestType {}; + +// Valid free declarations +void *operator new(std::type_identity, size_t, std::align_val_t); // #1 +void *operator new(std::type_identity, size_t, std::align_val_t, TestType&); // #2 +template void *operator new(std::type_identity, size_t, std::align_val_t); // #3 +template void *operator new(std::type_identity, size_t, std::align_val_t, TestType&); // #4 +template void *operator new(std::type_identity>, size_t, std::align_val_t, TestType&); // #5 +template void *operator new(std::type_identity, size_t, std::align_val_t, TemplateTestType&); // #6 +template