[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
This commit is contained in:
parent
887d4767b7
commit
b2d2a018d6
|
@ -356,29 +356,70 @@ static Nullability getNullabilityAnnotation(QualType Type) {
|
|||
return Nullability::Unspecified;
|
||||
}
|
||||
|
||||
template <typename ParamVarDeclRange>
|
||||
/// 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<loc::MemRegionVal>();
|
||||
if (!RegionVal)
|
||||
return false;
|
||||
|
||||
auto StoredVal =
|
||||
State->getSVal(RegionVal->getRegion()).getAs<DefinedOrUnknownSVal>();
|
||||
if (!StoredVal)
|
||||
return false;
|
||||
|
||||
if (getNullConstraint(*StoredVal, State) == NullConstraint::IsNull)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
checkParamsForPreconditionViolation(const ParamVarDeclRange &Params,
|
||||
checkParamsForPreconditionViolation(ArrayRef<ParmVarDecl *> 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<loc::MemRegionVal>();
|
||||
if (!RegVal)
|
||||
continue;
|
||||
static bool
|
||||
checkSelfIvarsForInvariantViolation(ProgramStateRef State,
|
||||
const LocationContext *LocCtxt) {
|
||||
auto *MD = dyn_cast<ObjCMethodDecl>(LocCtxt->getDecl());
|
||||
if (!MD || !MD->isInstanceMethod())
|
||||
return false;
|
||||
|
||||
auto ParamValue = State->getSVal(RegVal->getRegion())
|
||||
.template getAs<DefinedOrUnknownSVal>();
|
||||
if (!ParamValue)
|
||||
continue;
|
||||
const ImplicitParamDecl *SelfDecl = LocCtxt->getSelfDecl();
|
||||
if (!SelfDecl)
|
||||
return false;
|
||||
|
||||
if (getNullConstraint(*ParamValue, State) == NullConstraint::IsNull) {
|
||||
SVal SelfVal = State->getSVal(State->getRegion(SelfDecl, LocCtxt));
|
||||
|
||||
const ObjCObjectPointerType *SelfType =
|
||||
dyn_cast<ObjCObjectPointerType>(SelfDecl->getType());
|
||||
if (!SelfType)
|
||||
return false;
|
||||
|
||||
const ObjCInterfaceDecl *ID = SelfType->getInterfaceDecl();
|
||||
if (!ID)
|
||||
return false;
|
||||
|
||||
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<InvariantViolated>(true), N);
|
||||
return true;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue