[clang] Add clang/docs/FunctionEffectAnalysis.rst. (PR #109855)

Doug Wyatt via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 11 08:51:18 PDT 2024


https://github.com/dougsonos updated https://github.com/llvm/llvm-project/pull/109855

>From 085965b324efde41168c5d51db3a368578d3458f Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Mon, 23 Sep 2024 14:44:32 -0700
Subject: [PATCH 1/5] Add clang/docs/FunctionEffectAnalysis.rst.

---
 clang/docs/FunctionEffectAnalysis.rst | 503 ++++++++++++++++++++++++++
 clang/docs/index.rst                  |   1 +
 2 files changed, 504 insertions(+)
 create mode 100644 clang/docs/FunctionEffectAnalysis.rst

diff --git a/clang/docs/FunctionEffectAnalysis.rst b/clang/docs/FunctionEffectAnalysis.rst
new file mode 100644
index 00000000000000..8c1cf8a483c5f1
--- /dev/null
+++ b/clang/docs/FunctionEffectAnalysis.rst
@@ -0,0 +1,503 @@
+========================
+Function Effect Analysis
+========================
+
+Introduction
+============
+
+Clang Function Effect Analysis is a C++ language extension which can warn about "unsafe"
+constructs. The feature is currently tailored for the Performance Constraint attributes,
+``nonblocking`` and ``nonallocating``; functions with these attributes are verified as not
+containing any language constructs or calls to other functions which violate the constraint.
+(See :doc:`AttributeReference`.)
+
+
+The ``nonblocking`` and ``nonallocating`` attributes
+====================================================
+
+Attribute syntax
+----------------
+
+The ``nonblocking`` and ``nonallocating`` attributes apply to function types, allowing them to be
+attached to functions, blocks, function pointers, lambdas, and member functions.
+
+.. code-block:: c++
+
+  // Functions
+  void nonblockingFunction() [[clang::nonblocking]];
+  void nonallocatingFunction() [[clang::nonallocating]];
+
+  // Function pointers
+  void (*nonblockingFunctionPtr)() [[clang::nonblocking]];
+
+  // Typedefs, type aliases.
+  typedef void (*NBFunctionPtrTypedef)() [[clang::nonblocking]];
+  using NBFunctionPtrTypeAlias_gnu = __attribute__((nonblocking)) void (*)();
+  using NBFunctionPtrTypeAlias_std = void (*)() [[clang::nonblocking]];
+
+  // C++ methods
+  struct Struct {
+    void NBMethod() [[clang::nonblocking]];
+  };
+
+  // C++ lambdas
+  auto nbLambda = []() [[clang::nonblocking]] {};
+
+  // Blocks
+  void (^nbBlock)() = ^() [[clang::nonblocking]] {};
+
+The attribute applies only to the function itself. In particular, it does not apply to any nested
+functions or declarations, such as blocks, lambdas, and local classes.
+
+This document uses the C++/C23 syntax ``[[clang::nonblocking]]``, since it parallels the placement 
+of the ``noexcept`` specifier, and the attributes have other similarities to ``noexcept``. The GNU
+``__attribute__((nonblocking))`` syntax is also supported. Note that it requires a different 
+placement on a C++ type alias.
+
+Like ``noexcept``, ``nonblocking`` and ``nonallocating`` have an optional argument, a compile-time
+constant boolean expression. By default, the argument is true, so ``[[clang::nonblocking(true)]]``
+is equivalent to ``[[clang::nonblocking]]``, and declares the function type as never locking.
+
+
+Attribute semantics
+-------------------
+
+Together with ``noexcept``, the ``nonallocating`` and ``nonblocking`` attributes define an ordered
+series of performance constraints. From weakest to strongest:
+
+- ``noexcept`` (as per the C++ standard): The function type will never throw an exception.
+- ``nonallocating``: The function type will never allocate memory on the heap, and never throw an
+  exception.
+- ``nonblocking``: The function type will never block on a lock, never allocate memory on the heap,
+  and never throw an exception.
+
+``nonblocking`` includes the ``nonallocating`` guarantee. 
+
+``nonblocking`` and ``nonallocating`` include the ``noexcept`` guarantee, but the presence of either
+attribute does not implicitly specify ``noexcept``. (It would be inappropriate for a Clang 
+attribute, ignored by non-Clang compilers, to imply a standard language feature.)
+
+``nonblocking(true)`` and ``nonallocating(true)`` apply to function *types*, and by extension, to
+function-like declarations. When applied to a declaration with a body, the compiler verifies the
+function, as described in the section "Analysis and warnings", below. Functions without an explicit
+performance constraint are not verified.
+
+``nonblocking(false)`` and ``nonallocating(false)`` are synonyms for the attributes ``blocking`` and
+``allocating``. They can be used on a function-like declaration to explicitly disable any potential
+inference of ``nonblocking`` or ``nonallocating`` during verification. (Inference is described later
+in this document). ``nonblocking(false)`` and ``nonallocating(false)`` are legal, but superfluous 
+when applied to a function *type*. ``float (int) [[nonblocking(false)]]`` and ``float (int)`` are
+identical types.
+
+For all functions with no explicit performance constraint, the worst is assumed, that the function
+allocates memory and potentially blocks, unless it can be inferred otherwise, as described in the
+discussion of verification.
+
+The following list describes the meanings of all permutations of the two attributes and arguments:
+
+- ``nonblocking(true)`` + ``nonallocating(true)``: valid; ``nonallocating(true)`` is superfluous but
+  does not contradict the guarantee.
+- ``nonblocking(true)`` + ``nonallocating(false)``: error, contradictory.
+- ``nonblocking(false)`` + ``nonallocating(true)``: valid; the function does not allocate memory,
+  but may lock for other reasons.
+- ``nonblocking(false)`` + ``nonallocating(false)``: valid.
+
+Type conversions
+----------------
+
+A performance constraint can be removed or weakened via an implicit conversion. An attempt to add
+or strengthen a performance constraint is unsafe and results in a warning.
+
+.. code-block:: c++
+
+  void unannotated();
+  void nonblocking() [[clang::nonblocking]];
+  void nonallocating() [[clang::nonallocating]];
+
+  void example()
+  {
+    // It's fine to remove a performance constraint.
+    void (*fp_plain)();
+    fp_plain = unannotated;
+    fp_plain = nonblocking;
+    fp_plain = nonallocating;
+
+    // Adding/spoofing nonblocking is unsafe.
+    void (*fp_nonblocking)() [[clang::nonblocking]];
+    fp_nonblocking = nullptr;
+    fp_nonblocking = nonblocking;
+    fp_nonblocking = unannotated;
+    // ^ warning: attribute 'nonblocking' should not be added via type conversion
+    fp_nonblocking = nonallocating;
+    // ^ warning: attribute 'nonblocking' should not be added via type conversion
+
+    // Adding/spoofing nonallocating is unsafe.
+    void (*fp_nonallocating)() [[clang::nonallocating]];
+    fp_nonallocating = nullptr;
+    fp_nonallocating = nonallocating;
+    fp_nonallocating = nonblocking; // no warning because nonblocking includes nonallocating 
+    fp_nonallocating = unannotated;
+    // ^ warning: attribute 'nonallocating' should not be added via type conversion
+  }
+
+Virtual methods
+---------------
+
+In C++, when a base class's virtual method has a performance constraint, overriding methods in
+subclasses inherit the attribute.
+
+.. code-block:: c++
+
+  struct Base {
+    virtual void unsafe();
+    virtual void safe() noexcept [[clang::nonblocking]];
+  };
+
+  struct Derived : public Base {
+    void unsafe() [[clang::nonblocking]] override;
+    // It's okay for an overridden method to be more constrained
+
+    void safe() noexcept override;
+    // This method is implicitly declared `nonblocking`, inherited from Base.
+  };
+
+Redeclarations, overloads, and name mangling
+--------------------------------------------
+
+The ``nonblocking`` and ``nonallocating`` attributes, like ``noexcept``, do not factor into
+argument-dependent lookup and overloaded functions/methods.
+
+First, consider that ``noexcept`` is integral to a function's type:
+
+.. code-block:: c++
+
+  void f1(int);
+  void f1(int) noexcept;
+  // error: exception specification in declaration does not match previous
+  //   declaration
+
+Unlike ``noexcept``, a redeclaration of `f2` with an added or stronger performance constraint is
+legal, and propagates the attribute to the previous declaration:
+
+.. code-block:: c++
+
+  int f2();
+  int f2() [[clang::nonblocking]]; // redeclaration with stronger constraint is OK.
+
+This greatly eases adoption, by making it possible to annotate functions in external libraries
+without modifying library headers.
+
+A redeclaration with a removed or weaker performance constraint produces a warning, in order to
+parallel the behavior of ``noexcept``:
+
+.. code-block:: c++
+
+  int f2() { return 42; }
+  // warning: attribute 'nonblocking' on function does not match previous declaration
+
+In C++14, the following two declarations of `f3` are identical (a single function). In C++17 they
+are separate overloads:
+
+.. code-block:: c++
+
+  void f3(void (*)());
+  void f3(void (*)() noexcept);
+
+Similarly, the following two declarations of `f4` are separate overloads. This pattern may pose
+difficulties due to ambiguity:
+
+.. code-block:: c++
+
+  void f4(void (*)());
+  void f4(void (*)() [[clang::nonblocking]]);
+
+The attributes have no effect on the mangling of function and method names.
+
+``noexcept``
+------------
+
+``nonblocking`` and ``nonallocating`` are conceptually similar to a stronger form of C++'s
+``noexcept``, but with further diagnostics, as described later in this document. Therefore, in C++,
+a ``nonblocking`` or ``nonallocating`` function, method, block or lambda should also be declared
+``noexcept``.[^6] If ``noexcept`` is missing, a warning is issued. In Clang, this diagnostic is
+controlled by ``-Wperf-constraint-implies-noexcept``.
+
+Objective-C
+-----------
+
+The attributes are currently unsupported on Objective-C methods.
+
+Analysis and warnings
+=====================
+
+Constraints
+-----------
+
+Functions declared ``nonallocating`` or ``nonblocking``, when defined, are verified according to the
+following rules. Such functions:
+
+1. May not allocate or deallocate memory on the heap. The analysis follows the calls to
+   ``operator new`` and ``operator delete`` generated by the ``new`` and ``delete`` keywords, and
+   treats them like any other function call. The global ``operator new`` and ``operator delete``
+   aren't declared ``nonblocking`` or ``nonallocating`` and so they are considered unsafe. (This
+   is correct because most memory allocators are not lock-free. Note that the placement form of
+   ``operator new`` is implemented inline in libc++'s ``<new>`` header, and is verifiably
+   ``nonblocking``, since it merely casts the supplied pointer to the result type.)
+
+2. May not throw or catch exceptions. To throw, the compiler must allocate the exception on the
+   heap. (Also, many subclasses of ``std::exception`` allocate a ``std::string``). Exceptions are
+   deallocated when caught.
+
+3. May not make any indirect function call, via a virtual method, function pointer, or
+   pointer-to-member function, unless the target is explicitly declared with the same
+   ``nonblocking`` or ``nonallocating`` attribute (or stronger).
+
+4. May not make direct calls to any other function, with the following exceptions:
+
+  a. The callee is also explicitly declared with the same ``nonblocking`` or ``nonallocating``
+     attribute (or stronger).
+  b. The callee is defined in the same translation unit as the caller, does not have the ``false``
+     form of the required attribute, and can be verified to be have the same attribute or stronger,
+     according to these same rules.
+  c. The callee is a built-in function (other than builtins which are known to block or allocate).
+  d. The callee is declared ``noreturn`` and, if compiling C++, the callee is also declared
+     ``noexcept``. This exception excludes functions such as ``abort()`` and ``std::terminate()``
+     from the analysis.
+
+5. May not invoke or access an Objective-C method or property, since ``objc_msgSend()`` calls into 
+   the Objective-C runtime, which may allocate memory or otherwise block.
+
+Functions declared ``nonblocking`` have an additional constraint:
+
+6. May not declare static local variables (e.g. Meyers singletons). The compiler generates a lock
+   protecting the initialization of the variable.
+
+Violations of any of these rules result in warnings:
+
+.. code-block:: c++
+
+  void notInline();
+
+  void example() [[clang::nonblocking]]
+  {
+    auto* x = new int;
+    // warning: function with 'nonblocking' attribute must not allocate or deallocate
+    //   memory
+
+    if (x == nullptr) {
+      static Logger* logger = createLogger();
+      // warning: function with 'nonblocking' attribute must not have static locals
+
+      throw std::runtime_warning{ "null" };
+      // warning: 'nonblocking" function 'example' must not throw exceptions
+    }
+    notInline();
+    // warning: 'function with 'nonblocking' attribute must not call non-'nonblocking' function
+    //   'notInline'
+    // note (on notInline()): declaration cannot be inferred 'nonblocking' because it has no
+    //   definition in this translation unit
+  }
+
+Inferring ``nonblocking`` or ``nonallocating``
+----------------------------------------------
+
+In the absence of a ``nonblocking`` or ``nonallocating`` attribute (whether ``true`` or ``false``),
+a function, when found to be called from a performance-constrained function, can be analyzed to
+infer whether it has a desired attribute. This analysis happens when the function is not a virtual
+method, and it has a visible definition within the current translation unit (i.e. its body can be
+traversed).
+
+.. code-block:: c++
+
+  void notInline();
+  int implicitlySafe() { return 42; }
+  void implicitlyUnsafe() { notInline(); }
+
+  void example() [[clang::nonblocking]]
+  {
+    int x = implicitlySafe(); // OK
+    implicitlyUnsafe();
+    // warning: function with 'nonblocking' attribute must not call non-'nonblocking' function
+    //   'implicitlyUnsafe'
+    // note (on implicitlyUnsafe): function cannot be inferred 'nonblocking' because it calls
+    //   non-'nonblocking' function 'notInline'
+    // note (on notInline()): declaration cannot be inferred 'nonblocking' because it has no
+    //   definition in this translation unit
+  }
+
+Lambdas and blocks
+------------------
+
+As mentioned earlier, the performance constraint attributes apply only to a single function and not
+to any code nested inside it, including blocks, lambdas, and local classes. It is possible for a
+lock-free function to schedule the execution of a blocking lambda on another thread. Similarly, a
+blocking function may create a ``nonblocking`` lambda for use in a realtime context.
+
+Operations which create, destroy, copy, and move lambdas and blocks are analyzed in terms of the
+underlying function calls. For example, the creation of a lambda with captures generates a function
+call to an anonymous struct's constructor, passing the captures as parameters.
+
+Implicit function calls in the AST
+----------------------------------
+
+The ``nonblocking`` / ``nonallocating`` analysis occurs at the Sema phase of analysis in Clang.
+During Sema, there are some constructs which will eventually become function calls, but do not
+appear as function calls in the AST. For example, ``auto* foo = new Foo;`` becomes a declaration
+containing a ``CXXNewExpr`` which is understood as a function call to the global ``operator new``
+(in this example), and a ``CXXConstructExpr``, which, for analysis purposes, is a function call to
+``Foo``'s constructor. Most gaps in the analysis would be due to incomplete knowledge of AST
+constructs which become function calls.
+
+Disabling diagnostics
+---------------------
+
+Function effect diagnostics are controlled by ``-Wfunction-effects``.
+
+A construct like this can be used to exempt code from the checks described here:
+
+.. code-block:: c++
+
+  #define NONBLOCKING_UNSAFE(...)                                         \
+    _Pragma("clang diagnostic push")                                 \
+    _Pragma("clang diagnostic ignored \"-Wunknown-warning-option\"") \
+    _Pragma("clang diagnostic ignored \"-Wfunction-effects\"")       \
+    __VA_ARGS__                                                      \
+    _Pragma("clang diagnostic pop")
+
+Disabling the diagnostic allows for:
+
+- constructs which do block, but which in practice are used in ways to avoid unbounded blocking,
+  e.g. a thread pool with semaphores to coordinate multiple realtime threads.
+- using libraries which are safe but not yet annotated.
+- incremental adoption in a large codebase.
+
+Adoption
+========
+
+There are a few common issues that arise when adopting the ``nonblocking`` and ``nonallocating``
+attributes.
+
+C++ exceptions
+--------------
+
+Exceptions pose a challenge to the adoption of the performance constraints. Common library functions
+which throw exceptions include:
+
++----------------------------------+-----------------------------------------------------------------------+
+| Method                           | Alternative                                                           |
++==================================+=======================================================================+
+| ``std::vector<T>::at()``         | ``operator[](size_t)``, after verifying that the index is in range.   |
++----------------------------------+-----------------------------------------------------------------------+
+| ``std::optional<T>::value()``    | ``operator*``, after checking ``has_value()`` or ``operator bool()``. |
++----------------------------------+-----------------------------------------------------------------------+
+| ``std::expected<T, E>::value()`` | Same as for ``std::optional<T>::value()``.                            |
++----------------------------------+-----------------------------------------------------------------------+
+
+Interactions with type-erasure techniques
+-----------------------------------------
+
+``std::function<R(Args...)>`` illustrates a common C++ type-erasure technique. Using template
+argument deduction, it decomposes a function type into its return and parameter types. Additional
+components of the function type, including ``noexcept``, ``nonblocking``, ``nonallocating``, and any
+other attributes, are discarded.
+
+Standard library support for these components of a function type is not immediately forthcoming.
+
+Code can work around this limitation in either of two ways:
+
+1. Avoid abstractions like ``std::function`` and instead work directly with the original lambda type.
+
+2. Create a specialized alternative, e.g. ``nonblocking_function<R(Args...)>`` where all function
+   pointers used in the implementation and its interface are ``nonblocking``.
+
+As an example of the first approach, when using a lambda as a *Callable* template parameter, the
+attribute is preserved:
+
+.. code-block:: c++
+
+  std::sort(vec.begin(), vec.end(),
+    [](const Elem& a, const Elem& b) [[clang::nonblocking]] { return a.mem < b.mem; });
+
+Here, the type of the ``Compare`` template parameter is an anonymous class generated from the
+lambda, with an ``operator()`` method holding the ``nonblocking`` attribute.
+
+A complication arises when a *Callable* template parameter, instead of being a lambda or class
+implementing ``operator()``, is a function pointer:
+
+.. code-block:: c++
+
+  static bool compare_elems(const Elem& a, const Elem& b) [[clang::nonblocking]] {
+    return a.mem < b.mem; };
+
+  std::sort(vec.begin(), vec.end(), compare_elems);
+
+Here, the type of ``compare_elems`` is decomposed to ``bool(const Elem&, const Elem&)``, without
+``nonblocking``, when forming the template parameter. This can be solved using the second approach,
+creating a specialized alternative which explicitly requires the attribute. In this case, it's
+possible to use a small wrapper to transform the function pointer into a functor:
+
+.. code-block:: c++
+
+  template <typename>
+  class nonblocking_fp;
+
+  template <typename R, typename... Args>
+  class nonblocking_fp<R(Args...)> {
+  public:
+    using impl_t = R (*)(Args...) [[clang::nonblocking]];
+
+  private:
+    impl_t mImpl{ nullptr_t };
+  public:
+    nonblocking_fp() = default;
+    nonblocking_fp(impl_t f) : mImpl{ f } {}
+
+    R operator()(Args... args) const
+    {
+      return mImpl(std::forward<Args>(args)...);
+    }
+  };
+
+  // deduction guide (like std::function's)
+  template< class R, class... ArgTypes >
+  nonblocking_fp( R(*)(ArgTypes...) ) -> nonblocking_fp<R(ArgTypes...)>;
+
+  // --
+
+  // Wrap the function pointer in a functor which preserves ``nonblocking``.
+  std::sort(vec.begin(), vec.end(), nonblocking_fp{ compare_elems });
+
+Now, the ``nonblocking`` attribute of ``compare_elems`` is verified when it is converted to a
+``nonblocking`` function pointer, as the argument to ``nonblocking_fp``'s constructor. The template
+parameter is the functor class ``nonblocking_fp``.
+
+
+Static local variables
+----------------------
+
+Static local variables are often used for lazily-constructed globals (Meyers singletons). Beyond the
+compiler's use of a lock to ensure thread-safe initialization, it is dangerously easy to
+inadvertently trigger initialization, involving heap allocation, from a ``nonblocking`` or
+``nonallocating`` context.
+
+Generally, such singletons need to be replaced by globals, and care must be taken to ensure their
+initialization before they are used from ``nonblocking`` or ``nonallocating`` contexts.
+
+
+Annotating libraries
+--------------------
+
+It can be surprising that the analysis does not depend on knowledge of any primitives; it simply
+assumes the worst, that all function calls are unsafe unless explicitly marked as safe or able to be
+inferred as safe. With ``nonblocking``, this appears to suffice for all but the most primitive of
+spinlocks.
+
+At least for an operating system's C functions, it is possible to define an override header which
+redeclares safe common functions (e.g. ``pthread_self()``) with the addition of ``nonblocking``.
+This may help in adopting the feature incrementally.
+
+It also helps that for many of the functions in ``<math.h>``, Clang generates calls to built-in
+functions, which the diagnosis understands to be safe.
+
+Much of the C++ standard library consists of inline templated functions which work well with
+inference. A small number of primitives may need explicit ``nonblocking/nonallocating`` attributes.
diff --git a/clang/docs/index.rst b/clang/docs/index.rst
index 4a497f4d9bcc3c..1f4ff28d199b2d 100644
--- a/clang/docs/index.rst
+++ b/clang/docs/index.rst
@@ -26,6 +26,7 @@ Using Clang as a Compiler
    ClangStaticAnalyzer
    ThreadSafetyAnalysis
    DataFlowAnalysisIntro
