xazax.hun updated this revision to Diff 32869.
xazax.hun added a comment.
- Updated to the latest trunk.
- Relaxed an assert in ExprEngine which turned out to be unsound.
- The individual checks can be turned on or off.
- Added some framework specific heuristic to reduce the number of false
positive results.
- Refactoring.
http://reviews.llvm.org/D11468
Files:
lib/StaticAnalyzer/Checkers/CMakeLists.txt
lib/StaticAnalyzer/Checkers/Checkers.td
lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
test/Analysis/nullability.mm
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<NSObject>
+@end
+
+@interface NSString : NSObject<NSCopying>
+- (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 <typename T> 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);
+}
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: 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<Nullability>(
+ std::min(static_cast<char>(Lhs), static_cast<char>(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::Bind, check::PreCall, check::PreStmt<ReturnStmt>,
+ check::PostCall, check::PostStmt<ExplicitCastExpr>,
+ check::PostObjCMessage, check::DeadSymbols,
+ check::Event<ImplicitNullDerefEvent>> {
+ mutable std::unique_ptr<BugType> 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<NullabilityBugVisitor> {
+ 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<int>(Error)];
+ assert(Msg);
+ std::unique_ptr<BugReport> R(new BugReport(*BT, Msg, N));
+ if (Region) {
+ R->markInteresting(Region);
+ R->addVisitor(llvm::make_unique<NullabilityBugVisitor>(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<char>(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<StringRegion>() || Region->getAs<ObjCStringRegion>())
+ return false;
+
+ const SymbolicRegion *SymReg = Region->getAs<SymbolicRegion>();
+ if (!SymReg)
+ return true;
+
+ const SymbolRegionValue *SymRegVal =
+ dyn_cast<SymbolRegionValue>(SymReg->getSymbol());
+ if (!SymRegVal)
+ return true;
+
+ const DeclRegion *MemReg = SymRegVal->getRegion()->getAs<DeclRegion>();
+ 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<NullabilityMap>(Region);
+ const NullabilityState *TrackedNullabPrev =
+ statePrev->get<NullabilityMap>(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<StmtPoint> SP = ProgLoc.getAs<StmtPoint>()) {
+ 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<AttributedType>();
+ 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<NullabilityMap>();
+ for (NullabilityMapTy::iterator I = Nullabilities.begin(),
+ E = Nullabilities.end();
+ I != E; ++I) {
+ if (!SR.isLiveRegion(I->first)) {
+ State = State->remove<NullabilityMap>(I->first);
+ }
+ }
+}
+
+void NullabilityChecker::checkEvent(ImplicitNullDerefEvent Event) const {
+ SVal DereferencedSVal = Event.Location;
+
+ auto RegionSVal = DereferencedSVal.getAs<loc::MemRegionVal>();
+ if (!RegionSVal)
+ return;
+
+ ProgramStateRef State = Event.SinkNode->getState();
+ const MemRegion *Region = RegionSVal->getRegion();
+ const NullabilityState *TrackedNullability =
+ State->get<NullabilityMap>(Region);
+
+ if (!TrackedNullability) {
+ // Maybe a field or an element is loaded of a nullable pointer.
+ TrackedNullability = State->get<NullabilityMap>(
+ Region->getAs<SubRegion>()->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<DefinedOrUnknownSVal>());
+ 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<loc::MemRegionVal>();
+ if (!RetRegionSVal)
+ return;
+
+ const MemRegion *Region = RetRegionSVal->getRegion();
+ if (!shouldTrackRegion(Region, C.getCurrentAnalysisDeclContext()))
+ return;
+
+ const NullabilityState *TrackedNullability =
+ State->get<NullabilityMap>(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<NullabilityMap>(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<DefinedOrUnknownSVal>());
+ 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<loc::MemRegionVal>();
+ if (!ArgRegionSVal)
+ continue;
+
+ const MemRegion *Region = ArgRegionSVal->getRegion();
+ const NullabilityState *TrackedNullability =
+ State->get<NullabilityMap>(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<SymbolicRegion>())
+ continue;
+ Nullability ArgNullability = getNullability(ArgExpr->getType());
+ if (ArgNullability == Nullability::Unspecified)
+ continue;
+ State = State->set<NullabilityMap>(
+ 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<loc::MemRegionVal>();
+ 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<NullabilityMap>(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<loc::MemRegionVal>();
+ 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<NullabilityMap>(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<NullabilityMap>(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<loc::MemRegionVal>();
+ if (ValueRegionSVal) {
+ const MemRegion *SelfRegion = ValueRegionSVal->getRegion();
+ assert(SelfRegion);
+
+ const NullabilityState *TrackedSelfNullability =
+ State->get<NullabilityMap>(SelfRegion);
+ if (TrackedSelfNullability) {
+ SelfNullability = TrackedSelfNullability->getValue();
+ }
+ }
+ if (!Receiver.isUndef()) {
+ ConditionTruthVal Nullness =
+ State->isNull(Receiver.castAs<DefinedOrUnknownSVal>());
+ if (Nullness.isConstrainedFalse())
+ SelfNullability = Nullability::Nonnull;
+ }
+ }
+
+ const NullabilityState *TrackedNullability =
+ State->get<NullabilityMap>(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<NullabilityMap>(
+ 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<NullabilityMap>(
+ 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<loc::MemRegionVal>();
+ 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<DefinedOrUnknownSVal>());
+ if (IsNull.isConstrainedTrue()) {
+ State = State->set<NullabilityMap>(Region, Nullability::Contradicted);
+ C.addTransition(State);
+ return;
+ }
+ }
+
+ const NullabilityState *TrackedNullability =
+ State->get<NullabilityMap>(Region);
+
+ if (!TrackedNullability) {
+ State = State->set<NullabilityMap>(Region,
+ NullabilityState(DestNullability, CE));
+ C.addTransition(State);
+ return;
+ }
+
+ if (TrackedNullability->getValue() != DestNullability &&
+ TrackedNullability->getValue() != Nullability::Contradicted) {
+ State = State->set<NullabilityMap>(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<TypedValueRegion>(MR);
+ if (!TVR)
+ return;
+
+ QualType LocType = TVR->getValueType();
+ if (!LocType->isPointerType() && !LocType->isReferenceType())
+ return;
+
+ ProgramStateRef State = C.getState();
+ ConditionTruthVal IsNull = State->isNull(V.castAs<DefinedOrUnknownSVal>());
+ 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<loc::MemRegionVal>();
+ 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<NullabilityMap>(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<BinaryOperator>(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<NullabilityMap>(
+ ValueRegion, NullabilityState(ValNullability, NullabilitySource));
+ C.addTransition(State);
+ return;
+ }
+
+ if (LocNullability != Nullability::Unspecified) {
+ const Stmt *NullabilitySource = BinOp ? BinOp->getLHS() : S;
+ State = State->set<NullabilityMap>(
+ 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<NullabilityMap>();
+
+ 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<NullabilityChecker>(); \
+ 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/Checkers/Checkers.td
===================================================================
--- lib/StaticAnalyzer/Checkers/Checkers.td
+++ lib/StaticAnalyzer/Checkers/Checkers.td
@@ -19,6 +19,7 @@
def CoreBuiltin : Package<"builtin">, InPackage<Core>;
def CoreUninitialized : Package<"uninitialized">, InPackage<Core>;
def CoreAlpha : Package<"core">, InPackage<Alpha>, Hidden;
+def Nullability : Package<"nullability">, InPackage<CoreAlpha>, Hidden;
def Cplusplus : Package<"cplusplus">;
def CplusplusAlpha : Package<"cplusplus">, InPackage<Alpha>, 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/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
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits