106f32e7eSjoerg //===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==//
206f32e7eSjoerg //
306f32e7eSjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
406f32e7eSjoerg // See https://llvm.org/LICENSE.txt for license information.
506f32e7eSjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
606f32e7eSjoerg //
706f32e7eSjoerg //===----------------------------------------------------------------------===//
806f32e7eSjoerg //
906f32e7eSjoerg // This file defines GCDAntipatternChecker which checks against a common
1006f32e7eSjoerg // antipattern when synchronous API is emulated from asynchronous callbacks
1106f32e7eSjoerg // using a semaphore:
1206f32e7eSjoerg //
1306f32e7eSjoerg //   dispatch_semaphore_t sema = dispatch_semaphore_create(0);
1406f32e7eSjoerg //
1506f32e7eSjoerg //   AnyCFunctionCall(^{
1606f32e7eSjoerg //     // code…
1706f32e7eSjoerg //     dispatch_semaphore_signal(sema);
1806f32e7eSjoerg //   })
1906f32e7eSjoerg //   dispatch_semaphore_wait(sema, *)
2006f32e7eSjoerg //
2106f32e7eSjoerg // Such code is a common performance problem, due to inability of GCD to
2206f32e7eSjoerg // properly handle QoS when a combination of queues and semaphores is used.
2306f32e7eSjoerg // Good code would either use asynchronous API (when available), or perform
2406f32e7eSjoerg // the necessary action in asynchronous callback.
2506f32e7eSjoerg //
2606f32e7eSjoerg // Currently, the check is performed using a simple heuristical AST pattern
2706f32e7eSjoerg // matching.
2806f32e7eSjoerg //
2906f32e7eSjoerg //===----------------------------------------------------------------------===//
3006f32e7eSjoerg 
3106f32e7eSjoerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
3206f32e7eSjoerg #include "clang/ASTMatchers/ASTMatchFinder.h"
3306f32e7eSjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
3406f32e7eSjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
3506f32e7eSjoerg #include "clang/StaticAnalyzer/Core/Checker.h"
3606f32e7eSjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
3706f32e7eSjoerg #include "llvm/Support/Debug.h"
3806f32e7eSjoerg 
3906f32e7eSjoerg using namespace clang;
4006f32e7eSjoerg using namespace ento;
4106f32e7eSjoerg using namespace ast_matchers;
4206f32e7eSjoerg 
4306f32e7eSjoerg namespace {
4406f32e7eSjoerg 
4506f32e7eSjoerg // ID of a node at which the diagnostic would be emitted.
4606f32e7eSjoerg const char *WarnAtNode = "waitcall";
4706f32e7eSjoerg 
4806f32e7eSjoerg class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {
4906f32e7eSjoerg public:
5006f32e7eSjoerg   void checkASTCodeBody(const Decl *D,
5106f32e7eSjoerg                         AnalysisManager &AM,
5206f32e7eSjoerg                         BugReporter &BR) const;
5306f32e7eSjoerg };
5406f32e7eSjoerg 
callsName(const char * FunctionName)55*13fbcb42Sjoerg decltype(auto) callsName(const char *FunctionName) {
5606f32e7eSjoerg   return callee(functionDecl(hasName(FunctionName)));
5706f32e7eSjoerg }
5806f32e7eSjoerg 
equalsBoundArgDecl(int ArgIdx,const char * DeclName)59*13fbcb42Sjoerg decltype(auto) equalsBoundArgDecl(int ArgIdx, const char *DeclName) {
6006f32e7eSjoerg   return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
6106f32e7eSjoerg                                  to(varDecl(equalsBoundNode(DeclName))))));
6206f32e7eSjoerg }
6306f32e7eSjoerg 
bindAssignmentToDecl(const char * DeclName)64*13fbcb42Sjoerg decltype(auto) bindAssignmentToDecl(const char *DeclName) {
6506f32e7eSjoerg   return hasLHS(ignoringParenImpCasts(
6606f32e7eSjoerg                          declRefExpr(to(varDecl().bind(DeclName)))));
6706f32e7eSjoerg }
6806f32e7eSjoerg 
6906f32e7eSjoerg /// The pattern is very common in tests, and it is OK to use it there.
7006f32e7eSjoerg /// We have to heuristics for detecting tests: method name starts with "test"
7106f32e7eSjoerg /// (used in XCTest), and a class name contains "mock" or "test" (used in
7206f32e7eSjoerg /// helpers which are not tests themselves, but used exclusively in tests).
isTest(const Decl * D)7306f32e7eSjoerg static bool isTest(const Decl *D) {
7406f32e7eSjoerg   if (const auto* ND = dyn_cast<NamedDecl>(D)) {
7506f32e7eSjoerg     std::string DeclName = ND->getNameAsString();
7606f32e7eSjoerg     if (StringRef(DeclName).startswith("test"))
7706f32e7eSjoerg       return true;
7806f32e7eSjoerg   }
7906f32e7eSjoerg   if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) {
8006f32e7eSjoerg     if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) {
8106f32e7eSjoerg       std::string ContainerName = CD->getNameAsString();
8206f32e7eSjoerg       StringRef CN(ContainerName);
8306f32e7eSjoerg       if (CN.contains_lower("test") || CN.contains_lower("mock"))
8406f32e7eSjoerg         return true;
8506f32e7eSjoerg     }
8606f32e7eSjoerg   }
8706f32e7eSjoerg   return false;
8806f32e7eSjoerg }
8906f32e7eSjoerg 
findGCDAntiPatternWithSemaphore()9006f32e7eSjoerg static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {
9106f32e7eSjoerg 
9206f32e7eSjoerg   const char *SemaphoreBinding = "semaphore_name";
9306f32e7eSjoerg   auto SemaphoreCreateM = callExpr(allOf(
9406f32e7eSjoerg       callsName("dispatch_semaphore_create"),
9506f32e7eSjoerg       hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));
9606f32e7eSjoerg 
9706f32e7eSjoerg   auto SemaphoreBindingM = anyOf(
9806f32e7eSjoerg       forEachDescendant(
9906f32e7eSjoerg           varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
10006f32e7eSjoerg       forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
10106f32e7eSjoerg                      hasRHS(SemaphoreCreateM))));
10206f32e7eSjoerg 
10306f32e7eSjoerg   auto HasBlockArgumentM = hasAnyArgument(hasType(
10406f32e7eSjoerg             hasCanonicalType(blockPointerType())
10506f32e7eSjoerg             ));
10606f32e7eSjoerg 
10706f32e7eSjoerg   auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
10806f32e7eSjoerg           allOf(
10906f32e7eSjoerg               callsName("dispatch_semaphore_signal"),
11006f32e7eSjoerg               equalsBoundArgDecl(0, SemaphoreBinding)
11106f32e7eSjoerg               )))));
11206f32e7eSjoerg 
11306f32e7eSjoerg   auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
11406f32e7eSjoerg 
11506f32e7eSjoerg   auto HasBlockCallingSignalM =
11606f32e7eSjoerg     forEachDescendant(
11706f32e7eSjoerg       stmt(anyOf(
11806f32e7eSjoerg         callExpr(HasBlockAndCallsSignalM),
11906f32e7eSjoerg         objcMessageExpr(HasBlockAndCallsSignalM)
12006f32e7eSjoerg            )));
12106f32e7eSjoerg 
12206f32e7eSjoerg   auto SemaphoreWaitM = forEachDescendant(
12306f32e7eSjoerg     callExpr(
12406f32e7eSjoerg       allOf(
12506f32e7eSjoerg         callsName("dispatch_semaphore_wait"),
12606f32e7eSjoerg         equalsBoundArgDecl(0, SemaphoreBinding)
12706f32e7eSjoerg       )
12806f32e7eSjoerg     ).bind(WarnAtNode));
12906f32e7eSjoerg 
13006f32e7eSjoerg   return compoundStmt(
13106f32e7eSjoerg       SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
13206f32e7eSjoerg }
13306f32e7eSjoerg 
findGCDAntiPatternWithGroup()13406f32e7eSjoerg static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
13506f32e7eSjoerg 
13606f32e7eSjoerg   const char *GroupBinding = "group_name";
13706f32e7eSjoerg   auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
13806f32e7eSjoerg 
13906f32e7eSjoerg   auto GroupBindingM = anyOf(
14006f32e7eSjoerg       forEachDescendant(
14106f32e7eSjoerg           varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),
14206f32e7eSjoerg       forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),
14306f32e7eSjoerg                      hasRHS(DispatchGroupCreateM))));
14406f32e7eSjoerg 
14506f32e7eSjoerg   auto GroupEnterM = forEachDescendant(
14606f32e7eSjoerg       stmt(callExpr(allOf(callsName("dispatch_group_enter"),
14706f32e7eSjoerg                           equalsBoundArgDecl(0, GroupBinding)))));
14806f32e7eSjoerg 
14906f32e7eSjoerg   auto HasBlockArgumentM = hasAnyArgument(hasType(
15006f32e7eSjoerg             hasCanonicalType(blockPointerType())
15106f32e7eSjoerg             ));
15206f32e7eSjoerg 
15306f32e7eSjoerg   auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
15406f32e7eSjoerg           allOf(
15506f32e7eSjoerg               callsName("dispatch_group_leave"),
15606f32e7eSjoerg               equalsBoundArgDecl(0, GroupBinding)
15706f32e7eSjoerg               )))));
15806f32e7eSjoerg 
15906f32e7eSjoerg   auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);
16006f32e7eSjoerg 
16106f32e7eSjoerg   auto AcceptsBlockM =
16206f32e7eSjoerg     forEachDescendant(
16306f32e7eSjoerg       stmt(anyOf(
16406f32e7eSjoerg         callExpr(HasBlockAndCallsLeaveM),
16506f32e7eSjoerg         objcMessageExpr(HasBlockAndCallsLeaveM)
16606f32e7eSjoerg            )));
16706f32e7eSjoerg 
16806f32e7eSjoerg   auto GroupWaitM = forEachDescendant(
16906f32e7eSjoerg     callExpr(
17006f32e7eSjoerg       allOf(
17106f32e7eSjoerg         callsName("dispatch_group_wait"),
17206f32e7eSjoerg         equalsBoundArgDecl(0, GroupBinding)
17306f32e7eSjoerg       )
17406f32e7eSjoerg     ).bind(WarnAtNode));
17506f32e7eSjoerg 
17606f32e7eSjoerg   return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);
17706f32e7eSjoerg }
17806f32e7eSjoerg 
emitDiagnostics(const BoundNodes & Nodes,const char * Type,BugReporter & BR,AnalysisDeclContext * ADC,const GCDAntipatternChecker * Checker)17906f32e7eSjoerg static void emitDiagnostics(const BoundNodes &Nodes,
18006f32e7eSjoerg                             const char* Type,
18106f32e7eSjoerg                             BugReporter &BR,
18206f32e7eSjoerg                             AnalysisDeclContext *ADC,
18306f32e7eSjoerg                             const GCDAntipatternChecker *Checker) {
18406f32e7eSjoerg   const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);
18506f32e7eSjoerg   assert(SW);
18606f32e7eSjoerg 
18706f32e7eSjoerg   std::string Diagnostics;
18806f32e7eSjoerg   llvm::raw_string_ostream OS(Diagnostics);
18906f32e7eSjoerg   OS << "Waiting on a callback using a " << Type << " creates useless threads "
19006f32e7eSjoerg      << "and is subject to priority inversion; consider "
19106f32e7eSjoerg      << "using a synchronous API or changing the caller to be asynchronous";
19206f32e7eSjoerg 
19306f32e7eSjoerg   BR.EmitBasicReport(
19406f32e7eSjoerg     ADC->getDecl(),
19506f32e7eSjoerg     Checker,
19606f32e7eSjoerg     /*Name=*/"GCD performance anti-pattern",
19706f32e7eSjoerg     /*BugCategory=*/"Performance",
19806f32e7eSjoerg     OS.str(),
19906f32e7eSjoerg     PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
20006f32e7eSjoerg     SW->getSourceRange());
20106f32e7eSjoerg }
20206f32e7eSjoerg 
checkASTCodeBody(const Decl * D,AnalysisManager & AM,BugReporter & BR) const20306f32e7eSjoerg void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
20406f32e7eSjoerg                                              AnalysisManager &AM,
20506f32e7eSjoerg                                              BugReporter &BR) const {
20606f32e7eSjoerg   if (isTest(D))
20706f32e7eSjoerg     return;
20806f32e7eSjoerg 
20906f32e7eSjoerg   AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
21006f32e7eSjoerg 
21106f32e7eSjoerg   auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore();
21206f32e7eSjoerg   auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext());
21306f32e7eSjoerg   for (BoundNodes Match : Matches)
21406f32e7eSjoerg     emitDiagnostics(Match, "semaphore", BR, ADC, this);
21506f32e7eSjoerg 
21606f32e7eSjoerg   auto GroupMatcherM = findGCDAntiPatternWithGroup();
21706f32e7eSjoerg   Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());
21806f32e7eSjoerg   for (BoundNodes Match : Matches)
21906f32e7eSjoerg     emitDiagnostics(Match, "group", BR, ADC, this);
22006f32e7eSjoerg }
22106f32e7eSjoerg 
22206f32e7eSjoerg } // end of anonymous namespace
22306f32e7eSjoerg 
registerGCDAntipattern(CheckerManager & Mgr)22406f32e7eSjoerg void ento::registerGCDAntipattern(CheckerManager &Mgr) {
22506f32e7eSjoerg   Mgr.registerChecker<GCDAntipatternChecker>();
22606f32e7eSjoerg }
22706f32e7eSjoerg 
shouldRegisterGCDAntipattern(const CheckerManager & mgr)228*13fbcb42Sjoerg bool ento::shouldRegisterGCDAntipattern(const CheckerManager &mgr) {
22906f32e7eSjoerg   return true;
23006f32e7eSjoerg }
231