brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.9 KiB · 5637941 Raw
231 lines · cpp
1//===- GCDAntipatternChecker.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// This file defines GCDAntipatternChecker which checks against a common10// antipattern when synchronous API is emulated from asynchronous callbacks11// using a semaphore:12//13//   dispatch_semaphore_t sema = dispatch_semaphore_create(0);14//15//   AnyCFunctionCall(^{16//     // code…17//     dispatch_semaphore_signal(sema);18//   })19//   dispatch_semaphore_wait(sema, *)20//21// Such code is a common performance problem, due to inability of GCD to22// properly handle QoS when a combination of queues and semaphores is used.23// Good code would either use asynchronous API (when available), or perform24// the necessary action in asynchronous callback.25//26// Currently, the check is performed using a simple heuristical AST pattern27// matching.28//29//===----------------------------------------------------------------------===//30 31#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"32#include "clang/ASTMatchers/ASTMatchFinder.h"33#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"34#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"35#include "clang/StaticAnalyzer/Core/Checker.h"36#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"37#include "llvm/Support/Debug.h"38 39using namespace clang;40using namespace ento;41using namespace ast_matchers;42 43namespace {44 45// ID of a node at which the diagnostic would be emitted.46const char *WarnAtNode = "waitcall";47 48class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {49public:50  void checkASTCodeBody(const Decl *D,51                        AnalysisManager &AM,52                        BugReporter &BR) const;53};54 55decltype(auto) callsName(const char *FunctionName) {56  return callee(functionDecl(hasName(FunctionName)));57}58 59decltype(auto) equalsBoundArgDecl(int ArgIdx, const char *DeclName) {60  return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(61                                 to(varDecl(equalsBoundNode(DeclName))))));62}63 64decltype(auto) bindAssignmentToDecl(const char *DeclName) {65  return hasLHS(ignoringParenImpCasts(66                         declRefExpr(to(varDecl().bind(DeclName)))));67}68 69/// The pattern is very common in tests, and it is OK to use it there.70/// We have to heuristics for detecting tests: method name starts with "test"71/// (used in XCTest), and a class name contains "mock" or "test" (used in72/// helpers which are not tests themselves, but used exclusively in tests).73static bool isTest(const Decl *D) {74  if (const auto* ND = dyn_cast<NamedDecl>(D)) {75    std::string DeclName = ND->getNameAsString();76    if (StringRef(DeclName).starts_with("test"))77      return true;78  }79  if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) {80    if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) {81      std::string ContainerName = CD->getNameAsString();82      StringRef CN(ContainerName);83      if (CN.contains_insensitive("test") || CN.contains_insensitive("mock"))84        return true;85    }86  }87  return false;88}89 90static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {91 92  const char *SemaphoreBinding = "semaphore_name";93  auto SemaphoreCreateM = callExpr(allOf(94      callsName("dispatch_semaphore_create"),95      hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));96 97  auto SemaphoreBindingM = anyOf(98      forEachDescendant(99          varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),100      forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),101                     hasRHS(SemaphoreCreateM))));102 103  auto HasBlockArgumentM = hasAnyArgument(hasType(104            hasCanonicalType(blockPointerType())105            ));106 107  auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(108          allOf(109              callsName("dispatch_semaphore_signal"),110              equalsBoundArgDecl(0, SemaphoreBinding)111              )))));112 113  auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);114 115  auto HasBlockCallingSignalM =116    forEachDescendant(117      stmt(anyOf(118        callExpr(HasBlockAndCallsSignalM),119        objcMessageExpr(HasBlockAndCallsSignalM)120           )));121 122  auto SemaphoreWaitM = forEachDescendant(123    callExpr(124      allOf(125        callsName("dispatch_semaphore_wait"),126        equalsBoundArgDecl(0, SemaphoreBinding)127      )128    ).bind(WarnAtNode));129 130  return compoundStmt(131      SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);132}133 134static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {135 136  const char *GroupBinding = "group_name";137  auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));138 139  auto GroupBindingM = anyOf(140      forEachDescendant(141          varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),142      forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),143                     hasRHS(DispatchGroupCreateM))));144 145  auto GroupEnterM = forEachDescendant(146      stmt(callExpr(allOf(callsName("dispatch_group_enter"),147                          equalsBoundArgDecl(0, GroupBinding)))));148 149  auto HasBlockArgumentM = hasAnyArgument(hasType(150            hasCanonicalType(blockPointerType())151            ));152 153  auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(154          allOf(155              callsName("dispatch_group_leave"),156              equalsBoundArgDecl(0, GroupBinding)157              )))));158 159  auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);160 161  auto AcceptsBlockM =162    forEachDescendant(163      stmt(anyOf(164        callExpr(HasBlockAndCallsLeaveM),165        objcMessageExpr(HasBlockAndCallsLeaveM)166           )));167 168  auto GroupWaitM = forEachDescendant(169    callExpr(170      allOf(171        callsName("dispatch_group_wait"),172        equalsBoundArgDecl(0, GroupBinding)173      )174    ).bind(WarnAtNode));175 176  return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);177}178 179static void emitDiagnostics(const BoundNodes &Nodes,180                            const char* Type,181                            BugReporter &BR,182                            AnalysisDeclContext *ADC,183                            const GCDAntipatternChecker *Checker) {184  const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);185  assert(SW);186 187  std::string Diagnostics;188  llvm::raw_string_ostream OS(Diagnostics);189  OS << "Waiting on a callback using a " << Type << " creates useless threads "190     << "and is subject to priority inversion; consider "191     << "using a synchronous API or changing the caller to be asynchronous";192 193  BR.EmitBasicReport(194    ADC->getDecl(),195    Checker,196    /*Name=*/"GCD performance anti-pattern",197    /*BugCategory=*/"Performance",198    OS.str(),199    PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),200    SW->getSourceRange());201}202 203void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,204                                             AnalysisManager &AM,205                                             BugReporter &BR) const {206  if (isTest(D))207    return;208 209  AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);210 211  auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore();212  auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext());213  for (BoundNodes Match : Matches)214    emitDiagnostics(Match, "semaphore", BR, ADC, this);215 216  auto GroupMatcherM = findGCDAntiPatternWithGroup();217  Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());218  for (BoundNodes Match : Matches)219    emitDiagnostics(Match, "group", BR, ADC, this);220}221 222} // end of anonymous namespace223 224void ento::registerGCDAntipattern(CheckerManager &Mgr) {225  Mgr.registerChecker<GCDAntipatternChecker>();226}227 228bool ento::shouldRegisterGCDAntipattern(const CheckerManager &mgr) {229  return true;230}231