Author: Oliver Hunt Date: 2026-08-08T17:05:53-07:00 New Revision: cd67cfecb1a154d0759783ae926f8b44c104d7d3
URL: https://github.com/llvm/llvm-project/commit/cd67cfecb1a154d0759783ae926f8b44c104d7d3 DIFF: https://github.com/llvm/llvm-project/commit/cd67cfecb1a154d0759783ae926f8b44c104d7d3.diff LOG: [clang] Simplify the overload resolution logic for operator new and new[] (#211482) 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 LLM usage: Claude found align_val_t caching bug, and found and created a test for reentrant `getTypeIdentityArgument`+`tryBuildStdTypeIdentity` Added: clang/include/clang/Sema/DynamicAllocationArgumentsCXX.h clang/test/SemaCXX/microsoft-new-array-fallback.cpp clang/test/SemaCXX/type-aware-new-invalid-alignvalt-cache.cpp Modified: clang/include/clang/Basic/DiagnosticSemaKinds.td clang/include/clang/Sema/Sema.h clang/lib/Sema/SemaCoroutine.cpp clang/lib/Sema/SemaExprCXX.cpp clang/test/SemaCXX/type-aware-new-invalid-type-identity.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index b57ff6c197d1d..51fe39f3733b1 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -5524,6 +5524,10 @@ def err_no_viable_destructor : Error< def err_ambiguous_destructor : Error< "destructor of class %0 is ambiguous">; +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">; def err_ovl_ambiguous_object_call : Error< diff --git a/clang/include/clang/Sema/DynamicAllocationArgumentsCXX.h b/clang/include/clang/Sema/DynamicAllocationArgumentsCXX.h new file mode 100644 index 0000000000000..7ce800111dfe6 --- /dev/null +++ b/clang/include/clang/Sema/DynamicAllocationArgumentsCXX.h @@ -0,0 +1,68 @@ +//===- 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; + } + + const LookupResult & + updateLookupForMSVCCompatibility(Sema &, const LookupResult &, + std::optional<LookupResult> &) const; + TypeAwareAllocationMode PassTypeIdentity; + AlignedAllocationMode PassAlignment; + bool IsMSVCCompatibilityFallback; + +private: + ImplicitAllocationArguments(Sema &SemaRef, Expr *TypeIdentityArg, + Expr *SizeArg, Expr *AlignArg, + bool IsMSVCCompatibilityFallback); + + // Type-identity, size, and alignment + static constexpr unsigned MaxImplicitArguments = 3; + unsigned ArgumentCount; + Expr *ImplicitArguments[MaxImplicitArguments]; +}; + +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 778c1a2f5c427..b1d2488d2163b 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -136,6 +136,7 @@ struct DeductionFailureInfo; class DependentDiagnostic; class Designation; class IdentifierInfo; +struct ImplicitAllocationArguments; class ImplicitConversionSequence; typedef MutableArrayRef<ImplicitConversionSequence> ConversionSequenceList; class InitializationKind; @@ -157,6 +158,7 @@ enum OverloadCandidateRewriteKind : unsigned; class OverloadCandidateSet; class Preprocessor; struct APINotesSelectorDiagnosticState; +struct ResolvedAllocation; class SemaAMDGPU; class SemaARM; class SemaAVR; @@ -224,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); @@ -8659,12 +8665,11 @@ class Sema final : public SemaBase { /// 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: @@ -8968,6 +8973,19 @@ class Sema final : public SemaBase { void AnalyzeDeleteExprMismatch(FieldDecl *Field, SourceLocation DeleteLoc, bool DeleteWasArrayForm); + std::optional<AllocationArgumentSet> + resolveAllocationArguments(LookupResult &R, + const ImplicitAllocationParameters &, + ArrayRef<Expr *> PlacementArguments); + + // Attempts to construct the type identity argument for the call to a + // type aware operator new. Returns null on failure. + Expr *tryGetTypeIdentityArgument(QualType Type, SourceLocation); + + Expr *AllocationSizeExpr = nullptr; + Expr *AllocationAlignmentExpr = nullptr; + llvm::DenseMap<QualType, Expr *> AllocationTypeIdentityArguments; + ///@} // diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp index 7f9b1d642cf9d..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" @@ -1478,13 +1479,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..6cc80c8254a78 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" @@ -2438,6 +2439,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 +2456,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, + /*IsArray=*/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 +2487,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)) @@ -2723,9 +2726,9 @@ bool Sema::CheckAllocatedType(QualType AllocType, SourceLocation Loc, } static void diagnoseNoViableFunctionForAllocationOverloadResolution( - Sema &S, LookupResult &R, SourceRange Range, ArrayRef<Expr *> Args, + Sema &S, const LookupResult &R, SourceRange Range, ArrayRef<Expr *> Args, OverloadCandidateSet &Candidates, OverloadCandidateSet *AlignedCandidates, - Expr *AlignArg) { + 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. @@ -2785,114 +2788,81 @@ static void diagnoseNoViableFunctionForAllocationOverloadResolution( AlignedCandidates->NoteCandidates(S, AlignedArgs, AlignedCands, "", R.getNameLoc()); Candidates.NoteCandidates(S, Args, Cands, "", R.getNameLoc()); + if (IncludedMSVCFallback) + S.Diag(R.getNameLoc(), diag::note_ovl_ms_allocation_fallback_failed) + << Range; } -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; - } - - OverloadCandidateSet Candidates(R.getNameLoc(), - OverloadCandidateSet::CSK_Normal); - for (LookupResult::iterator Alloc = R.begin(), AllocEnd = R.end(); +enum class AllocatorResolveResult { Success, Retry, Error }; +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 = LocalLookup.begin(), + AllocEnd = LocalLookup.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); } // Do the resolution. OverloadCandidateSet::iterator Best; - switch (Candidates.BestViableFunction(S, R.getNameLoc(), Best)) { + switch (Candidates.BestViableFunction(S, LocalLookup.getNameLoc(), Best)) { case OR_Success: { - // Got one! 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 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) { Candidates.NoteCandidates( - PartialDiagnosticAt(R.getNameLoc(), + PartialDiagnosticAt(LocalLookup.getNameLoc(), S.PDiag(diag::err_ovl_ambiguous_call) - << R.getLookupName() << Range), - S, OCD_AmbiguousCandidates, Args); + << LocalLookup.getLookupName() << Range), + 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; + S.DiagnoseUseOfDeletedFunction(LocalLookup.getNameLoc(), Range, + LocalLookup.getLookupName(), Candidates, + Best->Function, TrialArguments); + return AllocatorResolveResult::Error; } } llvm_unreachable("Unreachable, bad result from BestViableFunction"); @@ -2918,55 +2888,187 @@ 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; +static void +DiagnoseAllocationLookupFailure(Sema &SemaRef, const LookupResult &R, + SourceRange Range, + AllocationArgumentSet &ArgumentCandidates, + ArrayRef<Expr *> PlacementArguments) { + 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) + 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) { + assert(!ArgumentList.IsMSVCCompatibilityFallback); + 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, + IncludedMSVCFallback); +} + +Expr *Sema::tryGetTypeIdentityArgument(QualType Type, SourceLocation Loc) { + if (auto Found = AllocationTypeIdentityArguments.find(Type); + Found != AllocationTypeIdentityArguments.end()) + return Found->second; + + QualType TypeIdentity = tryBuildStdTypeIdentity(Type, Loc); + if (TypeIdentity.isNull() || + RequireCompleteType(Loc, TypeIdentity, diag::err_incomplete_type)) + return nullptr; + + Expr *TypeIdentityArgument = + new (Context) CXXScalarValueInitExpr(TypeIdentity, nullptr, Loc); + AllocationTypeIdentityArguments.insert({Type, TypeIdentityArgument}); + return TypeIdentityArgument; +} + +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; + } +} + +const LookupResult & +ImplicitAllocationArguments::updateLookupForMSVCCompatibility( + Sema &S, const LookupResult &BaseLookup, + std::optional<LookupResult> &Buffer) const { + if (!IsMSVCCompatibilityFallback) + 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. + 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(Fallback, S.Context.getTranslationUnitDecl()); + return Fallback; +} + +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 (!AllocationAlignmentExpr) { + DeclareGlobalNewDelete(); + if (EnumDecl *StdAlignValT = getStdAlignValT()) { + QualType AlignValT = Context.getCanonicalTagType(StdAlignValT); + AllocationAlignmentExpr = new (Context) + CXXScalarValueInitExpr(AlignValT, nullptr, SourceLocation()); + } + } + + AllocationArgumentSet FoundArguments; 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)) - return true; - if (Operator) - return false; + Expr *TypeIdentityArgument = + tryGetTypeIdentityArgument(IAP.Type, R.getNameLoc()); + if (!TypeIdentityArgument) + return std::nullopt; + + Expr *AlignmentExpr = AllocationAlignmentExpr; + if (!PlacementArguments.empty() && + PlacementArguments.front()->getType()->isAlignValT()) + AlignmentExpr = nullptr; + FoundArguments.push_back(ImplicitAllocationArguments( + *this, TypeIdentityArgument, AllocationSizeExpr, AlignmentExpr, + /*IsMSVCCompatibilityFallback=*/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.push_back(AlignedArguments); + FoundArguments.push_back(UnalignedArguments); + + // The MSVC global fallback path + if (getLangOpts().MSVCCompat && + R.getLookupName().getCXXOverloadedOperator() == OO_Array_New) + FoundArguments.push_back(ImplicitAllocationArguments( + *this, /*TypeIdentityArg=*/nullptr, AllocationSizeExpr, + /*AlignArg=*/nullptr, /*IsMSVCCompatibilityFallback=*/true)); + return FoundArguments; +} - // 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) { +std::optional<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 +3081,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 +3092,10 @@ 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); + ResolvedAllocation Result = {/*OperatorNew=*/nullptr, + /*OperatorDelete=*/nullptr, + RequestedIAP, + {}}; // Find the allocation function. { @@ -3053,14 +3113,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 +3131,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 +3140,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> ArgumentListCandidates = + resolveAllocationArguments(R, RequestedIAP, PlaceArgs); + if (!ArgumentListCandidates) + return std::nullopt; + + for (ImplicitAllocationArguments &ArgumentList : *ArgumentListCandidates) { + 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, *ArgumentListCandidates, + 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 +3202,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 +3226,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,18 +3240,19 @@ 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::OptionallyTyped - : DeallocLookupMode::Untyped; + DeallocLookupMode LookupMode = + isTypeAwareAllocation(RequestedIAP.PassTypeIdentity) + ? DeallocLookupMode::OptionallyTyped + : DeallocLookupMode::Untyped; LookupGlobalDeallocationFunctions(*this, StartLoc, FoundDelete, LookupMode, DeleteName); } @@ -3188,7 +3276,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 +3300,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 +3343,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 +3357,7 @@ 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, RequestedIAP.PassTypeIdentity, alignedAllocationModeFromBool( hasNewExtendedAlignment(*this, AllocElemType)), sizedDeallocationModeFromBool(FoundGlobalDelete)}; @@ -3290,7 +3378,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 +3437,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 +3452,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 +3466,7 @@ bool Sema::FindAllocationFunctions( diag::note_member_declared_here) << DeleteName; } - return false; + return Result; } void Sema::DeclareGlobalNewDelete() { 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..7e2ca1bc5a4c9 --- /dev/null +++ b/clang/test/SemaCXX/microsoft-new-array-fallback.cpp @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -fms-compatibility -fsyntax-only -verify -std=c++11 %s + +typedef __SIZE_TYPE__ size_t; + +void *operator new[](size_t); // #new_array_decl +void *operator new(size_t); // #new_decl + +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_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..b1b9e67d6203a --- /dev/null +++ b/clang/test/SemaCXX/type-aware-new-invalid-alignvalt-cache.cpp @@ -0,0 +1,32 @@ +// 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; }; +} + +void second() { + new float; +} + +namespace std { + 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 third() { + (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 502f4fab6b519..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,11 +3,11 @@ // 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 { #if !defined(INVALID_TYPE_IDENTITY_VERSION) - // expected-no-diagnostics template <class T> struct type_identity { }; #define TYPE_IDENTITY(T) std::type_identity<T> @@ -30,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 {}; @@ -38,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. @@ -48,12 +61,24 @@ 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 {}; +#if !defined(INVALID_TYPE_IDENTITY_VERSION) +struct Bad {}; +template <> struct std::type_identity<Bad>; // #incomplete_specialization -void f() { - TestType *t = new TestType; - delete t; +// 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; + // 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
