diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -34,6 +34,7 @@ #include "MisplacedWideningCastCheck.h" #include "MoveForwardingReferenceCheck.h" #include "MultipleStatementMacroCheck.h" +#include "NoEscapeCheck.h" #include "NotNullTerminatedResultCheck.h" #include "ParentVirtualCallCheck.h" #include "PosixReturnCheck.h" @@ -120,6 +121,7 @@ "bugprone-multiple-statement-macro"); CheckFactories.registerCheck( "bugprone-narrowing-conversions"); + CheckFactories.registerCheck("bugprone-no-escape"); CheckFactories.registerCheck( "bugprone-not-null-terminated-result"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -29,6 +29,7 @@ MisplacedWideningCastCheck.cpp MoveForwardingReferenceCheck.cpp MultipleStatementMacroCheck.cpp + NoEscapeCheck.cpp NotNullTerminatedResultCheck.cpp ParentVirtualCallCheck.cpp PosixReturnCheck.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/NoEscapeCheck.h b/clang-tools-extra/clang-tidy/bugprone/NoEscapeCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/NoEscapeCheck.h @@ -0,0 +1,39 @@ +//===--- NoEscapeCheck.h - clang-tidy ---------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NOESCAPECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NOESCAPECHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Block arguments in `dispatch_async()` and `dispatch_after()` are guaranteed +/// to escape. If those blocks capture any pointers with the `noescape` +/// attribute, then we warn the user of their error. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-no-escape.html +class NoEscapeCheck : public ClangTidyCheck { +public: + NoEscapeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.Blocks; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NOESCAPECHECK_H diff --git a/clang-tools-extra/clang-tidy/bugprone/NoEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/NoEscapeCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/NoEscapeCheck.cpp @@ -0,0 +1,51 @@ +//===--- NoEscapeCheck.cpp - clang-tidy -----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "NoEscapeCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +void NoEscapeCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr(callee(functionDecl(hasName("::dispatch_async"))), + argumentCountIs(2), + hasArgument(1, blockExpr().bind("arg-block"))), + this); + Finder->addMatcher(callExpr(callee(functionDecl(hasName("::dispatch_after"))), + argumentCountIs(3), + hasArgument(2, blockExpr().bind("arg-block"))), + this); +} + +void NoEscapeCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedEscapingBlock = + Result.Nodes.getNodeAs("arg-block"); + const BlockDecl *EscapingBlockDecl = MatchedEscapingBlock->getBlockDecl(); + for (const BlockDecl::Capture &CapturedVar : EscapingBlockDecl->captures()) { + const VarDecl *Var = CapturedVar.getVariable(); + if (Var && Var->hasAttr()) { + // FIXME: Add a method to get the location of the use of a CapturedVar so + // that we can diagnose the use of the pointer instead of the block. + diag(MatchedEscapingBlock->getBeginLoc(), + "pointer %0 with attribute 'noescape' is captured by an " + "asynchronously-executed block") + << Var; + diag(Var->getBeginLoc(), "the 'noescape' attribute is declared here.", + DiagnosticIDs::Note); + } + } +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -94,6 +94,12 @@ result of a memory allocation function (``malloc()``, ``calloc()``, ``realloc()``, ``alloca()``) instead of its argument. +- New :doc:`bugprone-no-escape + ` check. + + Finds pointers with the ``noescape`` attribute that are captured by an + asynchronously-executed block. + - New :doc:`bugprone-spuriously-wake-up-functions ` check. @@ -201,14 +207,14 @@ Now checks ``std::basic_string_view`` by default. - Improved :doc:`readability-else-after-return - ` check now supports a + ` check now supports a `WarnOnConditionVariables` option to control whether to refactor condition variables where possible. - Improved :doc:`readability-identifier-naming ` check. - Now able to rename member references in class template definitions with + Now able to rename member references in class template definitions with explicit access. - Improved :doc:`readability-qualified-auto diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone-no-escape.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone-no-escape.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone-no-escape.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - bugprone-no-escape + +bugprone-no-escape +================== + +Finds pointers with the ``noescape`` attribute that are captured by an +asynchronously-executed block. The block arguments in ``dispatch_async()`` and +``dispatch_after()`` are guaranteed to escape, so it is an error if a pointer with the +``noescape`` attribute is captured by one of these blocks. + +The following is an example of an invalid use of the ``noescape`` attribute. + + .. code-block:: objc + void foo(__attribute__((noescape)) int *p) { + dispatch_async(queue, ^{ + *p = 123; + }); + }); diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -70,6 +70,7 @@ `bugprone-misplaced-widening-cast `_, `bugprone-move-forwarding-reference `_, "Yes" `bugprone-multiple-statement-macro `_, + `bugprone-no-escape `_, "Yes" `bugprone-not-null-terminated-result `_, "Yes" `bugprone-parent-virtual-call `_, "Yes" `bugprone-posix-return `_, "Yes" diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-no-escape.m b/clang-tools-extra/test/clang-tidy/checkers/bugprone-no-escape.m new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-no-escape.m @@ -0,0 +1,28 @@ +// RUN: %check_clang_tidy %s bugprone-no-escape %t +// RUN: %check_clang_tidy %s -assume-filename=bugprone-no-escape.c bugprone-no-escape %t -- -- -fblocks + +typedef struct dispatch_queue_s *dispatch_queue_t; +typedef struct dispatch_time_s *dispatch_time_t; +typedef void (^dispatch_block_t)(void); +void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); +void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); + +extern dispatch_queue_t queue; + +void test_noescape_attribute(__attribute__((noescape)) int *p, int *q) { + dispatch_async(queue, ^{ + *p = 123; + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: pointer 'p' with attribute 'noescape' is captured by an asynchronously-executed block [bugprone-no-escape] + // CHECK-MESSAGES: :[[@LINE-4]]:30: note: the 'noescape' attribute is declared here. + }); + + dispatch_after(456, queue, ^{ + *p = 789; + // CHECK-MESSAGES: :[[@LINE-2]]:30: warning: pointer 'p' with attribute 'noescape' is captured by an asynchronously-executed block [bugprone-no-escape] + }); + + dispatch_async(queue, ^{ + *q = 0; + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:25: warning: pointer 'q' with attribute 'noescape' is captured by an asynchronously-executed block + }); +}