Index: clang/include/clang/AST/FormatString.h =================================================================== --- clang/include/clang/AST/FormatString.h +++ clang/include/clang/AST/FormatString.h @@ -748,6 +748,12 @@ const char *beg, const char *end, const LangOptions &LO, const TargetInfo &Target); +/// Return true if the given string has at least one formatting specifier. +bool parseFormatStringHasFormattingSpecifiers(const char *Begin, + const char *End, + const LangOptions &LO, + const TargetInfo &Target); + } // end analyze_format_string namespace } // end clang namespace #endif Index: clang/include/clang/AST/NSAPI.h =================================================================== --- clang/include/clang/AST/NSAPI.h +++ clang/include/clang/AST/NSAPI.h @@ -160,6 +160,11 @@ return getOrInitSelector(Ids, setObjectAtIndexedSubscriptSel); } + Selector getLocalizedStringForKeySelector() const { + StringRef Ids[] = {"localizedStringForKey", "value", "table"}; + return getOrInitSelector(Ids, localizedStringForKeySel); + } + /// Returns selector for "isEqual:". Selector getIsEqualSelector() const { return getOrInitSelector(StringRef("isEqual"), isEqualSel); @@ -259,7 +264,7 @@ mutable Selector objectForKeyedSubscriptSel, objectAtIndexedSubscriptSel, setObjectForKeyedSubscriptSel,setObjectAtIndexedSubscriptSel, - isEqualSel, InitSel, NewSel; + localizedStringForKeySel, isEqualSel, InitSel, NewSel; mutable IdentifierInfo *BOOLId, *NSIntegerId, *NSUIntegerId; mutable IdentifierInfo *NSASCIIStringEncodingId, *NSUTF8StringEncodingId; Index: clang/lib/AST/PrintfFormatString.cpp =================================================================== --- clang/lib/AST/PrintfFormatString.cpp +++ clang/lib/AST/PrintfFormatString.cpp @@ -463,6 +463,23 @@ return false; } +bool clang::analyze_format_string::parseFormatStringHasFormattingSpecifiers( + const char *Begin, const char *End, const LangOptions &LO, + const TargetInfo &Target) { + unsigned ArgIndex = 0; + // Keep looking for a formatting specifier until we have exhausted the string. + FormatStringHandler H; + while (Begin != End) { + const PrintfSpecifierResult &FSR = + ParsePrintfSpecifier(H, Begin, End, ArgIndex, LO, Target, false, false); + if (FSR.shouldStop()) + break; + if (FSR.hasValue()) + return true; + } + return false; +} + //===----------------------------------------------------------------------===// // Methods on PrintfSpecifier. //===----------------------------------------------------------------------===// Index: clang/lib/Sema/SemaChecking.cpp =================================================================== --- clang/lib/Sema/SemaChecking.cpp +++ clang/lib/Sema/SemaChecking.cpp @@ -6575,7 +6575,8 @@ bool inFunctionCall, Sema::VariadicCallType CallType, llvm::SmallBitVector &CheckedVarArgs, - UncoveredArgHandler &UncoveredArg); + UncoveredArgHandler &UncoveredArg, + bool IgnoreStringsWithoutSpecifiers); // Determine if an expression is a string literal or constant string. // If this function returns false on the arguments to a function expecting a @@ -6588,7 +6589,8 @@ Sema::VariadicCallType CallType, bool InFunctionCall, llvm::SmallBitVector &CheckedVarArgs, UncoveredArgHandler &UncoveredArg, - llvm::APSInt Offset) { + llvm::APSInt Offset, + bool IgnoreStringsWithoutSpecifiers = false) { if (S.isConstantEvaluated()) return SLCT_NotALiteral; tryAgain: @@ -6639,17 +6641,17 @@ Left = checkFormatStringExpr(S, C->getTrueExpr(), Args, HasVAListArg, format_idx, firstDataArg, Type, CallType, InFunctionCall, - CheckedVarArgs, UncoveredArg, Offset); + CheckedVarArgs, UncoveredArg, Offset, + IgnoreStringsWithoutSpecifiers); if (Left == SLCT_NotALiteral || !CheckRight) { return Left; } } - StringLiteralCheckType Right = - checkFormatStringExpr(S, C->getFalseExpr(), Args, - HasVAListArg, format_idx, firstDataArg, - Type, CallType, InFunctionCall, CheckedVarArgs, - UncoveredArg, Offset); + StringLiteralCheckType Right = checkFormatStringExpr( + S, C->getFalseExpr(), Args, HasVAListArg, format_idx, firstDataArg, + Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, Offset, + IgnoreStringsWithoutSpecifiers); return (CheckLeft && Left < Right) ? Left : Right; } @@ -6753,7 +6755,8 @@ const Expr *Arg = CE->getArg(FA->getFormatIdx().getASTIndex()); StringLiteralCheckType Result = checkFormatStringExpr( S, Arg, Args, HasVAListArg, format_idx, firstDataArg, Type, - CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, Offset); + CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, Offset, + IgnoreStringsWithoutSpecifiers); if (IsFirst) { CommonResult = Result; IsFirst = false; @@ -6771,7 +6774,8 @@ HasVAListArg, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, - UncoveredArg, Offset); + UncoveredArg, Offset, + IgnoreStringsWithoutSpecifiers); } } } @@ -6780,12 +6784,28 @@ } case Stmt::ObjCMessageExprClass: { const auto *ME = cast(E); - if (const auto *ND = ME->getMethodDecl()) { - if (const auto *FA = ND->getAttr()) { + if (const auto *MD = ME->getMethodDecl()) { + if (const auto *FA = MD->getAttr()) { + // As a special case heuristic, if we're using the method -[NSBundle + // localizedStringForKey:value:table:], ignore any key strings that lack + // format specifiers. The idea is that if the key doesn't have any + // format specifiers then its probably just a key to map to the + // localized strings. If it does have format specifiers though, then its + // likely that the text of the key is the format string in the + // programmer's language, and should be checked. + const ObjCInterfaceDecl *IFace; + if (MD->isInstanceMethod() && (IFace = MD->getClassInterface()) && + IFace->getIdentifier() == &S.Context.Idents.get("NSBundle") && + MD->getSelector() == + S.NSAPIObj->getLocalizedStringForKeySelector()) { + IgnoreStringsWithoutSpecifiers = true; + } + const Expr *Arg = ME->getArg(FA->getFormatIdx().getASTIndex()); return checkFormatStringExpr( S, Arg, Args, HasVAListArg, format_idx, firstDataArg, Type, - CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, Offset); + CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, Offset, + IgnoreStringsWithoutSpecifiers); } } @@ -6809,7 +6829,8 @@ FormatStringLiteral FStr(StrE, Offset.sextOrTrunc(64).getSExtValue()); CheckFormatString(S, &FStr, E, Args, HasVAListArg, format_idx, firstDataArg, Type, InFunctionCall, CallType, - CheckedVarArgs, UncoveredArg); + CheckedVarArgs, UncoveredArg, + IgnoreStringsWithoutSpecifiers); return SLCT_CheckedLiteral; } @@ -8500,7 +8521,8 @@ bool inFunctionCall, Sema::VariadicCallType CallType, llvm::SmallBitVector &CheckedVarArgs, - UncoveredArgHandler &UncoveredArg) { + UncoveredArgHandler &UncoveredArg, + bool IgnoreStringsWithoutSpecifiers) { // CHECK: is the format string a wide literal? if (!FExpr->isAscii() && !FExpr->isUTF8()) { CheckFormatHandler::EmitFormatDiagnostic( @@ -8521,6 +8543,11 @@ size_t StrLen = std::min(std::max(TypeSize, size_t(1)) - 1, StrRef.size()); const unsigned numDataArgs = Args.size() - firstDataArg; + if (IgnoreStringsWithoutSpecifiers && + !analyze_format_string::parseFormatStringHasFormattingSpecifiers( + Str, Str + StrLen, S.getLangOpts(), S.Context.getTargetInfo())) + return; + // Emit a warning if the string literal is truncated and does not contain an // embedded null character. if (TypeSize <= StrRef.size() && Index: clang/test/SemaObjC/format-strings-objc.m =================================================================== --- clang/test/SemaObjC/format-strings-objc.m +++ clang/test/SemaObjC/format-strings-objc.m @@ -25,7 +25,11 @@ @protocol NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder; @end @interface NSObject {} @end typedef float CGFloat; -@interface NSString : NSObject - (NSUInteger)length; @end +@interface NSString : NSObject +- (NSUInteger)length; ++(instancetype)stringWithFormat:(NSString *)fmt, ... + __attribute__((format(__NSString__, 1, 2))); +@end @interface NSSimpleCString : NSString {} @end @interface NSConstantString : NSSimpleCString @end extern void *_NSConstantStringClassReference; @@ -302,3 +306,39 @@ } @end + +@interface NSBundle : NSObject +- (NSString *)localizedStringForKey:(NSString *)key + value:(nullable NSString *)value + table:(nullable NSString *)tableName + __attribute__((format_arg(1))); + +- (NSString *)someRandomMethod:(NSString *)key + value:(nullable NSString *)value + table:(nullable NSString *)tableName + __attribute__((format_arg(1))); +@end + +void useLocalizedStringForKey(NSBundle *bndl) { + [NSString stringWithFormat: + [bndl localizedStringForKey:@"%d" // expected-warning{{more '%' conversions than data arguments}} + value:0 + table:0]]; + // No warning, @"flerp" doesn't have a format specifier. + [NSString stringWithFormat: [bndl localizedStringForKey:@"flerp" value:0 table:0], 43, @"flarp"]; + + [NSString stringWithFormat: + [bndl localizedStringForKey:@"%f" + value:0 + table:0], 42]; // expected-warning{{format specifies type 'double' but the argument has type 'int'}} + + [NSString stringWithFormat: + [bndl someRandomMethod:@"%f" + value:0 + table:0], 42]; // expected-warning{{format specifies type 'double' but the argument has type 'int'}} + + [NSString stringWithFormat: + [bndl someRandomMethod:@"flerp" + value:0 + table:0], 42]; // expected-warning{{data argument not used by format string}} +}