forked from OSchip/llvm-project
[analyzer] Suppress nullability warning for defensive super initializer idiom.
A common idiom in Objective-C initializers is for a defensive nil-check on the result of a call to a super initializer: if (self = [super init]) { ... } return self; To avoid warning on this idiom, the nullability checker now suppress diagnostics for returns of nil on syntactic 'return self' even in initializers with non-null return types. llvm-svn: 258461
This commit is contained in:
parent
cef5c9551b
commit
4a330201ff
|
@ -470,6 +470,22 @@ static const Expr *lookThroughImplicitCasts(const Expr *E) {
|
||||||
return E;
|
return E;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true when the return statement is a syntactic 'return self' in
|
||||||
|
/// Objective-C.
|
||||||
|
static bool isReturnSelf(const ReturnStmt *RS, CheckerContext &C) {
|
||||||
|
const ImplicitParamDecl *SelfDecl =
|
||||||
|
C.getCurrentAnalysisDeclContext()->getSelfDecl();
|
||||||
|
if (!SelfDecl)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const Expr *ReturnExpr = lookThroughImplicitCasts(RS->getRetValue());
|
||||||
|
auto *RefExpr = dyn_cast<DeclRefExpr>(ReturnExpr);
|
||||||
|
if (!RefExpr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return RefExpr->getDecl() == SelfDecl;
|
||||||
|
}
|
||||||
|
|
||||||
/// This method check when nullable pointer or null value is returned from a
|
/// This method check when nullable pointer or null value is returned from a
|
||||||
/// function that has nonnull return type.
|
/// function that has nonnull return type.
|
||||||
///
|
///
|
||||||
|
@ -494,16 +510,28 @@ void NullabilityChecker::checkPreStmt(const ReturnStmt *S,
|
||||||
if (!RetSVal)
|
if (!RetSVal)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
bool IsReturnSelfInObjCInit = false;
|
||||||
|
|
||||||
QualType RequiredRetType;
|
QualType RequiredRetType;
|
||||||
AnalysisDeclContext *DeclCtxt =
|
AnalysisDeclContext *DeclCtxt =
|
||||||
C.getLocationContext()->getAnalysisDeclContext();
|
C.getLocationContext()->getAnalysisDeclContext();
|
||||||
const Decl *D = DeclCtxt->getDecl();
|
const Decl *D = DeclCtxt->getDecl();
|
||||||
if (auto *MD = dyn_cast<ObjCMethodDecl>(D))
|
if (auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
|
||||||
RequiredRetType = MD->getReturnType();
|
RequiredRetType = MD->getReturnType();
|
||||||
else if (auto *FD = dyn_cast<FunctionDecl>(D))
|
// Suppress diagnostics for returns of nil that are syntactic returns of
|
||||||
|
// self in ObjC initializers. This avoids warning under the common idiom of
|
||||||
|
// a defensive check of the result of a call to super:
|
||||||
|
// if (self = [super init]) {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// return self; // no-warning
|
||||||
|
IsReturnSelfInObjCInit = (MD->getMethodFamily() == OMF_init) &&
|
||||||
|
isReturnSelf(S, C);
|
||||||
|
} else if (auto *FD = dyn_cast<FunctionDecl>(D)) {
|
||||||
RequiredRetType = FD->getReturnType();
|
RequiredRetType = FD->getReturnType();
|
||||||
else
|
} else {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NullConstraint Nullness = getNullConstraint(*RetSVal, State);
|
NullConstraint Nullness = getNullConstraint(*RetSVal, State);
|
||||||
|
|
||||||
|
@ -520,7 +548,8 @@ void NullabilityChecker::checkPreStmt(const ReturnStmt *S,
|
||||||
if (Filter.CheckNullReturnedFromNonnull &&
|
if (Filter.CheckNullReturnedFromNonnull &&
|
||||||
Nullness == NullConstraint::IsNull &&
|
Nullness == NullConstraint::IsNull &&
|
||||||
RetExprTypeLevelNullability != Nullability::Nonnull &&
|
RetExprTypeLevelNullability != Nullability::Nonnull &&
|
||||||
RequiredNullability == Nullability::Nonnull) {
|
RequiredNullability == Nullability::Nonnull &&
|
||||||
|
!IsReturnSelfInObjCInit) {
|
||||||
static CheckerProgramPointTag Tag(this, "NullReturnedFromNonnull");
|
static CheckerProgramPointTag Tag(this, "NullReturnedFromNonnull");
|
||||||
ExplodedNode *N = C.generateErrorNode(State, &Tag);
|
ExplodedNode *N = C.generateErrorNode(State, &Tag);
|
||||||
if (!N)
|
if (!N)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// RUN: %clang_cc1 -fobjc-arc -analyze -analyzer-checker=core,nullability -verify %s
|
// RUN: %clang_cc1 -fobjc-arc -analyze -analyzer-checker=core,nullability -verify %s
|
||||||
|
// RUN: %clang_cc1 -analyze -analyzer-checker=core,nullability -verify %s
|
||||||
|
|
||||||
#define nil 0
|
#define nil 0
|
||||||
#define BOOL int
|
#define BOOL int
|
||||||
|
@ -279,10 +280,21 @@ Dummy *_Nonnull testDefensiveInlineChecks(Dummy * p) {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface SomeClass : NSObject
|
|
||||||
|
@interface SomeClass : NSObject {
|
||||||
|
int instanceVar;
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation SomeClass (MethodReturn)
|
@implementation SomeClass (MethodReturn)
|
||||||
|
- (id)initWithSomething:(int)i {
|
||||||
|
if (self = [super init]) {
|
||||||
|
instanceVar = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
- (TestObject * _Nonnull)testReturnsNullableInNonnullIndirectly {
|
- (TestObject * _Nonnull)testReturnsNullableInNonnullIndirectly {
|
||||||
TestObject *local = getNullableTestObject();
|
TestObject *local = getNullableTestObject();
|
||||||
return local; // expected-warning {{Nullable pointer is returned from a function that is expected to return a non-null value}}
|
return local; // expected-warning {{Nullable pointer is returned from a function that is expected to return a non-null value}}
|
||||||
|
@ -301,3 +313,41 @@ Dummy *_Nonnull testDefensiveInlineChecks(Dummy * p) {
|
||||||
return p; // no-warning
|
return p; // no-warning
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface ClassWithInitializers : NSObject
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation ClassWithInitializers
|
||||||
|
- (instancetype _Nonnull)initWithNonnullReturnAndSelfCheckingIdiom {
|
||||||
|
// This defensive check is a common-enough idiom that we filter 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;
|
||||||
|
// Still warn when when not returning via self.
|
||||||
|
return other; // expected-warning {{Null is returned from a function that is expected to return a non-null value}}
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface SubClassWithInitializers : ClassWithInitializers
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation SubClassWithInitializers
|
||||||
|
// Note: Because this is overridding
|
||||||
|
// -[ClassWithInitializers initWithNonnullReturnAndSelfCheckingIdiom],
|
||||||
|
// the return type of this method becomes implicitly id _Nonnull.
|
||||||
|
- (id)initWithNonnullReturnAndSelfCheckingIdiom {
|
||||||
|
if (self = [super initWithNonnullReturnAndSelfCheckingIdiom]) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return self; // no-warning
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
Loading…
Reference in New Issue