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 // ID of a node at which the diagnostic would be emitted. 47 const char *WarnAtNode = "waitcall"; 48 49 class GCDAntipatternChecker : public Checker<check::ASTCodeBody> { 50 public: 51 void checkASTCodeBody(const Decl *D, 52 AnalysisManager &AM, 53 BugReporter &BR) const; 54 }; 55 56 auto callsName(const char *FunctionName) 57 -> decltype(callee(functionDecl())) { 58 return callee(functionDecl(hasName(FunctionName))); 59 } 60 61 auto equalsBoundArgDecl(int ArgIdx, const char *DeclName) 62 -> decltype(hasArgument(0, expr())) { 63 return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr( 64 to(varDecl(equalsBoundNode(DeclName)))))); 65 } 66 67 auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) { 68 return hasLHS(ignoringParenImpCasts( 69 declRefExpr(to(varDecl().bind(DeclName))))); 70 } 71 72 /// The pattern is very common in tests, and it is OK to use it there. 73 /// We have to heuristics for detecting tests: method name starts with "test" 74 /// (used in XCTest), and a class name contains "mock" or "test" (used in 75 /// helpers which are not tests themselves, but used exclusively in tests). 76 static bool isTest(const Decl *D) { 77 if (const auto* ND = dyn_cast<NamedDecl>(D)) { 78 std::string DeclName = ND->getNameAsString(); 79 if (StringRef(DeclName).startswith("test")) 80 return true; 81 } 82 if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) { 83 if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) { 84 std::string ContainerName = CD->getNameAsString(); 85 StringRef CN(ContainerName); 86 if (CN.contains_lower("test") || CN.contains_lower("mock")) 87 return true; 88 } 89 } 90 return false; 91 } 92 93 static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) { 94 95 const char *SemaphoreBinding = "semaphore_name"; 96 auto SemaphoreCreateM = callExpr(callsName("dispatch_semaphore_create")); 97 98 auto SemaphoreBindingM = anyOf( 99 forEachDescendant( 100 varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)), 101 forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding), 102 hasRHS(SemaphoreCreateM)))); 103 104 auto HasBlockArgumentM = hasAnyArgument(hasType( 105 hasCanonicalType(blockPointerType()) 106 )); 107 108 auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr( 109 allOf( 110 callsName("dispatch_semaphore_signal"), 111 equalsBoundArgDecl(0, SemaphoreBinding) 112 ))))); 113 114 auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM); 115 116 auto HasBlockCallingSignalM = 117 forEachDescendant( 118 stmt(anyOf( 119 callExpr(HasBlockAndCallsSignalM), 120 objcMessageExpr(HasBlockAndCallsSignalM) 121 ))); 122 123 auto SemaphoreWaitM = forEachDescendant( 124 callExpr( 125 allOf( 126 callsName("dispatch_semaphore_wait"), 127 equalsBoundArgDecl(0, SemaphoreBinding) 128 ) 129 ).bind(WarnAtNode)); 130 131 return compoundStmt( 132 SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM); 133 } 134 135 static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) { 136 137 const char *GroupBinding = "group_name"; 138 auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create")); 139 140 auto GroupBindingM = anyOf( 141 forEachDescendant( 142 varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)), 143 forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding), 144 hasRHS(DispatchGroupCreateM)))); 145 146 auto GroupEnterM = forEachDescendant( 147 stmt(callExpr(allOf(callsName("dispatch_group_enter"), 148 equalsBoundArgDecl(0, GroupBinding))))); 149 150 auto HasBlockArgumentM = hasAnyArgument(hasType( 151 hasCanonicalType(blockPointerType()) 152 )); 153 154 auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr( 155 allOf( 156 callsName("dispatch_group_leave"), 157 equalsBoundArgDecl(0, GroupBinding) 158 ))))); 159 160 auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM); 161 162 auto AcceptsBlockM = 163 forEachDescendant( 164 stmt(anyOf( 165 callExpr(HasBlockAndCallsLeaveM), 166 objcMessageExpr(HasBlockAndCallsLeaveM) 167 ))); 168 169 auto GroupWaitM = forEachDescendant( 170 callExpr( 171 allOf( 172 callsName("dispatch_group_wait"), 173 equalsBoundArgDecl(0, GroupBinding) 174 ) 175 ).bind(WarnAtNode)); 176 177 return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM); 178 } 179 180 static void emitDiagnostics(const BoundNodes &Nodes, 181 const char* Type, 182 BugReporter &BR, 183 AnalysisDeclContext *ADC, 184 const GCDAntipatternChecker *Checker) { 185 const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode); 186 assert(SW); 187 188 std::string Diagnostics; 189 llvm::raw_string_ostream OS(Diagnostics); 190 OS << "Waiting on a " << Type << " with Grand Central Dispatch creates " 191 << "useless threads and is subject to priority inversion; consider " 192 << "using a synchronous API or changing the caller to be asynchronous"; 193 194 BR.EmitBasicReport( 195 ADC->getDecl(), 196 Checker, 197 /*Name=*/"GCD performance anti-pattern", 198 /*Category=*/"Performance", 199 OS.str(), 200 PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC), 201 SW->getSourceRange()); 202 } 203 204 void GCDAntipatternChecker::checkASTCodeBody(const Decl *D, 205 AnalysisManager &AM, 206 BugReporter &BR) const { 207 if (isTest(D)) 208 return; 209 210 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); 211 212 auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore(); 213 auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext()); 214 for (BoundNodes Match : Matches) 215 emitDiagnostics(Match, "semaphore", BR, ADC, this); 216 217 auto GroupMatcherM = findGCDAntiPatternWithGroup(); 218 Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext()); 219 for (BoundNodes Match : Matches) 220 emitDiagnostics(Match, "group", BR, ADC, this); 221 } 222 223 } 224 225 void ento::registerGCDAntipattern(CheckerManager &Mgr) { 226 Mgr.registerChecker<GCDAntipatternChecker>(); 227 } 228