r338667 - [analyzer] Extend NoStoreFuncVisitor to follow fields.

George Karpenkov via cfe-commits cfe-commits at lists.llvm.org
Wed Aug 1 19:02:40 PDT 2018


Author: george.karpenkov
Date: Wed Aug  1 19:02:40 2018
New Revision: 338667

URL: http://llvm.org/viewvc/llvm-project?rev=338667&view=rev
Log:
[analyzer] Extend NoStoreFuncVisitor to follow fields.

rdar://39701823

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

Modified:
    cfe/trunk/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
    cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.c
    cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.cpp
    cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.m

Modified: cfe/trunk/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp?rev=338667&r1=338666&r2=338667&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp Wed Aug  1 19:02:40 2018
@@ -269,10 +269,14 @@ namespace {
 /// pointer dereference outside.
 class NoStoreFuncVisitor final : public BugReporterVisitor {
   const SubRegion *RegionOfInterest;
+  MemRegionManager &MmrMgr;
   const SourceManager &SM;
   const PrintingPolicy &PP;
-  static constexpr const char *DiagnosticsMsg =
-      "Returning without writing to '";
+
+  /// Recursion limit for dereferencing fields when looking for the
+  /// region of interest.
+  /// The limit of two indicates that we will dereference fields only once.
+  static const unsigned DEREFERENCE_LIMIT = 2;
 
   /// Frames writing into \c RegionOfInterest.
   /// This visitor generates a note only if a function does not write into
@@ -285,15 +289,17 @@ class NoStoreFuncVisitor final : public
   llvm::SmallPtrSet<const StackFrameContext *, 32> FramesModifyingRegion;
   llvm::SmallPtrSet<const StackFrameContext *, 32> FramesModifyingCalculated;
 
+  using RegionVector = SmallVector<const MemRegion *, 5>;
 public:
   NoStoreFuncVisitor(const SubRegion *R)
-      : RegionOfInterest(R),
-        SM(R->getMemRegionManager()->getContext().getSourceManager()),
-        PP(R->getMemRegionManager()->getContext().getPrintingPolicy()) {}
+      : RegionOfInterest(R), MmrMgr(*R->getMemRegionManager()),
+        SM(MmrMgr.getContext().getSourceManager()),
+        PP(MmrMgr.getContext().getPrintingPolicy()) {}
 
   void Profile(llvm::FoldingSetNodeID &ID) const override {
     static int Tag = 0;
     ID.AddPointer(&Tag);
+    ID.AddPointer(RegionOfInterest);
   }
 
   std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
@@ -307,48 +313,69 @@ public:
     auto CallExitLoc = N->getLocationAs<CallExitBegin>();
 
     // No diagnostic if region was modified inside the frame.
-    if (!CallExitLoc)
+    if (!CallExitLoc || isRegionOfInterestModifiedInFrame(N))
       return nullptr;
 
     CallEventRef<> Call =
         BRC.getStateManager().getCallEventManager().getCaller(SCtx, State);
 
+    if (SM.isInSystemHeader(Call->getDecl()->getSourceRange().getBegin()))
+      return nullptr;
+
     // Region of interest corresponds to an IVar, exiting a method
     // which could have written into that IVar, but did not.
-    if (const auto *MC = dyn_cast<ObjCMethodCall>(Call))
-      if (const auto *IvarR = dyn_cast<ObjCIvarRegion>(RegionOfInterest))
-        if (potentiallyWritesIntoIvar(Call->getRuntimeDefinition().getDecl(),
-                                      IvarR->getDecl()) &&
-            !isRegionOfInterestModifiedInFrame(N))
-          return notModifiedMemberDiagnostics(
-              Ctx, *CallExitLoc, Call, MC->getReceiverSVal().getAsRegion());
+    if (const auto *MC = dyn_cast<ObjCMethodCall>(Call)) {
+      if (const auto *IvarR = dyn_cast<ObjCIvarRegion>(RegionOfInterest)) {
+        const MemRegion *SelfRegion = MC->getReceiverSVal().getAsRegion();
+        if (RegionOfInterest->isSubRegionOf(SelfRegion) &&
+            potentiallyWritesIntoIvar(Call->getRuntimeDefinition().getDecl(),
+                                      IvarR->getDecl()))
+          return notModifiedDiagnostics(Ctx, *CallExitLoc, Call, {}, SelfRegion,
+                                        "self", /*FirstIsReferenceType=*/false,
+                                        1);
+      }
+    }
 
     if (const auto *CCall = dyn_cast<CXXConstructorCall>(Call)) {
       const MemRegion *ThisR = CCall->getCXXThisVal().getAsRegion();
       if (RegionOfInterest->isSubRegionOf(ThisR)
-          && !CCall->getDecl()->isImplicit()
-          && !isRegionOfInterestModifiedInFrame(N))
-        return notModifiedMemberDiagnostics(Ctx, *CallExitLoc, Call, ThisR);
+          && !CCall->getDecl()->isImplicit())
+        return notModifiedDiagnostics(Ctx, *CallExitLoc, Call, {}, ThisR,
+                                      "this",
+                                      /*FirstIsReferenceType=*/false, 1);
+
+      // Do not generate diagnostics for not modified parameters in
+      // constructors.
+      return nullptr;
     }
 
     ArrayRef<ParmVarDecl *> parameters = getCallParameters(Call);
     for (unsigned I = 0; I < Call->getNumArgs() && I < parameters.size(); ++I) {
-      const ParmVarDecl *PVD = parameters[I];
+      Optional<unsigned> AdjustedIdx = Call->getAdjustedParameterIndex(I);
+      if (!AdjustedIdx)
+        continue;
+      const ParmVarDecl *PVD = parameters[*AdjustedIdx];
       SVal S = Call->getArgSVal(I);
-      unsigned IndirectionLevel = 1;
+      bool ParamIsReferenceType = PVD->getType()->isReferenceType();
+      std::string ParamName = PVD->getNameAsString();
+
+      int IndirectionLevel = 1;
       QualType T = PVD->getType();
       while (const MemRegion *R = S.getAsRegion()) {
-        if (RegionOfInterest->isSubRegionOf(R)
-            && !isPointerToConst(PVD->getType())) {
-
-          if (isRegionOfInterestModifiedInFrame(N))
-            return nullptr;
+        if (RegionOfInterest->isSubRegionOf(R) && !isPointerToConst(T))
+          return notModifiedDiagnostics(Ctx, *CallExitLoc, Call, {}, R,
+                                        ParamName, ParamIsReferenceType,
+                                        IndirectionLevel);
 
-          return notModifiedParameterDiagnostics(
-              Ctx, *CallExitLoc, Call, PVD, R, IndirectionLevel);
-        }
         QualType PT = T->getPointeeType();
         if (PT.isNull() || PT->isVoidType()) break;
+
+        if (const RecordDecl *RD = PT->getAsRecordDecl())
+          if (auto P = findRegionOfInterestInRecord(RD, State, R))
+            return notModifiedDiagnostics(
+              Ctx, *CallExitLoc, Call, *P, RegionOfInterest, ParamName,
+              ParamIsReferenceType, IndirectionLevel);
+
         S = State->getSVal(R, PT);
         T = PT;
         IndirectionLevel++;
@@ -359,20 +386,94 @@ public:
   }
 
 private:
+  /// Attempts to find the region of interest in a given CXX decl,
+  /// by either following the base classes or fields.
+  /// Dereferences fields up to a given recursion limit.
+  /// Note that \p Vec is passed by value, leading to quadratic copying cost,
+  /// but it's OK in practice since its length is limited to DEREFERENCE_LIMIT.
+  /// \return A chain fields leading to the region of interest or None.
+  const Optional<RegionVector>
+  findRegionOfInterestInRecord(const RecordDecl *RD, ProgramStateRef State,
+                               const MemRegion *R,
+                               RegionVector Vec = {},
+                               int depth = 0) {
+
+    if (depth == DEREFERENCE_LIMIT) // Limit the recursion depth.
+      return None;
+
+    if (const auto *RDX = dyn_cast<CXXRecordDecl>(RD))
+      if (!RDX->hasDefinition())
+        return None;
+
+    // Recursively examine the base classes.
+    // Note that following base classes does not increase the recursion depth.
+    if (const auto *RDX = dyn_cast<CXXRecordDecl>(RD))
+      for (const auto II : RDX->bases())
+        if (const RecordDecl *RRD = II.getType()->getAsRecordDecl())
+          if (auto Out = findRegionOfInterestInRecord(RRD, State, R, Vec, depth))
+            return Out;
+
+    for (const FieldDecl *I : RD->fields()) {
+      QualType FT = I->getType();
+      const FieldRegion *FR = MmrMgr.getFieldRegion(I, cast<SubRegion>(R));
+      const SVal V = State->getSVal(FR);
+      const MemRegion *VR = V.getAsRegion();
+
+      RegionVector VecF = Vec;
+      VecF.push_back(FR);
+
+      if (RegionOfInterest == VR)
+        return VecF;
+
+      if (const RecordDecl *RRD = FT->getAsRecordDecl())
+        if (auto Out =
+                findRegionOfInterestInRecord(RRD, State, FR, VecF, depth + 1))
+          return Out;
+
+      QualType PT = FT->getPointeeType();
+      if (PT.isNull() || PT->isVoidType() || !VR) continue;
+
+      if (const RecordDecl *RRD = PT->getAsRecordDecl())
+        if (auto Out =
+                findRegionOfInterestInRecord(RRD, State, VR, VecF, depth + 1))
+          return Out;
+
+    }
+
+    return None;
+  }
 
   /// \return Whether the method declaration \p Parent
   /// syntactically has a binary operation writing into the ivar \p Ivar.
   bool potentiallyWritesIntoIvar(const Decl *Parent,
                                  const ObjCIvarDecl *Ivar) {
     using namespace ast_matchers;
-    if (!Parent || !Parent->getBody())
+    const char * IvarBind = "Ivar";
+    if (!Parent || !Parent->hasBody())
       return false;
     StatementMatcher WriteIntoIvarM = binaryOperator(
-        hasOperatorName("="), hasLHS(ignoringParenImpCasts(objcIvarRefExpr(
-                                  hasDeclaration(equalsNode(Ivar))))));
+        hasOperatorName("="),
+        hasLHS(ignoringParenImpCasts(
+            objcIvarRefExpr(hasDeclaration(equalsNode(Ivar))).bind(IvarBind))));
     StatementMatcher ParentM = stmt(hasDescendant(WriteIntoIvarM));
     auto Matches = match(ParentM, *Parent->getBody(), Parent->getASTContext());
-    return !Matches.empty();
+    for (BoundNodes &Match : Matches) {
+      auto IvarRef = Match.getNodeAs<ObjCIvarRefExpr>(IvarBind);
+      if (IvarRef->isFreeIvar())
+        return true;
+
+      const Expr *Base = IvarRef->getBase();
+      if (const auto *ICE = dyn_cast<ImplicitCastExpr>(Base))
+        Base = ICE->getSubExpr();
+
+      if (const auto *DRE = dyn_cast<DeclRefExpr>(Base))
+        if (const auto *ID = dyn_cast<ImplicitParamDecl>(DRE->getDecl()))
+          if (ID->getParameterKind() == ImplicitParamDecl::ObjCSelf)
+            return true;
+
+      return false;
+    }
+    return false;
   }
 
   /// Check and lazily calculate whether the region of interest is
@@ -433,6 +534,8 @@ private:
     RuntimeDefinition RD = Call->getRuntimeDefinition();
     if (const auto *FD = dyn_cast_or_null<FunctionDecl>(RD.getDecl()))
       return FD->parameters();
+    if (const auto *MD = dyn_cast_or_null<ObjCMethodDecl>(RD.getDecl()))
+      return MD->parameters();
 
     return Call->parameters();
   }
