void CallAndMessageChecker::HandleNilReceiver(CheckerContext &C, ProgramStateRef state, const ObjCMethodCall &Msg) const { ASTContext &Ctx = C.getASTContext(); static CheckerProgramPointTag Tag(this, "NilReceiver"); // Check the return type of the message expression. A message to nil will // return different values depending on the return type and the architecture. QualType RetTy = Msg.getResultType(); CanQualType CanRetTy = Ctx.getCanonicalType(RetTy); const LocationContext *LCtx = C.getLocationContext(); if (CanRetTy->isStructureOrClassType()) { // Structure returns are safe since the compiler zeroes them out. SVal V = C.getSValBuilder().makeZeroVal(RetTy); C.addTransition(state->BindExpr(Msg.getOriginExpr(), LCtx, V), &Tag); return; } // Other cases: check if sizeof(return type) > sizeof(void*) if (CanRetTy != Ctx.VoidTy && C.getLocationContext()->getParentMap() .isConsumedExpr(Msg.getOriginExpr())) { // Compute: sizeof(void *) and sizeof(return type) const uint64_t voidPtrSize = Ctx.getTypeSize(Ctx.VoidPtrTy); const uint64_t returnTypeSize = Ctx.getTypeSize(CanRetTy); if (CanRetTy.getTypePtr()->isReferenceType()|| (voidPtrSize < returnTypeSize && !(supportsNilWithFloatRet(Ctx.getTargetInfo().getTriple()) && (Ctx.FloatTy == CanRetTy || Ctx.DoubleTy == CanRetTy || Ctx.LongDoubleTy == CanRetTy || Ctx.LongLongTy == CanRetTy || Ctx.UnsignedLongLongTy == CanRetTy)))) { if (ExplodedNode *N = C.generateSink(state, nullptr, &Tag)) emitNilReceiverBug(C, Msg, N); return; } // Handle the safe cases where the return value is 0 if the // receiver is nil. // // FIXME: For now take the conservative approach that we only // return null values if we *know* that the receiver is nil. // This is because we can have surprises like: // // ... = [[NSScreens screens] objectAtIndex:0]; // // What can happen is that [... screens] could return nil, but // it most likely isn't nil. We should assume the semantics // of this case unless we have *a lot* more knowledge. // SVal V = C.getSValBuilder().makeZeroVal(RetTy); C.addTransition(state->BindExpr(Msg.getOriginExpr(), LCtx, V), &Tag); return; } C.addTransition(state); }
void ObjCNonNilReturnValueChecker::checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const { ProgramStateRef State = C.getState(); if (!Initialized) { ASTContext &Ctx = C.getASTContext(); ObjectAtIndex = GetUnarySelector("objectAtIndex", Ctx); ObjectAtIndexedSubscript = GetUnarySelector("objectAtIndexedSubscript", Ctx); NullSelector = GetNullarySelector("null", Ctx); } // Check the receiver type. if (const ObjCInterfaceDecl *Interface = M.getReceiverInterface()) { // Assume that object returned from '[self init]' or '[super init]' is not // 'nil' if we are processing an inlined function/method. // // A defensive callee will (and should) check if the object returned by // '[super init]' is 'nil' before doing it's own initialization. However, // since 'nil' is rarely returned in practice, we should not warn when the // caller to the defensive constructor uses the object in contexts where // 'nil' is not accepted. if (!C.inTopFrame() && M.getDecl() && M.getDecl()->getMethodFamily() == OMF_init && M.isReceiverSelfOrSuper()) { State = assumeExprIsNonNull(M.getOriginExpr(), State, C); } FoundationClass Cl = findKnownClass(Interface); // Objects returned from // [NSArray|NSOrderedSet]::[ObjectAtIndex|ObjectAtIndexedSubscript] // are never 'nil'. if (Cl == FC_NSArray || Cl == FC_NSOrderedSet) { Selector Sel = M.getSelector(); if (Sel == ObjectAtIndex || Sel == ObjectAtIndexedSubscript) { // Go ahead and assume the value is non-nil. State = assumeExprIsNonNull(M.getOriginExpr(), State, C); } } // Objects returned from [NSNull null] are not nil. if (Cl == FC_NSNull) { if (M.getSelector() == NullSelector) { // Go ahead and assume the value is non-nil. State = assumeExprIsNonNull(M.getOriginExpr(), State, C); } } } C.addTransition(State); }
void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C, const ObjCMethodCall &msg, ExplodedNode *N) const { if (!BT_msg_ret) BT_msg_ret.reset( new BuiltinBug(this, "Receiver in message expression is 'nil'")); const ObjCMessageExpr *ME = msg.getOriginExpr(); QualType ResTy = msg.getResultType(); SmallString<200> buf; llvm::raw_svector_ostream os(buf); os << "The receiver of message '"; ME->getSelector().print(os); os << "' is nil"; if (ResTy->isReferenceType()) { os << ", which results in forming a null reference"; } else { os << " and returns a value of type '"; msg.getResultType().print(os, C.getLangOpts()); os << "' that will be garbage"; } auto report = llvm::make_unique<BugReport>(*BT_msg_ret, os.str(), N); report->addRange(ME->getReceiverRange()); // FIXME: This won't track "self" in messages to super. if (const Expr *receiver = ME->getInstanceReceiver()) { bugreporter::trackNullOrUndefValue(N, receiver, *report); } C.emitReport(std::move(report)); }
void ObjCSelfInitChecker::checkPostObjCMessage(const ObjCMethodCall &Msg, CheckerContext &C) const { // When encountering a message that does initialization (init rule), // tag the return value so that we know later on that if self has this value // then it is properly initialized. // FIXME: A callback should disable checkers at the start of functions. if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>( C.getCurrentAnalysisDeclContext()->getDecl()))) return; if (isInitMessage(Msg)) { // Tag the return value as the result of an initializer. ProgramStateRef state = C.getState(); // FIXME this really should be context sensitive, where we record // the current stack frame (for IPA). Also, we need to clean this // value out when we return from this method. state = state->set<CalledInit>(true); SVal V = state->getSVal(Msg.getOriginExpr(), C.getLocationContext()); addSelfFlag(state, V, SelfFlag_InitRes, C); return; } // We don't check for an invalid 'self' in an obj-c message expression to cut // down false positives where logging functions get information from self // (like its class) or doing "invalidation" on self when the initialization // fails. }
void ObjCSuperDeallocChecker::checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const { ProgramStateRef State = C.getState(); SymbolRef ReceiverSymbol = M.getReceiverSVal().getAsSymbol(); if (!ReceiverSymbol) { diagnoseCallArguments(M, C); return; } bool AlreadyCalled = State->contains<CalledSuperDealloc>(ReceiverSymbol); if (!AlreadyCalled) return; StringRef Desc; if (isSuperDeallocMessage(M)) { Desc = "[super dealloc] should not be called multiple times"; } else { Desc = StringRef(); } reportUseAfterDealloc(ReceiverSymbol, Desc, M.getOriginExpr(), C); return; }
void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C, const ObjCMethodCall &msg, ExplodedNode *N) const { if (!BT_msg_ret) BT_msg_ret.reset( new BuiltinBug("Receiver in message expression is " "'nil' and returns a garbage value")); const ObjCMessageExpr *ME = msg.getOriginExpr(); SmallString<200> buf; llvm::raw_svector_ostream os(buf); os << "The receiver of message '" << ME->getSelector().getAsString() << "' is nil and returns a value of type '"; msg.getResultType().print(os, C.getLangOpts()); os << "' that will be garbage"; BugReport *report = new BugReport(*BT_msg_ret, os.str(), N); report->addRange(ME->getReceiverRange()); // FIXME: This won't track "self" in messages to super. if (const Expr *receiver = ME->getInstanceReceiver()) { report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, receiver, report)); } C.EmitReport(report); }
/// Returns true if M is a call to '[super dealloc]'. bool ObjCDeallocChecker::isSuperDeallocMessage( const ObjCMethodCall &M) const { if (M.getOriginExpr()->getReceiverKind() != ObjCMessageExpr::SuperInstance) return false; return M.getSelector() == DeallocSel; }
/// Emits a warning if the current context is -dealloc and DeallocedValue /// must not be directly dealloced in a -dealloc. Returns true if a diagnostic /// was emitted. bool ObjCDeallocChecker::diagnoseMistakenDealloc(SymbolRef DeallocedValue, const ObjCMethodCall &M, CheckerContext &C) const { // Find the property backing the instance variable that M // is dealloc'ing. const ObjCPropertyImplDecl *PropImpl = findPropertyOnDeallocatingInstance(DeallocedValue, C); if (!PropImpl) return false; if (getDeallocReleaseRequirement(PropImpl) != ReleaseRequirement::MustRelease) { return false; } ExplodedNode *ErrNode = C.generateErrorNode(); if (!ErrNode) return false; std::string Buf; llvm::raw_string_ostream OS(Buf); OS << "'" << *PropImpl->getPropertyIvarDecl() << "' should be released rather than deallocated"; std::unique_ptr<BugReport> BR( new BugReport(*MistakenDeallocBugType, OS.str(), ErrNode)); BR->addRange(M.getOriginExpr()->getSourceRange()); C.emitReport(std::move(BR)); return true; }
void ObjCLoopChecker::checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const { if (!M.isInstanceMessage()) return; const ObjCInterfaceDecl *ClassID = M.getReceiverInterface(); if (!ClassID) return; FoundationClass Class = findKnownClass(ClassID); if (Class != FC_NSDictionary && Class != FC_NSArray && Class != FC_NSSet) return; SymbolRef ContainerS = M.getReceiverSVal().getAsSymbol(); if (!ContainerS) return; // If we are processing a call to "count", get the symbolic value returned by // a call to "count" and add it to the map. if (!isCollectionCountMethod(M, C)) return; const Expr *MsgExpr = M.getOriginExpr(); SymbolRef CountS = C.getSVal(MsgExpr).getAsSymbol(); if (CountS) { ProgramStateRef State = C.getState(); C.getSymbolManager().addSymbolDependency(ContainerS, CountS); State = State->set<ContainerCountMap>(ContainerS, CountS); C.addTransition(State); } return; }
void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const { SVal recVal = msg.getReceiverSVal(); if (recVal.isUndef()) { if (ExplodedNode *N = C.generateSink()) { BugType *BT = 0; switch (msg.getMessageKind()) { case OCM_Message: if (!BT_msg_undef) BT_msg_undef.reset(new BuiltinBug("Receiver in message expression " "is an uninitialized value")); BT = BT_msg_undef.get(); break; case OCM_PropertyAccess: if (!BT_objc_prop_undef) BT_objc_prop_undef.reset(new BuiltinBug("Property access on an " "uninitialized object " "pointer")); BT = BT_objc_prop_undef.get(); break; case OCM_Subscript: if (!BT_objc_subscript_undef) BT_objc_subscript_undef.reset(new BuiltinBug("Subscript access on an " "uninitialized object " "pointer")); BT = BT_objc_subscript_undef.get(); break; } assert(BT && "Unknown message kind."); BugReport *R = new BugReport(*BT, BT->getName(), N); const ObjCMessageExpr *ME = msg.getOriginExpr(); R->addRange(ME->getReceiverRange()); // FIXME: getTrackNullOrUndefValueVisitor can't handle "super" yet. if (const Expr *ReceiverE = ME->getInstanceReceiver()) R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, ReceiverE, R)); C.EmitReport(R); } return; } else { // Bifurcate the state into nil and non-nil ones. DefinedOrUnknownSVal receiverVal = cast<DefinedOrUnknownSVal>(recVal); ProgramStateRef state = C.getState(); ProgramStateRef notNilState, nilState; llvm::tie(notNilState, nilState) = state->assume(receiverVal); // Handle receiver must be nil. if (nilState && !notNilState) { HandleNilReceiver(C, state, msg); return; } } }
bool ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const { if (M.getOriginExpr()->getReceiverKind() != ObjCMessageExpr::SuperInstance) return false; ASTContext &Ctx = M.getState()->getStateManager().getContext(); initIdentifierInfoAndSelectors(Ctx); return M.getSelector() == SELdealloc; }
void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const { SVal recVal = msg.getReceiverSVal(); if (recVal.isUndef()) { if (ExplodedNode *N = C.generateErrorNode()) { BugType *BT = nullptr; switch (msg.getMessageKind()) { case OCM_Message: if (!BT_msg_undef) BT_msg_undef.reset(new BuiltinBug(this, "Receiver in message expression " "is an uninitialized value")); BT = BT_msg_undef.get(); break; case OCM_PropertyAccess: if (!BT_objc_prop_undef) BT_objc_prop_undef.reset(new BuiltinBug( this, "Property access on an uninitialized object pointer")); BT = BT_objc_prop_undef.get(); break; case OCM_Subscript: if (!BT_objc_subscript_undef) BT_objc_subscript_undef.reset(new BuiltinBug( this, "Subscript access on an uninitialized object pointer")); BT = BT_objc_subscript_undef.get(); break; } assert(BT && "Unknown message kind."); auto R = llvm::make_unique<BugReport>(*BT, BT->getName(), N); const ObjCMessageExpr *ME = msg.getOriginExpr(); R->addRange(ME->getReceiverRange()); // FIXME: getTrackNullOrUndefValueVisitor can't handle "super" yet. if (const Expr *ReceiverE = ME->getInstanceReceiver()) bugreporter::trackExpressionValue(N, ReceiverE, *R); C.emitReport(std::move(R)); } return; } }
/// This callback is used to infer the types for Class variables. This info is /// used later to validate messages that sent to classes. Class variables are /// initialized with by invoking the 'class' method on a class. /// This method is also used to infer the type information for the return /// types. // TODO: right now it only tracks generic types. Extend this to track every // type in the DynamicTypeMap and diagnose type errors! void DynamicTypePropagation::checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const { const ObjCMessageExpr *MessageExpr = M.getOriginExpr(); SymbolRef RetSym = M.getReturnValue().getAsSymbol(); if (!RetSym) return; Selector Sel = MessageExpr->getSelector(); ProgramStateRef State = C.getState(); // Inference for class variables. // We are only interested in cases where the class method is invoked on a // class. This method is provided by the runtime and available on all classes. if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Class && Sel.getAsString() == "class") { QualType ReceiverType = MessageExpr->getClassReceiver(); const auto *ReceiverClassType = ReceiverType->getAs<ObjCObjectType>(); QualType ReceiverClassPointerType = C.getASTContext().getObjCObjectPointerType( QualType(ReceiverClassType, 0)); if (!ReceiverClassType->isSpecialized()) return; const auto *InferredType = ReceiverClassPointerType->getAs<ObjCObjectPointerType>(); assert(InferredType); State = State->set<MostSpecializedTypeArgsMap>(RetSym, InferredType); C.addTransition(State); return; } // Tracking for return types. SymbolRef RecSym = M.getReceiverSVal().getAsSymbol(); if (!RecSym) return; const ObjCObjectPointerType *const *TrackedType = State->get<MostSpecializedTypeArgsMap>(RecSym); if (!TrackedType) return; ASTContext &ASTCtxt = C.getASTContext(); const ObjCMethodDecl *Method = findMethodDecl(MessageExpr, *TrackedType, ASTCtxt); if (!Method) return; Optional<ArrayRef<QualType>> TypeArgs = (*TrackedType)->getObjCSubstitutions(Method->getDeclContext()); if (!TypeArgs) return; QualType ResultType = getReturnTypeForMethod(Method, *TypeArgs, *TrackedType, ASTCtxt); // The static type is the same as the deduced type. if (ResultType.isNull()) return; const MemRegion *RetRegion = M.getReturnValue().getAsRegion(); ExplodedNode *Pred = C.getPredecessor(); // When there is an entry available for the return symbol in DynamicTypeMap, // the call was inlined, and the information in the DynamicTypeMap is should // be precise. if (RetRegion && !State->get<DynamicTypeMap>(RetRegion)) { // TODO: we have duplicated information in DynamicTypeMap and // MostSpecializedTypeArgsMap. We should only store anything in the later if // the stored data differs from the one stored in the former. State = setDynamicTypeInfo(State, RetRegion, ResultType, /*CanBeSubclass=*/true); Pred = C.addTransition(State); } const auto *ResultPtrType = ResultType->getAs<ObjCObjectPointerType>(); if (!ResultPtrType || ResultPtrType->isUnspecialized()) return; // When the result is a specialized type and it is not tracked yet, track it // for the result symbol. if (!State->get<MostSpecializedTypeArgsMap>(RetSym)) { State = State->set<MostSpecializedTypeArgsMap>(RetSym, ResultPtrType); C.addTransition(State, Pred); } }
/// When the receiver has a tracked type, use that type to validate the /// argumments of the message expression and the return value. void DynamicTypePropagation::checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const { ProgramStateRef State = C.getState(); SymbolRef Sym = M.getReceiverSVal().getAsSymbol(); if (!Sym) return; const ObjCObjectPointerType *const *TrackedType = State->get<MostSpecializedTypeArgsMap>(Sym); if (!TrackedType) return; // Get the type arguments from tracked type and substitute type arguments // before do the semantic check. ASTContext &ASTCtxt = C.getASTContext(); const ObjCMessageExpr *MessageExpr = M.getOriginExpr(); const ObjCMethodDecl *Method = findMethodDecl(MessageExpr, *TrackedType, ASTCtxt); // It is possible to call non-existent methods in Obj-C. if (!Method) return; Optional<ArrayRef<QualType>> TypeArgs = (*TrackedType)->getObjCSubstitutions(Method->getDeclContext()); // This case might happen when there is an unspecialized override of a // specialized method. if (!TypeArgs) return; for (unsigned i = 0; i < Method->param_size(); i++) { const Expr *Arg = MessageExpr->getArg(i); const ParmVarDecl *Param = Method->parameters()[i]; QualType OrigParamType = Param->getType(); if (!isObjCTypeParamDependent(OrigParamType)) continue; QualType ParamType = OrigParamType.substObjCTypeArgs( ASTCtxt, *TypeArgs, ObjCSubstitutionContext::Parameter); // Check if it can be assigned const auto *ParamObjectPtrType = ParamType->getAs<ObjCObjectPointerType>(); const auto *ArgObjectPtrType = stripCastsAndSugar(Arg)->getType()->getAs<ObjCObjectPointerType>(); if (!ParamObjectPtrType || !ArgObjectPtrType) continue; // Check if we have more concrete tracked type that is not a super type of // the static argument type. SVal ArgSVal = M.getArgSVal(i); SymbolRef ArgSym = ArgSVal.getAsSymbol(); if (ArgSym) { const ObjCObjectPointerType *const *TrackedArgType = State->get<MostSpecializedTypeArgsMap>(ArgSym); if (TrackedArgType && ASTCtxt.canAssignObjCInterfaces(ArgObjectPtrType, *TrackedArgType)) { ArgObjectPtrType = *TrackedArgType; } } // Warn when argument is incompatible with the parameter. if (!ASTCtxt.canAssignObjCInterfaces(ParamObjectPtrType, ArgObjectPtrType)) { static CheckerProgramPointTag Tag(this, "ArgTypeMismatch"); ExplodedNode *N = C.addTransition(State, &Tag); reportGenericsBug(ArgObjectPtrType, ParamObjectPtrType, N, Sym, C, Arg); return; } } }
/// Emits a warning if the current context is -dealloc and ReleasedValue /// must not be directly released in a -dealloc. Returns true if a diagnostic /// was emitted. bool ObjCDeallocChecker::diagnoseExtraRelease(SymbolRef ReleasedValue, const ObjCMethodCall &M, CheckerContext &C) const { // Try to get the region from which the released value was loaded. // Note that, unlike diagnosing for missing releases, here we don't track // values that must not be released in the state. This is because even if // these values escape, it is still an error under the rules of MRR to // release them in -dealloc. const ObjCPropertyImplDecl *PropImpl = findPropertyOnDeallocatingInstance(ReleasedValue, C); if (!PropImpl) return false; // If the ivar belongs to a property that must not be released directly // in dealloc, emit a warning. if (getDeallocReleaseRequirement(PropImpl) != ReleaseRequirement::MustNotReleaseDirectly) { return false; } // If the property is readwrite but it shadows a read-only property in its // external interface, treat the property a read-only. If the outside // world cannot write to a property then the internal implementation is free // to make its own convention about whether the value is stored retained // or not. We look up the shadow here rather than in // getDeallocReleaseRequirement() because doing so can be expensive. const ObjCPropertyDecl *PropDecl = findShadowedPropertyDecl(PropImpl); if (PropDecl) { if (PropDecl->isReadOnly()) return false; } else { PropDecl = PropImpl->getPropertyDecl(); } ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); if (!ErrNode) return false; std::string Buf; llvm::raw_string_ostream OS(Buf); assert(PropDecl->getSetterKind() == ObjCPropertyDecl::Weak || (PropDecl->getSetterKind() == ObjCPropertyDecl::Assign && !PropDecl->isReadOnly()) || isReleasedByCIFilterDealloc(PropImpl) ); const ObjCImplDecl *Container = getContainingObjCImpl(C.getLocationContext()); OS << "The '" << *PropImpl->getPropertyIvarDecl() << "' ivar in '" << *Container; if (isReleasedByCIFilterDealloc(PropImpl)) { OS << "' will be released by '-[CIFilter dealloc]' but also released here"; } else { OS << "' was synthesized for "; if (PropDecl->getSetterKind() == ObjCPropertyDecl::Weak) OS << "a weak"; else OS << "an assign, readwrite"; OS << " property but was released in 'dealloc'"; } std::unique_ptr<BugReport> BR( new BugReport(*ExtraReleaseBugType, OS.str(), ErrNode)); BR->addRange(M.getOriginExpr()->getSourceRange()); C.emitReport(std::move(BR)); return true; }
/// Calculate the nullability of the result of a message expr based on the /// nullability of the receiver, the nullability of the return value, and the /// constraints. void NullabilityChecker::checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const { auto Decl = M.getDecl(); if (!Decl) return; QualType RetType = Decl->getReturnType(); if (!RetType->isAnyPointerType()) return; ProgramStateRef State = C.getState(); if (State->get<PreconditionViolated>()) return; const MemRegion *ReturnRegion = getTrackRegion(M.getReturnValue()); if (!ReturnRegion) return; auto Interface = Decl->getClassInterface(); auto Name = Interface ? Interface->getName() : ""; // In order to reduce the noise in the diagnostics generated by this checker, // some framework and programming style based heuristics are used. These // heuristics are for Cocoa APIs which have NS prefix. if (Name.startswith("NS")) { // Developers rely on dynamic invariants such as an item should be available // in a collection, or a collection is not empty often. Those invariants can // not be inferred by any static analysis tool. To not to bother the users // with too many false positives, every item retrieval function should be // ignored for collections. The instance methods of dictionaries in Cocoa // are either item retrieval related or not interesting nullability wise. // Using this fact, to keep the code easier to read just ignore the return // value of every instance method of dictionaries. if (M.isInstanceMessage() && Name.find("Dictionary") != StringRef::npos) { State = State->set<NullabilityMap>(ReturnRegion, Nullability::Contradicted); C.addTransition(State); return; } // For similar reasons ignore some methods of Cocoa arrays. StringRef FirstSelectorSlot = M.getSelector().getNameForSlot(0); if (Name.find("Array") != StringRef::npos && (FirstSelectorSlot == "firstObject" || FirstSelectorSlot == "lastObject")) { State = State->set<NullabilityMap>(ReturnRegion, Nullability::Contradicted); C.addTransition(State); return; } // Encoding related methods of string should not fail when lossless // encodings are used. Using lossless encodings is so frequent that ignoring // this class of methods reduced the emitted diagnostics by about 30% on // some projects (and all of that was false positives). if (Name.find("String") != StringRef::npos) { for (auto Param : M.parameters()) { if (Param->getName() == "encoding") { State = State->set<NullabilityMap>(ReturnRegion, Nullability::Contradicted); C.addTransition(State); return; } } } } const ObjCMessageExpr *Message = M.getOriginExpr(); Nullability SelfNullability = getReceiverNullability(M, State); const NullabilityState *NullabilityOfReturn = State->get<NullabilityMap>(ReturnRegion); if (NullabilityOfReturn) { // When we have a nullability tracked for the return value, the nullability // of the expression will be the most nullable of the receiver and the // return value. Nullability RetValTracked = NullabilityOfReturn->getValue(); Nullability ComputedNullab = getMostNullable(RetValTracked, SelfNullability); if (ComputedNullab != RetValTracked && ComputedNullab != Nullability::Unspecified) { const Stmt *NullabilitySource = ComputedNullab == RetValTracked ? NullabilityOfReturn->getNullabilitySource() : Message->getInstanceReceiver(); State = State->set<NullabilityMap>( ReturnRegion, NullabilityState(ComputedNullab, NullabilitySource)); C.addTransition(State); } return; } // No tracked information. Use static type information for return value. Nullability RetNullability = getNullabilityAnnotation(RetType); // Properties might be computed. For this reason the static analyzer creates a // new symbol each time an unknown property is read. To avoid false pozitives // do not treat unknown properties as nullable, even when they explicitly // marked nullable. if (M.getMessageKind() == OCM_PropertyAccess && !C.wasInlined) RetNullability = Nullability::Nonnull; Nullability ComputedNullab = getMostNullable(RetNullability, SelfNullability); if (ComputedNullab == Nullability::Nullable) { const Stmt *NullabilitySource = ComputedNullab == RetNullability ? Message : Message->getInstanceReceiver(); State = State->set<NullabilityMap>( ReturnRegion, NullabilityState(ComputedNullab, NullabilitySource)); C.addTransition(State); } }
void DanglingDelegateChecker::checkPostObjCMessage(const ObjCMethodCall &message, CheckerContext &context) const { // if the call was inlined, there is nothing else to do if (context.wasInlined) { return; } const ObjCMessageExpr *expr = message.getOriginExpr(); if (!expr) { assert(false); return; } // want an instance message to a non-null receiver const Expr *receiver = expr->getInstanceReceiver(); if (!receiver) { return; } if (isKnownToBeNil(message.getReceiverSVal(), context)) { // we are sure that the receiver is nil => abort mission return; } // retrieves the static facts on ivars const ObjCImplFacts *facts = getCurrentFacts(getCurrentTopClassInterface(context)); if (!facts) { return; } // First we try to detect the setting of an interesting property of self if (isObjCSelfExpr(receiver)) { const ObjCPropertyDecl *propDecl = matchObjCMessageWithPropertySetter(*expr); if (propDecl) { // To mitigate false positives, we verify only setters that have an unknown body. // (Setters with a known body are unfortunately not always inlined.) RuntimeDefinition runtimeDefinition = message.getRuntimeDefinition(); if (!runtimeDefinition.getDecl() || runtimeDefinition.getDecl()->isImplicit()) { verifyIvarDynamicStateAgainstStaticFacts(*expr, propDecl->getPropertyIvarDecl(), context); } // Next we deal with a possible assignment self.x = nil to prevent further warning const ObjCIvarDecl *ivarDecl = propDecl->getPropertyIvarDecl(); if (ivarDecl && facts->_ivarFactsMap.find(ivarDecl) != facts->_ivarFactsMap.end()) { SVal value = message.getArgSVal(0); if (isKnownToBeNil(value, context)) { // mark the corresponding ivar as cleared ProgramStateRef state = context.getState(); IvarDynamicState clearedStateForIvar(facts->_ivarFactsMap.at(ivarDecl)); state = state->set<IvarMap>(ivarDecl, clearedStateForIvar); context.addTransition(state); } } return; } } // What follows detects when we correctly clear the references inside an ivar // This is dual to FactFinder::VisitObjCMessageExpr StringRef selectorStr = expr->getSelector().getAsString(); // do we have a first argument equal to self? bool paramIsSelf = isObjCSelfExpr(getArgOfObjCMessageExpr(*expr, 0)); // is the receiver an interesting ivar? const ObjCIvarDecl *ivarDecl = matchIvarLValueExpression(*receiver); if (ivarDecl && facts->_ivarFactsMap.find(ivarDecl) != facts->_ivarFactsMap.end()) { // is this a release? if (selectorStr == "release" || selectorStr == "autorelease") { assert(!paramIsSelf); verifyIvarDynamicStateAgainstStaticFacts(*expr, ivarDecl, context); return; } // Prepare a new state to modify, associated with the receiver ProgramStateRef state = context.getState(); // Copy the previous state if present IvarDynamicState ivarState(state->get<IvarMap>(ivarDecl)); // is this a setter of an assign property? const ObjCPropertyDecl *propDecl = matchObjCMessageWithPropertySetter(*expr); if (propDecl) { if (propDecl->getSetterKind() != ObjCPropertyDecl::Assign) { return; } std::string propName = propDecl->getNameAsString(); if (!paramIsSelf) { // the property is now considered cleared ivarState._assignPropertyWasCleared.insert(propName); } else { // "unclear" the property since we just stored self again in it ivarState._assignPropertyWasCleared.erase(propName); } } else if (paramIsSelf && selectorStr.startswith("removeTarget:")) { ivarState._targetWasCleared = true; } else if (paramIsSelf && selectorStr.startswith("removeObserver:")) { ivarState._observerWasCleared = true; } else { // return to avoid transitioning to a new identical state return; } // write the new state state = state->set<IvarMap>(ivarDecl, ivarState); context.addTransition(state); return; } // TODO: is the receiver an interesting "observable singleton object"? // string receiverObjectName = matchObservableSingletonObject(receiver); // if (!receiverObjectName.empty()) { // // if (paramIsSelf && selectorStr.startswith("addObserver:")) { // // TODO // } // // } }