diff options
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp')
| -rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp | 232 | 
1 files changed, 232 insertions, 0 deletions
| diff --git a/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp new file mode 100644 index 000000000000..d471c23b83bf --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp @@ -0,0 +1,232 @@ +//===- 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; +}; + +auto callsName(const char *FunctionName) +    -> decltype(callee(functionDecl())) { +  return callee(functionDecl(hasName(FunctionName))); +} + +auto equalsBoundArgDecl(int ArgIdx, const char *DeclName) +    -> decltype(hasArgument(0, expr())) { +  return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr( +                                 to(varDecl(equalsBoundNode(DeclName)))))); +} + +auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) { +  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_lower("test") || CN.contains_lower("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 LangOptions &LO) { +  return true; +} | 
