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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file defines GCDAntipatternChecker which checks against a common 10 // antipattern when synchronous API is emulated from asynchronous callbacks 11 // 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 to 22 // properly handle QoS when a combination of queues and semaphores is used. 23 // Good code would either use asynchronous API (when available), or perform 24 // the necessary action in asynchronous callback. 25 // 26 // Currently, the check is performed using a simple heuristical AST pattern 27 // 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 39 using namespace clang; 40 using namespace ento; 41 using namespace ast_matchers; 42 43 namespace { 44 45 // ID of a node at which the diagnostic would be emitted. 46 const char *WarnAtNode = "waitcall"; 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 decltype(auto) callsName(const char *FunctionName) { 56 return callee(functionDecl(hasName(FunctionName))); 57 } 58 59 decltype(auto) equalsBoundArgDecl(int ArgIdx, const char *DeclName) { 60 return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr( 61 to(varDecl(equalsBoundNode(DeclName)))))); 62 } 63 64 decltype(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 in 72 /// helpers which are not tests themselves, but used exclusively in tests). 73 static 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 90 static 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 134 static 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 179 static 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 203 void 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 namespace 223 224 void ento::registerGCDAntipattern(CheckerManager &Mgr) { 225 Mgr.registerChecker<GCDAntipatternChecker>(); 226 } 227 228 bool ento::shouldRegisterGCDAntipattern(const CheckerManager &mgr) { 229 return true; 230 } 231