RunLoopAutoreleaseLeakChecker.cpp 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. //=- RunLoopAutoreleaseLeakChecker.cpp --------------------------*- C++ -*-==//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //
  8. //===----------------------------------------------------------------------===//
  9. //
  10. // A checker for detecting leaks resulting from allocating temporary
  11. // autoreleased objects before starting the main run loop.
  12. //
  13. // Checks for two antipatterns:
  14. // 1. ObjCMessageExpr followed by [[NSRunLoop mainRunLoop] run] in the same
  15. // autorelease pool.
  16. // 2. ObjCMessageExpr followed by [[NSRunLoop mainRunLoop] run] in no
  17. // autorelease pool.
  18. //
  19. // Any temporary objects autoreleased in code called in those expressions
  20. // will not be deallocated until the program exits, and are effectively leaks.
  21. //
  22. //===----------------------------------------------------------------------===//
  23. //
  24. #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
  25. #include "clang/AST/Decl.h"
  26. #include "clang/AST/DeclObjC.h"
  27. #include "clang/ASTMatchers/ASTMatchFinder.h"
  28. #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
  29. #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
  30. #include "clang/StaticAnalyzer/Core/Checker.h"
  31. #include "clang/StaticAnalyzer/Core/CheckerManager.h"
  32. #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
  33. #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
  34. #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
  35. using namespace clang;
  36. using namespace ento;
  37. using namespace ast_matchers;
  38. namespace {
  39. const char * RunLoopBind = "NSRunLoopM";
  40. const char * RunLoopRunBind = "RunLoopRunM";
  41. const char * OtherMsgBind = "OtherMessageSentM";
  42. const char * AutoreleasePoolBind = "AutoreleasePoolM";
  43. const char * OtherStmtAutoreleasePoolBind = "OtherAutoreleasePoolM";
  44. class RunLoopAutoreleaseLeakChecker : public Checker<check::ASTCodeBody> {
  45. public:
  46. void checkASTCodeBody(const Decl *D,
  47. AnalysisManager &AM,
  48. BugReporter &BR) const;
  49. };
  50. } // end anonymous namespace
  51. /// \return Whether @c A occurs before @c B in traversal of
  52. /// @c Parent.
  53. /// Conceptually a very incomplete/unsound approximation of happens-before
  54. /// relationship (A is likely to be evaluated before B),
  55. /// but useful enough in this case.
  56. static bool seenBefore(const Stmt *Parent, const Stmt *A, const Stmt *B) {
  57. for (const Stmt *C : Parent->children()) {
  58. if (!C) continue;
  59. if (C == A)
  60. return true;
  61. if (C == B)
  62. return false;
  63. return seenBefore(C, A, B);
  64. }
  65. return false;
  66. }
  67. static void emitDiagnostics(BoundNodes &Match,
  68. const Decl *D,
  69. BugReporter &BR,
  70. AnalysisManager &AM,
  71. const RunLoopAutoreleaseLeakChecker *Checker) {
  72. assert(D->hasBody());
  73. const Stmt *DeclBody = D->getBody();
  74. AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
  75. const auto *ME = Match.getNodeAs<ObjCMessageExpr>(OtherMsgBind);
  76. assert(ME);
  77. const auto *AP =
  78. Match.getNodeAs<ObjCAutoreleasePoolStmt>(AutoreleasePoolBind);
  79. const auto *OAP =
  80. Match.getNodeAs<ObjCAutoreleasePoolStmt>(OtherStmtAutoreleasePoolBind);
  81. bool HasAutoreleasePool = (AP != nullptr);
  82. const auto *RL = Match.getNodeAs<ObjCMessageExpr>(RunLoopBind);
  83. const auto *RLR = Match.getNodeAs<Stmt>(RunLoopRunBind);
  84. assert(RLR && "Run loop launch not found");
  85. assert(ME != RLR);
  86. // Launch of run loop occurs before the message-sent expression is seen.
  87. if (seenBefore(DeclBody, RLR, ME))
  88. return;
  89. if (HasAutoreleasePool && (OAP != AP))
  90. return;
  91. PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
  92. ME, BR.getSourceManager(), ADC);
  93. SourceRange Range = ME->getSourceRange();
  94. BR.EmitBasicReport(ADC->getDecl(), Checker,
  95. /*Name=*/"Memory leak inside autorelease pool",
  96. /*BugCategory=*/"Memory",
  97. /*Name=*/
  98. (Twine("Temporary objects allocated in the") +
  99. " autorelease pool " +
  100. (HasAutoreleasePool ? "" : "of last resort ") +
  101. "followed by the launch of " +
  102. (RL ? "main run loop " : "xpc_main ") +
  103. "may never get released; consider moving them to a "
  104. "separate autorelease pool")
  105. .str(),
  106. Location, Range);
  107. }
  108. static StatementMatcher getRunLoopRunM(StatementMatcher Extra = anything()) {
  109. StatementMatcher MainRunLoopM =
  110. objcMessageExpr(hasSelector("mainRunLoop"),
  111. hasReceiverType(asString("NSRunLoop")),
  112. Extra)
  113. .bind(RunLoopBind);
  114. StatementMatcher MainRunLoopRunM = objcMessageExpr(hasSelector("run"),
  115. hasReceiver(MainRunLoopM),
  116. Extra).bind(RunLoopRunBind);
  117. StatementMatcher XPCRunM =
  118. callExpr(callee(functionDecl(hasName("xpc_main")))).bind(RunLoopRunBind);
  119. return anyOf(MainRunLoopRunM, XPCRunM);
  120. }
  121. static StatementMatcher getOtherMessageSentM(StatementMatcher Extra = anything()) {
  122. return objcMessageExpr(unless(anyOf(equalsBoundNode(RunLoopBind),
  123. equalsBoundNode(RunLoopRunBind))),
  124. Extra)
  125. .bind(OtherMsgBind);
  126. }
  127. static void
  128. checkTempObjectsInSamePool(const Decl *D, AnalysisManager &AM, BugReporter &BR,
  129. const RunLoopAutoreleaseLeakChecker *Chkr) {
  130. StatementMatcher RunLoopRunM = getRunLoopRunM();
  131. StatementMatcher OtherMessageSentM = getOtherMessageSentM(
  132. hasAncestor(autoreleasePoolStmt().bind(OtherStmtAutoreleasePoolBind)));
  133. StatementMatcher RunLoopInAutorelease =
  134. autoreleasePoolStmt(
  135. hasDescendant(RunLoopRunM),
  136. hasDescendant(OtherMessageSentM)).bind(AutoreleasePoolBind);
  137. DeclarationMatcher GroupM = decl(hasDescendant(RunLoopInAutorelease));
  138. auto Matches = match(GroupM, *D, AM.getASTContext());
  139. for (BoundNodes Match : Matches)
  140. emitDiagnostics(Match, D, BR, AM, Chkr);
  141. }
  142. static void
  143. checkTempObjectsInNoPool(const Decl *D, AnalysisManager &AM, BugReporter &BR,
  144. const RunLoopAutoreleaseLeakChecker *Chkr) {
  145. auto NoPoolM = unless(hasAncestor(autoreleasePoolStmt()));
  146. StatementMatcher RunLoopRunM = getRunLoopRunM(NoPoolM);
  147. StatementMatcher OtherMessageSentM = getOtherMessageSentM(NoPoolM);
  148. DeclarationMatcher GroupM = functionDecl(
  149. isMain(),
  150. hasDescendant(RunLoopRunM),
  151. hasDescendant(OtherMessageSentM)
  152. );
  153. auto Matches = match(GroupM, *D, AM.getASTContext());
  154. for (BoundNodes Match : Matches)
  155. emitDiagnostics(Match, D, BR, AM, Chkr);
  156. }
  157. void RunLoopAutoreleaseLeakChecker::checkASTCodeBody(const Decl *D,
  158. AnalysisManager &AM,
  159. BugReporter &BR) const {
  160. checkTempObjectsInSamePool(D, AM, BR, this);
  161. checkTempObjectsInNoPool(D, AM, BR, this);
  162. }
  163. void ento::registerRunLoopAutoreleaseLeakChecker(CheckerManager &mgr) {
  164. mgr.registerChecker<RunLoopAutoreleaseLeakChecker>();
  165. }
  166. bool ento::shouldRegisterRunLoopAutoreleaseLeakChecker(const CheckerManager &mgr) {
  167. return true;
  168. }