1 //===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==//
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
7 //===----------------------------------------------------------------------===//
9 // This file defines GCDAntipatternChecker which checks against a common
10 // antipattern when synchronous API is emulated from asynchronous callbacks
13 // dispatch_semaphore_t sema = dispatch_semaphore_create(0);
15 // AnyCFunctionCall(^{
17 // dispatch_semaphore_signal(sema);
19 // dispatch_semaphore_wait(sema, *)
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.
26 // Currently, the check is performed using a simple heuristical AST pattern
29 //===----------------------------------------------------------------------===//
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"
39 using namespace clang;
41 using namespace ast_matchers;
45 // ID of a node at which the diagnostic would be emitted.
46 const char *WarnAtNode = "waitcall";
48 class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {
50 void checkASTCodeBody(const Decl *D,
52 BugReporter &BR) const;
55 auto callsName(const char *FunctionName)
56 -> decltype(callee(functionDecl())) {
57 return callee(functionDecl(hasName(FunctionName)));
60 auto equalsBoundArgDecl(int ArgIdx, const char *DeclName)
61 -> decltype(hasArgument(0, expr())) {
62 return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
63 to(varDecl(equalsBoundNode(DeclName))))));
66 auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) {
67 return hasLHS(ignoringParenImpCasts(
68 declRefExpr(to(varDecl().bind(DeclName)))));
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).
75 static 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"))
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"))
92 static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {
94 const char *SemaphoreBinding = "semaphore_name";
95 auto SemaphoreCreateM = callExpr(allOf(
96 callsName("dispatch_semaphore_create"),
97 hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));
99 auto SemaphoreBindingM = anyOf(
101 varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
102 forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
103 hasRHS(SemaphoreCreateM))));
105 auto HasBlockArgumentM = hasAnyArgument(hasType(
106 hasCanonicalType(blockPointerType())
109 auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
111 callsName("dispatch_semaphore_signal"),
112 equalsBoundArgDecl(0, SemaphoreBinding)
115 auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
117 auto HasBlockCallingSignalM =
120 callExpr(HasBlockAndCallsSignalM),
121 objcMessageExpr(HasBlockAndCallsSignalM)
124 auto SemaphoreWaitM = forEachDescendant(
127 callsName("dispatch_semaphore_wait"),
128 equalsBoundArgDecl(0, SemaphoreBinding)
133 SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
136 static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
138 const char *GroupBinding = "group_name";
139 auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
141 auto GroupBindingM = anyOf(
143 varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),
144 forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),
145 hasRHS(DispatchGroupCreateM))));
147 auto GroupEnterM = forEachDescendant(
148 stmt(callExpr(allOf(callsName("dispatch_group_enter"),
149 equalsBoundArgDecl(0, GroupBinding)))));
151 auto HasBlockArgumentM = hasAnyArgument(hasType(
152 hasCanonicalType(blockPointerType())
155 auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
157 callsName("dispatch_group_leave"),
158 equalsBoundArgDecl(0, GroupBinding)
161 auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);
166 callExpr(HasBlockAndCallsLeaveM),
167 objcMessageExpr(HasBlockAndCallsLeaveM)
170 auto GroupWaitM = forEachDescendant(
173 callsName("dispatch_group_wait"),
174 equalsBoundArgDecl(0, GroupBinding)
178 return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);
181 static void emitDiagnostics(const BoundNodes &Nodes,
184 AnalysisDeclContext *ADC,
185 const GCDAntipatternChecker *Checker) {
186 const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);
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";
198 /*Name=*/"GCD performance anti-pattern",
199 /*BugCategory=*/"Performance",
201 PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
202 SW->getSourceRange());
205 void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
207 BugReporter &BR) const {
211 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
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);
218 auto GroupMatcherM = findGCDAntiPatternWithGroup();
219 Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());
220 for (BoundNodes Match : Matches)
221 emitDiagnostics(Match, "group", BR, ADC, this);
224 } // end of anonymous namespace
226 void ento::registerGCDAntipattern(CheckerManager &Mgr) {
227 Mgr.registerChecker<GCDAntipatternChecker>();
230 bool ento::shouldRegisterGCDAntipattern(const LangOptions &LO) {