r350982 - [analyzer] Support for OSObjects out parameters in RetainCountChecker

George Karpenkov via cfe-commits cfe-commits at lists.llvm.org
Fri Jan 11 15:35:17 PST 2019


Author: george.karpenkov
Date: Fri Jan 11 15:35:17 2019
New Revision: 350982

URL: http://llvm.org/viewvc/llvm-project?rev=350982&view=rev
Log:
[analyzer] Support for OSObjects out parameters in RetainCountChecker

rdar://46357478
rdar://47121327

Differential Revision: https://reviews.llvm.org/D56240

Modified:
    cfe/trunk/include/clang/StaticAnalyzer/Core/RetainSummaryManager.h
    cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp
    cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp
    cfe/trunk/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp
    cfe/trunk/test/Analysis/Inputs/expected-plists/retain-release.m.objc.plist
    cfe/trunk/test/Analysis/Inputs/expected-plists/retain-release.m.objcpp.plist
    cfe/trunk/test/Analysis/osobject-retain-release.cpp

Modified: cfe/trunk/include/clang/StaticAnalyzer/Core/RetainSummaryManager.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/StaticAnalyzer/Core/RetainSummaryManager.h?rev=350982&r1=350981&r2=350982&view=diff
==============================================================================
--- cfe/trunk/include/clang/StaticAnalyzer/Core/RetainSummaryManager.h (original)
+++ cfe/trunk/include/clang/StaticAnalyzer/Core/RetainSummaryManager.h Fri Jan 11 15:35:17 2019
@@ -77,13 +77,21 @@ enum ArgEffectKind {
   IncRef,
 
   /// The argument is a pointer to a retain-counted object; on exit, the new
-  /// value of the pointer is a +0 value or NULL.
+  /// value of the pointer is a +0 value.
   UnretainedOutParameter,
 
   /// The argument is a pointer to a retain-counted object; on exit, the new
-  /// value of the pointer is a +1 value or NULL.
+  /// value of the pointer is a +1 value.
   RetainedOutParameter,
 
+  /// The argument is a pointer to a retain-counted object; on exit, the new
+  /// value of the pointer is a +1 value iff the return code is zero.
+  RetainedOutParameterOnZero,
+
+  /// The argument is a pointer to a retain-counted object; on exit, the new
+  /// value of the pointer is a +1 value iff the return code is non-zero.
+  RetainedOutParameterOnNonZero,
+
   /// The argument is treated as potentially escaping, meaning that
   /// even when its reference count hits 0 it should be treated as still
   /// possibly being alive as someone else *may* be holding onto the object.

Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp?rev=350982&r1=350981&r2=350982&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp Fri Jan 11 15:35:17 2019
@@ -529,38 +529,92 @@ void RetainCountChecker::processSummaryO
   C.addTransition(state);
 }
 
