This patch adds support for the FreeBSD kernel specific printf format specifiers: %b, %D, %r and %y, via a new __freebsd_kprintf__ format string type. Sent upstream as http://reviews.llvm.org/D7154 Index: tools/clang/include/clang/Analysis/Analyses/FormatString.h =================================================================== --- tools/clang/include/clang/Analysis/Analyses/FormatString.h +++ tools/clang/include/clang/Analysis/Analyses/FormatString.h @@ -161,6 +161,12 @@ class ConversionSpecifier { ObjCObjArg, // '@' ObjCBeg = ObjCObjArg, ObjCEnd = ObjCObjArg, + // FreeBSD kernel specific specifiers. + FreeBSDbArg, + FreeBSDDArg, + FreeBSDrArg, + FreeBSDyArg, + // GlibC specific specifiers. PrintErrno, // 'm' @@ -204,7 +210,8 @@ class ConversionSpecifier { return EndScanList ? EndScanList - Position : 1; } - bool isIntArg() const { return kind >= IntArgBeg && kind <= IntArgEnd; } + bool isIntArg() const { return (kind >= IntArgBeg && kind <= IntArgEnd) || + kind == FreeBSDrArg || kind == FreeBSDyArg; } bool isUIntArg() const { return kind >= UIntArgBeg && kind <= UIntArgEnd; } bool isAnyIntArg() const { return kind >= IntArgBeg && kind <= UIntArgEnd; } const char *toString() const; @@ -646,7 +653,7 @@ class FormatStringHandler { bool ParsePrintfString(FormatStringHandler &H, const char *beg, const char *end, const LangOptions &LO, - const TargetInfo &Target); + const TargetInfo &Target, bool isFreeBSDKPrintf); bool ParseFormatStringHasSArg(const char *beg, const char *end, const LangOptions &LO, const TargetInfo &Target); Index: tools/clang/include/clang/Sema/Sema.h =================================================================== --- tools/clang/include/clang/Sema/Sema.h +++ tools/clang/include/clang/Sema/Sema.h @@ -8566,6 +8566,7 @@ class Sema { FST_Strftime, FST_Strfmon, FST_Kprintf, + FST_FreeBSDKPrintf, FST_Unknown }; static FormatStringType GetFormatStringType(const FormatAttr *Format); Index: tools/clang/lib/Analysis/FormatString.cpp =================================================================== --- tools/clang/lib/Analysis/FormatString.cpp +++ tools/clang/lib/Analysis/FormatString.cpp @@ -552,6 +552,12 @@ const char *ConversionSpecifier::toString() const // Objective-C specific specifiers. case ObjCObjArg: return "@"; + // FreeBSD kernel specific specifiers. + case FreeBSDbArg: return "b"; + case FreeBSDDArg: return "D"; + case FreeBSDrArg: return "r"; + case FreeBSDyArg: return "y"; + // GlibC specific specifiers. case PrintErrno: return "m"; @@ -647,6 +653,9 @@ bool FormatSpecifier::hasValidLengthModifier(const case ConversionSpecifier::XArg: case ConversionSpecifier::nArg: return true; + case ConversionSpecifier::FreeBSDrArg: + case ConversionSpecifier::FreeBSDyArg: + return Target.getTriple().isOSFreeBSD(); default: return false; } @@ -677,6 +686,9 @@ bool FormatSpecifier::hasValidLengthModifier(const case ConversionSpecifier::ScanListArg: case ConversionSpecifier::ZArg: return true; + case ConversionSpecifier::FreeBSDrArg: + case ConversionSpecifier::FreeBSDyArg: + return Target.getTriple().isOSFreeBSD(); default: return false; } @@ -807,6 +819,10 @@ bool FormatSpecifier::hasStandardConversionSpecifi case ConversionSpecifier::SArg: return LangOpt.ObjC1 || LangOpt.ObjC2; case ConversionSpecifier::InvalidSpecifier: + case ConversionSpecifier::FreeBSDbArg: + case ConversionSpecifier::FreeBSDDArg: + case ConversionSpecifier::FreeBSDrArg: + case ConversionSpecifier::FreeBSDyArg: case ConversionSpecifier::PrintErrno: case ConversionSpecifier::DArg: case ConversionSpecifier::OArg: Index: tools/clang/lib/Analysis/PrintfFormatString.cpp =================================================================== --- tools/clang/lib/Analysis/PrintfFormatString.cpp +++ tools/clang/lib/Analysis/PrintfFormatString.cpp @@ -55,7 +55,8 @@ static PrintfSpecifierResult ParsePrintfSpecifier( unsigned &argIndex, const LangOptions &LO, const TargetInfo &Target, - bool Warn) { + bool Warn, + bool isFreeBSDKPrintf) { using namespace clang::analyze_format_string; using namespace clang::analyze_printf; @@ -206,9 +207,24 @@ static PrintfSpecifierResult ParsePrintfSpecifier( case '@': k = ConversionSpecifier::ObjCObjArg; break; // Glibc specific. case 'm': k = ConversionSpecifier::PrintErrno; break; + // FreeBSD kernel specific. + case 'b': + if (isFreeBSDKPrintf) + k = ConversionSpecifier::FreeBSDbArg; // int followed by char * + break; + case 'r': + if (isFreeBSDKPrintf) + k = ConversionSpecifier::FreeBSDrArg; // int + break; + case 'y': + if (isFreeBSDKPrintf) + k = ConversionSpecifier::FreeBSDyArg; // int + break; // Apple-specific. case 'D': - if (Target.getTriple().isOSDarwin()) + if (isFreeBSDKPrintf) + k = ConversionSpecifier::FreeBSDDArg; // void * followed by char * + else if (Target.getTriple().isOSDarwin()) k = ConversionSpecifier::DArg; break; case 'O': @@ -228,6 +244,10 @@ static PrintfSpecifierResult ParsePrintfSpecifier( FS.setConversionSpecifier(CS); if (CS.consumesDataArgument() && !FS.usesPositionalArg()) FS.setArgIndex(argIndex++); + // FreeBSD kernel specific. + if (k == ConversionSpecifier::FreeBSDbArg || + k == ConversionSpecifier::FreeBSDDArg) + argIndex++; if (k == ConversionSpecifier::InvalidSpecifier) { // Assume the conversion takes one argument. @@ -240,7 +260,8 @@ bool clang::analyze_format_string::ParsePrintfStri const char *I, const char *E, const LangOptions &LO, - const TargetInfo &Target) { + const TargetInfo &Target, + bool isFreeBSDKPrintf) { unsigned argIndex = 0; @@ -247,7 +268,8 @@ bool clang::analyze_format_string::ParsePrintfStri // Keep looking for a format specifier until we have exhausted the string. while (I != E) { const PrintfSpecifierResult &FSR = ParsePrintfSpecifier(H, I, E, argIndex, - LO, Target, true); + LO, Target, true, + isFreeBSDKPrintf); // Did a fail-stop error of any kind occur when parsing the specifier? // If so, don't do any more processing. if (FSR.shouldStop()) @@ -276,7 +298,8 @@ bool clang::analyze_format_string::ParseFormatStri FormatStringHandler H; while (I != E) { const PrintfSpecifierResult &FSR = ParsePrintfSpecifier(H, I, E, argIndex, - LO, Target, false); + LO, Target, false, + false); // Did a fail-stop error of any kind occur when parsing the specifier? // If so, don't do any more processing. if (FSR.shouldStop()) @@ -674,6 +697,8 @@ bool PrintfSpecifier::hasValidPlusPrefix() const { case ConversionSpecifier::GArg: case ConversionSpecifier::aArg: case ConversionSpecifier::AArg: + case ConversionSpecifier::FreeBSDrArg: + case ConversionSpecifier::FreeBSDyArg: return true; default: @@ -699,6 +724,8 @@ bool PrintfSpecifier::hasValidAlternativeForm() co case ConversionSpecifier::FArg: case ConversionSpecifier::gArg: case ConversionSpecifier::GArg: + case ConversionSpecifier::FreeBSDrArg: + case ConversionSpecifier::FreeBSDyArg: return true; default: @@ -729,6 +756,8 @@ bool PrintfSpecifier::hasValidLeadingZeros() const case ConversionSpecifier::FArg: case ConversionSpecifier::gArg: case ConversionSpecifier::GArg: + case ConversionSpecifier::FreeBSDrArg: + case ConversionSpecifier::FreeBSDyArg: return true; default: @@ -753,6 +782,8 @@ bool PrintfSpecifier::hasValidSpacePrefix() const case ConversionSpecifier::GArg: case ConversionSpecifier::aArg: case ConversionSpecifier::AArg: + case ConversionSpecifier::FreeBSDrArg: + case ConversionSpecifier::FreeBSDyArg: return true; default: @@ -818,6 +849,8 @@ bool PrintfSpecifier::hasValidPrecision() const { case ConversionSpecifier::gArg: case ConversionSpecifier::GArg: case ConversionSpecifier::sArg: + case ConversionSpecifier::FreeBSDrArg: + case ConversionSpecifier::FreeBSDyArg: return true; default: Index: tools/clang/lib/Sema/SemaChecking.cpp =================================================================== --- tools/clang/lib/Sema/SemaChecking.cpp +++ tools/clang/lib/Sema/SemaChecking.cpp @@ -2584,6 +2584,7 @@ Sema::FormatStringType Sema::GetFormatStringType(c .Case("strftime", FST_Strftime) .Case("strfmon", FST_Strfmon) .Cases("kprintf", "cmn_err", "vcmn_err", "zcmn_err", FST_Kprintf) + .Case("freebsd_kprintf", FST_FreeBSDKPrintf) .Default(FST_Unknown); } @@ -3365,6 +3366,43 @@ CheckPrintfHandler::HandlePrintfSpecifier(const an CoveredArgs.set(argIndex); } + // FreeBSD kernel extensions. + if (CS.getKind() == ConversionSpecifier::FreeBSDbArg || + CS.getKind() == ConversionSpecifier::FreeBSDDArg) { + // We need at least two arguments. + if (!CheckNumArgs(FS, CS, startSpecifier, specifierLen, argIndex + 1)) + return false; + + // Claim the second argument. + CoveredArgs.set(argIndex + 1); + + // Type check the first argument (int for %b, pointer for %D) + const Expr *Ex = getDataArg(argIndex); + const analyze_printf::ArgType &AT = + (CS.getKind() == ConversionSpecifier::FreeBSDbArg) ? + ArgType(S.Context.IntTy) : ArgType::CPointerTy; + if (AT.isValid() && !AT.matchesType(S.Context, Ex->getType())) + EmitFormatDiagnostic( + S.PDiag(diag::warn_format_conversion_argument_type_mismatch) + << AT.getRepresentativeTypeName(S.Context) << Ex->getType() + << false << Ex->getSourceRange(), + Ex->getLocStart(), /*IsStringLocation*/false, + getSpecifierRange(startSpecifier, specifierLen)); + + // Type check the second argument (char * for both %b and %D) + Ex = getDataArg(argIndex + 1); + const analyze_printf::ArgType &AT2 = ArgType::CStrTy; + if (AT2.isValid() && !AT2.matchesType(S.Context, Ex->getType())) + EmitFormatDiagnostic( + S.PDiag(diag::warn_format_conversion_argument_type_mismatch) + << AT2.getRepresentativeTypeName(S.Context) << Ex->getType() + << false << Ex->getSourceRange(), + Ex->getLocStart(), /*IsStringLocation*/false, + getSpecifierRange(startSpecifier, specifierLen)); + + return true; + } + // Check for using an Objective-C specific conversion specifier // in a non-ObjC literal. if (!ObjCContext && CS.isObjCArg()) { @@ -3988,7 +4026,8 @@ void Sema::CheckFormatString(const StringLiteral * return; } - if (Type == FST_Printf || Type == FST_NSString) { + if (Type == FST_Printf || Type == FST_NSString || + Type == FST_FreeBSDKPrintf) { CheckPrintfHandler H(*this, FExpr, OrigFormatExpr, firstDataArg, numDataArgs, (Type == FST_NSString), Str, HasVAListArg, Args, format_idx, @@ -3996,7 +4035,8 @@ void Sema::CheckFormatString(const StringLiteral * if (!analyze_format_string::ParsePrintfString(H, Str, Str + StrLen, getLangOpts(), - Context.getTargetInfo())) + Context.getTargetInfo(), + Type == FST_FreeBSDKPrintf)) H.DoneProcessing(); } else if (Type == FST_Scanf) { CheckScanfHandler H(*this, FExpr, OrigFormatExpr, firstDataArg, numDataArgs, Index: tools/clang/lib/Sema/SemaDeclAttr.cpp =================================================================== --- tools/clang/lib/Sema/SemaDeclAttr.cpp +++ tools/clang/lib/Sema/SemaDeclAttr.cpp @@ -2481,6 +2481,7 @@ static FormatAttrKind getFormatAttrKind(StringRef .Cases("scanf", "printf", "printf0", "strfmon", SupportedFormat) .Cases("cmn_err", "vcmn_err", "zcmn_err", SupportedFormat) .Case("kprintf", SupportedFormat) // OpenBSD. + .Case("freebsd_kprintf", SupportedFormat) // FreeBSD. .Cases("gcc_diag", "gcc_cdiag", "gcc_cxxdiag", "gcc_tdiag", IgnoredFormat) .Default(InvalidFormat); Index: tools/clang/test/Sema/attr-format.c =================================================================== --- tools/clang/test/Sema/attr-format.c +++ tools/clang/test/Sema/attr-format.c @@ -57,8 +57,15 @@ void callnull(void){ null(0, (int*)0); // expected-warning {{incompatible pointer types}} } +// FreeBSD kernel extensions +void a3(const char *a, ...) __attribute__((format(freebsd_kprintf, 1,2))); // no-error +void b3(const char *a, ...) __attribute__((format(freebsd_kprintf, 1,1))); // expected-error {{'format' attribute parameter 3 is out of bounds}} +void c3(const char *a, ...) __attribute__((format(freebsd_kprintf, 0,2))); // expected-error {{'format' attribute parameter 2 is out of bounds}} +void d3(const char *a, int c) __attribute__((format(freebsd_kprintf, 1,2))); // expected-error {{format attribute requires variadic function}} +void e3(char *str, int c, ...) __attribute__((format(freebsd_kprintf, 2,3))); // expected-error {{format argument not a string type}} + // PR4470 int xx_vprintf(const char *, va_list); Index: tools/clang/test/Sema/format-strings-freebsd.c =================================================================== --- tools/clang/test/Sema/format-strings-freebsd.c +++ tools/clang/test/Sema/format-strings-freebsd.c @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -triple i386-unknown-freebsd %s +// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64-unknown-freebsd %s + +// Test FreeBSD kernel printf extensions. +int freebsd_kernel_printf(const char *, ...) __attribute__((__format__(__freebsd_kprintf__, 1, 2))); + +void check_freebsd_kernel_extensions(int i, long l, char *s) +{ + // %b expects an int and a char * + freebsd_kernel_printf("reg=%b\n", i, "\10\2BITTWO\1BITONE\n"); // no-warning + freebsd_kernel_printf("reg=%b\n", l, "\10\2BITTWO\1BITONE\n"); // expected-warning{{format specifies type 'int' but the argument has type 'long'}} + freebsd_kernel_printf("reg=%b\n", i, l); // expected-warning{{format specifies type 'char *' but the argument has type 'long'}} + freebsd_kernel_printf("reg=%b\n", i); // expected-warning{{more '%' conversions than data arguments}} + freebsd_kernel_printf("reg=%b\n", i, "\10\2BITTWO\1BITONE\n", l); // expected-warning{{data argument not used by format string}} + + // %D expects an unsigned char * and a char * + freebsd_kernel_printf("%6D", s, ":"); // no-warning + freebsd_kernel_printf("%6D", i, ":"); // expected-warning{{format specifies type 'void *' but the argument has type 'int'}} + freebsd_kernel_printf("%6D", s, i); // expected-warning{{format specifies type 'char *' but the argument has type 'int'}} + freebsd_kernel_printf("%6D", s); // expected-warning{{more '%' conversions than data arguments}} + freebsd_kernel_printf("%6D", s, ":", i); // expected-warning{{data argument not used by format string}} + + freebsd_kernel_printf("%*D", 42, s, ":"); // no-warning + freebsd_kernel_printf("%*D", 42, i, ":"); // expected-warning{{format specifies type 'void *' but the argument has type 'int'}} + freebsd_kernel_printf("%*D", 42, s, i); // expected-warning{{format specifies type 'char *' but the argument has type 'int'}} + freebsd_kernel_printf("%*D", 42, s); // expected-warning{{more '%' conversions than data arguments}} + freebsd_kernel_printf("%*D", 42, s, ":", i); // expected-warning{{data argument not used by format string}} + + // %r expects an int + freebsd_kernel_printf("%r", i); // no-warning + freebsd_kernel_printf("%r", l); // expected-warning{{format specifies type 'int' but the argument has type 'long'}} + freebsd_kernel_printf("%lr", i); // expected-warning{{format specifies type 'long' but the argument has type 'int'}} + freebsd_kernel_printf("%lr", l); // no-warning + + // %y expects an int + freebsd_kernel_printf("%y", i); // no-warning + freebsd_kernel_printf("%y", l); // expected-warning{{format specifies type 'int' but the argument has type 'long'}} + freebsd_kernel_printf("%ly", i); // expected-warning{{format specifies type 'long' but the argument has type 'int'}} + freebsd_kernel_printf("%ly", l); // no-warning +}