r337876 - [analyzer] Syntactic matcher for leaks associated with run loop and autoreleasepool

George Karpenkov via cfe-commits cfe-commits at lists.llvm.org
Tue Jul 24 18:27:15 PDT 2018


Author: george.karpenkov
Date: Tue Jul 24 18:27:15 2018
New Revision: 337876

URL: http://llvm.org/viewvc/llvm-project?rev=337876&view=rev
Log:
[analyzer] Syntactic matcher for leaks associated with run loop and autoreleasepool

A checker for detecting leaks resulting from allocating temporary
autoreleasing objects before starting the main run loop.

Checks for two antipatterns:

1. ObjCMessageExpr followed by [[NARunLoop mainRunLoop] run] in the same
autorelease pool.

2. ObjCMessageExpr followed by [[NARunLoop mainRunLoop] run] in no
autorelease pool.

Happens-before relationship is modeled purely syntactically.

rdar://39299145

Differential Revision: https://reviews.llvm.org/D49528

Added:
    cfe/trunk/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp
    cfe/trunk/test/Analysis/Checkers/
    cfe/trunk/test/Analysis/Checkers/RunLoopAutoreleaseLeakChecker.m
Modified:
    cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td
    cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt
    cfe/trunk/test/Analysis/Inputs/system-header-simulator-for-objc-dealloc.h

