Hi jordan_rose, zaks.anna, krememek,

Implement a path checker that warns about duplicate calls to
[super dealloc].  This is the first step before re-implementing
the use-after-free checks using a path checker (see D5042).

FIXME: This path checker needs to discriminate between different
'self' symbols when recursing into the -dealloc method in the
same class (for different objects).  See test case.

http://reviews.llvm.org/D5238

Files:
  lib/StaticAnalyzer/Checkers/Checkers.td
  lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp
  test/Analysis/DeallocUseAfterFreeErrors.m
Index: lib/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- lib/StaticAnalyzer/Checkers/Checkers.td
+++ lib/StaticAnalyzer/Checkers/Checkers.td
@@ -440,6 +440,11 @@
   HelpText<"Warn about Objective-C classes that lack a correct implementation of -dealloc">,
   DescFile<"CheckObjCDealloc.cpp">;
 
+def ObjCSuperDeallocChecker : Checker<"SuperDealloc">,
+  HelpText<"Warn about use of 'self' after '[super dealloc]' in Objective-C "
+           "-dealloc methods">,
+  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">;
Index: lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp
===================================================================
--- /dev/null
+++ lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp
@@ -0,0 +1,246 @@
+//===- ObjCSuperDeallocChecker.cpp - Check use of [super dealloc] message -===//
+//
+//                     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 checks for the
+// correct use of the [super dealloc] message in -dealloc 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 SuperDeallocState {
+
+  SymbolRef SuperDeallocSymbol;
+  unsigned BlockID;
+  const StackFrameContext *SFC;
+
+public:
+  SuperDeallocState(SymbolRef S, unsigned B, const StackFrameContext *SFC)
+      : SuperDeallocSymbol(S), BlockID(B), SFC(SFC) {}
+
+  SymbolRef getSuperDeallocSymbol() const { return SuperDeallocSymbol; }
+  const StackFrameContext *getStackFrameContext() const { return SFC; }
+
+  bool operator==(const SuperDeallocState &X) const {
+    return BlockID == X.BlockID && SFC == X.SFC &&
+           SuperDeallocSymbol == X.SuperDeallocSymbol;
+  }
+
+  void Profile(llvm::FoldingSetNodeID &ID) const {
+    ID.AddPointer(SuperDeallocSymbol);
+    ID.AddInteger(BlockID);
+    ID.AddPointer(SFC);
+  }
+};
+
+class SuperDeallocBRVisitor
+    : public BugReporterVisitorImpl<SuperDeallocBRVisitor> {
+
+  SymbolRef SuperDeallocSymbol;
+  const StackFrameContext *SFC;
+  bool Satisfied;
+
+public:
+  SuperDeallocBRVisitor(SymbolRef SuperDeallocSymbol,
+                        const StackFrameContext *SFC)
+      : SuperDeallocSymbol(SuperDeallocSymbol), SFC(SFC), Satisfied(false) {}
+
+  PathDiagnosticPiece *VisitNode(const ExplodedNode *Succ,
+                                 const ExplodedNode *Pred,
+                                 BugReporterContext &BRC,
+                                 BugReport &BR) override;
+
+  void Profile(llvm::FoldingSetNodeID &ID) const override {
+    ID.Add(SuperDeallocSymbol);
+    ID.Add(SFC);
+  }
+};
+
+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 shouldRunOnFunctionOrMethod(const NamedDecl *ND) const;
+
+public:
+  ObjCSuperDeallocChecker();
+  void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
+  void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
+};
+
+} // end anonymous namespace
+
+// Remember if we called [super dealloc] previously.
+REGISTER_LIST_WITH_PROGRAMSTATE(CalledSuperDealloc, SuperDeallocState)
+
+PathDiagnosticPiece *SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ,
+                                                      const ExplodedNode *Pred,
+                                                      BugReporterContext &BRC,
+                                                      BugReport &BR) {
+  if (Satisfied)
+    return nullptr;
+
+  const Expr *E = nullptr;
+
+  if (Optional<PostStmt> P = Succ->getLocationAs<PostStmt>())
+    E = P->getStmtAs<ObjCMessageExpr>();
+
+  if (!E)
+    return nullptr;
+
+  ProgramStateRef State = Succ->getState();
+  SVal S = State->getSVal(E, Succ->getLocationContext());
+  if (SuperDeallocSymbol == S.getAsSymbol() && SFC == Succ->getStackFrame()) {
+    Satisfied = true;
+
+    // Construct a new PathDiagnosticPiece.
+    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] originally called here");
+  }
+
+  return nullptr;
+}
+
+ObjCSuperDeallocChecker::ObjCSuperDeallocChecker()
+    : IIdealloc(nullptr), IINSObject(nullptr) {
+
+  DoubleSuperDeallocBugType.reset(
+      new BugType(this, "[super dealloc] should never be called twice",
+                  categories::CoreFoundationObjectiveC));
+}
+
+void ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M,
+                                                   CheckerContext &C) const {
+  initIdentifierInfoAndSelectors(C.getASTContext());
+
+  // FIXME: A callback should disable checkers at the start of functions.
+  if (!shouldRunOnFunctionOrMethod(
+          dyn_cast<NamedDecl>(C.getCurrentAnalysisDeclContext()->getDecl())))
+    return;
+
+  // Check for [super dealloc] method call.
+  if (M.getOriginExpr()->getReceiverKind() == ObjCMessageExpr::SuperInstance &&
+      M.getSelector() == SELdealloc) {
+    ProgramStateRef State = C.getState();
+    SVal V = State->getSVal(M.getOriginExpr(), C.getLocationContext());
+    State = State->add<CalledSuperDealloc>(
+        SuperDeallocState(V.getAsSymbol(), C.getBlockID(), C.getStackFrame()));
+    C.addTransition(State);
+  }
+}
+
+void ObjCSuperDeallocChecker::checkPreObjCMessage(const ObjCMethodCall &M,
+                                                  CheckerContext &C) const {
+  initIdentifierInfoAndSelectors(C.getASTContext());
+
+  // If [super dealloc] has not been called, there is nothing to do.
+  ProgramStateRef State = C.getState();
+  CalledSuperDeallocTy SDStateList = State->get<CalledSuperDealloc>();
+  if (SDStateList.isEmpty())
+    return;
+
+  assert(++SDStateList.begin() == SDStateList.end() &&
+         "There should only ever be one SuperDeallocState in the list "
+         "because a second one causes a sink node to be created.");
+
+  // Check for a duplicate [super dealloc] method call.
+  if (M.getOriginExpr()->getReceiverKind() == ObjCMessageExpr::SuperInstance &&
+      M.getSelector() == SELdealloc) {
+
+    // We have reached a bug that likely causes a crash, so stop exploring the
+    // path by generating a sink.
+    ExplodedNode *ErrNode = C.generateSink();
+    // If we've already reached this node on another path, return.
+    if (!ErrNode)
+      return;
+
+    // Generate the report.
+    std::unique_ptr<BugReport> R(
+        new BugReport(*DoubleSuperDeallocBugType,
+                      "[super dealloc] called a second time", ErrNode));
+    R->addRange(M.getOriginExpr()->getSourceRange());
+    const SuperDeallocState &SDState = SDStateList.getHead();
+    R->addVisitor(new SuperDeallocBRVisitor(SDState.getSuperDeallocSymbol(),
+                                            SDState.getStackFrameContext()));
+    C.emitReport(R.release());
+
+    return;
+  }
+}
+
+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);
+}
+
+// FIXME: A callback should disable checkers at the start of functions.
+bool ObjCSuperDeallocChecker::shouldRunOnFunctionOrMethod(
+    const NamedDecl *ND) const {
+
+  if (!ND)
+    return false;
+
+  const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(ND);
+  if (!MD)
+    return false;
+
+  // [super dealloc] only applies to NSObject subclasses.
+  for (ObjCInterfaceDecl *ID = MD->getClassInterface()->getSuperClass(); ID;
+       ID = ID->getSuperClass()) {
+
+    IdentifierInfo *II = ID->getIdentifier();
+
+    if (II == IINSObject)
+      return true;
+  }
+
+  return false;
+}
+
+//===----------------------------------------------------------------------===//
+// Checker Registration.
+//===----------------------------------------------------------------------===//
+
+void ento::registerObjCSuperDeallocChecker(CheckerManager &Mgr) {
+  const LangOptions &LangOpts = Mgr.getLangOpts();
+  if (LangOpts.getGC() == LangOptions::GCOnly || LangOpts.ObjCAutoRefCount)
+    return;
+  Mgr.registerChecker<ObjCSuperDeallocChecker>();
+}
Index: test/Analysis/DeallocUseAfterFreeErrors.m
===================================================================
--- /dev/null
+++ test/Analysis/DeallocUseAfterFreeErrors.m
@@ -0,0 +1,203 @@
+// RUN: %clang_cc1 -analyze -analyzer-checker=alpha.osx.cocoa.SuperDealloc -verify %s
+
+#define nil ((id)0)
+
+typedef unsigned long NSUInteger;
+@protocol NSObject
+- (instancetype)retain;
+- (oneway void)release;
+@end
+
+@interface NSObject <NSObject> { }
+- (void)dealloc;
+- (instancetype)init;
+@end
+
+typedef struct objc_selector *SEL;
+
+//===------------------------------------------------------------------------===
+//  <rdar://problem/6953275>
+//  Check that 'self' is not referenced after calling '[super dealloc]'.
+
+@interface SuperDeallocThenReleaseIvarClass : NSObject {
+  NSObject *_ivar;
+}
+@end
+
+@implementation SuperDeallocThenReleaseIvarClass
+- (instancetype)initWithIvar:(NSObject *)ivar {
+  self = [super init];
+  if (!self)
+    return nil;
+  _ivar = [ivar retain];
+  return self;
+}
+- (void)dealloc {
+  [super dealloc];
+  [_ivar release];
+}
+@end
+
+@interface SuperDeallocThenAssignNilToIvarClass : NSObject {
+  NSObject *_delegate;
+}
+@end
+
+@implementation SuperDeallocThenAssignNilToIvarClass
+- (instancetype)initWithDelegate:(NSObject *)delegate {
+  self = [super init];
+  if (!self)
+    return nil;
+  _delegate = delegate;
+  return self;
+}
+- (void)dealloc {
+  [super dealloc];
+  _delegate = nil;
+}
+@end
+
+@interface SuperDeallocThenReleasePropertyClass : NSObject { }
+@property (retain) NSObject *ivar;
+@end
+
+@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;
+}
+@end
+
+@interface SuperDeallocThenAssignNilToPropertyClass : NSObject { }
+@property (assign) NSObject *delegate;
+@end
+
+@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;
+}
+@end
+
+@interface SuperDeallocThenCallInstanceMethodClass : NSObject { }
+- (void)_invalidate;
+@end
+
+@implementation SuperDeallocThenCallInstanceMethodClass
+- (void)_invalidate {
+}
+- (void)dealloc {
+  [super dealloc];
+  [self _invalidate];
+}
+@end
+
+@interface SuperDeallocThenCallNonObjectiveCMethodClass : NSObject { }
+@end
+
+static void _invalidate(NSObject *object) {
+  (void)object;
+}
+
+@implementation SuperDeallocThenCallNonObjectiveCMethodClass
+- (void)dealloc {
+  [super dealloc];
+  _invalidate(self);
+}
+@end
+
+@interface TwoSuperDeallocCallsClass : NSObject {
+  NSObject *_ivar;
+}
+- (void)_invalidate;
+@end
+
+@implementation TwoSuperDeallocCallsClass
+- (void)_invalidate {
+}
+- (void)dealloc {
+  if (_ivar) {
+    [_ivar release];
+    [super dealloc];
+    return;
+  }
+  [super dealloc];
+  [self _invalidate];
+}
+@end
+
+@interface MissingReturnCausesDoubleSuperDeallocClass : NSObject {
+  NSObject *_ivar;
+}
+@end
+
+@implementation MissingReturnCausesDoubleSuperDeallocClass
+- (void)dealloc {
+  if (_ivar) {
+    [_ivar release];
+    [super dealloc];
+    //return;
+  }
+  [super dealloc]; // expected-warning{{[super dealloc] called a second time}}
+}
+@end
+
+@interface SuperDeallocInOtherMethodClass : NSObject {
+  NSObject *_ivar;
+}
+- (void)_cleanup;
+@end
+
+@implementation SuperDeallocInOtherMethodClass
+- (void)_cleanup {
+  [_ivar release];
+  [super dealloc];
+}
+- (void)dealloc {
+  [self _cleanup];
+  [super dealloc]; // expected-warning{{[super dealloc] called a second time}}
+}
+@end
+
+// A class that contains an ivar of itself can generate a false positive that
+// [super dealloc] is called twice if each object instance is not tracked
+// separately by the checker, and if custom reference counting is implemented,
+// as happens when _OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN is used.
+// This is just a simple approximation to trigger the bug.
+@class ClassWithOwnIvarInstanceClass;
+@interface ClassWithOwnIvarInstanceClass : NSObject {
+  ClassWithOwnIvarInstanceClass *_ivar;
+  NSUInteger _retainCount;
+}
+@end
+
+@implementation ClassWithOwnIvarInstanceClass
+- (instancetype)retain {
+  ++_retainCount;
+  return self;
+}
+- (oneway void)release {
+  --_retainCount;
+  if (!_retainCount)
+    [self dealloc];
+}
+- (void)dealloc {
+  [_ivar release];
+  [super dealloc]; // expected-warning{{[super dealloc] called a second time}}
+  // FIXME: This is a false-positive since the checker does not track 'self' values for different objects separately.
+}
+@end
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to