@@ -443,123 +546,105 @@ private:
            Ty->getPointeeType().getCanonicalType().isConstQualified();
   }
 
-  /// \return Diagnostics piece for the member field not modified
-  /// in a given function.
-  std::shared_ptr<PathDiagnosticPiece> notModifiedMemberDiagnostics(
-      const LocationContext *Ctx,
-      CallExitBegin &CallExitLoc,
-      CallEventRef<> Call,
-      const MemRegion *ArgRegion) {
-    const char *TopRegionName = isa<ObjCMethodCall>(Call) ? "self" : "this";
-    SmallString<256> sbuf;
-    llvm::raw_svector_ostream os(sbuf);
-    os << DiagnosticsMsg;
-    bool out = prettyPrintRegionName(TopRegionName, "->", /*IsReference=*/true,
-                                     /*IndirectionLevel=*/1, ArgRegion, os, PP);
-
-    // Return nothing if we have failed to pretty-print.
-    if (!out)
-      return nullptr;
-
-    os << "'";
-    PathDiagnosticLocation L =
-        getPathDiagnosticLocation(CallExitLoc.getReturnStmt(), SM, Ctx, Call);
-    return std::make_shared<PathDiagnosticEventPiece>(L, os.str());
-  }
-
-  /// \return Diagnostics piece for the parameter \p PVD not modified
-  /// in a given function.
-  /// \p IndirectionLevel How many times \c ArgRegion has to be dereferenced
-  /// before we get to the super region of \c RegionOfInterest
+  /// \return Diagnostics piece for region not modified in the current function.
   std::shared_ptr<PathDiagnosticPiece>
