https://github.com/ojhunt updated https://github.com/llvm/llvm-project/pull/211482
>From 9213eed76e07c0f7931675bcf4e8f4865a422e2d Mon Sep 17 00:00:00 2001 From: Oliver Hunt <[email protected]> Date: Thu, 23 Jul 2026 00:20:07 -0700 Subject: [PATCH 1/5] [clang] Simplify the overload resolution logic for operator new and new[] This PR replaces the current spec-equivalent argument list mutation with direct iteration of the correctly ordered set of argument lists for a given allocation. This adds a bit of architectural work around the argument list construction but the overall effect is substantially simplified search of the overload candidates --- clang/include/clang/Sema/Sema.h | 67 ++- clang/lib/Sema/SemaCoroutine.cpp | 11 +- clang/lib/Sema/SemaExprCXX.cpp | 477 ++++++++++-------- clang/lib/Sema/SemaOverload.cpp | 2 +- .../SemaCXX/microsoft-new-array-fallback.cpp | 14 + .../type-aware-new-invalid-type-identity.cpp | 15 +- 6 files changed, 378 insertions(+), 208 deletions(-) create mode 100644 clang/test/SemaCXX/microsoft-new-array-fallback.cpp diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index d43b6954196a2..292c53f460826 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -8651,14 +8651,52 @@ class Sema final : public SemaBase { bool CheckAllocatedType(QualType AllocType, SourceLocation Loc, SourceRange R); + struct ImplicitAllocationArguments { + friend Sema; + + ArrayRef<Expr *> getImplicitArguments() const { + return ArrayRef(ImplicitArguments, ArgumentCount); + } + + Expr *getAlignmentArgument() const { + if (PassAlignment == AlignedAllocationMode::Yes) + return ImplicitArguments[ArgumentCount - 1]; + return nullptr; + } + + void updateLookupForMSVCCompatibility(Sema &, LookupResult &); + TypeAwareAllocationMode PassTypeIdentity; + AlignedAllocationMode PassAlignment; + + private: + ImplicitAllocationArguments(Sema &SemaRef, Expr *TypeIdentityArg, + Expr *SizeArg, Expr *AlignArg, + bool IsMSVCCompatibilityFallback); + + // Type-identity, size, and alignment + static constexpr unsigned MaxImplicitArguments = 3; + bool IsMSVCCompatibilityFallback; + + unsigned ArgumentCount; + Expr *ImplicitArguments[MaxImplicitArguments]; + }; + + struct AllocationArgumentSet { + bool TypeAwareViable; + SmallVector<ImplicitAllocationArguments, 3> Candidates; + }; + +private: + struct ResolvedAllocation; + +public: /// Finds the overloads of operator new and delete that are appropriate /// for the allocation. - bool FindAllocationFunctions( + std::optional<ResolvedAllocation> FindAllocationFunctions( SourceLocation StartLoc, SourceRange Range, AllocationFunctionScope NewScope, AllocationFunctionScope DeleteScope, - QualType AllocType, bool IsArray, ImplicitAllocationParameters &IAP, - MultiExprArg PlaceArgs, FunctionDecl *&OperatorNew, - FunctionDecl *&OperatorDelete, bool Diagnose = true); + QualType AllocType, bool IsArray, const ImplicitAllocationParameters &IAP, + MultiExprArg PlaceArgs, bool Diagnose = true); /// DeclareGlobalNewDelete - Declare the global forms of operator new and /// delete. These are: @@ -8962,6 +9000,25 @@ class Sema final : public SemaBase { void AnalyzeDeleteExprMismatch(FieldDecl *Field, SourceLocation DeleteLoc, bool DeleteWasArrayForm); + struct ResolvedAllocation { + FunctionDecl *OperatorNew; + FunctionDecl *OperatorDelete; + ImplicitAllocationParameters IAP; + // type-identity, size, alignment, nothrow or other single placement + // parameter + SmallVector<Expr *, 4> Arguments; + }; + + std::optional<AllocationArgumentSet> + resolveAllocationArguments(LookupResult &R, + const ImplicitAllocationParameters &, + ArrayRef<Expr *> PlacementArguments); + bool getTypeIdentityArgument(QualType Type, SourceLocation, Expr **FoundExpr); + + Expr *AllocationSizeExpr = nullptr; + Expr *AllocationAlignmentExpr = nullptr; + llvm::DenseMap<QualType, Expr *> AllocationTypeIdentityArguments; + ///@} // @@ -10371,7 +10428,7 @@ class Sema final : public SemaBase { void DiagnoseUseOfDeletedFunction(SourceLocation Loc, SourceRange Range, DeclarationName Name, OverloadCandidateSet &CandidateSet, - FunctionDecl *Fn, MultiExprArg Args, + FunctionDecl *Fn, ArrayRef<Expr *> Args, bool IsMember = false); ExprResult InitializeExplicitObjectArgument(Sema &S, Expr *Obj, diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp index 7f9b1d642cf9d..4e8dbec4f35d1 100644 --- a/clang/lib/Sema/SemaCoroutine.cpp +++ b/clang/lib/Sema/SemaCoroutine.cpp @@ -1478,13 +1478,16 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() { IAP = ImplicitAllocationParameters( alignedAllocationModeFromBool(ShouldUseAlignedAlloc)); - FunctionDecl *UnusedResult = nullptr; - S.FindAllocationFunctions( + auto FoundAllocations = S.FindAllocationFunctions( Loc, SourceRange(), NewScope, /*DeleteScope=*/AllocationFunctionScope::Both, PromiseType, /*isArray=*/false, IAP, - WithoutPlacementArgs ? MultiExprArg{} : PlacementArgs, OperatorNew, - UnusedResult, /*Diagnose=*/false); + WithoutPlacementArgs ? MultiExprArg{} : PlacementArgs, + /*Diagnose=*/false); + if (FoundAllocations) { + IAP = FoundAllocations->IAP; + OperatorNew = FoundAllocations->OperatorNew; + } assert(!OperatorNew || !OperatorNew->isTypeAwareOperatorNewOrDelete()); }; diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 538604aa2e64b..f7c22c99968c6 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -2438,6 +2438,7 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, FunctionDecl *OperatorNew = nullptr; FunctionDecl *OperatorDelete = nullptr; + SmallVector<Expr *, 4> SelectedAllocationArgs; unsigned Alignment = AllocType->isDependentType() ? 0 : Context.getTypeAlign(AllocType); unsigned NewAlignment = Context.getTargetInfo().getNewAlign(); @@ -2454,13 +2455,19 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, SourceRange AllocationParameterRange = Range; if (PlacementLParen.isValid() && PlacementRParen.isValid()) AllocationParameterRange = SourceRange(PlacementLParen, PlacementRParen); - if (!AllocType->isDependentType() && - !Expr::hasAnyTypeDependentArguments(PlacementArgs) && - FindAllocationFunctions(StartLoc, AllocationParameterRange, Scope, Scope, - AllocType, ArraySize.has_value(), IAP, - PlacementArgs, OperatorNew, OperatorDelete)) - return ExprError(); + if (!AllocType->isDependentType() && + !Expr::hasAnyTypeDependentArguments(PlacementArgs)) { + auto FoundAllocation = FindAllocationFunctions( + StartLoc, AllocationParameterRange, Scope, Scope, AllocType, + ArraySize.has_value(), IAP, PlacementArgs); + if (!FoundAllocation) + return ExprError(); + IAP = FoundAllocation->IAP; + OperatorNew = FoundAllocation->OperatorNew; + OperatorDelete = FoundAllocation->OperatorDelete; + SelectedAllocationArgs = std::move(FoundAllocation->Arguments); + } // If this is an array allocation, compute whether the usual array // deallocation function for the type has a size_t parameter. bool UsualArrayDeleteWantsSize = false; @@ -2479,13 +2486,8 @@ 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 = 1; - if (isTypeAwareAllocation(IAP.PassTypeIdentity)) { - assert(OperatorNew->isTypeAwareOperatorNewOrDelete()); - NumImplicitArgs++; - } - if (isAlignedAllocation(IAP.PassAlignment)) - NumImplicitArgs++; + unsigned NumImplicitArgs = + SelectedAllocationArgs.size() - PlacementArgs.size(); if (GatherArgumentsForCall(AllocationParameterRange.getBegin(), OperatorNew, Proto, NumImplicitArgs, PlacementArgs, AllPlaceArgs, CallType)) @@ -2787,38 +2789,40 @@ static void diagnoseNoViableFunctionForAllocationOverloadResolution( Candidates.NoteCandidates(S, Args, Cands, "", R.getNameLoc()); } -enum class ResolveMode { Typed, Untyped }; -static bool resolveAllocationOverloadInterior( - Sema &S, LookupResult &R, SourceRange Range, ResolveMode Mode, - SmallVectorImpl<Expr *> &Args, AlignedAllocationMode &PassAlignment, - FunctionDecl *&Operator, OverloadCandidateSet *AlignedCandidates, - Expr *AlignArg, bool Diagnose) { - unsigned NonTypeArgumentOffset = 0; - if (Mode == ResolveMode::Typed) { - ++NonTypeArgumentOffset; - } +using ImplicitAllocationArguments = Sema::ImplicitAllocationArguments; +using AllocationArgumentSet = Sema::AllocationArgumentSet; - OverloadCandidateSet Candidates(R.getNameLoc(), - OverloadCandidateSet::CSK_Normal); +enum class AllocatorResolveResult { Success, Retry, Error }; +static AllocatorResolveResult +resolveAllocationOverload(Sema &S, LookupResult &R, SourceRange Range, + ImplicitAllocationArguments &AllocationArgs, + ArrayRef<Expr *> TrialArguments, + FunctionDecl *&Operator, + OverloadCandidateSet &Candidates, bool Diagnose) { + AllocationArgs.updateLookupForMSVCCompatibility(S, R); + + bool ArgumentListIsTypeAware = + isTypeAwareAllocation(AllocationArgs.PassTypeIdentity); for (LookupResult::iterator Alloc = R.begin(), AllocEnd = R.end(); Alloc != AllocEnd; ++Alloc) { // 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)) + bool CandidateIsTypeAware = + D->getAsFunction()->isTypeAwareOperatorNewOrDelete(); + if (CandidateIsTypeAware != ArgumentListIsTypeAware) continue; if (FunctionTemplateDecl *FnTemplate = dyn_cast<FunctionTemplateDecl>(D)) { S.AddTemplateOverloadCandidate(FnTemplate, Alloc.getPair(), - /*ExplicitTemplateArgs=*/nullptr, Args, - Candidates, + /*ExplicitTemplateArgs=*/nullptr, + TrialArguments, Candidates, /*SuppressUserConversions=*/false); continue; } FunctionDecl *Fn = cast<FunctionDecl>(D); - S.AddOverloadCandidate(Fn, Alloc.getPair(), Args, Candidates, + S.AddOverloadCandidate(Fn, Alloc.getPair(), TrialArguments, Candidates, /*SuppressUserConversions=*/false); } @@ -2826,57 +2830,17 @@ static bool resolveAllocationOverloadInterior( OverloadCandidateSet::iterator Best; switch (Candidates.BestViableFunction(S, R.getNameLoc(), Best)) { case OR_Success: { - // Got one! FunctionDecl *FnDecl = Best->Function; if (S.CheckAllocationAccess(R.getNameLoc(), Range, R.getNamingClass(), Best->FoundDecl) == Sema::AR_inaccessible) - return true; + return AllocatorResolveResult::Error; Operator = FnDecl; - return false; + return AllocatorResolveResult::Success; } case OR_No_Viable_Function: - // C++17 [expr.new]p13: - // 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 (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 - // if operator new[] cannot be found. Also, MSVC will leak by not - // generating a call to operator delete or operator delete[], but we - // will not replicate that bug. - // 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 && 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 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) - diagnoseNoViableFunctionForAllocationOverloadResolution( - S, R, Range, Args, Candidates, AlignedCandidates, AlignArg); - return true; + return AllocatorResolveResult::Retry; case OR_Ambiguous: if (Diagnose) { @@ -2884,15 +2848,16 @@ static bool resolveAllocationOverloadInterior( PartialDiagnosticAt(R.getNameLoc(), S.PDiag(diag::err_ovl_ambiguous_call) << R.getLookupName() << Range), - S, OCD_AmbiguousCandidates, Args); + S, OCD_AmbiguousCandidates, TrialArguments); } - return true; + return AllocatorResolveResult::Error; case OR_Deleted: { if (Diagnose) S.DiagnoseUseOfDeletedFunction(R.getNameLoc(), Range, R.getLookupName(), - Candidates, Best->Function, Args); - return true; + Candidates, Best->Function, + TrialArguments); + return AllocatorResolveResult::Error; } } llvm_unreachable("Unreachable, bad result from BestViableFunction"); @@ -2918,55 +2883,184 @@ static void LookupGlobalDeallocationFunctions(Sema &S, SourceLocation Loc, } } -static bool resolveAllocationOverload( - Sema &S, LookupResult &R, SourceRange Range, SmallVectorImpl<Expr *> &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<Expr *> 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)) +static void DiagnoseAllocationLookupFailure( + Sema &SemaRef, LookupResult &R, SourceRange Range, + std::optional<AllocationArgumentSet> &ArgumentCandidates, + ArrayRef<Expr *> PlacementArguments) { + ImplicitAllocationArguments *UnalignedArgumentList = nullptr; + ImplicitAllocationArguments *AlignedArgumentList = nullptr; + for (ImplicitAllocationArguments &AllocationArguments : + ArgumentCandidates->Candidates) { + if (AllocationArguments.PassTypeIdentity == TypeAwareAllocationMode::Yes) + continue; + if (AllocationArguments.PassAlignment == AlignedAllocationMode::Yes) + AlignedArgumentList = &AllocationArguments; + else + UnalignedArgumentList = &AllocationArguments; + } + if (!UnalignedArgumentList) + return; + + // We re-resolve the rejected candidates for diagnostics rather than requiring + // them to be tracked during the initial resolution path. This both simplifies + // the resolution logic, and helps with performance. + auto Rerun = [&](ImplicitAllocationArguments &ArgumentList, + OverloadCandidateSet &Candidates, + SmallVectorImpl<Expr *> &Args) { + llvm::append_range(Args, ArgumentList.getImplicitArguments()); + llvm::append_range(Args, PlacementArguments); + FunctionDecl *Unused = nullptr; + resolveAllocationOverload(SemaRef, R, Range, ArgumentList, Args, Unused, + Candidates, /*Diagnose=*/false); + }; + std::optional<OverloadCandidateSet> AlignedCandidates; + Expr *AlignArg = nullptr; + if (AlignedArgumentList) { + AlignedCandidates.emplace(R.getNameLoc(), OverloadCandidateSet::CSK_Normal); + SmallVector<Expr *, 4> AlignedArgs; + Rerun(*AlignedArgumentList, *AlignedCandidates, AlignedArgs); + AlignArg = AlignedArgumentList->getAlignmentArgument(); + } + OverloadCandidateSet UnalignedCandidates(R.getNameLoc(), + OverloadCandidateSet::CSK_Normal); + SmallVector<Expr *, 4> UnalignedArgs; + Rerun(*UnalignedArgumentList, UnalignedCandidates, UnalignedArgs); + diagnoseNoViableFunctionForAllocationOverloadResolution( + SemaRef, R, Range, UnalignedArgs, UnalignedCandidates, + AlignedCandidates ? &*AlignedCandidates : nullptr, AlignArg); +} + +bool Sema::getTypeIdentityArgument(QualType Type, SourceLocation Loc, + Expr **FoundExpr) { + auto [Slot, Inserted] = + AllocationTypeIdentityArguments.insert({Type, nullptr}); + if (!Inserted) { + if (!Slot->second) return true; - if (Operator) - return false; + *FoundExpr = Slot->second; + return false; + } + QualType TypeIdentity = tryBuildStdTypeIdentity(Type, SourceLocation()); + if (TypeIdentity.isNull()) + 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 = std::move(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) { + if (RequireCompleteType(Loc, TypeIdentity, diag::err_incomplete_type)) + return true; + Expr *TypeIdentityArgument = + new (Context) CXXScalarValueInitExpr(TypeIdentity, nullptr, Loc); + Slot->second = TypeIdentityArgument; + *FoundExpr = TypeIdentityArgument; + return false; +} + +ImplicitAllocationArguments::ImplicitAllocationArguments( + Sema &SemaRef, Expr *TypeIdentityArg, Expr *SizeArg, Expr *AlignArg, + bool IsMSVCCompatibilityFallback) + : PassTypeIdentity(typeAwareAllocationModeFromBool(TypeIdentityArg)), + PassAlignment(alignedAllocationModeFromBool(AlignArg)), + IsMSVCCompatibilityFallback(IsMSVCCompatibilityFallback), + ArgumentCount(0) { + ASTContext &Ctx = SemaRef.getASTContext(); + if (TypeIdentityArg) { + assert(SemaRef.isStdTypeIdentity(TypeIdentityArg->getType(), nullptr)); + ImplicitArguments[ArgumentCount++] = TypeIdentityArg; + } + assert(SizeArg); + assert(Ctx.hasSameType(SizeArg->getType(), Ctx.getSizeType())); + ImplicitArguments[ArgumentCount++] = SizeArg; + if (AlignArg) { + assert(AlignArg->getType()->isAlignValT()); + ImplicitArguments[ArgumentCount++] = AlignArg; + } +} + +void ImplicitAllocationArguments::updateLookupForMSVCCompatibility( + Sema &S, LookupResult &R) { + if (!IsMSVCCompatibilityFallback) + return; + // MSVC will fall back on trying to find a matching global operator new + // if operator new[] cannot be found. Also, MSVC will leak by not + // generating a call to operator delete or operator delete[], but we + // will not replicate that bug. + // FIXME: Find out how this interacts with the std::align_val_t fallback + // once MSVC implements it. + R.clear(); + R.setLookupName(S.Context.DeclarationNames.getCXXOperatorName(OO_New)); + // FIXME: This will give bad diagnostics pointing at the wrong functions. + S.LookupQualifiedName(R, S.Context.getTranslationUnitDecl()); +} + +std::optional<AllocationArgumentSet> +Sema::resolveAllocationArguments(LookupResult &R, + const ImplicitAllocationParameters &IAP, + ArrayRef<Expr *> PlacementArguments) { + // FIXME: Should Sema create per-callsite versions expressions so they can be + // reused during codegen? This would likely create yet another case where we + // need to serialize information, however it would ensure identical arguments + // between Sema and CodeGen. + if (!AllocationSizeExpr) { + DeclareGlobalNewDelete(); + QualType SizeTy = Context.getSizeType(); + unsigned SizeTyWidth = Context.getTypeSize(SizeTy); + AllocationSizeExpr = IntegerLiteral::Create( + Context, llvm::APInt::getZero(SizeTyWidth), SizeTy, SourceLocation()); + if (EnumDecl *StdAlignValT = getStdAlignValT()) { + QualType AlignValT = Context.getCanonicalTagType(StdAlignValT); + AllocationAlignmentExpr = new (Context) + CXXScalarValueInitExpr(AlignValT, nullptr, SourceLocation()); + } + } + + AllocationArgumentSet FoundArguments = { + isTypeAwareAllocation(IAP.PassTypeIdentity), {}}; + if (isTypeAwareAllocation(IAP.PassTypeIdentity)) { + Expr *TypeIdentityArgument = nullptr; + if (getTypeIdentityArgument(IAP.Type, R.getNameLoc(), + &TypeIdentityArgument)) + return std::nullopt; + if (TypeIdentityArgument) { + Expr *AlignmentExpr = AllocationAlignmentExpr; + if (!PlacementArguments.empty() && + PlacementArguments.front()->getType()->isAlignValT()) + AlignmentExpr = nullptr; + FoundArguments.Candidates.push_back(ImplicitAllocationArguments( + *this, TypeIdentityArgument, AllocationSizeExpr, AlignmentExpr, + /*IsMSVCCompatibilityFallback=*/false)); + } else + FoundArguments.TypeAwareViable = false; + } + + ImplicitAllocationArguments UnalignedArguments( + *this, /*TypeIdentityArg=*/nullptr, AllocationSizeExpr, + /*AlignArg=*/nullptr, /*IsMSVCCompatibilityFallback=*/false); + ImplicitAllocationArguments AlignedArguments( + *this, /*TypeIdentityArg=*/nullptr, AllocationSizeExpr, + AllocationAlignmentExpr, /*IsMSVCCompatibilityFallback=*/false); + + // C++17 [expr.new]p13: + // 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 (IAP.PassAlignment == AlignedAllocationMode::Yes) + FoundArguments.Candidates.push_back(AlignedArguments); + FoundArguments.Candidates.push_back(UnalignedArguments); + + // The MSVC global fallback path + if (getLangOpts().MSVCCompat && + R.getLookupName().getCXXOverloadedOperator() == OO_Array_New) + FoundArguments.Candidates.push_back(ImplicitAllocationArguments( + *this, /*TypeIdentityArg=*/nullptr, AllocationSizeExpr, + /*AlignArg=*/nullptr, /*IsMSVCCompatibilityFallback=*/true)); + return FoundArguments; +} + +std::optional<Sema::ResolvedAllocation> +Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, + AllocationFunctionScope NewScope, + AllocationFunctionScope DeleteScope, + QualType AllocType, bool IsArray, + const ImplicitAllocationParameters &RequestedIAP, + MultiExprArg PlaceArgs, bool Diagnose) { // --- Choosing an allocation function --- // C++ 5.3.4p8 - 14 & 18 // 1) If looking in AllocationFunctionScope::Global scope for allocation @@ -2979,9 +3073,6 @@ bool Sema::FindAllocationFunctions( // 3) The first argument is always size_t. Append the arguments from the // placement form. - SmallVector<Expr*, 8> AllocArgs; - AllocArgs.reserve(IAP.getNumImplicitArgs() + PlaceArgs.size()); - // C++ [expr.new]p8: // If the allocated type is a non-array type, the allocation // function's name is operator new and the deallocation function's @@ -2993,49 +3084,11 @@ bool Sema::FindAllocationFunctions( 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.getCanonicalTagType(getStdAlignValT()); - } - CXXScalarValueInitExpr Align(AlignValT, nullptr, SourceLocation()); - if (IncludeAlignParam) - AllocArgs.push_back(&Align); - - llvm::append_range(AllocArgs, PlaceArgs); + bool ConsideredTypeAwareAllocation = false; + ResolvedAllocation Result = {/*OperatorNew=*/nullptr, + /*OperatorDelete=*/nullptr, + RequestedIAP, + {}}; // Find the allocation function. { @@ -3053,14 +3106,14 @@ bool Sema::FindAllocationFunctions( // We can see ambiguity here if the allocation function is found in // multiple base classes. if (R.isAmbiguous()) - return true; + return std::nullopt; // If this lookup fails to find the name, or if the allocated type is not // a class type, the allocation function's name is looked up in the // global scope. if (R.empty()) { if (NewScope == AllocationFunctionScope::Class) - return true; + return std::nullopt; LookupQualifiedName(R, Context.getTranslationUnitDecl()); } @@ -3071,7 +3124,7 @@ bool Sema::FindAllocationFunctions( } else { Diag(StartLoc, diag::err_openclcxx_placement_new); } - return true; + return std::nullopt; } assert(!R.empty() && "implicitly declared allocation functions not found"); @@ -3080,17 +3133,44 @@ bool Sema::FindAllocationFunctions( // We do our own custom access checks below. R.suppressDiagnostics(); - if (resolveAllocationOverload(*this, R, Range, AllocArgs, IAP, OperatorNew, - /*Candidates=*/nullptr, - /*AlignArg=*/nullptr, Diagnose)) - return true; + std::optional<AllocationArgumentSet> Candidates = + resolveAllocationArguments(R, RequestedIAP, PlaceArgs); + if (!Candidates) + return std::nullopt; + ConsideredTypeAwareAllocation = Candidates->TypeAwareViable; + + for (ImplicitAllocationArguments &ArgumentList : Candidates->Candidates) { + SmallVector<Expr *, 4> TrialArguments( + ArgumentList.getImplicitArguments()); + llvm::append_range(TrialArguments, PlaceArgs); + OverloadCandidateSet OverloadCandidates(R.getNameLoc(), + OverloadCandidateSet::CSK_Normal); + FunctionDecl *Operator = nullptr; + switch (resolveAllocationOverload(*this, R, Range, ArgumentList, + TrialArguments, Operator, + OverloadCandidates, Diagnose)) { + case AllocatorResolveResult::Error: + return std::nullopt; + case AllocatorResolveResult::Retry: + continue; + case AllocatorResolveResult::Success: + Result.OperatorNew = Operator; + Result.IAP.PassTypeIdentity = ArgumentList.PassTypeIdentity; + Result.IAP.PassAlignment = ArgumentList.PassAlignment; + Result.Arguments = std::move(TrialArguments); + goto foundCandidate; + } + } + if (Diagnose) + DiagnoseAllocationLookupFailure(*this, R, Range, Candidates, PlaceArgs); + return std::nullopt; } +foundCandidate: + FunctionDecl *OperatorNew = Result.OperatorNew; // We don't need an operator delete if we're running under -fno-exceptions. - if (!getLangOpts().Exceptions) { - OperatorDelete = nullptr; - return false; - } + if (!getLangOpts().Exceptions) + return Result; // Note, the name of OperatorNew might have been changed from array to // non-array by resolveAllocationOverload. @@ -3115,7 +3195,7 @@ bool Sema::FindAllocationFunctions( LookupQualifiedName(FoundDelete, RD); } if (FoundDelete.isAmbiguous()) - return true; // FIXME: clean up expressions? + return std::nullopt; // FIXME: clean up expressions? // Filter out any destroying operator deletes. We can't possibly call such a // function in this context, because we're handling the case where the object @@ -3139,10 +3219,10 @@ bool Sema::FindAllocationFunctions( bool FoundGlobalDelete = FoundDelete.empty(); bool IsClassScopedTypeAwareNew = - isTypeAwareAllocation(IAP.PassTypeIdentity) && + isTypeAwareAllocation(Result.IAP.PassTypeIdentity) && OperatorNewContext->isRecord(); auto DiagnoseMissingTypeAwareCleanupOperator = [&](bool IsPlacementOperator) { - assert(isTypeAwareAllocation(IAP.PassTypeIdentity)); + assert(isTypeAwareAllocation(Result.IAP.PassTypeIdentity)); if (Diagnose) { Diag(StartLoc, diag::err_mismatching_type_aware_cleanup_deallocator) << OperatorNew->getDeclName() << IsPlacementOperator << DeleteName; @@ -3153,16 +3233,16 @@ bool Sema::FindAllocationFunctions( }; if (IsClassScopedTypeAwareNew && FoundDelete.empty()) { DiagnoseMissingTypeAwareCleanupOperator(/*isPlacementNew=*/false); - return true; + return std::nullopt; } if (FoundDelete.empty()) { FoundDelete.clear(LookupOrdinaryName); if (DeleteScope == AllocationFunctionScope::Class) - return true; + return std::nullopt; DeclareGlobalNewDelete(); - DeallocLookupMode LookupMode = isTypeAwareAllocation(OriginalTypeAwareState) + DeallocLookupMode LookupMode = ConsideredTypeAwareAllocation ? DeallocLookupMode::OptionallyTyped : DeallocLookupMode::Untyped; LookupGlobalDeallocationFunctions(*this, StartLoc, FoundDelete, LookupMode, @@ -3188,7 +3268,7 @@ bool Sema::FindAllocationFunctions( // type uses the sized or non-sized form of aligned operator delete. unsigned NonPlacementNewArgCount = 1; // size parameter - if (isTypeAwareAllocation(IAP.PassTypeIdentity)) + if (isTypeAwareAllocation(Result.IAP.PassTypeIdentity)) NonPlacementNewArgCount = /* type-identity */ 1 + /* size */ 1 + /* alignment */ 1; bool isPlacementNew = !PlaceArgs.empty() || @@ -3212,8 +3292,8 @@ bool Sema::FindAllocationFunctions( SmallVector<QualType, 6> ArgTypes; int InitialParamOffset = 0; - if (isTypeAwareAllocation(IAP.PassTypeIdentity)) { - ArgTypes.push_back(TypeIdentity); + if (isTypeAwareAllocation(Result.IAP.PassTypeIdentity)) { + ArgTypes.push_back(Result.Arguments.front()->getType()); InitialParamOffset = 1; } ArgTypes.push_back(Context.VoidPtrTy); @@ -3255,9 +3335,9 @@ bool Sema::FindAllocationFunctions( if (getLangOpts().CUDA) CUDA().EraseUnwantedMatches(getCurFunctionDecl(/*AllowLambda=*/true), Matches); - if (Matches.empty() && isTypeAwareAllocation(IAP.PassTypeIdentity)) { + if (Matches.empty() && isTypeAwareAllocation(Result.IAP.PassTypeIdentity)) { DiagnoseMissingTypeAwareCleanupOperator(isPlacementNew); - return true; + return std::nullopt; } } else { // C++1y [expr.new]p22: @@ -3269,7 +3349,8 @@ bool Sema::FindAllocationFunctions( // with a size_t where possible (which it always is in this case). llvm::SmallVector<UsualDeallocFnInfo, 4> BestDeallocFns; ImplicitDeallocationParameters IDP = { - AllocElemType, OriginalTypeAwareState, + AllocElemType, + typeAwareAllocationModeFromBool(ConsideredTypeAwareAllocation), alignedAllocationModeFromBool( hasNewExtendedAlignment(*this, AllocElemType)), sizedDeallocationModeFromBool(FoundGlobalDelete)}; @@ -3290,7 +3371,8 @@ bool Sema::FindAllocationFunctions( // function, that function will be called; otherwise, no // deallocation function will be called. if (Matches.size() == 1) { - OperatorDelete = Matches[0].second; + Result.OperatorDelete = Matches[0].second; + FunctionDecl *OperatorDelete = Result.OperatorDelete; DeclContext *OperatorDeleteContext = GetRedeclContext(OperatorDelete); bool FoundTypeAwareOperator = OperatorDelete->isTypeAwareOperatorNewOrDelete() || @@ -3348,7 +3430,8 @@ bool Sema::FindAllocationFunctions( IsSizedDelete = false; } - if (IsSizedDelete && !isTypeAwareAllocation(IAP.PassTypeIdentity)) { + if (IsSizedDelete && + !isTypeAwareAllocation(Result.IAP.PassTypeIdentity)) { SourceRange R = PlaceArgs.empty() ? SourceRange() : SourceRange(PlaceArgs.front()->getBeginLoc(), @@ -3362,7 +3445,7 @@ bool Sema::FindAllocationFunctions( if (CheckDeleteOperator(*this, StartLoc, Range, Diagnose, FoundDelete.getNamingClass(), Matches[0].first, Matches[0].second)) - return true; + return std::nullopt; } else if (!Matches.empty()) { // We found multiple suitable operators. Per [expr.new]p20, that means we @@ -3376,7 +3459,7 @@ bool Sema::FindAllocationFunctions( diag::note_member_declared_here) << DeleteName; } - return false; + return Result; } void Sema::DeclareGlobalNewDelete() { diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index eafda32198f11..067f3f51c7f41 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -17375,7 +17375,7 @@ bool clang::shouldEnforceArgLimit(bool PartialOverloading, void Sema::DiagnoseUseOfDeletedFunction(SourceLocation Loc, SourceRange Range, DeclarationName Name, OverloadCandidateSet &CandidateSet, - FunctionDecl *Fn, MultiExprArg Args, + FunctionDecl *Fn, ArrayRef<Expr *> Args, bool IsMember) { StringLiteral *Msg = Fn->getDeletedMessage(); CandidateSet.NoteCandidates( diff --git a/clang/test/SemaCXX/microsoft-new-array-fallback.cpp b/clang/test/SemaCXX/microsoft-new-array-fallback.cpp new file mode 100644 index 0000000000000..fde63f35bb9e2 --- /dev/null +++ b/clang/test/SemaCXX/microsoft-new-array-fallback.cpp @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -fms-compatibility -fsyntax-only -verify -std=c++11 %s + +typedef __SIZE_TYPE__ size_t; + +void *operator new(size_t); // #new_decl + +struct Tag {}; + +void f() { + int *p = new (Tag{}) int[4]; + // expected-error@-1 {{no matching function for call to 'operator new'}} + // expected-note@#new_decl {{candidate function not viable: requires 1 argument, but 2 were provided}} + (void)p; +} diff --git a/clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp b/clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp index 502f4fab6b519..caf1821c0e3f2 100644 --- a/clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp +++ b/clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp @@ -7,7 +7,6 @@ namespace std { #if !defined(INVALID_TYPE_IDENTITY_VERSION) - // expected-no-diagnostics template <class T> struct type_identity { }; #define TYPE_IDENTITY(T) std::type_identity<T> @@ -57,3 +56,17 @@ void f() { TestType *t = new TestType; delete t; } + +#if !defined(INVALID_TYPE_IDENTITY_VERSION) +struct Bad {}; +template <> struct std::type_identity<Bad>; // #incomplete_specialization + +// This is a pure implementation test to ensure correct caching behavior if +// constructing the type_identity argument fails. +void failedTypeIdentitySpecialization() { + Bad *a = new Bad; + // expected-error@-1 {{incomplete type 'std::type_identity<Bad>' where a complete type is required}} + // expected-note@#incomplete_specialization {{forward declaration of 'std::type_identity<Bad>'}} + Bad *b = new Bad; +} +#endif >From 727c182002c6d7d521791d7ececa07af020ff1dc Mon Sep 17 00:00:00 2001 From: Oliver Hunt <[email protected]> Date: Thu, 23 Jul 2026 13:17:10 -0700 Subject: [PATCH 2/5] Update for reviewer feedback --- .../Sema/DynamicAllocationArgumentsCXX.h | 72 +++++++++++++++++++ clang/include/clang/Sema/Sema.h | 52 +------------- clang/lib/Sema/SemaCoroutine.cpp | 1 + clang/lib/Sema/SemaExprCXX.cpp | 9 +-- clang/lib/Sema/SemaOverload.cpp | 2 +- 5 files changed, 80 insertions(+), 56 deletions(-) create mode 100644 clang/include/clang/Sema/DynamicAllocationArgumentsCXX.h diff --git a/clang/include/clang/Sema/DynamicAllocationArgumentsCXX.h b/clang/include/clang/Sema/DynamicAllocationArgumentsCXX.h new file mode 100644 index 0000000000000..c3ee16382dcb6 --- /dev/null +++ b/clang/include/clang/Sema/DynamicAllocationArgumentsCXX.h @@ -0,0 +1,72 @@ +//===- DynamicAllocationArgumentsCXX.h - operator new/delete args ---------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the argument candidate and resolution types for operators +// new and new[] overload resolution. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SEMA_DYNAMICALLOCATIONARGUMENTSCXX_H +#define LLVM_CLANG_SEMA_DYNAMICALLOCATIONARGUMENTSCXX_H + +#include "clang/AST/ExprCXX.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" + +namespace clang { + +class LookupResult; +class Sema; + +struct ImplicitAllocationArguments { + friend Sema; + + ArrayRef<Expr *> getImplicitArguments() const { + return ArrayRef(ImplicitArguments, ArgumentCount); + } + + Expr *getAlignmentArgument() const { + if (PassAlignment == AlignedAllocationMode::Yes) + return ImplicitArguments[ArgumentCount - 1]; + return nullptr; + } + + void updateLookupForMSVCCompatibility(Sema &, LookupResult &); + TypeAwareAllocationMode PassTypeIdentity; + AlignedAllocationMode PassAlignment; + +private: + ImplicitAllocationArguments(Sema &SemaRef, Expr *TypeIdentityArg, + Expr *SizeArg, Expr *AlignArg, + bool IsMSVCCompatibilityFallback); + + // Type-identity, size, and alignment + static constexpr unsigned MaxImplicitArguments = 3; + bool IsMSVCCompatibilityFallback; + + unsigned ArgumentCount; + Expr *ImplicitArguments[MaxImplicitArguments]; +}; + +struct AllocationArgumentSet { + bool TypeAwareViable; + SmallVector<ImplicitAllocationArguments, 3> Candidates; +}; + +struct ResolvedAllocation { + FunctionDecl *OperatorNew; + FunctionDecl *OperatorDelete; + ImplicitAllocationParameters IAP; + // type-identity, size, alignment, nothrow or other single placement + // parameter + SmallVector<Expr *, 4> Arguments; +}; + +} // namespace clang + +#endif // LLVM_CLANG_SEMA_DYNAMICALLOCATIONARGUMENTSCXX_H diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 292c53f460826..963065b86a1d0 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -116,6 +116,7 @@ struct InlineAsmIdentifierInfo; namespace clang { class ADLResult; +struct AllocationArgumentSet; class APValue; struct ASTConstraintSatisfaction; class ASTConsumer; @@ -157,6 +158,7 @@ enum class OverloadCandidateParamOrder : char; enum OverloadCandidateRewriteKind : unsigned; class OverloadCandidateSet; class Preprocessor; +struct ResolvedAllocation; class SemaAMDGPU; class SemaARM; class SemaAVR; @@ -8651,45 +8653,6 @@ class Sema final : public SemaBase { bool CheckAllocatedType(QualType AllocType, SourceLocation Loc, SourceRange R); - struct ImplicitAllocationArguments { - friend Sema; - - ArrayRef<Expr *> getImplicitArguments() const { - return ArrayRef(ImplicitArguments, ArgumentCount); - } - - Expr *getAlignmentArgument() const { - if (PassAlignment == AlignedAllocationMode::Yes) - return ImplicitArguments[ArgumentCount - 1]; - return nullptr; - } - - void updateLookupForMSVCCompatibility(Sema &, LookupResult &); - TypeAwareAllocationMode PassTypeIdentity; - AlignedAllocationMode PassAlignment; - - private: - ImplicitAllocationArguments(Sema &SemaRef, Expr *TypeIdentityArg, - Expr *SizeArg, Expr *AlignArg, - bool IsMSVCCompatibilityFallback); - - // Type-identity, size, and alignment - static constexpr unsigned MaxImplicitArguments = 3; - bool IsMSVCCompatibilityFallback; - - unsigned ArgumentCount; - Expr *ImplicitArguments[MaxImplicitArguments]; - }; - - struct AllocationArgumentSet { - bool TypeAwareViable; - SmallVector<ImplicitAllocationArguments, 3> Candidates; - }; - -private: - struct ResolvedAllocation; - -public: /// Finds the overloads of operator new and delete that are appropriate /// for the allocation. std::optional<ResolvedAllocation> FindAllocationFunctions( @@ -9000,15 +8963,6 @@ class Sema final : public SemaBase { void AnalyzeDeleteExprMismatch(FieldDecl *Field, SourceLocation DeleteLoc, bool DeleteWasArrayForm); - struct ResolvedAllocation { - FunctionDecl *OperatorNew; - FunctionDecl *OperatorDelete; - ImplicitAllocationParameters IAP; - // type-identity, size, alignment, nothrow or other single placement - // parameter - SmallVector<Expr *, 4> Arguments; - }; - std::optional<AllocationArgumentSet> resolveAllocationArguments(LookupResult &R, const ImplicitAllocationParameters &, @@ -10428,7 +10382,7 @@ class Sema final : public SemaBase { void DiagnoseUseOfDeletedFunction(SourceLocation Loc, SourceRange Range, DeclarationName Name, OverloadCandidateSet &CandidateSet, - FunctionDecl *Fn, ArrayRef<Expr *> Args, + FunctionDecl *Fn, MultiExprArg Args, bool IsMember = false); ExprResult InitializeExplicitObjectArgument(Sema &S, Expr *Obj, diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp index 4e8dbec4f35d1..aceb5f2aa33a4 100644 --- a/clang/lib/Sema/SemaCoroutine.cpp +++ b/clang/lib/Sema/SemaCoroutine.cpp @@ -23,6 +23,7 @@ #include "clang/Basic/Builtins.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/Preprocessor.h" +#include "clang/Sema/DynamicAllocationArgumentsCXX.h" #include "clang/Sema/EnterExpressionEvaluationContext.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Overload.h" diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index f7c22c99968c6..0079a488c13c1 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -32,6 +32,7 @@ #include "clang/Basic/TokenKinds.h" #include "clang/Lex/Preprocessor.h" #include "clang/Sema/DeclSpec.h" +#include "clang/Sema/DynamicAllocationArgumentsCXX.h" #include "clang/Sema/EnterExpressionEvaluationContext.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Lookup.h" @@ -2789,15 +2790,11 @@ static void diagnoseNoViableFunctionForAllocationOverloadResolution( Candidates.NoteCandidates(S, Args, Cands, "", R.getNameLoc()); } -using ImplicitAllocationArguments = Sema::ImplicitAllocationArguments; -using AllocationArgumentSet = Sema::AllocationArgumentSet; - enum class AllocatorResolveResult { Success, Retry, Error }; static AllocatorResolveResult resolveAllocationOverload(Sema &S, LookupResult &R, SourceRange Range, ImplicitAllocationArguments &AllocationArgs, - ArrayRef<Expr *> TrialArguments, - FunctionDecl *&Operator, + MultiExprArg TrialArguments, FunctionDecl *&Operator, OverloadCandidateSet &Candidates, bool Diagnose) { AllocationArgs.updateLookupForMSVCCompatibility(S, R); @@ -3054,7 +3051,7 @@ Sema::resolveAllocationArguments(LookupResult &R, return FoundArguments; } -std::optional<Sema::ResolvedAllocation> +std::optional<ResolvedAllocation> Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, AllocationFunctionScope NewScope, AllocationFunctionScope DeleteScope, diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 067f3f51c7f41..eafda32198f11 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -17375,7 +17375,7 @@ bool clang::shouldEnforceArgLimit(bool PartialOverloading, void Sema::DiagnoseUseOfDeletedFunction(SourceLocation Loc, SourceRange Range, DeclarationName Name, OverloadCandidateSet &CandidateSet, - FunctionDecl *Fn, ArrayRef<Expr *> Args, + FunctionDecl *Fn, MultiExprArg Args, bool IsMember) { StringLiteral *Msg = Fn->getDeletedMessage(); CandidateSet.NoteCandidates( >From 57bdce15a0a723e87c086fd64d4a5803bcaf3e5f Mon Sep 17 00:00:00 2001 From: Oliver Hunt <[email protected]> Date: Thu, 23 Jul 2026 15:10:56 -0700 Subject: [PATCH 3/5] sigh, missed this --- clang/lib/Sema/SemaExprCXX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 0079a488c13c1..fad2b99a70a3d 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -2461,7 +2461,7 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, !Expr::hasAnyTypeDependentArguments(PlacementArgs)) { auto FoundAllocation = FindAllocationFunctions( StartLoc, AllocationParameterRange, Scope, Scope, AllocType, - ArraySize.has_value(), IAP, PlacementArgs); + /*IsArray=*/ArraySize.has_value(), IAP, PlacementArgs); if (!FoundAllocation) return ExprError(); IAP = FoundAllocation->IAP; >From 2e695908b66d263b44be81785913b4d0c0e77adb Mon Sep 17 00:00:00 2001 From: Oliver Hunt <[email protected]> Date: Sun, 26 Jul 2026 18:11:11 -0700 Subject: [PATCH 4/5] Stop mutating the initial LookupResult, and instead use an optional buffer for the MSVC fallback. The refactoring actually removed the need for a the old "did we try type aware allocators", and a few other things also got cleaned up. --- .../clang/Basic/DiagnosticSemaKinds.td | 3 + .../Sema/DynamicAllocationArgumentsCXX.h | 12 +- clang/include/clang/Sema/Sema.h | 12 +- clang/lib/Sema/SemaExprCXX.cpp | 171 ++++++++++-------- .../SemaCXX/microsoft-new-array-fallback.cpp | 5 +- .../type-aware-new-invalid-type-identity.cpp | 2 + 6 files changed, 114 insertions(+), 91 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 371ad4535e95f..ef1478b15c9fc 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -5524,6 +5524,9 @@ def err_no_viable_destructor : Error< def err_ambiguous_destructor : Error< "destructor of class %0 is ambiguous">; +def note_ovl_msvc_allocation_fallback_failed : Note< + "MSVC compatibility fall back to %0 failed">; + def err_ovl_no_viable_object_call : Error< "no matching function for call to object of type %0">; def err_ovl_ambiguous_object_call : Error< diff --git a/clang/include/clang/Sema/DynamicAllocationArgumentsCXX.h b/clang/include/clang/Sema/DynamicAllocationArgumentsCXX.h index c3ee16382dcb6..7ce800111dfe6 100644 --- a/clang/include/clang/Sema/DynamicAllocationArgumentsCXX.h +++ b/clang/include/clang/Sema/DynamicAllocationArgumentsCXX.h @@ -36,9 +36,12 @@ struct ImplicitAllocationArguments { return nullptr; } - void updateLookupForMSVCCompatibility(Sema &, LookupResult &); + const LookupResult & + updateLookupForMSVCCompatibility(Sema &, const LookupResult &, + std::optional<LookupResult> &) const; TypeAwareAllocationMode PassTypeIdentity; AlignedAllocationMode PassAlignment; + bool IsMSVCCompatibilityFallback; private: ImplicitAllocationArguments(Sema &SemaRef, Expr *TypeIdentityArg, @@ -47,17 +50,10 @@ struct ImplicitAllocationArguments { // Type-identity, size, and alignment static constexpr unsigned MaxImplicitArguments = 3; - bool IsMSVCCompatibilityFallback; - unsigned ArgumentCount; Expr *ImplicitArguments[MaxImplicitArguments]; }; -struct AllocationArgumentSet { - bool TypeAwareViable; - SmallVector<ImplicitAllocationArguments, 3> Candidates; -}; - struct ResolvedAllocation { FunctionDecl *OperatorNew; FunctionDecl *OperatorDelete; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 963065b86a1d0..4ed05a183a70b 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -116,7 +116,6 @@ struct InlineAsmIdentifierInfo; namespace clang { class ADLResult; -struct AllocationArgumentSet; class APValue; struct ASTConstraintSatisfaction; class ASTConsumer; @@ -138,6 +137,7 @@ struct DeductionFailureInfo; class DependentDiagnostic; class Designation; class IdentifierInfo; +struct ImplicitAllocationArguments; class ImplicitConversionSequence; typedef MutableArrayRef<ImplicitConversionSequence> ConversionSequenceList; class InitializationKind; @@ -226,6 +226,10 @@ enum class AssignmentAction { Passing_CFAudited }; +// Inline capacity for type-aware, aligned, and unaligned allocation argument +// list candidates. +using AllocationArgumentSet = SmallVector<ImplicitAllocationArguments, 3>; + namespace threadSafety { class BeforeSet; void threadSafetyCleanup(BeforeSet *Cache); @@ -8967,7 +8971,11 @@ class Sema final : public SemaBase { resolveAllocationArguments(LookupResult &R, const ImplicitAllocationParameters &, ArrayRef<Expr *> PlacementArguments); - bool getTypeIdentityArgument(QualType Type, SourceLocation, Expr **FoundExpr); + + // Attempts to construct the type identity argument for the call to a + // type aware operator new. In the event of an error this returns + // std::nullopt. + std::optional<Expr *> getTypeIdentityArgument(QualType Type, SourceLocation); Expr *AllocationSizeExpr = nullptr; Expr *AllocationAlignmentExpr = nullptr; diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index fad2b99a70a3d..796244a1eab8a 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -2726,9 +2726,10 @@ bool Sema::CheckAllocatedType(QualType AllocType, SourceLocation Loc, } static void diagnoseNoViableFunctionForAllocationOverloadResolution( - Sema &S, LookupResult &R, SourceRange Range, ArrayRef<Expr *> Args, - OverloadCandidateSet &Candidates, OverloadCandidateSet *AlignedCandidates, - Expr *AlignArg) { + Sema &S, const LookupResult &R, + const std::optional<LookupResult> &MSVCFallback, SourceRange Range, + ArrayRef<Expr *> Args, OverloadCandidateSet &Candidates, + OverloadCandidateSet *AlignedCandidates, Expr *AlignArg) { // 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), // diagnose the reason for the error. @@ -2788,19 +2789,27 @@ static void diagnoseNoViableFunctionForAllocationOverloadResolution( AlignedCandidates->NoteCandidates(S, AlignedArgs, AlignedCands, "", R.getNameLoc()); Candidates.NoteCandidates(S, Args, Cands, "", R.getNameLoc()); + if (MSVCFallback) + S.Diag(MSVCFallback->getNameLoc(), + diag::note_ovl_msvc_allocation_fallback_failed) + << MSVCFallback->getLookupName() << Range; } enum class AllocatorResolveResult { Success, Retry, Error }; -static AllocatorResolveResult -resolveAllocationOverload(Sema &S, LookupResult &R, SourceRange Range, - ImplicitAllocationArguments &AllocationArgs, - MultiExprArg TrialArguments, FunctionDecl *&Operator, - OverloadCandidateSet &Candidates, bool Diagnose) { - AllocationArgs.updateLookupForMSVCCompatibility(S, R); +static AllocatorResolveResult resolveAllocationOverload( + Sema &S, const LookupResult &BaseLookup, SourceRange Range, + ImplicitAllocationArguments &AllocationArgs, MultiExprArg TrialArguments, + FunctionDecl *&Operator, OverloadCandidateSet &Candidates, bool Diagnose) { + std::optional<LookupResult> MSVCFallback; + const LookupResult &LocalLookup = + AllocationArgs.updateLookupForMSVCCompatibility(S, BaseLookup, + MSVCFallback); bool ArgumentListIsTypeAware = isTypeAwareAllocation(AllocationArgs.PassTypeIdentity); - for (LookupResult::iterator Alloc = R.begin(), AllocEnd = R.end(); + + for (LookupResult::iterator Alloc = LocalLookup.begin(), + AllocEnd = LocalLookup.end(); Alloc != AllocEnd; ++Alloc) { // Even member operator new/delete are implicitly treated as // static, so don't use AddMemberCandidate. @@ -2825,10 +2834,11 @@ resolveAllocationOverload(Sema &S, LookupResult &R, SourceRange Range, // Do the resolution. OverloadCandidateSet::iterator Best; - switch (Candidates.BestViableFunction(S, R.getNameLoc(), Best)) { + switch (Candidates.BestViableFunction(S, LocalLookup.getNameLoc(), Best)) { case OR_Success: { FunctionDecl *FnDecl = Best->Function; - if (S.CheckAllocationAccess(R.getNameLoc(), Range, R.getNamingClass(), + if (S.CheckAllocationAccess(LocalLookup.getNameLoc(), Range, + LocalLookup.getNamingClass(), Best->FoundDecl) == Sema::AR_inaccessible) return AllocatorResolveResult::Error; @@ -2842,18 +2852,18 @@ resolveAllocationOverload(Sema &S, LookupResult &R, SourceRange Range, case OR_Ambiguous: if (Diagnose) { Candidates.NoteCandidates( - PartialDiagnosticAt(R.getNameLoc(), + PartialDiagnosticAt(LocalLookup.getNameLoc(), S.PDiag(diag::err_ovl_ambiguous_call) - << R.getLookupName() << Range), + << LocalLookup.getLookupName() << Range), S, OCD_AmbiguousCandidates, TrialArguments); } return AllocatorResolveResult::Error; case OR_Deleted: { if (Diagnose) - S.DiagnoseUseOfDeletedFunction(R.getNameLoc(), Range, R.getLookupName(), - Candidates, Best->Function, - TrialArguments); + S.DiagnoseUseOfDeletedFunction(LocalLookup.getNameLoc(), Range, + LocalLookup.getLookupName(), Candidates, + Best->Function, TrialArguments); return AllocatorResolveResult::Error; } } @@ -2880,14 +2890,15 @@ static void LookupGlobalDeallocationFunctions(Sema &S, SourceLocation Loc, } } -static void DiagnoseAllocationLookupFailure( - Sema &SemaRef, LookupResult &R, SourceRange Range, - std::optional<AllocationArgumentSet> &ArgumentCandidates, - ArrayRef<Expr *> PlacementArguments) { +static void +DiagnoseAllocationLookupFailure(Sema &SemaRef, const LookupResult &BaseLookup, + SourceRange Range, + AllocationArgumentSet &ArgumentCandidates, + ArrayRef<Expr *> PlacementArguments) { + std::optional<LookupResult> MSVCFallback; ImplicitAllocationArguments *UnalignedArgumentList = nullptr; ImplicitAllocationArguments *AlignedArgumentList = nullptr; - for (ImplicitAllocationArguments &AllocationArguments : - ArgumentCandidates->Candidates) { + for (ImplicitAllocationArguments &AllocationArguments : ArgumentCandidates) { if (AllocationArguments.PassTypeIdentity == TypeAwareAllocationMode::Yes) continue; if (AllocationArguments.PassAlignment == AlignedAllocationMode::Yes) @@ -2904,50 +2915,52 @@ static void DiagnoseAllocationLookupFailure( auto Rerun = [&](ImplicitAllocationArguments &ArgumentList, OverloadCandidateSet &Candidates, SmallVectorImpl<Expr *> &Args) { + const LookupResult &LocalLookup = + ArgumentList.updateLookupForMSVCCompatibility(SemaRef, BaseLookup, + MSVCFallback); llvm::append_range(Args, ArgumentList.getImplicitArguments()); llvm::append_range(Args, PlacementArguments); FunctionDecl *Unused = nullptr; - resolveAllocationOverload(SemaRef, R, Range, ArgumentList, Args, Unused, - Candidates, /*Diagnose=*/false); + resolveAllocationOverload(SemaRef, LocalLookup, Range, ArgumentList, Args, + Unused, Candidates, /*Diagnose=*/false); }; std::optional<OverloadCandidateSet> AlignedCandidates; Expr *AlignArg = nullptr; if (AlignedArgumentList) { - AlignedCandidates.emplace(R.getNameLoc(), OverloadCandidateSet::CSK_Normal); + AlignedCandidates.emplace(BaseLookup.getNameLoc(), + OverloadCandidateSet::CSK_Normal); SmallVector<Expr *, 4> AlignedArgs; Rerun(*AlignedArgumentList, *AlignedCandidates, AlignedArgs); AlignArg = AlignedArgumentList->getAlignmentArgument(); } - OverloadCandidateSet UnalignedCandidates(R.getNameLoc(), + OverloadCandidateSet UnalignedCandidates(BaseLookup.getNameLoc(), OverloadCandidateSet::CSK_Normal); SmallVector<Expr *, 4> UnalignedArgs; Rerun(*UnalignedArgumentList, UnalignedCandidates, UnalignedArgs); diagnoseNoViableFunctionForAllocationOverloadResolution( - SemaRef, R, Range, UnalignedArgs, UnalignedCandidates, - AlignedCandidates ? &*AlignedCandidates : nullptr, AlignArg); + SemaRef, BaseLookup, MSVCFallback, Range, UnalignedArgs, + UnalignedCandidates, AlignedCandidates ? &*AlignedCandidates : nullptr, + AlignArg); } -bool Sema::getTypeIdentityArgument(QualType Type, SourceLocation Loc, - Expr **FoundExpr) { +std::optional<Expr *> Sema::getTypeIdentityArgument(QualType Type, + SourceLocation Loc) { auto [Slot, Inserted] = AllocationTypeIdentityArguments.insert({Type, nullptr}); - if (!Inserted) { - if (!Slot->second) - return true; - *FoundExpr = Slot->second; - return false; - } - QualType TypeIdentity = tryBuildStdTypeIdentity(Type, SourceLocation()); - if (TypeIdentity.isNull()) - return false; - if (RequireCompleteType(Loc, TypeIdentity, diag::err_incomplete_type)) - return true; + if (!Inserted) + return Slot->second; + + QualType TypeIdentity = tryBuildStdTypeIdentity(Type, SourceLocation()); + if (TypeIdentity.isNull() || + RequireCompleteType(Loc, TypeIdentity, diag::err_incomplete_type)) { + AllocationTypeIdentityArguments.erase(Slot); + return std::nullopt; + } Expr *TypeIdentityArgument = new (Context) CXXScalarValueInitExpr(TypeIdentity, nullptr, Loc); Slot->second = TypeIdentityArgument; - *FoundExpr = TypeIdentityArgument; - return false; + return TypeIdentityArgument; } ImplicitAllocationArguments::ImplicitAllocationArguments( @@ -2971,20 +2984,23 @@ ImplicitAllocationArguments::ImplicitAllocationArguments( } } -void ImplicitAllocationArguments::updateLookupForMSVCCompatibility( - Sema &S, LookupResult &R) { +const LookupResult & +ImplicitAllocationArguments::updateLookupForMSVCCompatibility( + Sema &S, const LookupResult &BaseLookup, + std::optional<LookupResult> &Buffer) const { if (!IsMSVCCompatibilityFallback) - return; + return BaseLookup; // MSVC will fall back on trying to find a matching global operator new // if operator new[] cannot be found. Also, MSVC will leak by not // generating a call to operator delete or operator delete[], but we // will not replicate that bug. // FIXME: Find out how this interacts with the std::align_val_t fallback // once MSVC implements it. - R.clear(); - R.setLookupName(S.Context.DeclarationNames.getCXXOperatorName(OO_New)); + LookupResult &Fallback = Buffer.emplace(LookupResult::Temporary, BaseLookup); + Fallback.setLookupName(S.Context.DeclarationNames.getCXXOperatorName(OO_New)); // FIXME: This will give bad diagnostics pointing at the wrong functions. - S.LookupQualifiedName(R, S.Context.getTranslationUnitDecl()); + S.LookupQualifiedName(Fallback, S.Context.getTranslationUnitDecl()); + return Fallback; } std::optional<AllocationArgumentSet> @@ -3008,23 +3024,21 @@ Sema::resolveAllocationArguments(LookupResult &R, } } - AllocationArgumentSet FoundArguments = { - isTypeAwareAllocation(IAP.PassTypeIdentity), {}}; + AllocationArgumentSet FoundArguments; if (isTypeAwareAllocation(IAP.PassTypeIdentity)) { - Expr *TypeIdentityArgument = nullptr; - if (getTypeIdentityArgument(IAP.Type, R.getNameLoc(), - &TypeIdentityArgument)) + std::optional<Expr *> TypeIdentityArgument = + getTypeIdentityArgument(IAP.Type, R.getNameLoc()); + if (!TypeIdentityArgument) return std::nullopt; - if (TypeIdentityArgument) { - Expr *AlignmentExpr = AllocationAlignmentExpr; - if (!PlacementArguments.empty() && - PlacementArguments.front()->getType()->isAlignValT()) - AlignmentExpr = nullptr; - FoundArguments.Candidates.push_back(ImplicitAllocationArguments( - *this, TypeIdentityArgument, AllocationSizeExpr, AlignmentExpr, - /*IsMSVCCompatibilityFallback=*/false)); - } else - FoundArguments.TypeAwareViable = false; + + assert(*TypeIdentityArgument); + Expr *AlignmentExpr = AllocationAlignmentExpr; + if (!PlacementArguments.empty() && + PlacementArguments.front()->getType()->isAlignValT()) + AlignmentExpr = nullptr; + FoundArguments.push_back(ImplicitAllocationArguments( + *this, *TypeIdentityArgument, AllocationSizeExpr, AlignmentExpr, + /*IsMSVCCompatibilityFallback=*/false)); } ImplicitAllocationArguments UnalignedArguments( @@ -3039,13 +3053,13 @@ Sema::resolveAllocationArguments(LookupResult &R, // new-extended alignment, the alignment argument is removed from the // argument list, and overload resolution is performed again. if (IAP.PassAlignment == AlignedAllocationMode::Yes) - FoundArguments.Candidates.push_back(AlignedArguments); - FoundArguments.Candidates.push_back(UnalignedArguments); + FoundArguments.push_back(AlignedArguments); + FoundArguments.push_back(UnalignedArguments); // The MSVC global fallback path if (getLangOpts().MSVCCompat && R.getLookupName().getCXXOverloadedOperator() == OO_Array_New) - FoundArguments.Candidates.push_back(ImplicitAllocationArguments( + FoundArguments.push_back(ImplicitAllocationArguments( *this, /*TypeIdentityArg=*/nullptr, AllocationSizeExpr, /*AlignArg=*/nullptr, /*IsMSVCCompatibilityFallback=*/true)); return FoundArguments; @@ -3081,7 +3095,6 @@ Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, QualType AllocElemType = Context.getBaseElementType(AllocType); - bool ConsideredTypeAwareAllocation = false; ResolvedAllocation Result = {/*OperatorNew=*/nullptr, /*OperatorDelete=*/nullptr, RequestedIAP, @@ -3130,13 +3143,12 @@ Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, // We do our own custom access checks below. R.suppressDiagnostics(); - std::optional<AllocationArgumentSet> Candidates = + std::optional<AllocationArgumentSet> ArgumentListCandidates = resolveAllocationArguments(R, RequestedIAP, PlaceArgs); - if (!Candidates) + if (!ArgumentListCandidates) return std::nullopt; - ConsideredTypeAwareAllocation = Candidates->TypeAwareViable; - for (ImplicitAllocationArguments &ArgumentList : Candidates->Candidates) { + for (ImplicitAllocationArguments &ArgumentList : *ArgumentListCandidates) { SmallVector<Expr *, 4> TrialArguments( ArgumentList.getImplicitArguments()); llvm::append_range(TrialArguments, PlaceArgs); @@ -3159,7 +3171,8 @@ Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, } } if (Diagnose) - DiagnoseAllocationLookupFailure(*this, R, Range, Candidates, PlaceArgs); + DiagnoseAllocationLookupFailure(*this, R, Range, *ArgumentListCandidates, + PlaceArgs); return std::nullopt; } foundCandidate: @@ -3239,9 +3252,10 @@ Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, return std::nullopt; DeclareGlobalNewDelete(); - DeallocLookupMode LookupMode = ConsideredTypeAwareAllocation - ? DeallocLookupMode::OptionallyTyped - : DeallocLookupMode::Untyped; + DeallocLookupMode LookupMode = + isTypeAwareAllocation(RequestedIAP.PassTypeIdentity) + ? DeallocLookupMode::OptionallyTyped + : DeallocLookupMode::Untyped; LookupGlobalDeallocationFunctions(*this, StartLoc, FoundDelete, LookupMode, DeleteName); } @@ -3346,8 +3360,7 @@ Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, // with a size_t where possible (which it always is in this case). llvm::SmallVector<UsualDeallocFnInfo, 4> BestDeallocFns; ImplicitDeallocationParameters IDP = { - AllocElemType, - typeAwareAllocationModeFromBool(ConsideredTypeAwareAllocation), + AllocElemType, RequestedIAP.PassTypeIdentity, alignedAllocationModeFromBool( hasNewExtendedAlignment(*this, AllocElemType)), sizedDeallocationModeFromBool(FoundGlobalDelete)}; diff --git a/clang/test/SemaCXX/microsoft-new-array-fallback.cpp b/clang/test/SemaCXX/microsoft-new-array-fallback.cpp index fde63f35bb9e2..80bb65363f828 100644 --- a/clang/test/SemaCXX/microsoft-new-array-fallback.cpp +++ b/clang/test/SemaCXX/microsoft-new-array-fallback.cpp @@ -7,8 +7,9 @@ void *operator new(size_t); // #new_decl struct Tag {}; void f() { - int *p = new (Tag{}) int[4]; - // expected-error@-1 {{no matching function for call to 'operator new'}} + int *p = new (Tag{}) int[4]; // #new_expr + // expected-error@#new_expr {{no matching function for call to 'operator new[]'}} // expected-note@#new_decl {{candidate function not viable: requires 1 argument, but 2 were provided}} + // expected-note@#new_expr {{MSVC compatibility fall back to 'operator new' failed}} (void)p; } diff --git a/clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp b/clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp index caf1821c0e3f2..1696466eae00a 100644 --- a/clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp +++ b/clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp @@ -68,5 +68,7 @@ void failedTypeIdentitySpecialization() { // expected-error@-1 {{incomplete type 'std::type_identity<Bad>' where a complete type is required}} // expected-note@#incomplete_specialization {{forward declaration of 'std::type_identity<Bad>'}} Bad *b = new Bad; + // expected-error@-1 {{incomplete type 'std::type_identity<Bad>' where a complete type is required}} + // expected-note@#incomplete_specialization {{forward declaration of 'std::type_identity<Bad>'}} } #endif >From 3093caf42cee733b886116cd3127c10291b91bd9 Mon Sep 17 00:00:00 2001 From: Oliver Hunt <[email protected]> Date: Tue, 28 Jul 2026 13:20:24 -0700 Subject: [PATCH 5/5] Improved MSVC diagnostics, fixed some bugs identifier by (sigh) claude. LLM usage: Depressingly claude actually did something right: - Identified align_val_t being cached alongside size_t was likely unsound, which was easy enough to create a test for. - It also noted that tryBuildStdTypeIdentity could be reentrant, and was able to construct a test case when i could not. That is now test #5 in type-aware-new-invalid-type-identity.cpp. An invalid type_identity defined as: ```cpp template <class T> struct type_identity { using type = decltype(new T); }; ``` triggers re-entrant evaluation of getTypeIdentityArgument invalidating the insertion slot. --- .../clang/Basic/DiagnosticSemaKinds.td | 5 +- clang/lib/Sema/SemaExprCXX.cpp | 53 ++++++++++--------- .../SemaCXX/microsoft-new-array-fallback.cpp | 5 +- ...type-aware-new-invalid-alignvalt-cache.cpp | 25 +++++++++ .../type-aware-new-invalid-type-identity.cpp | 26 ++++++--- 5 files changed, 76 insertions(+), 38 deletions(-) create mode 100644 clang/test/SemaCXX/type-aware-new-invalid-alignvalt-cache.cpp diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index ef1478b15c9fc..8fc23ab2f67be 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -5524,8 +5524,9 @@ def err_no_viable_destructor : Error< def err_ambiguous_destructor : Error< "destructor of class %0 is ambiguous">; -def note_ovl_msvc_allocation_fallback_failed : Note< - "MSVC compatibility fall back to %0 failed">; +def note_ovl_ms_allocation_fallback_failed : Note< + "Microsoft compatibility array allocation fallback to " + "'::operator new(size_t)' failed">; def err_ovl_no_viable_object_call : Error< "no matching function for call to object of type %0">; diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 796244a1eab8a..f15dca03bf5e5 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -2726,10 +2726,9 @@ bool Sema::CheckAllocatedType(QualType AllocType, SourceLocation Loc, } static void diagnoseNoViableFunctionForAllocationOverloadResolution( - Sema &S, const LookupResult &R, - const std::optional<LookupResult> &MSVCFallback, SourceRange Range, - ArrayRef<Expr *> Args, OverloadCandidateSet &Candidates, - OverloadCandidateSet *AlignedCandidates, Expr *AlignArg) { + Sema &S, const LookupResult &R, SourceRange Range, ArrayRef<Expr *> Args, + OverloadCandidateSet &Candidates, OverloadCandidateSet *AlignedCandidates, + Expr *AlignArg, bool IncludedMSVCFallback) { // 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), // diagnose the reason for the error. @@ -2789,10 +2788,9 @@ static void diagnoseNoViableFunctionForAllocationOverloadResolution( AlignedCandidates->NoteCandidates(S, AlignedArgs, AlignedCands, "", R.getNameLoc()); Candidates.NoteCandidates(S, Args, Cands, "", R.getNameLoc()); - if (MSVCFallback) - S.Diag(MSVCFallback->getNameLoc(), - diag::note_ovl_msvc_allocation_fallback_failed) - << MSVCFallback->getLookupName() << Range; + if (IncludedMSVCFallback) + S.Diag(R.getNameLoc(), diag::note_ovl_ms_allocation_fallback_failed) + << Range; } enum class AllocatorResolveResult { Success, Retry, Error }; @@ -2891,14 +2889,18 @@ static void LookupGlobalDeallocationFunctions(Sema &S, SourceLocation Loc, } static void -DiagnoseAllocationLookupFailure(Sema &SemaRef, const LookupResult &BaseLookup, +DiagnoseAllocationLookupFailure(Sema &SemaRef, const LookupResult &R, SourceRange Range, AllocationArgumentSet &ArgumentCandidates, ArrayRef<Expr *> PlacementArguments) { - std::optional<LookupResult> MSVCFallback; ImplicitAllocationArguments *UnalignedArgumentList = nullptr; ImplicitAllocationArguments *AlignedArgumentList = nullptr; + bool IncludedMSVCFallback = false; for (ImplicitAllocationArguments &AllocationArguments : ArgumentCandidates) { + if (AllocationArguments.IsMSVCCompatibilityFallback) { + IncludedMSVCFallback = true; + continue; + } if (AllocationArguments.PassTypeIdentity == TypeAwareAllocationMode::Yes) continue; if (AllocationArguments.PassAlignment == AlignedAllocationMode::Yes) @@ -2915,9 +2917,9 @@ DiagnoseAllocationLookupFailure(Sema &SemaRef, const LookupResult &BaseLookup, auto Rerun = [&](ImplicitAllocationArguments &ArgumentList, OverloadCandidateSet &Candidates, SmallVectorImpl<Expr *> &Args) { + std::optional<LookupResult> MSVCFallback; const LookupResult &LocalLookup = - ArgumentList.updateLookupForMSVCCompatibility(SemaRef, BaseLookup, - MSVCFallback); + ArgumentList.updateLookupForMSVCCompatibility(SemaRef, R, MSVCFallback); llvm::append_range(Args, ArgumentList.getImplicitArguments()); llvm::append_range(Args, PlacementArguments); FunctionDecl *Unused = nullptr; @@ -2927,39 +2929,35 @@ DiagnoseAllocationLookupFailure(Sema &SemaRef, const LookupResult &BaseLookup, std::optional<OverloadCandidateSet> AlignedCandidates; Expr *AlignArg = nullptr; if (AlignedArgumentList) { - AlignedCandidates.emplace(BaseLookup.getNameLoc(), - OverloadCandidateSet::CSK_Normal); + AlignedCandidates.emplace(R.getNameLoc(), OverloadCandidateSet::CSK_Normal); SmallVector<Expr *, 4> AlignedArgs; Rerun(*AlignedArgumentList, *AlignedCandidates, AlignedArgs); AlignArg = AlignedArgumentList->getAlignmentArgument(); } - OverloadCandidateSet UnalignedCandidates(BaseLookup.getNameLoc(), + OverloadCandidateSet UnalignedCandidates(R.getNameLoc(), OverloadCandidateSet::CSK_Normal); SmallVector<Expr *, 4> UnalignedArgs; Rerun(*UnalignedArgumentList, UnalignedCandidates, UnalignedArgs); diagnoseNoViableFunctionForAllocationOverloadResolution( - SemaRef, BaseLookup, MSVCFallback, Range, UnalignedArgs, - UnalignedCandidates, AlignedCandidates ? &*AlignedCandidates : nullptr, - AlignArg); + SemaRef, R, Range, UnalignedArgs, UnalignedCandidates, + AlignedCandidates ? &*AlignedCandidates : nullptr, AlignArg, + IncludedMSVCFallback); } std::optional<Expr *> Sema::getTypeIdentityArgument(QualType Type, SourceLocation Loc) { - auto [Slot, Inserted] = - AllocationTypeIdentityArguments.insert({Type, nullptr}); + if (auto Found = AllocationTypeIdentityArguments.find(Type); Found != AllocationTypeIdentityArguments.end()) + return Found->second; - if (!Inserted) - return Slot->second; - - QualType TypeIdentity = tryBuildStdTypeIdentity(Type, SourceLocation()); + QualType TypeIdentity = tryBuildStdTypeIdentity(Type, Loc); if (TypeIdentity.isNull() || RequireCompleteType(Loc, TypeIdentity, diag::err_incomplete_type)) { - AllocationTypeIdentityArguments.erase(Slot); return std::nullopt; } + Expr *TypeIdentityArgument = new (Context) CXXScalarValueInitExpr(TypeIdentity, nullptr, Loc); - Slot->second = TypeIdentityArgument; + AllocationTypeIdentityArguments.insert({Type, TypeIdentityArgument}); return TypeIdentityArgument; } @@ -3017,6 +3015,9 @@ Sema::resolveAllocationArguments(LookupResult &R, unsigned SizeTyWidth = Context.getTypeSize(SizeTy); AllocationSizeExpr = IntegerLiteral::Create( Context, llvm::APInt::getZero(SizeTyWidth), SizeTy, SourceLocation()); + } + if (!AllocationAlignmentExpr) { + DeclareGlobalNewDelete(); if (EnumDecl *StdAlignValT = getStdAlignValT()) { QualType AlignValT = Context.getCanonicalTagType(StdAlignValT); AllocationAlignmentExpr = new (Context) diff --git a/clang/test/SemaCXX/microsoft-new-array-fallback.cpp b/clang/test/SemaCXX/microsoft-new-array-fallback.cpp index 80bb65363f828..7e2ca1bc5a4c9 100644 --- a/clang/test/SemaCXX/microsoft-new-array-fallback.cpp +++ b/clang/test/SemaCXX/microsoft-new-array-fallback.cpp @@ -2,6 +2,7 @@ typedef __SIZE_TYPE__ size_t; +void *operator new[](size_t); // #new_array_decl void *operator new(size_t); // #new_decl struct Tag {}; @@ -9,7 +10,7 @@ struct Tag {}; void f() { int *p = new (Tag{}) int[4]; // #new_expr // expected-error@#new_expr {{no matching function for call to 'operator new[]'}} - // expected-note@#new_decl {{candidate function not viable: requires 1 argument, but 2 were provided}} - // expected-note@#new_expr {{MSVC compatibility fall back to 'operator new' failed}} + // expected-note@#new_array_decl {{candidate function not viable: requires 1 argument, but 2 were provided}} + // expected-note@#new_expr {{Microsoft compatibility array allocation fallback to '::operator new(size_t)' failed}} (void)p; } diff --git a/clang/test/SemaCXX/type-aware-new-invalid-alignvalt-cache.cpp b/clang/test/SemaCXX/type-aware-new-invalid-alignvalt-cache.cpp new file mode 100644 index 0000000000000..d4db8e357b845 --- /dev/null +++ b/clang/test/SemaCXX/type-aware-new-invalid-alignvalt-cache.cpp @@ -0,0 +1,25 @@ +// RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -std=c++26 \ +// RUN: -fno-aligned-allocation -Wno-ext-cxx-type-aware-allocators -verify %s + +void first() { + new int; +} + +namespace std { + using size_t = __SIZE_TYPE__; + template <class T> struct type_identity { using type = T; }; + enum class align_val_t : size_t {}; +} + +template <class T> void *operator new(std::type_identity<T>, std::size_t, std::align_val_t) = delete; // #new_decl +template <class T> void operator delete(std::type_identity<T>, void *, std::size_t, std::align_val_t) = delete; + +struct Foo { + int x; +}; + +void second() { + (void)new Foo; // #new_expr + // expected-error@#new_expr {{call to deleted function 'operator new'}} + // expected-note@#new_decl {{candidate function [with T = Foo] has been explicitly deleted}} +} diff --git a/clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp b/clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp index 1696466eae00a..7456e2db7eeda 100644 --- a/clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp +++ b/clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp @@ -3,6 +3,7 @@ // RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -Wno-ext-cxx-type-aware-allocators -std=c++26 -DINVALID_TYPE_IDENTITY_VERSION=2 // RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -Wno-ext-cxx-type-aware-allocators -std=c++26 -DINVALID_TYPE_IDENTITY_VERSION=3 // RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -Wno-ext-cxx-type-aware-allocators -std=c++26 -DINVALID_TYPE_IDENTITY_VERSION=4 +// RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -Wno-ext-cxx-type-aware-allocators -std=c++26 -DINVALID_TYPE_IDENTITY_VERSION=5 // RUN: %clang_cc1 -triple arm64-apple-macosx -fsyntax-only -verify %s -Wno-ext-cxx-type-aware-allocators -std=c++26 namespace std { @@ -29,6 +30,11 @@ namespace std { template <class T> struct inner {}; template <class T> using type_identity = inner<T>; #define TYPE_IDENTITY(T) std::type_identity<T> +#elif INVALID_TYPE_IDENTITY_VERSION==5 +template <class T> struct type_identity { // #reentrant_type_identity_decl + using type = decltype(new T); // #reentrant_type_identity_type_decl +}; +#define TYPE_IDENTITY(T) std::type_identity<T> #endif using size_t = __SIZE_TYPE__; enum class align_val_t : long {}; @@ -37,6 +43,14 @@ namespace std { template <class T> void *operator new(TYPE_IDENTITY(T), std::size_t, std::align_val_t); // #operator_new template <class T> void operator delete(TYPE_IDENTITY(T), void*, std::size_t, std::align_val_t); // #operator_delete +using size_t = __SIZE_TYPE__; +struct TestType {}; + +void reentrant_type_identity() { + TestType *t = new TestType; // #reentrant_new + delete t; +} + // These error messages aren't great, but they fall out of the way we model // alias types. Getting them in this way requires extremely unlikely code to be // used, so this is not terrible. @@ -47,16 +61,12 @@ template <class T> void operator delete(TYPE_IDENTITY(T), void*, std::size_t, st #elif INVALID_TYPE_IDENTITY_VERSION==4 // expected-error@#operator_new {{'operator new' cannot take a dependent type as its 1st parameter; use size_t ('unsigned long') instead}} // expected-error@#operator_delete {{'operator delete' cannot take a dependent type as its 1st parameter; use 'void *' instead}} +#elif INVALID_TYPE_IDENTITY_VERSION==5 +// expected-error@#reentrant_type_identity_type_decl {{incomplete type 'std::type_identity<TestType>' where a complete type is required}} +// expected-note@#reentrant_type_identity_decl {{definition of 'std::type_identity<TestType>' is not complete until the closing '}'}} +// expected-note@#reentrant_new {{in instantiation of template class 'std::type_identity<TestType>' requested here}} #endif -using size_t = __SIZE_TYPE__; -struct TestType {}; - -void f() { - TestType *t = new TestType; - delete t; -} - #if !defined(INVALID_TYPE_IDENTITY_VERSION) struct Bad {}; template <> struct std::type_identity<Bad>; // #incomplete_specialization _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