+   FunctionEffectAnalysis
    AddressSanitizer
    ThreadSanitizer
    MemorySanitizer

>From 0ab0c55d7761a52ad1b4f8ea8b56ffb29c5da36e Mon Sep 17 00:00:00 2001
From: Doug Wyatt <doug at sonosphere.com>
Date: Thu, 26 Sep 2024 08:46:41 -0700
Subject: [PATCH 2/5] Apply suggestions from code review

Co-authored-by: Sirraide <aeternalmail at gmail.com>
---
 clang/docs/FunctionEffectAnalysis.rst | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/clang/docs/FunctionEffectAnalysis.rst b/clang/docs/FunctionEffectAnalysis.rst
index 8c1cf8a483c5f1..ecfc1af4786c30 100644
--- a/clang/docs/FunctionEffectAnalysis.rst
+++ b/clang/docs/FunctionEffectAnalysis.rst
@@ -55,8 +55,8 @@ of the ``noexcept`` specifier, and the attributes have other similarities to ``n
 placement on a C++ type alias.
 
 Like ``noexcept``, ``nonblocking`` and ``nonallocating`` have an optional argument, a compile-time
-constant boolean expression. By default, the argument is true, so ``[[clang::nonblocking(true)]]``
-is equivalent to ``[[clang::nonblocking]]``, and declares the function type as never locking.
+constant boolean expression. By default, the argument is ``true``, so ``[[clang::nonblocking]]``
+is equivalent to ``[[clang::nonblocking(true)]]``, and declares the function type as never locking.
 
 
 Attribute semantics
@@ -82,8 +82,8 @@ function-like declarations. When applied to a declaration with a body, the compi
 function, as described in the section "Analysis and warnings", below. Functions without an explicit
 performance constraint are not verified.
 
-``nonblocking(false)`` and ``nonallocating(false)`` are synonyms for the attributes ``blocking`` and
-``allocating``. They can be used on a function-like declaration to explicitly disable any potential
+``blocking`` and ``allocating`` are synonyms for ``nonblocking(false)`` and
+``nonallocating(false)``, respectively. They can be used on a function-like declaration to explicitly disable any potential
 inference of ``nonblocking`` or ``nonallocating`` during verification. (Inference is described later
 in this document). ``nonblocking(false)`` and ``nonallocating(false)`` are legal, but superfluous 
 when applied to a function *type*. ``float (int) [[nonblocking(false)]]`` and ``float (int)`` are
@@ -144,7 +144,7 @@ Virtual methods
 ---------------
 
 In C++, when a base class's virtual method has a performance constraint, overriding methods in
-subclasses inherit the attribute.
+subclasses inherit the constraint.
 
 .. code-block:: c++
 
@@ -259,7 +259,7 @@ following rules. Such functions:
   b. The callee is defined in the same translation unit as the caller, does not have the ``false``
      form of the required attribute, and can be verified to be have the same attribute or stronger,
      according to these same rules.
