blob: eda7a5fcd172c991aa4234628dfb163708fb90c4 [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.
George Karpenkov15e814f2018-03-06 00:18:21 +000091 if (const auto* ND = dyn_cast<NamedDecl>(D)) {
92 std::string DeclName = ND->getNameAsString();
93 if (StringRef(DeclName).startswith("test"))
George Karpenkov436d5cc2018-03-05 22:03:32 +000094 return;
George Karpenkov15e814f2018-03-06 00:18:21 +000095 }
George Karpenkov436d5cc2018-03-05 22:03:32 +000096
97 const char *SemaphoreBinding = "semaphore_name";
98 auto SemaphoreCreateM = callExpr(callsName("dispatch_semaphore_create"));
99
100 auto SemaphoreBindingM = anyOf(
101 forEachDescendant(
102 varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
103 forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
104 hasRHS(SemaphoreCreateM))));
105
106 auto SemaphoreWaitM = forEachDescendant(
107 callExpr(
108 allOf(
109 callsName("dispatch_semaphore_wait"),
110 equalsBoundArgDecl(0, SemaphoreBinding)
111 )
112 ).bind(WarningBinding));
113
George Karpenkov4cbeeb12018-03-07 02:54:01 +0000114 auto HasBlockArgumentM = hasAnyArgument(hasType(
George Karpenkov436d5cc2018-03-05 22:03:32 +0000115 hasCanonicalType(blockPointerType())
George Karpenkov4cbeeb12018-03-07 02:54:01 +0000116 ));
George Karpenkov436d5cc2018-03-05 22:03:32 +0000117
George Karpenkov4cbeeb12018-03-07 02:54:01 +0000118 auto ArgCallsSignalM = hasArgument(0, hasDescendant(callExpr(
George Karpenkov436d5cc2018-03-05 22:03:32 +0000119 allOf(
120 callsName("dispatch_semaphore_signal"),
121 equalsBoundArgDecl(0, SemaphoreBinding)
George Karpenkov4cbeeb12018-03-07 02:54:01 +0000122 ))));
George Karpenkov436d5cc2018-03-05 22:03:32 +0000123
George Karpenkov4cbeeb12018-03-07 02:54:01 +0000124 auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
125
126 auto AcceptsBlockM =
127 forEachDescendant(
128 stmt(anyOf(
129 callExpr(HasBlockAndCallsSignalM),
130 objcMessageExpr(HasBlockAndCallsSignalM)
131 )));
132
133 auto FinalM = compoundStmt(SemaphoreBindingM, SemaphoreWaitM, AcceptsBlockM);
George Karpenkov436d5cc2018-03-05 22:03:32 +0000134
135 MatchFinder F;
136 Callback CB(BR, AM.getAnalysisDeclContext(D), this);
137
138 F.addMatcher(FinalM, &CB);
139 F.match(*D->getBody(), AM.getASTContext());
140}
141
142void Callback::run(const MatchFinder::MatchResult &Result) {
143 const auto *SW = Result.Nodes.getNodeAs<CallExpr>(WarningBinding);
144 assert(SW);
145 BR.EmitBasicReport(
146 ADC->getDecl(), C,
147 /*Name=*/"Semaphore performance anti-pattern",
148 /*Category=*/"Performance",
149 "Possible semaphore performance anti-pattern: wait on a semaphore "
150 "signalled to in a callback",
151 PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
152 SW->getSourceRange());
153}
154
155}
156
157void ento::registerGCDAsyncSemaphoreChecker(CheckerManager &Mgr) {
158 Mgr.registerChecker<GCDAsyncSemaphoreChecker>();
159}