Index: include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- include/clang/StaticAnalyzer/Checkers/Checkers.td +++ include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -610,6 +610,13 @@ } // end "osx.cocoa" +let ParentPackage = OSXAlpha in { + +def GCDAsyncSemaphoreChecker : Checker<"GCDAsyncSemaphore">, + HelpText<"Checker for performance anti-pattern when using semaphors from async API">, + DescFile<"GCDAsyncSemaphoreChecker.cpp">; +} // end "alpha.osx" + let ParentPackage = CocoaAlpha in { def InstanceVariableInvalidation : Checker<"InstanceVariableInvalidation">, Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -37,6 +37,7 @@ DynamicTypeChecker.cpp ExprInspectionChecker.cpp FixedAddressChecker.cpp + GCDAsyncSemaphorChecker.cpp GenericTaintChecker.cpp GTestChecker.cpp IdenticalExprChecker.cpp Index: lib/StaticAnalyzer/Checkers/GCDAsyncSemaphorChecker.cpp =================================================================== --- /dev/null +++ lib/StaticAnalyzer/Checkers/GCDAsyncSemaphorChecker.cpp @@ -0,0 +1,115 @@ +//===- 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" + +using namespace clang; +using namespace ento; +using namespace ast_matchers; + +namespace { + +class GCDAsyncSemaphoreChecker : public Checker { +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; +}; + +void GCDAsyncSemaphoreChecker::checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const { + auto SemaphoreCreateM = hasDescendant( + callExpr(callee(functionDecl(hasName("dispatch_semaphore_create"))))); + + auto SemaphoreWaitM = hasDescendant( + callExpr(callee(functionDecl(hasName("dispatch_semaphore_wait")))) + .bind("semaphore_wait")); + + auto AcceptsBlockM = + hasDescendant(callExpr(hasAnyArgument(hasType(blockPointerType())))); + + auto BlockSignallingM = + hasDescendant(callExpr(hasAnyArgument(hasDescendant(callExpr( + callee(functionDecl(hasName("dispatch_semaphore_signal")))))))); + + auto FinalM = stmt(allOf( + SemaphoreCreateM, + 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("semaphore_wait"); + assert(SW); + BR.EmitBasicReport( + ADC->getDecl(), + C, + /*Name=*/"Possible semaphore anti-pattern", + /*Category=*/"Performance", + + // TODO: a better description message. + /*Str=*/"Possible semaphore anti-pattern", + PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC), + SW->getSourceRange()); +} + +} + +void ento::registerGCDAsyncSemaphoreChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} Index: test/gcdasyncsemaphorchecker_test.m =================================================================== --- /dev/null +++ test/gcdasyncsemaphorchecker_test.m @@ -0,0 +1,20 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.osx.GCDAsyncSemaphore %s -verify +typedef int dispatch_semaphore_t; +dispatch_semaphore_t dispatch_semaphore_create(int); +void dispatch_semaphore_wait(dispatch_semaphore_t, int); +void dispatch_semaphore_signal(dispatch_semaphore_t); + +void func(void (^)(void)); + +void foo() { + foo(); +} + +int main() { + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + + func(^{ + dispatch_semaphore_signal(sema); // expected-warning + }); + dispatch_semaphore_wait(sema, 100); +}