-  c. The callee is a built-in function (other than builtins which are known to block or allocate).
+  c. The callee is a built-in function that is known not to block or allocated.
   d. The callee is declared ``noreturn`` and, if compiling C++, the callee is also declared
      ``noexcept``. This exception excludes functions such as ``abort()`` and ``std::terminate()``
      from the analysis.

>From 76396881af7c3a8de10aea22bf0cf26c6577740b Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 26 Sep 2024 09:59:24 -0700
Subject: [PATCH 3/5] Address helpful review feedback from @Sirraide and
 @cjappl.

---
 clang/docs/FunctionEffectAnalysis.rst | 101 ++++++++++++++++++--------
 1 file changed, 69 insertions(+), 32 deletions(-)

diff --git a/clang/docs/FunctionEffectAnalysis.rst b/clang/docs/FunctionEffectAnalysis.rst
index ecfc1af4786c30..abe071afb84444 100644
--- a/clang/docs/FunctionEffectAnalysis.rst
+++ b/clang/docs/FunctionEffectAnalysis.rst
@@ -2,10 +2,15 @@
 Function Effect Analysis
 ========================
 
+.. contents::
+  :depth: 3
+  :local:
+
+
 Introduction
 ============
 
-Clang Function Effect Analysis is a C++ language extension which can warn about "unsafe"
+Clang Function Effect Analysis is a language extension which can warn about "unsafe"
 constructs. The feature is currently tailored for the Performance Constraint attributes,
 ``nonblocking`` and ``nonallocating``; functions with these attributes are verified as not
 containing any language constructs or calls to other functions which violate the constraint.