-static ProgramStateRef updateOutParameter(ProgramStateRef State,
-                                          SVal ArgVal,
-                                          ArgEffectKind Effect) {
-  auto *ArgRegion = dyn_cast_or_null<TypedValueRegion>(ArgVal.getAsRegion());
-  if (!ArgRegion)
-    return State;
-
-  QualType PointeeTy = ArgRegion->getValueType();
-  if (!coreFoundation::isCFObjectRef(PointeeTy))
-    return State;
-
-  SVal PointeeVal = State->getSVal(ArgRegion);
-  SymbolRef Pointee = PointeeVal.getAsLocSymbol();
-  if (!Pointee)
-    return State;
-
-  switch (Effect) {
-  case UnretainedOutParameter:
-    State = setRefBinding(State, Pointee,
-                          RefVal::makeNotOwned(ObjKind::CF, PointeeTy));
-    break;
-  case RetainedOutParameter:
-    // Do nothing. Retained out parameters will either point to a +1 reference
-    // or NULL, but the way you check for failure differs depending on the API.
-    // Consequently, we don't have a good way to track them yet.
-    break;
+static bool shouldEscapeRegion(const MemRegion *R) {
 
-  default:
-    llvm_unreachable("only for out parameters");
+  // We do not currently model what happens when a symbol is
+  // assigned to a struct field, so be conservative here and let the symbol
+  // go. TODO: This could definitely be improved upon.
+  return !R->hasStackStorage() || !isa<VarRegion>(R);
+}
+
+static SmallVector<ProgramStateRef, 2>
+updateOutParameters(ProgramStateRef State, const RetainSummary &Summ,
+                    const CallEvent &CE) {
+
+  SVal L = CE.getReturnValue();
+
+  // Splitting is required to support out parameters,
+  // as out parameters might be created only on the "success" branch.
+  // We want to avoid eagerly splitting unless out parameters are actually
+  // needed.
+  bool SplitNecessary = false;
+  for (auto &P : Summ.getArgEffects())
+    if (P.second.getKind() == RetainedOutParameterOnNonZero ||
+        P.second.getKind() == RetainedOutParameterOnZero)
+      SplitNecessary = true;
+
+  ProgramStateRef AssumeNonZeroReturn = State;
+  ProgramStateRef AssumeZeroReturn = State;
+
+  if (SplitNecessary) {
+    if (auto DL = L.getAs<DefinedOrUnknownSVal>()) {
+      AssumeNonZeroReturn = AssumeNonZeroReturn->assume(*DL, true);
+      AssumeZeroReturn = AssumeZeroReturn->assume(*DL, false);
+    }
   }
 
-  return State;
+  for (unsigned idx = 0, e = CE.getNumArgs(); idx != e; ++idx) {
+    SVal ArgVal = CE.getArgSVal(idx);
+    ArgEffect AE = Summ.getArg(idx);
+
+    auto *ArgRegion = dyn_cast_or_null<TypedValueRegion>(ArgVal.getAsRegion());
+    if (!ArgRegion)
+      continue;
+
+    QualType PointeeTy = ArgRegion->getValueType();
+    SVal PointeeVal = State->getSVal(ArgRegion);
+    SymbolRef Pointee = PointeeVal.getAsLocSymbol();
+    if (!Pointee)
+      continue;
+
+    if (shouldEscapeRegion(ArgRegion))
+      continue;
+
+    auto makeNotOwnedParameter = [&](ProgramStateRef St) {
+      return setRefBinding(St, Pointee,
+                           RefVal::makeNotOwned(AE.getObjKind(), PointeeTy));
+    };
+    auto makeOwnedParameter = [&](ProgramStateRef St) {
+      return setRefBinding(St, Pointee,
+                           RefVal::makeOwned(ObjKind::OS, PointeeTy));
+    };
+
+    switch (AE.getKind()) {
+    case UnretainedOutParameter:
+      AssumeNonZeroReturn = makeNotOwnedParameter(AssumeNonZeroReturn);
+      AssumeZeroReturn = makeNotOwnedParameter(AssumeZeroReturn);
+      break;
+    case RetainedOutParameter:
+      AssumeNonZeroReturn = makeOwnedParameter(AssumeNonZeroReturn);
+      AssumeZeroReturn = makeOwnedParameter(AssumeZeroReturn);
+      break;
+    case RetainedOutParameterOnNonZero:
+      AssumeNonZeroReturn = makeOwnedParameter(AssumeNonZeroReturn);
+      break;
+    case RetainedOutParameterOnZero:
+      AssumeZeroReturn = makeOwnedParameter(AssumeZeroReturn);
+      break;
+    default:
+      break;
+    }
+  }
+
+  if (SplitNecessary) {
+    return {AssumeNonZeroReturn, AssumeZeroReturn};
+  } else {
+    assert(AssumeZeroReturn == AssumeNonZeroReturn);
+    return {AssumeZeroReturn};
+  }
 }
 
 void RetainCountChecker::checkSummary(const RetainSummary &Summ,
@@ -582,10 +636,7 @@ void RetainCountChecker::checkSummary(co
     SVal V = CallOrMsg.getArgSVal(idx);
 
     ArgEffect Effect = Summ.getArg(idx);
-    if (Effect.getKind() == RetainedOutParameter ||
-        Effect.getKind() == UnretainedOutParameter) {
-      state = updateOutParameter(state, V, Effect.getKind());
-    } else if (SymbolRef Sym = V.getAsLocSymbol()) {
+    if (SymbolRef Sym = V.getAsLocSymbol()) {
       if (const RefVal *T = getRefBinding(state, Sym)) {
 
         if (shouldEscapeOSArgumentOnCall(CallOrMsg, idx, T))
@@ -661,10 +712,15 @@ void RetainCountChecker::checkSummary(co
       state = setRefBinding(state, Sym, *updatedRefVal);
   }
 
-  if (DeallocSent) {
-    C.addTransition(state, C.getPredecessor(), &DeallocSentTag);
-  } else {
-    C.addTransition(state);
+  SmallVector<ProgramStateRef, 2> Out =
+      updateOutParameters(state, Summ, CallOrMsg);
+
+  for (ProgramStateRef St : Out) {
+    if (DeallocSent) {
+      C.addTransition(St, C.getPredecessor(), &DeallocSentTag);
+    } else {
+      C.addTransition(St);
+    }
   }
 }
 
@@ -700,6 +756,8 @@ ProgramStateRef RetainCountChecker::upda
   switch (AE.getKind()) {
     case UnretainedOutParameter:
     case RetainedOutParameter:
+    case RetainedOutParameterOnZero:
+    case RetainedOutParameterOnNonZero:
       llvm_unreachable("Applies to pointer-to-pointer parameters, which should "
                        "not have ref state.");
 
@@ -1094,29 +1152,10 @@ void RetainCountChecker::checkBind(SVal
   //
   // (1) we are binding to something that is not a memory region.
   // (2) we are binding to a memregion that does not have stack storage
-  // (3) we are binding to a memregion with stack storage that the store
-  //     does not understand.
   ProgramStateRef state = C.getState();
 
   if (auto regionLoc = loc.getAs<loc::MemRegionVal>()) {
-    escapes = !regionLoc->getRegion()->hasStackStorage();
-
-    if (!escapes) {
-      // To test (3), generate a new state with the binding added.  If it is
-      // the same state, then it escapes (since the store cannot represent
-      // the binding).
-      // Do this only if we know that the store is not supposed to generate the
-      // same state.
-      SVal StoredVal = state->getSVal(regionLoc->getRegion());
-      if (StoredVal != val)
-        escapes = (state == (state->bindLoc(*regionLoc, val, C.getLocationContext())));
-    }
-    if (!escapes) {
-      // Case 4: We do not currently model what happens when a symbol is
-      // assigned to a struct field, so be conservative here and let the symbol
-      // go. TODO: This could definitely be improved upon.
-      escapes = !isa<VarRegion>(regionLoc->getRegion());
-    }
+    escapes = shouldEscapeRegion(regionLoc->getRegion());
   }
 
   // If we are storing the value into an auto function scope variable annotated

Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp?rev=350982&r1=350981&r2=350982&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp Fri Jan 11 15:35:17 2019
@@ -113,11 +113,31 @@ static bool shouldGenerateNote(llvm::raw
   return true;
 }
 
+/// Finds argument index of the out paramter in the call {@code S}
+/// corresponding to the symbol {@code Sym}.
+/// If none found, returns None.
+static Optional<unsigned> findArgIdxOfSymbol(ProgramStateRef CurrSt,
+                                             const LocationContext *LCtx,
+                                             SymbolRef &Sym,
+                                             Optional<CallEventRef<>> CE) {
+  if (!CE)
+    return None;
+
+  for (unsigned Idx = 0; Idx < (*CE)->getNumArgs(); Idx++)
+    if (const MemRegion *MR = (*CE)->getArgSVal(Idx).getAsRegion())
+      if (const auto *TR = dyn_cast<TypedValueRegion>(MR))
+        if (CurrSt->getSVal(MR, TR->getValueType()).getAsSymExpr() == Sym)
+          return Idx;
+
+  return None;
+}
+
 static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt,
                                            const LocationContext *LCtx,
                                            const RefVal &CurrV, SymbolRef &Sym,
                                            const Stmt *S,
                                            llvm::raw_string_ostream &os) {
+  CallEventManager &Mgr = CurrSt->getStateManager().getCallEventManager();
   if (const CallExpr *CE = dyn_cast<CallExpr>(S)) {
     // Get the name of the callee (if it is available)
     // from the tracked SVal.
@@ -139,7 +159,6 @@ static void generateDiagnosticsForCallLi
     os << "Operator 'new'";
   } else {
     assert(isa<ObjCMessageExpr>(S));
-    CallEventManager &Mgr = CurrSt->getStateManager().getCallEventManager();
     CallEventRef<ObjCMethodCall> Call =
         Mgr.getObjCMethodCall(cast<ObjCMessageExpr>(S), CurrSt, LCtx);
 
@@ -156,7 +175,15 @@ static void generateDiagnosticsForCallLi
     }
   }
 
-   os << " returns ";
+  Optional<CallEventRef<>> CE = Mgr.getCall(S, CurrSt, LCtx);
+  auto Idx = findArgIdxOfSymbol(CurrSt, LCtx, Sym, CE);
+
+  // If index is not found, we assume that the symbol was returned.
+  if (!Idx) {
+    os << " returns ";
+  } else {
+    os << " writes ";
+  }
 
   if (CurrV.getObjKind() == ObjKind::CF) {
     os << "a Core Foundation object of type '"
@@ -185,6 +212,25 @@ static void generateDiagnosticsForCallLi
     assert(CurrV.isNotOwned());
     os << "+0 retain count";
   }
+
+  if (Idx) {
+    os << " into an out parameter '";
+    const ParmVarDecl *PVD = (*CE)->parameters()[*Idx];
+    PVD->getNameForDiagnostic(os, PVD->getASTContext().getPrintingPolicy(),
+                              /*Qualified=*/false);
+    os << "'";
+
+    QualType RT = (*CE)->getResultType();
+    if (!RT.isNull() && !RT->isVoidType()) {
+      SVal RV = (*CE)->getReturnValue();
+      if (CurrSt->isNull(RV).isConstrainedTrue()) {
+        os << " (assuming the call returns zero)";
+      } else if (CurrSt->isNonNull(RV).isConstrainedTrue()) {
+        os << " (assuming the call returns non-zero)";
+      }
+
+    }
+  }
 }
 
 namespace clang {

Modified: cfe/trunk/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp?rev=350982&r1=350981&r2=350982&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp Fri Jan 11 15:35:17 2019
@@ -88,7 +88,9 @@ Optional<ObjKind> RetainSummaryManager::
       return None;
     K = ObjKind::ObjC;
   } else if (isOneOf<T, OSConsumedAttr, OSConsumesThisAttr,
-                     OSReturnsNotRetainedAttr, OSReturnsRetainedAttr>()) {
+                     OSReturnsNotRetainedAttr, OSReturnsRetainedAttr,
+                     OSReturnsRetainedOnZeroAttr,
+                     OSReturnsRetainedOnNonZeroAttr>()) {
     if (!TrackOSObjects)
       return None;
     K = ObjKind::OS;
@@ -522,6 +524,8 @@ static ArgEffect getStopTrackingHardEqui
   case IncRef:
   case UnretainedOutParameter:
   case RetainedOutParameter:
+  case RetainedOutParameterOnZero:
+  case RetainedOutParameterOnNonZero:
   case MayEscape:
   case StopTracking:
   case StopTrackingHard:
@@ -811,6 +815,29 @@ RetainSummaryManager::getRetEffectFromAn
   return None;
 }
 
+/// \return Whether the chain of typedefs starting from {@code QT}
+/// has a typedef with a given name {@code Name}.
+static bool hasTypedefNamed(QualType QT,
+                            StringRef Name) {
+  while (auto *T = dyn_cast<TypedefType>(QT)) {
+    const auto &Context = T->getDecl()->getASTContext();
+    if (T->getDecl()->getIdentifier() == &Context.Idents.get(Name))
+      return true;
+    QT = T->getDecl()->getUnderlyingType();
+  }
+  return false;
+}
+
+static QualType getCallableReturnType(const NamedDecl *ND) {
+  if (const auto *FD = dyn_cast<FunctionDecl>(ND)) {
+    return FD->getReturnType();
+  } else if (const auto *MD = dyn_cast<ObjCMethodDecl>(ND)) {
+    return MD->getReturnType();
+  } else {
+    llvm_unreachable("Unexpected decl");
+  }
+}
+
 bool RetainSummaryManager::applyParamAnnotationEffect(
     const ParmVarDecl *pd, unsigned parm_idx, const NamedDecl *FD,
     RetainSummaryTemplate &Template) {
@@ -820,21 +847,54 @@ bool RetainSummaryManager::applyParamAnn
                               GeneralizedConsumedAttr>(pd, QT)) {
     Template->addArg(AF, parm_idx, ArgEffect(DecRef, *K));
     return true;
-  } else if (auto K =
-                 hasAnyEnabledAttrOf<CFReturnsRetainedAttr,
-                                     GeneralizedReturnsRetainedAttr>(pd, QT)) {
-    Template->addArg(AF, parm_idx, ArgEffect(RetainedOutParameter, *K));
+  } else if (auto K = hasAnyEnabledAttrOf<
+                 CFReturnsRetainedAttr, OSReturnsRetainedAttr,
+                 OSReturnsRetainedOnNonZeroAttr, OSReturnsRetainedOnZeroAttr,
+                 GeneralizedReturnsRetainedAttr>(pd, QT)) {
+
+    // For OSObjects, we try to guess whether the object is created based
+    // on the return value.
+    if (K == ObjKind::OS) {
+      QualType QT = getCallableReturnType(FD);
+
+      bool HasRetainedOnZero = pd->hasAttr<OSReturnsRetainedOnZeroAttr>();
+      bool HasRetainedOnNonZero = pd->hasAttr<OSReturnsRetainedOnNonZeroAttr>();
+
+      // The usual convention is to create an object on non-zero return, but
+      // it's reverted if the typedef chain has a typedef kern_return_t,
+      // because kReturnSuccess constant is defined as zero.
+      // The convention can be overwritten by custom attributes.
+      bool SuccessOnZero =
+          HasRetainedOnZero ||
+          (hasTypedefNamed(QT, "kern_return_t") && !HasRetainedOnNonZero);
+      bool ShouldSplit = !QT.isNull() && !QT->isVoidType();
+      ArgEffectKind AK = RetainedOutParameter;
+      if (ShouldSplit && SuccessOnZero) {
+        AK = RetainedOutParameterOnZero;
+      } else if (ShouldSplit && (!SuccessOnZero || HasRetainedOnNonZero)) {
+        AK = RetainedOutParameterOnNonZero;
+      }
+      Template->addArg(AF, parm_idx, ArgEffect(AK, ObjKind::OS));
+    }
+
+    // For others:
+    // Do nothing. Retained out parameters will either point to a +1 reference
+    // or NULL, but the way you check for failure differs depending on the
+    // API. Consequently, we don't have a good way to track them yet.
     return true;
-  } else if (auto K = hasAnyEnabledAttrOf<CFReturnsNotRetainedAttr>(pd, QT)) {
+  } else if (auto K = hasAnyEnabledAttrOf<CFReturnsNotRetainedAttr,
+                                          OSReturnsNotRetainedAttr,
+                                          GeneralizedReturnsNotRetainedAttr>(
+                 pd, QT)) {
     Template->addArg(AF, parm_idx, ArgEffect(UnretainedOutParameter, *K));
     return true;
-  } else {
-    if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
-      for (const auto *OD : MD->overridden_methods()) {
-        const ParmVarDecl *OP = OD->parameters()[parm_idx];
-        if (applyParamAnnotationEffect(OP, parm_idx, OD, Template))
-          return true;
-      }
+  }
+
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
+    for (const auto *OD : MD->overridden_methods()) {
+      const ParmVarDecl *OP = OD->parameters()[parm_idx];
+      if (applyParamAnnotationEffect(OP, parm_idx, OD, Template))
+        return true;
     }
   }
 

Modified: cfe/trunk/test/Analysis/Inputs/expected-plists/retain-release.m.objc.plist
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/Inputs/expected-plists/retain-release.m.objc.plist?rev=350982&r1=350981&r2=350982&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/Inputs/expected-plists/retain-release.m.objc.plist (original)
+++ cfe/trunk/test/Analysis/Inputs/expected-plists/retain-release.m.objc.plist Fri Jan 11 15:35:17 2019
@@ -25854,9 +25854,9 @@
      </array>
      <key>depth</key><integer>0</integer>
      <key>extended_message</key>
-     <string>Call to function 'getViaParam' returns a Core Foundation object of type 'CFTypeRef' with a +0 retain count</string>
+     <string>Call to function 'getViaParam' writes a Core Foundation object of type 'CFTypeRef' with a +0 retain count into an out parameter 'outObj'</string>
      <key>message</key>
-     <string>Call to function 'getViaParam' returns a Core Foundation object of type 'CFTypeRef' with a +0 retain count</string>
+     <string>Call to function 'getViaParam' writes a Core Foundation object of type 'CFTypeRef' with a +0 retain count into an out parameter 'outObj'</string>
     </dict>
     <dict>
      <key>kind</key><string>control</string>
@@ -26010,9 +26010,9 @@
      </array>
      <key>depth</key><integer>0</integer>
      <key>extended_message</key>
-     <string>Call to function 'getViaParam2' returns a Core Foundation object of type 'CFTypeRef' with a +0 retain count</string>
+     <string>Call to function 'getViaParam2' writes a Core Foundation object of type 'CFTypeRef' with a +0 retain count into an out parameter 'outObj'</string>
      <key>message</key>
-     <string>Call to function 'getViaParam2' returns a Core Foundation object of type 'CFTypeRef' with a +0 retain count</string>
+     <string>Call to function 'getViaParam2' writes a Core Foundation object of type 'CFTypeRef' with a +0 retain count into an out parameter 'outObj'</string>
     </dict>
     <dict>
      <key>kind</key><string>control</string>

Modified: cfe/trunk/test/Analysis/Inputs/expected-plists/retain-release.m.objcpp.plist
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/Inputs/expected-plists/retain-release.m.objcpp.plist?rev=350982&r1=350981&r2=350982&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/Inputs/expected-plists/retain-release.m.objcpp.plist (original)
+++ cfe/trunk/test/Analysis/Inputs/expected-plists/retain-release.m.objcpp.plist Fri Jan 11 15:35:17 2019
@@ -25923,9 +25923,9 @@
      </array>
      <key>depth</key><integer>0</integer>
      <key>extended_message</key>
-     <string>Call to function 'getViaParam' returns a Core Foundation object of type 'CFTypeRef' with a +0 retain count</string>
+     <string>Call to function 'getViaParam' writes a Core Foundation object of type 'CFTypeRef' with a +0 retain count into an out parameter 'outObj'</string>
      <key>message</key>
-     <string>Call to function 'getViaParam' returns a Core Foundation object of type 'CFTypeRef' with a +0 retain count</string>
+     <string>Call to function 'getViaParam' writes a Core Foundation object of type 'CFTypeRef' with a +0 retain count into an out parameter 'outObj'</string>
     </dict>
     <dict>
      <key>kind</key><string>control</string>
@@ -26079,9 +26079,9 @@
      </array>
      <key>depth</key><integer>0</integer>
      <key>extended_message</key>
-     <string>Call to function 'getViaParam2' returns a Core Foundation object of type 'CFTypeRef' with a +0 retain count</string>
+     <string>Call to function 'getViaParam2' writes a Core Foundation object of type 'CFTypeRef' with a +0 retain count into an out parameter 'outObj'</string>
      <key>message</key>
-     <string>Call to function 'getViaParam2' returns a Core Foundation object of type 'CFTypeRef' with a +0 retain count</string>
+     <string>Call to function 'getViaParam2' writes a Core Foundation object of type 'CFTypeRef' with a +0 retain count into an out parameter 'outObj'</string>
     </dict>
     <dict>
      <key>kind</key><string>control</string>

Modified: cfe/trunk/test/Analysis/osobject-retain-release.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/osobject-retain-release.cpp?rev=350982&r1=350981&r2=350982&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/osobject-retain-release.cpp (original)
+++ cfe/trunk/test/Analysis/osobject-retain-release.cpp Fri Jan 11 15:35:17 2019
@@ -5,6 +5,8 @@ struct OSMetaClass;
 
 #define OS_CONSUME __attribute__((os_consumed))
 #define OS_RETURNS_RETAINED __attribute__((os_returns_retained))
+#define OS_RETURNS_RETAINED_ON_ZERO __attribute__((os_returns_retained_on_zero))
+#define OS_RETURNS_RETAINED_ON_NONZERO __attribute__((os_returns_retained_on_non_zero))
 #define OS_RETURNS_NOT_RETAINED __attribute__((os_returns_not_retained))
 #define OS_CONSUMES_THIS __attribute__((os_consumes_this))
 
@@ -94,6 +96,182 @@ void escape(void *);
 void escape_with_source(void *p) {}
 bool coin();
 
+typedef int kern_return_t;
+typedef kern_return_t IOReturn;
+typedef kern_return_t OSReturn;
+#define kOSReturnSuccess  0
+#define kIOReturnSuccess 0
+
+bool write_into_out_param_on_success(OS_RETURNS_RETAINED OSObject **obj);
+
+void use_out_param() {
+  OSObject *obj;
+  if (write_into_out_param_on_success(&obj)) {
+    obj->release();
+  }
+}
+
+void use_out_param_leak() {
+  OSObject *obj;
+  write_into_out_param_on_success(&obj); // expected-note-re{{Call to function 'write_into_out_param_on_success' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'obj' (assuming the call returns non-zero){{$}}}}
+} // expected-warning{{Potential leak of an object stored into 'obj'}}
+ // expected-note at -1{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}}
+
+bool write_into_out_param_on_failure(OS_RETURNS_RETAINED_ON_ZERO OSObject **obj);
+
+void use_out_param_leak2() {
+  OSObject *obj;
+  write_into_out_param_on_failure(&obj); // expected-note-re{{Call to function 'write_into_out_param_on_failure' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'obj' (assuming the call returns zero){{$}}}}
+} // expected-warning{{Potential leak of an object stored into 'obj'}}
+ // expected-note at -1{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}}
+
+void use_out_param_on_failure() {
+  OSObject *obj;
+  if (!write_into_out_param_on_failure(&obj)) {
+    obj->release();
+  }
+}
+
+IOReturn write_into_out_param_on_nonzero(OS_RETURNS_RETAINED_ON_NONZERO OSObject **obj);
+
+void use_out_param_on_nonzero() {
+  OSObject *obj;
+  if (write_into_out_param_on_nonzero(&obj) != kIOReturnSuccess) {
+    obj->release();
+  }
+}
+
+bool write_into_two_out_params(OS_RETURNS_RETAINED OSObject **a,
+                               OS_RETURNS_RETAINED OSObject **b);
+
+void use_write_into_two_out_params() {
+  OSObject *obj1;
+  OSObject *obj2;
+  if (write_into_two_out_params(&obj1, &obj2)) {
+    obj1->release();
+    obj2->release();
+  }
+}
+
+void use_write_two_out_params_leak() {
+  OSObject *obj1;
+  OSObject *obj2;
+  write_into_two_out_params(&obj1, &obj2); // expected-note-re{{Call to function 'write_into_two_out_params' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'a' (assuming the call returns non-zero){{$}}}}
+                                           // expected-note-re at -1{{Call to function 'write_into_two_out_params' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'b' (assuming the call returns non-zero){{$}}}}
+} // expected-warning{{Potential leak of an object stored into 'obj1'}}
+  // expected-warning at -1{{Potential leak of an object stored into 'obj2'}}
+  // expected-note at -2{{Object leaked: object allocated and stored into 'obj1' is not referenced later in this execution path and has a retain count of +1}}
+  // expected-note at -3{{Object leaked: object allocated and stored into 'obj2' is not referenced later in this execution path and has a retain count of +1}}
+
+void always_write_into_two_out_params(OS_RETURNS_RETAINED OSObject **a,
+                                      OS_RETURNS_RETAINED OSObject **b);
+
+void use_always_write_into_two_out_params() {
+  OSObject *obj1;
+  OSObject *obj2;
+  always_write_into_two_out_params(&obj1, &obj2);
+  obj1->release();
+  obj2->release();
+}
+
+void use_always_write_into_two_out_params_leak() {
+  OSObject *obj1;
+  OSObject *obj2;
+  always_write_into_two_out_params(&obj1, &obj2); // expected-note-re{{Call to function 'always_write_into_two_out_params' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'a'{{$}}}}
+                                                  // expected-note-re at -1{{Call to function 'always_write_into_two_out_params' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'b'{{$}}}}
+} // expected-warning{{Potential leak of an object stored into 'obj1'}}
+  // expected-warning at -1{{Potential leak of an object stored into 'obj2'}}
+  // expected-note at -2{{Object leaked: object allocated and stored into 'obj1' is not referenced later in this execution path and has a retain count of +1}}
+  // expected-note at -3{{Object leaked: object allocated and stored into 'obj2' is not referenced later in this execution path and has a retain count of +1}}
+
+char *write_into_out_param_on_nonnull(OS_RETURNS_RETAINED OSObject **obj);
+
+void use_out_param_osreturn_on_nonnull() {
+  OSObject *obj;
+  if (write_into_out_param_on_nonnull(&obj)) {
+    obj->release();
+  }
+}
+
+void use_out_param_leak_osreturn_on_nonnull() {
+  OSObject *obj;
+  write_into_out_param_on_nonnull(&obj); // expected-note-re{{Call to function 'write_into_out_param_on_nonnull' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'obj' (assuming the call returns non-zero){{$}}}}
+} // expected-warning{{Potential leak of an object stored into 'obj'}}
+  // expected-note at -1{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}}
+
+bool write_optional_out_param(OS_RETURNS_RETAINED OSObject **obj=nullptr);
+
+void use_optional_out_param() {
+  if (write_optional_out_param()) {};
+}
+
+OSReturn write_into_out_param_on_os_success(OS_RETURNS_RETAINED OSObject **obj);
+
+void write_into_non_retained_out_param(OS_RETURNS_NOT_RETAINED OSObject **obj);
+
+void use_write_into_non_retained_out_param() {
+  OSObject *obj;
+  write_into_non_retained_out_param(&obj);
+}
+
+void use_write_into_non_retained_out_param_uaf() {
+  OSObject *obj;
+  write_into_non_retained_out_param(&obj); // expected-note-re{{Call to function 'write_into_non_retained_out_param' writes an OSObject of type 'OSObject' with a +0 retain count into an out parameter 'obj'{{$}}}}
+  obj->release(); // expected-warning{{Incorrect decrement of the reference count of an object that is not owned at this point by the caller}}
+                  // expected-note at -1{{Incorrect decrement of the reference count of an object that is not owned at this point by the caller}}
+}
+
+void always_write_into_out_param(OS_RETURNS_RETAINED OSObject **obj);
+
+void pass_through_out_param(OSObject **obj) {
+  always_write_into_out_param(obj);
+}
+
+void always_write_into_out_param_has_source(OS_RETURNS_RETAINED OSObject **obj) {
+  *obj = new OSObject; // expected-note{{Operator 'new' returns an OSObject of type 'OSObject' with a +1 retain count}}
+}
+
+void use_always_write_into_out_param_has_source_leak() {
+  OSObject *obj;
+  always_write_into_out_param_has_source(&obj); // expected-note{{Calling 'always_write_into_out_param_has_source'}}
+                                                // expected-note at -1{{Returning from 'always_write_into_out_param_has_source'}}
+} // expected-warning{{Potential leak of an object stored into 'obj'}}
+  // expected-note at -1{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}}
+
+void use_void_out_param_osreturn() {
+  OSObject *obj;
+  always_write_into_out_param(&obj);
+  obj->release();
+}
+
+void use_void_out_param_osreturn_leak() {
+  OSObject *obj;
+  always_write_into_out_param(&obj); // expected-note-re{{Call to function 'always_write_into_out_param' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'obj'{{$}}}}
+} // expected-warning{{Potential leak of an object stored into 'obj'}}
+  // expected-note at -1{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}}
+
+void use_out_param_osreturn() {
+  OSObject *obj;
+  if (write_into_out_param_on_os_success(&obj) == kOSReturnSuccess) {
+    obj->release();
+  }
+}
+
+void use_out_param_leak_osreturn() {
+  OSObject *obj;
+  write_into_out_param_on_os_success(&obj); // expected-note-re{{Call to function 'write_into_out_param_on_os_success' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'obj' (assuming the call returns zero){{$}}}}
+} // expected-warning{{Potential leak of an object stored into 'obj'}}
+  // expected-note at -1{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}}
+
+struct StructWithField {
+  OSObject *obj;
+
+  void initViaOutParamCall() { // no warning on writing into fields
+    always_write_into_out_param(&obj);
+  }
+
+};
+
 bool os_consume_violation_two_args(OS_CONSUME OSObject *obj, bool extra) {
   if (coin()) { // expected-note{{Assuming the condition is false}}
                 // expected-note at -1{{Taking false branch}}
@@ -431,4 +609,3 @@ typedef bool (^Blk)(OSObject *);
 void test_escape_to_unknown_block(Blk blk) {
   blk(getObject()); // no-crash
 }
-




More information about the cfe-commits mailing list