536 lines · plain
1========================2Function Effect Analysis3========================4 5.. contents::6 :depth: 37 :local:8 9 10Introduction11============12 13Clang Function Effect Analysis is a language extension which can warn about "unsafe"14constructs. The feature is currently tailored for the Performance Constraint attributes15``nonblocking`` and ``nonallocating``; functions with these attributes are verified as not16containing any language constructs or calls to other functions which violate the constraint.17(See :doc:`AttributeReference`.)18 19 20The ``nonblocking`` and ``nonallocating`` attributes21====================================================22 23Attribute syntax24----------------25 26The ``nonblocking`` and ``nonallocating`` attributes apply to function types, allowing them to be27attached to functions, blocks, function pointers, lambdas, and member functions.28 29.. code-block:: c++30 31 // Functions32 void nonblockingFunction() [[clang::nonblocking]];33 void nonallocatingFunction() [[clang::nonallocating]];34 35 // Function pointers36 void (*nonblockingFunctionPtr)() [[clang::nonblocking]];37 38 // Typedefs, type aliases.39 typedef void (*NBFunctionPtrTypedef)() [[clang::nonblocking]];40 using NBFunctionPtrTypeAlias_gnu = __attribute__((nonblocking)) void (*)();41 using NBFunctionPtrTypeAlias_std = void (*)() [[clang::nonblocking]];42 43 // C++ methods44 struct Struct {45 void NBMethod() [[clang::nonblocking]];46 };47 48 // C++ lambdas49 auto nbLambda = []() [[clang::nonblocking]] {};50 51 // Blocks52 void (^nbBlock)() = ^() [[clang::nonblocking]] {};53 54The attribute applies only to the function itself. In particular, it does not apply to any nested55functions or declarations, such as blocks, lambdas, and local classes.56 57This document uses the C++/C23 syntax ``[[clang::nonblocking]]``, since it parallels the placement58of the ``noexcept`` specifier, and the attributes have other similarities to ``noexcept``. The GNU59``__attribute__((nonblocking))`` syntax is also supported. Note that it requires a different60placement on a C++ type alias.61 62Like ``noexcept``, ``nonblocking`` and ``nonallocating`` have an optional argument, a compile-time63constant boolean expression. By default, the argument is ``true``, so ``[[clang::nonblocking]]``64is equivalent to ``[[clang::nonblocking(true)]]``, and declares the function type as never blocking.65 66 67Attribute semantics68-------------------69 70Together with ``noexcept``, the ``nonallocating`` and ``nonblocking`` attributes define an ordered71series of performance constraints. From weakest to strongest:72 73- ``noexcept`` (as per the C++ standard): The function type will never throw an exception.74- ``nonallocating``: The function type will never allocate memory on the heap or throw an75 exception.76- ``nonblocking``: The function type will never block on a lock, allocate memory on the heap,77 or throw an exception.78 79``nonblocking`` includes the ``nonallocating`` guarantee.80 81While ``nonblocking`` and ``nonallocating`` are conceptually a superset of ``noexcept``, neither82attribute implicitly specifies ``noexcept``. Further, ``noexcept`` has a specified runtime behavior of83aborting if an exception is thrown, while the ``nonallocating`` and ``nonblocking`` attributes are84mainly for compile-time analysis and have no runtime behavior, except in code built85with Clang's :doc:`RealtimeSanitizer`. Nonetheless, Clang emits a86warning if, in C++, a function is declared ``nonblocking`` or ``nonallocating`` without87``noexcept``. This diagnostic is controlled by ``-Wperf-constraint-implies-noexcept``.88 89``nonblocking(true)`` and ``nonallocating(true)`` apply to function *types*, and by extension, to90function-like declarations. When applied to a declaration with a body, the compiler verifies the91function, as described in the section "Analysis and warnings", below.92 93``blocking`` and ``allocating`` are synonyms for ``nonblocking(false)`` and94``nonallocating(false)``, respectively. They can be used on a function-like declaration to95explicitly disable any potential inference of ``nonblocking`` or ``nonallocating`` during96verification. (Inference is described later in this document). ``nonblocking(false)`` and97``nonallocating(false)`` are legal, but superfluous when applied to a function *type*98that is not part of a declarator: ``float (int) [[nonblocking(false)]]`` and99``float (int)`` are identical types.100 101For functions with no explicit performance constraint, the worst is assumed: the function102allocates memory and potentially blocks, unless it can be inferred otherwise. This is detailed in the103discussion of verification.104 105The following example describes the meanings of all permutations of the two attributes and arguments:106 107.. code-block:: c++108 109 void nb1_na1() [[clang::nonblocking(true)]] [[clang::nonallocating(true)]];110 // Valid; nonallocating(true) is superfluous but doesn't contradict the guarantee.111 112 void nb1_na0() [[clang::nonblocking(true)]] [[clang::nonallocating(false)]];113 // error: 'allocating' and 'nonblocking' attributes are not compatible114 115 void nb0_na1() [[clang::nonblocking(false)]] [[clang::nonallocating(true)]];116 // Valid; the function does not allocate memory, but may lock for other reasons.117 118 void nb0_na0() [[clang::nonblocking(false)]] [[clang::nonallocating(false)]];119 // Valid.120 121 122Type conversions123----------------124 125A performance constraint can be removed or weakened via an implicit conversion. An attempt to add126or strengthen a performance constraint is unsafe and results in a warning. The rules for this127are comparable to that for ``noexcept`` in C++17 and later.128 129.. code-block:: c++130 131 void unannotated();132 void nonblocking() [[clang::nonblocking]];133 void nonallocating() [[clang::nonallocating]];134 135 void example()136 {137 // It's fine to remove a performance constraint.138 void (*fp_plain)();139 fp_plain = unannotated;140 fp_plain = nonblocking;141 fp_plain = nonallocating;142 143 // Adding/spoofing nonblocking is unsafe.144 void (*fp_nonblocking)() [[clang::nonblocking]];145 fp_nonblocking = nullptr;146 fp_nonblocking = nonblocking;147 fp_nonblocking = unannotated;148 // ^ warning: attribute 'nonblocking' should not be added via type conversion149 fp_nonblocking = nonallocating;150 // ^ warning: attribute 'nonblocking' should not be added via type conversion151 152 // Adding/spoofing nonallocating is unsafe.153 void (*fp_nonallocating)() [[clang::nonallocating]];154 fp_nonallocating = nullptr;155 fp_nonallocating = nonallocating;156 fp_nonallocating = nonblocking; // no warning because nonblocking includes nonallocating157 fp_nonallocating = unannotated;158 // ^ warning: attribute 'nonallocating' should not be added via type conversion159 }160 161Virtual methods162---------------163 164In C++, when a virtual method has a performance constraint, overriding methods in165subclasses inherit the constraint.166 167.. code-block:: c++168 169 struct Base {170 virtual void unsafe();171 virtual void safe() noexcept [[clang::nonblocking]];172 };173 174 struct Derived : public Base {175 void unsafe() [[clang::nonblocking]] override;176 // It's okay for an overridden method to be more constrained177 178 void safe() noexcept override;179 // This method is implicitly declared `nonblocking`, inherited from Base.180 };181 182Redeclarations, overloads, and name mangling183--------------------------------------------184 185The ``nonblocking`` and ``nonallocating`` attributes, like ``noexcept``, do not factor into186argument-dependent lookup and overloaded functions/methods.187 188First, consider that ``noexcept`` is integral to a function's type:189 190.. code-block:: c++191 192 void f1(int);193 void f1(int) noexcept;194 // error: exception specification in declaration does not match previous195 // declaration196 197Unlike ``noexcept``, a redeclaration of ``f2`` with an added or stronger performance constraint is198legal and propagates the attribute to the previous declaration:199 200.. code-block:: c++201 202 int f2();203 int f2() [[clang::nonblocking]]; // redeclaration with stronger constraint is OK.204 205This greatly eases adoption by making it possible to annotate functions in external libraries206without modifying library headers.207 208A redeclaration with a removed or weaker performance constraint produces a warning, paralleling209the behavior of ``noexcept``:210 211.. code-block:: c++212 213 int f2() { return 42; }214 // warning: attribute 'nonblocking' on function does not match previous declaration215 216In C++14, the following two declarations of `f3` are identical (a single function). In C++17 they217are separate overloads:218 219.. code-block:: c++220 221 void f3(void (*)());222 void f3(void (*)() noexcept);223 224Similarly, the following two declarations of `f4` are separate overloads. This pattern may pose225difficulties due to ambiguity:226 227.. code-block:: c++228 229 void f4(void (*)());230 void f4(void (*)() [[clang::nonblocking]]);231 232The attributes have no effect on the mangling of function and method names.233 234Objective-C235-----------236 237The attributes are currently unsupported on Objective-C methods.238 239Analysis and warnings240=====================241 242Constraints243-----------244 245Functions declared ``nonallocating`` or ``nonblocking``, when defined, are verified according to the246following rules. Such functions:247 2481. May not allocate or deallocate memory on the heap. The analysis follows the calls to249 ``operator new`` and ``operator delete`` generated by the ``new`` and ``delete`` keywords, and250 treats them like any other function call. The global ``operator new`` and ``operator delete``251 aren't declared ``nonblocking`` or ``nonallocating`` and so they are considered unsafe. (This252 is correct because most memory allocators are not lock-free. Note that the placement form of253 ``operator new`` is implemented inline in libc++'s ``<new>`` header, and is verifiably254 ``nonblocking``, since it merely casts the supplied pointer to the result type.)255 2562. May not throw or catch exceptions. To throw, the compiler must allocate the exception on the257 heap. (Also, many subclasses of ``std::exception`` allocate a string). Exceptions are258 deallocated when caught.259 2603. May not make any indirect function call, via a virtual method, function pointer, or261 pointer-to-member function, unless the target is explicitly declared with the same262 ``nonblocking`` or ``nonallocating`` attribute (or stronger).263 2644. May not make direct calls to any other function, with the following exceptions:265 266 a. The callee is also explicitly declared with the same ``nonblocking`` or ``nonallocating``267 attribute (or stronger).268 b. The callee is defined in the same translation unit as the caller, does not have the ``false``269 form of the required attribute, and can be verified to have the same attribute or stronger,270 according to these same rules.271 c. The callee is a built-in function that is known not to block or allocate.272 d. The callee is declared ``noreturn`` and, if compiling C++, the callee is also declared273 ``noexcept``. This special case excludes functions such as ``abort()`` and ``std::terminate()``274 from the analysis. (The reason for requiring ``noexcept`` in C++ is that a function declared275 ``noreturn`` could be a wrapper for ``throw``.)276 2775. May not invoke or access an Objective-C method or property, since ``objc_msgSend()`` calls into278 the Objective-C runtime, which may allocate memory or otherwise block.279 2806. May not access thread-local variables. Typically, thread-local variables are allocated on the281 heap when first accessed.282 283Functions declared ``nonblocking`` have an additional constraint:284 2857. May not declare static local variables (e.g. Meyers singletons). The compiler generates a lock286 protecting the initialization of the variable.287 288Violations of any of these rules result in warnings, in the ``-Wfunction-effects`` category:289 290.. code-block:: c++291 292 void notInline();293 294 void example() [[clang::nonblocking]]295 {296 auto* x = new int;297 // warning: function with 'nonblocking' attribute must not allocate or deallocate298 // memory299 300 if (x == nullptr) {301 static Logger* logger = createLogger();302 // warning: function with 'nonblocking' attribute must not have static local variables303 304 throw std::runtime_warning{ "null" };305 // warning: 'nonblocking" function 'example' must not throw exceptions306 }307 notInline();308 // warning: 'function with 'nonblocking' attribute must not call non-'nonblocking' function309 // 'notInline'310 // note (on notInline()): declaration cannot be inferred 'nonblocking' because it has no311 // definition in this translation unit312 }313 314Inferring ``nonblocking`` or ``nonallocating``315----------------------------------------------316 317In the absence of a ``nonblocking`` or ``nonallocating`` attribute (whether ``true`` or ``false``),318a function that is called from a performance-constrained function may be analyzed to319infer whether it has a desired attribute. This analysis happens when the function is not a virtual320method, and it has a visible definition within the current translation unit (i.e. its body can be321traversed).322 323.. code-block:: c++324 325 void notInline();326 int implicitlySafe() { return 42; }327 void implicitlyUnsafe() { notInline(); }328 329 void example() [[clang::nonblocking]]330 {331 int x = implicitlySafe(); // OK332 implicitlyUnsafe();333 // warning: function with 'nonblocking' attribute must not call non-'nonblocking' function334 // 'implicitlyUnsafe'335 // note (on implicitlyUnsafe): function cannot be inferred 'nonblocking' because it calls336 // non-'nonblocking' function 'notInline'337 // note (on notInline()): declaration cannot be inferred 'nonblocking' because it has no338 // definition in this translation unit339 }340 341Lambdas and blocks342------------------343 344As mentioned earlier, the performance constraint attributes apply only to a single function and not345to any code nested inside it, including blocks, lambdas, and local classes. It is possible for a346nonblocking function to schedule the execution of a blocking lambda on another thread. Similarly, a347blocking function may create a ``nonblocking`` lambda for use in a realtime context.348 349Operations which create, destroy, copy, and move lambdas and blocks are analyzed in terms of the350underlying function calls. For example, the creation of a lambda with captures generates a function351call to an anonymous struct's constructor, passing the captures as parameters.352 353Implicit function calls in the AST354----------------------------------355 356The ``nonblocking`` / ``nonallocating`` analysis occurs at the Sema phase of analysis in Clang.357During Sema, there are some constructs which will eventually become function calls, but do not358appear as function calls in the AST. For example, ``auto* foo = new Foo;`` becomes a declaration359containing a ``CXXNewExpr`` which is understood as a function call to the global ``operator new``360(in this example), and a ``CXXConstructExpr``, which, for analysis purposes, is a function call to361``Foo``'s constructor. Most gaps in the analysis would be due to incomplete knowledge of AST362constructs which become function calls.363 364Disabling diagnostics365---------------------366 367Function effect diagnostics are controlled by ``-Wfunction-effects``.368 369A construct like this can be used to exempt code from the checks described here:370 371.. code-block:: c++372 373 #define NONBLOCKING_UNSAFE(...) \374 _Pragma("clang diagnostic push") \375 _Pragma("clang diagnostic ignored \"-Wunknown-warning-option\"") \376 _Pragma("clang diagnostic ignored \"-Wfunction-effects\"") \377 __VA_ARGS__ \378 _Pragma("clang diagnostic pop")379 380Disabling the diagnostic allows for:381 382- constructs which do block, but which in practice are used in ways to avoid unbounded blocking,383 e.g. a thread pool with semaphores to coordinate multiple realtime threads;384- using libraries which are safe but not yet annotated;385- incremental adoption in a large codebase.386 387Adoption388========389 390There are a few common issues that arise when adopting the ``nonblocking`` and ``nonallocating``391attributes.392 393C++ exceptions394--------------395 396Exceptions pose a challenge to the adoption of the performance constraints. Common library functions397which throw exceptions include:398 399+----------------------------------+-----------------------------------------------------------------------+400| Method | Alternative |401+==================================+=======================================================================+402| ``std::vector<T>::at()`` | ``operator[](size_t)``, after verifying that the index is in range. |403+----------------------------------+-----------------------------------------------------------------------+404| ``std::optional<T>::value()`` | ``operator*``, after checking ``has_value()`` or ``operator bool()``. |405+----------------------------------+-----------------------------------------------------------------------+406| ``std::expected<T, E>::value()`` | Same as for ``std::optional<T>::value()``. |407+----------------------------------+-----------------------------------------------------------------------+408 409 410``std::function<R(Args...)>``411-----------------------------412 413``std::function<R(Args...)>`` is generally incompatible with ``nonblocking`` and ``nonallocating``414code, because a typical implementation may allocate heap memory in the constructor.415 416Alternatives:417 418- ``std::function_ref`` (available in C++26 or as ``llvm::function_ref``). This is appropriate and419 optimal when a functor's lifetime does not need to extend past the function that created it.420 421- ``inplace_function`` from WG14. This solves the allocation problem by giving the functor wrapper422 a fixed size known at compile time and using an inline buffer.423 424While these alternatives both address the heap allocation of ``std::function``, they are still425obstacles to ``nonblocking/nonallocating`` verification, for reasons detailed in the next section.426 427 428Interactions with type-erasure techniques429-----------------------------------------430 431``std::function<R(Args...)>`` illustrates a common C++ type-erasure technique. Using template432argument deduction, it decomposes a function type into its return and parameter types. Additional433components of the function type, including ``noexcept``, ``nonblocking``, ``nonallocating``, and any434other attributes, are discarded.435 436Standard library support for these components of a function type is not immediately forthcoming.437 438Code can work around this limitation in either of two ways:439 4401. Avoid abstractions like ``std::function`` and instead work directly with the original lambda type.441 4422. Create a specialized alternative, e.g. ``nonblocking_function_ref<R(Args...)>`` where all function443 pointers used in the implementation and its interface are ``nonblocking``.444 445As an example of the first approach, when using a lambda as a *Callable* template parameter, the446attribute is preserved:447 448.. code-block:: c++449 450 std::sort(vec.begin(), vec.end(),451 [](const Elem& a, const Elem& b) [[clang::nonblocking]] { return a.mem < b.mem; });452 453Here, the type of the ``Compare`` template parameter is an anonymous class generated from the454lambda, with an ``operator()`` method holding the ``nonblocking`` attribute.455 456A complication arises when a *Callable* template parameter, instead of being a lambda or class457implementing ``operator()``, is a function pointer:458 459.. code-block:: c++460 461 static bool compare_elems(const Elem& a, const Elem& b) [[clang::nonblocking]] {462 return a.mem < b.mem; };463 464 std::sort(vec.begin(), vec.end(), compare_elems);465 466Here, the type of ``compare_elems`` is decomposed to ``bool(const Elem&, const Elem&)``, without467``nonblocking``, when forming the template parameter. This can be solved using the second approach,468creating a specialized alternative which explicitly requires the attribute. In this case, it's469possible to use a small wrapper to transform the function pointer into a functor:470 471.. code-block:: c++472 473 template <typename>474 class nonblocking_fp;475 476 template <typename R, typename... Args>477 class nonblocking_fp<R(Args...)> {478 public:479 using impl_t = R (*)(Args...) [[clang::nonblocking]];480 481 private:482 impl_t mImpl{ nullptr_t };483 public:484 nonblocking_fp() = default;485 nonblocking_fp(impl_t f) : mImpl{ f } {}486 487 R operator()(Args... args) const488 {489 return mImpl(std::forward<Args>(args)...);490 }491 };492 493 // deduction guide (like std::function's)494 template< class R, class... ArgTypes >495 nonblocking_fp( R(*)(ArgTypes...) ) -> nonblocking_fp<R(ArgTypes...)>;496 497 // --498 499 // Wrap the function pointer in a functor which preserves ``nonblocking``.500 std::sort(vec.begin(), vec.end(), nonblocking_fp{ compare_elems });501 502Now, the ``nonblocking`` attribute of ``compare_elems`` is verified when it is converted to a503``nonblocking`` function pointer, as the argument to ``nonblocking_fp``'s constructor. The template504parameter is the functor class ``nonblocking_fp``.505 506 507Static local variables508----------------------509 510Static local variables are often used for lazily-constructed globals (Meyers singletons). Beyond the511compiler's use of a lock to ensure thread-safe initialization, it is dangerously easy to512inadvertently trigger initialization, involving heap allocation, from a ``nonblocking`` or513``nonallocating`` context.514 515Generally, such singletons need to be replaced by globals, and care must be taken to ensure their516initialization before they are used from ``nonblocking`` or ``nonallocating`` contexts.517 518 519Annotating libraries520--------------------521 522It can be surprising that the analysis does not depend on knowledge of any primitives; it simply523assumes the worst, that all function calls are unsafe unless explicitly marked as safe or able to be524inferred as safe. With ``nonblocking``, this appears to suffice for all but the most primitive of525spinlocks.526 527At least for an operating system's C functions, it is possible to define an override header which528redeclares safe common functions (e.g. ``pthread_self()``) with the addition of ``nonblocking``.529This may help in adopting the feature incrementally.530 531It also helps that many of the functions in the standard C libraries (notably ``<math.h>``)532are treated as built-in functions by Clang, which the diagnosis understands to be safe.533 534Much of the C++ standard library consists of inline templated functions which work well with535inference. A small number of primitives may need explicit ``nonblocking/nonallocating`` attributes.536