xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp (revision 44a3b7c1304df65e0aff751cac2d7ed365520401)
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 const char *WarningBinding = "semaphore_wait";
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 class Callback : public MatchFinder::MatchCallback {
56   BugReporter &BR;
57   const GCDAntipatternChecker *C;
58   AnalysisDeclContext *ADC;
59 
60 public:
61   Callback(BugReporter &BR,
62            AnalysisDeclContext *ADC,
63            const GCDAntipatternChecker *C) : BR(BR), C(C), ADC(ADC) {}
64 
65   virtual void run(const MatchFinder::MatchResult &Result) override;
66 };
67 
68 auto callsName(const char *FunctionName)
69     -> decltype(callee(functionDecl())) {
70   return callee(functionDecl(hasName(FunctionName)));
71 }
72 
73 auto equalsBoundArgDecl(int ArgIdx, const char *DeclName)
74     -> decltype(hasArgument(0, expr())) {
75   return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
76                                  to(varDecl(equalsBoundNode(DeclName))))));
77 }
78 
79 auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) {
80   return hasLHS(ignoringParenImpCasts(
81                          declRefExpr(to(varDecl().bind(DeclName)))));
82 }
83 
84 void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
85                                                AnalysisManager &AM,
86                                                BugReporter &BR) const {
87 
88   // The pattern is very common in tests, and it is OK to use it there.
89   if (const auto* ND = dyn_cast<NamedDecl>(D)) {
90     std::string DeclName = ND->getNameAsString();
91     if (StringRef(DeclName).startswith("test"))
92       return;
93   }
94   if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) {
95     if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) {
96       std::string ContainerName = CD->getNameAsString();
97       StringRef CN(ContainerName);
98       if (CN.contains_lower("test") || CN.contains_lower("mock"))
99         return;
100     }
101   }
102 
103   const char *SemaphoreBinding = "semaphore_name";
104   auto SemaphoreCreateM = callExpr(callsName("dispatch_semaphore_create"));
105 
106   auto SemaphoreBindingM = anyOf(
107       forEachDescendant(
108           varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
109       forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
110                      hasRHS(SemaphoreCreateM))));
111 
112   auto SemaphoreWaitM = forEachDescendant(
113     callExpr(
114       allOf(
115         callsName("dispatch_semaphore_wait"),
116         equalsBoundArgDecl(0, SemaphoreBinding)
117       )
118     ).bind(WarningBinding));
119 
120   auto HasBlockArgumentM = hasAnyArgument(hasType(
121             hasCanonicalType(blockPointerType())
122             ));
123 
124   auto ArgCallsSignalM = hasArgument(0, hasDescendant(callExpr(
125           allOf(
126               callsName("dispatch_semaphore_signal"),
127               equalsBoundArgDecl(0, SemaphoreBinding)
128               ))));
129 
130   auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
131 
132   auto AcceptsBlockM =
133     forEachDescendant(
134       stmt(anyOf(
135         callExpr(HasBlockAndCallsSignalM),
136         objcMessageExpr(HasBlockAndCallsSignalM)
137            )));
138 
139   auto FinalM = compoundStmt(SemaphoreBindingM, SemaphoreWaitM, AcceptsBlockM);
140 
141   MatchFinder F;
142   Callback CB(BR, AM.getAnalysisDeclContext(D), this);
143 
144   F.addMatcher(FinalM, &CB);
145   F.match(*D->getBody(), AM.getASTContext());
146 }
147 
148 void Callback::run(const MatchFinder::MatchResult &Result) {
149   const auto *SW = Result.Nodes.getNodeAs<CallExpr>(WarningBinding);
150   assert(SW);
151   BR.EmitBasicReport(
152       ADC->getDecl(), C,
153       /*Name=*/"Semaphore performance anti-pattern",
154       /*Category=*/"Performance",
155       "Waiting on a semaphore with Grand Central Dispatch creates useless "
156       "threads and is subject to priority inversion; consider "
157       "using a synchronous API or changing the caller to be asynchronous",
158       PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
159       SW->getSourceRange());
160 }
161 
162 }
163 
164 void ento::registerGCDAntipattern(CheckerManager &Mgr) {
165   Mgr.registerChecker<GCDAntipatternChecker>();
166 }
167