]> granicus.if.org Git - clang/commitdiff
[analyzer] Handle the dot syntax for properties in the ExprEngine.
authorArgyrios Kyrtzidis <akyrtzi@gmail.com>
Tue, 25 Jan 2011 00:04:03 +0000 (00:04 +0000)
committerArgyrios Kyrtzidis <akyrtzi@gmail.com>
Tue, 25 Jan 2011 00:04:03 +0000 (00:04 +0000)
We translate property accesses to obj-c messages by simulating "loads" or "stores" to properties
using a pseudo-location SVal kind (ObjCPropRef).

Checkers can now reason about obj-c messages for both explicit message expressions and implicit
messages due to property accesses.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@124161 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/StaticAnalyzer/PathSensitive/ExprEngine.h
include/clang/StaticAnalyzer/PathSensitive/SVals.h
lib/StaticAnalyzer/BasicStore.cpp
lib/StaticAnalyzer/CFRefCount.cpp
lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
lib/StaticAnalyzer/Checkers/ExprEngine.cpp
lib/StaticAnalyzer/RegionStore.cpp
lib/StaticAnalyzer/SVals.cpp
test/Analysis/properties.m [new file with mode: 0644]

index 8a9a9f2cd7a90d5c9ed25d1f3689c8e24c180b02..7b8aab0d68f4c985f12f11c92e6a56d039acc4cd 100644 (file)
@@ -375,6 +375,9 @@ public:
   void VisitObjCAtSynchronizedStmt(const ObjCAtSynchronizedStmt *S,
                                    ExplodedNode *Pred, ExplodedNodeSet &Dst);
 
+  void VisitObjCPropertyRefExpr(const ObjCPropertyRefExpr *E,
+                                ExplodedNode *Pred, ExplodedNodeSet &Dst);
+
   /// Transfer function logic for computing the lvalue of an Objective-C ivar.
   void VisitLvalObjCIvarRefExpr(const ObjCIvarRefExpr* DR, ExplodedNode* Pred,
                                 ExplodedNodeSet& Dst);
index d4d846954e8b7936983324166747bc93b099973c..721c3598dc2aa73b370571b5f14892feb2b489a2 100644 (file)
@@ -427,7 +427,7 @@ public:
 
 namespace loc {
 
-enum Kind { GotoLabelKind, MemRegionKind, ConcreteIntKind };
+enum Kind { GotoLabelKind, MemRegionKind, ConcreteIntKind, ObjCPropRefKind };
 
 class GotoLabel : public Loc {
 public:
@@ -505,6 +505,28 @@ public:
   }
 };
 
+/// \brief Pseudo-location SVal used by the ExprEngine to simulate a "load" or
+/// "store" of an ObjC property for the dot syntax.
+class ObjCPropRef : public Loc {
+public:
+  explicit ObjCPropRef(const ObjCPropertyRefExpr *E)
+    : Loc(ObjCPropRefKind, E) {}
+
+  const ObjCPropertyRefExpr *getPropRefExpr() const {
+    return static_cast<const ObjCPropertyRefExpr *>(Data);
+  }
+
+  // Implement isa<T> support.
+  static inline bool classof(const SVal* V) {
+    return V->getBaseKind() == LocKind &&
+           V->getSubKind() == ObjCPropRefKind;
+  }
+
+  static inline bool classof(const Loc* V) {
+    return V->getSubKind() == ObjCPropRefKind;
+  }
+};
+
 } // end ento::loc namespace
 } // end GR namespace
 
index 42fd3391f16abe5e39815eb481fafd50e3511300..0254ba777a48d272846b3c282689a709ef7b45af 100644 (file)
@@ -196,6 +196,7 @@ SVal BasicStoreManager::Retrieve(Store store, Loc loc, QualType T) {
       return V.isUnknownOrUndef() ? V : CastRetrievedVal(V, TR, T);
     }
 
+    case loc::ObjCPropRefKind:
     case loc::ConcreteIntKind:
       // Support direct accesses to memory.  It's up to individual checkers
       // to flag an error.
index 5cc4a3c3ee3406ce6aa9ee3200e5d5c0afdf02ca..473b7313bb7ab871bf234e8328c8d0fbba07ed20 100644 (file)
@@ -2033,9 +2033,10 @@ PathDiagnosticPiece* CFRefReport::VisitNode(const ExplodedNode* N,
       else
         os << "function call";
     }