-  notModifiedParameterDiagnostics(const LocationContext *Ctx,
+  notModifiedDiagnostics(const LocationContext *Ctx,
                          CallExitBegin &CallExitLoc,
                          CallEventRef<> Call,
-                         const ParmVarDecl *PVD,
-                         const MemRegion *ArgRegion,
+                         RegionVector FieldChain,
+                         const MemRegion *MatchedRegion,
+                         StringRef FirstElement,
+                         bool FirstIsReferenceType,
                          unsigned IndirectionLevel) {
-    PathDiagnosticLocation L = getPathDiagnosticLocation(
-        CallExitLoc.getReturnStmt(), SM, Ctx, Call);
+
+    PathDiagnosticLocation L;
+    if (const ReturnStmt *RS = CallExitLoc.getReturnStmt()) {
+      L = PathDiagnosticLocation::createBegin(RS, SM, Ctx);
+    } else {
+      L = PathDiagnosticLocation(
+          Call->getRuntimeDefinition().getDecl()->getSourceRange().getEnd(),
+          SM);
+    }
+
     SmallString<256> sbuf;
     llvm::raw_svector_ostream os(sbuf);
-    os << DiagnosticsMsg;
-    bool IsReference = PVD->getType()->isReferenceType();
-    const char *Sep = IsReference && IndirectionLevel == 1 ? "." : "->";
-    bool Success = prettyPrintRegionName(
-        PVD->getQualifiedNameAsString().c_str(),
-        Sep, IsReference, IndirectionLevel, ArgRegion, os, PP);
-
-    // Print the parameter name if the pretty-printing has failed.
-    if (!Success)
-      PVD->printQualifiedName(os);
+    os << "Returning without writing to '";
+    prettyPrintRegionName(FirstElement, FirstIsReferenceType, MatchedRegion,
+                          FieldChain, IndirectionLevel, os);
+
     os << "'";
     return std::make_shared<PathDiagnosticEventPiece>(L, os.str());
   }
 
-  /// \return a path diagnostic location for the optionally
-  /// present return statement \p RS.
-  PathDiagnosticLocation getPathDiagnosticLocation(const ReturnStmt *RS,
-                                                   const SourceManager &SM,
-                                                   const LocationContext *Ctx,
-                                                   CallEventRef<> Call) {
-    if (RS)
-      return PathDiagnosticLocation::createBegin(RS, SM, Ctx);
-    return PathDiagnosticLocation(
-        Call->getRuntimeDefinition().getDecl()->getSourceRange().getEnd(), SM);
-  }
-
-  /// Pretty-print region \p ArgRegion starting from parent to \p os.
-  /// \return whether printing has succeeded
-  bool prettyPrintRegionName(StringRef TopRegionName,
-                             StringRef Sep,
-                             bool IsReference,
-                             int IndirectionLevel,
-                             const MemRegion *ArgRegion,
-                             llvm::raw_svector_ostream &os,
-                             const PrintingPolicy &PP) {
-    SmallVector<const MemRegion *, 5> Subregions;
+  /// Pretty-print region \p MatchedRegion to \p os.
+  void prettyPrintRegionName(StringRef FirstElement, bool FirstIsReferenceType,
+                             const MemRegion *MatchedRegion,
+                             RegionVector FieldChain, int IndirectionLevel,
+                             llvm::raw_svector_ostream &os) {
+
+    if (FirstIsReferenceType)
+      IndirectionLevel--;
+
+    RegionVector RegionSequence;
+
+    // Add the regions in the reverse order, then reverse the resulting array.
+    assert(RegionOfInterest->isSubRegionOf(MatchedRegion));
     const MemRegion *R = RegionOfInterest;
-    while (R != ArgRegion) {
-      if (!(isa<FieldRegion>(R) || isa<CXXBaseObjectRegion>(R) ||
-            isa<ObjCIvarRegion>(R)))
-        return false; // Pattern-matching failed.
-      Subregions.push_back(R);
+    while (R != MatchedRegion) {
+      RegionSequence.push_back(R);
       R = cast<SubRegion>(R)->getSuperRegion();
     }
-    bool IndirectReference = !Subregions.empty();
+    std::reverse(RegionSequence.begin(), RegionSequence.end());
+    RegionSequence.append(FieldChain.begin(), FieldChain.end());
 
-    if (IndirectReference)
-      IndirectionLevel--; // Due to "->" symbol.
+    StringRef Sep;
+    for (const MemRegion *R : RegionSequence) {
 
-    if (IsReference)
-      IndirectionLevel--; // Due to reference semantics.
+      // Just keep going up to the base region.
+      if (isa<CXXBaseObjectRegion>(R) || isa<CXXTempObjectRegion>(R))
+        continue;
+
+      if (Sep.empty())
+        Sep = prettyPrintFirstElement(FirstElement,
+                                      /*MoreItemsExpected=*/true,
+                                      IndirectionLevel, os);
+
+      os << Sep;
+
+      const auto *DR = cast<DeclRegion>(R);
+      Sep = DR->getValueType()->isAnyPointerType() ? "->" : ".";
+      DR->getDecl()->getDeclName().print(os, PP);
+    }
+
+    if (Sep.empty())
+      prettyPrintFirstElement(FirstElement,
+                              /*MoreItemsExpected=*/false, IndirectionLevel,
+                              os);
+  }
 
-    bool ShouldSurround = IndirectReference && IndirectionLevel > 0;
+  /// Print first item in the chain, return new separator.
+  StringRef prettyPrintFirstElement(StringRef FirstElement,
+                       bool MoreItemsExpected,
+                       int IndirectionLevel,
+                       llvm::raw_svector_ostream &os) {
+    StringRef Out = ".";
 
-    if (ShouldSurround)
+    if (IndirectionLevel > 0 && MoreItemsExpected) {
+      IndirectionLevel--;
+      Out = "->";
+    }
+
+    if (IndirectionLevel > 0 && MoreItemsExpected)
       os << "(";
-    for (int i = 0; i < IndirectionLevel; i++)
+
+    for (int i=0; i<IndirectionLevel; i++)
       os << "*";
-    os << TopRegionName;
-    if (ShouldSurround)
+    os << FirstElement;
+
+    if (IndirectionLevel > 0 && MoreItemsExpected)
       os << ")";
 
-    for (auto I = Subregions.rbegin(), E = Subregions.rend(); I != E; ++I) {
-      if (const auto *FR = dyn_cast<FieldRegion>(*I)) {
-        os << Sep;
-        FR->getDecl()->getDeclName().print(os, PP);
-        Sep = ".";
-      } else if (const auto *IR = dyn_cast<ObjCIvarRegion>(*I)) {
-        os << "->";
-        IR->getDecl()->getDeclName().print(os, PP);
-        Sep = ".";
-      } else if (isa<CXXBaseObjectRegion>(*I)) {
-        continue; // Just keep going up to the base region.
-      } else {
-        llvm_unreachable("Previous check has missed an unexpected region");
-      }
-    }
-    return true;
+    return Out;
   }
 };
 
@@ -1595,6 +1680,7 @@ bool bugreporter::trackNullOrUndefValue(
         LVNode->getSVal(Inner).getAsRegion();
 
     if (R) {
+      ProgramStateRef S = N->getState();
 
       // Mark both the variable region and its contents as interesting.
       SVal V = LVState->getRawSVal(loc::MemRegionVal(R));

Modified: cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.c
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.c?rev=338667&r1=338666&r2=338667&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.c (original)
+++ cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.c Wed Aug  1 19:02:40 2018
@@ -224,3 +224,23 @@ int useindirectingstruct() {
   return s.x; // expected-warning{{Undefined or garbage value returned to caller}}
               // expected-note at -1{{Undefined or garbage value returned to caller}}
 }
+
+typedef struct {
+  int *x;
+} D;
+
+void initializeMaybeInStruct(D* pD) {
+  if (coin()) // expected-note{{Assuming the condition is false}}
+              // expected-note at -1{{Taking false branch}}
+    *pD->x = 120;
+} // expected-note{{Returning without writing to 'pD->x'}}
+
+int useInitializeMaybeInStruct() {
+  int z; // expected-note{{'z' declared without an initial value}}
+  D d;
+  d.x = &z;
+  initializeMaybeInStruct(&d); // expected-note{{Calling 'initializeMaybeInStruct'}}
+                               // expected-note at -1{{Returning from 'initializeMaybeInStruct'}}
+  return z; // expected-warning{{Undefined or garbage value returned to caller}}
+            // expected-note at -1{{Undefined or garbage value returned to caller}}
+}

Modified: cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.cpp?rev=338667&r1=338666&r2=338667&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.cpp (original)
+++ cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.cpp Wed Aug  1 19:02:40 2018
@@ -10,10 +10,10 @@ int initializer1(int &p, int x) {
 }
 
 int param_not_initialized_by_func() {
-  int p;                        // expected-note {{'p' declared without an initial value}}
-  int out = initializer1(p, 0); // expected-note{{Calling 'initializer1'}}
+  int outP;                        // expected-note {{'outP' declared without an initial value}}
+  int out = initializer1(outP, 0); // expected-note{{Calling 'initializer1'}}
                                 // expected-note at -1{{Returning from 'initializer1'}}
-  return p;                     // expected-note{{Undefined or garbage value returned to caller}}
+  return outP;                     // expected-note{{Undefined or garbage value returned to caller}}
                                 // expected-warning at -1{{Undefined or garbage value returned to caller}}
 }
 
@@ -175,3 +175,161 @@ void rdar40335545() {
                           //expected-note at -1{{}}
     (void)useLocal;
 }
+
+////////
+
+struct HasRef {
+  int &a;
+  HasRef(int &a) : a(a) {}
+};
+
+
+void maybeInitialize(const HasRef &&pA) {
+  if (coin()) // expected-note{{Assuming the condition is false}}
+              // expected-note at -1{{Taking false branch}}
+    pA.a = 120;
+} // expected-note{{Returning without writing to 'pA.a'}}
+
+int useMaybeInitializerWritingIntoField() {
+  int z; // expected-note{{'z' declared without an initial value}}
+  maybeInitialize(HasRef(z)); // expected-note{{Calling constructor for 'HasRef'}}
+                              // expected-note at -1{{Returning from constructor for 'HasRef'}}
+                              // expected-note at -2{{Calling 'maybeInitialize'}}
+                              // expected-note at -3{{Returning from 'maybeInitialize'}}
+  return z; // expected-warning{{Undefined or garbage value returned to caller}}
+            // expected-note at -1{{Undefined or garbage value returned to caller}}
+}
+
+////////
+
+struct HasRefToItself {
+  HasRefToItself &Ref; // no infinite loop
+  int &z;
+  HasRefToItself(int &z) : Ref(*this), z(z) {}
+};
+
+void maybeInitialize(const HasRefToItself &&pA) {
+  if (coin()) // expected-note{{Assuming the condition is false}}
+              // expected-note at -1{{Taking false branch}}
+    pA.z = 120;
+} // expected-note{{Returning without writing to 'pA.Ref.z'}}
+
+int useMaybeInitializerWritingIntoFieldWithRefToItself() {
+  int z; // expected-note{{'z' declared without an initial value}}
+  maybeInitialize(HasRefToItself(z)); // expected-note{{Calling constructor for 'HasRefToItself'}}
+                              // expected-note at -1{{Returning from constructor for 'HasRefToItself'}}
+                              // expected-note at -2{{Calling 'maybeInitialize'}}
+                              // expected-note at -3{{Returning from 'maybeInitialize'}}
+  return z; // expected-warning{{Undefined or garbage value returned to caller}}
+            // expected-note at -1{{Undefined or garbage value returned to caller}}
+}
+
+////
+
+void maybeInitialize(const HasRef *pA) {
+  if (coin()) // expected-note{{Assuming the condition is false}}
+              // expected-note at -1{{Taking false branch}}
+    pA->a = 120;
+} // expected-note{{Returning without writing to 'pA->a'}}
+
+int useMaybeInitializerStructByPointer() {
+  int z; // expected-note{{'z' declared without an initial value}}
+  HasRef wrapper(z); // expected-note{{Calling constructor for 'HasRef'}}
+                     // expected-note at -1{{Returning from constructor for 'HasRef'}}
+  maybeInitialize(&wrapper); // expected-note{{Calling 'maybeInitialize'}}
+                             // expected-note at -1{{Returning from 'maybeInitialize'}}
+  return z; // expected-warning{{Undefined or garbage value returned to caller}}
+            // expected-note at -1{{Undefined or garbage value returned to caller}}
+}
+
+////////
+
+struct HasParentWithRef : public HasRef {
+  HasParentWithRef(int &a) : HasRef(a) {} // expected-note{{Calling constructor for 'HasRef'}}
+                                          // expected-note at -1{{Returning from constructor for 'HasRef'}}
+};
+
+void maybeInitializeWithParent(const HasParentWithRef &pA) {
+  if (coin()) // expected-note{{Assuming the condition is false}}
+              // expected-note at -1{{Taking false branch}}
+    pA.a = 120;
+} // expected-note{{Returning without writing to 'pA.a'}}
+
+int useMaybeInitializerWritingIntoParentField() {
+  int z; // expected-note{{'z' declared without an initial value}}
+  maybeInitializeWithParent(HasParentWithRef(z)); // expected-note{{Calling constructor for 'HasParentWithRef'}}
+                              // expected-note at -1{{Returning from constructor for 'HasParentWithRef'}}
+                              // expected-note at -2{{Calling 'maybeInitializeWithParent'}}
+                              // expected-note at -3{{Returning from 'maybeInitializeWithParent'}}
+  return z; // expected-warning{{Undefined or garbage value returned to caller}}
+            // expected-note at -1{{Undefined or garbage value returned to caller}}
+}
+
+////////
+
+struct HasIndirectRef {
+  HasRef &Ref;
+  HasIndirectRef(HasRef &Ref) : Ref(Ref) {}
+};
+
+void maybeInitializeIndirectly(const HasIndirectRef &pA) {
+  if (coin()) // expected-note{{Assuming the condition is false}}
+              // expected-note at -1{{Taking false branch}}
+    pA.Ref.a = 120;
+} // expected-note{{Returning without writing to 'pA.Ref.a'}}
+
+int useMaybeInitializeIndirectly() {
+  int z; // expected-note{{'z' declared without an initial value}}
+  HasRef r(z); // expected-note{{Calling constructor for 'HasRef'}}
+               // expected-note at -1{{Returning from constructor for 'HasRef'}}
+  maybeInitializeIndirectly(HasIndirectRef(r)); // expected-note{{Calling 'maybeInitializeIndirectly'}}
+                                                // expected-note at -1{{Returning from 'maybeInitializeIndirectly'}}
+  return z; // expected-warning{{Undefined or garbage value returned to caller}}
+            // expected-note at -1{{Undefined or garbage value returned to caller}}
+}
+
+////////
+
+struct HasIndirectRefByValue {
+  HasRef Ref;
+  HasIndirectRefByValue(HasRef Ref) : Ref(Ref) {}
+};
+
+void maybeInitializeIndirectly(const HasIndirectRefByValue &pA) {
+  if (coin()) // expected-note{{Assuming the condition is false}}
+              // expected-note at -1{{Taking false branch}}
+    pA.Ref.a = 120;
+} // expected-note{{Returning without writing to 'pA.Ref.a'}}
+
+int useMaybeInitializeIndirectlyIndirectRefByValue() {
+  int z; // expected-note{{'z' declared without an initial value}}
+  HasRef r(z); // expected-note{{Calling constructor for 'HasRef'}}
+               // expected-note at -1{{Returning from constructor for 'HasRef'}}
+  maybeInitializeIndirectly(HasIndirectRefByValue(r)); // expected-note{{Calling 'maybeInitializeIndirectly'}}
+                                                // expected-note at -1{{Returning from 'maybeInitializeIndirectly'}}
+  return z; // expected-warning{{Undefined or garbage value returned to caller}}
+            // expected-note at -1{{Undefined or garbage value returned to caller}}
+}
+
+////////
+
+struct HasIndirectPointerRef {
+  HasRef *Ref;
+  HasIndirectPointerRef(HasRef *Ref) : Ref(Ref) {}
+};
+
+void maybeInitializeIndirectly(const HasIndirectPointerRef &pA) {
+  if (coin()) // expected-note{{Assuming the condition is false}}
+              // expected-note at -1{{Taking false branch}}
+    pA.Ref->a = 120;
+} // expected-note{{Returning without writing to 'pA.Ref->a'}}
+
+int useMaybeInitializeIndirectlyWithPointer() {
+  int z; // expected-note{{'z' declared without an initial value}}
+  HasRef r(z); // expected-note{{Calling constructor for 'HasRef'}}
+               // expected-note at -1{{Returning from constructor for 'HasRef'}}
+  maybeInitializeIndirectly(HasIndirectPointerRef(&r)); // expected-note{{Calling 'maybeInitializeIndirectly'}}
+                                                // expected-note at -1{{Returning from 'maybeInitializeIndirectly'}}
+  return z; // expected-warning{{Undefined or garbage value returned to caller}}
+            // expected-note at -1{{Undefined or garbage value returned to caller}}
+}

Modified: cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.m
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.m?rev=338667&r1=338666&r2=338667&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.m (original)
+++ cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.m Wed Aug  1 19:02:40 2018
@@ -52,7 +52,6 @@ int initFromBlock() {
 extern void expectNonNull(NSString * _Nonnull a);
 
 @interface A : NSObject
-- (void) func;
 - (void) initAMaybe;
 @end
 
@@ -66,7 +65,7 @@ extern void expectNonNull(NSString * _No
     a = @"string";
 } // expected-note{{Returning without writing to 'self->a'}}
 
-- (void) func {
+- (void) passNullToNonnull {
   a = nil; // expected-note{{nil object reference stored to 'a'}}
   [self initAMaybe]; // expected-note{{Calling 'initAMaybe'}}
                      // expected-note at -1{{Returning from 'initAMaybe'}}
@@ -74,4 +73,33 @@ extern void expectNonNull(NSString * _No
                     // expected-note at -1{{nil passed to a callee that requires a non-null 1st parameter}}
 }
 
+- (void) initAMaybeWithExplicitSelf {
+  if (coin()) // expected-note{{Assuming the condition is false}}
+              // expected-note at -1{{Taking false branch}}
+    self->a = @"string";
+} // expected-note{{Returning without writing to 'self->a'}}
+
+- (void) passNullToNonnullWithExplicitSelf {
+  self->a = nil; // expected-note{{nil object reference stored to 'a'}}
+  [self initAMaybeWithExplicitSelf]; // expected-note{{Calling 'initAMaybeWithExplicitSelf'}}
+                     // expected-note at -1{{Returning from 'initAMaybeWithExplicitSelf'}}
+  expectNonNull(a); // expected-warning{{nil passed to a callee that requires a non-null 1st parameter}}
+                    // expected-note at -1{{nil passed to a callee that requires a non-null 1st parameter}}
+}
+
+- (void) initPassedAMaybe:(A *) param {
+  if (coin()) // expected-note{{Assuming the condition is false}}
+              // expected-note at -1{{Taking false branch}}
+    param->a = @"string";
+} // expected-note{{Returning without writing to 'param->a'}}
+
+- (void) useInitPassedAMaybe:(A *) paramA {
+  paramA->a = nil; // expected-note{{nil object reference stored to 'a'}}
+  [self initPassedAMaybe:paramA]; // expected-note{{Calling 'initPassedAMaybe:'}}
+                                  // expected-note at -1{{Returning from 'initPassedAMaybe:'}}
+  expectNonNull(paramA->a); // expected-warning{{nil passed to a callee that requires a non-null 1st parameter}}
+                            // expected-note at -1{{nil passed to a callee that requires a non-null 1st parameter}}
+
+}
+
 @end




More information about the cfe-commits mailing list