Path: blob/main/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp
35269 views
//===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file defines GCDAntipatternChecker which checks against a common9// antipattern when synchronous API is emulated from asynchronous callbacks10// using a semaphore:11//12// dispatch_semaphore_t sema = dispatch_semaphore_create(0);13//14// AnyCFunctionCall(^{15// // codeā¦16// dispatch_semaphore_signal(sema);17// })18// dispatch_semaphore_wait(sema, *)19//20// Such code is a common performance problem, due to inability of GCD to21// properly handle QoS when a combination of queues and semaphores is used.22// Good code would either use asynchronous API (when available), or perform23// the necessary action in asynchronous callback.24//25// Currently, the check is performed using a simple heuristical AST pattern26// matching.27//28//===----------------------------------------------------------------------===//2930#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"3738using namespace clang;39using namespace ento;40using namespace ast_matchers;4142namespace {4344// ID of a node at which the diagnostic would be emitted.45const char *WarnAtNode = "waitcall";4647class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {48public:49void checkASTCodeBody(const Decl *D,50AnalysisManager &AM,51BugReporter &BR) const;52};5354decltype(auto) callsName(const char *FunctionName) {55return callee(functionDecl(hasName(FunctionName)));56}5758decltype(auto) equalsBoundArgDecl(int ArgIdx, const char *DeclName) {59return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(60to(varDecl(equalsBoundNode(DeclName))))));61}6263decltype(auto) bindAssignmentToDecl(const char *DeclName) {64return hasLHS(ignoringParenImpCasts(65declRefExpr(to(varDecl().bind(DeclName)))));66}6768/// The pattern is very common in tests, and it is OK to use it there.69/// We have to heuristics for detecting tests: method name starts with "test"70/// (used in XCTest), and a class name contains "mock" or "test" (used in71/// helpers which are not tests themselves, but used exclusively in tests).72static bool isTest(const Decl *D) {73if (const auto* ND = dyn_cast<NamedDecl>(D)) {74std::string DeclName = ND->getNameAsString();75if (StringRef(DeclName).starts_with("test"))76return true;77}78if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) {79if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) {80std::string ContainerName = CD->getNameAsString();81StringRef CN(ContainerName);82if (CN.contains_insensitive("test") || CN.contains_insensitive("mock"))83return true;84}85}86return false;87}8889static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {9091const char *SemaphoreBinding = "semaphore_name";92auto SemaphoreCreateM = callExpr(allOf(93callsName("dispatch_semaphore_create"),94hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));9596auto SemaphoreBindingM = anyOf(97forEachDescendant(98varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),99forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),100hasRHS(SemaphoreCreateM))));101102auto HasBlockArgumentM = hasAnyArgument(hasType(103hasCanonicalType(blockPointerType())104));105106auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(107allOf(108callsName("dispatch_semaphore_signal"),109equalsBoundArgDecl(0, SemaphoreBinding)110)))));111112auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);113114auto HasBlockCallingSignalM =115forEachDescendant(116stmt(anyOf(117callExpr(HasBlockAndCallsSignalM),118objcMessageExpr(HasBlockAndCallsSignalM)119)));120121auto SemaphoreWaitM = forEachDescendant(122callExpr(123allOf(124callsName("dispatch_semaphore_wait"),125equalsBoundArgDecl(0, SemaphoreBinding)126)127).bind(WarnAtNode));128129return compoundStmt(130SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);131}132133static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {134135const char *GroupBinding = "group_name";136auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));137138auto GroupBindingM = anyOf(139forEachDescendant(140varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),141forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),142hasRHS(DispatchGroupCreateM))));143144auto GroupEnterM = forEachDescendant(145stmt(callExpr(allOf(callsName("dispatch_group_enter"),146equalsBoundArgDecl(0, GroupBinding)))));147148auto HasBlockArgumentM = hasAnyArgument(hasType(149hasCanonicalType(blockPointerType())150));151152auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(153allOf(154callsName("dispatch_group_leave"),155equalsBoundArgDecl(0, GroupBinding)156)))));157158auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);159160auto AcceptsBlockM =161forEachDescendant(162stmt(anyOf(163callExpr(HasBlockAndCallsLeaveM),164objcMessageExpr(HasBlockAndCallsLeaveM)165)));166167auto GroupWaitM = forEachDescendant(168callExpr(169allOf(170callsName("dispatch_group_wait"),171equalsBoundArgDecl(0, GroupBinding)172)173).bind(WarnAtNode));174175return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);176}177178static void emitDiagnostics(const BoundNodes &Nodes,179const char* Type,180BugReporter &BR,181AnalysisDeclContext *ADC,182const GCDAntipatternChecker *Checker) {183const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);184assert(SW);185186std::string Diagnostics;187llvm::raw_string_ostream OS(Diagnostics);188OS << "Waiting on a callback using a " << Type << " creates useless threads "189<< "and is subject to priority inversion; consider "190<< "using a synchronous API or changing the caller to be asynchronous";191192BR.EmitBasicReport(193ADC->getDecl(),194Checker,195/*Name=*/"GCD performance anti-pattern",196/*BugCategory=*/"Performance",197OS.str(),198PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),199SW->getSourceRange());200}201202void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,203AnalysisManager &AM,204BugReporter &BR) const {205if (isTest(D))206return;207208AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);209210auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore();211auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext());212for (BoundNodes Match : Matches)213emitDiagnostics(Match, "semaphore", BR, ADC, this);214215auto GroupMatcherM = findGCDAntiPatternWithGroup();216Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());217for (BoundNodes Match : Matches)218emitDiagnostics(Match, "group", BR, ADC, this);219}220221} // end of anonymous namespace222223void ento::registerGCDAntipattern(CheckerManager &Mgr) {224Mgr.registerChecker<GCDAntipatternChecker>();225}226227bool ento::shouldRegisterGCDAntipattern(const CheckerManager &mgr) {228return true;229}230231232