Index: include/clang/Analysis/Analyses/FormatString.h =================================================================== --- include/clang/Analysis/Analyses/FormatString.h +++ include/clang/Analysis/Analyses/FormatString.h @@ -256,18 +256,19 @@ private: const Kind K; QualType T; - const char *Name; - bool Ptr; + const char *Name = nullptr; + bool Ptr = false, IsSizeT = false; + public: - ArgType(Kind k = UnknownTy, const char *n = nullptr) - : K(k), Name(n), Ptr(false) {} - ArgType(QualType t, const char *n = nullptr) - : K(SpecificTy), T(t), Name(n), Ptr(false) {} - ArgType(CanQualType t) : K(SpecificTy), T(t), Name(nullptr), Ptr(false) {} + ArgType(Kind k = UnknownTy, const char *n = nullptr) : K(k), Name(n) {} + ArgType(QualType t, const char *n = nullptr) : K(SpecificTy), T(t), Name(n) {} + ArgType(CanQualType t) : K(SpecificTy), T(t) {} static ArgType Invalid() { return ArgType(InvalidTy); } bool isValid() const { return K != InvalidTy; } + bool isSizeT() const { return IsSizeT; } + /// Create an ArgType which corresponds to the type pointer to A. static ArgType PtrTo(const ArgType& A) { assert(A.K >= InvalidTy && "ArgType cannot be pointer to invalid/unknown"); @@ -276,6 +277,13 @@ return Res; } + /// Create an ArgType which corresponds to the size_t/ssize_t type. + static ArgType makeSizeT(const ArgType &A) { + ArgType Res = A; + Res.IsSizeT = true; + return Res; + } + MatchKind matchesType(ASTContext &C, QualType argTy) const; QualType getRepresentativeType(ASTContext &C) const; Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -7831,6 +7831,10 @@ "%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">, + InGroup, DefaultIgnore; def warn_printf_positional_arg_exceeds_data_args : Warning < "data argument position '%0' exceeds the number of data arguments (%1)">, InGroup; Index: lib/Analysis/PrintfFormatString.cpp =================================================================== --- lib/Analysis/PrintfFormatString.cpp +++ lib/Analysis/PrintfFormatString.cpp @@ -466,7 +466,7 @@ case LengthModifier::AsIntMax: return ArgType(Ctx.getIntMaxType(), "intmax_t"); case LengthModifier::AsSizeT: - return ArgType(Ctx.getSignedSizeType(), "ssize_t"); + return ArgType::makeSizeT(ArgType(Ctx.getSignedSizeType(), "ssize_t")); case LengthModifier::AsInt3264: return Ctx.getTargetInfo().getTriple().isArch64Bit() ? ArgType(Ctx.LongLongTy, "__int64") @@ -499,7 +499,7 @@ case LengthModifier::AsIntMax: return ArgType(Ctx.getUIntMaxType(), "uintmax_t"); case LengthModifier::AsSizeT: - return ArgType(Ctx.getSizeType(), "size_t"); + return ArgType::makeSizeT(ArgType(Ctx.getSizeType(), "size_t")); case LengthModifier::AsInt3264: return Ctx.getTargetInfo().getTriple().isArch64Bit() ? ArgType(Ctx.UnsignedLongLongTy, "unsigned __int64") Index: lib/Sema/SemaChecking.cpp =================================================================== --- lib/Sema/SemaChecking.cpp +++ lib/Sema/SemaChecking.cpp @@ -6594,11 +6594,11 @@ ExprTy = TET->getUnderlyingExpr()->getType(); } - analyze_printf::ArgType::MatchKind match = AT.matchesType(S.Context, ExprTy); - - if (match == analyze_printf::ArgType::Match) { + const analyze_printf::ArgType::MatchKind match = + AT.matchesType(S.Context, ExprTy); + bool pedantic = match == analyze_printf::ArgType::NoMatchPedantic; + if (match == analyze_printf::ArgType::Match) return true; - } // Look through argument promotions for our error message's reported type. // This includes the integral and floating promotions, but excludes array @@ -6674,6 +6674,11 @@ QualType CastTy; std::tie(CastTy, CastTyName) = shouldNotPrintDirectly(S.Context, IntendedTy, E); if (!CastTy.isNull()) { + // %zi/%zu are OK to use for NSInteger/NSUInteger of type int + // (long in ASTContext). Only complain to pedants. + if ((CastTyName == "NSInteger" || CastTyName == "NSUInteger") && + AT.isSizeT() && AT.matchesType(S.Context, CastTy)) + pedantic = true; IntendedTy = CastTy; ShouldNotPrintDirectly = true; } @@ -6693,10 +6698,10 @@ CharSourceRange SpecRange = getSpecifierRange(StartSpecifier, SpecifierLen); if (IntendedTy == ExprTy && !ShouldNotPrintDirectly) { - unsigned diag = diag::warn_format_conversion_argument_type_mismatch; - if (match == analyze_format_string::ArgType::NoMatchPedantic) { - diag = diag::warn_format_conversion_argument_type_mismatch_pedantic; - } + unsigned diag = + pedantic + ? diag::warn_format_conversion_argument_type_mismatch_pedantic + : diag::warn_format_conversion_argument_type_mismatch; // In this case, the specifier is wrong and should be changed to match // the argument. EmitFormatDiagnostic(S.PDiag(diag) @@ -6752,9 +6757,11 @@ Name = TypedefTy->getDecl()->getName(); else Name = CastTyName; - EmitFormatDiagnostic(S.PDiag(diag::warn_format_argument_needs_cast) - << Name << IntendedTy << IsEnum - << E->getSourceRange(), + unsigned diag = pedantic + ? diag::warn_format_argument_needs_cast_pedantic + : diag::warn_format_argument_needs_cast; + EmitFormatDiagnostic(S.PDiag(diag) << Name << IntendedTy << IsEnum + << E->getSourceRange(), E->getLocStart(), /*IsStringLocation=*/false, SpecRange, Hints); } else { @@ -6778,10 +6785,10 @@ switch (S.isValidVarArgType(ExprTy)) { case Sema::VAK_Valid: case Sema::VAK_ValidInCXX11: { - unsigned diag = diag::warn_format_conversion_argument_type_mismatch; - if (match == analyze_printf::ArgType::NoMatchPedantic) { - diag = diag::warn_format_conversion_argument_type_mismatch_pedantic; - } + unsigned diag = + pedantic + ? diag::warn_format_conversion_argument_type_mismatch_pedantic + : diag::warn_format_conversion_argument_type_mismatch; EmitFormatDiagnostic( S.PDiag(diag) << AT.getRepresentativeTypeName(S.Context) << ExprTy @@ -6969,18 +6976,17 @@ analyze_format_string::ArgType::MatchKind match = AT.matchesType(S.Context, Ex->getType()); - if (match == analyze_format_string::ArgType::Match) { + bool pedantic = match == analyze_format_string::ArgType::NoMatchPedantic; + if (match == analyze_format_string::ArgType::Match) return true; - } ScanfSpecifier fixedFS = FS; bool success = fixedFS.fixType(Ex->getType(), Ex->IgnoreImpCasts()->getType(), S.getLangOpts(), S.Context); - unsigned diag = diag::warn_format_conversion_argument_type_mismatch; - if (match == analyze_format_string::ArgType::NoMatchPedantic) { - diag = diag::warn_format_conversion_argument_type_mismatch_pedantic; - } + unsigned diag = + pedantic ? diag::warn_format_conversion_argument_type_mismatch_pedantic + : diag::warn_format_conversion_argument_type_mismatch; if (success) { // Get the fix string from the fixed format specifier. Index: test/FixIt/fixit-format-ios-nopedantic.m =================================================================== --- /dev/null +++ test/FixIt/fixit-format-ios-nopedantic.m @@ -0,0 +1,21 @@ +// RUN: cp %s %t +// RUN: %clang_cc1 -triple thumbv7-apple-ios8.0.0 -fsyntax-only -Wformat -Werror -fixit %t + +int printf(const char *restrict, ...); +typedef unsigned int NSUInteger; +typedef int NSInteger; +NSUInteger getNSUInteger(); +NSInteger getNSInteger(); + +void test() { + // For thumbv7-apple-ios8.0.0 the underlying type of ssize_t is long + // and the underlying type of size_t is unsigned long. + + printf("test 1: %zu", getNSUInteger()); + + printf("test 2: %zu %zu", getNSUInteger(), getNSUInteger()); + + printf("test 3: %zd", getNSInteger()); + + printf("test 4: %zd %zd", getNSInteger(), getNSInteger()); +} Index: test/FixIt/fixit-format-ios.m =================================================================== --- test/FixIt/fixit-format-ios.m +++ test/FixIt/fixit-format-ios.m @@ -1,5 +1,5 @@ // RUN: cp %s %t -// RUN: %clang_cc1 -triple thumbv7-apple-ios8.0.0 -fsyntax-only -Wformat -fixit %t +// RUN: %clang_cc1 -triple thumbv7-apple-ios8.0.0 -fsyntax-only -Wformat-pedantic -fixit %t // RUN: grep -v CHECK %t | FileCheck %s int printf(const char * restrict, ...); Index: test/SemaObjC/format-size-spec-nsinteger.m =================================================================== --- /dev/null +++ test/SemaObjC/format-size-spec-nsinteger.m @@ -0,0 +1,30 @@ +// RUN: %clang_cc1 -triple thumbv7-apple-ios -Wno-objc-root-class -fsyntax-only -verify -Wformat %s +// RUN: %clang_cc1 -triple thumbv7-apple-ios -Wno-objc-root-class -fsyntax-only -verify -Wformat-pedantic -DPEDANTIC %s + +#if !defined(PEDANTIC) +// expected-no-diagnostics +#endif + +#if __LP64__ +typedef unsigned long NSUInteger; +typedef long NSInteger; +#else +typedef unsigned int NSUInteger; +typedef int NSInteger; +#endif + +@class NSString; + +extern void NSLog(NSString *format, ...); + +void testSizeSpecifier() { + NSInteger i = 0; + NSUInteger j = 0; + NSLog(@"max NSInteger = %zi", i); + NSLog(@"max NSUinteger = %zu", j); + +#if defined(PEDANTIC) + // expected-warning@-4 {{values of type 'NSInteger' should not be used as format arguments; add an explicit cast to 'long' instead}} + // expected-warning@-4 {{values of type 'NSUInteger' should not be used as format arguments; add an explicit cast to 'unsigned long' instead}} +#endif +}