[clang] [clang] Simplify the overload resolution logic for operator new and new[] (PR #211482)
Oliver Hunt via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 28 17:58:34 PDT 2026
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 <oliver at apple.com>
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 at -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 at -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 <oliver at apple.com>
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 <oliver at apple.com>
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 <oliver at apple.com>
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 at -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 at -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 at -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 <oliver at apple.com>
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
More information about the cfe-commits
mailing list