Index: clang/include/clang/Basic/Attr.td =================================================================== --- clang/include/clang/Basic/Attr.td +++ clang/include/clang/Basic/Attr.td @@ -1567,6 +1567,13 @@ let Documentation = [PassObjectSizeDocs]; } +def FortifyStdLib : InheritableAttr { + let Spellings = [Clang<"fortify_stdlib">]; + let Args = [IntArgument<"Type">, IntArgument<"Flag">]; + let Subjects = SubjectList<[Function]>; + let Documentation = [FortifyStdLibDocs]; +} + // Nullability type attributes. def TypeNonNull : TypeAttr { let Spellings = [Keyword<"_Nonnull">]; Index: clang/include/clang/Basic/AttrDocs.td =================================================================== --- clang/include/clang/Basic/AttrDocs.td +++ clang/include/clang/Basic/AttrDocs.td @@ -965,6 +965,52 @@ }]; } +def FortifyStdLibDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The ``fortify_stdlib`` attribute applies to declarations of C stdlib functions +(memcpy, sprintf, ...), and causes clang to emit calls to their fortified +variants with ``__builtin_object_size``. For instance: + +.. code-block:: c + + __attribute__((fortify_stdlib(0, 0))) + int sprintf(char *buf, const char *fmt, ...); + + int main() { + char buf[5]; + sprintf(buf, "%f", 42.0); + // Clang generates code equivalent to: + // __sprintf_chk(buf, 0, __builtin_object_size(0), "%f", 42.0); + } + +The first argument to the attribute is the type passed to +`__builtin_object_size`, and the second is the flag that the fortified format +functions accept. + +Only a specific set of standard library functions are supported: + - memcpy + - memmove + - memset + - stpcpy + - strcat + - strcpy + - strlcat + - strlcpy + - strncat + - strncpy + - stpncpy + - snprintf + - vsnprintf + - sprintf + - vsprintf + - fprintf + - vfprintf + - printf + - vprintf +}]; +} + def ConvergentDocs : Documentation { let Category = DocCatFunction; let Content = [{ Index: clang/include/clang/Basic/Builtins.h =================================================================== --- clang/include/clang/Basic/Builtins.h +++ clang/include/clang/Basic/Builtins.h @@ -242,7 +242,13 @@ const char *Fmt) const; }; -} +/// For a given BuiltinID, return the ID of the fortified variant function. For +/// instance, if Builtin::BIsprintf is passed, this function will return +/// Builtin::BI__builtin___sprintf_chk. If BuiltinID doesn't have a fortified +/// variant, 0 is returned. +unsigned getFortifiedVariantFunction(unsigned BuiltinID); + +} // end namespace Builtin /// Kinds of BuiltinTemplateDecl. enum BuiltinTemplateKind : int { Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4820,6 +4820,8 @@ def ext_forward_ref_enum_def : Extension< "redeclaration of already-defined enum %0 is a GNU extension">, InGroup; +def err_fortify_std_lib_bad_decl : Error< + "'fortify_stdlib' attribute applied to an unknown function">; def err_redefinition_of_enumerator : Error<"redefinition of enumerator %0">; def err_duplicate_member : Error<"duplicate member %0">; Index: clang/lib/Basic/Builtins.cpp =================================================================== --- clang/lib/Basic/Builtins.cpp +++ clang/lib/Basic/Builtins.cpp @@ -188,3 +188,28 @@ (!hasReferenceArgsOrResult(ID) && !hasCustomTypechecking(ID)); } + +unsigned Builtin::getFortifiedVariantFunction(unsigned BuiltinID) { + switch (BuiltinID) { + case Builtin::BImemcpy: return Builtin::BI__builtin___memcpy_chk; + case Builtin::BImemmove: return Builtin::BI__builtin___memmove_chk; + case Builtin::BImemset: return Builtin::BI__builtin___memset_chk; + case Builtin::BIstpcpy: return Builtin::BI__builtin___stpcpy_chk; + case Builtin::BIstrcat: return Builtin::BI__builtin___strcat_chk; + case Builtin::BIstrcpy: return Builtin::BI__builtin___strcpy_chk; + case Builtin::BIstrlcat: return Builtin::BI__builtin___strlcat_chk; + case Builtin::BIstrlcpy: return Builtin::BI__builtin___strlcpy_chk; + case Builtin::BIstrncat: return Builtin::BI__builtin___strncat_chk; + case Builtin::BIstrncpy: return Builtin::BI__builtin___strncpy_chk; + case Builtin::BIstpncpy: return Builtin::BI__builtin___stpncpy_chk; + case Builtin::BIsnprintf: return Builtin::BI__builtin___snprintf_chk; + case Builtin::BIvsnprintf: return Builtin::BI__builtin___vsnprintf_chk; + case Builtin::BIsprintf: return Builtin::BI__builtin___sprintf_chk; + case Builtin::BIvsprintf: return Builtin::BI__builtin___vsprintf_chk; + case Builtin::BIfprintf: return Builtin::BI__builtin___fprintf_chk; + case Builtin::BIvfprintf: return Builtin::BI__builtin___vfprintf_chk; + case Builtin::BIprintf: return Builtin::BI__builtin___printf_chk; + case Builtin::BIvprintf: return Builtin::BI__builtin___vprintf_chk; + default: return 0; + } +} Index: clang/lib/CodeGen/CGBuiltin.cpp =================================================================== --- clang/lib/CodeGen/CGBuiltin.cpp +++ clang/lib/CodeGen/CGBuiltin.cpp @@ -1474,6 +1474,87 @@ return RValue::get(Builder.CreateCall(F, { Src, Src, ShiftAmt })); } +/// For a call to a builtin C standard library function, emit a call to a +/// fortified variant using __builtin_object_size. For instance, instead of +/// emitting `sprintf(buf, "%d", 32)`, this function would emit +/// `__sprintf_chk(buf, Flag, __builtin_object_size(buf, 0), "%d", 32)`. +RValue CodeGenFunction::emitFortifiedStdLibCall(CodeGenFunction &CGF, + const CallExpr *CE, + unsigned BuiltinID, + unsigned BOSType, + unsigned Flag) { + SmallVector ArgVals; + for (const Expr *Arg : CE->arguments()) + ArgVals.push_back(EmitScalarExpr(Arg)); + + llvm::Value *FlagVal = llvm::ConstantInt::get(IntTy, Flag); + auto emitObjSize = [&]() -> llvm::Value * { + return evaluateOrEmitBuiltinObjectSize(CE->getArg(0), BOSType, SizeTy, + ArgVals[0], false); + }; + + unsigned FortifiedVariantID = Builtin::getFortifiedVariantFunction(BuiltinID); + assert(FortifiedVariantID != 0 && "Should be diagnosed in Sema"); + + // Adjust ArgVals to include a __builtin_object_size(n) or flag argument at + // the right position. Variadic printf-like functions take a flag and object + // size (if they're printing to a string) before the format string, and all + // other functions just take the object size as their last argument. The + // object size, if present, always corresponds to the first argument. + switch (BuiltinID) { + case Builtin::BImemcpy: + case Builtin::BImemmove: + case Builtin::BImemset: + case Builtin::BIstpcpy: + case Builtin::BIstrcat: + case Builtin::BIstrcpy: + case Builtin::BIstrlcat: + case Builtin::BIstrlcpy: + case Builtin::BIstrncat: + case Builtin::BIstrncpy: + case Builtin::BIstpncpy: + ArgVals.push_back(emitObjSize()); + break; + + case Builtin::BIsnprintf: + case Builtin::BIvsnprintf: + ArgVals.insert(ArgVals.begin() + 2, FlagVal); + ArgVals.insert(ArgVals.begin() + 3, emitObjSize()); + break; + + case Builtin::BIsprintf: + case Builtin::BIvsprintf: + ArgVals.insert(ArgVals.begin() + 1, FlagVal); + ArgVals.insert(ArgVals.begin() + 2, emitObjSize()); + break; + + case Builtin::BIfprintf: + case Builtin::BIvfprintf: + ArgVals.insert(ArgVals.begin() + 1, FlagVal); + break; + + case Builtin::BIprintf: + case Builtin::BIvprintf: + ArgVals.insert(ArgVals.begin(), FlagVal); + break; + + default: + llvm_unreachable("Unknown fortified builtin?"); + } + + ASTContext::GetBuiltinTypeError Err; + QualType VariantTy = getContext().GetBuiltinType(FortifiedVariantID, Err); + assert(Err == ASTContext::GE_None && "Should not codegen an error"); + llvm::FunctionType *LLVMVariantTy = + cast(ConvertType(VariantTy)); + StringRef VariantName = getContext().BuiltinInfo.getName(FortifiedVariantID) + + strlen("__builtin_"); + + llvm::Value *V = Builder.CreateCall( + CGM.CreateRuntimeFunction(LLVMVariantTy, VariantName), ArgVals); + return RValue::get(V); +} + RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, const CallExpr *E, ReturnValueSlot ReturnValue) { @@ -1490,6 +1571,10 @@ Result.Val.getFloat())); } + if (auto *Attr = FD->getAttr()) + return emitFortifiedStdLibCall(*this, E, BuiltinID, Attr->getType(), + Attr->getFlag()); + // There are LLVM math intrinsics/instructions corresponding to math library // functions except the LLVM op will never set errno while the math library // might. Also, math builtins have the same semantics as their math library Index: clang/lib/CodeGen/CodeGenFunction.h =================================================================== --- clang/lib/CodeGen/CodeGenFunction.h +++ clang/lib/CodeGen/CodeGenFunction.h @@ -3697,6 +3697,10 @@ RValue EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, const CallExpr *E, ReturnValueSlot ReturnValue); + RValue emitFortifiedStdLibCall(CodeGenFunction &CGF, const CallExpr *CE, + unsigned BuiltinID, unsigned BOSType, + unsigned Flag); + RValue emitRotate(const CallExpr *E, bool IsRotateRight); /// Emit IR for __builtin_os_log_format. Index: clang/lib/Sema/SemaDeclAttr.cpp =================================================================== --- clang/lib/Sema/SemaDeclAttr.cpp +++ clang/lib/Sema/SemaDeclAttr.cpp @@ -6419,6 +6419,31 @@ handleSimpleAttribute(S, D, AL); } +static void handleFortifyStdLib(Sema &S, Decl *D, const ParsedAttr &AL) { + auto *FD = cast(D); + unsigned VariantID = Builtin::getFortifiedVariantFunction(FD->getBuiltinID()); + if (VariantID == 0) { + S.Diag(D->getLocation(), diag::err_fortify_std_lib_bad_decl); + return; + } + + uint32_t BOSType, Flag; + if (!checkUInt32Argument(S, AL, AL.getArgAsExpr(0), BOSType, 0, true) || + !checkUInt32Argument(S, AL, AL.getArgAsExpr(1), Flag, 1, true)) + return; + + if (BOSType > 3) { + S.Diag(AL.getArgAsExpr(0)->getBeginLoc(), + diag::err_attribute_argument_outof_range) + << AL << 0 << 3; + return; + } + + D->addAttr(::new (S.getASTContext()) FortifyStdLibAttr( + AL.getLoc(), S.getASTContext(), BOSType, Flag, + AL.getAttributeSpellingListIndex())); +} + //===----------------------------------------------------------------------===// // Top Level Sema Entry Points //===----------------------------------------------------------------------===// @@ -7148,6 +7173,10 @@ case ParsedAttr::AT_ObjCExternallyRetained: handleObjCExternallyRetainedAttr(S, D, AL); break; + + case ParsedAttr::AT_FortifyStdLib: + handleFortifyStdLib(S, D, AL); + break; } } Index: clang/test/CodeGen/fortify-std-lib.c =================================================================== --- /dev/null +++ clang/test/CodeGen/fortify-std-lib.c @@ -0,0 +1,220 @@ +// RUN: %clang_cc1 -triple x86_64-apple-macosx10.14.0 %s -emit-llvm -O0 -disable-llvm-passes -o - -Wno-format-security | FileCheck %s +// RUN: %clang_cc1 -xc++ -triple x86_64-apple-macosx10.14.0 %s -emit-llvm -O0 -disable-llvm-passes -o - -Wno-format-security | FileCheck %s + +#ifdef __cplusplus +#define EXTERN extern "C" +#else +#define EXTERN +#endif + +#define FSL(x,y) __attribute__((fortify_stdlib(x,y))) +typedef unsigned long size_t; + +FSL(0, 0) EXTERN +void *memcpy(void *dst, const void *src, size_t sz); + +EXTERN +void call_memcpy(void *dst, const void *src, size_t sz) { + memcpy(dst, src, sz); + // CHECK-LABEL: define void @call_memcpy + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i8* @__memcpy_chk(i8* {{.*}}, i8* {{.*}}, i64 {{.*}}, i64 [[REG]]) +} + +FSL(0, 0) EXTERN +void *memmove(void *dst, const void *src, size_t sz); + +EXTERN +void call_memmove(void *dst, const void *src, size_t sz) { + memmove(dst, src, sz); + // CHECK-LABEL: define void @call_memmove + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i8* @__memmove_chk(i8* {{.*}}, i8* {{.*}}, i64 {{.*}}, i64 [[REG]]) +} + +FSL(0, 0) EXTERN +void *memset(void *dst, int c, size_t sz); + +EXTERN +void call_memset(void *dst, int c, size_t sz) { + memset(dst, c, sz); + // CHECK-LABEL: define void @call_memset + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i8* @__memset_chk(i8* {{.*}}, i32 {{.*}}, i64 {{.*}}, i64 [[REG]]) +} + +FSL(0, 0) EXTERN +char *stpcpy(char* dst, const char *src); + +EXTERN +void call_stpcpy(char *dst, const char *src) { + stpcpy(dst, src); + // CHECK-LABEL: define void @call_stpcpy + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i8* @__stpcpy_chk(i8* {{.*}}, i8* {{.*}}, i64 [[REG]]) +} + +FSL(0, 0) EXTERN +char *strcat(char* dst, const char *src); + +EXTERN +void call_strcat(char *dst, const char *src) { + strcat(dst, src); + // CHECK-LABEL: define void @call_strcat + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i8* @__strcat_chk(i8* {{.*}}, i8* {{.*}}, i64 [[REG]]) +} + +FSL(0, 0) EXTERN +char *strcpy(char* dst, const char *src); + +EXTERN +void call_strcpy(char *dst, const char *src) { + strcpy(dst, src); + // CHECK-LABEL: define void @call_strcpy + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i8* @__strcpy_chk(i8* {{.*}}, i8* {{.*}}, i64 [[REG]]) +} + +FSL(0, 0) EXTERN +size_t strlcat(char* dst, const char *src, size_t len); + +EXTERN +void call_strlcat(char *dst, const char *src, size_t len) { + strlcat(dst, src, len); + // CHECK-LABEL: define void @call_strlcat + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i64 @__strlcat_chk(i8* {{.*}}, i8* {{.*}}, i64 {{.*}}, i64 [[REG]]) +} + +FSL(0, 0) EXTERN +size_t strlcpy(char* dst, const char *src, size_t len); + +EXTERN +void call_strlcpy(char *dst, const char *src, size_t len) { + strlcpy(dst, src, len); + // CHECK-LABEL: define void @call_strlcpy + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i64 @__strlcpy_chk(i8* {{.*}}, i8* {{.*}}, i64 {{.*}}, i64 [[REG]]) +} + +FSL(0, 0) EXTERN +char *strncat(char* dst, const char *src, size_t len); + +EXTERN +void call_strncat(char *dst, const char *src, size_t len) { + strncat(dst, src, len); + // CHECK-LABEL: define void @call_strncat + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i8* @__strncat_chk(i8* {{.*}}, i8* {{.*}}, i64 {{.*}}, i64 [[REG]]) +} + +FSL(0, 0) EXTERN +char *strncpy(char* dst, const char *src, size_t len); + +EXTERN +void call_strncpy(char *dst, const char *src, size_t len) { + strncpy(dst, src, len); + // CHECK-LABEL: define void @call_strncpy + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i8* @__strncpy_chk(i8* {{.*}}, i8* {{.*}}, i64 {{.*}}, i64 [[REG]]) +} + +FSL(0, 0) EXTERN +char *stpncpy(char* dst, const char *src, size_t len); + +EXTERN +void call_stpncpy(char *dst, const char *src, size_t len) { + stpncpy(dst, src, len); + // CHECK-LABEL: define void @call_stpncpy + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i8* @__stpncpy_chk(i8* {{.*}}, i8* {{.*}}, i64 {{.*}}, i64 [[REG]]) +} + +FSL(0, 0) EXTERN +int snprintf(char *buf, size_t n, const char *fmt, ...); + +EXTERN +void call_snprintf(char *buf, size_t n, const char *fmt) { + snprintf(buf, n, fmt); + // CHECK-LABEL: define void @call_snprintf + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i32 (i8*, i64, i32, i64, i8*, ...) @__snprintf_chk(i8* {{.*}}, i64 {{.*}}, i32 0, i64 [[REG]] +} + +FSL(0, 0) EXTERN +int vsnprintf(char *buf, size_t n, const char *fmt, __builtin_va_list lst); + +EXTERN +void call_vsnprintf(char *buf, size_t n, const char *fmt, __builtin_va_list lst) { + vsnprintf(buf, n, fmt, lst); + // CHECK-LABEL: define void @call_vsnprintf + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i32 @__vsnprintf_chk(i8* {{.*}}, i64 {{.*}}, i32 0, i64 [[REG]] +} + +FSL(0,0) EXTERN +int sprintf(char *buf, const char *fmt, ...); + +void call_sprintf(char *buf, const char* fmt) { + sprintf(buf, fmt); + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i32 (i8*, i32, i64, i8*, ...) @__sprintf_chk(i8* {{.*}}, i32 0, i64 [[REG]] + sprintf(buf, fmt, 1, 2, 3); + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i32 (i8*, i32, i64, i8*, ...) @__sprintf_chk(i8* {{.*}}, i32 0, i64 [[REG]], i8* {{.*}}, i32 1, i32 2, i32 3) +} + +FSL(0, 0) EXTERN +int vsprintf(char *buf, const char *fmt, __builtin_va_list lst); + +EXTERN +void call_vsprintf(char *buf, const char *fmt, __builtin_va_list lst) { + vsprintf(buf, fmt, lst); + // CHECK-LABEL: define void @call_vsprintf + // CHECK: [[REG:%[0-9]+]] = call i64 @llvm.objectsize.i64.p0i8(i8*{{.*}}, i1 false, i1 true, i1 false) + // CHECK: call i32 @__vsprintf_chk(i8* {{.*}}, i32 0, i64 [[REG]] +} + +typedef struct {} FILE; + +FSL(0, 0) EXTERN +int fprintf(FILE *file, const char *fmt, ...); + +EXTERN +void call_fprintf(FILE *file, const char *fmt) { + fprintf(file, fmt); + // CHECK-LABEL: define void @call_fprintf + // CHECK: call i32 ({{.*}}*, i32, i8*, ...) @__fprintf_chk({{.*}}, i32 0, i8* {{.*}}) +} + +FSL(0, 0) EXTERN +int vfprintf(FILE *file, const char *fmt, __builtin_va_list lst); + +EXTERN +void call_vfprintf(FILE *file, const char *fmt, __builtin_va_list lst) { + vfprintf(file, fmt, lst); + // CHECK-LABEL: define void @call_vfprintf + // CHECK: call i32 @__vfprintf_chk({{.*}}, i32 0, i8* {{.*}}, {{.*}}) +} + +FSL(0, 0) EXTERN +int printf(const char *fmt, ...); + +EXTERN +void call_printf(const char *fmt) { + printf(fmt); + // CHECK-LABEL: define void @call_printf + // CHECK: call i32 (i32, i8*, ...) @__printf_chk(i32 0, i8* {{.*}}) +} + +FSL(0, 0) EXTERN +int vprintf(const char *fmt, __builtin_va_list lst); + +EXTERN +void call_vprintf(const char *fmt, __builtin_va_list lst) { + vprintf(fmt, lst); + // CHECK-LABEL: define void @call_vprintf + // CHECK: call i32 @__vprintf_chk(i32 0, {{.*}}) +} + Index: clang/test/Misc/pragma-attribute-supported-attributes-list.test =================================================================== --- clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -52,6 +52,7 @@ // CHECK-NEXT: ExternalSourceSymbol ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable)) // CHECK-NEXT: FlagEnum (SubjectMatchRule_enum) // CHECK-NEXT: Flatten (SubjectMatchRule_function) +// CHECK-NEXT: FortifyStdLib (SubjectMatchRule_function) // CHECK-NEXT: GNUInline (SubjectMatchRule_function) // CHECK-NEXT: Hot (SubjectMatchRule_function) // CHECK-NEXT: IBAction (SubjectMatchRule_objc_method_is_instance) Index: clang/test/Sema/fortify-std-lib.c =================================================================== --- /dev/null +++ clang/test/Sema/fortify-std-lib.c @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -triple x86_64-apple-macosx10.14.0 -fsyntax-only %s -verify + +typedef unsigned long size_t; + +__attribute__((fortify_stdlib(0, 0))) +int not_anything_special(); // expected-error {{'fortify_stdlib' attribute applied to an unknown function}} + +__attribute__((fortify_stdlib(4, 0))) // expected-error {{'fortify_stdlib' attribute requires integer constant between 0 and 3 inclusive}} +int sprintf(char *, const char *, ...); + +__attribute__((fortify_stdlib())) // expected-error {{'fortify_stdlib' attribute requires exactly 2 arguments}} +int sprintf(char *, const char *, ...); + +__attribute__((fortify_stdlib(1, 2, 3))) // expected-error {{'fortify_stdlib' attribute requires exactly 2 arguments}} +int sprintf(char *, const char *, ...); + +__attribute__((fortify_stdlib(-1, 2))) // expected-error {{'fortify_stdlib' attribute requires a non-negative integral compile time constant expression}} +int sprintf(char *, const char *, ...);