123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- //===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- //===----------------------------------------------------------------------===//
- //
- // This file defines GCDAntipatternChecker which checks against a common
- // antipattern when synchronous API is emulated from asynchronous callbacks
- // using a semaphore:
- //
- // dispatch_semaphore_t sema = dispatch_semaphore_create(0);
- //
- // AnyCFunctionCall(^{
- // // code…
- // dispatch_semaphore_signal(sema);
- // })
- // dispatch_semaphore_wait(sema, *)
- //
- // Such code is a common performance problem, due to inability of GCD to
- // properly handle QoS when a combination of queues and semaphores is used.
- // Good code would either use asynchronous API (when available), or perform
- // the necessary action in asynchronous callback.
- //
- // Currently, the check is performed using a simple heuristical AST pattern
- // matching.
- //
- //===----------------------------------------------------------------------===//
- #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
- #include "clang/ASTMatchers/ASTMatchFinder.h"
- #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
- #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
- #include "clang/StaticAnalyzer/Core/Checker.h"
- #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
- #include "llvm/Support/Debug.h"
- using namespace clang;
- using namespace ento;
- using namespace ast_matchers;
- namespace {
- // ID of a node at which the diagnostic would be emitted.
- const char *WarnAtNode = "waitcall";
- class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {
- public:
- void checkASTCodeBody(const Decl *D,
- AnalysisManager &AM,
- BugReporter &BR) const;
- };
- decltype(auto) callsName(const char *FunctionName) {
- return callee(functionDecl(hasName(FunctionName)));
- }
- decltype(auto) equalsBoundArgDecl(int ArgIdx, const char *DeclName) {
- return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
- to(varDecl(equalsBoundNode(DeclName))))));
- }
- decltype(auto) bindAssignmentToDecl(const char *DeclName) {
- return hasLHS(ignoringParenImpCasts(
- declRefExpr(to(varDecl().bind(DeclName)))));
- }
- /// The pattern is very common in tests, and it is OK to use it there.
- /// We have to heuristics for detecting tests: method name starts with "test"
- /// (used in XCTest), and a class name contains "mock" or "test" (used in
- /// helpers which are not tests themselves, but used exclusively in tests).
- static bool isTest(const Decl *D) {
- if (const auto* ND = dyn_cast<NamedDecl>(D)) {
- std::string DeclName = ND->getNameAsString();
- if (StringRef(DeclName).startswith("test"))
- return true;
- }
- if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) {
- if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) {
- std::string ContainerName = CD->getNameAsString();
- StringRef CN(ContainerName);
- if (CN.contains_insensitive("test") || CN.contains_insensitive("mock"))
- return true;
- }
- }
- return false;
- }
- static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {
- const char *SemaphoreBinding = "semaphore_name";
- auto SemaphoreCreateM = callExpr(allOf(
- callsName("dispatch_semaphore_create"),
- hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));
- auto SemaphoreBindingM = anyOf(
- forEachDescendant(
- varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
- forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
- hasRHS(SemaphoreCreateM))));
- auto HasBlockArgumentM = hasAnyArgument(hasType(
- hasCanonicalType(blockPointerType())
- ));
- auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
- allOf(
- callsName("dispatch_semaphore_signal"),
- equalsBoundArgDecl(0, SemaphoreBinding)
- )))));
- auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
- auto HasBlockCallingSignalM =
- forEachDescendant(
- stmt(anyOf(
- callExpr(HasBlockAndCallsSignalM),
- objcMessageExpr(HasBlockAndCallsSignalM)
- )));
- auto SemaphoreWaitM = forEachDescendant(
- callExpr(
- allOf(
- callsName("dispatch_semaphore_wait"),
- equalsBoundArgDecl(0, SemaphoreBinding)
- )
- ).bind(WarnAtNode));
- return compoundStmt(
- SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
- }
- static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
- const char *GroupBinding = "group_name";
- auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
- auto GroupBindingM = anyOf(
- forEachDescendant(
- varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),
- forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),
- hasRHS(DispatchGroupCreateM))));
- auto GroupEnterM = forEachDescendant(
- stmt(callExpr(allOf(callsName("dispatch_group_enter"),
- equalsBoundArgDecl(0, GroupBinding)))));
- auto HasBlockArgumentM = hasAnyArgument(hasType(
- hasCanonicalType(blockPointerType())
- ));
- auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
- allOf(
- callsName("dispatch_group_leave"),
- equalsBoundArgDecl(0, GroupBinding)
- )))));
- auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);
- auto AcceptsBlockM =
- forEachDescendant(
- stmt(anyOf(
- callExpr(HasBlockAndCallsLeaveM),
- objcMessageExpr(HasBlockAndCallsLeaveM)
- )));
- auto GroupWaitM = forEachDescendant(
- callExpr(
- allOf(
- callsName("dispatch_group_wait"),
- equalsBoundArgDecl(0, GroupBinding)
- )
- ).bind(WarnAtNode));
- return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);
- }
- static void emitDiagnostics(const BoundNodes &Nodes,
- const char* Type,
- BugReporter &BR,
- AnalysisDeclContext *ADC,
- const GCDAntipatternChecker *Checker) {
- const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);
- assert(SW);
- std::string Diagnostics;
- llvm::raw_string_ostream OS(Diagnostics);
- OS << "Waiting on a callback using a " << Type << " creates useless threads "
- << "and is subject to priority inversion; consider "
- << "using a synchronous API or changing the caller to be asynchronous";
- BR.EmitBasicReport(
- ADC->getDecl(),
- Checker,
- /*Name=*/"GCD performance anti-pattern",
- /*BugCategory=*/"Performance",
- OS.str(),
- PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
- SW->getSourceRange());
- }
- void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
- AnalysisManager &AM,
- BugReporter &BR) const {
- if (isTest(D))
- return;
- AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
- auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore();
- auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext());
- for (BoundNodes Match : Matches)
- emitDiagnostics(Match, "semaphore", BR, ADC, this);
- auto GroupMatcherM = findGCDAntiPatternWithGroup();
- Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());
- for (BoundNodes Match : Matches)
- emitDiagnostics(Match, "group", BR, ADC, this);
- }
- } // end of anonymous namespace
- void ento::registerGCDAntipattern(CheckerManager &Mgr) {
- Mgr.registerChecker<GCDAntipatternChecker>();
- }
- bool ento::shouldRegisterGCDAntipattern(const CheckerManager &mgr) {
- return true;
- }
|