@@ -73,9 +78,11 @@ series of performance constraints. From weakest to strongest:
 
 ``nonblocking`` includes the ``nonallocating`` guarantee. 
 
-``nonblocking`` and ``nonallocating`` include the ``noexcept`` guarantee, but the presence of either
-attribute does not implicitly specify ``noexcept``. (It would be inappropriate for a Clang 
-attribute, ignored by non-Clang compilers, to imply a standard language feature.)
+``nonblocking`` and ``nonallocating`` include the ``noexcept`` guarantee, but neither
+attribute implicitly specifies ``noexcept``. (It would be inappropriate for a Clang 
+attribute, ignored by non-Clang compilers, to imply a standard language feature.) Nonetheless,
+Clang emits a warning if, in C++, a function is declared ``nonblocking`` or ``nonallocating``
+without ``noexcept``. This diagnostic is controlled by ``-Wperf-constraint-implies-noexcept``.
 
 ``nonblocking(true)`` and ``nonallocating(true)`` apply to function *types*, and by extension, to
 function-like declarations. When applied to a declaration with a body, the compiler verifies the
@@ -83,24 +90,32 @@ function, as described in the section "Analysis and warnings", below. Functions
 performance constraint are not verified.
 
 ``blocking`` and ``allocating`` are synonyms for ``nonblocking(false)`` and
