diff --git a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp index a6bcb6930a..fb43964a89 100644 --- a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp +++ b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp @@ -83,10 +83,6 @@ static FoundationClass findKnownClass(const ObjCInterfaceDecl *ID) { return result; } -static inline bool isNil(SVal X) { - return X.getAs().hasValue(); -} - //===----------------------------------------------------------------------===// // NilArgChecker - Check for prohibited nil arguments to ObjC method calls. //===----------------------------------------------------------------------===// @@ -95,26 +91,51 @@ namespace { class NilArgChecker : public Checker { mutable OwningPtr BT; - void WarnNilArg(CheckerContext &C, - const ObjCMethodCall &msg, unsigned Arg) const; + void WarnIfNilArg(CheckerContext &C, + const ObjCMethodCall &msg, unsigned Arg, + FoundationClass Class, + bool CanBeSubscript = false) const; public: void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; }; } -void NilArgChecker::WarnNilArg(CheckerContext &C, - const ObjCMethodCall &msg, - unsigned int Arg) const -{ +void NilArgChecker::WarnIfNilArg(CheckerContext &C, + const ObjCMethodCall &msg, + unsigned int Arg, + FoundationClass Class, + bool CanBeSubscript) const { + // Check if the argument is nil. + ProgramStateRef State = C.getState(); + if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue()) + return; + if (!BT) BT.reset(new APIMisuse("nil argument")); - + if (ExplodedNode *N = C.generateSink()) { SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); - os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '" - << msg.getSelector().getAsString() << "' cannot be nil"; + + if (CanBeSubscript && msg.getMessageKind() == OCM_Subscript) { + + if (Class == FC_NSArray) { + os << "Array element cannot be nil"; + } else if (Class == FC_NSDictionary) { + if (Arg == 0) + os << "Dictionary object cannot be nil"; + else { + assert(Arg == 1); + os << "Dictionary key cannot be nil"; + } + } else + llvm_unreachable("Missing foundation class for the subscript expr"); + + } else { + os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '" + << msg.getSelector().getAsString() << "' cannot be nil"; + } BugReport *R = new BugReport(*BT, os.str(), N); R->addRange(msg.getArgSourceRange(Arg)); @@ -132,7 +153,8 @@ void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg, static const unsigned InvalidArgIndex = UINT_MAX; unsigned Arg = InvalidArgIndex; - + bool CanBeSubscript = false; + if (Class == FC_NSString) { Selector S = msg.getSelector(); @@ -176,14 +198,38 @@ void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg, } else if (S.getNameForSlot(0).equals("setObject") && S.getNameForSlot(1).equals("atIndexedSubscript")) { Arg = 0; + CanBeSubscript = true; } else if (S.getNameForSlot(0).equals("arrayByAddingObject")) { Arg = 0; } + } else if (Class == FC_NSDictionary) { + Selector S = msg.getSelector(); + + if (S.isUnarySelector()) + return; + + if (S.getNameForSlot(0).equals("dictionaryWithObject") && + S.getNameForSlot(1).equals("forKey")) { + Arg = 0; + WarnIfNilArg(C, msg, /* Arg */1, Class); + } else if (S.getNameForSlot(0).equals("setObject") && + S.getNameForSlot(1).equals("forKey")) { + Arg = 0; + WarnIfNilArg(C, msg, /* Arg */1, Class); + } else if (S.getNameForSlot(0).equals("setObject") && + S.getNameForSlot(1).equals("forKeyedSubscript")) { + CanBeSubscript = true; + Arg = 0; + WarnIfNilArg(C, msg, /* Arg */1, Class, CanBeSubscript); + } else if (S.getNameForSlot(0).equals("removeObjectForKey")) { + Arg = 0; + } } + // If argument is '0', report a warning. - if ((Arg != InvalidArgIndex) && isNil(msg.getArgSVal(Arg))) - WarnNilArg(C, msg, Arg); + if ((Arg != InvalidArgIndex)) + WarnIfNilArg(C, msg, Arg, Class, CanBeSubscript); } diff --git a/test/Analysis/NSContainers.m b/test/Analysis/NSContainers.m index 7542a0226c..44075ad3af 100644 --- a/test/Analysis/NSContainers.m +++ b/test/Analysis/NSContainers.m @@ -24,7 +24,6 @@ typedef struct _NSZone NSZone; - (id)init; + (id)alloc; @end - @interface NSArray : NSObject - (NSUInteger)count; @@ -47,30 +46,99 @@ typedef struct _NSZone NSZone; @end +@interface NSDictionary : NSObject + +- (NSUInteger)count; +- (id)objectForKey:(id)aKey; +- (NSEnumerator *)keyEnumerator; + +@end + +@interface NSDictionary (NSDictionaryCreation) + ++ (id)dictionary; ++ (id)dictionaryWithObject:(id)object forKey:(id )key; +@end + +@interface NSMutableDictionary : NSDictionary + +- (void)removeObjectForKey:(id)aKey; +- (void)setObject:(id)anObject forKey:(id )aKey; + +@end + +@interface NSMutableDictionary (NSExtendedMutableDictionary) + +- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary; +- (void)removeAllObjects; +- (void)removeObjectsForKeys:(NSArray *)keyArray; +- (void)setDictionary:(NSDictionary *)otherDictionary; +- (void)setObject:(id)obj forKeyedSubscript:(id )key __attribute__((availability(macosx,introduced=10.8))); + +@end + +@interface NSString : NSObject + +@end + // NSMutableArray API -void testNilArg1() { +void testNilArgNSMutableArray1() { NSMutableArray *marray = [[NSMutableArray alloc] init]; [marray addObject:0]; // expected-warning {{Argument to 'NSMutableArray' method 'addObject:' cannot be nil}} } -void testNilArg2() { +void testNilArgNSMutableArray2() { NSMutableArray *marray = [[NSMutableArray alloc] init]; [marray insertObject:0 atIndex:1]; // expected-warning {{Argument to 'NSMutableArray' method 'insertObject:atIndex:' cannot be nil}} } -void testNilArg3() { +void testNilArgNSMutableArray3() { NSMutableArray *marray = [[NSMutableArray alloc] init]; [marray replaceObjectAtIndex:1 withObject:0]; // expected-warning {{Argument to 'NSMutableArray' method 'replaceObjectAtIndex:withObject:' cannot be nil}} } -void testNilArg4() { +void testNilArgNSMutableArray4() { NSMutableArray *marray = [[NSMutableArray alloc] init]; [marray setObject:0 atIndexedSubscript:1]; // expected-warning {{Argument to 'NSMutableArray' method 'setObject:atIndexedSubscript:' cannot be nil}} } +void testNilArgNSMutableArray5() { + NSMutableArray *marray = [[NSMutableArray alloc] init]; + marray[1] = 0; // expected-warning {{Array element cannot be nil}} +} + // NSArray API -void testNilArg5() { +void testNilArgNSArray1() { NSArray *array = [[NSArray alloc] init]; NSArray *copyArray = [array arrayByAddingObject:0]; // expected-warning {{Argument to 'NSArray' method 'arrayByAddingObject:' cannot be nil}} } +// NSMutableDictionary and NSDictionary APIs. +void testNilArgNSMutableDictionary1(NSMutableDictionary *d, NSString* key) { + [d setObject:0 forKey:key]; // expected-warning {{Argument to 'NSMutableDictionary' method 'setObject:forKey:' cannot be nil}} +} + +void testNilArgNSMutableDictionary2(NSMutableDictionary *d, NSObject *obj) { + [d setObject:obj forKey:0]; // expected-warning {{Argument to 'NSMutableDictionary' method 'setObject:forKey:' cannot be nil}} +} + +void testNilArgNSMutableDictionary3(NSMutableDictionary *d) { + [d removeObjectForKey:0]; // expected-warning {{Argument to 'NSMutableDictionary' method 'removeObjectForKey:' cannot be nil}} +} + +void testNilArgNSMutableDictionary5(NSMutableDictionary *d, NSString* key) { + d[key] = 0; // expected-warning {{Dictionary object cannot be nil}} +} +void testNilArgNSMutableDictionary6(NSMutableDictionary *d, NSString *key) { + if (key) + ; + d[key] = 0; // expected-warning {{Dictionary key cannot be nil}} + // expected-warning@-1 {{Dictionary object cannot be nil}} +} + +NSDictionary *testNilArgNSDictionary1(NSString* key) { + return [NSDictionary dictionaryWithObject:0 forKey:key]; // expected-warning {{Argument to 'NSDictionary' method 'dictionaryWithObject:forKey:' cannot be nil}} +} +NSDictionary *testNilArgNSDictionary2(NSObject *obj) { + return [NSDictionary dictionaryWithObject:obj forKey:0]; // expected-warning {{Argument to 'NSDictionary' method 'dictionaryWithObject:forKey:' cannot be nil}} +}