Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -49,6 +49,7 @@ NSErrorChecker.cpp NoReturnFunctionChecker.cpp NonNullParamChecker.cpp + NullabilityChecker.cpp ObjCAtSyncChecker.cpp ObjCContainersASTChecker.cpp ObjCContainersChecker.cpp Index: lib/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- lib/StaticAnalyzer/Checkers/Checkers.td +++ lib/StaticAnalyzer/Checkers/Checkers.td @@ -19,6 +19,7 @@ def CoreBuiltin : Package<"builtin">, InPackage; def CoreUninitialized : Package<"uninitialized">, InPackage; def CoreAlpha : Package<"core">, InPackage, Hidden; +def Nullability : Package<"nullability">, InPackage, Hidden; def Cplusplus : Package<"cplusplus">; def CplusplusAlpha : Package<"cplusplus">, InPackage, Hidden; @@ -130,6 +131,30 @@ } // end "alpha.core" +let ParentPackage = Nullability in { + +def NullPassedToNonnullChecker : Checker<"NullPassedToNonnull">, + HelpText<"Warns when a null pointer is passed to a nonnull pointer.">, + DescFile<"NullabilityChecker.cpp">; + +def NullReturnedFromNonnullChecker : Checker<"NullReturnedFromNonnull">, + HelpText<"Warns when a null pointer is returned from a nonnull returning function.">, + DescFile<"NullabilityChecker.cpp">; + +def NullableDereferencedChecker : Checker<"NullableDereferenced">, + HelpText<"Warns when a nullable pointer is dereferenced.">, + DescFile<"NullabilityChecker.cpp">; + +def NullablePassedToNonnullChecker : Checker<"NullablePassedToNonnull">, + HelpText<"Warns when a nullable pointer is passed to a nonnull pointer.">, + DescFile<"NullabilityChecker.cpp">; + +def NullableReturnedFromNonnullChecker : Checker<"NullablePassedToNonnull">, + HelpText<"Warns when a nullable pointer is returned from a nonnull returning function.">, + DescFile<"NullabilityChecker.cpp">; + +} // end "alpha.core.nullability" + //===----------------------------------------------------------------------===// // Evaluate "builtin" functions. //===----------------------------------------------------------------------===// Index: lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp =================================================================== --- /dev/null +++ lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -0,0 +1,762 @@ +//== Nullabilityhecker.cpp - Nullability checker ----------------*- C++ -*--==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This checker tries to find nullability violations. The assumption of the +// checker is that, the user is running this checker after all the nullability +// warnings that is emitted by the compiler was fixed. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "llvm/Support/Path.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" + +using namespace clang; +using namespace ento; + +namespace { +enum class Nullability : char { + Contradicted, // Tracked nullability is contradicted by an explicit cast. + Nullable, + Unspecified, // Optimization: Most pointers expected to be unspecified. When + // memory region is not stored in the state, it implicitly means + // unspecified. + Nonnull +}; + +static const char *getNullabilityString(Nullability Nullab) { + switch (Nullab) { + case Nullability::Contradicted: + return "contradicted"; + case Nullability::Nullable: + return "nullable"; + case Nullability::Unspecified: + return "unspecified"; + case Nullability::Nonnull: + return "nonnull"; + } + assert(false); + return ""; +} + +static Nullability getMostNullable(Nullability Lhs, Nullability Rhs) { + return static_cast( + std::min(static_cast(Lhs), static_cast(Rhs))); +} + +enum class ErrorKind : int { + NilAssignedToNonnull, + NilPassedToNonnull, + NilReturnedToNonnull, + NullPointerEnd, + NullableAssignedToNonnull, + NullableReturnedToNonnull, + NullableDereferenced, + NullableAssignedToReference, + NullablePassedToNonnull +}; + +const char *ErrorMessages[] = { + "Null pointer is assigned to nonnull pointer", + "Null pointer is passed to a nonnull parameter", + "Null pointer is returned from a nonnull returning function", + nullptr, + "Nullable pointer is assigned to nonnull", + "Nullable pointer is returned from a nonnull returning function", + "Nullable pointer is dereferenced", + "Nullable pointer is assigned to a reference", + "Nullable pointer is passed to a nonnull parameter"}; + +class NullabilityChecker + : public Checker, + check::PostCall, check::PostStmt, + check::PostObjCMessage, check::DeadSymbols, + check::Event> { + mutable std::unique_ptr BT; + +public: + void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; + void checkPostStmt(const ExplicitCastExpr *CE, CheckerContext &C) const; + void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; + void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; + void checkEvent(ImplicitNullDerefEvent Event) const; + + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; + + struct NullabilityChecksFilter { + DefaultBool CheckNullPassedToNonnull; + DefaultBool CheckNullReturnedFromNonnull; + DefaultBool CheckNullableDereferenced; + DefaultBool CheckNullablePassedToNonnull; + DefaultBool CheckNullableReturnedFromNonnull; + + CheckName CheckNameNullPassedToNonnull; + CheckName CheckNameNullReturnedFromNonnull; + CheckName CheckNameNullableDereferenced; + CheckName CheckNameNullablePassedToNonnull; + CheckName CheckNameNullableReturnedFromNonnull; + }; + + NullabilityChecksFilter Filter; + +private: + class NullabilityBugVisitor + : public BugReporterVisitorImpl { + public: + NullabilityBugVisitor(const MemRegion *M) : Region(M) {} + ~NullabilityBugVisitor() override {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Region); + } + + PathDiagnosticPiece *VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) override; + + private: + // The tracked region. + const MemRegion *Region; + }; + + void reportBug(ErrorKind Error, ExplodedNode *N, const MemRegion *Region, + BugReporter &BR, const Stmt *ValueExpr = nullptr) const { + if (!BT) + BT.reset(new BugType(this, "Nullability", "Memory error")); + const char *Msg = ErrorMessages[static_cast(Error)]; + assert(Msg); + std::unique_ptr R(new BugReport(*BT, Msg, N)); + if (Region) { + R->markInteresting(Region); + R->addVisitor(llvm::make_unique(Region)); + } + if (ValueExpr) { + R->addRange(ValueExpr->getSourceRange()); + if (Error < ErrorKind::NullPointerEnd) + bugreporter::trackNullOrUndefValue(N, ValueExpr, *R); + } + BR.emitReport(std::move(R)); + } +}; + +class NullabilityState { +public: + NullabilityState(Nullability Nullab, const Stmt *Source = nullptr) + : Nullab(Nullab), Source(Source) {} + + const Stmt *getNullabilitySource() const { return Source; } + + Nullability getValue() const { return Nullab; } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(static_cast(Nullab)); + ID.AddPointer(Source); + } + + void print(raw_ostream &Out) const { + Out << getNullabilityString(Nullab) << "\n"; + } + +private: + Nullability Nullab; + const Stmt *Source; +}; + +bool operator==(NullabilityState Lhs, NullabilityState Rhs) { + return Lhs.getValue() == Rhs.getValue() && + Lhs.getNullabilitySource() == Rhs.getNullabilitySource(); +} + +} // end anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(NullabilityMap, const MemRegion *, + NullabilityState) + +static bool shouldTrackRegion(const MemRegion *Region, + AnalysisDeclContext *DeclContext) { + // Literals are never null, so we should not track them. This information is + // always available. + if (Region->getAs() || Region->getAs()) + return false; + + const SymbolicRegion *SymReg = Region->getAs(); + if (!SymReg) + return true; + + const SymbolRegionValue *SymRegVal = + dyn_cast(SymReg->getSymbol()); + if (!SymRegVal) + return true; + + const DeclRegion *MemReg = SymRegVal->getRegion()->getAs(); + if (!MemReg) + return true; + + // Self is mostly nonnull. No tracking needed. + // FIXME: there are some code that assigns to self. + if (MemReg->getDecl() == DeclContext->getSelfDecl()) + return false; + + return true; +} + +PathDiagnosticPiece *NullabilityChecker::NullabilityBugVisitor::VisitNode( + const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, + BugReport &BR) { + ProgramStateRef state = N->getState(); + ProgramStateRef statePrev = PrevN->getState(); + + const NullabilityState *TrackedNullab = state->get(Region); + const NullabilityState *TrackedNullabPrev = + statePrev->get(Region); + if (!TrackedNullab) + return nullptr; + + if (TrackedNullabPrev && + TrackedNullabPrev->getValue() == TrackedNullab->getValue()) + return nullptr; + + // Retrieve the associated statement. + const Stmt *S = TrackedNullab->getNullabilitySource(); + if (!S) { + ProgramPoint ProgLoc = N->getLocation(); + if (Optional SP = ProgLoc.getAs()) { + S = SP->getStmt(); + } + } + + if (!S) + return nullptr; + + std::string InfoText = + (llvm::Twine("Nullability '") + + getNullabilityString(TrackedNullab->getValue()) + "' is infered") + .str(); + + // Generate the extra diagnostic. + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return new PathDiagnosticEventPiece(Pos, InfoText, true, nullptr); +} + +static Nullability getNullability(QualType Type) { + const auto *AttrType = Type->getAs(); + if (!AttrType) + return Nullability::Unspecified; + if (AttrType->getAttrKind() == AttributedType::attr_nullable) + return Nullability::Nullable; + else if (AttrType->getAttrKind() == AttributedType::attr_nonnull) + return Nullability::Nonnull; + return Nullability::Unspecified; +} + +void NullabilityChecker::checkDeadSymbols(SymbolReaper &SR, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + NullabilityMapTy Nullabilities = State->get(); + for (NullabilityMapTy::iterator I = Nullabilities.begin(), + E = Nullabilities.end(); + I != E; ++I) { + if (!SR.isLiveRegion(I->first)) { + State = State->remove(I->first); + } + } +} + +void NullabilityChecker::checkEvent(ImplicitNullDerefEvent Event) const { + SVal DereferencedSVal = Event.Location; + + auto RegionSVal = DereferencedSVal.getAs(); + if (!RegionSVal) + return; + + ProgramStateRef State = Event.SinkNode->getState(); + const MemRegion *Region = RegionSVal->getRegion(); + const NullabilityState *TrackedNullability = + State->get(Region); + + if (!TrackedNullability) { + // Maybe a field or an element is loaded of a nullable pointer. + TrackedNullability = State->get( + Region->getAs()->getSuperRegion()); + if (!TrackedNullability) + return; + } + + if (Filter.CheckNullableDereferenced && + TrackedNullability->getValue() == Nullability::Nullable) { + BugReporter &BR = *Event.BR; + reportBug(ErrorKind::NullableDereferenced, Event.SinkNode, Region, BR); + } +} + +void NullabilityChecker::checkPreStmt(const ReturnStmt *S, + CheckerContext &C) const { + auto RetExpr = S->getRetValue(); + if (!RetExpr) + return; + + QualType RetExprType = RetExpr->getType(); + if (!RetExprType->isPointerType() && !RetExprType->isObjCObjectPointerType()) + return; + + ProgramStateRef State = C.getState(); + SVal RetSVal = State->getSVal(S, C.getLocationContext()); + if (RetSVal.isUndef()) + return; + + AnalysisDeclContext *DeclCtxt = + C.getLocationContext()->getAnalysisDeclContext(); + const FunctionType *FuncType = DeclCtxt->getDecl()->getFunctionType(); + if (!FuncType) + return; + + ConditionTruthVal Nullness = + State->isNull(RetSVal.castAs()); + bool IsNotNull = Nullness.isConstrainedFalse(); + bool IsNull = Nullness.isConstrainedTrue(); + + Nullability StaticNullability = getNullability(FuncType->getReturnType()); + + if (Filter.CheckNullReturnedFromNonnull && IsNull && + StaticNullability == Nullability::Nonnull) { + static CheckerProgramPointTag Tag(this, "NullReturnedFromNonnull"); + ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag); + reportBug(ErrorKind::NilReturnedToNonnull, N, nullptr, C.getBugReporter(), + S); + return; + } + + auto RetRegionSVal = RetSVal.getAs(); + if (!RetRegionSVal) + return; + + const MemRegion *Region = RetRegionSVal->getRegion(); + if (!shouldTrackRegion(Region, C.getCurrentAnalysisDeclContext())) + return; + + const NullabilityState *TrackedNullability = + State->get(Region); + if (TrackedNullability) { + Nullability TrackedNullabValue = TrackedNullability->getValue(); + if (Filter.CheckNullableReturnedFromNonnull && !IsNotNull && + TrackedNullabValue == Nullability::Nullable && + StaticNullability == Nullability::Nonnull) { + static CheckerProgramPointTag Tag(this, "NullableReturnedFromNonnull"); + ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag); + reportBug(ErrorKind::NullableReturnedToNonnull, N, Region, + C.getBugReporter()); + } + return; + } + if (StaticNullability != Nullability::Unspecified) { + State = State->set(Region, + NullabilityState(StaticNullability, S)); + C.addTransition(State); + } +} + +void NullabilityChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + if (!Call.getDecl()) + return; + + ProgramStateRef State = C.getState(); + ProgramStateRef OrigState = State; + + unsigned Idx = 0; + for (const ParmVarDecl *Param : Call.parameters()) { + if (Param->isParameterPack()) + break; + + const Expr *ArgExpr = nullptr; + if (Idx < Call.getNumArgs()) + ArgExpr = Call.getArgExpr(Idx); + SVal ArgSVal = Call.getArgSVal(Idx++); + if (ArgSVal.isUndef()) + continue; + + if (!Param->getType()->isPointerType() && + !Param->getType()->isReferenceType() && + !Param->getType()->isObjCObjectPointerType()) { + continue; + } + + ConditionTruthVal Nullness = + State->isNull(ArgSVal.castAs()); + bool IsNotNull = Nullness.isConstrainedFalse(); + bool IsNull = Nullness.isConstrainedTrue(); + + Nullability ParamNullability = getNullability(Param->getType()); + + if (Filter.CheckNullPassedToNonnull && IsNull && + ParamNullability == Nullability::Nonnull) { + static CheckerProgramPointTag Tag(this, "NullPassedToNonnull"); + ExplodedNode *N = C.generateSink(State, C.getPredecessor(), &Tag); + reportBug(ErrorKind::NilPassedToNonnull, N, nullptr, C.getBugReporter(), + ArgExpr); + return; + } + + auto ArgRegionSVal = ArgSVal.getAs(); + if (!ArgRegionSVal) + continue; + + const MemRegion *Region = ArgRegionSVal->getRegion(); + const NullabilityState *TrackedNullability = + State->get(Region); + + if (!shouldTrackRegion(Region, C.getCurrentAnalysisDeclContext())) + continue; + + if (TrackedNullability) { + if (IsNotNull || TrackedNullability->getValue() != Nullability::Nullable) + continue; + + if (Filter.CheckNullablePassedToNonnull && + ParamNullability == Nullability::Nonnull) { + static CheckerProgramPointTag Tag(this, "NullablePassedToNonnull"); + ExplodedNode *N = C.generateSink(State, C.getPredecessor(), &Tag); + reportBug(ErrorKind::NullablePassedToNonnull, N, Region, + C.getBugReporter(), ArgExpr); + return; + } + if (Filter.CheckNullableDereferenced && + Param->getType()->isReferenceType()) { + static CheckerProgramPointTag Tag(this, "NullableDereferenced"); + ExplodedNode *N = C.generateSink(State, C.getPredecessor(), &Tag); + reportBug(ErrorKind::NullableAssignedToReference, N, Region, + C.getBugReporter(), ArgExpr); + return; + } + continue; + } + // No tracked nullability yet. + // Marking memory regions of variables to be nullable would be a mistake. + // Marking otherwise is redundant. + if (!Region->getAs()) + continue; + Nullability ArgNullability = getNullability(ArgExpr->getType()); + if (ArgNullability == Nullability::Unspecified) + continue; + State = State->set( + Region, NullabilityState(ArgNullability, ArgExpr)); + } + if (State != OrigState) + C.addTransition(State); +} + +void NullabilityChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + auto Decl = Call.getDecl(); + if (!Decl) + return; + const FunctionType *FuncType = Decl->getFunctionType(); + if (!FuncType) + return; + QualType RetType = FuncType->getReturnType(); + if (!RetType->isPointerType() && !RetType->isObjCObjectPointerType()) + return; + SVal ResultSVal = Call.getReturnValue(); + auto MemRegVal = ResultSVal.getAs(); + if (!MemRegVal) + return; + + // CG headers are misannotated. Do not warn for symbols that are the results + // of CG calls. + const SourceManager &SM = C.getSourceManager(); + StringRef FilePath = SM.getFilename(SM.getSpellingLoc(Decl->getLocStart())); + if (llvm::sys::path::filename(FilePath).startswith("CG")) { + ProgramStateRef State = C.getState(); + const MemRegion *Region = MemRegVal->getRegion(); + State = State->set(Region, Nullability::Contradicted); + C.addTransition(State); + } +} + +void NullabilityChecker::checkPostObjCMessage(const ObjCMethodCall &M, + CheckerContext &C) const { + auto Decl = M.getDecl(); + if (!Decl) + return; + QualType RetType = Decl->getReturnType(); + if (!RetType->isPointerType() && !RetType->isObjCObjectPointerType()) + return; + + SVal ResultSVal = M.getReturnValue(); + auto MemRegVal = ResultSVal.getAs(); + if (!MemRegVal) + return; + + ProgramStateRef State = C.getState(); + const MemRegion *ReturnRegion = MemRegVal->getRegion(); + + auto Interface = Decl->getClassInterface(); + auto Name = Interface ? Interface->getName() : ""; + // Frameworks related heuristics. + if (Name.startswith("NS")) { + // Ignore the return value of container methods. + if (Name.find("Array") != StringRef::npos || + Name.find("Dictionary") != StringRef::npos) { + State = + State->set(ReturnRegion, Nullability::Contradicted); + C.addTransition(State); + return; + } + + // Ignore the return value of string encoding methods. + if (Name.find("String") != StringRef::npos) { + for (auto Param : M.parameters()) { + if (Param->getName() == "encoding") { + State = State->set(ReturnRegion, + Nullability::Contradicted); + C.addTransition(State); + return; + } + } + } + } + + if (!shouldTrackRegion(ReturnRegion, C.getCurrentAnalysisDeclContext())) + return; + + const ObjCMessageExpr *Message = M.getOriginExpr(); + Nullability SelfNullability = Nullability::Unspecified; + if (Message->getReceiverKind() == ObjCMessageExpr::SuperClass || + Message->getReceiverKind() == ObjCMessageExpr::SuperInstance) { + SelfNullability = Nullability::Nonnull; + } else { + SVal Receiver = M.getReceiverSVal(); + auto ValueRegionSVal = Receiver.getAs(); + if (ValueRegionSVal) { + const MemRegion *SelfRegion = ValueRegionSVal->getRegion(); + assert(SelfRegion); + + const NullabilityState *TrackedSelfNullability = + State->get(SelfRegion); + if (TrackedSelfNullability) { + SelfNullability = TrackedSelfNullability->getValue(); + } + } + if (!Receiver.isUndef()) { + ConditionTruthVal Nullness = + State->isNull(Receiver.castAs()); + if (Nullness.isConstrainedFalse()) + SelfNullability = Nullability::Nonnull; + } + } + + const NullabilityState *TrackedNullability = + State->get(ReturnRegion); + + if (TrackedNullability) { + Nullability RetValTracked = TrackedNullability->getValue(); + Nullability ComputedNullab = + getMostNullable(RetValTracked, SelfNullability); + if (ComputedNullab != RetValTracked && + ComputedNullab != Nullability::Unspecified) { + const Stmt *NullabilitySource = + ComputedNullab == RetValTracked + ? TrackedNullability->getNullabilitySource() + : Message->getInstanceReceiver(); + State = State->set( + ReturnRegion, NullabilityState(ComputedNullab, NullabilitySource)); + C.addTransition(State); + } + return; + } + + // No tracked information. Use static type information for return value. + Nullability RetNullability = getNullability(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::Unspecified) { + const Stmt *NullabilitySource = ComputedNullab == RetNullability + ? Message + : Message->getInstanceReceiver(); + State = State->set( + ReturnRegion, NullabilityState(ComputedNullab, NullabilitySource)); + C.addTransition(State); + } +} + +void NullabilityChecker::checkPostStmt(const ExplicitCastExpr *CE, + CheckerContext &C) const { + QualType OriginType = CE->getSubExpr()->getType(); + QualType DestType = CE->getType(); + if (!OriginType->isPointerType() && !OriginType->isObjCObjectPointerType()) + return; + if (!DestType->isPointerType() && !DestType->isObjCObjectPointerType()) + return; + + Nullability DestNullability = getNullability(DestType); + + if (DestNullability == Nullability::Unspecified) + return; + + ProgramStateRef State = C.getState(); + SVal ExprSVal = State->getSVal(CE, C.getLocationContext()); + auto RegionSVal = ExprSVal.getAs(); + if (!RegionSVal) + return; + + const MemRegion *Region = RegionSVal->getRegion(); + if (!shouldTrackRegion(Region, C.getCurrentAnalysisDeclContext())) + return; + + // When 0 is converted to nonnull mark it as contradicted. + if (DestNullability == Nullability::Nonnull && !ExprSVal.isUndef()) { + ConditionTruthVal IsNull = + State->isNull(ExprSVal.castAs()); + if (IsNull.isConstrainedTrue()) { + State = State->set(Region, Nullability::Contradicted); + C.addTransition(State); + return; + } + } + + const NullabilityState *TrackedNullability = + State->get(Region); + + if (!TrackedNullability) { + State = State->set(Region, + NullabilityState(DestNullability, CE)); + C.addTransition(State); + return; + } + + if (TrackedNullability->getValue() != DestNullability && + TrackedNullability->getValue() != Nullability::Contradicted) { + State = State->set(Region, Nullability::Contradicted); + C.addTransition(State); + } +} + +void NullabilityChecker::checkBind(SVal L, SVal V, const Stmt *S, + CheckerContext &C) const { + const MemRegion *MR = L.getAsRegion(); + const TypedValueRegion *TVR = dyn_cast_or_null(MR); + if (!TVR) + return; + + QualType LocType = TVR->getValueType(); + if (!LocType->isPointerType() && !LocType->isReferenceType()) + return; + + ProgramStateRef State = C.getState(); + ConditionTruthVal IsNull = State->isNull(V.castAs()); + bool RhsIsNull = IsNull.isConstrainedTrue(); + bool RhsIsNotNull = IsNull.isConstrainedFalse(); + + Nullability LocNullability = getNullability(LocType); + // The null pointer is loaded to a reference is handled in another checker. + if (Filter.CheckNullPassedToNonnull && RhsIsNull && + LocNullability == Nullability::Nonnull) { + static CheckerProgramPointTag Tag(this, "NullPassedToNonnull"); + ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag); + reportBug(ErrorKind::NilAssignedToNonnull, N, nullptr, C.getBugReporter(), + S); + return; + } + + auto ValueRegionSVal = V.getAs(); + if (!ValueRegionSVal) + return; + + const MemRegion *ValueRegion = ValueRegionSVal->getRegion(); + if (!shouldTrackRegion(ValueRegion, C.getCurrentAnalysisDeclContext())) + return; + + Nullability ValNullability = Nullability::Unspecified; + if (SymbolRef Sym = V.getAsSymbol()) + ValNullability = getNullability(Sym->getType()); + + const NullabilityState *TrackedNullability = + State->get(ValueRegion); + + if (TrackedNullability) { + if (RhsIsNotNull || TrackedNullability->getValue() != Nullability::Nullable) + return; + if (Filter.CheckNullablePassedToNonnull && + LocNullability == Nullability::Nonnull) { + static CheckerProgramPointTag Tag(this, "NullablePassedToNonnull"); + ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag); + reportBug(ErrorKind::NullableAssignedToNonnull, N, ValueRegion, + C.getBugReporter()); + } + return; + } + + const auto *BinOp = dyn_cast(S); + + if (ValNullability != Nullability::Unspecified) { + // Trust the static information of the value more than the static + // information on the location. + const Stmt *NullabilitySource = BinOp ? BinOp->getRHS() : S; + State = State->set( + ValueRegion, NullabilityState(ValNullability, NullabilitySource)); + C.addTransition(State); + return; + } + + if (LocNullability != Nullability::Unspecified) { + const Stmt *NullabilitySource = BinOp ? BinOp->getLHS() : S; + State = State->set( + ValueRegion, NullabilityState(LocNullability, NullabilitySource)); + C.addTransition(State); + } +} + +void NullabilityChecker::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + + NullabilityMapTy B = State->get(); + + if (B.isEmpty()) + return; + + Out << Sep << NL; + + for (NullabilityMapTy::iterator I = B.begin(), E = B.end(); I != E; ++I) { + Out << I->first << " : "; + I->second.print(Out); + Out << NL; + } +} + +#define REGISTER_CHECKER(name) \ + void ento::register##name##Checker(CheckerManager &mgr) { \ + NullabilityChecker *checker = mgr.registerChecker(); \ + checker->Filter.Check##name = true; \ + checker->Filter.CheckName##name = mgr.getCurrentCheckName(); \ + } + +REGISTER_CHECKER(NullPassedToNonnull) +REGISTER_CHECKER(NullReturnedFromNonnull) +REGISTER_CHECKER(NullableDereferenced) +REGISTER_CHECKER(NullablePassedToNonnull) +REGISTER_CHECKER(NullableReturnedFromNonnull) Index: lib/StaticAnalyzer/Core/ExprEngineObjC.cpp =================================================================== --- lib/StaticAnalyzer/Core/ExprEngineObjC.cpp +++ lib/StaticAnalyzer/Core/ExprEngineObjC.cpp @@ -186,8 +186,12 @@ // Generate a transition to non-Nil state. if (notNilState != State) { + ExplodedNode *RealPred = Pred; Pred = Bldr.generateNode(ME, Pred, notNilState); - assert(Pred && "Should have cached out already!"); + assert((Pred || RealPred->getLocation().getTag()) + && "Should have cached out already!"); + if (!Pred) + continue; } } } else { Index: test/Analysis/nullability.mm =================================================================== --- /dev/null +++ test/Analysis/nullability.mm @@ -0,0 +1,170 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=core,alpha.core.nullability -verify %s + +#define nil 0 +#define BOOL int + +@protocol NSObject ++ (id)alloc; +- (id)init; +@end + +@protocol NSCopying +@end + +__attribute__((objc_root_class)) +@interface +NSObject +@end + +@interface NSString : NSObject +- (BOOL)isEqualToString : (NSString *_Nonnull)aString; +- (NSString *)stringByAppendingString:(NSString *_Nonnull)aString; +@end + +@interface TestObject : NSObject +- (int *_Nonnull)returnsNonnull; +- (int *_Nullable)returnsNullable; +- (int *)returnsUnspecified; +- (void)takesNonnull:(int *_Nonnull)p; +- (void)takesNullable:(int *_Nullable)p; +- (void)takesUnspecified:(int *)p; +@property(readonly, strong) NSString *stuff; +@end + +TestObject * getUnspecifiedTestObject(); +TestObject *_Nonnull getNonnullTestObject(); +TestObject *_Nullable getNullableTestObject(); + +int getRandom(); + +typedef struct Dummy { int val; } Dummy; + +void takesNullable(Dummy *_Nullable); +void takesNonnull(Dummy *_Nonnull); +void takesUnspecified(Dummy *); + +Dummy *_Nullable returnsNullable(); +Dummy *_Nonnull returnsNonnull(); +Dummy *returnsUnspecified(); +int *_Nullable returnsNullableInt(); + +template T *eraseNullab(T *p) { return p; } + +void testBasicRules() { + Dummy *p = returnsNullable(); + int *ptr = returnsNullableInt(); + // Make every dereference a different path to avoid sinks after errors. + switch (getRandom()) { + case 0: { + Dummy &r = *p; // expected-warning {{}} + } break; + case 1: { + int b = p->val; // expected-warning {{}} + } break; + case 2: { + int stuff = *ptr; // expected-warning {{}} + } break; + case 3: + takesNonnull(p); // expected-warning {{}} + break; + case 4: { + Dummy d; + takesNullable(&d); + Dummy dd(d); + break; + } + // Here the copy constructor is called, so a reference is initialized with the + // value of p. No ImplicitNullDereference event will be dispatched for this + // case. A followup patch is expected to fix this in NonNullParamChecker. + default: { Dummy d = *p; } break; // No warning. + } + if (p) { + takesNonnull(p); + if (getRandom()) { + Dummy &r = *p; + } else { + int b = p->val; + } + } + Dummy *q = 0; + if (getRandom()) { + takesNullable(q); + takesNonnull(q); // expected-warning {{}} + } + Dummy a; + Dummy *_Nonnull nonnull = &a; + nonnull = q; // expected-warning {{}} + q = &a; + takesNullable(q); + takesNonnull(q); +} + +void testArgumentTracking(Dummy *_Nonnull nonnull, Dummy *_Nullable nullable) { + Dummy *p = nullable; + nonnull = p; // expected-warning {{}} + p = 0; + Dummy *q = nonnull; + q = p; +} + +Dummy *_Nonnull testNullableReturn(Dummy *_Nullable a) { + Dummy *p = a; + return p; // expected-warning {{}} +} + +Dummy *_Nonnull testNullReturn() { + Dummy *p = 0; + return p; // expected-warning {{}} +} + +void testObjCMessageResultNullability() { + // The expected result: the most nullable of self and method return type. + TestObject *o = getUnspecifiedTestObject(); + int *shouldBeNullable = [eraseNullab(getNullableTestObject()) returnsNonnull]; + switch (getRandom()) { + case 0: + // The core analyzer assumes that the receiver is non-null after a message + // send. This is to avoid some false positives, and increase performance + // but it also reduces the coverage and makes this checker unable to reason + // about the nullness of the receiver. + [o takesNonnull:shouldBeNullable]; // No warning expected. + break; + case 1: + shouldBeNullable = + [eraseNullab(getNullableTestObject()) returnsUnspecified]; + [o takesNonnull:shouldBeNullable]; // No warning expected. + break; + case 3: + shouldBeNullable = [eraseNullab(getNullableTestObject()) returnsNullable]; + [o takesNonnull:shouldBeNullable]; // expected-warning {{}} + break; + case 4: + shouldBeNullable = [eraseNullab(getNonnullTestObject()) returnsNullable]; + [o takesNonnull:shouldBeNullable]; // expected-warning {{}} + break; + case 5: + shouldBeNullable = + [eraseNullab(getUnspecifiedTestObject()) returnsNullable]; + [o takesNonnull:shouldBeNullable]; // expected-warning {{}} + break; + case 6: + shouldBeNullable = [eraseNullab(getNullableTestObject()) returnsNullable]; + [o takesNonnull:shouldBeNullable]; // expected-warning {{}} + break; + case 7: { + int *shouldBeNonnull = [eraseNullab(getNonnullTestObject()) returnsNonnull]; + [o takesNonnull:shouldBeNonnull]; + } break; + } +} + +void testCast() { + Dummy *p = (Dummy * _Nonnull)returnsNullable(); + takesNonnull(p); +} + +void testInvalidPropagation() { + Dummy *p = returnsUnspecified(); + takesNullable(p); + takesNonnull(p); +}