From 55afcebf8e68d3ad113442b2843073a8d1e41b3c Mon Sep 17 00:00:00 2001 From: Ted Kremenek Date: Thu, 2 Jul 2015 05:39:16 +0000 Subject: [PATCH] Parse 'technical term' format specifier. Objective-C format strings now support modifier flags that can be attached to a '@' conversion. Currently the only one supported, as of iOS 9 and OS X 10.11, is the new "technical term", denoted by the flag "tt", for example: %[tt]@ instead of just: %@ The 'tt' stands for "technical term", which is used by the string-localization facilities on Darwin to add the appropriate spacing or quotation depending the language locale. Implements . git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@241243 91177308-0d34-0410-b5e6-96231b3b80d8 --- .../clang/Analysis/Analyses/FormatString.h | 17 ++++- include/clang/Basic/DiagnosticSemaKinds.td | 9 +++ lib/Analysis/PrintfFormatString.cpp | 62 +++++++++++++++++++ lib/Sema/SemaChecking.cpp | 47 +++++++++++++- test/SemaObjC/format-strings-objc.m | 13 ++++ 5 files changed, 146 insertions(+), 2 deletions(-) diff --git a/include/clang/Analysis/Analyses/FormatString.h b/include/clang/Analysis/Analyses/FormatString.h index 78299bafdc..4471311a33 100644 --- a/include/clang/Analysis/Analyses/FormatString.h +++ b/include/clang/Analysis/Analyses/FormatString.h @@ -436,12 +436,14 @@ class PrintfSpecifier : public analyze_format_string::FormatSpecifier { OptionalFlag HasSpacePrefix; // ' ' OptionalFlag HasAlternativeForm; // '#' OptionalFlag HasLeadingZeroes; // '0' + OptionalFlag HasObjCTechnicalTerm; // '[tt]' OptionalAmount Precision; public: PrintfSpecifier() : FormatSpecifier(/* isPrintf = */ true), HasThousandsGrouping("'"), IsLeftJustified("-"), HasPlusPrefix("+"), - HasSpacePrefix(" "), HasAlternativeForm("#"), HasLeadingZeroes("0") {} + HasSpacePrefix(" "), HasAlternativeForm("#"), HasLeadingZeroes("0"), + HasObjCTechnicalTerm("tt") {} static PrintfSpecifier Parse(const char *beg, const char *end); @@ -467,6 +469,9 @@ public: void setHasLeadingZeros(const char *position) { HasLeadingZeroes.setPosition(position); } + void setHasObjCTechnicalTerm(const char *position) { + HasObjCTechnicalTerm.setPosition(position); + } void setUsesPositionalArg() { UsesPositionalArg = true; } // Methods for querying the format specifier. @@ -503,6 +508,7 @@ public: const OptionalFlag &hasAlternativeForm() const { return HasAlternativeForm; } const OptionalFlag &hasLeadingZeros() const { return HasLeadingZeroes; } const OptionalFlag &hasSpacePrefix() const { return HasSpacePrefix; } + const OptionalFlag &hasObjCTechnicalTerm() const { return HasObjCTechnicalTerm; } bool usesPositionalArg() const { return UsesPositionalArg; } /// Changes the specifier and length according to a QualType, retaining any @@ -615,6 +621,15 @@ public: virtual void HandleIncompleteSpecifier(const char *startSpecifier, unsigned specifierLen) {} + virtual void HandleEmptyObjCModifierFlag(const char *startFlags, + unsigned flagsLen) {} + + virtual void HandleInvalidObjCModifierFlag(const char *startFlag, + unsigned flagLen) {} + + virtual void HandleObjCFlagsWithNonObjCConversion(const char *flagsStart, + const char *flagsEnd, + const char *conversionPosition) {} // Printf-specific handlers. virtual bool HandleInvalidPrintfConversionSpecifier( diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 134c355179..0d0dcdb561 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -6803,6 +6803,15 @@ def warn_format_non_standard_conversion_spec: Warning< def warn_printf_ignored_flag: Warning< "flag '%0' is ignored when flag '%1' is present">, InGroup; +def warn_printf_empty_objc_flag: Warning< + "missing object format flag">, + InGroup; +def warn_printf_ObjCflags_without_ObjCConversion: Warning< + "object format flags cannot be used with '%0' conversion specifier">, + InGroup; +def warn_printf_invalid_objc_flag: Warning< + "'%0' is not a valid object format flag">, + InGroup; def warn_scanf_scanlist_incomplete : Warning< "no closing ']' for '%%[' in scanf format string">, InGroup; diff --git a/lib/Analysis/PrintfFormatString.cpp b/lib/Analysis/PrintfFormatString.cpp index b8d3ec1801..f0976bce97 100644 --- a/lib/Analysis/PrintfFormatString.cpp +++ b/lib/Analysis/PrintfFormatString.cpp @@ -49,6 +49,24 @@ static bool ParsePrecision(FormatStringHandler &H, PrintfSpecifier &FS, return false; } +static bool ParseObjCFlags(FormatStringHandler &H, PrintfSpecifier &FS, + const char *FlagBeg, const char *E, bool Warn) { + StringRef Flag(FlagBeg, E - FlagBeg); + // Currently there is only one flag. + if (Flag == "tt") { + FS.setHasObjCTechnicalTerm(FlagBeg); + return false; + } + // Handle either the case of no flag or an invalid flag. + if (Warn) { + if (Flag == "") + H.HandleEmptyObjCModifierFlag(FlagBeg, E - FlagBeg); + else + H.HandleInvalidObjCModifierFlag(FlagBeg, E - FlagBeg); + } + return true; +} + static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H, const char *&Beg, const char *E, @@ -168,6 +186,38 @@ static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H, return true; } + // Look for the Objective-C modifier flags, if any. + // We parse these here, even if they don't apply to + // the conversion specifier, and then emit an error + // later if the conversion specifier isn't '@'. This + // enables better recovery, and we don't know if + // these flags are applicable until later. + const char *ObjCModifierFlagsStart = nullptr, + *ObjCModifierFlagsEnd = nullptr; + if (*I == '[') { + ObjCModifierFlagsStart = I; + ++I; + auto flagStart = I; + for (;; ++I) { + ObjCModifierFlagsEnd = I; + if (I == E) { + if (Warn) + H.HandleIncompleteSpecifier(Start, E - Start); + return true; + } + // Did we find the closing ']'? + if (*I == ']') { + if (ParseObjCFlags(H, FS, flagStart, I, Warn)) + return true; + ++I; + break; + } + // There are no separators defined yet for multiple + // Objective-C modifier flags. When those are + // defined, this is the place to check. + } + } + if (*I == '\0') { // Detect spurious null characters, which are likely errors. H.HandleNullChar(I); @@ -240,6 +290,18 @@ static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H, if (Target.getTriple().isOSMSVCRT()) k = ConversionSpecifier::ZArg; } + + // Check to see if we used the Objective-C modifier flags with + // a conversion specifier other than '@'. + if (k != ConversionSpecifier::ObjCObjArg && + k != ConversionSpecifier::InvalidSpecifier && + ObjCModifierFlagsStart) { + H.HandleObjCFlagsWithNonObjCConversion(ObjCModifierFlagsStart, + ObjCModifierFlagsEnd + 1, + conversionPosition); + return true; + } + PrintfConversionSpecifier CS(conversionPosition, k); FS.setConversionSpecifier(CS); if (CS.consumesDataArgument() && !FS.usesPositionalArg()) diff --git a/lib/Sema/SemaChecking.cpp b/lib/Sema/SemaChecking.cpp index 870da782b9..5737e8338f 100644 --- a/lib/Sema/SemaChecking.cpp +++ b/lib/Sema/SemaChecking.cpp @@ -3572,8 +3572,18 @@ public: const char *startSpecifier, unsigned specifierLen); bool checkForCStrMembers(const analyze_printf::ArgType &AT, const Expr *E); + + void HandleEmptyObjCModifierFlag(const char *startFlag, + unsigned flagLen) override; -}; + void HandleInvalidObjCModifierFlag(const char *startFlag, + unsigned flagLen) override; + + void HandleObjCFlagsWithNonObjCConversion(const char *flagsStart, + const char *flagsEnd, + const char *conversionPosition) + override; +}; } bool CheckPrintfHandler::HandleInvalidPrintfConversionSpecifier( @@ -3693,6 +3703,41 @@ void CheckPrintfHandler::HandleIgnoredFlag( getSpecifierRange(ignoredFlag.getPosition(), 1))); } +// void EmitFormatDiagnostic(PartialDiagnostic PDiag, SourceLocation StringLoc, +// bool IsStringLocation, Range StringRange, +// ArrayRef Fixit = None); + +void CheckPrintfHandler::HandleEmptyObjCModifierFlag(const char *startFlag, + unsigned flagLen) { + // Warn about an empty flag. + EmitFormatDiagnostic(S.PDiag(diag::warn_printf_empty_objc_flag), + getLocationOfByte(startFlag), + /*IsStringLocation*/true, + getSpecifierRange(startFlag, flagLen)); +} + +void CheckPrintfHandler::HandleInvalidObjCModifierFlag(const char *startFlag, + unsigned flagLen) { + // Warn about an invalid flag. + auto Range = getSpecifierRange(startFlag, flagLen); + StringRef flag(startFlag, flagLen); + EmitFormatDiagnostic(S.PDiag(diag::warn_printf_invalid_objc_flag) << flag, + getLocationOfByte(startFlag), + /*IsStringLocation*/true, + Range, FixItHint::CreateRemoval(Range)); +} + +void CheckPrintfHandler::HandleObjCFlagsWithNonObjCConversion( + const char *flagsStart, const char *flagsEnd, const char *conversionPosition) { + // Warn about using '[...]' without a '@' conversion. + auto Range = getSpecifierRange(flagsStart, flagsEnd - flagsStart + 1); + auto diag = diag::warn_printf_ObjCflags_without_ObjCConversion; + EmitFormatDiagnostic(S.PDiag(diag) << StringRef(conversionPosition, 1), + getLocationOfByte(conversionPosition), + /*IsStringLocation*/true, + Range, FixItHint::CreateRemoval(Range)); +} + // Determines if the specified is a C++ class or struct containing // a member with the specified name and kind (e.g. a CXXMethodDecl named // "c_str()"). diff --git a/test/SemaObjC/format-strings-objc.m b/test/SemaObjC/format-strings-objc.m index f4c528cc7a..079460cc76 100644 --- a/test/SemaObjC/format-strings-objc.m +++ b/test/SemaObjC/format-strings-objc.m @@ -251,3 +251,16 @@ void testUnicode() { NSLog(@"%C", 0x202200); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'int'}} } +// Test Objective-C modifier flags. +void testObjCModifierFlags() { + NSLog(@"%[]@", @"Foo"); // expected-warning {{missing object format flag}} + NSLog(@"%[", @"Foo"); // expected-warning {{incomplete format specifier}} + NSLog(@"%[tt", @"Foo"); // expected-warning {{incomplete format specifier}} + NSLog(@"%[tt]@", @"Foo"); // no-warning + NSLog(@"%[tt]@ %s", @"Foo", "hello"); // no-warning + NSLog(@"%s %[tt]@", "hello", @"Foo"); // no-warning + NSLog(@"%[blark]@", @"Foo"); // expected-warning {{'blark' is not a valid object format flag}} + NSLog(@"%2$[tt]@ %1$[tt]@", @"Foo", @"Bar"); // no-warning + NSLog(@"%2$[tt]@ %1$[tt]s", @"Foo", @"Bar"); // expected-warning {{object format flags cannot be used with 's' conversion specifier}} +} + -- 2.40.0