1 //=-- ExprEngineObjC.cpp - ExprEngine support for Objective-C ---*- C++ -*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // This file defines ExprEngine's support for Objective-C expressions.
11 //===----------------------------------------------------------------------===//
13 #include "clang/AST/StmtObjC.h"
14 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
15 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
16 #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
18 using namespace clang;
21 void ExprEngine::VisitLvalObjCIvarRefExpr(const ObjCIvarRefExpr *Ex,
23 ExplodedNodeSet &Dst) {
24 ProgramStateRef state = Pred->getState();
25 const LocationContext *LCtx = Pred->getLocationContext();
26 SVal baseVal = state->getSVal(Ex->getBase(), LCtx);
27 SVal location = state->getLValue(Ex->getDecl(), baseVal);
29 ExplodedNodeSet dstIvar;
30 StmtNodeBuilder Bldr(Pred, dstIvar, *currBldrCtx);
31 Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, location));
33 // Perform the post-condition check of the ObjCIvarRefExpr and store
34 // the created nodes in 'Dst'.
35 getCheckerManager().runCheckersForPostStmt(Dst, dstIvar, Ex, *this);
38 void ExprEngine::VisitObjCAtSynchronizedStmt(const ObjCAtSynchronizedStmt *S,
40 ExplodedNodeSet &Dst) {
41 getCheckerManager().runCheckersForPreStmt(Dst, Pred, S, *this);
44 /// Generate a node in \p Bldr for an iteration statement using ObjC
45 /// for-loop iterator.
46 static void populateObjCForDestinationSet(
47 ExplodedNodeSet &dstLocation, SValBuilder &svalBuilder,
48 const ObjCForCollectionStmt *S, const Stmt *elem, SVal elementV,
49 SymbolManager &SymMgr, const NodeBuilderContext *currBldrCtx,
50 StmtNodeBuilder &Bldr, bool hasElements) {
52 for (ExplodedNode *Pred : dstLocation) {
53 ProgramStateRef state = Pred->getState();
54 const LocationContext *LCtx = Pred->getLocationContext();
56 SVal hasElementsV = svalBuilder.makeTruthVal(hasElements);
58 // FIXME: S is not an expression. We should not be binding values to it.
59 ProgramStateRef nextState = state->BindExpr(S, LCtx, hasElementsV);
61 if (auto MV = elementV.getAs<loc::MemRegionVal>())
62 if (const auto *R = dyn_cast<TypedValueRegion>(MV->getRegion())) {
63 // FIXME: The proper thing to do is to really iterate over the
64 // container. We will do this with dispatch logic to the store.
65 // For now, just 'conjure' up a symbolic value.
66 QualType T = R->getValueType();
67 assert(Loc::isLocType(T));
71 SymbolRef Sym = SymMgr.conjureSymbol(elem, LCtx, T,
72 currBldrCtx->blockCount());
73 V = svalBuilder.makeLoc(Sym);
75 V = svalBuilder.makeIntVal(0, T);
78 nextState = nextState->bindLoc(elementV, V, LCtx);
81 Bldr.generateNode(S, Pred, nextState);
85 void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S,
87 ExplodedNodeSet &Dst) {
89 // ObjCForCollectionStmts are processed in two places. This method
90 // handles the case where an ObjCForCollectionStmt* occurs as one of the
91 // statements within a basic block. This transfer function does two things:
93 // (1) binds the next container value to 'element'. This creates a new
94 // node in the ExplodedGraph.
96 // (2) binds the value 0/1 to the ObjCForCollectionStmt* itself, indicating
97 // whether or not the container has any more elements. This value
98 // will be tested in ProcessBranch. We need to explicitly bind
99 // this value because a container can contain nil elements.
101 // FIXME: Eventually this logic should actually do dispatches to
102 // 'countByEnumeratingWithState:objects:count:' (NSFastEnumeration).
103 // This will require simulating a temporary NSFastEnumerationState, either
104 // through an SVal or through the use of MemRegions. This value can
105 // be affixed to the ObjCForCollectionStmt* instead of 0/1; when the loop
106 // terminates we reclaim the temporary (it goes out of scope) and we
107 // we can test if the SVal is 0 or if the MemRegion is null (depending
108 // on what approach we take).
110 // For now: simulate (1) by assigning either a symbol or nil if the
111 // container is empty. Thus this transfer function will by default
112 // result in state splitting.
114 const Stmt *elem = S->getElement();
115 const Stmt *collection = S->getCollection();
116 ProgramStateRef state = Pred->getState();
117 SVal collectionV = state->getSVal(collection, Pred->getLocationContext());
120 if (const auto *DS = dyn_cast<DeclStmt>(elem)) {
121 const VarDecl *elemD = cast<VarDecl>(DS->getSingleDecl());
122 assert(elemD->getInit() == nullptr);
123 elementV = state->getLValue(elemD, Pred->getLocationContext());
125 elementV = state->getSVal(elem, Pred->getLocationContext());
128 bool isContainerNull = state->isNull(collectionV).isConstrainedTrue();
130 ExplodedNodeSet dstLocation;
131 evalLocation(dstLocation, S, elem, Pred, state, elementV, false);
134 StmtNodeBuilder Bldr(Pred, Tmp, *currBldrCtx);
136 if (!isContainerNull)
137 populateObjCForDestinationSet(dstLocation, svalBuilder, S, elem, elementV,
138 SymMgr, currBldrCtx, Bldr,
139 /*hasElements=*/true);
141 populateObjCForDestinationSet(dstLocation, svalBuilder, S, elem, elementV,
142 SymMgr, currBldrCtx, Bldr,
143 /*hasElements=*/false);
145 // Finally, run any custom checkers.
146 // FIXME: Eventually all pre- and post-checks should live in VisitStmt.
147 getCheckerManager().runCheckersForPostStmt(Dst, Tmp, S, *this);
150 void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
152 ExplodedNodeSet &Dst) {
153 CallEventManager &CEMgr = getStateManager().getCallEventManager();
154 CallEventRef<ObjCMethodCall> Msg =
155 CEMgr.getObjCMethodCall(ME, Pred->getState(), Pred->getLocationContext());
157 // There are three cases for the receiver:
158 // (1) it is definitely nil,
159 // (2) it is definitely non-nil, and
160 // (3) we don't know.
162 // If the receiver is definitely nil, we skip the pre/post callbacks and
163 // instead call the ObjCMessageNil callbacks and return.
165 // If the receiver is definitely non-nil, we call the pre- callbacks,
166 // evaluate the call, and call the post- callbacks.
168 // If we don't know, we drop the potential nil flow and instead
169 // continue from the assumed non-nil state as in (2). This approach
170 // intentionally drops coverage in order to prevent false alarms
171 // in the following scenario:
173 // id result = [o someMethod]
176 // // <-- This program point should be unreachable because if o is nil
177 // // it must the case that result is nil as well.
181 // We could avoid dropping coverage by performing an explicit case split
182 // on each method call -- but this would get very expensive. An alternative
183 // would be to introduce lazy constraints.
184 // FIXME: This ignores many potential bugs (<rdar://problem/11733396>).
185 // Revisit once we have lazier constraints.
186 if (Msg->isInstanceMessage()) {
187 SVal recVal = Msg->getReceiverSVal();
188 if (!recVal.isUndef()) {
189 // Bifurcate the state into nil and non-nil ones.
190 DefinedOrUnknownSVal receiverVal =
191 recVal.castAs<DefinedOrUnknownSVal>();
192 ProgramStateRef State = Pred->getState();
194 ProgramStateRef notNilState, nilState;
195 std::tie(notNilState, nilState) = State->assume(receiverVal);
197 // Receiver is definitely nil, so run ObjCMessageNil callbacks and return.
198 if (nilState && !notNilState) {
199 ExplodedNodeSet dstNil;
200 StmtNodeBuilder Bldr(Pred, dstNil, *currBldrCtx);
201 bool HasTag = Pred->getLocation().getTag();
202 Pred = Bldr.generateNode(ME, Pred, nilState, nullptr,
203 ProgramPoint::PreStmtKind);
204 assert((Pred || HasTag) && "Should have cached out already!");
209 ExplodedNodeSet dstPostCheckers;
210 getCheckerManager().runCheckersForObjCMessageNil(dstPostCheckers, Pred,
212 for (auto I : dstPostCheckers)
213 finishArgumentConstruction(Dst, I, *Msg);
217 ExplodedNodeSet dstNonNil;
218 StmtNodeBuilder Bldr(Pred, dstNonNil, *currBldrCtx);
219 // Generate a transition to the non-nil state, dropping any potential
221 if (notNilState != State) {
222 bool HasTag = Pred->getLocation().getTag();
223 Pred = Bldr.generateNode(ME, Pred, notNilState);
224 assert((Pred || HasTag) && "Should have cached out already!");
232 // Handle the previsits checks.
233 ExplodedNodeSet dstPrevisit;
234 getCheckerManager().runCheckersForPreObjCMessage(dstPrevisit, Pred,
236 ExplodedNodeSet dstGenericPrevisit;
237 getCheckerManager().runCheckersForPreCall(dstGenericPrevisit, dstPrevisit,
240 // Proceed with evaluate the message expression.
241 ExplodedNodeSet dstEval;
242 StmtNodeBuilder Bldr(dstGenericPrevisit, dstEval, *currBldrCtx);
244 for (ExplodedNodeSet::iterator DI = dstGenericPrevisit.begin(),
245 DE = dstGenericPrevisit.end(); DI != DE; ++DI) {
246 ExplodedNode *Pred = *DI;
247 ProgramStateRef State = Pred->getState();
248 CallEventRef<ObjCMethodCall> UpdatedMsg = Msg.cloneWithState(State);
250 if (UpdatedMsg->isInstanceMessage()) {
251 SVal recVal = UpdatedMsg->getReceiverSVal();
252 if (!recVal.isUndef()) {
253 if (ObjCNoRet.isImplicitNoReturn(ME)) {
254 // If we raise an exception, for now treat it as a sink.
255 // Eventually we will want to handle exceptions properly.
256 Bldr.generateSink(ME, Pred, State);
261 // Check for special class methods that are known to not return
262 // and that we should treat as a sink.
263 if (ObjCNoRet.isImplicitNoReturn(ME)) {
264 // If we raise an exception, for now treat it as a sink.
265 // Eventually we will want to handle exceptions properly.
266 Bldr.generateSink(ME, Pred, Pred->getState());
271 defaultEvalCall(Bldr, Pred, *UpdatedMsg);
274 // If there were constructors called for object-type arguments, clean them up.
275 ExplodedNodeSet dstArgCleanup;
276 for (auto I : dstEval)
277 finishArgumentConstruction(dstArgCleanup, I, *Msg);
279 ExplodedNodeSet dstPostvisit;
280 getCheckerManager().runCheckersForPostCall(dstPostvisit, dstArgCleanup,
283 // Finally, perform the post-condition check of the ObjCMessageExpr and store
284 // the created nodes in 'Dst'.
285 getCheckerManager().runCheckersForPostObjCMessage(Dst, dstPostvisit,