302 lines · cpp
1//=-- ExprEngineObjC.cpp - ExprEngine support for Objective-C ---*- C++ -*-===//2//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-exception6//7//===----------------------------------------------------------------------===//8//9// This file defines ExprEngine's support for Objective-C expressions.10//11//===----------------------------------------------------------------------===//12 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"17 18using namespace clang;19using namespace ento;20 21void ExprEngine::VisitLvalObjCIvarRefExpr(const ObjCIvarRefExpr *Ex,22 ExplodedNode *Pred,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);28 29 ExplodedNodeSet dstIvar;30 StmtNodeBuilder Bldr(Pred, dstIvar, *currBldrCtx);31 Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, location));32 33 // Perform the post-condition check of the ObjCIvarRefExpr and store34 // the created nodes in 'Dst'.35 getCheckerManager().runCheckersForPostStmt(Dst, dstIvar, Ex, *this);36}37 38void ExprEngine::VisitObjCAtSynchronizedStmt(const ObjCAtSynchronizedStmt *S,39 ExplodedNode *Pred,40 ExplodedNodeSet &Dst) {41 getCheckerManager().runCheckersForPreStmt(Dst, Pred, S, *this);42}43 44/// Generate a node in \p Bldr for an iteration statement using ObjC45/// for-loop iterator.46static void populateObjCForDestinationSet(47 ExplodedNodeSet &dstLocation, SValBuilder &svalBuilder,48 const ObjCForCollectionStmt *S, ConstCFGElementRef elem, SVal elementV,49 SymbolManager &SymMgr, const NodeBuilderContext *currBldrCtx,50 StmtNodeBuilder &Bldr, bool hasElements) {51 52 for (ExplodedNode *Pred : dstLocation) {53 ProgramStateRef state = Pred->getState();54 const LocationContext *LCtx = Pred->getLocationContext();55 56 ProgramStateRef nextState =57 ExprEngine::setWhetherHasMoreIteration(state, S, LCtx, hasElements);58 59 if (auto MV = elementV.getAs<loc::MemRegionVal>())60 if (const auto *R = dyn_cast<TypedValueRegion>(MV->getRegion())) {61 // FIXME: The proper thing to do is to really iterate over the62 // container. We will do this with dispatch logic to the store.63 // For now, just 'conjure' up a symbolic value.64 QualType T = R->getValueType();65 assert(Loc::isLocType(T));66 67 SVal V;68 if (hasElements) {69 SymbolRef Sym =70 SymMgr.conjureSymbol(elem, LCtx, T, currBldrCtx->blockCount());71 V = svalBuilder.makeLoc(Sym);72 } else {73 V = svalBuilder.makeIntVal(0, T);74 }75 76 nextState = nextState->bindLoc(elementV, V, LCtx);77 }78 79 Bldr.generateNode(S, Pred, nextState);80 }81}82 83void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S,84 ExplodedNode *Pred,85 ExplodedNodeSet &Dst) {86 87 // ObjCForCollectionStmts are processed in two places. This method88 // handles the case where an ObjCForCollectionStmt* occurs as one of the89 // statements within a basic block. This transfer function does two things:90 //91 // (1) binds the next container value to 'element'. This creates a new92 // node in the ExplodedGraph.93 //94 // (2) note whether the collection has any more elements (or in other words,95 // whether the loop has more iterations). This will be tested in96 // processBranch.97 //98 // FIXME: Eventually this logic should actually do dispatches to99 // 'countByEnumeratingWithState:objects:count:' (NSFastEnumeration).100 // This will require simulating a temporary NSFastEnumerationState, either101 // through an SVal or through the use of MemRegions. This value can102 // be affixed to the ObjCForCollectionStmt* instead of 0/1; when the loop103 // terminates we reclaim the temporary (it goes out of scope) and we104 // we can test if the SVal is 0 or if the MemRegion is null (depending105 // on what approach we take).106 //107 // For now: simulate (1) by assigning either a symbol or nil if the108 // container is empty. Thus this transfer function will by default109 // result in state splitting.110 111 const Stmt *elem = S->getElement();112 const Stmt *collection = S->getCollection();113 const ConstCFGElementRef &elemRef = getCFGElementRef();114 ProgramStateRef state = Pred->getState();115 SVal collectionV = state->getSVal(collection, Pred->getLocationContext());116 117 SVal elementV;118 if (const auto *DS = dyn_cast<DeclStmt>(elem)) {119 const VarDecl *elemD = cast<VarDecl>(DS->getSingleDecl());120 assert(elemD->getInit() == nullptr);121 elementV = state->getLValue(elemD, Pred->getLocationContext());122 } else {123 elementV = state->getSVal(elem, Pred->getLocationContext());124 }125 126 bool isContainerNull = state->isNull(collectionV).isConstrainedTrue();127 128 ExplodedNodeSet DstLocation; // states in `DstLocation` may differ from `Pred`129 evalLocation(DstLocation, S, elem, Pred, state, elementV, false);130 131 for (ExplodedNode *dstLocation : DstLocation) {132 ExplodedNodeSet DstLocationSingleton{dstLocation}, Tmp;133 StmtNodeBuilder Bldr(dstLocation, Tmp, *currBldrCtx);134 135 if (!isContainerNull)136 populateObjCForDestinationSet(DstLocationSingleton, svalBuilder, S,137 elemRef, elementV, SymMgr, currBldrCtx,138 Bldr,139 /*hasElements=*/true);140 141 populateObjCForDestinationSet(DstLocationSingleton, svalBuilder, S, elemRef,142 elementV, SymMgr, currBldrCtx, Bldr,143 /*hasElements=*/false);144 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);148 }149}150 151void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,152 ExplodedNode *Pred,153 ExplodedNodeSet &Dst) {154 CallEventManager &CEMgr = getStateManager().getCallEventManager();155 CallEventRef<ObjCMethodCall> Msg = CEMgr.getObjCMethodCall(156 ME, Pred->getState(), Pred->getLocationContext(), getCFGElementRef());157 158 // There are three cases for the receiver:159 // (1) it is definitely nil,160 // (2) it is definitely non-nil, and161 // (3) we don't know.162 //163 // If the receiver is definitely nil, we skip the pre/post callbacks and164 // instead call the ObjCMessageNil callbacks and return.165 //166 // If the receiver is definitely non-nil, we call the pre- callbacks,167 // evaluate the call, and call the post- callbacks.168 //169 // If we don't know, we drop the potential nil flow and instead170 // continue from the assumed non-nil state as in (2). This approach171 // intentionally drops coverage in order to prevent false alarms172 // in the following scenario:173 //174 // id result = [o someMethod]175 // if (result) {176 // if (!o) {177 // // <-- This program point should be unreachable because if o is nil178 // // it must the case that result is nil as well.179 // }180 // }181 //182 // However, it also loses coverage of the nil path prematurely,183 // leading to missed reports.184 //185 // It's possible to handle this by performing a state split on every call:186 // explore the state where the receiver is non-nil, and independently187 // explore the state where it's nil. But this is not only slow, but188 // completely unwarranted. The mere presence of the message syntax in the code189 // isn't sufficient evidence that nil is a realistic possibility.190 //191 // An ideal solution would be to add the following constraint that captures192 // both possibilities without splitting the state:193 //194 // ($x == 0) => ($y == 0) (1)195 //196 // where in our case '$x' is the receiver symbol, '$y' is the returned symbol,197 // and '=>' is logical implication. But RangeConstraintManager can't handle198 // such constraints yet, so for now we go with a simpler, more restrictive199 // constraint: $x != 0, from which (1) follows as a vacuous truth.200 if (Msg->isInstanceMessage()) {201 SVal recVal = Msg->getReceiverSVal();202 if (!recVal.isUndef()) {203 // Bifurcate the state into nil and non-nil ones.204 DefinedOrUnknownSVal receiverVal =205 recVal.castAs<DefinedOrUnknownSVal>();206 ProgramStateRef State = Pred->getState();207 208 ProgramStateRef notNilState, nilState;209 std::tie(notNilState, nilState) = State->assume(receiverVal);210 211 // Receiver is definitely nil, so run ObjCMessageNil callbacks and return.212 if (nilState && !notNilState) {213 ExplodedNodeSet dstNil;214 StmtNodeBuilder Bldr(Pred, dstNil, *currBldrCtx);215 bool HasTag = Pred->getLocation().getTag();216 Pred = Bldr.generateNode(ME, Pred, nilState, nullptr,217 ProgramPoint::PreStmtKind);218 assert((Pred || HasTag) && "Should have cached out already!");219 (void)HasTag;220 if (!Pred)221 return;222 223 ExplodedNodeSet dstPostCheckers;224 getCheckerManager().runCheckersForObjCMessageNil(dstPostCheckers, Pred,225 *Msg, *this);226 for (auto *I : dstPostCheckers)227 finishArgumentConstruction(Dst, I, *Msg);228 return;229 }230 231 ExplodedNodeSet dstNonNil;232 StmtNodeBuilder Bldr(Pred, dstNonNil, *currBldrCtx);233 // Generate a transition to the non-nil state, dropping any potential234 // nil flow.235 if (notNilState != State) {236 bool HasTag = Pred->getLocation().getTag();237 Pred = Bldr.generateNode(ME, Pred, notNilState);238 assert((Pred || HasTag) && "Should have cached out already!");239 (void)HasTag;240 if (!Pred)241 return;242 }243 }244 }245 246 // Handle the previsits checks.247 ExplodedNodeSet dstPrevisit;248 getCheckerManager().runCheckersForPreObjCMessage(dstPrevisit, Pred,249 *Msg, *this);250 ExplodedNodeSet dstGenericPrevisit;251 getCheckerManager().runCheckersForPreCall(dstGenericPrevisit, dstPrevisit,252 *Msg, *this);253 254 // Proceed with evaluate the message expression.255 ExplodedNodeSet dstEval;256 StmtNodeBuilder Bldr(dstGenericPrevisit, dstEval, *currBldrCtx);257 258 for (ExplodedNodeSet::iterator DI = dstGenericPrevisit.begin(),259 DE = dstGenericPrevisit.end(); DI != DE; ++DI) {260 ExplodedNode *Pred = *DI;261 ProgramStateRef State = Pred->getState();262 CallEventRef<ObjCMethodCall> UpdatedMsg = Msg.cloneWithState(State);263 264 if (UpdatedMsg->isInstanceMessage()) {265 SVal recVal = UpdatedMsg->getReceiverSVal();266 if (!recVal.isUndef()) {267 if (ObjCNoRet.isImplicitNoReturn(ME)) {268 // If we raise an exception, for now treat it as a sink.269 // Eventually we will want to handle exceptions properly.270 Bldr.generateSink(ME, Pred, State);271 continue;272 }273 }274 } else {275 // Check for special class methods that are known to not return276 // and that we should treat as a sink.277 if (ObjCNoRet.isImplicitNoReturn(ME)) {278 // If we raise an exception, for now treat it as a sink.279 // Eventually we will want to handle exceptions properly.280 Bldr.generateSink(ME, Pred, Pred->getState());281 continue;282 }283 }284 285 defaultEvalCall(Bldr, Pred, *UpdatedMsg);286 }287 288 // If there were constructors called for object-type arguments, clean them up.289 ExplodedNodeSet dstArgCleanup;290 for (auto *I : dstEval)291 finishArgumentConstruction(dstArgCleanup, I, *Msg);292 293 ExplodedNodeSet dstPostvisit;294 getCheckerManager().runCheckersForPostCall(dstPostvisit, dstArgCleanup,295 *Msg, *this);296 297 // Finally, perform the post-condition check of the ObjCMessageExpr and store298 // the created nodes in 'Dst'.299 getCheckerManager().runCheckersForPostObjCMessage(Dst, dstPostvisit,300 *Msg, *this);301}302