Modified: cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td?rev=337876&r1=337875&r2=337876&view=diff
==============================================================================
--- cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td (original)
+++ cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td Tue Jul 24 18:27:15 2018
@@ -568,6 +568,10 @@ def ObjCPropertyChecker : Checker<"ObjCP
 
 let ParentPackage = Cocoa in {
 
+def RunLoopAutoreleaseLeakChecker : Checker<"RunLoopAutoreleaseLeak">,
+  HelpText<"Check for leaked memory in autorelease pools that will never be drained">,
+  DescFile<"RunLoopAutoreleaseLeakChecker.cpp">;
+
 def ObjCAtSyncChecker : Checker<"AtSync">,
   HelpText<"Check for nil pointers used as mutexes for @synchronized">,
   DescFile<"ObjCAtSyncChecker.cpp">;

Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt?rev=337876&r1=337875&r2=337876&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt (original)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt Tue Jul 24 18:27:15 2018
@@ -79,6 +79,7 @@ add_clang_library(clangStaticAnalyzerChe
   RetainCountChecker.cpp
   ReturnPointerRangeChecker.cpp
   ReturnUndefChecker.cpp
+  RunLoopAutoreleaseLeakChecker.cpp
   SimpleStreamChecker.cpp
   StackAddrEscapeChecker.cpp
   StdLibraryFunctionsChecker.cpp

Added: cfe/trunk/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp?rev=337876&view=auto
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp (added)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp Tue Jul 24 18:27:15 2018
@@ -0,0 +1,217 @@
+//=- RunLoopAutoreleaseLeakChecker.cpp --------------------------*- C++ -*-==//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//
+//===----------------------------------------------------------------------===//
+//
+// A checker for detecting leaks resulting from allocating temporary
+// autoreleased objects before starting the main run loop.
+//
+// Checks for two antipatterns:
+// 1. ObjCMessageExpr followed by [[NSRunLoop mainRunLoop] run] in the same
+// autorelease pool.
+// 2. ObjCMessageExpr followed by [[NSRunLoop mainRunLoop] run] in no
+// autorelease pool.
+//
+// Any temporary objects autoreleased in code called in those expressions
+// will not be deallocated until the program exits, and are effectively leaks.
+//
+//===----------------------------------------------------------------------===//
+//
+
+#include "ClangSACheckers.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
+
+using namespace clang;
+using namespace ento;
+using namespace ast_matchers;
+
+namespace {
+
+const char * RunLoopBind = "NSRunLoopM";
+const char * RunLoopRunBind = "RunLoopRunM";
+const char * OtherMsgBind = "OtherMessageSentM";
+const char * AutoreleasePoolBind = "AutoreleasePoolM";
+
+class RunLoopAutoreleaseLeakChecker : public Checker<
+                                      check::ASTCodeBody> {
+
+public:
+  void checkASTCodeBody(const Decl *D,
+                        AnalysisManager &AM,
+                        BugReporter &BR) const;
+
+};
+
+} // end anonymous namespace
+
+
+using TriBoolTy = Optional<bool>;
+using MemoizationMapTy = llvm::DenseMap<const Stmt *, Optional<TriBoolTy>>;
+
+static TriBoolTy
+seenBeforeRec(const Stmt *Parent, const Stmt *A, const Stmt *B,
+              MemoizationMapTy &Memoization) {
+  for (const Stmt *C : Parent->children()) {
+    if (C == A)
+      return true;
+
+    if (C == B)
+      return false;
+
+    Optional<TriBoolTy> &Cached = Memoization[C];
+    if (!Cached)
+      Cached = seenBeforeRec(C, A, B, Memoization);
+
+    if (Cached->hasValue())
+      return Cached->getValue();
+  }
+
+  return None;
+}
+
+/// \return Whether {@code A} occurs before {@code B} in traversal of
+/// {@code Parent}.
+/// Conceptually a very incomplete/unsound approximation of happens-before
+/// relationship (A is likely to be evaluated before B),
+/// but useful enough in this case.
+static bool seenBefore(const Stmt *Parent, const Stmt *A, const Stmt *B) {
+  MemoizationMapTy Memoization;
+  TriBoolTy Val = seenBeforeRec(Parent, A, B, Memoization);
+  return Val.getValue();
+}
+
+static void emitDiagnostics(BoundNodes &Match,
+                            const Decl *D,
+                            BugReporter &BR,
+                            AnalysisManager &AM,
+                            const RunLoopAutoreleaseLeakChecker *Checker) {
+
+  assert(D->hasBody());
+  const Stmt *DeclBody = D->getBody();
+
+  AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
+
+  const auto *ME = Match.getNodeAs<ObjCMessageExpr>(OtherMsgBind);
+  assert(ME);
+
+  const auto *AP =
+      Match.getNodeAs<ObjCAutoreleasePoolStmt>(AutoreleasePoolBind);
+  bool HasAutoreleasePool = (AP != nullptr);
+
+  const auto *RL = Match.getNodeAs<ObjCMessageExpr>(RunLoopBind);
+  const auto *RLR = Match.getNodeAs<Stmt>(RunLoopRunBind);
+  assert(RLR && "Run loop launch not found");
+
+  assert(ME != RLR);
+  if (HasAutoreleasePool && seenBefore(AP, RLR, ME))
+    return;
+
+  if (!HasAutoreleasePool && seenBefore(DeclBody, RLR, ME))
+    return;
+
+  PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
+    ME, BR.getSourceManager(), ADC);
+  SourceRange Range = ME->getSourceRange();
+
+  BR.EmitBasicReport(ADC->getDecl(), Checker,
+                     /*Name=*/"Memory leak inside autorelease pool",
+                     /*Category=*/"Memory",
+                     /*Name=*/
+                     (Twine("Temporary objects allocated in the") +
+                      " autorelease pool " +
+                      (HasAutoreleasePool ? "" : "of last resort ") +
+                      "followed by the launch of " +
+                      (RL ? "main run loop " : "xpc_main ") +
+                      "may never get released; consider moving them to a "
+                      "separate autorelease pool")
+                         .str(),
+                     Location, Range);
+}
+
+static StatementMatcher getRunLoopRunM(StatementMatcher Extra = anything()) {
+  StatementMatcher MainRunLoopM =
+      objcMessageExpr(hasSelector("mainRunLoop"),
+                      hasReceiverType(asString("NSRunLoop")),
+                      Extra)
+          .bind(RunLoopBind);
+
+  StatementMatcher MainRunLoopRunM = objcMessageExpr(hasSelector("run"),
+                         hasReceiver(MainRunLoopM),
+                         Extra).bind(RunLoopRunBind);
+
+  StatementMatcher XPCRunM =
+      callExpr(callee(functionDecl(hasName("xpc_main")))).bind(RunLoopRunBind);
+  return anyOf(MainRunLoopRunM, XPCRunM);
+}
+
+static StatementMatcher getOtherMessageSentM(StatementMatcher Extra = anything()) {
+  return objcMessageExpr(unless(anyOf(equalsBoundNode(RunLoopBind),
+                                      equalsBoundNode(RunLoopRunBind))),
+                         Extra)
+      .bind(OtherMsgBind);
+}
+
+static void
+checkTempObjectsInSamePool(const Decl *D, AnalysisManager &AM, BugReporter &BR,
+                           const RunLoopAutoreleaseLeakChecker *Chkr) {
+  StatementMatcher RunLoopRunM = getRunLoopRunM();
+  StatementMatcher OtherMessageSentM = getOtherMessageSentM();
+
+  StatementMatcher RunLoopInAutorelease =
+      autoreleasePoolStmt(
+        hasDescendant(RunLoopRunM),
+        hasDescendant(OtherMessageSentM)).bind(AutoreleasePoolBind);
+
+  DeclarationMatcher GroupM = decl(hasDescendant(RunLoopInAutorelease));
+
+  auto Matches = match(GroupM, *D, AM.getASTContext());
+  for (BoundNodes Match : Matches)
+    emitDiagnostics(Match, D, BR, AM, Chkr);
+}
+
+static void
+checkTempObjectsInNoPool(const Decl *D, AnalysisManager &AM, BugReporter &BR,
+                         const RunLoopAutoreleaseLeakChecker *Chkr) {
+
+  auto NoPoolM = unless(hasAncestor(autoreleasePoolStmt()));
+
+  StatementMatcher RunLoopRunM = getRunLoopRunM(NoPoolM);
+  StatementMatcher OtherMessageSentM = getOtherMessageSentM(NoPoolM);
+
+  DeclarationMatcher GroupM = functionDecl(
+    isMain(),
+    hasDescendant(RunLoopRunM),
+    hasDescendant(OtherMessageSentM)
+  );
+
+  auto Matches = match(GroupM, *D, AM.getASTContext());
+
+  for (BoundNodes Match : Matches)
+    emitDiagnostics(Match, D, BR, AM, Chkr);
+
+}
+
+void RunLoopAutoreleaseLeakChecker::checkASTCodeBody(const Decl *D,
+                        AnalysisManager &AM,
+                        BugReporter &BR) const {
+  checkTempObjectsInSamePool(D, AM, BR, this);
+  checkTempObjectsInNoPool(D, AM, BR, this);
+}
+
+void ento::registerRunLoopAutoreleaseLeakChecker(CheckerManager &mgr) {
+  mgr.registerChecker<RunLoopAutoreleaseLeakChecker>();
+}

Added: cfe/trunk/test/Analysis/Checkers/RunLoopAutoreleaseLeakChecker.m
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/Checkers/RunLoopAutoreleaseLeakChecker.m?rev=337876&view=auto
==============================================================================
--- cfe/trunk/test/Analysis/Checkers/RunLoopAutoreleaseLeakChecker.m (added)
+++ cfe/trunk/test/Analysis/Checkers/RunLoopAutoreleaseLeakChecker.m Tue Jul 24 18:27:15 2018
@@ -0,0 +1,104 @@
+// UNSUPPORTED: system-windows
+// RUN: %clang_analyze_cc1 -fobjc-arc -analyzer-checker=core,osx.cocoa.RunLoopAutoreleaseLeak %s -triple x86_64-darwin -verify
+// RUN: %clang_analyze_cc1 -DEXTRA=1 -DAP1=1 -fobjc-arc -analyzer-checker=core,osx.cocoa.RunLoopAutoreleaseLeak %s -triple x86_64-darwin -verify
+// RUN: %clang_analyze_cc1 -DEXTRA=1 -DAP2=1 -fobjc-arc -analyzer-checker=core,osx.cocoa.RunLoopAutoreleaseLeak %s -triple x86_64-darwin -verify
+// RUN: %clang_analyze_cc1 -DEXTRA=1 -DAP3=1 -fobjc-arc -analyzer-checker=core,osx.cocoa.RunLoopAutoreleaseLeak %s -triple x86_64-darwin -verify
+// RUN: %clang_analyze_cc1 -DEXTRA=1 -DAP4=1 -fobjc-arc -analyzer-checker=core,osx.cocoa.RunLoopAutoreleaseLeak %s -triple x86_64-darwin -verify
+
+#include "../Inputs/system-header-simulator-for-objc-dealloc.h"
+
+#ifndef EXTRA
+
+void just_runloop() { // No warning: no statements in between
+  @autoreleasepool {
+    [[NSRunLoop mainRunLoop] run]; // no-warning
+  }
+}
+
+void just_xpcmain() { // No warning: no statements in between
+  @autoreleasepool {
+    xpc_main(); // no-warning
+  }
+}
+
+void runloop_init_before() { // Warning: object created before the loop.
+  @autoreleasepool {
+    NSObject *object = [[NSObject alloc] init]; // expected-warning{{Temporary objects allocated in the autorelease pool followed by the launch of main run loop may never get released; consider moving them to a separate autorelease pool}}
+    (void) object;
+    [[NSRunLoop mainRunLoop] run]; 
+  }
+}
+
+void xpcmain_init_before() { // Warning: object created before the loop.
+  @autoreleasepool {
+    NSObject *object = [[NSObject alloc] init]; // expected-warning{{Temporary objects allocated in the autorelease pool followed by the launch of xpc_main may never get released; consider moving them to a separate autorelease pool}}
+    (void) object;
+    xpc_main(); 
+  }
+}
+
+void runloop_init_before_two_objects() { // Warning: object created before the loop.
+  @autoreleasepool {
+    NSObject *object = [[NSObject alloc] init]; // expected-warning{{Temporary objects allocated in the autorelease pool followed by the launch of main run loop may never get released; consider moving them to a separate autorelease pool}}
+    NSObject *object2 = [[NSObject alloc] init]; // no-warning, warning on the first one is enough.
+    (void) object;
+    (void) object2;
+    [[NSRunLoop mainRunLoop] run]; 
+  }
+}
+
+void runloop_no_autoreleasepool() {
+  NSObject *object = [[NSObject alloc] init]; // no-warning
+  (void)object;
+  [[NSRunLoop mainRunLoop] run];
+}
+
+void runloop_init_after() { // No warning: objects created after the loop
+  @autoreleasepool {
+    [[NSRunLoop mainRunLoop] run]; 
+    NSObject *object = [[NSObject alloc] init]; // no-warning
+    (void) object;
+  }
+}
+
+#endif
+
+#ifdef AP1
+int main() {
+    NSObject *object = [[NSObject alloc] init]; // expected-warning{{Temporary objects allocated in the autorelease pool of last resort followed by the launch of main run loop may never get released; consider moving them to a separate autorelease pool}}
+    (void) object;
+    [[NSRunLoop mainRunLoop] run]; 
+    return 0;
+}
+#endif
+
+#ifdef AP2
+// expected-no-diagnostics
+int main() {
+  NSObject *object = [[NSObject alloc] init]; // no-warning
+  (void) object;
+  @autoreleasepool {
+    [[NSRunLoop mainRunLoop] run]; 
+  }
+  return 0;
+}
+#endif
+
+#ifdef AP3
+// expected-no-diagnostics
+int main() {
+    [[NSRunLoop mainRunLoop] run];
+    NSObject *object = [[NSObject alloc] init]; // no-warning
+    (void) object;
+    return 0;
+}
+#endif
+
+#ifdef AP4
+int main() {
+    NSObject *object = [[NSObject alloc] init]; // expected-warning{{Temporary objects allocated in the autorelease pool of last resort followed by the launch of xpc_main may never get released; consider moving them to a separate autorelease pool}}
+    (void) object;
+    xpc_main();
+    return 0;
+}
+#endif

Modified: cfe/trunk/test/Analysis/Inputs/system-header-simulator-for-objc-dealloc.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/Inputs/system-header-simulator-for-objc-dealloc.h?rev=337876&r1=337875&r2=337876&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/Inputs/system-header-simulator-for-objc-dealloc.h (original)
+++ cfe/trunk/test/Analysis/Inputs/system-header-simulator-for-objc-dealloc.h Tue Jul 24 18:27:15 2018
@@ -18,6 +18,8 @@ typedef signed char BOOL;
 
 @interface NSRunLoop : NSObject
 + (NSRunLoop *)currentRunLoop;
++ (NSRunLoop *)mainRunLoop;
+- (void) run;
 - (void)cancelPerformSelectorsWithTarget:(id)target;
 @end
 
@@ -33,3 +35,5 @@ void _Block_release(const void *aBlock);
 
 @interface CIFilter : NSObject
 @end
+
+extern void xpc_main(void);




More information about the cfe-commits mailing list