From: Eli Friedman Date: Fri, 31 Aug 2012 00:14:07 +0000 (+0000) Subject: Change the representation of builtin functions in the AST X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=a6c66cedc022c9e5d45a937d6b8cff491a6bf81b;p=clang Change the representation of builtin functions in the AST (__builtin_* etc.) so that it isn't possible to take their address. Specifically, introduce a new type to represent a reference to a builtin function, and a new cast kind to convert it to a function pointer in the operand of a call. Fixes PR13195. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@162962 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/AST/ASTContext.h b/include/clang/AST/ASTContext.h index f61ada37e9..a5c7012d34 100644 --- a/include/clang/AST/ASTContext.h +++ b/include/clang/AST/ASTContext.h @@ -676,6 +676,7 @@ public: CanQualType FloatComplexTy, DoubleComplexTy, LongDoubleComplexTy; CanQualType VoidPtrTy, NullPtrTy; CanQualType DependentTy, OverloadTy, BoundMemberTy, UnknownAnyTy; + CanQualType BuiltinFnTy; CanQualType PseudoObjectTy, ARCUnbridgedCastTy; CanQualType ObjCBuiltinIdTy, ObjCBuiltinClassTy, ObjCBuiltinSelTy; CanQualType ObjCBuiltinBoolTy; diff --git a/include/clang/AST/BuiltinTypes.def b/include/clang/AST/BuiltinTypes.def index 34e6fc5cd8..ba322fb326 100644 --- a/include/clang/AST/BuiltinTypes.def +++ b/include/clang/AST/BuiltinTypes.def @@ -206,6 +206,8 @@ PLACEHOLDER_TYPE(PseudoObject, PseudoObjectTy) // unknown type, most notably explicit casts. PLACEHOLDER_TYPE(UnknownAny, UnknownAnyTy) +PLACEHOLDER_TYPE(BuiltinFn, BuiltinFnTy) + // The type of a cast which, in ARC, would normally require a // __bridge, but which might be okay depending on the immediate // context. diff --git a/include/clang/AST/OperationKinds.h b/include/clang/AST/OperationKinds.h index 63594141ac..18169fd60c 100644 --- a/include/clang/AST/OperationKinds.h +++ b/include/clang/AST/OperationKinds.h @@ -288,7 +288,11 @@ enum CastKind { /// /// This particular cast kind is used for the conversion from a C++11 /// lambda expression to a block pointer. - CK_CopyAndAutoreleaseBlockObject + CK_CopyAndAutoreleaseBlockObject, + + // Convert a builtin function to a function pointer; only allowed in the + // callee of a call expression. + CK_BuiltinFnToFnPtr }; static const CastKind CK_Invalid = static_cast(-1); diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 8e9f2821ab..05877705af 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -4910,6 +4910,8 @@ def note_callee_decl : Note< "%0 declared here">; def note_defined_here : Note<"%0 defined here">; +def err_builtin_fn_use : Error<"builtin functions must be directly called">; + def warn_call_wrong_number_of_arguments : Warning< "too %select{few|many}0 arguments in call to %1">; def err_atomic_builtin_must_be_pointer : Error< diff --git a/include/clang/Serialization/ASTBitCodes.h b/include/clang/Serialization/ASTBitCodes.h index 2f9e1c21b6..f2134134ae 100644 --- a/include/clang/Serialization/ASTBitCodes.h +++ b/include/clang/Serialization/ASTBitCodes.h @@ -642,7 +642,9 @@ namespace clang { /// \brief The pseudo-object placeholder type. PREDEF_TYPE_PSEUDO_OBJECT = 35, /// \brief The __va_list_tag placeholder type. - PREDEF_TYPE_VA_LIST_TAG = 36 + PREDEF_TYPE_VA_LIST_TAG = 36, + /// \brief The placeholder type for builtin functions. + PREDEF_TYPE_BUILTIN_FN = 37 }; /// \brief The number of predefined type IDs that are reserved for diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 42a5fa37b1..df1273cb1e 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -803,6 +803,9 @@ void ASTContext::InitBuiltinTypes(const TargetInfo &Target) { // Placeholder type for unbridged ARC casts. InitBuiltinType(ARCUnbridgedCastTy, BuiltinType::ARCUnbridgedCast); + // Placeholder type for builtin functions. + InitBuiltinType(BuiltinFnTy, BuiltinType::BuiltinFn); + // C99 6.2.5p11. FloatComplexTy = getComplexType(FloatTy); DoubleComplexTy = getComplexType(DoubleTy); diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index cad8341afa..2ede77ec03 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -1310,12 +1310,16 @@ void CastExpr::CheckCastConsistency() const { assert(getType()->isBlockPointerType()); assert(getSubExpr()->getType()->isBlockPointerType()); goto CheckNoBasePath; - + + case CK_FunctionToPointerDecay: + assert(getType()->isPointerType()); + assert(getSubExpr()->getType()->isFunctionType()); + goto CheckNoBasePath; + // These should not have an inheritance path. case CK_Dynamic: case CK_ToUnion: case CK_ArrayToPointerDecay: - case CK_FunctionToPointerDecay: case CK_NullToMemberPointer: case CK_NullToPointer: case CK_ConstructorConversion: @@ -1356,6 +1360,7 @@ void CastExpr::CheckCastConsistency() const { case CK_IntegralComplexToBoolean: case CK_LValueBitCast: // -> bool& case CK_UserDefinedConversion: // operator bool() + case CK_BuiltinFnToFnPtr: CheckNoBasePath: assert(path_empty() && "Cast kind should not have a base path!"); break; @@ -1468,6 +1473,8 @@ const char *CastExpr::getCastKindName() const { return "NonAtomicToAtomic"; case CK_CopyAndAutoreleaseBlockObject: return "CopyAndAutoreleaseBlockObject"; + case CK_BuiltinFnToFnPtr: + return "BuiltinFnToFnPtr"; } llvm_unreachable("Unhandled cast kind!"); diff --git a/lib/AST/ExprConstant.cpp b/lib/AST/ExprConstant.cpp index fb0c4d26ea..2cd7f04e92 100644 --- a/lib/AST/ExprConstant.cpp +++ b/lib/AST/ExprConstant.cpp @@ -5357,6 +5357,7 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) { case CK_IntegralRealToComplex: case CK_IntegralComplexCast: case CK_IntegralComplexToFloatingComplex: + case CK_BuiltinFnToFnPtr: llvm_unreachable("invalid cast kind for integral value"); case CK_BitCast: @@ -5843,6 +5844,7 @@ bool ComplexExprEvaluator::VisitCastExpr(const CastExpr *E) { case CK_ARCReclaimReturnedObject: case CK_ARCExtendBlockObject: case CK_CopyAndAutoreleaseBlockObject: + case CK_BuiltinFnToFnPtr: llvm_unreachable("invalid cast kind for complex value"); case CK_LValueToRValue: diff --git a/lib/AST/NSAPI.cpp b/lib/AST/NSAPI.cpp index 39077d1766..0837509194 100644 --- a/lib/AST/NSAPI.cpp +++ b/lib/AST/NSAPI.cpp @@ -344,6 +344,7 @@ NSAPI::getNSNumberFactoryMethodKind(QualType T) const { case BuiltinType::ARCUnbridgedCast: case BuiltinType::Half: case BuiltinType::PseudoObject: + case BuiltinType::BuiltinFn: break; } diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index abefae44b2..7445152d62 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -1480,6 +1480,7 @@ StringRef BuiltinType::getName(const PrintingPolicy &Policy) const { case Dependent: return ""; case UnknownAny: return ""; case ARCUnbridgedCast: return ""; + case BuiltinFn: return ""; case ObjCId: return "id"; case ObjCClass: return "Class"; case ObjCSel: return "SEL"; diff --git a/lib/AST/TypeLoc.cpp b/lib/AST/TypeLoc.cpp index c7bb7da4d1..bd30a65259 100644 --- a/lib/AST/TypeLoc.cpp +++ b/lib/AST/TypeLoc.cpp @@ -241,6 +241,7 @@ TypeSpecifierType BuiltinTypeLoc::getWrittenTypeSpec() const { case BuiltinType::ObjCId: case BuiltinType::ObjCClass: case BuiltinType::ObjCSel: + case BuiltinType::BuiltinFn: return TST_unspecified; } diff --git a/lib/CodeGen/CGExpr.cpp b/lib/CodeGen/CGExpr.cpp index e7dbe791c9..e17494aac8 100644 --- a/lib/CodeGen/CGExpr.cpp +++ b/lib/CodeGen/CGExpr.cpp @@ -2396,7 +2396,10 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E) { case CK_Dependent: llvm_unreachable("dependent cast kind in IR gen!"); - + + case CK_BuiltinFnToFnPtr: + llvm_unreachable("builtin functions are handled elsewhere"); + // These two casts are currently treated as no-ops, although they could // potentially be real operations depending on the target's ABI. case CK_NonAtomicToAtomic: diff --git a/lib/CodeGen/CGExprAgg.cpp b/lib/CodeGen/CGExprAgg.cpp index b5628b5e96..a2101fb442 100644 --- a/lib/CodeGen/CGExprAgg.cpp +++ b/lib/CodeGen/CGExprAgg.cpp @@ -647,6 +647,7 @@ void AggExprEmitter::VisitCastExpr(CastExpr *E) { case CK_ARCReclaimReturnedObject: case CK_ARCExtendBlockObject: case CK_CopyAndAutoreleaseBlockObject: + case CK_BuiltinFnToFnPtr: llvm_unreachable("cast kind invalid for aggregate types"); } } diff --git a/lib/CodeGen/CGExprComplex.cpp b/lib/CodeGen/CGExprComplex.cpp index 0233745afa..de3505c105 100644 --- a/lib/CodeGen/CGExprComplex.cpp +++ b/lib/CodeGen/CGExprComplex.cpp @@ -427,6 +427,7 @@ ComplexPairTy ComplexExprEmitter::EmitCast(CastExpr::CastKind CK, Expr *Op, case CK_ARCReclaimReturnedObject: case CK_ARCExtendBlockObject: case CK_CopyAndAutoreleaseBlockObject: + case CK_BuiltinFnToFnPtr: llvm_unreachable("invalid cast kind for complex value"); case CK_FloatingRealToComplex: diff --git a/lib/CodeGen/CGExprConstant.cpp b/lib/CodeGen/CGExprConstant.cpp index a17a43639a..8a2a586fa4 100644 --- a/lib/CodeGen/CGExprConstant.cpp +++ b/lib/CodeGen/CGExprConstant.cpp @@ -691,6 +691,9 @@ public: case CK_Dependent: llvm_unreachable("saw dependent cast!"); + case CK_BuiltinFnToFnPtr: + llvm_unreachable("builtin functions are handled elsewhere"); + case CK_ReinterpretMemberPointer: case CK_DerivedToBaseMemberPointer: case CK_BaseToDerivedMemberPointer: diff --git a/lib/CodeGen/CGExprScalar.cpp b/lib/CodeGen/CGExprScalar.cpp index 58692f5cb3..d5ed233a97 100644 --- a/lib/CodeGen/CGExprScalar.cpp +++ b/lib/CodeGen/CGExprScalar.cpp @@ -1036,7 +1036,9 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) { // are in the same order as in the CastKind enum. switch (Kind) { case CK_Dependent: llvm_unreachable("dependent cast kind in IR gen!"); - + case CK_BuiltinFnToFnPtr: + llvm_unreachable("builtin functions are handled elsewhere"); + case CK_LValueBitCast: case CK_ObjCObjectLValueCast: { Value *V = EmitLValue(E).getAddress(); diff --git a/lib/Edit/RewriteObjCFoundationAPI.cpp b/lib/Edit/RewriteObjCFoundationAPI.cpp index d15b7a75e8..de96fee416 100644 --- a/lib/Edit/RewriteObjCFoundationAPI.cpp +++ b/lib/Edit/RewriteObjCFoundationAPI.cpp @@ -920,6 +920,7 @@ static bool rewriteToNumericBoxedExpression(const ObjCMessageExpr *Msg, case CK_ARCExtendBlockObject: case CK_NonAtomicToAtomic: case CK_CopyAndAutoreleaseBlockObject: + case CK_BuiltinFnToFnPtr: return false; } } diff --git a/lib/Sema/SemaChecking.cpp b/lib/Sema/SemaChecking.cpp index 8760c5e77f..137f3e1a5e 100644 --- a/lib/Sema/SemaChecking.cpp +++ b/lib/Sema/SemaChecking.cpp @@ -1232,14 +1232,14 @@ Sema::SemaBuiltinAtomicOverloaded(ExprResult TheCallResult) { NewBuiltinDecl, /*enclosing*/ false, DRE->getLocation(), - NewBuiltinDecl->getType(), + Context.BuiltinFnTy, DRE->getValueKind()); // Set the callee in the CallExpr. - // FIXME: This leaks the original parens and implicit casts. - ExprResult PromotedCall = UsualUnaryConversions(NewDRE); - if (PromotedCall.isInvalid()) - return ExprError(); + // FIXME: This loses syntactic information. + QualType CalleePtrTy = Context.getPointerType(NewBuiltinDecl->getType()); + ExprResult PromotedCall = ImpCastExprToType(NewDRE, CalleePtrTy, + CK_BuiltinFnToFnPtr); TheCall->setCallee(PromotedCall.take()); // Change the result type of the call to match the original value type. This diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index 9fbe14a16c..176e3fecb9 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -7847,8 +7847,8 @@ void Sema::DefineImplicitCopyAssignment(SourceLocation CurrentLocation, } CollectableMemCpyRef = BuildDeclRefExpr(CollectableMemCpy, - CollectableMemCpy->getType(), - VK_LValue, Loc, 0).take(); + Context.BuiltinFnTy, + VK_RValue, Loc, 0).take(); assert(CollectableMemCpyRef && "Builtin reference cannot fail"); } } @@ -7867,8 +7867,8 @@ void Sema::DefineImplicitCopyAssignment(SourceLocation CurrentLocation, } BuiltinMemCpyRef = BuildDeclRefExpr(BuiltinMemCpy, - BuiltinMemCpy->getType(), - VK_LValue, Loc, 0).take(); + Context.BuiltinFnTy, + VK_RValue, Loc, 0).take(); assert(BuiltinMemCpyRef && "Builtin reference cannot fail"); } @@ -8396,8 +8396,8 @@ void Sema::DefineImplicitMoveAssignment(SourceLocation CurrentLocation, } CollectableMemCpyRef = BuildDeclRefExpr(CollectableMemCpy, - CollectableMemCpy->getType(), - VK_LValue, Loc, 0).take(); + Context.BuiltinFnTy, + VK_RValue, Loc, 0).take(); assert(CollectableMemCpyRef && "Builtin reference cannot fail"); } } @@ -8416,8 +8416,8 @@ void Sema::DefineImplicitMoveAssignment(SourceLocation CurrentLocation, } BuiltinMemCpyRef = BuildDeclRefExpr(BuiltinMemCpy, - BuiltinMemCpy->getType(), - VK_LValue, Loc, 0).take(); + Context.BuiltinFnTy, + VK_RValue, Loc, 0).take(); assert(BuiltinMemCpyRef && "Builtin reference cannot fail"); } diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index f2e6070539..57dab09e46 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -2416,6 +2416,14 @@ Sema::BuildDeclarationNameExpr(const CXXScopeSpec &SS, } case Decl::Function: { + if (unsigned BID = cast(VD)->getBuiltinID()) { + if (!Context.BuiltinInfo.isPredefinedLibFunction(BID)) { + type = Context.BuiltinFnTy; + valueKind = VK_RValue; + break; + } + } + const FunctionType *fty = type->castAs(); // If we're referring to a function with an __unknown_anytype @@ -3929,9 +3937,19 @@ Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl, SourceLocation RParenLoc, Expr *Config, bool IsExecConfig) { FunctionDecl *FDecl = dyn_cast_or_null(NDecl); + unsigned BuiltinID = (FDecl ? FDecl->getBuiltinID() : 0); // Promote the function operand. - ExprResult Result = UsualUnaryConversions(Fn); + // We special-case function promotion here because we only allow promoting + // builtin functions to function pointers in the callee of a call. + ExprResult Result; + if (BuiltinID && + Fn->getType()->isSpecificBuiltinType(BuiltinType::BuiltinFn)) { + Result = ImpCastExprToType(Fn, Context.getPointerType(FDecl->getType()), + CK_BuiltinFnToFnPtr).take(); + } else { + Result = UsualUnaryConversions(Fn); + } if (Result.isInvalid()) return ExprError(); Fn = Result.take(); @@ -3953,8 +3971,6 @@ Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl, VK_RValue, RParenLoc); - unsigned BuiltinID = (FDecl ? FDecl->getBuiltinID() : 0); - // Bail out early if calling a builtin with custom typechecking. if (BuiltinID && Context.BuiltinInfo.hasCustomTypechecking(BuiltinID)) return CheckBuiltinFunctionCall(BuiltinID, TheCall); @@ -8635,6 +8651,7 @@ ExprResult Sema::CreateBuiltinUnaryOp(SourceLocation OpLoc, break; case UO_Deref: { Input = DefaultFunctionArrayLvalueConversion(Input.take()); + if (Input.isInvalid()) return ExprError(); resultType = CheckIndirectionOperand(*this, Input.get(), VK, OpLoc); break; } @@ -11747,6 +11764,10 @@ ExprResult Sema::CheckPlaceholderExpr(Expr *E) { case BuiltinType::PseudoObject: return checkPseudoObjectRValue(E); + case BuiltinType::BuiltinFn: + Diag(E->getLocStart(), diag::err_builtin_fn_use); + return ExprError(); + // Everything else should be impossible. #define BUILTIN_TYPE(Id, SingletonId) \ case BuiltinType::Id: diff --git a/lib/Sema/TreeTransform.h b/lib/Sema/TreeTransform.h index 2bbc41326e..19636f4143 100644 --- a/lib/Sema/TreeTransform.h +++ b/lib/Sema/TreeTransform.h @@ -2413,17 +2413,16 @@ public: // Build a reference to the __builtin_shufflevector builtin FunctionDecl *Builtin = cast(*Lookup.first); - ExprResult Callee - = SemaRef.Owned(new (SemaRef.Context) DeclRefExpr(Builtin, false, - Builtin->getType(), - VK_LValue, BuiltinLoc)); - Callee = SemaRef.UsualUnaryConversions(Callee.take()); - if (Callee.isInvalid()) - return ExprError(); + Expr *Callee = new (SemaRef.Context) DeclRefExpr(Builtin, false, + SemaRef.Context.BuiltinFnTy, + VK_RValue, BuiltinLoc); + QualType CalleePtrTy = SemaRef.Context.getPointerType(Builtin->getType()); + Callee = SemaRef.ImpCastExprToType(Callee, CalleePtrTy, + CK_BuiltinFnToFnPtr).take(); // Build the CallExpr ExprResult TheCall = SemaRef.Owned( - new (SemaRef.Context) CallExpr(SemaRef.Context, Callee.take(), SubExprs, + new (SemaRef.Context) CallExpr(SemaRef.Context, Callee, SubExprs, Builtin->getCallResultType(), Expr::getValueKindForType(Builtin->getResultType()), RParenLoc)); diff --git a/lib/Serialization/ASTCommon.cpp b/lib/Serialization/ASTCommon.cpp index 67f74f7d7a..0ec03cfe1e 100644 --- a/lib/Serialization/ASTCommon.cpp +++ b/lib/Serialization/ASTCommon.cpp @@ -60,6 +60,9 @@ serialization::TypeIdxFromBuiltin(const BuiltinType *BT) { case BuiltinType::ObjCId: ID = PREDEF_TYPE_OBJC_ID; break; case BuiltinType::ObjCClass: ID = PREDEF_TYPE_OBJC_CLASS; break; case BuiltinType::ObjCSel: ID = PREDEF_TYPE_OBJC_SEL; break; + case BuiltinType::BuiltinFn: + ID = PREDEF_TYPE_BUILTIN_FN; break; + } return TypeIdx(ID); diff --git a/lib/Serialization/ASTReader.cpp b/lib/Serialization/ASTReader.cpp index ff46bf0e13..478217bd03 100644 --- a/lib/Serialization/ASTReader.cpp +++ b/lib/Serialization/ASTReader.cpp @@ -4466,6 +4466,10 @@ QualType ASTReader::GetType(TypeID ID) { case PREDEF_TYPE_VA_LIST_TAG: T = Context.getVaListTagType(); break; + + case PREDEF_TYPE_BUILTIN_FN: + T = Context.BuiltinFnTy; + break; } assert(!T.isNull() && "Unknown predefined type"); diff --git a/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/lib/StaticAnalyzer/Core/ExprEngineC.cpp index 56858f4883..8881f26366 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -266,7 +266,8 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, case CK_NoOp: case CK_ConstructorConversion: case CK_UserDefinedConversion: - case CK_FunctionToPointerDecay: { + case CK_FunctionToPointerDecay: + case CK_BuiltinFnToFnPtr: { // Copy the SVal of Ex to CastE. ProgramStateRef state = Pred->getState(); const LocationContext *LCtx = Pred->getLocationContext(); diff --git a/test/CodeGenCXX/builtins.cpp b/test/CodeGenCXX/builtins.cpp index 4542563717..0629c31015 100644 --- a/test/CodeGenCXX/builtins.cpp +++ b/test/CodeGenCXX/builtins.cpp @@ -7,15 +7,3 @@ int main() { // CHECK: call signext i8 @memmove() return memmove(); } - -// - -template -int equal(const char *s1, const char *s2) { - return Compare(s1, s2) == 0; -} - -// CHECK: define weak_odr i32 @_Z5equalIXadL_Z16__builtin_strcmpPKcS1_EEEiS1_S1_ -// CHECK: call i32 @strcmp -template int equal<&__builtin_strcmp>(const char*, const char*); - diff --git a/test/Parser/builtin_classify_type.c b/test/Parser/builtin_classify_type.c index a7c08555c9..ff483b2097 100644 --- a/test/Parser/builtin_classify_type.c +++ b/test/Parser/builtin_classify_type.c @@ -10,7 +10,7 @@ int main() { static int ary[__builtin_classify_type(a)]; static int ary2[(__builtin_classify_type)(a)]; // expected-error{{variable length array declaration can not have 'static' storage duration}} - static int ary3[(*__builtin_classify_type)(a)]; // expected-error{{variable length array declaration can not have 'static' storage duration}} + static int ary3[(*__builtin_classify_type)(a)]; // expected-error{{builtin functions must be directly called}} int result; diff --git a/test/SemaCXX/builtins.cpp b/test/SemaCXX/builtins.cpp index 568ba5dde1..6b055cff64 100644 --- a/test/SemaCXX/builtins.cpp +++ b/test/SemaCXX/builtins.cpp @@ -7,3 +7,16 @@ void f() { } void a() { __builtin_va_list x, y; ::__builtin_va_copy(x, y); } + +// +template +int equal(const char *s1, const char *s2) { + return Compare(s1, s2) == 0; +} +// FIXME: Our error recovery here sucks +template int equal<&__builtin_strcmp>(const char*, const char*); // expected-error {{builtin functions must be directly called}} expected-error {{expected unqualified-id}} expected-error {{expected ')'}} expected-note {{to match this '('}} + +// PR13195 +void f2() { + __builtin_isnan; // expected-error {{builtin functions must be directly called}} +}