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; } } }
void NilArgChecker::warnIfNilArg(CheckerContext &C, const ObjCMethodCall &msg, unsigned int Arg, FoundationClass Class, bool CanBeSubscript) const { // Check if the argument is nil. ProgramStateRef State = C.getState(); if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue()) return; // NOTE: We cannot throw non-fatal errors from warnIfNilExpr, // because it's called multiple times from some callers, so it'd cause // an unwanted state split if two or more non-fatal errors are thrown // within the same checker callback. For now we don't want to, but // it'll need to be fixed if we ever want to. if (ExplodedNode *N = C.generateErrorNode()) { SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); if (CanBeSubscript && msg.getMessageKind() == OCM_Subscript) { if (Class == FC_NSArray) { os << "Array element cannot be nil"; } else if (Class == FC_NSDictionary) { if (Arg == 0) { os << "Value stored into '"; os << GetReceiverInterfaceName(msg) << "' cannot be nil"; } else { assert(Arg == 1); os << "'"<< GetReceiverInterfaceName(msg) << "' key cannot be nil"; } } else llvm_unreachable("Missing foundation class for the subscript expr"); } else { if (Class == FC_NSDictionary) { if (Arg == 0) os << "Value argument "; else { assert(Arg == 1); os << "Key argument "; } os << "to '"; msg.getSelector().print(os); os << "' cannot be nil"; } else { os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '"; msg.getSelector().print(os); os << "' cannot be nil"; } } generateBugReport(N, os.str(), msg.getArgSourceRange(Arg), msg.getArgExpr(Arg), C); } }
void NilArgChecker::WarnIfNilArg(CheckerContext &C, const ObjCMethodCall &msg, unsigned int Arg, FoundationClass Class, bool CanBeSubscript) const { // Check if the argument is nil. ProgramStateRef State = C.getState(); if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue()) return; if (!BT) BT.reset(new APIMisuse("nil argument")); if (ExplodedNode *N = C.generateSink()) { SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); if (CanBeSubscript && msg.getMessageKind() == OCM_Subscript) { if (Class == FC_NSArray) { os << "Array element cannot be nil"; } else if (Class == FC_NSDictionary) { if (Arg == 0) { os << "Value stored into '"; os << GetReceiverInterfaceName(msg) << "' cannot be nil"; } else { assert(Arg == 1); os << "'"<< GetReceiverInterfaceName(msg) << "' key cannot be nil"; } } else llvm_unreachable("Missing foundation class for the subscript expr"); } else { if (Class == FC_NSDictionary) { if (Arg == 0) os << "Value argument "; else { assert(Arg == 1); os << "Key argument "; } os << "to '" << msg.getSelector().getAsString() << "' cannot be nil"; } else { os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '" << msg.getSelector().getAsString() << "' cannot be nil"; } } BugReport *R = new BugReport(*BT, os.str(), N); R->addRange(msg.getArgSourceRange(Arg)); bugreporter::trackNullOrUndefValue(N, msg.getArgExpr(Arg), *R); C.emitReport(R); } }
void NilArgChecker::warnIfNilArg(CheckerContext &C, const ObjCMethodCall &msg, unsigned int Arg, FoundationClass Class, bool CanBeSubscript) const { // Check if the argument is nil. ProgramStateRef State = C.getState(); if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue()) return; if (ExplodedNode *N = C.generateErrorNode()) { SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); if (CanBeSubscript && msg.getMessageKind() == OCM_Subscript) { if (Class == FC_NSArray) { os << "Array element cannot be nil"; } else if (Class == FC_NSDictionary) { if (Arg == 0) { os << "Value stored into '"; os << GetReceiverInterfaceName(msg) << "' cannot be nil"; } else { assert(Arg == 1); os << "'"<< GetReceiverInterfaceName(msg) << "' key cannot be nil"; } } else llvm_unreachable("Missing foundation class for the subscript expr"); } else { if (Class == FC_NSDictionary) { if (Arg == 0) os << "Value argument "; else { assert(Arg == 1); os << "Key argument "; } os << "to '"; msg.getSelector().print(os); os << "' cannot be nil"; } else { os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '"; msg.getSelector().print(os); os << "' cannot be nil"; } } generateBugReport(N, os.str(), msg.getArgSourceRange(Arg), msg.getArgExpr(Arg), C); } }
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; } }
/// 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); } }