[analyzer] Warn about nil elements/keys/values in array and dictionary literals.
authorAnna Zaks <ganna@apple.com>
Mon, 13 May 2013 21:48:20 +0000 (21:48 +0000)
committerAnna Zaks <ganna@apple.com>
Mon, 13 May 2013 21:48:20 +0000 (21:48 +0000)
llvm-svn: 181738

clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp
clang/test/Analysis/NSContainers.m

index 6388a8d..c723e4f 100644 (file)
@@ -90,20 +90,53 @@ static FoundationClass findKnownClass(const ObjCInterfaceDecl *ID) {
 //===----------------------------------------------------------------------===//
 
 namespace {
-  class NilArgChecker : public Checker<check::PreObjCMessage> {
+  class NilArgChecker : public Checker<check::PreObjCMessage,
+                                       check::PostStmt<ObjCDictionaryLiteral>,
+                                       check::PostStmt<ObjCArrayLiteral> > {
     mutable OwningPtr<APIMisuse> BT;
 
-    void WarnIfNilArg(CheckerContext &C,
-                    const ObjCMethodCall &msg, unsigned Arg,
-                    FoundationClass Class,
-                    bool CanBeSubscript = false) const;
+    void warnIfNilExpr(const Expr *E,
+                       const char *Msg,
+                       CheckerContext &C) const;
+
+    void warnIfNilArg(CheckerContext &C,
+                      const ObjCMethodCall &msg, unsigned Arg,
+                      FoundationClass Class,
+                      bool CanBeSubscript = false) const;
+
+    void generateBugReport(ExplodedNode *N,
+                           llvm::raw_svector_ostream &os,
+                           SourceRange Range,
+                           const Expr *Expr,
+                           CheckerContext &C) const;
 
   public:
     void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
+    void checkPostStmt(const ObjCDictionaryLiteral *DL,
+                       CheckerContext &C) const;
+    void checkPostStmt(const ObjCArrayLiteral *AL,
+                       CheckerContext &C) const;
   };
 }
 
-void NilArgChecker::WarnIfNilArg(CheckerContext &C,
+void NilArgChecker::warnIfNilExpr(const Expr *E,
+                                  const char *Msg,
+                                  CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  SVal SV = State->getSVal(E, C.getLocationContext());
+  if (State->isNull(SV).isConstrainedTrue()) {
+
+    if (ExplodedNode *N = C.generateSink()) {
+      SmallString<128> sbuf;
+      llvm::raw_svector_ostream os(sbuf);
+      os << Msg;
+      generateBugReport(N, os, E->getSourceRange(), E, C);
+    }
+    
+  }
+}
+
+void NilArgChecker::warnIfNilArg(CheckerContext &C,
                                  const ObjCMethodCall &msg,
                                  unsigned int Arg,
                                  FoundationClass Class,
@@ -113,9 +146,6 @@ void NilArgChecker::WarnIfNilArg(CheckerContext &C,
   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);
@@ -149,14 +179,26 @@ void NilArgChecker::WarnIfNilArg(CheckerContext &C,
         << msg.getSelector().getAsString() << "' cannot be nil";
       }
     }
-
-    BugReport *R = new BugReport(*BT, os.str(), N);
-    R->addRange(msg.getArgSourceRange(Arg));
-    bugreporter::trackNullOrUndefValue(N, msg.getArgExpr(Arg), *R);
-    C.emitReport(R);
+    
+    generateBugReport(N, os, msg.getArgSourceRange(Arg),
+                      msg.getArgExpr(Arg), C);
   }
 }
 
+void NilArgChecker::generateBugReport(ExplodedNode *N,
+                                      llvm::raw_svector_ostream &os,
+                                      SourceRange Range,
+                                      const Expr *Expr,
+                                      CheckerContext &C) const {
+  if (!BT)
+    BT.reset(new APIMisuse("nil argument"));
+
+  BugReport *R = new BugReport(*BT, os.str(), N);
+  R->addRange(Range);
+  bugreporter::trackNullOrUndefValue(N, Expr, *R);
+  C.emitReport(R);
+}
+
 void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
                                         CheckerContext &C) const {
   const ObjCInterfaceDecl *ID = msg.getReceiverInterface();
@@ -225,28 +267,43 @@ void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
     if (S.getNameForSlot(0).equals("dictionaryWithObject") &&
         S.getNameForSlot(1).equals("forKey")) {
       Arg = 0;
-      WarnIfNilArg(C, msg, /* Arg */1, Class);
+      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);
+      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);
+      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))
-    WarnIfNilArg(C, msg, Arg, Class, CanBeSubscript);
+    warnIfNilArg(C, msg, Arg, Class, CanBeSubscript);
 
 }
 
