diff options
author | George Karpenkov <ekarpenkov@apple.com> | 2018-03-05 22:03:32 +0000 |
---|---|---|
committer | George Karpenkov <ekarpenkov@apple.com> | 2018-03-05 22:03:32 +0000 |
commit | 436d5cc7ee39f6b395c6581eb30c010b050705bc (patch) | |
tree | 9518f3d20aa032ea12c6d60722e483b3bbf5d51f /clang/lib/StaticAnalyzer/Checkers/GCDAsyncSemaphoreChecker.cpp | |
parent | bf320ee335888af71d0f82f0f57f21b4b3531f6d (diff) | |
download | bcm5719-llvm-436d5cc7ee39f6b395c6581eb30c010b050705bc.tar.gz bcm5719-llvm-436d5cc7ee39f6b395c6581eb30c010b050705bc.zip |
[analyzer] AST-matching checker to detect global central dispatch performance anti-pattern
rdar://37312818
NB: The checker does not care about the ordering of callbacks, see the
relevant FIXME in tests.
Differential Revision: https://reviews.llvm.org/D44059
llvm-svn: 326746
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/GCDAsyncSemaphoreChecker.cpp')
-rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/GCDAsyncSemaphoreChecker.cpp | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/GCDAsyncSemaphoreChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/GCDAsyncSemaphoreChecker.cpp new file mode 100644 index 00000000000..48e9e1e7625 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/GCDAsyncSemaphoreChecker.cpp @@ -0,0 +1,154 @@ +//===- GCDAsyncSemaphoreChecker.cpp -----------------------------*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines GCDAsyncSemaphoreChecker which checks against a common +// antipattern when synchronous API is emulated from asynchronous callbacks +// using a semaphor: +// +// dispatch_semapshore_t sema = dispatch_semaphore_create(0); + +// AnyCFunctionCall(^{ +// // code⦠+// dispatch_semapshore_signal(sema); +// }) +// dispatch_semapshore_wait(sema, *) +// +// Such code is a common performance problem, due to inability of GCD to +// properly handle QoS when a combination of queues and semaphors 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 "ClangSACheckers.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" + +#define DEBUG_TYPE "gcdasyncsemaphorechecker" + +using namespace clang; +using namespace ento; +using namespace ast_matchers; + +namespace { + +const char *WarningBinding = "semaphore_wait"; + +class GCDAsyncSemaphoreChecker : public Checker<check::ASTCodeBody> { +public: + void checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const; +}; + +class Callback : public MatchFinder::MatchCallback { + BugReporter &BR; + const GCDAsyncSemaphoreChecker *C; + AnalysisDeclContext *ADC; + +public: + Callback(BugReporter &BR, + AnalysisDeclContext *ADC, + const GCDAsyncSemaphoreChecker *C) : BR(BR), C(C), ADC(ADC) {} + + virtual void run(const MatchFinder::MatchResult &Result) override; +}; + +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))))); +} + +void GCDAsyncSemaphoreChecker::checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const { + + // The pattern is very common in tests, and it is OK to use it there. + if (const auto* ND = dyn_cast<NamedDecl>(D)) + if (ND->getName().startswith("test")) + return; + + const char *SemaphoreBinding = "semaphore_name"; + auto SemaphoreCreateM = callExpr(callsName("dispatch_semaphore_create")); + + auto SemaphoreBindingM = anyOf( + forEachDescendant( + varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)), + forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding), + hasRHS(SemaphoreCreateM)))); + + auto SemaphoreWaitM = forEachDescendant( + callExpr( + allOf( + callsName("dispatch_semaphore_wait"), + equalsBoundArgDecl(0, SemaphoreBinding) + ) + ).bind(WarningBinding)); + + auto AcceptsBlockM = + forEachDescendant(callExpr(hasAnyArgument(hasType( + hasCanonicalType(blockPointerType()) + )))); + + auto BlockSignallingM = + forEachDescendant(callExpr(hasAnyArgument(hasDescendant(callExpr( + allOf( + callsName("dispatch_semaphore_signal"), + equalsBoundArgDecl(0, SemaphoreBinding) + )))))); + + auto FinalM = compoundStmt( + SemaphoreBindingM, + SemaphoreWaitM, + AcceptsBlockM, + BlockSignallingM); + + MatchFinder F; + Callback CB(BR, AM.getAnalysisDeclContext(D), this); + + F.addMatcher(FinalM, &CB); + F.match(*D->getBody(), AM.getASTContext()); +} + +void Callback::run(const MatchFinder::MatchResult &Result) { + const auto *SW = Result.Nodes.getNodeAs<CallExpr>(WarningBinding); + assert(SW); + BR.EmitBasicReport( + ADC->getDecl(), C, + /*Name=*/"Semaphore performance anti-pattern", + /*Category=*/"Performance", + "Possible semaphore performance anti-pattern: wait on a semaphore " + "signalled to in a callback", + PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC), + SW->getSourceRange()); +} + +} + +void ento::registerGCDAsyncSemaphoreChecker(CheckerManager &Mgr) { + Mgr.registerChecker<GCDAsyncSemaphoreChecker>(); +} |