[analyzer] Add CIFIlter modeling to DeallocChecker.

The -dealloc method in CIFilter is highly unusual in that it will release
instance variables belonging to its *subclasses* if the variable name
starts with "input" or backs a property whose name starts with "input".
Subclasses should not release these ivars in their own -dealloc method --
doing so could result in an over release.

Before this commit, the DeallocChecker would warn about missing releases for
such "input" properties -- which could cause users of the analyzer to add
over releases to silence the warning.

To avoid this, DeallocChecker now treats CIFilter "input-prefixed" ivars
as MustNotReleaseDirectly and so will not require a release. Further, it
will now warn when such an ivar is directly released in -dealloc.

rdar://problem/25364901

llvm-svn: 264463
This commit is contained in:
Devin Coughlin 2016-03-25 21:18:22 +00:00
parent 36c53fe147
commit b8076292ab
3 changed files with 150 additions and 11 deletions

View File

@ -98,7 +98,8 @@ class ObjCDeallocChecker
check::PointerEscape,
check::PreStmt<ReturnStmt>> {
mutable IdentifierInfo *NSObjectII, *SenTestCaseII, *Block_releaseII;
mutable IdentifierInfo *NSObjectII, *SenTestCaseII, *Block_releaseII,
*CIFilterII;
mutable Selector DeallocSel, ReleaseSel;
std::unique_ptr<BugType> MissingReleaseBugType;
@ -169,6 +170,8 @@ private:
void initIdentifierInfoAndSelectors(ASTContext &Ctx) const;
bool classHasSeparateTeardown(const ObjCInterfaceDecl *ID) const;
bool isReleasedByCIFilterDealloc(const ObjCPropertyImplDecl *PropImpl) const;
};
} // End anonymous namespace.
@ -688,19 +691,27 @@ bool ObjCDeallocChecker::diagnoseExtraRelease(SymbolRef ReleasedValue,
assert(PropDecl->getSetterKind() == ObjCPropertyDecl::Weak ||
(PropDecl->getSetterKind() == ObjCPropertyDecl::Assign &&
!PropDecl->isReadOnly()));
!PropDecl->isReadOnly()) ||
isReleasedByCIFilterDealloc(PropImpl)
);
const ObjCImplDecl *Container = getContainingObjCImpl(C.getLocationContext());
OS << "The '" << *PropImpl->getPropertyIvarDecl()
<< "' ivar in '" << *Container
<< "' was synthesized for ";
<< "' ivar in '" << *Container;
if (PropDecl->getSetterKind() == ObjCPropertyDecl::Weak)
OS << "a weak";
else
OS << "an assign, readwrite";
OS << " property but was released in 'dealloc'";
if (isReleasedByCIFilterDealloc(PropImpl)) {
OS << "' will be released by '-[CIFilter dealloc]' but also released here";
} else {
OS << "' was synthesized for ";
if (PropDecl->getSetterKind() == ObjCPropertyDecl::Weak)
OS << "a weak";
else
OS << "an assign, readwrite";
OS << " property but was released in 'dealloc'";
}
std::unique_ptr<BugReport> BR(
new BugReport(*ExtraReleaseBugType, OS.str(), ErrNode));
@ -751,7 +762,7 @@ bool ObjCDeallocChecker::diagnoseMistakenDealloc(SymbolRef DeallocedValue,
ObjCDeallocChecker::
ObjCDeallocChecker()
: NSObjectII(nullptr), SenTestCaseII(nullptr) {
: NSObjectII(nullptr), SenTestCaseII(nullptr), CIFilterII(nullptr) {
MissingReleaseBugType.reset(
new BugType(this, "Missing ivar release (leak)",
@ -774,6 +785,7 @@ void ObjCDeallocChecker::initIdentifierInfoAndSelectors(
NSObjectII = &Ctx.Idents.get("NSObject");
SenTestCaseII = &Ctx.Idents.get("SenTestCase");
Block_releaseII = &Ctx.Idents.get("_Block_release");
CIFilterII = &Ctx.Idents.get("CIFilter");
IdentifierInfo *DeallocII = &Ctx.Idents.get("dealloc");
IdentifierInfo *ReleaseII = &Ctx.Idents.get("release");
@ -894,6 +906,9 @@ ReleaseRequirement ObjCDeallocChecker::getDeallocReleaseRequirement(
// the value in their instance variables must be released in -dealloc.
case ObjCPropertyDecl::Retain:
case ObjCPropertyDecl::Copy:
if (isReleasedByCIFilterDealloc(PropImpl))
return ReleaseRequirement::MustNotReleaseDirectly;
return ReleaseRequirement::MustRelease;
case ObjCPropertyDecl::Weak:
@ -1019,6 +1034,37 @@ bool ObjCDeallocChecker::classHasSeparateTeardown(
return true;
}
/// The -dealloc method in CIFilter highly unusual in that is will release
/// instance variables belonging to its *subclasses* if the variable name
/// starts with "input" or backs a property whose name starts with "input".
/// Subclasses should not release these ivars in their own -dealloc method --
/// doing so could result in an over release.
///
/// This method returns true if the property will be released by
/// -[CIFilter dealloc].
bool ObjCDeallocChecker::isReleasedByCIFilterDealloc(
const ObjCPropertyImplDecl *PropImpl) const {
assert(PropImpl->getPropertyIvarDecl());
StringRef PropName = PropImpl->getPropertyDecl()->getName();
StringRef IvarName = PropImpl->getPropertyIvarDecl()->getName();
const char *ReleasePrefix = "input";
if (!(PropName.startswith(ReleasePrefix) ||
IvarName.startswith(ReleasePrefix))) {
return false;
}
const ObjCInterfaceDecl *ID =
PropImpl->getPropertyIvarDecl()->getContainingInterface();
for ( ; ID ; ID = ID->getSuperClass()) {
IdentifierInfo *II = ID->getIdentifier();
if (II == CIFilterII)
return true;
}
return false;
}
void ento::registerObjCDeallocChecker(CheckerManager &Mgr) {
const LangOptions &LangOpts = Mgr.getLangOpts();
// These checker only makes sense under MRR.

View File

@ -747,10 +747,100 @@ __attribute__((objc_root_class))
{
#if NON_ARC
// Only warn for synthesized ivars.
[okToDeallocDirectly dealloc]; // now-warning
[okToDeallocDirectly dealloc]; // no-warning
[_ivar dealloc]; // expected-warning {{'_ivar' should be released rather than deallocated}}
[super dealloc];
#endif
}
@end
// CIFilter special cases.
// By design, -[CIFilter dealloc] releases (by calling -setValue: forKey: with
// 'nil') all ivars (even in its *subclasses*) with names starting with
// 'input' or that are backed by properties with names starting with 'input'.
// The Dealloc checker needs to take particular care to not warn about missing
// releases in this case -- if the user adds a release quiet the
// warning it may result in an over release.
@interface ImmediateSubCIFilter : CIFilter {
NSObject *inputIvar;
NSObject *nonInputIvar;
NSObject *notPrefixedButBackingPrefixedProperty;
NSObject *inputPrefixedButBackingNonPrefixedProperty;
}
@property(retain) NSObject *inputIvar;
@property(retain) NSObject *nonInputIvar;
@property(retain) NSObject *inputAutoSynthesizedIvar;
@property(retain) NSObject *inputExplicitlySynthesizedToNonPrefixedIvar;
@property(retain) NSObject *nonPrefixedPropertyBackedByExplicitlySynthesizedPrefixedIvar;
@end
@implementation ImmediateSubCIFilter
@synthesize inputIvar = inputIvar;
@synthesize nonInputIvar = nonInputIvar;
@synthesize inputExplicitlySynthesizedToNonPrefixedIvar = notPrefixedButBackingPrefixedProperty;
@synthesize nonPrefixedPropertyBackedByExplicitlySynthesizedPrefixedIvar = inputPrefixedButBackingNonPrefixedProperty;
- (void)dealloc {
#if NON_ARC
// We don't want warnings here for:
// inputIvar
// inputAutoSynthesizedIvar
// inputExplicitlySynthesizedToNonPrefixedIvar
// inputPrefixedButBackingNonPrefixedProperty
[super dealloc];
// expected-warning@-1 {{The 'nonInputIvar' ivar in 'ImmediateSubCIFilter' was retained by a synthesized property but not released before '[super dealloc]'}}
#endif
}
@end
@interface SubSubCIFilter : CIFilter {
NSObject *inputIvarInSub;
}
@property(retain) NSObject *inputIvarInSub;
@end
@implementation SubSubCIFilter
@synthesize inputIvarInSub = inputIvarInSub;
- (void)dealloc {
// Don't warn about inputIvarInSub.
#if NON_ARC
[super dealloc];
#endif
}
@end
@interface OverreleasingCIFilter : CIFilter {
NSObject *inputIvar;
}
@property(retain) NSObject *inputIvar;
@end
@implementation OverreleasingCIFilter
@synthesize inputIvar = inputIvar;
- (void)dealloc {
#if NON_ARC
// This is an over release because CIFilter's dealloc will also release it.
[inputIvar release]; // expected-warning {{The 'inputIvar' ivar in 'OverreleasingCIFilter' will be released by '-[CIFilter dealloc]' but also released here}}
[super dealloc]; // no-warning
#endif
}
@end
@interface NotMissingDeallocCIFilter : CIFilter {
NSObject *inputIvar;
}
@property(retain) NSObject *inputIvar;
@end
@implementation NotMissingDeallocCIFilter // no-warning
@synthesize inputIvar = inputIvar;
@end

View File

@ -30,3 +30,6 @@ typedef struct objc_selector *SEL;
void _Block_release(const void *aBlock);
#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))
@interface CIFilter : NSObject
@end