Add hook to add attributes to function declarations that we know
about, whether they are builtins or not. Use this to add the
appropriate "format" attribute to NSLog, NSLogv, asprintf, and
vasprintf, and to translate builtin attributes (from Builtins.def)
into actual attributes on the function declaration.
Use the "printf" format attribute on function declarations to
determine whether we should do format string checking, rather than
looking at an ad hoc list of builtins and "known" function names.
Be a bit more careful about when we consider a function a "builtin" in
C++.
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@64561 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/include/clang/AST/Attr.h b/include/clang/AST/Attr.h
index 288a1c4..8d4e109 100644
--- a/include/clang/AST/Attr.h
+++ b/include/clang/AST/Attr.h
@@ -347,6 +347,7 @@
Type(type), formatIdx(idx), firstArg(first) {}
const std::string& getType() const { return Type; }
+ void setType(const std::string &type) { Type = type; }
int getFormatIdx() const { return formatIdx; }
int getFirstArg() const { return firstArg; }
diff --git a/include/clang/AST/Decl.h b/include/clang/AST/Decl.h
index a4e3065..b39467a 100644
--- a/include/clang/AST/Decl.h
+++ b/include/clang/AST/Decl.h
@@ -600,7 +600,7 @@
PreviousDeclaration = PrevDecl;
}
- unsigned getBuiltinID() const;
+ unsigned getBuiltinID(ASTContext &Context) const;
// Iterator access to formal parameters.
unsigned param_size() const { return getNumParams(); }
diff --git a/include/clang/AST/Expr.h b/include/clang/AST/Expr.h
index 2baaa43..b7c5c89 100644
--- a/include/clang/AST/Expr.h
+++ b/include/clang/AST/Expr.h
@@ -834,7 +834,7 @@
/// isBuiltinCall - If this is a call to a builtin, return the builtin ID. If
/// not, return 0.
- unsigned isBuiltinCall() const;
+ unsigned isBuiltinCall(ASTContext &Context) const;
SourceLocation getRParenLoc() const { return RParenLoc; }
diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp
index 13b40e8..ca1fa0c 100644
--- a/lib/AST/Decl.cpp
+++ b/lib/AST/Decl.cpp
@@ -259,15 +259,33 @@
/// will be 0 for functions that do not correspond to a builtin, a
/// value of type \c Builtin::ID if in the target-independent range
/// \c [1,Builtin::First), or a target-specific builtin value.
-unsigned FunctionDecl::getBuiltinID() const {
- if (getIdentifier() &&
- (getDeclContext()->isTranslationUnit() ||
- (isa<LinkageSpecDecl>(getDeclContext()) &&
- cast<LinkageSpecDecl>(getDeclContext())->getLanguage()
- == LinkageSpecDecl::lang_c)))
- return getIdentifier()->getBuiltinID();
-
- // Not a builtin.
+unsigned FunctionDecl::getBuiltinID(ASTContext &Context) const {
+ if (!getIdentifier() || !getIdentifier()->getBuiltinID())
+ return 0;
+
+ unsigned BuiltinID = getIdentifier()->getBuiltinID();
+ if (!Context.BuiltinInfo.isPredefinedLibFunction(BuiltinID))
+ return BuiltinID;
+
+ // This function has the name of a known C library
+ // function. Determine whether it actually refers to the C library
+ // function or whether it just has the same name.
+
+ // If this function is at translation-unit scope and we're not in
+ // C++, it refers to the C library function.
+ if (!Context.getLangOptions().CPlusPlus &&
+ getDeclContext()->isTranslationUnit())
+ return BuiltinID;
+
+ // If the function is in an extern "C" linkage specification and is
+ // not marked "overloadable", it's the real function.
+ if (isa<LinkageSpecDecl>(getDeclContext()) &&
+ cast<LinkageSpecDecl>(getDeclContext())->getLanguage()
+ == LinkageSpecDecl::lang_c &&
+ !getAttr<OverloadableAttr>())
+ return BuiltinID;
+
+ // Not a builtin
return 0;
}
diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp
index 7063b76..7a04ffc 100644
--- a/lib/AST/Expr.cpp
+++ b/lib/AST/Expr.cpp
@@ -173,7 +173,7 @@
/// isBuiltinCall - If this is a call to a builtin, return the builtin ID. If
/// not, return 0.
-unsigned CallExpr::isBuiltinCall() const {
+unsigned CallExpr::isBuiltinCall(ASTContext &Context) const {
// All simple function calls (e.g. func()) are implicitly cast to pointer to
// function. As a result, we try and obtain the DeclRefExpr from the
// ImplicitCastExpr.
@@ -192,7 +192,7 @@
if (!FDecl->getIdentifier())
return 0;
- return FDecl->getBuiltinID();
+ return FDecl->getBuiltinID(Context);
}
@@ -922,7 +922,7 @@
// If this is a call to a builtin function, constant fold it otherwise
// reject it.
- if (CE->isBuiltinCall()) {
+ if (CE->isBuiltinCall(Ctx)) {
EvalResult EvalResult;
if (CE->Evaluate(EvalResult, Ctx)) {
assert(!EvalResult.HasSideEffects &&
@@ -1205,7 +1205,7 @@
// expression, and it is fully evaluated. This is an important GNU
// extension. See GCC PR38377 for discussion.
if (const CallExpr *CallCE = dyn_cast<CallExpr>(Cond->IgnoreParenCasts()))
- if (CallCE->isBuiltinCall() == Builtin::BI__builtin_constant_p) {
+ if (CallCE->isBuiltinCall(Ctx) == Builtin::BI__builtin_constant_p) {
EvalResult EVResult;
if (!Evaluate(EVResult, Ctx) || EVResult.HasSideEffects)
return false;
diff --git a/lib/AST/ExprConstant.cpp b/lib/AST/ExprConstant.cpp
index c7cc854..3a3b152 100644
--- a/lib/AST/ExprConstant.cpp
+++ b/lib/AST/ExprConstant.cpp
@@ -350,7 +350,8 @@
}
APValue PointerExprEvaluator::VisitCallExpr(CallExpr *E) {
- if (E->isBuiltinCall() == Builtin::BI__builtin___CFStringMakeConstantString)
+ if (E->isBuiltinCall(Info.Ctx) ==
+ Builtin::BI__builtin___CFStringMakeConstantString)
return APValue(E, 0);
return APValue();
}
@@ -646,7 +647,7 @@
bool IntExprEvaluator::VisitCallExpr(const CallExpr *E) {
Result.zextOrTrunc(getIntTypeSizeInBits(E->getType()));
- switch (E->isBuiltinCall()) {
+ switch (E->isBuiltinCall(Info.Ctx)) {
default:
return Error(E->getLocStart(), diag::note_invalid_subexpr_in_ice, E);
case Builtin::BI__builtin_classify_type:
@@ -1173,7 +1174,7 @@
}
bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) {
- switch (E->isBuiltinCall()) {
+ switch (E->isBuiltinCall(Info.Ctx)) {
default: return false;
case Builtin::BI__builtin_huge_val:
case Builtin::BI__builtin_huge_valf:
diff --git a/lib/Analysis/GRExprEngine.cpp b/lib/Analysis/GRExprEngine.cpp
index a48b18c..1a7f359 100644
--- a/lib/Analysis/GRExprEngine.cpp
+++ b/lib/Analysis/GRExprEngine.cpp
@@ -1263,7 +1263,8 @@
if (isa<loc::FuncVal>(L)) {
- if (unsigned id = cast<loc::FuncVal>(L).getDecl()->getBuiltinID())
+ if (unsigned id
+ = cast<loc::FuncVal>(L).getDecl()->getBuiltinID(getContext()))
switch (id) {
case Builtin::BI__builtin_expect: {
// For __builtin_expect, just return the value of the subexpression.
diff --git a/lib/CodeGen/CGExpr.cpp b/lib/CodeGen/CGExpr.cpp
index ddecbeb..3e44c00 100644
--- a/lib/CodeGen/CGExpr.cpp
+++ b/lib/CodeGen/CGExpr.cpp
@@ -964,7 +964,7 @@
dyn_cast<const DeclRefExpr>(IcExpr->getSubExpr()))
if (const FunctionDecl *FDecl =
dyn_cast<const FunctionDecl>(DRExpr->getDecl()))
- if (unsigned builtinID = FDecl->getBuiltinID())
+ if (unsigned builtinID = FDecl->getBuiltinID(getContext()))
return EmitBuiltinExpr(builtinID, E);
if (E->getCallee()->getType()->isBlockPointerType())
diff --git a/lib/CodeGen/CGExprConstant.cpp b/lib/CodeGen/CGExprConstant.cpp
index 91153f7..bfb523b 100644
--- a/lib/CodeGen/CGExprConstant.cpp
+++ b/lib/CodeGen/CGExprConstant.cpp
@@ -582,7 +582,8 @@
}
case Expr::CallExprClass: {
CallExpr* CE = cast<CallExpr>(E);
- if (CE->isBuiltinCall() != Builtin::BI__builtin___CFStringMakeConstantString)
+ if (CE->isBuiltinCall(CGM.getContext()) !=
+ Builtin::BI__builtin___CFStringMakeConstantString)
break;
const Expr *Arg = CE->getArg(0)->IgnoreParenCasts();
const StringLiteral *Literal = cast<StringLiteral>(Arg);
diff --git a/lib/Sema/Sema.cpp b/lib/Sema/Sema.cpp
index 4666250..539457f 100644
--- a/lib/Sema/Sema.cpp
+++ b/lib/Sema/Sema.cpp
@@ -132,6 +132,7 @@
IdentifierTable &IT = PP.getIdentifierTable();
KnownFunctionIDs[id_NSLog] = &IT.get("NSLog");
+ KnownFunctionIDs[id_NSLogv] = &IT.get("NSLogv");
KnownFunctionIDs[id_asprintf] = &IT.get("asprintf");
KnownFunctionIDs[id_vasprintf] = &IT.get("vasprintf");
diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h
index 68c425c..7c2d3b6 100644
--- a/lib/Sema/Sema.h
+++ b/lib/Sema/Sema.h
@@ -181,6 +181,7 @@
// Enum values used by KnownFunctionIDs (see below).
enum {
id_NSLog,
+ id_NSLogv,
id_asprintf,
id_vasprintf,
id_num_known_functions
@@ -883,6 +884,7 @@
SourceLocation Loc);
NamedDecl *ImplicitlyDefineFunction(SourceLocation Loc, IdentifierInfo &II,
Scope *S);
+ void AddKnownFunctionAttributes(FunctionDecl *FD);
// More parsing and symbol table subroutines.
@@ -1986,12 +1988,12 @@
bool SemaBuiltinPrefetch(CallExpr *TheCall);
bool SemaBuiltinObjectSize(CallExpr *TheCall);
bool SemaCheckStringLiteral(Expr *E, CallExpr *TheCall, bool HasVAListArg,
- unsigned format_idx);
+ unsigned format_idx, unsigned firstDataArg);
void CheckPrintfString(StringLiteral *FExpr, Expr *OrigFormatExpr,
CallExpr *TheCall, bool HasVAListArg,
- unsigned format_idx);
- void CheckPrintfArguments(CallExpr *TheCall,
- bool HasVAListArg, unsigned format_idx);
+ unsigned format_idx, unsigned firstDataArg);
+ void CheckPrintfArguments(CallExpr *TheCall, bool HasVAListArg,
+ unsigned format_idx, unsigned firstDataArg);
void CheckReturnStackAddr(Expr *RetValExp, QualType lhsType,
SourceLocation ReturnLoc);
void CheckFloatComparison(SourceLocation loc, Expr* lex, Expr* rex);
diff --git a/lib/Sema/SemaChecking.cpp b/lib/Sema/SemaChecking.cpp
index 7ecc304..e2ec267 100644
--- a/lib/Sema/SemaChecking.cpp
+++ b/lib/Sema/SemaChecking.cpp
@@ -18,7 +18,6 @@
#include "clang/AST/ExprCXX.h"
#include "clang/AST/ExprObjC.h"
#include "clang/Lex/Preprocessor.h"
-#include "SemaUtil.h"
using namespace clang;
/// CheckFunctionCall - Check a direct function call for various correctness
@@ -34,7 +33,7 @@
if (!FnInfo)
return move(TheCallResult);
- switch (FDecl->getBuiltinID()) {
+ switch (FDecl->getBuiltinID(Context)) {
case Builtin::BI__builtin___CFStringMakeConstantString:
assert(TheCall->getNumArgs() == 1 &&
"Wrong # arguments to builtin CFStringMakeConstantString");
@@ -78,27 +77,17 @@
// handlers.
// Printf checking.
- unsigned format_idx = 0;
- bool HasVAListArg = false;
- if (FDecl->getBuiltinID() &&
- Context.BuiltinInfo.isPrintfLike(FDecl->getBuiltinID(), format_idx,
- HasVAListArg)) {
- // Found a printf builtin.
- } else if (FnInfo == KnownFunctionIDs[id_NSLog]) {
- format_idx = 0;
- HasVAListArg = false;
- } else if (FnInfo == KnownFunctionIDs[id_asprintf]) {
- format_idx = 1;
- HasVAListArg = false;
- } else if (FnInfo == KnownFunctionIDs[id_vasprintf]) {
- format_idx = 1;
- HasVAListArg = true;
- } else {
- return move(TheCallResult);
+ if (const FormatAttr *Format = FDecl->getAttr<FormatAttr>()) {
+ if (Format->getType() == "printf") {
+ bool HasVAListArg = false;
+ if (const FunctionTypeProto *Proto
+ = FDecl->getType()->getAsFunctionTypeProto())
+ HasVAListArg = !Proto->isVariadic();
+ CheckPrintfArguments(TheCall, HasVAListArg, Format->getFormatIdx() - 1,
+ Format->getFirstArg() - 1);
+ }
}
- CheckPrintfArguments(TheCall, HasVAListArg, format_idx);
-
return move(TheCallResult);
}
@@ -364,27 +353,27 @@
// Handle i > 1 ? "x" : "y", recursivelly
bool Sema::SemaCheckStringLiteral(Expr *E, CallExpr *TheCall, bool HasVAListArg,
- unsigned format_idx) {
+ unsigned format_idx, unsigned firstDataArg) {
switch (E->getStmtClass()) {
case Stmt::ConditionalOperatorClass: {
ConditionalOperator *C = cast<ConditionalOperator>(E);
return SemaCheckStringLiteral(C->getLHS(), TheCall,
- HasVAListArg, format_idx)
+ HasVAListArg, format_idx, firstDataArg)
&& SemaCheckStringLiteral(C->getRHS(), TheCall,
- HasVAListArg, format_idx);
+ HasVAListArg, format_idx, firstDataArg);
}
case Stmt::ImplicitCastExprClass: {
ImplicitCastExpr *Expr = dyn_cast<ImplicitCastExpr>(E);
return SemaCheckStringLiteral(Expr->getSubExpr(), TheCall, HasVAListArg,
- format_idx);
+ format_idx, firstDataArg);
}
case Stmt::ParenExprClass: {
ParenExpr *Expr = dyn_cast<ParenExpr>(E);
return SemaCheckStringLiteral(Expr->getSubExpr(), TheCall, HasVAListArg,
- format_idx);
+ format_idx, firstDataArg);
}
default: {
@@ -397,7 +386,8 @@
StrE = dyn_cast<StringLiteral>(E);
if (StrE) {
- CheckPrintfString(StrE, E, TheCall, HasVAListArg, format_idx);
+ CheckPrintfString(StrE, E, TheCall, HasVAListArg, format_idx,
+ firstDataArg);
return true;
}
@@ -458,7 +448,7 @@
/// For now, we ONLY do (1), (3), (5), (6), (7), and (8).
void
Sema::CheckPrintfArguments(CallExpr *TheCall, bool HasVAListArg,
- unsigned format_idx) {
+ unsigned format_idx, unsigned firstDataArg) {
Expr *Fn = TheCall->getCallee();
// CHECK: printf-like function is called with no format string.
@@ -482,7 +472,9 @@
// C string (e.g. "%d")
// ObjC string uses the same format specifiers as C string, so we can use
// the same format string checking logic for both ObjC and C strings.
- bool isFExpr = SemaCheckStringLiteral(OrigFormatExpr, TheCall, HasVAListArg, format_idx);
+ bool isFExpr = SemaCheckStringLiteral(OrigFormatExpr, TheCall,
+ HasVAListArg, format_idx,
+ firstDataArg);
if (!isFExpr) {
// For vprintf* functions (i.e., HasVAListArg==true), we add a
@@ -516,7 +508,8 @@
}
void Sema::CheckPrintfString(StringLiteral *FExpr, Expr *OrigFormatExpr,
- CallExpr *TheCall, bool HasVAListArg, unsigned format_idx) {
+ CallExpr *TheCall, bool HasVAListArg, unsigned format_idx,
+ unsigned firstDataArg) {
ObjCStringLiteral *ObjCFExpr = dyn_cast<ObjCStringLiteral>(OrigFormatExpr);
// CHECK: is the format string a wide literal?
@@ -554,7 +547,7 @@
// string. This can only be determined for non vprintf-like
// functions. For those functions, this value is 1 (the sole
// va_arg argument).
- unsigned numDataArgs = TheCall->getNumArgs()-(format_idx+1);
+ unsigned numDataArgs = TheCall->getNumArgs()-firstDataArg;
// Inspect the format string.
unsigned StrIdx = 0;
@@ -1025,12 +1018,12 @@
// Check for comparisons with builtin types.
if (EmitWarning)
if (CallExpr* CL = dyn_cast<CallExpr>(LeftExprSansParen))
- if (isCallBuiltin(CL))
+ if (CL->isBuiltinCall(Context))
EmitWarning = false;
if (EmitWarning)
if (CallExpr* CR = dyn_cast<CallExpr>(RightExprSansParen))
- if (isCallBuiltin(CR))
+ if (CR->isBuiltinCall(Context))
EmitWarning = false;
// Emit the diagnostic.
diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp
index 16e8691..37e1f54 100644
--- a/lib/Sema/SemaDecl.cpp
+++ b/lib/Sema/SemaDecl.cpp
@@ -340,7 +340,7 @@
New->setParams(Context, &Params[0], Params.size());
}
-
+ AddKnownFunctionAttributes(New);
// TUScope is the translation-unit scope to insert this function into.
// FIXME: This is hideous. We need to teach PushOnScopeChains to
@@ -522,7 +522,7 @@
if (Old->isThisDeclarationADefinition())
PrevDiag = diag::note_previous_definition;
else if (Old->isImplicit()) {
- if (Old->getBuiltinID())
+ if (Old->getBuiltinID(Context))
PrevDiag = diag::note_previous_builtin_declaration;
else
PrevDiag = diag::note_previous_implicit_declaration;
@@ -1771,6 +1771,7 @@
// Handle attributes. We need to have merged decls when handling attributes
// (for example to check for conflicts, etc).
ProcessDeclAttributes(NewFD, D);
+ AddKnownFunctionAttributes(NewFD);
if (OverloadableAttrRequired && !NewFD->getAttr<OverloadableAttr>()) {
// If a function name is overloadable in C, then every function
@@ -1872,7 +1873,7 @@
case Expr::CallExprClass:
case Expr::CXXOperatorCallExprClass:
// __builtin___CFStringMakeConstantString is a valid constant l-value.
- if (cast<CallExpr>(Init)->isBuiltinCall() ==
+ if (cast<CallExpr>(Init)->isBuiltinCall(Context) ==
Builtin::BI__builtin___CFStringMakeConstantString)
return false;
@@ -2071,7 +2072,7 @@
const CallExpr *CE = cast<CallExpr>(Init);
// Allow any constant foldable calls to builtins.
- if (CE->isBuiltinCall() && CE->isEvaluatable(Context))
+ if (CE->isBuiltinCall(Context) && CE->isEvaluatable(Context))
return false;
InitializerElementNotConstant(Init);
@@ -2856,9 +2857,73 @@
CurContext = PrevDC;
+ AddKnownFunctionAttributes(FD);
+
return FD;
}
+/// \brief Adds any function attributes that we know a priori based on
+/// the declaration of this function.
+///
+/// These attributes can apply both to implicitly-declared builtins
+/// (like __builtin___printf_chk) or to library-declared functions
+/// like NSLog or printf.
+void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) {
+ if (FD->isInvalidDecl())
+ return;
+
+ // If this is a built-in function, map its builtin attributes to
+ // actual attributes.
+ if (unsigned BuiltinID = FD->getBuiltinID(Context)) {
+ // Handle printf-formatting attributes.
+ unsigned FormatIdx;
+ bool HasVAListArg;
+ if (Context.BuiltinInfo.isPrintfLike(BuiltinID, FormatIdx, HasVAListArg)) {
+ if (!FD->getAttr<FormatAttr>())
+ FD->addAttr(new FormatAttr("printf", FormatIdx + 1, FormatIdx + 2));
+ }
+ }
+
+ IdentifierInfo *Name = FD->getIdentifier();
+ if (!Name)
+ return;
+ if ((!getLangOptions().CPlusPlus &&
+ FD->getDeclContext()->isTranslationUnit()) ||
+ (isa<LinkageSpecDecl>(FD->getDeclContext()) &&
+ cast<LinkageSpecDecl>(FD->getDeclContext())->getLanguage() ==
+ LinkageSpecDecl::lang_c)) {
+ // Okay: this could be a libc/libm/Objective-C function we know
+ // about.
+ } else
+ return;
+
+ unsigned KnownID;
+ for (KnownID = 0; KnownID != id_num_known_functions; ++KnownID)
+ if (KnownFunctionIDs[KnownID] == Name)
+ break;
+
+ switch (KnownID) {
+ case id_NSLog:
+ case id_NSLogv:
+ if (const FormatAttr *Format = FD->getAttr<FormatAttr>()) {
+ // FIXME: We known better than our headers.
+ const_cast<FormatAttr *>(Format)->setType("printf");
+ } else
+ FD->addAttr(new FormatAttr("printf", 1, 2));
+ break;
+
+ case id_asprintf:
+ case id_vasprintf:
+ if (!FD->getAttr<FormatAttr>())
+ FD->addAttr(new FormatAttr("printf", 2, 3));
+ break;
+
+ default:
+ // Unknown function or known function without any attributes to
+ // add. Do nothing.
+ break;
+ }
+}
TypedefDecl *Sema::ParseTypedefDecl(Scope *S, Declarator &D, QualType T,
Decl *LastDeclarator) {
diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp
index ee3ef04..ef4ae54 100644
--- a/lib/Sema/SemaExpr.cpp
+++ b/lib/Sema/SemaExpr.cpp
@@ -1921,7 +1921,7 @@
if (Ovl || (getLangOptions().CPlusPlus && (FDecl || UnqualifiedName))) {
// We don't perform ADL for implicit declarations of builtins.
- if (FDecl && FDecl->getBuiltinID() && FDecl->isImplicit())
+ if (FDecl && FDecl->getBuiltinID(Context) && FDecl->isImplicit())
ADL = false;
// We don't perform ADL in C.
diff --git a/lib/Sema/SemaUtil.h b/lib/Sema/SemaUtil.h
deleted file mode 100644
index 5c64c76..0000000
--- a/lib/Sema/SemaUtil.h
+++ /dev/null
@@ -1,36 +0,0 @@
-//===--- SemaUtil.h - Utility functions for semantic analysis -------------===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file provides a few static inline functions that are useful for
-// performing semantic analysis.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_CLANG_SEMA_UTIL_H
-#define LLVM_CLANG_SEMA_UTIL_H
-
-#include "clang/AST/Expr.h"
-
-namespace clang {
-
-/// Utility method to determine if a CallExpr is a call to a builtin.
-static inline bool isCallBuiltin(CallExpr* cexp) {
- Expr* sub = cexp->getCallee()->IgnoreParenCasts();
-
- if (DeclRefExpr* E = dyn_cast<DeclRefExpr>(sub))
- if (FunctionDecl *Fn = dyn_cast<FunctionDecl>(E->getDecl()))
- if (Fn->getBuiltinID() > 0)
- return true;
-
- return false;
-}
-
-} // end namespace clang
-
-#endif
diff --git a/test/Sema/format-strings.c b/test/Sema/format-strings.c
index 9e558e9..707873f 100644
--- a/test/Sema/format-strings.c
+++ b/test/Sema/format-strings.c
@@ -85,3 +85,9 @@
printf("%*d","foo",x); // expected-warning {{field width should have type 'int', but argument has type 'char *'}}
printf("%.*d","foo",x); // expected-warning {{field precision should have type 'int', but argument has type 'char *'}}
}
+
+void __attribute__((format(printf,1,3))) myprintf(const char*, int blah, ...);
+
+void test_myprintf() {
+ myprintf("%d", 17, 18); // okay
+}