blob: bb1bd85ce0bc1722802f3618512735a25d88e6c1 [file] [log] [blame]
George Karpenkov44a3b7c2018-03-12 18:27:36 +00001//===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==//
George Karpenkov436d5cc2018-03-05 22:03:32 +00002//
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//
George Karpenkov44a3b7c2018-03-12 18:27:36 +000010// This file defines GCDAntipatternChecker which checks against a common
George Karpenkov436d5cc2018-03-05 22:03:32 +000011// antipattern when synchronous API is emulated from asynchronous callbacks
George Karpenkov44a3b7c2018-03-12 18:27:36 +000012// using a semaphore:
George Karpenkov436d5cc2018-03-05 22:03:32 +000013//
George Karpenkov44a3b7c2018-03-12 18:27:36 +000014// dispatch_semaphore_t sema = dispatch_semaphore_create(0);
15//
George Karpenkov436d5cc2018-03-05 22:03:32 +000016// AnyCFunctionCall(^{
17// // code…
George Karpenkov44a3b7c2018-03-12 18:27:36 +000018// dispatch_semaphore_signal(sema);
George Karpenkov436d5cc2018-03-05 22:03:32 +000019// })
George Karpenkov44a3b7c2018-03-12 18:27:36 +000020// dispatch_semaphore_wait(sema, *)
George Karpenkov436d5cc2018-03-05 22:03:32 +000021//
22// Such code is a common performance problem, due to inability of GCD to
George Karpenkov44a3b7c2018-03-12 18:27:36 +000023// properly handle QoS when a combination of queues and semaphores is used.
George Karpenkov436d5cc2018-03-05 22:03:32 +000024// 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
George Karpenkov436d5cc2018-03-05 22:03:32 +000040using namespace clang;
41using namespace ento;
42using namespace ast_matchers;
43
44namespace {
45
46const char *WarningBinding = "semaphore_wait";
47
George Karpenkov44a3b7c2018-03-12 18:27:36 +000048class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {
George Karpenkov436d5cc2018-03-05 22:03:32 +000049public:
50 void checkASTCodeBody(const Decl *D,
51 AnalysisManager &AM,
52 BugReporter &BR) const;
53};
54
55class Callback : public MatchFinder::MatchCallback {
56 BugReporter &BR;
George Karpenkov44a3b7c2018-03-12 18:27:36 +000057 const GCDAntipatternChecker *C;
George Karpenkov436d5cc2018-03-05 22:03:32 +000058 AnalysisDeclContext *ADC;
59
60public:
61 Callback(BugReporter &BR,
62 AnalysisDeclContext *ADC,
George Karpenkov44a3b7c2018-03-12 18:27:36 +000063 const GCDAntipatternChecker *C) : BR(BR), C(C), ADC(ADC) {}
George Karpenkov436d5cc2018-03-05 22:03:32 +000064
65 virtual void run(const MatchFinder::MatchResult &Result) override;
66};
67
68auto callsName(const char *FunctionName)
69 -> decltype(callee(functionDecl())) {
70 return callee(functionDecl(hasName(FunctionName)));
71}
72
73auto equalsBoundArgDecl(int ArgIdx, const char *DeclName)
74 -> decltype(hasArgument(0, expr())) {
75 return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
76 to(varDecl(equalsBoundNode(DeclName))))));
77}
78
79auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) {
80 return hasLHS(ignoringParenImpCasts(
81 declRefExpr(to(varDecl().bind(DeclName)))));
82}
83
George Karpenkov44a3b7c2018-03-12 18:27:36 +000084void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
George Karpenkov436d5cc2018-03-05 22:03:32 +000085 AnalysisManager &AM,
86 BugReporter &BR) const {
87
88 // The pattern is very common in tests, and it is OK to use it there.
George Karpenkov15e814f2018-03-06 00:18:21 +000089 if (const auto* ND = dyn_cast<NamedDecl>(D)) {
90 std::string DeclName = ND->getNameAsString();
91 if (StringRef(DeclName).startswith("test"))
George Karpenkov436d5cc2018-03-05 22:03:32 +000092 return;
George Karpenkov15e814f2018-03-06 00:18:21 +000093 }
George Karpenkov44a3b7c2018-03-12 18:27:36 +000094 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 }
George Karpenkov436d5cc2018-03-05 22:03:32 +0000102
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
George Karpenkov4cbeeb12018-03-07 02:54:01 +0000120 auto HasBlockArgumentM = hasAnyArgument(hasType(
George Karpenkov436d5cc2018-03-05 22:03:32 +0000121 hasCanonicalType(blockPointerType())
George Karpenkov4cbeeb12018-03-07 02:54:01 +0000122 ));
George Karpenkov436d5cc2018-03-05 22:03:32 +0000123
George Karpenkov460675e2018-03-13 17:27:01 +0000124 auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
George Karpenkov436d5cc2018-03-05 22:03:32 +0000125 allOf(
126 callsName("dispatch_semaphore_signal"),
127 equalsBoundArgDecl(0, SemaphoreBinding)
George Karpenkov460675e2018-03-13 17:27:01 +0000128 )))));
George Karpenkov436d5cc2018-03-05 22:03:32 +0000129
George Karpenkov4cbeeb12018-03-07 02:54:01 +0000130 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);
George Karpenkov436d5cc2018-03-05 22:03:32 +0000140
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
148void 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",
George Karpenkov44a3b7c2018-03-12 18:27:36 +0000155 "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",
George Karpenkov436d5cc2018-03-05 22:03:32 +0000158 PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
159 SW->getSourceRange());
160}
161
162}
163
George Karpenkov44a3b7c2018-03-12 18:27:36 +0000164void ento::registerGCDAntipattern(CheckerManager &Mgr) {
165 Mgr.registerChecker<GCDAntipatternChecker>();
George Karpenkov436d5cc2018-03-05 22:03:32 +0000166}