-    else {
-      assert (isa<ObjCMessageExpr>(S));
+    else if (isa<ObjCMessageExpr>(S)) {
       os << "Method";
+    } else {
+      os << "Property";
     }
 
     if (CurrV.getObjKind() == RetEffect::CF) {
index e6a40bb59757bcab589c0fc65c970cb90c3bb4bd..1f5f233b033fcdd5cff932fe4bccaa1099dcee70 100644 (file)
@@ -246,10 +246,11 @@ void CallAndMessageChecker::preVisitObjCMessage(CheckerContext &C,
       return;
     }
 
+  const char *bugDesc = msg.isPropertySetter() ?
+                     "Argument for property setter is an uninitialized value"
+                   : "Argument in message expression is an uninitialized value";
   // Check for any arguments that are uninitialized/undefined.
-  PreVisitProcessArgs(C, CallOrObjCMessage(msg, state),
-                      "Argument in message expression "
-                      "is an uninitialized value", BT_msg_arg);
+  PreVisitProcessArgs(C, CallOrObjCMessage(msg, state), bugDesc, BT_msg_arg);
 }
 
 bool CallAndMessageChecker::evalNilReceiver(CheckerContext &C,
index 9a74bd99a15e923260b527076007c89bb37b42cc..53931dc607a883d92e8cd6dc6a2af72cebad3302 100644 (file)
@@ -865,6 +865,10 @@ void ExprEngine::Visit(const Stmt* S, ExplodedNode* Pred,
       VisitObjCAtSynchronizedStmt(cast<ObjCAtSynchronizedStmt>(S), Pred, Dst);
       break;
 
+    case Stmt::ObjCPropertyRefExprClass:
+      VisitObjCPropertyRefExpr(cast<ObjCPropertyRefExpr>(S), Pred, Dst);
+      break;
+
     // Cases not handled yet; but will handle some day.
     case Stmt::DesignatedInitExprClass:
     case Stmt::ExtVectorElementExprClass:
@@ -875,7 +879,6 @@ void ExprEngine::Visit(const Stmt* S, ExplodedNode* Pred,
     case Stmt::ObjCAtTryStmtClass:
     case Stmt::ObjCEncodeExprClass:
     case Stmt::ObjCIsaExprClass:
-    case Stmt::ObjCPropertyRefExprClass:
     case Stmt::ObjCProtocolExprClass:
     case Stmt::ObjCSelectorExprClass:
     case Stmt::ObjCStringLiteralClass:
@@ -1799,6 +1802,17 @@ void ExprEngine::evalStore(ExplodedNodeSet& Dst, const Expr *AssignE,
 
   assert(Builder && "StmtNodeBuilder must be defined.");
 
+  // Proceed with the store.  We use AssignE as the anchor for the PostStore
+  // ProgramPoint if it is non-NULL, and LocationE otherwise.
+  const Expr *StoreE = AssignE ? AssignE : LocationE;
+
+  if (isa<loc::ObjCPropRef>(location)) {
+    loc::ObjCPropRef prop = cast<loc::ObjCPropRef>(location);
+    ExplodedNodeSet src = Pred;
+    return VisitObjCMessage(ObjCPropertySetter(prop.getPropRefExpr(),
+                                               StoreE, Val), src, Dst);
+  }
+
   // Evaluate the location (checks for bad dereferences).
   ExplodedNodeSet Tmp;
   evalLocation(Tmp, LocationE, Pred, state, location, tag, false);
@@ -1811,10 +1825,6 @@ void ExprEngine::evalStore(ExplodedNodeSet& Dst, const Expr *AssignE,
   SaveAndRestore<ProgramPoint::Kind> OldSPointKind(Builder->PointKind,
                                                    ProgramPoint::PostStoreKind);
 
-  // Proceed with the store.  We use AssignE as the anchor for the PostStore
-  // ProgramPoint if it is non-NULL, and LocationE otherwise.
-  const Expr *StoreE = AssignE ? AssignE : LocationE;
-
   for (ExplodedNodeSet::iterator NI=Tmp.begin(), NE=Tmp.end(); NI!=NE; ++NI)
     evalBind(Dst, StoreE, *NI, GetState(*NI), location, Val);
 }
@@ -1825,6 +1835,13 @@ void ExprEngine::evalLoad(ExplodedNodeSet& Dst, const Expr *Ex,
                             const void *tag, QualType LoadTy) {
   assert(!isa<NonLoc>(location) && "location cannot be a NonLoc.");
 
+  if (isa<loc::ObjCPropRef>(location)) {
+    loc::ObjCPropRef prop = cast<loc::ObjCPropRef>(location);
+    ExplodedNodeSet src = Pred;
+    return VisitObjCMessage(ObjCPropertyGetter(prop.getPropRefExpr(), Ex),
+                            src, Dst);
+  }
+
   // Are we loading from a region?  This actually results in two loads; one
   // to fetch the address of the referenced value and one to fetch the
   // referenced value.
@@ -2047,6 +2064,34 @@ void ExprEngine::VisitCall(const CallExpr* CE, ExplodedNode* Pred,
   CheckerVisit(CE, Dst, DstTmp3, PostVisitStmtCallback);
 }
 
+//===----------------------------------------------------------------------===//
+// Transfer function: Objective-C dot-syntax to access a property.
+//===----------------------------------------------------------------------===//
+
+void ExprEngine::VisitObjCPropertyRefExpr(const ObjCPropertyRefExpr *Ex,
+                                          ExplodedNode *Pred,
+                                          ExplodedNodeSet &Dst) {
+
+  // Visit the base expression, which is needed for computing the lvalue
+  // of the ivar.
+  ExplodedNodeSet dstBase;
+  const Expr *baseExpr = Ex->getBase();
+  Visit(baseExpr, Pred, dstBase);
+
+  ExplodedNodeSet dstPropRef;
+
+  // Using the base, compute the lvalue of the instance variable.
+  for (ExplodedNodeSet::iterator I = dstBase.begin(), E = dstBase.end();
+       I!=E; ++I) {
+    ExplodedNode *nodeBase = *I;
+    const GRState *state = GetState(nodeBase);
+    SVal baseVal = state->getSVal(baseExpr);
+    MakeNode(dstPropRef, Ex, *I, state->BindExpr(Ex, loc::ObjCPropRef(Ex)));
+  }
+  
+  Dst.insert(dstPropRef);
+}
+
 //===----------------------------------------------------------------------===//
 // Transfer function: Objective-C ivar references.
 //===----------------------------------------------------------------------===//
@@ -2426,7 +2471,8 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex,
   ExplodedNodeSet S2;
   CheckerVisit(CastE, S2, S1, PreVisitStmtCallback);
   
-  if (CastE->getCastKind() == CK_LValueToRValue) {
+  if (CastE->getCastKind() == CK_LValueToRValue ||
+      CastE->getCastKind() == CK_GetObjCProperty) {
     for (ExplodedNodeSet::iterator I = S2.begin(), E = S2.end(); I!=E; ++I) {
       ExplodedNode *subExprNode = *I;
       const GRState *state = GetState(subExprNode);
index 986278e628493a34525d514d47a2dd16872ed4b4..d5af1c2747be09f476e089632ac8d874ae6a4eb5 100644 (file)
@@ -985,6 +985,9 @@ SVal RegionStoreManager::Retrieve(Store store, Loc L, QualType T) {
   if (isa<loc::ConcreteInt>(L)) {
     return UnknownVal();
   }
+  if (!isa<loc::MemRegionVal>(L)) {
+    return UnknownVal();
+  }
 
   const MemRegion *MR = cast<loc::MemRegionVal>(L).getRegion();
 
index dd8508a50b227562e8457f757dda4bd91591b95d..1b2f1de49de9f0acf4a5b492cc2384632682be08 100644 (file)
@@ -13,6 +13,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/StaticAnalyzer/PathSensitive/GRState.h"
+#include "clang/AST/ExprObjC.h"
 #include "clang/Basic/IdentifierTable.h"
 
 using namespace clang;
@@ -354,6 +355,22 @@ void Loc::dumpToStream(llvm::raw_ostream& os) const {
     case loc::MemRegionKind:
       os << '&' << cast<loc::MemRegionVal>(this)->getRegion()->getString();
       break;
+    case loc::ObjCPropRefKind: {
+      const ObjCPropertyRefExpr *E = cast<loc::ObjCPropRef>(this)->getPropRefExpr();
+      os << "objc-prop{";
+      if (E->isSuperReceiver())
+        os << "super.";
+      else if (E->getBase())
+        os << "<base>.";
+
+      if (E->isImplicitProperty())
+        os << E->getImplicitPropertyGetter()->getSelector().getAsString();
+      else
+        os << E->getExplicitProperty()->getName();
+
+      os << "}";
+      break;
+    }
     default:
       assert(false && "Pretty-printing not implemented for this Loc.");
       break;
diff --git a/test/Analysis/properties.m b/test/Analysis/properties.m
new file mode 100644 (file)
index 0000000..b0325a1
--- /dev/null
@@ -0,0 +1,135 @@
+// RUN: %clang_cc1 -analyze -analyzer-check-objc-mem -analyzer-store=region -verify %s
+
+typedef signed char BOOL;
+typedef unsigned int NSUInteger;
+typedef struct _NSZone NSZone;
+@class NSInvocation, NSMethodSignature, NSCoder, NSString, NSEnumerator;
+@protocol NSObject  - (BOOL)isEqual:(id)object; @end
+@protocol NSCopying  - (id)copyWithZone:(NSZone *)zone; @end
+@protocol NSCoding  - (void)encodeWithCoder:(NSCoder *)aCoder; @end
+@protocol NSMutableCopying  - (id)mutableCopyWithZone:(NSZone *)zone; @end
+@interface NSObject <NSObject> {}
++(id)alloc;
+-(id)init;
+-(id)autorelease;
+-(id)copy;
+-(id)retain;
+@end
+@interface NSString : NSObject <NSCopying, NSMutableCopying, NSCoding>
+- (NSUInteger)length;
+-(id)initWithFormat:(NSString *)f,...;
+-(BOOL)isEqualToString:(NSString *)s;
++ (id)string;
+@end
+@interface NSNumber : NSObject {}
++(id)alloc;
+-(id)initWithInteger:(int)i;
+@end
+
+// rdar://6946338
+
+@interface Test1 : NSObject {
+  NSString *text;
+}
+-(id)myMethod;
+@property (nonatomic, assign) NSString *text;
+@end
+
+
+@implementation Test1
+
+@synthesize text;
+
+-(id)myMethod {
+  Test1 *cell = [[[Test1 alloc] init] autorelease];
+
+  NSString *string1 = [[NSString alloc] initWithFormat:@"test %f", 0.0]; // expected-warning {{Potential leak}}
+  cell.text = string1;
+
+  return cell;
+}
+
+@end
+
+
+// rdar://8824416
+
+@interface MyNumber : NSObject
+{
+  NSNumber* _myNumber;
+}
+
+- (id)initWithNumber:(NSNumber *)number;
+
+@property (nonatomic, readonly) NSNumber* myNumber;
+@property (nonatomic, readonly) NSNumber* newMyNumber;
+
+@end
+
+@implementation MyNumber
+@synthesize myNumber=_myNumber;
+- (id)initWithNumber:(NSNumber *)number
+{
+  self = [super init];
+  
+  if ( self )
+  {
+    _myNumber = [number copy];
+  }
+  
+  return self;
+}
+
+- (NSNumber*)newMyNumber
+{
+  if ( _myNumber )
+    return [_myNumber retain];
+  
+  return [[NSNumber alloc] initWithInteger:1];
+}
+
+- (id)valueForUndefinedKey:(NSString*)key
+{
+  id value = 0;
+  
+  if ([key isEqualToString:@"MyIvarNumberAsPropertyOverReleased"])
+    value = self.myNumber; // _myNumber will be over released, since the value returned from self.myNumber is not retained.
+  else if ([key isEqualToString:@"MyIvarNumberAsPropertyOk"])
+    value = [self.myNumber retain]; // this line fixes the over release
+  else if ([key isEqualToString:@"MyIvarNumberAsNewMyNumber"])
+    value = self.newMyNumber; // this one is ok, since value is returned retained
+  else 
+    value = [[NSNumber alloc] initWithInteger:0];
+  
+  return [value autorelease]; // expected-warning {{Object sent -autorelease too many times}}
+}
+
+@end
+
+NSNumber* numberFromMyNumberProperty(MyNumber* aMyNumber)
+{
+  NSNumber* result = aMyNumber.myNumber;
+    
+  return [result autorelease]; // expected-warning {{Object sent -autorelease too many times}}
+}
+
+
+// rdar://6611873
+
+@interface Person : NSObject {
+  NSString *_name;
+}
+@property (retain) NSString * name;
+@end
+
+@implementation Person
+@synthesize name = _name;
+@end
+
+void rdar6611873() {
+  Person *p = [[[Person alloc] init] autorelease];
+  
+  p.name = [[NSString string] retain]; // expected-warning {{leak}}
+  p.name = [[NSString alloc] init]; // expected-warning {{leak}}
+}