blob: 48e9e1e7625fb08a4459e9acae2a219e6585bc37 [file] [log] [blame]
George Karpenkov436d5cc2018-03-05 22:03:32 +00001//===- GCDAsyncSemaphoreChecker.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 GCDAsyncSemaphoreChecker which checks against a common
11// antipattern when synchronous API is emulated from asynchronous callbacks
12// using a semaphor:
13//
14// dispatch_semapshore_t sema = dispatch_semaphore_create(0);
15
16// AnyCFunctionCall(^{
17// // code…
18// dispatch_semapshore_signal(sema);
19// })
20// dispatch_semapshore_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 semaphors 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#define DEBUG_TYPE "gcdasyncsemaphorechecker"
41
42using namespace clang;
43using namespace ento;
44using namespace ast_matchers;
45
46namespace {
47
48const char *WarningBinding = "semaphore_wait";
49
50class GCDAsyncSemaphoreChecker : public Checker<check::ASTCodeBody> {
51public:
52 void checkASTCodeBody(const Decl *D,
53 AnalysisManager &AM,
54 BugReporter &BR) const;
55};
56
57class Callback : public MatchFinder::MatchCallback {
58 BugReporter &BR;
59 const GCDAsyncSemaphoreChecker *C;
60 AnalysisDeclContext *ADC;
61
62public:
63 Callback(BugReporter &BR,
64 AnalysisDeclContext *ADC,
65 const GCDAsyncSemaphoreChecker *C) : BR(BR), C(C), ADC(ADC) {}
66
67 virtual void run(const MatchFinder::MatchResult &Result) override;
68};
69
70auto callsName(const char *FunctionName)
71 -> decltype(callee(functionDecl())) {
72 return callee(functionDecl(hasName(FunctionName)));
73}
74
75auto equalsBoundArgDecl(int ArgIdx, const char *DeclName)
76 -> decltype(hasArgument(0, expr())) {
77 return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
78 to(varDecl(equalsBoundNode(DeclName))))));
79}
80
81auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) {
82 return hasLHS(ignoringParenImpCasts(
83 declRefExpr(to(varDecl().bind(DeclName)))));
84}
85
86void GCDAsyncSemaphoreChecker::checkASTCodeBody(const Decl *D,
87 AnalysisManager &AM,
88 BugReporter &BR) const {
89
90 // The pattern is very common in tests, and it is OK to use it there.
91 if (const auto* ND = dyn_cast<NamedDecl>(D))
92 if (ND->getName().startswith("test"))
93 return;
94
95 const char *SemaphoreBinding = "semaphore_name";
96 auto SemaphoreCreateM = callExpr(callsName("dispatch_semaphore_create"));
97
98 auto SemaphoreBindingM = anyOf(
99 forEachDescendant(
100 varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
101 forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
102 hasRHS(SemaphoreCreateM))));
103
104 auto SemaphoreWaitM = forEachDescendant(
105 callExpr(
106 allOf(
107 callsName("dispatch_semaphore_wait"),
108 equalsBoundArgDecl(0, SemaphoreBinding)
109 )
110 ).bind(WarningBinding));
111
112 auto AcceptsBlockM =
113 forEachDescendant(callExpr(hasAnyArgument(hasType(
114 hasCanonicalType(blockPointerType())
115 ))));
116
117 auto BlockSignallingM =
118 forEachDescendant(callExpr(hasAnyArgument(hasDescendant(callExpr(
119 allOf(
120 callsName("dispatch_semaphore_signal"),
121 equalsBoundArgDecl(0, SemaphoreBinding)
122 ))))));
123
124 auto FinalM = compoundStmt(
125 SemaphoreBindingM,
126 SemaphoreWaitM,
127 AcceptsBlockM,
128 BlockSignallingM);
129
130 MatchFinder F;
131 Callback CB(BR, AM.getAnalysisDeclContext(D), this);
132
133 F.addMatcher(FinalM, &CB);
134 F.match(*D->getBody(), AM.getASTContext());
135}
136
137void Callback::run(const MatchFinder::MatchResult &Result) {
138 const auto *SW = Result.Nodes.getNodeAs<CallExpr>(WarningBinding);
139 assert(SW);
140 BR.EmitBasicReport(
141 ADC->getDecl(), C,
142 /*Name=*/"Semaphore performance anti-pattern",
143 /*Category=*/"Performance",
144 "Possible semaphore performance anti-pattern: wait on a semaphore "
145 "signalled to in a callback",
146 PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
147 SW->getSourceRange());
148}
149
150}
151
152void ento::registerGCDAsyncSemaphoreChecker(CheckerManager &Mgr) {
153 Mgr.registerChecker<GCDAsyncSemaphoreChecker>();
154}