r264647 - [analyzer] Nullability: Don't warn along paths where null returned from non-null.
Devin Coughlin via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 28 13:30:26 PDT 2016
Author: dcoughlin
Date: Mon Mar 28 15:30:25 2016
New Revision: 264647
URL: http://llvm.org/viewvc/llvm-project?rev=264647&view=rev
Log:
[analyzer] Nullability: Don't warn along paths where null returned from non-null.
Change the nullability checker to not warn along paths where null is returned from
a method with a non-null return type, even when the diagnostic for this return
has been suppressed. This prevents warning from methods with non-null return types
that inline methods that themselves return nil but that suppressed the diagnostic.
Also change the PreconditionViolated state component to be called "InvariantViolated"
because it is set when a post-condition is violated, as well.
rdar://problem/25393539
Modified:
cfe/trunk/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
cfe/trunk/test/Analysis/Inputs/system-header-simulator-for-nullability.h
cfe/trunk/test/Analysis/nullability-no-arc.mm
Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp?rev=264647&r1=264646&r2=264647&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp Mon Mar 28 15:30:25 2016
@@ -168,11 +168,11 @@ private:
///
/// When \p SuppressPath is set to true, no more bugs will be reported on this
/// path by this checker.
- void reportBugIfPreconditionHolds(StringRef Msg, ErrorKind Error,
- ExplodedNode *N, const MemRegion *Region,
- CheckerContext &C,
- const Stmt *ValueExpr = nullptr,
- bool SuppressPath = false) const;
+ void reportBugIfInvariantHolds(StringRef Msg, ErrorKind Error,
+ ExplodedNode *N, const MemRegion *Region,
+ CheckerContext &C,
+ const Stmt *ValueExpr = nullptr,
+ bool SuppressPath = false) const;
void reportBug(StringRef Msg, ErrorKind Error, ExplodedNode *N,
const MemRegion *Region, BugReporter &BR,
@@ -247,12 +247,31 @@ bool operator==(NullabilityState Lhs, Nu
REGISTER_MAP_WITH_PROGRAMSTATE(NullabilityMap, const MemRegion *,
NullabilityState)
-// If the nullability precondition of a function is violated, we should not
-// report nullability related issues on that path. For this reason once a
-// precondition is not met on a path, this checker will be esentially turned off
-// for the rest of the analysis. We do not want to generate a sink node however,
-// so this checker would not lead to reduced coverage.
-REGISTER_TRAIT_WITH_PROGRAMSTATE(PreconditionViolated, bool)
+// We say "the nullability type invariant is violated" when a location with a
+// non-null type contains NULL or a function with a non-null return type returns
+// NULL. Violations of the nullability type invariant can be detected either
+// directly (for example, when NULL is passed as an argument to a nonnull
+// parameter) or indirectly (for example, when, inside a function, the
+// programmer defensively checks whether a nonnull parameter contains NULL and
+// finds that it does).
+//
+// As a matter of policy, the nullability checker typically warns on direct
+// violations of the nullability invariant (although it uses various
+// heuristics to suppress warnings in some cases) but will not warn if the
+// invariant has already been violated along the path (either directly or
+// indirectly). As a practical matter, this prevents the analyzer from
+// (1) warning on defensive code paths where a nullability precondition is
+// determined to have been violated, (2) warning additional times after an
+// initial direct violation has been discovered, and (3) warning after a direct
+// violation that has been implicitly or explicitly suppressed (for
+// example, with a cast of NULL to _Nonnull). In essence, once an invariant
+// violation is detected on a path, this checker will be esentially turned off
+// for the rest of the analysis
+//
+// The analyzer takes this approach (rather than generating a sink node) to
+// ensure coverage of defensive paths, which may be important for backwards
+// compatibility in codebases that were developed without nullability in mind.
+REGISTER_TRAIT_WITH_PROGRAMSTATE(InvariantViolated, bool)
enum class NullConstraint { IsNull, IsNotNull, Unknown };
@@ -366,9 +385,9 @@ checkParamsForPreconditionViolation(cons
return false;
}
-static bool checkPreconditionViolation(ProgramStateRef State, ExplodedNode *N,
- CheckerContext &C) {
- if (State->get<PreconditionViolated>())
+static bool checkInvariantViolation(ProgramStateRef State, ExplodedNode *N,
+ CheckerContext &C) {
+ if (State->get<InvariantViolated>())
return true;
const LocationContext *LocCtxt = C.getLocationContext();
@@ -388,21 +407,21 @@ static bool checkPreconditionViolation(P
if (checkParamsForPreconditionViolation(Params, State, LocCtxt)) {
if (!N->isSink())
- C.addTransition(State->set<PreconditionViolated>(true), N);
+ C.addTransition(State->set<InvariantViolated>(true), N);
return true;
}
return false;
}
-void NullabilityChecker::reportBugIfPreconditionHolds(StringRef Msg,
+void NullabilityChecker::reportBugIfInvariantHolds(StringRef Msg,
ErrorKind Error, ExplodedNode *N, const MemRegion *Region,
CheckerContext &C, const Stmt *ValueExpr, bool SuppressPath) const {
ProgramStateRef OriginalState = N->getState();
- if (checkPreconditionViolation(OriginalState, N, C))
+ if (checkInvariantViolation(OriginalState, N, C))
return;
if (SuppressPath) {
- OriginalState = OriginalState->set<PreconditionViolated>(true);
+ OriginalState = OriginalState->set<InvariantViolated>(true);
N = C.addTransition(OriginalState, N);
}
@@ -430,7 +449,7 @@ void NullabilityChecker::checkDeadSymbol
// preconditions are violated. It is not enough to check this only when we
// actually report an error, because at that time interesting symbols might be
// reaped.
- if (checkPreconditionViolation(State, C.getPredecessor(), C))
+ if (checkInvariantViolation(State, C.getPredecessor(), C))
return;
C.addTransition(State);
}
@@ -439,7 +458,7 @@ void NullabilityChecker::checkDeadSymbol
/// not know anything about the value of that pointer. When that pointer is
/// nullable, this code emits a warning.
void NullabilityChecker::checkEvent(ImplicitNullDerefEvent Event) const {
- if (Event.SinkNode->getState()->get<PreconditionViolated>())
+ if (Event.SinkNode->getState()->get<InvariantViolated>())
return;
const MemRegion *Region =
@@ -486,10 +505,6 @@ static const Expr *lookThroughImplicitCa
/// This method check when nullable pointer or null value is returned from a
/// function that has nonnull return type.
-///
-/// TODO: when nullability preconditons are violated, it is ok to violate the
-/// nullability postconditons (i.e.: when one of the nonnull parameters are null
-/// this check should not report any nullability related issue).
void NullabilityChecker::checkPreStmt(const ReturnStmt *S,
CheckerContext &C) const {
auto RetExpr = S->getRetValue();
@@ -500,7 +515,7 @@ void NullabilityChecker::checkPreStmt(co
return;
ProgramStateRef State = C.getState();
- if (State->get<PreconditionViolated>())
+ if (State->get<InvariantViolated>())
return;
auto RetSVal =
@@ -542,10 +557,11 @@ void NullabilityChecker::checkPreStmt(co
Nullability RetExprTypeLevelNullability =
getNullabilityAnnotation(lookThroughImplicitCasts(RetExpr)->getType());
+ bool NullReturnedFromNonNull = (RequiredNullability == Nullability::Nonnull &&
+ Nullness == NullConstraint::IsNull);
if (Filter.CheckNullReturnedFromNonnull &&
- Nullness == NullConstraint::IsNull &&
+ NullReturnedFromNonNull &&
RetExprTypeLevelNullability != Nullability::Nonnull &&
- RequiredNullability == Nullability::Nonnull &&
!InSuppressedMethodFamily) {
static CheckerProgramPointTag Tag(this, "NullReturnedFromNonnull");
ExplodedNode *N = C.generateErrorNode(State, &Tag);
@@ -557,9 +573,17 @@ void NullabilityChecker::checkPreStmt(co
OS << "Null is returned from a " << C.getDeclDescription(D) <<
" that is expected to return a non-null value";
- reportBugIfPreconditionHolds(OS.str(),
- ErrorKind::NilReturnedToNonnull, N, nullptr, C,
- RetExpr);
+ reportBugIfInvariantHolds(OS.str(),
+ ErrorKind::NilReturnedToNonnull, N, nullptr, C,
+ RetExpr);
+ return;
+ }
+
+ // If null was returned from a non-null function, mark the nullability
+ // invariant as violated even if the diagnostic was suppressed.
+ if (NullReturnedFromNonNull) {
+ State = State->set<InvariantViolated>(true);
+ C.addTransition(State);
return;
}
@@ -583,9 +607,9 @@ void NullabilityChecker::checkPreStmt(co
OS << "Nullable pointer is returned from a " << C.getDeclDescription(D) <<
" that is expected to return a non-null value";
- reportBugIfPreconditionHolds(OS.str(),
- ErrorKind::NullableReturnedToNonnull, N,
- Region, C);
+ reportBugIfInvariantHolds(OS.str(),
+ ErrorKind::NullableReturnedToNonnull, N,
+ Region, C);
}
return;
}
@@ -605,7 +629,7 @@ void NullabilityChecker::checkPreCall(co
return;
ProgramStateRef State = C.getState();
- if (State->get<PreconditionViolated>())
+ if (State->get<InvariantViolated>())
return;
ProgramStateRef OrigState = State;
@@ -646,9 +670,9 @@ void NullabilityChecker::checkPreCall(co
llvm::raw_svector_ostream OS(SBuf);
OS << "Null passed to a callee that requires a non-null " << ParamIdx
<< llvm::getOrdinalSuffix(ParamIdx) << " parameter";
- reportBugIfPreconditionHolds(OS.str(), ErrorKind::NilPassedToNonnull, N,
- nullptr, C,
- ArgExpr, /*SuppressPath=*/false);
+ reportBugIfInvariantHolds(OS.str(), ErrorKind::NilPassedToNonnull, N,
+ nullptr, C,
+ ArgExpr, /*SuppressPath=*/false);
return;
}
@@ -672,17 +696,17 @@ void NullabilityChecker::checkPreCall(co
llvm::raw_svector_ostream OS(SBuf);
OS << "Nullable pointer is passed to a callee that requires a non-null "
<< ParamIdx << llvm::getOrdinalSuffix(ParamIdx) << " parameter";
- reportBugIfPreconditionHolds(OS.str(),
- ErrorKind::NullablePassedToNonnull, N,
- Region, C, ArgExpr, /*SuppressPath=*/true);
+ reportBugIfInvariantHolds(OS.str(),
+ ErrorKind::NullablePassedToNonnull, N,
+ Region, C, ArgExpr, /*SuppressPath=*/true);
return;
}
if (Filter.CheckNullableDereferenced &&
Param->getType()->isReferenceType()) {
ExplodedNode *N = C.addTransition(State);
- reportBugIfPreconditionHolds("Nullable pointer is dereferenced",
- ErrorKind::NullableDereferenced, N, Region,
- C, ArgExpr, /*SuppressPath=*/true);
+ reportBugIfInvariantHolds("Nullable pointer is dereferenced",
+ ErrorKind::NullableDereferenced, N, Region,
+ C, ArgExpr, /*SuppressPath=*/true);
return;
}
continue;
@@ -713,7 +737,7 @@ void NullabilityChecker::checkPostCall(c
if (!ReturnType->isAnyPointerType())
return;
ProgramStateRef State = C.getState();
- if (State->get<PreconditionViolated>())
+ if (State->get<InvariantViolated>())
return;
const MemRegion *Region = getTrackRegion(Call.getReturnValue());
@@ -782,7 +806,7 @@ void NullabilityChecker::checkPostObjCMe
return;
ProgramStateRef State = C.getState();
- if (State->get<PreconditionViolated>())
+ if (State->get<InvariantViolated>())
return;
const MemRegion *ReturnRegion = getTrackRegion(M.getReturnValue());
@@ -897,7 +921,7 @@ void NullabilityChecker::checkPostStmt(c
return;
ProgramStateRef State = C.getState();
- if (State->get<PreconditionViolated>())
+ if (State->get<InvariantViolated>())
return;
Nullability DestNullability = getNullabilityAnnotation(DestType);
@@ -1022,7 +1046,7 @@ void NullabilityChecker::checkBind(SVal
return;
ProgramStateRef State = C.getState();
- if (State->get<PreconditionViolated>())
+ if (State->get<InvariantViolated>())
return;
auto ValDefOrUnknown = V.getAs<DefinedOrUnknownSVal>();
@@ -1050,10 +1074,10 @@ void NullabilityChecker::checkBind(SVal
if (!ValueExpr)
ValueExpr = S;
- reportBugIfPreconditionHolds("Null is assigned to a pointer which is "
- "expected to have non-null value",
- ErrorKind::NilAssignedToNonnull, N, nullptr, C,
- ValueExpr);
+ reportBugIfInvariantHolds("Null is assigned to a pointer which is "
+ "expected to have non-null value",
+ ErrorKind::NilAssignedToNonnull, N, nullptr, C,
+ ValueExpr);
return;
}
// Intentionally missing case: '0' is bound to a reference. It is handled by
@@ -1074,10 +1098,10 @@ void NullabilityChecker::checkBind(SVal
LocNullability == Nullability::Nonnull) {
static CheckerProgramPointTag Tag(this, "NullablePassedToNonnull");
ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag);
- reportBugIfPreconditionHolds("Nullable pointer is assigned to a pointer "
- "which is expected to have non-null value",
- ErrorKind::NullableAssignedToNonnull, N,
- ValueRegion, C);
+ reportBugIfInvariantHolds("Nullable pointer is assigned to a pointer "
+ "which is expected to have non-null value",
+ ErrorKind::NullableAssignedToNonnull, N,
+ ValueRegion, C);
}
return;
}
Modified: cfe/trunk/test/Analysis/Inputs/system-header-simulator-for-nullability.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/Inputs/system-header-simulator-for-nullability.h?rev=264647&r1=264646&r2=264647&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/Inputs/system-header-simulator-for-nullability.h (original)
+++ cfe/trunk/test/Analysis/Inputs/system-header-simulator-for-nullability.h Mon Mar 28 15:30:25 2016
@@ -11,8 +11,9 @@ NS_ASSUME_NONNULL_BEGIN
typedef struct _NSZone NSZone;
@protocol NSObject
-+ (id)alloc;
-- (id)init;
++ (instancetype)alloc;
+- (instancetype)init;
+- (instancetype)autorelease;
@end
@protocol NSCopying
Modified: cfe/trunk/test/Analysis/nullability-no-arc.mm
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/nullability-no-arc.mm?rev=264647&r1=264646&r2=264647&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/nullability-no-arc.mm (original)
+++ cfe/trunk/test/Analysis/nullability-no-arc.mm Mon Mar 28 15:30:25 2016
@@ -5,6 +5,8 @@
@protocol NSObject
+ (id)alloc;
- (id)init;
+- (instancetype)autorelease;
+- (void)release;
@end
__attribute__((objc_root_class))
@@ -43,3 +45,56 @@ void testObjCNonARCNoInitialization(Test
void testObjCNonARCExplicitZeroInitialization() {
TestObject * _Nonnull explicitlyZeroInitialized = nil; // expected-warning {{Null is assigned to a pointer which is expected to have non-null value}}
}
+
+ at interface ClassWithInitializers : NSObject
+ at end
+
+ at implementation ClassWithInitializers
+- (instancetype _Nonnull)initWithNonnullReturnAndSelfCheckingIdiom {
+ // This defensive check is a common-enough idiom that we don't want
+ // to issue a diagnostic for it.
+ if (self = [super init]) {
+ }
+
+ return self; // no-warning
+}
+
+- (instancetype _Nonnull)initWithNonnullReturnAndNilReturnViaLocal {
+ self = [super init];
+ // This leaks, but we're not checking for that here.
+
+ ClassWithInitializers *other = nil;
+ // False negative. Once we have more subtle suppression of defensive checks in
+ // initializers we should warn here.
+ return other;
+}
+
+- (instancetype _Nonnull)initWithPreconditionViolation:(int)p {
+ self = [super init];
+ if (p < 0) {
+ [self release];
+ return (ClassWithInitializers * _Nonnull)nil;
+ }
+ return self;
+}
+
++ (instancetype _Nonnull)factoryCallingInitWithNonnullReturnAndSelfCheckingIdiom {
+ return [[[self alloc] initWithNonnullReturnAndSelfCheckingIdiom] autorelease]; // no-warning
+}
+
++ (instancetype _Nonnull)factoryCallingInitWithNonnullReturnAndNilReturnViaLocal {
+ return [[[self alloc] initWithNonnullReturnAndNilReturnViaLocal] autorelease]; // no-warning
+}
+
++ (instancetype _Nonnull)initWithPreconditionViolation:(int) p {
+ return [[[self alloc] initWithPreconditionViolation:p] autorelease]; // no-warning
+}
+
+- (TestObject * _Nonnull) returnsNil {
+ return (TestObject * _Nonnull)nil;
+}
+- (TestObject * _Nonnull) inlineOfReturnsNilObjCInstanceDirectlyWithSuppressingCast {
+ TestObject *o = [self returnsNil];
+ return o;
+}
+ at end
More information about the cfe-commits
mailing list