Index: cfe/trunk/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp =================================================================== --- cfe/trunk/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ cfe/trunk/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -269,10 +269,14 @@ /// 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 @@ llvm::SmallPtrSet FramesModifyingRegion; llvm::SmallPtrSet FramesModifyingCalculated; + using RegionVector = SmallVector; 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 VisitNode(const ExplodedNode *N, @@ -307,48 +313,69 @@ auto CallExitLoc = N->getLocationAs(); // 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(Call)) - if (const auto *IvarR = dyn_cast(RegionOfInterest)) - if (potentiallyWritesIntoIvar(Call->getRuntimeDefinition().getDecl(), - IvarR->getDecl()) && - !isRegionOfInterestModifiedInFrame(N)) - return notModifiedMemberDiagnostics( - Ctx, *CallExitLoc, Call, MC->getReceiverSVal().getAsRegion()); + if (const auto *MC = dyn_cast(Call)) { + if (const auto *IvarR = dyn_cast(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(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 parameters = getCallParameters(Call); for (unsigned I = 0; I < Call->getNumArgs() && I < parameters.size(); ++I) { - const ParmVarDecl *PVD = parameters[I]; + Optional 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 @@ } 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 + 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(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(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(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(IvarBind); + if (IvarRef->isFreeIvar()) + return true; + + const Expr *Base = IvarRef->getBase(); + if (const auto *ICE = dyn_cast(Base)) + Base = ICE->getSubExpr(); + + if (const auto *DRE = dyn_cast(Base)) + if (const auto *ID = dyn_cast(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 @@ RuntimeDefinition RD = Call->getRuntimeDefinition(); if (const auto *FD = dyn_cast_or_null(RD.getDecl())) return FD->parameters(); + if (const auto *MD = dyn_cast_or_null(RD.getDecl())) + return MD->parameters(); return Call->parameters(); } @@ -443,123 +546,105 @@ Ty->getPointeeType().getCanonicalType().isConstQualified(); } - /// \return Diagnostics piece for the member field not modified - /// in a given function. - std::shared_ptr notModifiedMemberDiagnostics( - const LocationContext *Ctx, - CallExitBegin &CallExitLoc, - CallEventRef<> Call, - const MemRegion *ArgRegion) { - const char *TopRegionName = isa(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(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 - 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(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 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(R) || isa(R) || - isa(R))) - return false; // Pattern-matching failed. - Subregions.push_back(R); + while (R != MatchedRegion) { + RegionSequence.push_back(R); R = cast(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(R) || isa(R)) + continue; + + if (Sep.empty()) + Sep = prettyPrintFirstElement(FirstElement, + /*MoreItemsExpected=*/true, + IndirectionLevel, os); + + os << Sep; + + const auto *DR = cast(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 0 && MoreItemsExpected) os << ")"; - for (auto I = Subregions.rbegin(), E = Subregions.rend(); I != E; ++I) { - if (const auto *FR = dyn_cast(*I)) { - os << Sep; - FR->getDecl()->getDeclName().print(os, PP); - Sep = "."; - } else if (const auto *IR = dyn_cast(*I)) { - os << "->"; - IR->getDecl()->getDeclName().print(os, PP); - Sep = "."; - } else if (isa(*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 @@ 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)); Index: cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.c =================================================================== --- cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.c +++ cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.c @@ -224,3 +224,23 @@ return s.x; // expected-warning{{Undefined or garbage value returned to caller}} // expected-note@-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@-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@-1{{Returning from 'initializeMaybeInStruct'}} + return z; // expected-warning{{Undefined or garbage value returned to caller}} + // expected-note@-1{{Undefined or garbage value returned to caller}} +} Index: cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.cpp =================================================================== --- cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.cpp +++ cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.cpp @@ -10,10 +10,10 @@ } 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@-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@-1{{Undefined or garbage value returned to caller}} } @@ -175,3 +175,161 @@ //expected-note@-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@-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@-1{{Returning from constructor for 'HasRef'}} + // expected-note@-2{{Calling 'maybeInitialize'}} + // expected-note@-3{{Returning from 'maybeInitialize'}} + return z; // expected-warning{{Undefined or garbage value returned to caller}} + // expected-note@-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@-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@-1{{Returning from constructor for 'HasRefToItself'}} + // expected-note@-2{{Calling 'maybeInitialize'}} + // expected-note@-3{{Returning from 'maybeInitialize'}} + return z; // expected-warning{{Undefined or garbage value returned to caller}} + // expected-note@-1{{Undefined or garbage value returned to caller}} +} + +//// + +void maybeInitialize(const HasRef *pA) { + if (coin()) // expected-note{{Assuming the condition is false}} + // expected-note@-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@-1{{Returning from constructor for 'HasRef'}} + maybeInitialize(&wrapper); // expected-note{{Calling 'maybeInitialize'}} + // expected-note@-1{{Returning from 'maybeInitialize'}} + return z; // expected-warning{{Undefined or garbage value returned to caller}} + // expected-note@-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@-1{{Returning from constructor for 'HasRef'}} +}; + +void maybeInitializeWithParent(const HasParentWithRef &pA) { + if (coin()) // expected-note{{Assuming the condition is false}} + // expected-note@-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@-1{{Returning from constructor for 'HasParentWithRef'}} + // expected-note@-2{{Calling 'maybeInitializeWithParent'}} + // expected-note@-3{{Returning from 'maybeInitializeWithParent'}} + return z; // expected-warning{{Undefined or garbage value returned to caller}} + // expected-note@-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@-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@-1{{Returning from constructor for 'HasRef'}} + maybeInitializeIndirectly(HasIndirectRef(r)); // expected-note{{Calling 'maybeInitializeIndirectly'}} + // expected-note@-1{{Returning from 'maybeInitializeIndirectly'}} + return z; // expected-warning{{Undefined or garbage value returned to caller}} + // expected-note@-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@-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@-1{{Returning from constructor for 'HasRef'}} + maybeInitializeIndirectly(HasIndirectRefByValue(r)); // expected-note{{Calling 'maybeInitializeIndirectly'}} + // expected-note@-1{{Returning from 'maybeInitializeIndirectly'}} + return z; // expected-warning{{Undefined or garbage value returned to caller}} + // expected-note@-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@-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@-1{{Returning from constructor for 'HasRef'}} + maybeInitializeIndirectly(HasIndirectPointerRef(&r)); // expected-note{{Calling 'maybeInitializeIndirectly'}} + // expected-note@-1{{Returning from 'maybeInitializeIndirectly'}} + return z; // expected-warning{{Undefined or garbage value returned to caller}} + // expected-note@-1{{Undefined or garbage value returned to caller}} +} Index: cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.m =================================================================== --- cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.m +++ cfe/trunk/test/Analysis/diagnostics/no-store-func-path-notes.m @@ -52,7 +52,6 @@ extern void expectNonNull(NSString * _Nonnull a); @interface A : NSObject -- (void) func; - (void) initAMaybe; @end @@ -66,7 +65,7 @@ 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@-1{{Returning from 'initAMaybe'}} @@ -74,4 +73,33 @@ // expected-note@-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@-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@-1{{Returning from 'initAMaybeWithExplicitSelf'}} + expectNonNull(a); // expected-warning{{nil passed to a callee that requires a non-null 1st parameter}} + // expected-note@-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@-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@-1{{Returning from 'initPassedAMaybe:'}} + expectNonNull(paramA->a); // expected-warning{{nil passed to a callee that requires a non-null 1st parameter}} + // expected-note@-1{{nil passed to a callee that requires a non-null 1st parameter}} + +} + @end