Index: cfe/trunk/include/clang/Basic/Builtins.def =================================================================== --- cfe/trunk/include/clang/Basic/Builtins.def +++ cfe/trunk/include/clang/Basic/Builtins.def @@ -1371,8 +1371,8 @@ // Clang builtins (not available in GCC). BUILTIN(__builtin_addressof, "v*v&", "nct") -BUILTIN(__builtin_operator_new, "v*z", "c") -BUILTIN(__builtin_operator_delete, "vv*", "n") +BUILTIN(__builtin_operator_new, "v*z", "tc") +BUILTIN(__builtin_operator_delete, "vv*", "tn") BUILTIN(__builtin_char_memchr, "c*cC*iz", "n") // Safestack builtins Index: cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td +++ cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td @@ -7627,6 +7627,11 @@ "alignment parameter">; def note_implicit_delete_this_in_destructor_here : Note< "while checking implicit 'delete this' for virtual destructor">; +def err_builtin_operator_new_delete_not_usual : Error< + "call to '%select{__builtin_operator_new|__builtin_operator_delete}0' " + "selects non-usual %select{allocation|deallocation}0 function">; +def note_non_usual_function_declared_here : Note< + "non-usual %0 declared here">; // C++ literal operators def err_literal_operator_outside_namespace : Error< Index: cfe/trunk/include/clang/Sema/Sema.h =================================================================== --- cfe/trunk/include/clang/Sema/Sema.h +++ cfe/trunk/include/clang/Sema/Sema.h @@ -10376,6 +10376,8 @@ ExprResult SemaBuiltinNontemporalOverloaded(ExprResult TheCallResult); ExprResult SemaAtomicOpsOverloaded(ExprResult TheCallResult, AtomicExpr::AtomicOp Op); + ExprResult SemaBuiltinOperatorNewDeleteOverloaded(ExprResult TheCallResult, + bool IsDelete); bool SemaBuiltinConstantArg(CallExpr *TheCall, int ArgNum, llvm::APSInt &Result); bool SemaBuiltinConstantArgRange(CallExpr *TheCall, int ArgNum, Index: cfe/trunk/lib/CodeGen/CGBuiltin.cpp =================================================================== --- cfe/trunk/lib/CodeGen/CGBuiltin.cpp +++ cfe/trunk/lib/CodeGen/CGBuiltin.cpp @@ -2611,11 +2611,12 @@ case Builtin::BI__builtin_addressof: return RValue::get(EmitLValue(E->getArg(0)).getPointer()); case Builtin::BI__builtin_operator_new: - return EmitBuiltinNewDeleteCall(FD->getType()->castAs(), - E->getArg(0), false); + return EmitBuiltinNewDeleteCall( + E->getCallee()->getType()->castAs(), E, false); case Builtin::BI__builtin_operator_delete: - return EmitBuiltinNewDeleteCall(FD->getType()->castAs(), - E->getArg(0), true); + return EmitBuiltinNewDeleteCall( + E->getCallee()->getType()->castAs(), E, true); + case Builtin::BI__noop: // __noop always evaluates to an integer literal zero. return RValue::get(ConstantInt::get(IntTy, 0)); Index: cfe/trunk/lib/CodeGen/CGExprCXX.cpp =================================================================== --- cfe/trunk/lib/CodeGen/CGExprCXX.cpp +++ cfe/trunk/lib/CodeGen/CGExprCXX.cpp @@ -1307,19 +1307,19 @@ } RValue CodeGenFunction::EmitBuiltinNewDeleteCall(const FunctionProtoType *Type, - const Expr *Arg, + const CallExpr *TheCall, bool IsDelete) { CallArgList Args; - const Stmt *ArgS = Arg; - EmitCallArgs(Args, *Type->param_type_begin(), llvm::makeArrayRef(ArgS)); + EmitCallArgs(Args, Type->getParamTypes(), TheCall->arguments()); // Find the allocation or deallocation function that we're calling. ASTContext &Ctx = getContext(); DeclarationName Name = Ctx.DeclarationNames .getCXXOperatorName(IsDelete ? OO_Delete : OO_New); + for (auto *Decl : Ctx.getTranslationUnitDecl()->lookup(Name)) if (auto *FD = dyn_cast(Decl)) if (Ctx.hasSameType(FD->getType(), QualType(Type, 0))) - return EmitNewDeleteCall(*this, cast(Decl), Type, Args); + return EmitNewDeleteCall(*this, FD, Type, Args); llvm_unreachable("predeclared global operator new/delete is missing"); } Index: cfe/trunk/lib/CodeGen/CodeGenFunction.h =================================================================== --- cfe/trunk/lib/CodeGen/CodeGenFunction.h +++ cfe/trunk/lib/CodeGen/CodeGenFunction.h @@ -2356,7 +2356,7 @@ CharUnits CookieSize = CharUnits()); RValue EmitBuiltinNewDeleteCall(const FunctionProtoType *Type, - const Expr *Arg, bool IsDelete); + const CallExpr *TheCallExpr, bool IsDelete); llvm::Value *EmitCXXTypeidExpr(const CXXTypeidExpr *E); llvm::Value *EmitDynamicCast(Address V, const CXXDynamicCastExpr *DCE); Index: cfe/trunk/lib/Lex/PPMacroExpansion.cpp =================================================================== --- cfe/trunk/lib/Lex/PPMacroExpansion.cpp +++ cfe/trunk/lib/Lex/PPMacroExpansion.cpp @@ -1801,12 +1801,21 @@ [this](Token &Tok, bool &HasLexedNextToken) -> int { IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this, diag::err_feature_check_malformed); + const LangOptions &LangOpts = getLangOpts(); if (!II) return false; - else if (II->getBuiltinID() != 0) + else if (II->getBuiltinID() != 0) { + switch (II->getBuiltinID()) { + case Builtin::BI__builtin_operator_new: + case Builtin::BI__builtin_operator_delete: + // denotes date of behavior change to support calling arbitrary + // usual allocation and deallocation functions. Required by libc++ + return 201802; + default: + return true; + } return true; - else { - const LangOptions &LangOpts = getLangOpts(); + } else { return llvm::StringSwitch(II->getName()) .Case("__make_integer_seq", LangOpts.CPlusPlus) .Case("__type_pack_element", LangOpts.CPlusPlus) Index: cfe/trunk/lib/Sema/SemaChecking.cpp =================================================================== --- cfe/trunk/lib/Sema/SemaChecking.cpp +++ cfe/trunk/lib/Sema/SemaChecking.cpp @@ -1097,20 +1097,14 @@ return ExprError(); break; case Builtin::BI__builtin_operator_new: - case Builtin::BI__builtin_operator_delete: - if (!getLangOpts().CPlusPlus) { - Diag(TheCall->getExprLoc(), diag::err_builtin_requires_language) - << (BuiltinID == Builtin::BI__builtin_operator_new - ? "__builtin_operator_new" - : "__builtin_operator_delete") - << "C++"; - return ExprError(); - } - // CodeGen assumes it can find the global new and delete to call, - // so ensure that they are declared. - DeclareGlobalNewDelete(); - break; - + case Builtin::BI__builtin_operator_delete: { + bool IsDelete = BuiltinID == Builtin::BI__builtin_operator_delete; + ExprResult Res = + SemaBuiltinOperatorNewDeleteOverloaded(TheCallResult, IsDelete); + if (Res.isInvalid()) + CorrectDelayedTyposInExpr(TheCallResult.get()); + return Res; + } // check secure string manipulation functions where overflows // are detectable at compile time case Builtin::BI__builtin___memcpy_chk: Index: cfe/trunk/lib/Sema/SemaExprCXX.cpp =================================================================== --- cfe/trunk/lib/Sema/SemaExprCXX.cpp +++ cfe/trunk/lib/Sema/SemaExprCXX.cpp @@ -1443,7 +1443,7 @@ CUDAPref = S.IdentifyCUDAPreference(Caller, FD); } - operator bool() const { return FD; } + explicit operator bool() const { return FD; } bool isBetterThan(const UsualDeallocFnInfo &Other, bool WantSize, bool WantAlign) const { @@ -2271,7 +2271,6 @@ llvm_unreachable("Unreachable, bad result from BestViableFunction"); } - /// FindAllocationFunctions - Finds the overloads of operator new and delete /// that are appropriate for the allocation. bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range, @@ -3343,6 +3342,128 @@ return Result; } +static bool resolveBuiltinNewDeleteOverload(Sema &S, CallExpr *TheCall, + bool IsDelete, + FunctionDecl *&Operator) { + + DeclarationName NewName = S.Context.DeclarationNames.getCXXOperatorName( + IsDelete ? OO_Delete : OO_New); + + LookupResult R(S, NewName, TheCall->getLocStart(), Sema::LookupOrdinaryName); + S.LookupQualifiedName(R, S.Context.getTranslationUnitDecl()); + assert(!R.empty() && "implicitly declared allocation functions not found"); + assert(!R.isAmbiguous() && "global allocation functions are ambiguous"); + + // We do our own custom access checks below. + R.suppressDiagnostics(); + + SmallVector Args(TheCall->arg_begin(), TheCall->arg_end()); + OverloadCandidateSet Candidates(R.getNameLoc(), + OverloadCandidateSet::CSK_Normal); + for (LookupResult::iterator FnOvl = R.begin(), FnOvlEnd = R.end(); + FnOvl != FnOvlEnd; ++FnOvl) { + // Even member operator new/delete are implicitly treated as + // static, so don't use AddMemberCandidate. + NamedDecl *D = (*FnOvl)->getUnderlyingDecl(); + + if (FunctionTemplateDecl *FnTemplate = dyn_cast(D)) { + S.AddTemplateOverloadCandidate(FnTemplate, FnOvl.getPair(), + /*ExplicitTemplateArgs=*/nullptr, Args, + Candidates, + /*SuppressUserConversions=*/false); + continue; + } + + FunctionDecl *Fn = cast(D); + S.AddOverloadCandidate(Fn, FnOvl.getPair(), Args, Candidates, + /*SuppressUserConversions=*/false); + } + + SourceRange Range = TheCall->getSourceRange(); + + // Do the resolution. + OverloadCandidateSet::iterator Best; + switch (Candidates.BestViableFunction(S, R.getNameLoc(), Best)) { + case OR_Success: { + // Got one! + FunctionDecl *FnDecl = Best->Function; + assert(R.getNamingClass() == nullptr && + "class members should not be considered"); + + if (!FnDecl->isReplaceableGlobalAllocationFunction()) { + S.Diag(R.getNameLoc(), diag::err_builtin_operator_new_delete_not_usual) + << (IsDelete ? 1 : 0) << Range; + S.Diag(FnDecl->getLocation(), diag::note_non_usual_function_declared_here) + << R.getLookupName() << FnDecl->getSourceRange(); + return true; + } + + Operator = FnDecl; + return false; + } + + case OR_No_Viable_Function: + S.Diag(R.getNameLoc(), diag::err_ovl_no_viable_function_in_call) + << R.getLookupName() << Range; + Candidates.NoteCandidates(S, OCD_AllCandidates, Args); + return true; + + case OR_Ambiguous: + S.Diag(R.getNameLoc(), diag::err_ovl_ambiguous_call) + << R.getLookupName() << Range; + Candidates.NoteCandidates(S, OCD_ViableCandidates, Args); + return true; + + case OR_Deleted: { + S.Diag(R.getNameLoc(), diag::err_ovl_deleted_call) + << Best->Function->isDeleted() << R.getLookupName() + << S.getDeletedOrUnavailableSuffix(Best->Function) << Range; + Candidates.NoteCandidates(S, OCD_AllCandidates, Args); + return true; + } + } + llvm_unreachable("Unreachable, bad result from BestViableFunction"); +} + +ExprResult +Sema::SemaBuiltinOperatorNewDeleteOverloaded(ExprResult TheCallResult, + bool IsDelete) { + CallExpr *TheCall = cast(TheCallResult.get()); + if (!getLangOpts().CPlusPlus) { + Diag(TheCall->getExprLoc(), diag::err_builtin_requires_language) + << (IsDelete ? "__builtin_operator_delete" : "__builtin_operator_new") + << "C++"; + return ExprError(); + } + // CodeGen assumes it can find the global new and delete to call, + // so ensure that they are declared. + DeclareGlobalNewDelete(); + + FunctionDecl *OperatorNewOrDelete = nullptr; + if (resolveBuiltinNewDeleteOverload(*this, TheCall, IsDelete, + OperatorNewOrDelete)) + return ExprError(); + assert(OperatorNewOrDelete && "should be found"); + + TheCall->setType(OperatorNewOrDelete->getReturnType()); + for (unsigned i = 0; i != TheCall->getNumArgs(); ++i) { + QualType ParamTy = OperatorNewOrDelete->getParamDecl(i)->getType(); + InitializedEntity Entity = + InitializedEntity::InitializeParameter(Context, ParamTy, false); + ExprResult Arg = PerformCopyInitialization( + Entity, TheCall->getArg(i)->getLocStart(), TheCall->getArg(i)); + if (Arg.isInvalid()) + return ExprError(); + TheCall->setArg(i, Arg.get()); + } + auto Callee = dyn_cast(TheCall->getCallee()); + assert(Callee && Callee->getCastKind() == CK_BuiltinFnToFnPtr && + "Callee expected to be implicit cast to a builtin function pointer"); + Callee->setType(OperatorNewOrDelete->getType()); + + return TheCallResult; +} + void Sema::CheckVirtualDtorCall(CXXDestructorDecl *dtor, SourceLocation Loc, bool IsDelete, bool CallCanBeVirtual, bool WarnOnNonAbstractTypes, Index: cfe/trunk/test/CodeGenCXX/builtin-operator-new-delete.cpp =================================================================== --- cfe/trunk/test/CodeGenCXX/builtin-operator-new-delete.cpp +++ cfe/trunk/test/CodeGenCXX/builtin-operator-new-delete.cpp @@ -0,0 +1,71 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-unknown %s \ +// RUN: -faligned-allocation -fsized-deallocation -emit-llvm -o - \ +// RUN: | FileCheck %s + +typedef __SIZE_TYPE__ size_t; + +// Declare an 'operator new' template to tickle a bug in __builtin_operator_new. +template void *operator new(size_t, int (*)(T)); + +// Ensure that this declaration doesn't cause operator new to lose its +// 'noalias' attribute. +void *operator new(size_t); + +namespace std { + struct nothrow_t {}; + enum class align_val_t : size_t { __zero = 0, + __max = (size_t)-1 }; +} +std::nothrow_t nothrow; + +// Declare the reserved placement operators. +void *operator new(size_t, void*) throw(); +void operator delete(void*, void*) throw(); +void *operator new[](size_t, void*) throw(); +void operator delete[](void*, void*) throw(); + +// Declare the replaceable global allocation operators. +void *operator new(size_t, const std::nothrow_t &) throw(); +void *operator new[](size_t, const std::nothrow_t &) throw(); +void operator delete(void *, const std::nothrow_t &) throw(); +void operator delete[](void *, const std::nothrow_t &) throw(); + +// Declare some other placement operators. +void *operator new(size_t, void*, bool) throw(); +void *operator new[](size_t, void*, bool) throw(); + + +// CHECK-LABEL: define void @test_basic( +extern "C" void test_basic() { + // CHECK: call i8* @_Znwm(i64 4) [[ATTR_BUILTIN_NEW:#[^ ]*]] + // CHECK: call void @_ZdlPv({{.*}}) [[ATTR_BUILTIN_DELETE:#[^ ]*]] + // CHECK: ret void + __builtin_operator_delete(__builtin_operator_new(4)); +} +// CHECK: declare noalias i8* @_Znwm(i64) [[ATTR_NOBUILTIN:#[^ ]*]] +// CHECK: declare void @_ZdlPv(i8*) [[ATTR_NOBUILTIN_NOUNWIND:#[^ ]*]] + +// CHECK-LABEL: define void @test_aligned_alloc( +extern "C" void test_aligned_alloc() { + // CHECK: call i8* @_ZnwmSt11align_val_t(i64 4, i64 4) [[ATTR_BUILTIN_NEW:#[^ ]*]] + // CHECK: call void @_ZdlPvSt11align_val_t({{.*}}, i64 4) [[ATTR_BUILTIN_DELETE:#[^ ]*]] + __builtin_operator_delete(__builtin_operator_new(4, std::align_val_t(4)), std::align_val_t(4)); +} +// CHECK: declare noalias i8* @_ZnwmSt11align_val_t(i64, i64) [[ATTR_NOBUILTIN:#[^ ]*]] +// CHECK: declare void @_ZdlPvSt11align_val_t(i8*, i64) [[ATTR_NOBUILTIN_NOUNWIND:#[^ ]*]] + + +// CHECK-LABEL: define void @test_sized_delete( +extern "C" void test_sized_delete() { + // CHECK: call i8* @_Znwm(i64 4) [[ATTR_BUILTIN_NEW:#[^ ]*]] + // CHECK: call void @_ZdlPvm({{.*}}, i64 4) [[ATTR_BUILTIN_DELETE:#[^ ]*]] + __builtin_operator_delete(__builtin_operator_new(4), 4); +} +// CHECK: declare void @_ZdlPvm(i8*, i64) [[ATTR_NOBUILTIN_UNWIND:#[^ ]*]] + + +// CHECK-DAG: attributes [[ATTR_NOBUILTIN]] = {{[{].*}} nobuiltin {{.*[}]}} +// CHECK-DAG: attributes [[ATTR_NOBUILTIN_NOUNWIND]] = {{[{].*}} nobuiltin nounwind {{.*[}]}} + +// CHECK-DAG: attributes [[ATTR_BUILTIN_NEW]] = {{[{].*}} builtin {{.*[}]}} +// CHECK-DAG: attributes [[ATTR_BUILTIN_DELETE]] = {{[{].*}} builtin {{.*[}]}} Index: cfe/trunk/test/SemaCXX/builtin-operator-new-delete.cpp =================================================================== --- cfe/trunk/test/SemaCXX/builtin-operator-new-delete.cpp +++ cfe/trunk/test/SemaCXX/builtin-operator-new-delete.cpp @@ -0,0 +1,153 @@ +// RUN: %clang_cc1 -std=c++1z -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++03 -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++03 -faligned-allocation -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify -fsized-deallocation %s + +#if !__has_builtin(__builtin_operator_new) || !__has_builtin(__builtin_operator_delete) +#error builtins should always be available +#endif + +#if __has_builtin(__builtin_operator_new) != 201802L || \ + __has_builtin(__builtin_operator_delete) != 201802L +#error builtin should report updated value +#endif + +typedef __SIZE_TYPE__ size_t; +namespace std { + struct nothrow_t {}; +#if __cplusplus >= 201103L +enum class align_val_t : size_t {}; +#else + enum align_val_t { __zero = 0, + __max = (size_t)-1 }; +#endif +} +std::nothrow_t nothrow; + +void *operator new(size_t); // expected-note 1+ {{candidate function}} +void operator delete(void *); // expected-note 1+ {{candidate function}} + +// Declare the reserved placement operators. +void *operator new(size_t, void*) throw(); // expected-note 1+ {{candidate function}} +void operator delete(void *, void *)throw(); // expected-note 1+ {{candidate function}} +void *operator new[](size_t, void*) throw(); +void operator delete[](void*, void*) throw(); + +// Declare the replaceable global allocation operators. +void *operator new(size_t, const std::nothrow_t &) throw(); // expected-note 1+ {{candidate function}} +void *operator new[](size_t, const std::nothrow_t &) throw(); +void operator delete(void *, const std::nothrow_t &)throw(); // expected-note 1+ {{candidate function}} +void operator delete[](void *, const std::nothrow_t &) throw(); + +// aligned allocation and deallocation functions. +void* operator new ( size_t count, std::align_val_t al); // expected-note 1+ {{candidate function}} +void operator delete(void *, std::align_val_t); // expected-note 1+ {{candidate}} +#ifndef __cpp_aligned_new +// expected-note@-3 1+ {{non-usual 'operator new' declared here}} +// expected-note@-3 1+ {{non-usual 'operator delete' declared here}} +#endif +void *operator new[](size_t count, std::align_val_t al); +void operator delete[](void*, std::align_val_t); + +void operator delete(void *, size_t); // expected-note 1+ {{candidate}} +#ifndef __cpp_sized_deallocation +// expected-note@-2 1+ {{non-usual 'operator delete' declared here}} +#endif +void operator delete[](void*, size_t); + +// Declare some other placemenet operators. +void *operator new(size_t, void*, bool) throw(); // expected-note 1+ {{candidate function}} +void *operator new[](size_t, void*, bool) throw(); + +void *NP = 0; + +void test_typo_in_args() { + __builtin_operator_new(DNE); // expected-error {{undeclared identifier 'DNE'}} + __builtin_operator_new(DNE, DNE2); // expected-error {{undeclared identifier 'DNE'}} expected-error {{'DNE2'}} + __builtin_operator_delete(DNE); // expected-error {{'DNE'}} + __builtin_operator_delete(DNE, DNE2); // expected-error {{'DNE'}} expected-error {{'DNE2'}} +} + +void test_arg_types() { + __builtin_operator_new(NP); // expected-error {{no matching function for call to 'operator new'}} + __builtin_operator_new(NP, std::align_val_t(0)); // expected-error {{no matching function for call to 'operator new'}}} +} +void test_return_type() { + int w = __builtin_operator_new(42); // expected-error {{cannot initialize a variable of type 'int' with an rvalue of type 'void *'}} + int y = __builtin_operator_delete(NP); // expected-error {{cannot initialize a variable of type 'int' with an rvalue of type 'void'}} +} + +void test_aligned_new() { +#ifdef __cpp_aligned_new + void *p = __builtin_operator_new(42, std::align_val_t(2)); + __builtin_operator_delete(p, std::align_val_t(2)); +#else + // FIXME: We've manually declared the aligned new/delete overloads, + // but LangOpts::AlignedAllocation is false. Should our overloads be considered + // usual allocation/deallocation functions? + void *p = __builtin_operator_new(42, std::align_val_t(2)); // expected-error {{call to '__builtin_operator_new' selects non-usual allocation function}} + __builtin_operator_delete(p, std::align_val_t(2)); // expected-error {{call to '__builtin_operator_delete' selects non-usual deallocation function}} +#endif +} + +void test_sized_delete() { +#ifdef __cpp_sized_deallocation + __builtin_operator_delete(NP, 4); +#else + __builtin_operator_delete(NP, 4); // expected-error {{call to '__builtin_operator_delete' selects non-usual deallocation function}} +#endif +} + +void *operator new(size_t, bool); // expected-note 1+ {{candidate}} +// expected-note@-1 {{non-usual 'operator new' declared here}} +void operator delete(void *, bool); // expected-note 1+ {{candidate}} +// expected-note@-1 {{non-usual 'operator delete' declared here}} + +void test_non_usual() { + __builtin_operator_new(42, true); // expected-error {{call to '__builtin_operator_new' selects non-usual allocation function}} + __builtin_operator_delete(NP, false); // expected-error {{call to '__builtin_operator_delete' selects non-usual deallocation function}} +} + +template +struct Tag {}; +struct ConvertsToTypes { + operator std::align_val_t() const; + operator Tag<0>() const; +}; + +void *operator new(size_t, Tag<0>); // expected-note 0+ {{candidate}} +void operator delete(void *, Tag<0>); // expected-note 0+ {{candidate}} + +void test_ambiguous() { +#ifdef __cpp_aligned_new + ConvertsToTypes cvt; + __builtin_operator_new(42, cvt); // expected-error {{call to 'operator new' is ambiguous}} + __builtin_operator_delete(NP, cvt); // expected-error {{call to 'operator delete' is ambiguous}} +#endif +} + +void test_no_args() { + __builtin_operator_new(); // expected-error {{no matching function for call to 'operator new'}} + __builtin_operator_delete(); // expected-error {{no matching function for call to 'operator delete'}} +} + +void test_no_matching_fn() { + Tag<1> tag; + __builtin_operator_new(42, tag); // expected-error {{no matching function for call to 'operator new'}} + __builtin_operator_delete(NP, tag); // expected-error {{no matching function for call to 'operator delete'}} +} + +template +void test_dependent_call(Tp new_arg, Up delete_arg, RetT) { + RetT ret = __builtin_operator_new(new_arg); + __builtin_operator_delete(delete_arg); +} +template void test_dependent_call(int, int*, void*); + +void test_const_attribute() { + __builtin_operator_new(42); // expected-warning {{ignoring return value of function declared with const attribute}} +#ifdef __cpp_aligned_new + __builtin_operator_new(42, std::align_val_t(8)); // expected-warning {{ignoring return value of function declared with const attribute}} +#endif +}