r232849 - [analyzer] RetainCountChecker: Don't assume +0 for ivars backing readonly properties.

Jordan Rose jordan_rose at apple.com
Fri Mar 20 14:12:27 PDT 2015


Author: jrose
Date: Fri Mar 20 16:12:27 2015
New Revision: 232849

URL: http://llvm.org/viewvc/llvm-project?rev=232849&view=rev
Log:
[analyzer] RetainCountChecker: Don't assume +0 for ivars backing readonly properties.

Similarly, don't assume +0 if the property's setter is manually implemented.
In both cases, if the property's ownership is explicitly written, then we /do/
assume the ivar has the same ownership.

rdar://problem/20218183

Modified:
    cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp
    cfe/trunk/test/Analysis/properties.m

Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp?rev=232849&r1=232848&r2=232849&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp Fri Mar 20 16:12:27 2015
@@ -2844,6 +2844,40 @@ static const ObjCPropertyDecl *findPropF
   return nullptr;
 }
 
+namespace {
+  enum Retaining_t {
+    NonRetaining,
+    Retaining
+  };
+}
+
+static Optional<Retaining_t> getRetainSemantics(const ObjCPropertyDecl *Prop) {
+  assert(Prop->getPropertyIvarDecl() &&
+         "should only be used for properties with synthesized implementations");
+
+  if (!Prop->hasWrittenStorageAttribute()) {
+    // Don't assume anything about the retain semantics of readonly properties.
+    if (Prop->isReadOnly())
+      return None;
+
+    // Don't assume anything about readwrite properties with manually-supplied
+    // setters.
+    const ObjCMethodDecl *Setter = Prop->getSetterMethodDecl();
+    bool HasManualSetter = std::any_of(Setter->redecls_begin(),
+                                       Setter->redecls_end(),
+                                       [](const Decl *SetterRedecl) -> bool {
+      return cast<ObjCMethodDecl>(SetterRedecl)->hasBody();
+    });
+    if (HasManualSetter)
+      return None;
+
+    // If the setter /is/ synthesized, we're already relying on the retain
+    // semantics of the property. Continue as normal.
+  }
+
+  return Prop->isRetaining() ? Retaining : NonRetaining;
+}
+
 void RetainCountChecker::checkPostStmt(const ObjCIvarRefExpr *IRE,
                                        CheckerContext &C) const {
   Optional<Loc> IVarLoc = C.getSVal(IRE).getAs<Loc>();
@@ -2884,8 +2918,9 @@ void RetainCountChecker::checkPostStmt(c
     // there's no outstanding retain count for the value.
     if (Kind == RetEffect::ObjC)
       if (const ObjCPropertyDecl *Prop = findPropForIvar(IRE->getDecl()))
-        if (!Prop->isRetaining())
-          return;
+        if (auto retainSemantics = getRetainSemantics(Prop))
+          if (retainSemantics.getValue() == NonRetaining)
+            return;
 
     // Note that this value has been loaded from an ivar.
     C.addTransition(setRefBinding(State, Sym, RV->withIvarAccess()));
@@ -2900,18 +2935,23 @@ void RetainCountChecker::checkPostStmt(c
     return;
   }
 
-  // Try to find the property associated with this ivar.
-  if (Kind != RetEffect::ObjC) {
-    State = setRefBinding(State, Sym, PlusZero.withIvarAccess());
-  } else {
-    const ObjCPropertyDecl *Prop = findPropForIvar(IRE->getDecl());
-
-    if (Prop && !Prop->isRetaining())
-      State = setRefBinding(State, Sym, PlusZero);
-    else
-      State = setRefBinding(State, Sym, PlusZero.withIvarAccess());
+  bool didUpdateState = false;
+  if (Kind == RetEffect::ObjC) {
+    // Check if the ivar is known to be unretained. If so, we know that
+    // there's no outstanding retain count for the value.
+    if (const ObjCPropertyDecl *Prop = findPropForIvar(IRE->getDecl())) {
+      if (auto retainSemantics = getRetainSemantics(Prop)) {
+        if (retainSemantics.getValue() == NonRetaining) {
+          State = setRefBinding(State, Sym, PlusZero);
+          didUpdateState = true;
+        }
+      }
+    }
   }
 
+  if (!didUpdateState)
+    State = setRefBinding(State, Sym, PlusZero.withIvarAccess());
+
   C.addTransition(State);
 }
 

Modified: cfe/trunk/test/Analysis/properties.m
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/properties.m?rev=232849&r1=232848&r2=232849&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/properties.m (original)
+++ cfe/trunk/test/Analysis/properties.m Fri Mar 20 16:12:27 2015
@@ -356,6 +356,9 @@ void testOpaqueConsistency(OpaqueIntWrap
 @property (strong) id ownedProp;
 @property (unsafe_unretained) id unownedProp;
 @property (nonatomic, strong) id manualProp;
+ at property (readonly) id readonlyProp;
+ at property (nonatomic, readwrite/*, assign */) id implicitManualProp; // expected-warning {{'assign' is assumed}} expected-warning {{'assign' not appropriate}}
+ at property (nonatomic, readwrite/*, assign */) id implicitSynthProp; // expected-warning {{'assign' is assumed}} expected-warning {{'assign' not appropriate}}
 @property CFTypeRef cfProp;
 @end
 
@@ -367,6 +370,8 @@ void testOpaqueConsistency(OpaqueIntWrap
   return _manualProp;
 }
 
+- (void)setImplicitManualProp:(id)newValue {}
+
 - (void)testOverreleaseOwnedIvar {
   [_ownedProp retain];
   [_ownedProp release];
@@ -387,6 +392,26 @@ void testOpaqueConsistency(OpaqueIntWrap
   [_ivarOnly release]; // expected-warning{{used after it is released}}
 }
 
+- (void)testOverreleaseReadonlyIvar {
+  [_readonlyProp retain];
+  [_readonlyProp release];
+  [_readonlyProp release];
+  [_readonlyProp release]; // expected-warning{{used after it is released}}
+}
+
+- (void)testOverreleaseImplicitManualIvar {
+  [_implicitManualProp retain];
+  [_implicitManualProp release];
+  [_implicitManualProp release];
+  [_implicitManualProp release]; // expected-warning{{used after it is released}}
+}
+
+- (void)testOverreleaseImplicitSynthIvar {
+  [_implicitSynthProp retain];
+  [_implicitSynthProp release];
+  [_implicitSynthProp release]; // expected-warning{{not owned at this point by the caller}}
+}
+
 - (void)testOverreleaseCF {
   CFRetain(_cfProp);
   CFRelease(_cfProp);
@@ -501,6 +526,48 @@ void testOpaqueConsistency(OpaqueIntWrap
   clang_analyzer_eval(owned == fromIvar); // expected-warning{{TRUE}}
 }
 
+- (void)testPropertyAccessThenReleaseReadonly {
+  id prop = [self.readonlyProp retain];
+  [prop release];
+  [_readonlyProp release]; // no-warning
+}
+
+- (void)testPropertyAccessThenReleaseReadonly2 {
+  id fromIvar = _readonlyProp;
+  id prop = [self.readonlyProp retain];
+  [prop release];
+  clang_analyzer_eval(prop == fromIvar); // expected-warning{{TRUE}}
+  [fromIvar release]; // no-warning
+}
+
+- (void)testPropertyAccessThenReleaseImplicitManual {
+  id prop = [self.implicitManualProp retain];
+  [prop release];
+  [_implicitManualProp release]; // no-warning
+}
+
+- (void)testPropertyAccessThenReleaseImplicitManual2 {
+  id fromIvar = _implicitManualProp;
+  id prop = [self.implicitManualProp retain];
+  [prop release];
+  clang_analyzer_eval(prop == fromIvar); // expected-warning{{TRUE}}
+  [fromIvar release]; // no-warning
+}
+
+- (void)testPropertyAccessThenReleaseImplicitSynth {
+  id prop = [self.implicitSynthProp retain];
+  [prop release];
+  [_implicitSynthProp release]; // expected-warning{{not owned}}
+}
+
+- (void)testPropertyAccessThenReleaseImplicitSynth2 {
+  id fromIvar = _implicitSynthProp;
+  id prop = [self.implicitSynthProp retain];
+  [prop release];
+  clang_analyzer_eval(prop == fromIvar); // expected-warning{{TRUE}}
+  [fromIvar release]; // expected-warning{{not owned}}
+}
+
 - (id)getUnownedFromProperty {
   [_ownedProp retain];
   [_ownedProp autorelease];
@@ -539,6 +606,21 @@ void testOpaqueConsistency(OpaqueIntWrap
   CFRelease(_cfProp); // FIXME: no-warning{{not owned}}
 }
 
+- (void)testAssignReadonly:(id)newValue {
+  _readonlyProp = newValue;
+  [_readonlyProp release]; // FIXME: no-warning{{not owned}}
+}
+
+- (void)testAssignImplicitManual:(id)newValue {
+  _implicitManualProp = newValue;
+  [_implicitManualProp release]; // FIXME: no-warning{{not owned}}
+}
+
+- (void)testAssignImplicitSynth:(id)newValue {
+  _implicitSynthProp = newValue;
+  [_implicitSynthProp release]; // FIXME: no-warning{{not owned}}
+}
+
 - (void)testAssignOwnedOkay:(id)newValue {
   _ownedProp = [newValue retain];
   [_ownedProp release]; // no-warning
@@ -559,6 +641,21 @@ void testOpaqueConsistency(OpaqueIntWrap
   CFRelease(_cfProp); // no-warning
 }
 
+- (void)testAssignReadonlyOkay:(id)newValue {
+  _readonlyProp = [newValue retain];
+  [_readonlyProp release]; // FIXME: no-warning{{not owned}}
+}
+
+- (void)testAssignImplicitManualOkay:(id)newValue {
+  _implicitManualProp = [newValue retain];
+  [_implicitManualProp release]; // FIXME: no-warning{{not owned}}
+}
+
+- (void)testAssignImplicitSynthOkay:(id)newValue {
+  _implicitSynthProp = [newValue retain];
+  [_implicitSynthProp release]; // FIXME: no-warning{{not owned}}
+}
+
 // rdar://problem/19862648
 - (void)establishIvarIsNilDuringLoops {
   extern id getRandomObject();





More information about the cfe-commits mailing list