17330f729Sjoerg //===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg //
97330f729Sjoerg // This file defines GCDAntipatternChecker which checks against a common
107330f729Sjoerg // antipattern when synchronous API is emulated from asynchronous callbacks
117330f729Sjoerg // using a semaphore:
127330f729Sjoerg //
137330f729Sjoerg // dispatch_semaphore_t sema = dispatch_semaphore_create(0);
147330f729Sjoerg //
157330f729Sjoerg // AnyCFunctionCall(^{
167330f729Sjoerg // // code…
177330f729Sjoerg // dispatch_semaphore_signal(sema);
187330f729Sjoerg // })
197330f729Sjoerg // dispatch_semaphore_wait(sema, *)
207330f729Sjoerg //
217330f729Sjoerg // Such code is a common performance problem, due to inability of GCD to
227330f729Sjoerg // properly handle QoS when a combination of queues and semaphores is used.
237330f729Sjoerg // Good code would either use asynchronous API (when available), or perform
247330f729Sjoerg // the necessary action in asynchronous callback.
257330f729Sjoerg //
267330f729Sjoerg // Currently, the check is performed using a simple heuristical AST pattern
277330f729Sjoerg // matching.
287330f729Sjoerg //
297330f729Sjoerg //===----------------------------------------------------------------------===//
307330f729Sjoerg
317330f729Sjoerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
327330f729Sjoerg #include "clang/ASTMatchers/ASTMatchFinder.h"
337330f729Sjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
347330f729Sjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
357330f729Sjoerg #include "clang/StaticAnalyzer/Core/Checker.h"
367330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
377330f729Sjoerg #include "llvm/Support/Debug.h"
387330f729Sjoerg
397330f729Sjoerg using namespace clang;
407330f729Sjoerg using namespace ento;
417330f729Sjoerg using namespace ast_matchers;
427330f729Sjoerg
437330f729Sjoerg namespace {
447330f729Sjoerg
457330f729Sjoerg // ID of a node at which the diagnostic would be emitted.
467330f729Sjoerg const char *WarnAtNode = "waitcall";
477330f729Sjoerg
487330f729Sjoerg class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {
497330f729Sjoerg public:
507330f729Sjoerg void checkASTCodeBody(const Decl *D,
517330f729Sjoerg AnalysisManager &AM,
527330f729Sjoerg BugReporter &BR) const;
537330f729Sjoerg };
547330f729Sjoerg
callsName(const char * FunctionName)55*e038c9c4Sjoerg decltype(auto) callsName(const char *FunctionName) {
567330f729Sjoerg return callee(functionDecl(hasName(FunctionName)));
577330f729Sjoerg }
587330f729Sjoerg
equalsBoundArgDecl(int ArgIdx,const char * DeclName)59*e038c9c4Sjoerg decltype(auto) equalsBoundArgDecl(int ArgIdx, const char *DeclName) {
607330f729Sjoerg return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
617330f729Sjoerg to(varDecl(equalsBoundNode(DeclName))))));
627330f729Sjoerg }
637330f729Sjoerg
bindAssignmentToDecl(const char * DeclName)64*e038c9c4Sjoerg decltype(auto) bindAssignmentToDecl(const char *DeclName) {
657330f729Sjoerg return hasLHS(ignoringParenImpCasts(
667330f729Sjoerg declRefExpr(to(varDecl().bind(DeclName)))));
677330f729Sjoerg }
687330f729Sjoerg
697330f729Sjoerg /// The pattern is very common in tests, and it is OK to use it there.
707330f729Sjoerg /// We have to heuristics for detecting tests: method name starts with "test"
717330f729Sjoerg /// (used in XCTest), and a class name contains "mock" or "test" (used in
727330f729Sjoerg /// helpers which are not tests themselves, but used exclusively in tests).
isTest(const Decl * D)737330f729Sjoerg static bool isTest(const Decl *D) {
747330f729Sjoerg if (const auto* ND = dyn_cast<NamedDecl>(D)) {
757330f729Sjoerg std::string DeclName = ND->getNameAsString();
767330f729Sjoerg if (StringRef(DeclName).startswith("test"))
777330f729Sjoerg return true;
787330f729Sjoerg }
797330f729Sjoerg if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) {
807330f729Sjoerg if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) {
817330f729Sjoerg std::string ContainerName = CD->getNameAsString();
827330f729Sjoerg StringRef CN(ContainerName);
837330f729Sjoerg if (CN.contains_lower("test") || CN.contains_lower("mock"))
847330f729Sjoerg return true;
857330f729Sjoerg }
867330f729Sjoerg }
877330f729Sjoerg return false;
887330f729Sjoerg }
897330f729Sjoerg
findGCDAntiPatternWithSemaphore()907330f729Sjoerg static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {
917330f729Sjoerg
927330f729Sjoerg const char *SemaphoreBinding = "semaphore_name";
937330f729Sjoerg auto SemaphoreCreateM = callExpr(allOf(
947330f729Sjoerg callsName("dispatch_semaphore_create"),
957330f729Sjoerg hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));
967330f729Sjoerg
977330f729Sjoerg auto SemaphoreBindingM = anyOf(
987330f729Sjoerg forEachDescendant(
997330f729Sjoerg varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
1007330f729Sjoerg forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
1017330f729Sjoerg hasRHS(SemaphoreCreateM))));
1027330f729Sjoerg
1037330f729Sjoerg auto HasBlockArgumentM = hasAnyArgument(hasType(
1047330f729Sjoerg hasCanonicalType(blockPointerType())
1057330f729Sjoerg ));
1067330f729Sjoerg
1077330f729Sjoerg auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
1087330f729Sjoerg allOf(
1097330f729Sjoerg callsName("dispatch_semaphore_signal"),
1107330f729Sjoerg equalsBoundArgDecl(0, SemaphoreBinding)
1117330f729Sjoerg )))));
1127330f729Sjoerg
1137330f729Sjoerg auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
1147330f729Sjoerg
1157330f729Sjoerg auto HasBlockCallingSignalM =
1167330f729Sjoerg forEachDescendant(
1177330f729Sjoerg stmt(anyOf(
1187330f729Sjoerg callExpr(HasBlockAndCallsSignalM),
1197330f729Sjoerg objcMessageExpr(HasBlockAndCallsSignalM)
1207330f729Sjoerg )));
1217330f729Sjoerg
1227330f729Sjoerg auto SemaphoreWaitM = forEachDescendant(
1237330f729Sjoerg callExpr(
1247330f729Sjoerg allOf(
1257330f729Sjoerg callsName("dispatch_semaphore_wait"),
1267330f729Sjoerg equalsBoundArgDecl(0, SemaphoreBinding)
1277330f729Sjoerg )
1287330f729Sjoerg ).bind(WarnAtNode));
1297330f729Sjoerg
1307330f729Sjoerg return compoundStmt(
1317330f729Sjoerg SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
1327330f729Sjoerg }
1337330f729Sjoerg
findGCDAntiPatternWithGroup()1347330f729Sjoerg static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
1357330f729Sjoerg
1367330f729Sjoerg const char *GroupBinding = "group_name";
1377330f729Sjoerg auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
1387330f729Sjoerg
1397330f729Sjoerg auto GroupBindingM = anyOf(
1407330f729Sjoerg forEachDescendant(
1417330f729Sjoerg varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),
1427330f729Sjoerg forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),
1437330f729Sjoerg hasRHS(DispatchGroupCreateM))));
1447330f729Sjoerg
1457330f729Sjoerg auto GroupEnterM = forEachDescendant(
1467330f729Sjoerg stmt(callExpr(allOf(callsName("dispatch_group_enter"),
1477330f729Sjoerg equalsBoundArgDecl(0, GroupBinding)))));
1487330f729Sjoerg
1497330f729Sjoerg auto HasBlockArgumentM = hasAnyArgument(hasType(
1507330f729Sjoerg hasCanonicalType(blockPointerType())
1517330f729Sjoerg ));
1527330f729Sjoerg
1537330f729Sjoerg auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
1547330f729Sjoerg allOf(
1557330f729Sjoerg callsName("dispatch_group_leave"),
1567330f729Sjoerg equalsBoundArgDecl(0, GroupBinding)
1577330f729Sjoerg )))));
1587330f729Sjoerg
1597330f729Sjoerg auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);
1607330f729Sjoerg
1617330f729Sjoerg auto AcceptsBlockM =
1627330f729Sjoerg forEachDescendant(
1637330f729Sjoerg stmt(anyOf(
1647330f729Sjoerg callExpr(HasBlockAndCallsLeaveM),
1657330f729Sjoerg objcMessageExpr(HasBlockAndCallsLeaveM)
1667330f729Sjoerg )));
1677330f729Sjoerg
1687330f729Sjoerg auto GroupWaitM = forEachDescendant(
1697330f729Sjoerg callExpr(
1707330f729Sjoerg allOf(
1717330f729Sjoerg callsName("dispatch_group_wait"),
1727330f729Sjoerg equalsBoundArgDecl(0, GroupBinding)
1737330f729Sjoerg )
1747330f729Sjoerg ).bind(WarnAtNode));
1757330f729Sjoerg
1767330f729Sjoerg return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);
1777330f729Sjoerg }
1787330f729Sjoerg
emitDiagnostics(const BoundNodes & Nodes,const char * Type,BugReporter & BR,AnalysisDeclContext * ADC,const GCDAntipatternChecker * Checker)1797330f729Sjoerg static void emitDiagnostics(const BoundNodes &Nodes,
1807330f729Sjoerg const char* Type,
1817330f729Sjoerg BugReporter &BR,
1827330f729Sjoerg AnalysisDeclContext *ADC,
1837330f729Sjoerg const GCDAntipatternChecker *Checker) {
1847330f729Sjoerg const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);
1857330f729Sjoerg assert(SW);
1867330f729Sjoerg
1877330f729Sjoerg std::string Diagnostics;
1887330f729Sjoerg llvm::raw_string_ostream OS(Diagnostics);
1897330f729Sjoerg OS << "Waiting on a callback using a " << Type << " creates useless threads "
1907330f729Sjoerg << "and is subject to priority inversion; consider "
1917330f729Sjoerg << "using a synchronous API or changing the caller to be asynchronous";
1927330f729Sjoerg
1937330f729Sjoerg BR.EmitBasicReport(
1947330f729Sjoerg ADC->getDecl(),
1957330f729Sjoerg Checker,
1967330f729Sjoerg /*Name=*/"GCD performance anti-pattern",
1977330f729Sjoerg /*BugCategory=*/"Performance",
1987330f729Sjoerg OS.str(),
1997330f729Sjoerg PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
2007330f729Sjoerg SW->getSourceRange());
2017330f729Sjoerg }
2027330f729Sjoerg
checkASTCodeBody(const Decl * D,AnalysisManager & AM,BugReporter & BR) const2037330f729Sjoerg void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
2047330f729Sjoerg AnalysisManager &AM,
2057330f729Sjoerg BugReporter &BR) const {
2067330f729Sjoerg if (isTest(D))
2077330f729Sjoerg return;
2087330f729Sjoerg
2097330f729Sjoerg AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
2107330f729Sjoerg
2117330f729Sjoerg auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore();
2127330f729Sjoerg auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext());
2137330f729Sjoerg for (BoundNodes Match : Matches)
2147330f729Sjoerg emitDiagnostics(Match, "semaphore", BR, ADC, this);
2157330f729Sjoerg
2167330f729Sjoerg auto GroupMatcherM = findGCDAntiPatternWithGroup();
2177330f729Sjoerg Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());
2187330f729Sjoerg for (BoundNodes Match : Matches)
2197330f729Sjoerg emitDiagnostics(Match, "group", BR, ADC, this);
2207330f729Sjoerg }
2217330f729Sjoerg
2227330f729Sjoerg } // end of anonymous namespace
2237330f729Sjoerg
registerGCDAntipattern(CheckerManager & Mgr)2247330f729Sjoerg void ento::registerGCDAntipattern(CheckerManager &Mgr) {
2257330f729Sjoerg Mgr.registerChecker<GCDAntipatternChecker>();
2267330f729Sjoerg }
2277330f729Sjoerg
shouldRegisterGCDAntipattern(const CheckerManager & mgr)228*e038c9c4Sjoerg bool ento::shouldRegisterGCDAntipattern(const CheckerManager &mgr) {
2297330f729Sjoerg return true;
2307330f729Sjoerg }
231