From b2d2a018d6383f6e568de4de056f80433f32bf5f Mon Sep 17 00:00:00 2001 From: Devin Coughlin Date: Wed, 13 Apr 2016 00:41:54 +0000 Subject: [PATCH] [analyzer] Nullability: Treat nil _Nonnull ivar as invariant violation. Treat a _Nonnull ivar that is nil as an invariant violation in a similar fashion to how a nil _Nonnull parameter is treated as a precondition violation. This avoids warning on defensive returns of nil on defensive internal checks, such as the following common idiom: @class InternalImplementation @interface PublicClass { InternalImplementation * _Nonnull _internal; } -(id _Nonnull)foo; @end @implementation PublicClass -(id _Nonnull)foo { if (!_internal) return nil; // no-warning return [_internal foo]; } @end rdar://problem/24485171 llvm-svn: 266157 --- .../StaticAnalyzer/Checkers/NullabilityChecker.cpp | 70 +++++++++++++++++----- clang/test/Analysis/nullability.mm | 42 +++++++++++++ 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index d8a224e..0941b52 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -356,29 +356,70 @@ static Nullability getNullabilityAnnotation(QualType Type) { return Nullability::Unspecified; } -template +/// Returns true when the value stored at the given location is null +/// and the passed in type is nonnnull. +static bool checkValueAtLValForInvariantViolation(ProgramStateRef State, + SVal LV, QualType T) { + if (getNullabilityAnnotation(T) != Nullability::Nonnull) + return false; + + auto RegionVal = LV.getAs(); + if (!RegionVal) + return false; + + auto StoredVal = + State->getSVal(RegionVal->getRegion()).getAs(); + if (!StoredVal) + return false; + + if (getNullConstraint(*StoredVal, State) == NullConstraint::IsNull) + return true; + + return false; +} + static bool -checkParamsForPreconditionViolation(const ParamVarDeclRange &Params, +checkParamsForPreconditionViolation(ArrayRef Params, ProgramStateRef State, const LocationContext *LocCtxt) { for (const auto *ParamDecl : Params) { if (ParamDecl->isParameterPack()) break; - if (getNullabilityAnnotation(ParamDecl->getType()) != Nullability::Nonnull) - continue; + SVal LV = State->getLValue(ParamDecl, LocCtxt); + if (checkValueAtLValForInvariantViolation(State, LV, + ParamDecl->getType())) { + return true; + } + } + return false; +} - auto RegVal = State->getLValue(ParamDecl, LocCtxt) - .template getAs(); - if (!RegVal) - continue; +static bool +checkSelfIvarsForInvariantViolation(ProgramStateRef State, + const LocationContext *LocCtxt) { + auto *MD = dyn_cast(LocCtxt->getDecl()); + if (!MD || !MD->isInstanceMethod()) + return false; - auto ParamValue = State->getSVal(RegVal->getRegion()) - .template getAs(); - if (!ParamValue) - continue; + const ImplicitParamDecl *SelfDecl = LocCtxt->getSelfDecl(); + if (!SelfDecl) + return false; + + SVal SelfVal = State->getSVal(State->getRegion(SelfDecl, LocCtxt)); + + const ObjCObjectPointerType *SelfType = + dyn_cast(SelfDecl->getType()); + if (!SelfType) + return false; + + const ObjCInterfaceDecl *ID = SelfType->getInterfaceDecl(); + if (!ID) + return false; - if (getNullConstraint(*ParamValue, State) == NullConstraint::IsNull) { + for (const auto *IvarDecl : ID->ivars()) { + SVal LV = State->getLValue(IvarDecl, SelfVal); + if (checkValueAtLValForInvariantViolation(State, LV, IvarDecl->getType())) { return true; } } @@ -405,7 +446,8 @@ static bool checkInvariantViolation(ProgramStateRef State, ExplodedNode *N, else return false; - if (checkParamsForPreconditionViolation(Params, State, LocCtxt)) { + if (checkParamsForPreconditionViolation(Params, State, LocCtxt) || + checkSelfIvarsForInvariantViolation(State, LocCtxt)) { if (!N->isSink()) C.addTransition(State->set(true), N); return true; diff --git a/clang/test/Analysis/nullability.mm b/clang/test/Analysis/nullability.mm index 1e01cdf..d4deecd9 100644 --- a/clang/test/Analysis/nullability.mm +++ b/clang/test/Analysis/nullability.mm @@ -444,3 +444,45 @@ void callMethodInSystemHeader() { // expected-warning@-2{{Nullable pointer is passed to a callee that requires a non-null 1st parameter}} #endif } + +// Test to make sure the analyzer doesn't warn when an a nullability invariant +// has already been found to be violated on an instance variable. + +@class MyInternalClass; +@interface MyClass : NSObject { + MyInternalClass * _Nonnull _internal; +} +@end + +@interface MyInternalClass : NSObject { + @public + id _someIvar; +} +-(id _Nonnull)methodWithInternalImplementation; +@end + +@interface MyClass () { + MyInternalClass * _Nonnull _nilledOutInternal; +} +@end + +@implementation MyClass +-(id _Nonnull)methodWithInternalImplementation { + if (!_internal) + return nil; // no-warning + + return [_internal methodWithInternalImplementation]; +} + +- (id _Nonnull)methodReturningIvarInImplementation; { + return _internal == 0 ? nil : _internal->_someIvar; // no-warning +} + +-(id _Nonnull)methodWithNilledOutInternal { + // The cast below should (but does not yet) suppress the warning on the + // assignment. + _nilledOutInternal = (id _Nonnull)nil; // expected-warning {{Null is assigned to a pointer which is expected to have non-null value}} + + return nil; // no-warning +} +@end -- 2.7.4