-``nonallocating(false)``, respectively. They can be used on a function-like declaration to explicitly disable any potential
-inference of ``nonblocking`` or ``nonallocating`` during verification. (Inference is described later
-in this document). ``nonblocking(false)`` and ``nonallocating(false)`` are legal, but superfluous 
-when applied to a function *type*. ``float (int) [[nonblocking(false)]]`` and ``float (int)`` are
-identical types.
-
-For all functions with no explicit performance constraint, the worst is assumed, that the function
-allocates memory and potentially blocks, unless it can be inferred otherwise, as described in the
+``nonallocating(false)``, respectively. They can be used on a function-like declaration to
+explicitly disable any potential inference of ``nonblocking`` or ``nonallocating`` during
+verification. (Inference is described later in this document). ``nonblocking(false)`` and
+``nonallocating(false)`` are legal, but superfluous  when applied to a function *type*.
+``float (int) [[nonblocking(false)]]`` and ``float (int)`` are identical types.
+
+For functions with no explicit performance constraint, the worst is assumed: the function
+allocates memory and potentially blocks, unless it can be inferred otherwise. This is detailed in the
 discussion of verification.
 
-The following list describes the meanings of all permutations of the two attributes and arguments:
+The following example describes the meanings of all permutations of the two attributes and arguments:
+
+.. code-block:: c++
+
+  void nb1_na1() [[clang::nonblocking(true)]] [[clang::nonallocating(true)]];
+  // Valid; nonallocating(true) is superfluous but doesn't contradict the guarantee.
+
+  void nb1_na0() [[clang::nonblocking(true)]] [[clang::nonallocating(false)]];
+  // error: 'allocating' and 'nonblocking' attributes are not compatible
+
+  void nb0_na1() [[clang::nonblocking(false)]] [[clang::nonallocating(true)]];
+  // Valid; the function does not allocate memory, but may lock for other reasons.
+
+  void nb0_na0() [[clang::nonblocking(false)]] [[clang::nonallocating(false)]];
+  // Valid.
 
