r177789 - [analyzer] Warn when a nil key or value are passed to NSMutableDictionary and ensure it works with subscripting.

Anna Zaks ganna at apple.com
Fri Mar 22 17:39:21 PDT 2013


Author: zaks
Date: Fri Mar 22 19:39:21 2013
New Revision: 177789

URL: http://llvm.org/viewvc/llvm-project?rev=177789&view=rev
Log:
[analyzer] Warn when a nil key or value are passed to NSMutableDictionary and ensure it works with subscripting.

Modified:
    cfe/trunk/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp
    cfe/trunk/test/Analysis/NSContainers.m

Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp?rev=177789&r1=177788&r2=177789&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp Fri Mar 22 19:39:21 2013
@@ -83,10 +83,6 @@ static FoundationClass findKnownClass(co
   return result;
 }
 
-static inline bool isNil(SVal X) {
-  return X.getAs<loc::ConcreteInt>().hasValue();
-}
-
 //===----------------------------------------------------------------------===//
 // NilArgChecker - Check for prohibited nil arguments to ObjC method calls.
 //===----------------------------------------------------------------------===//
@@ -95,26 +91,51 @@ namespace {
   class NilArgChecker : public Checker<check::PreObjCMessage> {
     mutable OwningPtr<APIMisuse> 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(
 
   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(
     } 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);
 
 }
 

Modified: cfe/trunk/test/Analysis/NSContainers.m
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/NSContainers.m?rev=177789&r1=177788&r2=177789&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/NSContainers.m (original)
+++ cfe/trunk/test/Analysis/NSContainers.m Fri Mar 22 19:39:21 2013
@@ -24,7 +24,6 @@ typedef struct _NSZone NSZone;
 - (id)init;
 + (id)alloc;
 @end
-
 @interface NSArray : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>
 
 - (NSUInteger)count;
@@ -47,30 +46,99 @@ typedef struct _NSZone NSZone;
 
 @end
 
+ at interface NSDictionary : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>
+
+- (NSUInteger)count;
+- (id)objectForKey:(id)aKey;
+- (NSEnumerator *)keyEnumerator;
+
+ at end
+
+ at interface NSDictionary (NSDictionaryCreation)
+
++ (id)dictionary;
++ (id)dictionaryWithObject:(id)object forKey:(id <NSCopying>)key;
+ at end
+
+ at interface NSMutableDictionary : NSDictionary
+
+- (void)removeObjectForKey:(id)aKey;
+- (void)setObject:(id)anObject forKey:(id <NSCopying>)aKey;
+
+ at end
+
+ at interface NSMutableDictionary (NSExtendedMutableDictionary)
+
+- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary;
+- (void)removeAllObjects;
+- (void)removeObjectsForKeys:(NSArray *)keyArray;
+- (void)setDictionary:(NSDictionary *)otherDictionary;
+- (void)setObject:(id)obj forKeyedSubscript:(id <NSCopying>)key __attribute__((availability(macosx,introduced=10.8)));
+
+ at end
+
+ at interface NSString : NSObject <NSCopying, NSMutableCopying, NSSecureCoding>
+
+ at 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 at -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}}
+}





More information about the cfe-commits mailing list