r326746 - [analyzer] AST-matching checker to detect global central dispatch performance anti-pattern
George Karpenkov via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 5 14:03:32 PST 2018
Author: george.karpenkov
Date: Mon Mar 5 14:03:32 2018
New Revision: 326746
URL: http://llvm.org/viewvc/llvm-project?rev=326746&view=rev
Log:
[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
Added:
cfe/trunk/lib/StaticAnalyzer/Checkers/GCDAsyncSemaphoreChecker.cpp
cfe/trunk/test/gcdasyncsemaphorechecker_test.m
Modified:
cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td
cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt
Modified: cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td?rev=326746&r1=326745&r2=326746&view=diff
==============================================================================
--- cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td (original)
+++ cfe/trunk/include/clang/StaticAnalyzer/Checkers/Checkers.td Mon Mar 5 14:03:32 2018
@@ -610,6 +610,13 @@ def ObjCSuperDeallocChecker : Checker<"S
} // 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">,
Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt?rev=326746&r1=326745&r2=326746&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt (original)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt Mon Mar 5 14:03:32 2018
@@ -37,6 +37,7 @@ add_clang_library(clangStaticAnalyzerChe
DynamicTypeChecker.cpp
ExprInspectionChecker.cpp
FixedAddressChecker.cpp
+ GCDAsyncSemaphoreChecker.cpp
GenericTaintChecker.cpp
GTestChecker.cpp
IdenticalExprChecker.cpp
Added: cfe/trunk/lib/StaticAnalyzer/Checkers/GCDAsyncSemaphoreChecker.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/GCDAsyncSemaphoreChecker.cpp?rev=326746&view=auto
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Checkers/GCDAsyncSemaphoreChecker.cpp (added)
+++ cfe/trunk/lib/StaticAnalyzer/Checkers/GCDAsyncSemaphoreChecker.cpp Mon Mar 5 14:03:32 2018
@@ -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>();
+}
Added: cfe/trunk/test/gcdasyncsemaphorechecker_test.m
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/gcdasyncsemaphorechecker_test.m?rev=326746&view=auto
==============================================================================
--- cfe/trunk/test/gcdasyncsemaphorechecker_test.m (added)
+++ cfe/trunk/test/gcdasyncsemaphorechecker_test.m Mon Mar 5 14:03:32 2018
@@ -0,0 +1,169 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.osx.GCDAsyncSemaphore %s -fblocks -verify
+//
+typedef int dispatch_semaphore_t;
+typedef void (^block_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 func_w_typedef(block_t);
+
+int coin();
+
+void use_semaphor_antipattern() {
+ dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+
+ func(^{
+ dispatch_semaphore_signal(sema);
+ });
+ dispatch_semaphore_wait(sema, 100); // expected-warning{{Possible semaphore performance anti-pattern}}
+}
+
+// It's OK to use pattern in tests.
+// We simply match the containing function name against ^test.
+void test_no_warning() {
+ dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+
+ func(^{
+ dispatch_semaphore_signal(sema);
+ });
+ dispatch_semaphore_wait(sema, 100);
+}
+
+void use_semaphor_antipattern_multiple_times() {
+ dispatch_semaphore_t sema1 = dispatch_semaphore_create(0);
+
+ func(^{
+ dispatch_semaphore_signal(sema1);
+ });
+ dispatch_semaphore_wait(sema1, 100); // expected-warning{{Possible semaphore performance anti-pattern}}
+
+ dispatch_semaphore_t sema2 = dispatch_semaphore_create(0);
+
+ func(^{
+ dispatch_semaphore_signal(sema2);
+ });
+ dispatch_semaphore_wait(sema2, 100); // expected-warning{{Possible semaphore performance anti-pattern}}
+}
+
+void use_semaphor_antipattern_multiple_wait() {
+ dispatch_semaphore_t sema1 = dispatch_semaphore_create(0);
+
+ func(^{
+ dispatch_semaphore_signal(sema1);
+ });
+ // FIXME: multiple waits on same semaphor should not raise a warning.
+ dispatch_semaphore_wait(sema1, 100); // expected-warning{{Possible semaphore performance anti-pattern}}
+ dispatch_semaphore_wait(sema1, 100); // expected-warning{{Possible semaphore performance anti-pattern}}
+}
+
+void warn_incorrect_order() {
+ // FIXME: ASTMatchers do not allow ordered matching, so would match even
+ // if out of order.
+ dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+
+ dispatch_semaphore_wait(sema, 100); // expected-warning{{Possible semaphore performance anti-pattern}}
+ func(^{
+ dispatch_semaphore_signal(sema);
+ });
+}
+
+void warn_w_typedef() {
+ dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+
+ func_w_typedef(^{
+ dispatch_semaphore_signal(sema);
+ });
+ dispatch_semaphore_wait(sema, 100); // expected-warning{{Possible semaphore performance anti-pattern}}
+}
+
+void warn_nested_ast() {
+ dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+
+ if (coin()) {
+ func(^{
+ dispatch_semaphore_signal(sema);
+ });
+ } else {
+ func(^{
+ dispatch_semaphore_signal(sema);
+ });
+ }
+ dispatch_semaphore_wait(sema, 100); // expected-warning{{Possible semaphore performance anti-pattern}}
+}
+
+void use_semaphore_assignment() {
+ dispatch_semaphore_t sema;
+ sema = dispatch_semaphore_create(0);
+
+ func(^{
+ dispatch_semaphore_signal(sema);
+ });
+ dispatch_semaphore_wait(sema, 100); // expected-warning{{Possible semaphore performance anti-pattern}}
+}
+
+void use_semaphore_assignment_init() {
+ dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+ sema = dispatch_semaphore_create(1);
+
+ func(^{
+ dispatch_semaphore_signal(sema);
+ });
+ dispatch_semaphore_wait(sema, 100); // expected-warning{{Possible semaphore performance anti-pattern}}
+}
+
+void differentsemaphoreok() {
+ dispatch_semaphore_t sema1 = dispatch_semaphore_create(0);
+ dispatch_semaphore_t sema2 = dispatch_semaphore_create(0);
+
+ func(^{
+ dispatch_semaphore_signal(sema1);
+ });
+ dispatch_semaphore_wait(sema2, 100); // no-warning
+}
+
+void nosignalok() {
+ dispatch_semaphore_t sema1 = dispatch_semaphore_create(0);
+ dispatch_semaphore_wait(sema1, 100);
+}
+
+void nowaitok() {
+ dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+ func(^{
+ dispatch_semaphore_signal(sema);
+ });
+}
+
+void noblockok() {
+ dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+ dispatch_semaphore_signal(sema);
+ dispatch_semaphore_wait(sema, 100);
+}
+
+void storedblockok() {
+ dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+ block_t b = ^{
+ dispatch_semaphore_signal(sema);
+ };
+ dispatch_semaphore_wait(sema, 100);
+}
+
+void passed_semaphore_ok(dispatch_semaphore_t sema) {
+ func(^{
+ dispatch_semaphore_signal(sema);
+ });
+ dispatch_semaphore_wait(sema, 100);
+}
+
+void warn_with_cast() {
+ dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+
+ func(^{
+ dispatch_semaphore_signal((int)sema);
+ });
+ dispatch_semaphore_wait((int)sema, 100); // expected-warning{{Possible semaphore performance anti-pattern}}
+}
+
+
More information about the cfe-commits
mailing list