|
@@ -139,6 +139,74 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
|
|
CallEventRef<ObjCMethodCall> Msg =
|
|
CallEventRef<ObjCMethodCall> Msg =
|
|
CEMgr.getObjCMethodCall(ME, Pred->getState(), Pred->getLocationContext());
|
|
CEMgr.getObjCMethodCall(ME, Pred->getState(), Pred->getLocationContext());
|
|
|
|
|
|
|
|
+ // There are three cases for the receiver:
|
|
|
|
+ // (1) it is definitely nil,
|
|
|
|
+ // (2) it is definitely non-nil, and
|
|
|
|
+ // (3) we don't know.
|
|
|
|
+ //
|
|
|
|
+ // If the receiver is definitely nil, we skip the pre/post callbacks and
|
|
|
|
+ // instead call the ObjCMessageNil callbacks and return.
|
|
|
|
+ //
|
|
|
|
+ // If the receiver is definitely non-nil, we call the pre- callbacks,
|
|
|
|
+ // evaluate the call, and call the post- callbacks.
|
|
|
|
+ //
|
|
|
|
+ // If we don't know, we drop the potential nil flow and instead
|
|
|
|
+ // continue from the assumed non-nil state as in (2). This approach
|
|
|
|
+ // intentionally drops coverage in order to prevent false alarms
|
|
|
|
+ // in the following scenario:
|
|
|
|
+ //
|
|
|
|
+ // id result = [o someMethod]
|
|
|
|
+ // if (result) {
|
|
|
|
+ // if (!o) {
|
|
|
|
+ // // <-- This program point should be unreachable because if o is nil
|
|
|
|
+ // // it must the case that result is nil as well.
|
|
|
|
+ // }
|
|
|
|
+ // }
|
|
|
|
+ //
|
|
|
|
+ // We could avoid dropping coverage by performing an explicit case split
|
|
|
|
+ // on each method call -- but this would get very expensive. An alternative
|
|
|
|
+ // would be to introduce lazy constraints.
|
|
|
|
+ // FIXME: This ignores many potential bugs (<rdar://problem/11733396>).
|
|
|
|
+ // Revisit once we have lazier constraints.
|
|
|
|
+ if (Msg->isInstanceMessage()) {
|
|
|
|
+ SVal recVal = Msg->getReceiverSVal();
|
|
|
|
+ if (!recVal.isUndef()) {
|
|
|
|
+ // Bifurcate the state into nil and non-nil ones.
|
|
|
|
+ DefinedOrUnknownSVal receiverVal =
|
|
|
|
+ recVal.castAs<DefinedOrUnknownSVal>();
|
|
|
|
+ ProgramStateRef State = Pred->getState();
|
|
|
|
+
|
|
|
|
+ ProgramStateRef notNilState, nilState;
|
|
|
|
+ std::tie(notNilState, nilState) = State->assume(receiverVal);
|
|
|
|
+
|
|
|
|
+ // Receiver is definitely nil, so run ObjCMessageNil callbacks and return.
|
|
|
|
+ if (nilState && !notNilState) {
|
|
|
|
+ StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx);
|
|
|
|
+ bool HasTag = Pred->getLocation().getTag();
|
|
|
|
+ Pred = Bldr.generateNode(ME, Pred, nilState, nullptr,
|
|
|
|
+ ProgramPoint::PreStmtKind);
|
|
|
|
+ assert((Pred || HasTag) && "Should have cached out already!");
|
|
|
|
+ if (!Pred)
|
|
|
|
+ return;
|
|
|
|
+ getCheckerManager().runCheckersForObjCMessageNil(Dst, Pred,
|
|
|
|
+ *Msg, *this);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ExplodedNodeSet dstNonNil;
|
|
|
|
+ StmtNodeBuilder Bldr(Pred, dstNonNil, *currBldrCtx);
|
|
|
|
+ // Generate a transition to the non-nil state, dropping any potential
|
|
|
|
+ // nil flow.
|
|
|
|
+ if (notNilState != State) {
|
|
|
|
+ bool HasTag = Pred->getLocation().getTag();
|
|
|
|
+ Pred = Bldr.generateNode(ME, Pred, notNilState);
|
|
|
|
+ assert((Pred || HasTag) && "Should have cached out already!");
|
|
|
|
+ if (!Pred)
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
// Handle the previsits checks.
|
|
// Handle the previsits checks.
|
|
ExplodedNodeSet dstPrevisit;
|
|
ExplodedNodeSet dstPrevisit;
|
|
getCheckerManager().runCheckersForPreObjCMessage(dstPrevisit, Pred,
|
|
getCheckerManager().runCheckersForPreObjCMessage(dstPrevisit, Pred,
|
|
@@ -160,38 +228,12 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
|
|
if (UpdatedMsg->isInstanceMessage()) {
|
|
if (UpdatedMsg->isInstanceMessage()) {
|
|
SVal recVal = UpdatedMsg->getReceiverSVal();
|
|
SVal recVal = UpdatedMsg->getReceiverSVal();
|
|
if (!recVal.isUndef()) {
|
|
if (!recVal.isUndef()) {
|
|
- // Bifurcate the state into nil and non-nil ones.
|
|
|
|
- DefinedOrUnknownSVal receiverVal =
|
|
|
|
- recVal.castAs<DefinedOrUnknownSVal>();
|
|
|
|
-
|
|
|
|
- ProgramStateRef notNilState, nilState;
|
|
|
|
- std::tie(notNilState, nilState) = State->assume(receiverVal);
|
|
|
|
-
|
|
|
|
- // There are three cases: can be nil or non-nil, must be nil, must be
|
|
|
|
- // non-nil. We ignore must be nil, and merge the rest two into non-nil.
|
|
|
|
- // FIXME: This ignores many potential bugs (<rdar://problem/11733396>).
|
|
|
|
- // Revisit once we have lazier constraints.
|
|
|
|
- if (nilState && !notNilState) {
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Check if the "raise" message was sent.
|
|
|
|
- assert(notNilState);
|
|
|
|
if (ObjCNoRet.isImplicitNoReturn(ME)) {
|
|
if (ObjCNoRet.isImplicitNoReturn(ME)) {
|
|
// If we raise an exception, for now treat it as a sink.
|
|
// If we raise an exception, for now treat it as a sink.
|
|
// Eventually we will want to handle exceptions properly.
|
|
// Eventually we will want to handle exceptions properly.
|
|
Bldr.generateSink(ME, Pred, State);
|
|
Bldr.generateSink(ME, Pred, State);
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
-
|
|
|
|
- // Generate a transition to non-Nil state.
|
|
|
|
- if (notNilState != State) {
|
|
|
|
- bool HasTag = Pred->getLocation().getTag();
|
|
|
|
- Pred = Bldr.generateNode(ME, Pred, notNilState);
|
|
|
|
- assert((Pred || HasTag) && "Should have cached out already!");
|
|
|
|
- if (!Pred)
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
// Check for special class methods that are known to not return
|
|
// Check for special class methods that are known to not return
|