-- ``nonblocking(true)`` + ``nonallocating(true)``: valid; ``nonallocating(true)`` is superfluous but
-  does not contradict the guarantee.
-- ``nonblocking(true)`` + ``nonallocating(false)``: error, contradictory.
-- ``nonblocking(false)`` + ``nonallocating(true)``: valid; the function does not allocate memory,
-  but may lock for other reasons.
-- ``nonblocking(false)`` + ``nonallocating(false)``: valid.
 
 Type conversions
 ----------------
@@ -219,8 +234,7 @@ The attributes have no effect on the mangling of function and method names.
 ``nonblocking`` and ``nonallocating`` are conceptually similar to a stronger form of C++'s
 ``noexcept``, but with further diagnostics, as described later in this document. Therefore, in C++,
 a ``nonblocking`` or ``nonallocating`` function, method, block or lambda should also be declared
-``noexcept``.[^6] If ``noexcept`` is missing, a warning is issued. In Clang, this diagnostic is
-controlled by ``-Wperf-constraint-implies-noexcept``.
+``noexcept``.
 
 Objective-C
 -----------
@@ -245,7 +259,7 @@ following rules. Such functions:
    ``nonblocking``, since it merely casts the supplied pointer to the result type.)
 
 2. May not throw or catch exceptions. To throw, the compiler must allocate the exception on the
-   heap. (Also, many subclasses of ``std::exception`` allocate a ``std::string``). Exceptions are
+   heap. (Also, many subclasses of ``std::exception`` allocate a string). Exceptions are
    deallocated when caught.
 
 3. May not make any indirect function call, via a virtual method, function pointer, or
@@ -259,20 +273,24 @@ following rules. Such functions:
   b. The callee is defined in the same translation unit as the caller, does not have the ``false``
      form of the required attribute, and can be verified to be have the same attribute or stronger,
      according to these same rules.
-  c. The callee is a built-in function that is known not to block or allocated.
+  c. The callee is a built-in function that is known not to block or allocate.
   d. The callee is declared ``noreturn`` and, if compiling C++, the callee is also declared
-     ``noexcept``. This exception excludes functions such as ``abort()`` and ``std::terminate()``
-     from the analysis.
+     ``noexcept``. This special case excludes functions such as ``abort()`` and ``std::terminate()``
+     from the analysis. (The reason for requiring ``noexcept`` in C++ is that a function declared
+     ``noreturn`` could be a wrapper for ``throw``.)
 
 5. May not invoke or access an Objective-C method or property, since ``objc_msgSend()`` calls into 
    the Objective-C runtime, which may allocate memory or otherwise block.
 
+6. May not access thread-local variables. Typically, thread-local variables are allocated on the
+   heap when first accessed.
+
 Functions declared ``nonblocking`` have an additional constraint:
 
-6. May not declare static local variables (e.g. Meyers singletons). The compiler generates a lock
+7. May not declare static local variables (e.g. Meyers singletons). The compiler generates a lock
    protecting the initialization of the variable.
 
-Violations of any of these rules result in warnings:
+Violations of any of these rules result in warnings, in the ``-Wfunction-effects`` category:
 
 .. code-block:: c++
 
