Index: clang/include/clang/AST/FormatString.h =================================================================== --- clang/include/clang/AST/FormatString.h +++ clang/include/clang/AST/FormatString.h @@ -251,7 +251,20 @@ enum Kind { UnknownTy, InvalidTy, SpecificTy, ObjCPointerTy, CPointerTy, AnyCharTy, CStrTy, WCStrTy, WIntTy }; - enum MatchKind { NoMatch = 0, Match = 1, NoMatchPedantic }; + /// How well a given conversion specifier matches its argument. + enum MatchKind { + /// The conversion specifier and the argument types are incompatible. For + /// instance, "%d" and float. + NoMatch = 0, + /// The conversion specifier and the argument type are compatible. + Match = 1, + /// The conversion specifier and the argument type are disallowed by the C + /// standard, but in practice harmless. For instance, "%p" and int*. + NoMatchPedantic, + /// The conversion specifier and the argument type are compatible, but still + /// seems likely to be an error. For instance, "%hd" and _Bool. + NoMatchTypeConfusion, + }; private: const Kind K; Index: clang/include/clang/Basic/DiagnosticGroups.td =================================================================== --- clang/include/clang/Basic/DiagnosticGroups.td +++ clang/include/clang/Basic/DiagnosticGroups.td @@ -778,6 +778,7 @@ def FormatNonStandard : DiagGroup<"format-non-iso">; def FormatY2K : DiagGroup<"format-y2k">; def FormatPedantic : DiagGroup<"format-pedantic">; +def FormatTypeConfusion : DiagGroup<"format-type-confusion">; def Format : DiagGroup<"format", [FormatExtraArgs, FormatZeroLength, NonNull, FormatSecurity, FormatY2K, FormatInvalidSpecifier]>, Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -8055,16 +8055,17 @@ "%select{type|underlying type}2 %1">, InGroup; def warn_format_conversion_argument_type_mismatch_pedantic : Extension< - "format specifies type %0 but the argument has " - "%select{type|underlying type}2 %1">, + warn_format_conversion_argument_type_mismatch.Text>, InGroup; +def warn_format_conversion_argument_type_mismatch_confusion : Warning< + warn_format_conversion_argument_type_mismatch.Text>, + InGroup, DefaultIgnore; def warn_format_argument_needs_cast : Warning< "%select{values of type|enum values with underlying type}2 '%0' should not " "be used as format arguments; add an explicit cast to %1 instead">, InGroup; def warn_format_argument_needs_cast_pedantic : Warning< - "%select{values of type|enum values with underlying type}2 '%0' should not " - "be used as format arguments; add an explicit cast to %1 instead">, + warn_format_argument_needs_cast.Text>, InGroup, DefaultIgnore; def warn_printf_positional_arg_exceeds_data_args : Warning < "data argument position '%0' exceeds the number of data arguments (%1)">, Index: clang/lib/AST/FormatString.cpp =================================================================== --- clang/lib/AST/FormatString.cpp +++ clang/lib/AST/FormatString.cpp @@ -389,7 +389,7 @@ case BuiltinType::UChar: case BuiltinType::Bool: if (T == C.UnsignedShortTy || T == C.ShortTy) - return NoMatchPedantic; + return NoMatchTypeConfusion; return T == C.UnsignedCharTy || T == C.SignedCharTy ? Match : NoMatch; case BuiltinType::Short: Index: clang/lib/Sema/SemaChecking.cpp =================================================================== --- clang/lib/Sema/SemaChecking.cpp +++ clang/lib/Sema/SemaChecking.cpp @@ -8135,9 +8135,7 @@ return true; } - const analyze_printf::ArgType::MatchKind Match = - AT.matchesType(S.Context, ExprTy); - bool Pedantic = Match == analyze_printf::ArgType::NoMatchPedantic; + analyze_printf::ArgType::MatchKind Match = AT.matchesType(S.Context, ExprTy); if (Match == analyze_printf::ArgType::Match) return true; @@ -8161,8 +8159,10 @@ AT.matchesType(S.Context, ExprTy); if (ImplicitMatch == analyze_printf::ArgType::Match) return true; - if (ImplicitMatch == analyze_printf::ArgType::NoMatchPedantic) - Pedantic = true; + if (ImplicitMatch == ArgType::NoMatchPedantic) + Match = ArgType::NoMatchPedantic; + if (ImplicitMatch == ArgType::NoMatchTypeConfusion) + Match = ArgType::NoMatchTypeConfusion; } } } else if (const CharacterLiteral *CL = dyn_cast(E)) { @@ -8224,7 +8224,7 @@ if ((CastTyName == "NSInteger" || CastTyName == "NSUInteger") && (AT.isSizeT() || AT.isPtrdiffT()) && AT.matchesType(S.Context, CastTy)) - Pedantic = true; + Match = ArgType::NoMatchPedantic; IntendedTy = CastTy; ShouldNotPrintDirectly = true; } @@ -8244,10 +8244,20 @@ CharSourceRange SpecRange = getSpecifierRange(StartSpecifier, SpecifierLen); if (IntendedTy == ExprTy && !ShouldNotPrintDirectly) { - unsigned Diag = - Pedantic - ? diag::warn_format_conversion_argument_type_mismatch_pedantic - : diag::warn_format_conversion_argument_type_mismatch; + unsigned Diag; + switch (Match) { + case ArgType::Match: llvm_unreachable("expected non-matching"); + case ArgType::NoMatchPedantic: + Diag = diag::warn_format_conversion_argument_type_mismatch_pedantic; + break; + case ArgType::NoMatchTypeConfusion: + Diag = diag::warn_format_conversion_argument_type_mismatch_confusion; + break; + case ArgType::NoMatch: + Diag = diag::warn_format_conversion_argument_type_mismatch; + break; + } + // In this case, the specifier is wrong and should be changed to match // the argument. EmitFormatDiagnostic(S.PDiag(Diag) @@ -8303,7 +8313,7 @@ Name = TypedefTy->getDecl()->getName(); else Name = CastTyName; - unsigned Diag = Pedantic + unsigned Diag = Match == ArgType::NoMatchPedantic ? diag::warn_format_argument_needs_cast_pedantic : diag::warn_format_argument_needs_cast; EmitFormatDiagnostic(S.PDiag(Diag) << Name << IntendedTy << IsEnum @@ -8330,10 +8340,19 @@ switch (S.isValidVarArgType(ExprTy)) { case Sema::VAK_Valid: case Sema::VAK_ValidInCXX11: { - unsigned Diag = - Pedantic - ? diag::warn_format_conversion_argument_type_mismatch_pedantic - : diag::warn_format_conversion_argument_type_mismatch; + unsigned Diag; + switch (Match) { + case ArgType::Match: llvm_unreachable("expected non-matching"); + case ArgType::NoMatchPedantic: + Diag = diag::warn_format_conversion_argument_type_mismatch_pedantic; + break; + case ArgType::NoMatchTypeConfusion: + Diag = diag::warn_format_conversion_argument_type_mismatch_confusion; + break; + case ArgType::NoMatch: + Diag = diag::warn_format_conversion_argument_type_mismatch; + break; + } EmitFormatDiagnostic( S.PDiag(Diag) << AT.getRepresentativeTypeName(S.Context) << ExprTy Index: clang/test/Sema/format-bool.c =================================================================== --- clang/test/Sema/format-bool.c +++ clang/test/Sema/format-bool.c @@ -1,8 +1,8 @@ // RUN: %clang_cc1 -xc %s -verify -DBOOL=_Bool // RUN: %clang_cc1 -xc++ %s -verify -DBOOL=bool // RUN: %clang_cc1 -xobjective-c %s -verify -DBOOL=_Bool -// RUN: %clang_cc1 -xc %s -verify -DBOOL=_Bool -Wformat-pedantic -DPEDANTIC -// RUN: %clang_cc1 -xc++ %s -verify -DBOOL=bool -Wformat-pedantic -DPEDANTIC +// RUN: %clang_cc1 -xc %s -verify -DBOOL=_Bool -Wformat-type-confusion -DTYPE_CONF +// RUN: %clang_cc1 -xc++ %s -verify -DBOOL=bool -Wformat-type-confusion -DTYPE_CONF __attribute__((format(__printf__, 1, 2))) int p(const char *fmt, ...); @@ -22,13 +22,13 @@ int main() { p("%d", b); p("%hd", b); -#ifdef PEDANTIC +#ifdef TYPE_CONF // expected-warning@-2 {{format specifies type 'short' but the argument has type}} #endif p("%hhd", b); p("%u", b); p("%hu", b); -#ifdef PEDANTIC +#ifdef TYPE_CONF // expected-warning@-2 {{format specifies type 'unsigned short' but the argument has type}} #endif p("%hhu", b); Index: clang/test/Sema/format-strings-pedantic.c =================================================================== --- clang/test/Sema/format-strings-pedantic.c +++ clang/test/Sema/format-strings-pedantic.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -verify -Wformat -Wformat-pedantic -isystem %S/Inputs %s +// RUN: %clang_cc1 -fsyntax-only -verify -Wformat -Wformat-type-confusion %s int printf(const char *restrict, ...); Index: clang/test/Sema/format-type-confusion.c =================================================================== --- /dev/null +++ clang/test/Sema/format-type-confusion.c @@ -0,0 +1,26 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9.0 -fsyntax-only -verify -Wno-format -Wformat-type-confusion %s + +__attribute__((format(__printf__, 1, 2))) +int printf(const char *msg, ...); + +#define FMT "%hd %hu %d %u %hhd %hhu %c" + +int main() { + _Bool b = 0; + printf(FMT, + b, // expected-warning {{format specifies type 'short' but the argument has type '_Bool'}} + b, // expected-warning {{format specifies type 'unsigned short' but the argument has type '_Bool'}} + b, b, b, b, b); + + unsigned char uc = 0; + printf(FMT, + uc, // expected-warning {{format specifies type 'short' but the argument has type 'unsigned char'}} + uc, // expected-warning {{format specifies type 'unsigned short' but the argument has type 'unsigned char'}} + uc, uc, uc, uc, uc); + + signed char sc = 0; + printf(FMT, + sc, // expected-warning {{format specifies type 'short' but the argument has type 'signed char'}} + sc, // expected-warning {{format specifies type 'unsigned short' but the argument has type 'signed char'}} + sc, sc, sc, sc, sc); +}