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