r261545 - [analyzer] Detect duplicate [super dealloc] calls
Devin Coughlin via cfe-commits
cfe-commits at lists.llvm.org
Mon Feb 22 09:56:25 PST 2016
Author: dcoughlin
Date: Mon Feb 22 11:56:24 2016
New Revision: 261545
URL: http://llvm.org/viewvc/llvm-project?rev=261545&view=rev
Log:
[analyzer] Detect duplicate [super dealloc] calls
Add an alpha path checker that warns about duplicate calls to [super dealloc].
This will form the foundation of a checker that will detect uses of
'self' after calling [super dealloc].
Part of rdar://problem/6953275.
Based on a patch by David Kilzer!
Differential Revision: http://reviews.llvm.org/D5238
Added:
cfe/trunk/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp
cfe/trunk/test/Analysis/DeallocUseAfterFreeErrors.m
Modified:
cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt
cfe/trunk/lib/StaticAnalyzer/Checkers/Checkers.td
Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt?rev=261545&r1=261544&r2=261545&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt (original)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt Mon Feb 22 11:56:24 2016
@@ -56,6 +56,7 @@ add_clang_library(clangStaticAnalyzerChe
ObjCContainersChecker.cpp
ObjCMissingSuperCallChecker.cpp
ObjCSelfInitChecker.cpp
+ ObjCSuperDeallocChecker.cpp
ObjCUnusedIVarsChecker.cpp
PaddingChecker.cpp
PointerArithChecker.cpp
Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/Checkers.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/Checkers.td?rev=261545&r1=261544&r2=261545&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/Checkers.td (original)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/Checkers.td Mon Feb 22 11:56:24 2016
@@ -513,6 +513,10 @@ def ObjCDeallocChecker : Checker<"Deallo
HelpText<"Warn about Objective-C classes that lack a correct implementation of -dealloc">,
DescFile<"CheckObjCDealloc.cpp">;
+def ObjCSuperDeallocChecker : Checker<"SuperDealloc">,
+ HelpText<"Warn about improper use of '[super dealloc]' in Objective-C">,
+ DescFile<"ObjCSuperDeallocChecker.cpp">;
+
def InstanceVariableInvalidation : Checker<"InstanceVariableInvalidation">,
HelpText<"Check that the invalidatable instance variables are invalidated in the methods annotated with objc_instance_variable_invalidator">,
DescFile<"IvarInvalidationChecker.cpp">;
Added: cfe/trunk/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp?rev=261545&view=auto
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp (added)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp Mon Feb 22 11:56:24 2016
@@ -0,0 +1,197 @@
+//===- ObjCSuperDeallocChecker.cpp - Check correct use of [super dealloc] -===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This defines ObjCSuperDeallocChecker, a builtin check that warns when
+// [super dealloc] is called twice on the same instance in MRR mode.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+class ObjCSuperDeallocChecker
+ : public Checker<check::PostObjCMessage, check::PreObjCMessage> {
+
+ mutable IdentifierInfo *IIdealloc, *IINSObject;
+ mutable Selector SELdealloc;
+
+ std::unique_ptr<BugType> DoubleSuperDeallocBugType;
+
+ void initIdentifierInfoAndSelectors(ASTContext &Ctx) const;
+
+ bool isSuperDeallocMessage(const ObjCMethodCall &M) const;
+
+public:
+ ObjCSuperDeallocChecker();
+ void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
+ void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
+};
+
+} // End anonymous namespace.
+
+// Remember whether [super dealloc] has previously been called on the
+// a SymbolRef for the receiver.
+REGISTER_SET_WITH_PROGRAMSTATE(CalledSuperDealloc, SymbolRef)
+
+class SuperDeallocBRVisitor final
+ : public BugReporterVisitorImpl<SuperDeallocBRVisitor> {
+
+ SymbolRef ReceiverSymbol;
+ bool Satisfied;
+
+public:
+ SuperDeallocBRVisitor(SymbolRef ReceiverSymbol)
+ : ReceiverSymbol(ReceiverSymbol),
+ Satisfied(false) {}
+
+ PathDiagnosticPiece *VisitNode(const ExplodedNode *Succ,
+ const ExplodedNode *Pred,
+ BugReporterContext &BRC,
+ BugReport &BR) override;
+
+ void Profile(llvm::FoldingSetNodeID &ID) const override {
+ ID.Add(ReceiverSymbol);
+ }
+};
+
+void ObjCSuperDeallocChecker::checkPreObjCMessage(const ObjCMethodCall &M,
+ CheckerContext &C) const {
+ if (!isSuperDeallocMessage(M))
+ return;
+
+ ProgramStateRef State = C.getState();
+ SymbolRef ReceiverSymbol = M.getReceiverSVal().getAsSymbol();
+ assert(ReceiverSymbol && "No receiver symbol at call to [super dealloc]?");
+
+ bool AlreadyCalled = State->contains<CalledSuperDealloc>(ReceiverSymbol);
+
+ // If [super dealloc] has not been called, there is nothing to do. We'll
+ // note the fact that [super dealloc] was called in checkPostObjCMessage.
+ if (!AlreadyCalled)
+ return;
+
+ // We have a duplicate [super dealloc] method call.
+ // This likely causes a crash, so stop exploring the
+ // path by generating a sink.
+ ExplodedNode *ErrNode = C.generateErrorNode();
+ // If we've already reached this node on another path, return.
+ if (!ErrNode)
+ return;
+
+ // Generate the report.
+ std::unique_ptr<BugReport> BR(
+ new BugReport(*DoubleSuperDeallocBugType,
+ "[super dealloc] should not be called multiple times",
+ ErrNode));
+ BR->addRange(M.getOriginExpr()->getSourceRange());
+ BR->addVisitor(llvm::make_unique<SuperDeallocBRVisitor>(ReceiverSymbol));
+ C.emitReport(std::move(BR));
+
+ return;
+}
+
+void ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M,
+ CheckerContext &C) const {
+ // Check for [super dealloc] method call.
+ if (!isSuperDeallocMessage(M))
+ return;
+
+ ProgramStateRef State = C.getState();
+ SymbolRef ReceiverSymbol = M.getSelfSVal().getAsSymbol();
+ assert(ReceiverSymbol && "No receiver symbol at call to [super dealloc]?");
+
+ // We add this transition in checkPostObjCMessage to avoid warning when
+ // we inline a call to [super dealloc] where the inlined call itself
+ // calls [super dealloc].
+ State = State->add<CalledSuperDealloc>(ReceiverSymbol);
+ C.addTransition(State);
+}
+
+ObjCSuperDeallocChecker::ObjCSuperDeallocChecker()
+ : IIdealloc(nullptr), IINSObject(nullptr) {
+
+ DoubleSuperDeallocBugType.reset(
+ new BugType(this, "[super dealloc] should not be called more than once",
+ categories::CoreFoundationObjectiveC));
+}
+
+void
+ObjCSuperDeallocChecker::initIdentifierInfoAndSelectors(ASTContext &Ctx) const {
+ if (IIdealloc)
+ return;
+
+ IIdealloc = &Ctx.Idents.get("dealloc");
+ IINSObject = &Ctx.Idents.get("NSObject");
+
+ SELdealloc = Ctx.Selectors.getSelector(0, &IIdealloc);
+}
+
+bool
+ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const {
+ if (M.getOriginExpr()->getReceiverKind() != ObjCMessageExpr::SuperInstance)
+ return false;
+
+ ASTContext &Ctx = M.getState()->getStateManager().getContext();
+ initIdentifierInfoAndSelectors(Ctx);
+
+ return M.getSelector() == SELdealloc;
+}
+
+PathDiagnosticPiece *SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ,
+ const ExplodedNode *Pred,
+ BugReporterContext &BRC,
+ BugReport &BR) {
+ if (Satisfied)
+ return nullptr;
+
+ ProgramStateRef State = Succ->getState();
+
+ bool CalledNow =
+ Succ->getState()->contains<CalledSuperDealloc>(ReceiverSymbol);
+ bool CalledBefore =
+ Pred->getState()->contains<CalledSuperDealloc>(ReceiverSymbol);
+
+ // Is Succ the node on which the analyzer noted that [super dealloc] was
+ // called on ReceiverSymbol?
+ if (CalledNow && !CalledBefore) {
+ Satisfied = true;
+
+ ProgramPoint P = Succ->getLocation();
+ PathDiagnosticLocation L =
+ PathDiagnosticLocation::create(P, BRC.getSourceManager());
+
+ if (!L.isValid() || !L.asLocation().isValid())
+ return nullptr;
+
+ return new PathDiagnosticEventPiece(
+ L, "[super dealloc] called here");
+ }
+
+ return nullptr;
+}
+
+//===----------------------------------------------------------------------===//
+// Checker Registration.
+//===----------------------------------------------------------------------===//
+
+void ento::registerObjCSuperDeallocChecker(CheckerManager &Mgr) {
+ const LangOptions &LangOpts = Mgr.getLangOpts();
+ if (LangOpts.getGC() == LangOptions::GCOnly || LangOpts.ObjCAutoRefCount)
+ return;
+ Mgr.registerChecker<ObjCSuperDeallocChecker>();
+}
Added: cfe/trunk/test/Analysis/DeallocUseAfterFreeErrors.m
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/DeallocUseAfterFreeErrors.m?rev=261545&view=auto
==============================================================================
--- cfe/trunk/test/Analysis/DeallocUseAfterFreeErrors.m (added)
+++ cfe/trunk/test/Analysis/DeallocUseAfterFreeErrors.m Mon Feb 22 11:56:24 2016
@@ -0,0 +1,298 @@
+// RUN: %clang_cc1 -analyze -analyzer-checker=core,alpha.osx.cocoa.SuperDealloc,debug.ExprInspection -analyzer-output=text -verify %s
+
+void clang_analyzer_warnIfReached();
+
+#define nil ((id)0)
+
+typedef unsigned long NSUInteger;
+ at protocol NSObject
+- (instancetype)retain;
+- (oneway void)release;
+ at end
+
+ at interface NSObject <NSObject> { }
+- (void)dealloc;
+- (instancetype)init;
+ at end
+
+typedef struct objc_selector *SEL;
+
+//===------------------------------------------------------------------------===
+// <rdar://problem/6953275>
+// Check that 'self' is not referenced after calling '[super dealloc]'.
+
+ at interface SuperDeallocThenReleaseIvarClass : NSObject {
+ NSObject *_ivar;
+}
+ at end
+
+ at implementation SuperDeallocThenReleaseIvarClass
+- (instancetype)initWithIvar:(NSObject *)ivar {
+ self = [super init];
+ if (!self)
+ return nil;
+ _ivar = [ivar retain];
+ return self;
+}
+- (void)dealloc {
+ [super dealloc];
+ [_ivar release];
+}
+ at end
+
+ at interface SuperDeallocThenAssignNilToIvarClass : NSObject {
+ NSObject *_delegate;
+}
+ at end
+
+ at implementation SuperDeallocThenAssignNilToIvarClass
+- (instancetype)initWithDelegate:(NSObject *)delegate {
+ self = [super init];
+ if (!self)
+ return nil;
+ _delegate = delegate;
+ return self;
+}
+- (void)dealloc {
+ [super dealloc];
+ _delegate = nil;
+}
+ at end
+
+ at interface SuperDeallocThenReleasePropertyClass : NSObject { }
+ at property (retain) NSObject *ivar;
+ at end
+
+ at implementation SuperDeallocThenReleasePropertyClass
+- (instancetype)initWithProperty:(NSObject *)ivar {
+ self = [super init];
+ if (!self)
+ return nil;
+ self.ivar = ivar;
+ return self;
+}
+- (void)dealloc {
+ [super dealloc];
+ self.ivar = nil;
+}
+ at end
+
+ at interface SuperDeallocThenAssignNilToPropertyClass : NSObject { }
+ at property (assign) NSObject *delegate;
+ at end
+
+ at implementation SuperDeallocThenAssignNilToPropertyClass
+- (instancetype)initWithDelegate:(NSObject *)delegate {
+ self = [super init];
+ if (!self)
+ return nil;
+ self.delegate = delegate;
+ return self;
+}
+- (void)dealloc {
+ [super dealloc];
+ self.delegate = nil;
+}
+ at end
+
+ at interface SuperDeallocThenCallInstanceMethodClass : NSObject { }
+- (void)_invalidate;
+ at end
+
+ at implementation SuperDeallocThenCallInstanceMethodClass
+- (void)_invalidate {
+}
+- (void)dealloc {
+ [super dealloc];
+ [self _invalidate];
+}
+ at end
+
+ at interface SuperDeallocThenCallNonObjectiveCMethodClass : NSObject { }
+ at end
+
+static void _invalidate(NSObject *object) {
+ (void)object;
+}
+
+ at implementation SuperDeallocThenCallNonObjectiveCMethodClass
+- (void)dealloc {
+ [super dealloc];
+ _invalidate(self);
+}
+ at end
+
+ at interface TwoSuperDeallocCallsClass : NSObject {
+ NSObject *_ivar;
+}
+- (void)_invalidate;
+ at end
+
+ at implementation TwoSuperDeallocCallsClass
+- (void)_invalidate {
+}
+- (void)dealloc {
+ if (_ivar) {
+ [_ivar release];
+ [super dealloc];
+ return;
+ }
+ [super dealloc];
+ [self _invalidate];
+}
+ at end
+
+//===------------------------------------------------------------------------===
+// Warn about calling [super dealloc] twice due to missing return statement.
+
+ at interface MissingReturnCausesDoubleSuperDeallocClass : NSObject {
+ NSObject *_ivar;
+}
+ at end
+
+ at implementation MissingReturnCausesDoubleSuperDeallocClass
+- (void)dealloc {
+ if (_ivar) { // expected-note {{Taking true branch}}
+ [_ivar release];
+ [super dealloc]; // expected-note {{[super dealloc] called here}}
+ // return;
+ }
+ [super dealloc]; // expected-warning{{[super dealloc] should not be called multiple times}}
+ // expected-note at -1{{[super dealloc] should not be called multiple times}}
+}
+ at end
+
+//===------------------------------------------------------------------------===
+// Warn about calling [super dealloc] twice in two different methods.
+
+ at interface SuperDeallocInOtherMethodClass : NSObject {
+ NSObject *_ivar;
+}
+- (void)_cleanup;
+ at end
+
+ at implementation SuperDeallocInOtherMethodClass
+- (void)_cleanup {
+ [_ivar release];
+ [super dealloc]; // expected-note {{[super dealloc] called here}}
+}
+- (void)dealloc {
+ [self _cleanup]; // expected-note {{Calling '_cleanup'}}
+ //expected-note at -1 {{Returning from '_cleanup'}}
+ [super dealloc]; // expected-warning {{[super dealloc] should not be called multiple times}}
+ // expected-note at -1 {{[super dealloc] should not be called multiple times}}
+}
+ at end
+
+//===------------------------------------------------------------------------===
+// Do not warn about calling [super dealloc] recursively for different objects
+// of the same type with custom retain counting.
+//
+// A class that contains an ivar of itself with custom retain counting (such
+// as provided by _OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN) can generate
+// a false positive that [super dealloc] is called twice if each object instance
+// is not tracked separately by the checker. This test case is just a simple
+// approximation to trigger the false positive.
+
+ at class ClassWithOwnIvarInstanceClass;
+ at interface ClassWithOwnIvarInstanceClass : NSObject {
+ ClassWithOwnIvarInstanceClass *_ivar;
+ NSUInteger _retainCount;
+}
+ at end
+
+ at implementation ClassWithOwnIvarInstanceClass
+- (instancetype)retain {
+ ++_retainCount;
+ return self;
+}
+- (oneway void)release {
+ --_retainCount;
+ if (!_retainCount)
+ [self dealloc];
+}
+- (void)dealloc {
+ [_ivar release];
+ [super dealloc]; // no warning: different instances of same class
+}
+ at end
+
+//===------------------------------------------------------------------------===
+// Do not warn about calling [super dealloc] twice if +dealloc is a class
+// method.
+
+ at interface SuperDeallocClassMethodIgnoredClass : NSObject { }
++ (void)dealloc;
+ at end
+
+ at implementation SuperDeallocClassMethodIgnoredClass
++ (void)dealloc { }
+ at end
+
+ at interface SuperDeallocClassMethodIgnoredSubClass : NSObject { }
++ (void)dealloc;
+ at end
+
+ at implementation SuperDeallocClassMethodIgnoredSubClass
++ (void)dealloc {
+ [super dealloc];
+ [super dealloc]; // no warning: class method
+}
+ at end
+
+//===------------------------------------------------------------------------===
+// Do not warn about calling [super dealloc] twice if when the analyzer has
+// inlined the call to its super deallocator.
+
+ at interface SuperClassCallingSuperDealloc : NSObject
+ at end
+
+ at implementation SuperClassCallingSuperDealloc
+- (void)dealloc; {
+ [super dealloc];
+}
+ at end
+
+ at interface SubclassCallingSuperDealloc : SuperClassCallingSuperDealloc
+ at end
+
+ at implementation SubclassCallingSuperDealloc
+- (void)dealloc; {
+ [super dealloc];
+}
+ at end
+
+//===------------------------------------------------------------------------===
+// Treat calling [super dealloc] twice as as a sink.
+
+ at interface CallingSuperDeallocTwiceIsSink : NSObject
+ at end
+
+ at implementation CallingSuperDeallocTwiceIsSink
+- (void)dealloc; {
+ [super dealloc]; // expected-note {{[super dealloc] called here}}
+ [super dealloc]; // expected-warning {{[super dealloc] should not be called multiple times}}
+ // expected-note at -1 {{[super dealloc] should not be called multiple times}}
+
+ clang_analyzer_warnIfReached(); // no-warning
+}
+ at end
+
+
+//===------------------------------------------------------------------------===
+// Test path notes with intervening method call on self.
+
+ at interface InterveningMethodCallOnSelf : NSObject
+ at end
+
+ at implementation InterveningMethodCallOnSelf
+- (void)anotherMethod {
+}
+
+- (void)dealloc; {
+ [super dealloc]; // expected-note {{[super dealloc] called here}}
+ [self anotherMethod];
+ [super dealloc]; // expected-warning {{[super dealloc] should not be called multiple times}}
+ // expected-note at -1 {{[super dealloc] should not be called multiple times}}
+}
+ at end
More information about the cfe-commits
mailing list