@@ -286,7 +304,7 @@ Violations of any of these rules result in warnings:
 
     if (x == nullptr) {
       static Logger* logger = createLogger();
-      // warning: function with 'nonblocking' attribute must not have static locals
+      // warning: function with 'nonblocking' attribute must not have static local variables
 
       throw std::runtime_warning{ "null" };
       // warning: 'nonblocking" function 'example' must not throw exceptions
@@ -330,7 +348,7 @@ Lambdas and blocks
 
 As mentioned earlier, the performance constraint attributes apply only to a single function and not
 to any code nested inside it, including blocks, lambdas, and local classes. It is possible for a
-lock-free function to schedule the execution of a blocking lambda on another thread. Similarly, a
+nonblocking function to schedule the execution of a blocking lambda on another thread. Similarly, a
 blocking function may create a ``nonblocking`` lambda for use in a realtime context.
 
 Operations which create, destroy, copy, and move lambdas and blocks are analyzed in terms of the
@@ -393,6 +411,25 @@ which throw exceptions include:
 | ``std::expected<T, E>::value()`` | Same as for ``std::optional<T>::value()``.                            |
 +----------------------------------+-----------------------------------------------------------------------+
 
+
+``std::function<R(Args...)>``
+-----------------------------
+
+``std::function<R(Args...)>`` is generally incompatible with ``nonblocking`` and ``nonallocating``
+code, because an implementation typically allocates heap memory in the constructor.
+
+Alternatives:
+
+- ``std::function_ref`` (available in C++26 or as ``llvm::function_ref``). This is appropriate and
+  optimal when a functor's lifetime does not need to extend past the function that created it.
+
+- ``inplace_function`` from WG14. This solves the allocation problem by giving the functor wrapper
+  a fixed size known at compile time and using an inline buffer.
+
+While these alternatives both address the heap allocation of ``std::function``, they are still
+obstacles to ``nonblocking/nonallocating`` verification, for reasons detailed in the next section.
+
+
 Interactions with type-erasure techniques
 -----------------------------------------
 
@@ -407,7 +444,7 @@ Code can work around this limitation in either of two ways:
 
 1. Avoid abstractions like ``std::function`` and instead work directly with the original lambda type.
 
-2. Create a specialized alternative, e.g. ``nonblocking_function<R(Args...)>`` where all function
+2. Create a specialized alternative, e.g. ``nonblocking_function_ref<R(Args...)>`` where all function
    pointers used in the implementation and its interface are ``nonblocking``.
 
 As an example of the first approach, when using a lambda as a *Callable* template parameter, the
@@ -496,8 +533,8 @@ At least for an operating system's C functions, it is possible to define an over
 redeclares safe common functions (e.g. ``pthread_self()``) with the addition of ``nonblocking``.
 This may help in adopting the feature incrementally.
 
-It also helps that for many of the functions in ``<math.h>``, Clang generates calls to built-in
-functions, which the diagnosis understands to be safe.
+It also helps that for many of the functions in the standard C libraries (notably ``<math.h>``),
+Clang generates calls to built-in functions, which the diagnosis understands to be safe.
 
 Much of the C++ standard library consists of inline templated functions which work well with
 inference. A small number of primitives may need explicit ``nonblocking/nonallocating`` attributes.

>From 61464c00d27e0a4acb3c938f39cc23e3a0d40872 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 26 Sep 2024 10:08:44 -0700
Subject: [PATCH 4/5] Tiny whitespace tweak.

---
 clang/docs/FunctionEffectAnalysis.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/docs/FunctionEffectAnalysis.rst b/clang/docs/FunctionEffectAnalysis.rst
index abe071afb84444..66feb29276de2f 100644
--- a/clang/docs/FunctionEffectAnalysis.rst
+++ b/clang/docs/FunctionEffectAnalysis.rst
@@ -375,7 +375,7 @@ A construct like this can be used to exempt code from the checks described here:
 
 .. code-block:: c++
 
-  #define NONBLOCKING_UNSAFE(...)                                         \
+  #define NONBLOCKING_UNSAFE(...)                                    \
     _Pragma("clang diagnostic push")                                 \
     _Pragma("clang diagnostic ignored \"-Wunknown-warning-option\"") \
     _Pragma("clang diagnostic ignored \"-Wfunction-effects\"")       \

>From 2c3e35ef8f8d9b938a15cc389daa55e8741793d6 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 11 Oct 2024 08:50:56 -0700
Subject: [PATCH 5/5] Clarify the relationship with noexcept, add a reference
 to the RealtimeSanitizer docs.

---
 clang/docs/FunctionEffectAnalysis.rst | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/clang/docs/FunctionEffectAnalysis.rst b/clang/docs/FunctionEffectAnalysis.rst
index 66feb29276de2f..b1be70919f92ab 100644
--- a/clang/docs/FunctionEffectAnalysis.rst
+++ b/clang/docs/FunctionEffectAnalysis.rst
@@ -78,11 +78,15 @@ series of performance constraints. From weakest to strongest:
 
 ``nonblocking`` includes the ``nonallocating`` guarantee. 
 
-``nonblocking`` and ``nonallocating`` include the ``noexcept`` guarantee, but neither
-attribute implicitly specifies ``noexcept``. (It would be inappropriate for a Clang 
-attribute, ignored by non-Clang compilers, to imply a standard language feature.) Nonetheless,
-Clang emits a warning if, in C++, a function is declared ``nonblocking`` or ``nonallocating``
-without ``noexcept``. This diagnostic is controlled by ``-Wperf-constraint-implies-noexcept``.
+While ``nonblocking`` and ``nonallocating`` are conceptually a superset of ``noexcept``, neither
+attribute implicitly specifies ``noexcept``. Further, ``noexcept`` has a specified runtime behavior of 
+aborting if an exception is thrown, while the ``nonallocating`` and ``nonblocking`` attributes are
+purely for compile-time analysis and have no potential runtime behavior. Nonetheless, Clang emits a
+warning if, in C++, a function is declared ``nonblocking`` or ``nonallocating`` without
+``noexcept``. This diagnostic is controlled by ``-Wperf-constraint-implies-noexcept``.
+
+Also, the ``nonblocking`` and ``blocking`` attributes do have special runtime behavior in code built
+with Clang's :doc:`RealtimeSanitizer`.
 
 ``nonblocking(true)`` and ``nonallocating(true)`` apply to function *types*, and by extension, to
 function-like declarations. When applied to a declaration with a body, the compiler verifies the



More information about the cfe-commits mailing list