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(allOf( 97 callsName("dispatch_semaphore_create"), 98 hasArgument(0, ignoringParenCasts(integerLiteral(equals(0)))))); 99 100 auto SemaphoreBindingM = anyOf( 101 forEachDescendant( 102 varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)), 103 forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding), 104 hasRHS(SemaphoreCreateM)))); 105 106 auto HasBlockArgumentM = hasAnyArgument(hasType( 107 hasCanonicalType(blockPointerType()) 108 )); 109 110 auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr( 111 allOf( 112 callsName("dispatch_semaphore_signal"), 113 equalsBoundArgDecl(0, SemaphoreBinding) 114 ))))); 115 116 auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM); 117 118 auto HasBlockCallingSignalM = 119 forEachDescendant( 120 stmt(anyOf( 121 callExpr(HasBlockAndCallsSignalM), 122 objcMessageExpr(HasBlockAndCallsSignalM) 123 ))); 124 125 auto SemaphoreWaitM = forEachDescendant( 126 callExpr( 127 allOf( 128 callsName("dispatch_semaphore_wait"), 129 equalsBoundArgDecl(0, SemaphoreBinding) 130 ) 131 ).bind(WarnAtNode)); 132 133 return compoundStmt( 134 SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM); 135 } 136 137 static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) { 138 139 const char *GroupBinding = "group_name"; 140 auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create")); 141 142 auto GroupBindingM = anyOf( 143 forEachDescendant( 144 varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)), 145 forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding), 146 hasRHS(DispatchGroupCreateM)))); 147 148 auto GroupEnterM = forEachDescendant( 149 stmt(callExpr(allOf(callsName("dispatch_group_enter"), 150 equalsBoundArgDecl(0, GroupBinding))))); 151 152 auto HasBlockArgumentM = hasAnyArgument(hasType( 153 hasCanonicalType(blockPointerType()) 154 )); 155 156 auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr( 157 allOf( 158 callsName("dispatch_group_leave"), 159 equalsBoundArgDecl(0, GroupBinding) 160 ))))); 161 162 auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM); 163 164 auto AcceptsBlockM = 165 forEachDescendant( 166 stmt(anyOf( 167 callExpr(HasBlockAndCallsLeaveM), 168 objcMessageExpr(HasBlockAndCallsLeaveM) 169 ))); 170 171 auto GroupWaitM = forEachDescendant( 172 callExpr( 173 allOf( 174 callsName("dispatch_group_wait"), 175 equalsBoundArgDecl(0, GroupBinding) 176 ) 177 ).bind(WarnAtNode)); 178 179 return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM); 180 } 181 182 static void emitDiagnostics(const BoundNodes &Nodes, 183 const char* Type, 184 BugReporter &BR, 185 AnalysisDeclContext *ADC, 186 const GCDAntipatternChecker *Checker) { 187 const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode); 188 assert(SW); 189 190 std::string Diagnostics; 191 llvm::raw_string_ostream OS(Diagnostics); 192 OS << "Waiting on a callback using a " << Type << " creates useless threads " 193 << "and is subject to priority inversion; consider " 194 << "using a synchronous API or changing the caller to be asynchronous"; 195 196 BR.EmitBasicReport( 197 ADC->getDecl(), 198 Checker, 199 /*Name=*/"GCD performance anti-pattern", 200 /*Category=*/"Performance", 201 OS.str(), 202 PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC), 203 SW->getSourceRange()); 204 } 205 206 void GCDAntipatternChecker::checkASTCodeBody(const Decl *D, 207 AnalysisManager &AM, 208 BugReporter &BR) const { 209 if (isTest(D)) 210 return; 211 212 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); 213 214 auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore(); 215 auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext()); 216 for (BoundNodes Match : Matches) 217 emitDiagnostics(Match, "semaphore", BR, ADC, this); 218 219 auto GroupMatcherM = findGCDAntiPatternWithGroup(); 220 Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext()); 221 for (BoundNodes Match : Matches) 222 emitDiagnostics(Match, "group", BR, ADC, this); 223 } 224 225 } 226 227 void ento::registerGCDAntipattern(CheckerManager &Mgr) { 228 Mgr.registerChecker<GCDAntipatternChecker>(); 229 } 230