+void NilArgChecker::checkPostStmt(const ObjCArrayLiteral *AL,
+                                  CheckerContext &C) const {
+  for (unsigned i = 0; i < AL->getNumElements(); ++i) {
+    warnIfNilExpr(AL->getElement(i), "Array element cannot be nil", C);
+  }
+}
+
+void NilArgChecker::checkPostStmt(const ObjCDictionaryLiteral *DL,
+                                  CheckerContext &C) const {
+  for (unsigned i = 0; i < DL->getNumElements(); ++i) {
+    ObjCDictionaryElement Element = DL->getKeyValueElement(i);
+    warnIfNilExpr(Element.Key, "Dictionary key cannot be nil", C);
+    warnIfNilExpr(Element.Value, "Dictionary value cannot be nil", C);
+  }
+}
+
 //===----------------------------------------------------------------------===//
 // Error reporting.
 //===----------------------------------------------------------------------===//
index 6b4089b..959f367 100644 (file)
@@ -36,6 +36,10 @@ typedef struct _NSZone NSZone;
 - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx __attribute__((availability(macosx,introduced=10.8)));
 @end
 
+@interface NSArray (NSArrayCreation)
++ (instancetype)arrayWithObjects:(const id [])objects count:(NSUInteger)cnt;
+@end
+
 @interface NSMutableArray : NSArray
 
 - (void)addObject:(id)anObject;
@@ -58,6 +62,8 @@ typedef struct _NSZone NSZone;
 
 + (id)dictionary;
 + (id)dictionaryWithObject:(id)object forKey:(id <NSCopying>)key;
++ (instancetype)dictionaryWithObjects:(const id [])objects forKeys:(const id <NSCopying> [])keys count:(NSUInteger)cnt;
+
 @end
 
 @interface NSMutableDictionary : NSDictionary
@@ -147,6 +153,33 @@ NSDictionary *testNilArgNSDictionary2(NSObject *obj) {
   return [NSDictionary dictionaryWithObject:obj forKey:0]; // expected-warning {{Key argument to 'dictionaryWithObject:forKey:' cannot be nil}}
 }
 
+id testCreateDictionaryLiteralKey(id value, id nilKey) {
+  if (nilKey)
+    ;
+  return @{@"abc":value, nilKey:@"abc"}; // expected-warning {{Dictionary key cannot be nil}}
+}
+
+id testCreateDictionaryLiteralValue(id nilValue) {
+  if (nilValue)
+    ;
+  return @{@"abc":nilValue}; // expected-warning {{Dictionary value cannot be nil}}
+}
+
+id testCreateDictionaryLiteral(id nilValue, id nilKey) {
+  if (nilValue)
+    ;
+  if (nilKey)
+    ;
+  return @{@"abc":nilValue, nilKey:@"abc"}; // expected-warning {{Dictionary key cannot be nil}}
+                                            // expected-warning@-1 {{Dictionary value cannot be nil}}
+}
+
+id testCreateArrayLiteral(id myNil) {
+  if (myNil)
+    ;
+  return @[ @"a", myNil, @"c" ]; // expected-warning {{Array element cannot be nil}}
+}
+
 // Test inline defensive checks suppression.
 void idc(id x) {
   if (x)