1 //===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // This file defines GCDAntipatternChecker which checks against a common 11 // antipattern when synchronous API is emulated from asynchronous callbacks 12 // using a semaphore: 13 // 14 // dispatch_semaphore_t sema = dispatch_semaphore_create(0); 15 // 16 // AnyCFunctionCall(^{ 17 // // code… 18 // dispatch_semaphore_signal(sema); 19 // }) 20 // dispatch_semaphore_wait(sema, *) 21 // 22 // Such code is a common performance problem, due to inability of GCD to 23 // properly handle QoS when a combination of queues and semaphores is used. 24 // Good code would either use asynchronous API (when available), or perform 25 // the necessary action in asynchronous callback. 26 // 27 // Currently, the check is performed using a simple heuristical AST pattern 28 // matching. 29 // 30 //===----------------------------------------------------------------------===// 31 32 #include "ClangSACheckers.h" 33 #include "clang/ASTMatchers/ASTMatchFinder.h" 34 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 35 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 36 #include "clang/StaticAnalyzer/Core/Checker.h" 37 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" 38 #include "llvm/Support/Debug.h" 39 40 using namespace clang; 41 using namespace ento; 42 using namespace ast_matchers; 43 44 namespace { 45 46 const char *WarningBinding = "semaphore_wait"; 47 48 class GCDAntipatternChecker : public Checker<check::ASTCodeBody> { 49 public: 50 void checkASTCodeBody(const Decl *D, 51 AnalysisManager &AM, 52 BugReporter &BR) const; 53 }; 54 55 class Callback : public MatchFinder::MatchCallback { 56 BugReporter &BR; 57 const GCDAntipatternChecker *C; 58 AnalysisDeclContext *ADC; 59 60 public: 61 Callback(BugReporter &BR, 62 AnalysisDeclContext *ADC, 63 const GCDAntipatternChecker *C) : BR(BR), C(C), ADC(ADC) {} 64 65 virtual void run(const MatchFinder::MatchResult &Result) override; 66 }; 67 68 auto callsName(const char *FunctionName) 69 -> decltype(callee(functionDecl())) { 70 return callee(functionDecl(hasName(FunctionName))); 71 } 72 73 auto equalsBoundArgDecl(int ArgIdx, const char *DeclName) 74 -> decltype(hasArgument(0, expr())) { 75 return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr( 76 to(varDecl(equalsBoundNode(DeclName)))))); 77 } 78 79 auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) { 80 return hasLHS(ignoringParenImpCasts( 81 declRefExpr(to(varDecl().bind(DeclName))))); 82 } 83 84 void GCDAntipatternChecker::checkASTCodeBody(const Decl *D, 85 AnalysisManager &AM, 86 BugReporter &BR) const { 87 88 // The pattern is very common in tests, and it is OK to use it there. 89 if (const auto* ND = dyn_cast<NamedDecl>(D)) { 90 std::string DeclName = ND->getNameAsString(); 91 if (StringRef(DeclName).startswith("test")) 92 return; 93 } 94 if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) { 95 if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) { 96 std::string ContainerName = CD->getNameAsString(); 97 StringRef CN(ContainerName); 98 if (CN.contains_lower("test") || CN.contains_lower("mock")) 99 return; 100 } 101 } 102 103 const char *SemaphoreBinding = "semaphore_name"; 104 auto SemaphoreCreateM = callExpr(callsName("dispatch_semaphore_create")); 105 106 auto SemaphoreBindingM = anyOf( 107 forEachDescendant( 108 varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)), 109 forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding), 110 hasRHS(SemaphoreCreateM)))); 111 112 auto SemaphoreWaitM = forEachDescendant( 113 callExpr( 114 allOf( 115 callsName("dispatch_semaphore_wait"), 116 equalsBoundArgDecl(0, SemaphoreBinding) 117 ) 118 ).bind(WarningBinding)); 119 120 auto HasBlockArgumentM = hasAnyArgument(hasType( 121 hasCanonicalType(blockPointerType()) 122 )); 123 124 auto ArgCallsSignalM = hasArgument(0, hasDescendant(callExpr( 125 allOf( 126 callsName("dispatch_semaphore_signal"), 127 equalsBoundArgDecl(0, SemaphoreBinding) 128 )))); 129 130 auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM); 131 132 auto AcceptsBlockM = 133 forEachDescendant( 134 stmt(anyOf( 135 callExpr(HasBlockAndCallsSignalM), 136 objcMessageExpr(HasBlockAndCallsSignalM) 137 ))); 138 139 auto FinalM = compoundStmt(SemaphoreBindingM, SemaphoreWaitM, AcceptsBlockM); 140 141 MatchFinder F; 142 Callback CB(BR, AM.getAnalysisDeclContext(D), this); 143 144 F.addMatcher(FinalM, &CB); 145 F.match(*D->getBody(), AM.getASTContext()); 146 } 147 148 void Callback::run(const MatchFinder::MatchResult &Result) { 149 const auto *SW = Result.Nodes.getNodeAs<CallExpr>(WarningBinding); 150 assert(SW); 151 BR.EmitBasicReport( 152 ADC->getDecl(), C, 153 /*Name=*/"Semaphore performance anti-pattern", 154 /*Category=*/"Performance", 155 "Waiting on a semaphore with Grand Central Dispatch creates useless " 156 "threads and is subject to priority inversion; consider " 157 "using a synchronous API or changing the caller to be asynchronous", 158 PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC), 159 SW->getSourceRange()); 160 } 161 162 } 163 164 void ento::registerGCDAntipattern(CheckerManager &Mgr) { 165 Mgr.registerChecker<GCDAntipatternChecker>(); 166 } 167