GCDAntipatternChecker.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. //===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. //
  9. // This file defines GCDAntipatternChecker which checks against a common
  10. // antipattern when synchronous API is emulated from asynchronous callbacks
  11. // using a semaphore:
  12. //
  13. // dispatch_semaphore_t sema = dispatch_semaphore_create(0);
  14. //
  15. // AnyCFunctionCall(^{
  16. // // code…
  17. // dispatch_semaphore_signal(sema);
  18. // })
  19. // dispatch_semaphore_wait(sema, *)
  20. //
  21. // Such code is a common performance problem, due to inability of GCD to
  22. // properly handle QoS when a combination of queues and semaphores is used.
  23. // Good code would either use asynchronous API (when available), or perform
  24. // the necessary action in asynchronous callback.
  25. //
  26. // Currently, the check is performed using a simple heuristical AST pattern
  27. // matching.
  28. //
  29. //===----------------------------------------------------------------------===//
  30. #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
  31. #include "clang/ASTMatchers/ASTMatchFinder.h"
  32. #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
  33. #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
  34. #include "clang/StaticAnalyzer/Core/Checker.h"
  35. #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
  36. #include "llvm/Support/Debug.h"
  37. using namespace clang;
  38. using namespace ento;
  39. using namespace ast_matchers;
  40. namespace {
  41. // ID of a node at which the diagnostic would be emitted.
  42. const char *WarnAtNode = "waitcall";
  43. class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {
  44. public:
  45. void checkASTCodeBody(const Decl *D,
  46. AnalysisManager &AM,
  47. BugReporter &BR) const;
  48. };
  49. decltype(auto) callsName(const char *FunctionName) {
  50. return callee(functionDecl(hasName(FunctionName)));
  51. }
  52. decltype(auto) equalsBoundArgDecl(int ArgIdx, const char *DeclName) {
  53. return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
  54. to(varDecl(equalsBoundNode(DeclName))))));
  55. }
  56. decltype(auto) bindAssignmentToDecl(const char *DeclName) {
  57. return hasLHS(ignoringParenImpCasts(
  58. declRefExpr(to(varDecl().bind(DeclName)))));
  59. }
  60. /// The pattern is very common in tests, and it is OK to use it there.
  61. /// We have to heuristics for detecting tests: method name starts with "test"
  62. /// (used in XCTest), and a class name contains "mock" or "test" (used in
  63. /// helpers which are not tests themselves, but used exclusively in tests).
  64. static bool isTest(const Decl *D) {
  65. if (const auto* ND = dyn_cast<NamedDecl>(D)) {
  66. std::string DeclName = ND->getNameAsString();
  67. if (StringRef(DeclName).startswith("test"))
  68. return true;
  69. }
  70. if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) {
  71. if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) {
  72. std::string ContainerName = CD->getNameAsString();
  73. StringRef CN(ContainerName);
  74. if (CN.contains_insensitive("test") || CN.contains_insensitive("mock"))
  75. return true;
  76. }
  77. }
  78. return false;
  79. }
  80. static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {
  81. const char *SemaphoreBinding = "semaphore_name";
  82. auto SemaphoreCreateM = callExpr(allOf(
  83. callsName("dispatch_semaphore_create"),
  84. hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));
  85. auto SemaphoreBindingM = anyOf(
  86. forEachDescendant(
  87. varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
  88. forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
  89. hasRHS(SemaphoreCreateM))));
  90. auto HasBlockArgumentM = hasAnyArgument(hasType(
  91. hasCanonicalType(blockPointerType())
  92. ));
  93. auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
  94. allOf(
  95. callsName("dispatch_semaphore_signal"),
  96. equalsBoundArgDecl(0, SemaphoreBinding)
  97. )))));
  98. auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
  99. auto HasBlockCallingSignalM =
  100. forEachDescendant(
  101. stmt(anyOf(
  102. callExpr(HasBlockAndCallsSignalM),
  103. objcMessageExpr(HasBlockAndCallsSignalM)
  104. )));
  105. auto SemaphoreWaitM = forEachDescendant(
  106. callExpr(
  107. allOf(
  108. callsName("dispatch_semaphore_wait"),
  109. equalsBoundArgDecl(0, SemaphoreBinding)
  110. )
  111. ).bind(WarnAtNode));
  112. return compoundStmt(
  113. SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
  114. }
  115. static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
  116. const char *GroupBinding = "group_name";
  117. auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
  118. auto GroupBindingM = anyOf(
  119. forEachDescendant(
  120. varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),
  121. forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),
  122. hasRHS(DispatchGroupCreateM))));
  123. auto GroupEnterM = forEachDescendant(
  124. stmt(callExpr(allOf(callsName("dispatch_group_enter"),
  125. equalsBoundArgDecl(0, GroupBinding)))));
  126. auto HasBlockArgumentM = hasAnyArgument(hasType(
  127. hasCanonicalType(blockPointerType())
  128. ));
  129. auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
  130. allOf(
  131. callsName("dispatch_group_leave"),
  132. equalsBoundArgDecl(0, GroupBinding)
  133. )))));
  134. auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);
  135. auto AcceptsBlockM =
  136. forEachDescendant(
  137. stmt(anyOf(
  138. callExpr(HasBlockAndCallsLeaveM),
  139. objcMessageExpr(HasBlockAndCallsLeaveM)
  140. )));
  141. auto GroupWaitM = forEachDescendant(
  142. callExpr(
  143. allOf(
  144. callsName("dispatch_group_wait"),
  145. equalsBoundArgDecl(0, GroupBinding)
  146. )
  147. ).bind(WarnAtNode));
  148. return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);
  149. }
  150. static void emitDiagnostics(const BoundNodes &Nodes,
  151. const char* Type,
  152. BugReporter &BR,
  153. AnalysisDeclContext *ADC,
  154. const GCDAntipatternChecker *Checker) {
  155. const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);
  156. assert(SW);
  157. std::string Diagnostics;
  158. llvm::raw_string_ostream OS(Diagnostics);
  159. OS << "Waiting on a callback using a " << Type << " creates useless threads "
  160. << "and is subject to priority inversion; consider "
  161. << "using a synchronous API or changing the caller to be asynchronous";
  162. BR.EmitBasicReport(
  163. ADC->getDecl(),
  164. Checker,
  165. /*Name=*/"GCD performance anti-pattern",
  166. /*BugCategory=*/"Performance",
  167. OS.str(),
  168. PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
  169. SW->getSourceRange());
  170. }
  171. void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
  172. AnalysisManager &AM,
  173. BugReporter &BR) const {
  174. if (isTest(D))
  175. return;
  176. AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
  177. auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore();
  178. auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext());
  179. for (BoundNodes Match : Matches)
  180. emitDiagnostics(Match, "semaphore", BR, ADC, this);
  181. auto GroupMatcherM = findGCDAntiPatternWithGroup();
  182. Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());
  183. for (BoundNodes Match : Matches)
  184. emitDiagnostics(Match, "group", BR, ADC, this);
  185. }
  186. } // end of anonymous namespace
  187. void ento::registerGCDAntipattern(CheckerManager &Mgr) {
  188. Mgr.registerChecker<GCDAntipatternChecker>();
  189. }
  190. bool ento::shouldRegisterGCDAntipattern(const CheckerManager &mgr) {
  191. return true;
  192. }