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