brintos

brintos / llvm-project-archived public Read only

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