blob: ca13883c46ac29d1c9a13c3d7deb0195b269c2c5 [file] [log] [blame]
.. _chapter-pw-assert:
.. default-domain:: cpp
.. highlight:: cpp
=========
pw_assert
=========
--------
Overview
--------
Pigweed's assert module enables applications to check preconditions, triggering
a crash if the condition is not met. Consistent use of asserts is one aspect of
defensive programming that can lead to more reliable and less buggy code.
The assert API facilitates flexible crash handling through Pigweed's facade
mechanism. The API is desigend to enable features like:
- Optional ancillary printf-style messages along assertions
- Capturing actual values of binary operator assertions like ``a < b``
- Compatibility with pw_tokenizer for reduced binary code size
The ``pw_assert`` API provides three classes of macros:
- **PW_CRASH(format, ...)** - Trigger a crash with a message.
- **PW_CHECK(condition[, format, ...])** - Assert a condition, optionally with
a message.
- **PW_CHECK_<type>_<cmp>(a, b[, fmt, ...])** - Assert that the expression ``a
<cmp> b`` is true, optionally with a message.
.. tip::
All of the assert macros optionally support a message with additional
arguments, to assist in debugging when an assert triggers:
.. code-block:: cpp
PW_CHECK_INT_LE(ItemCount(), 100);
PW_CHECK_INT_LE(ItemCount(), 100, "System state: %s", GetStateStr());
Example
-------
.. code-block:: cpp
#include "pw_assert/assert.h"
int main() {
bool sensor_running = StartSensor(&msg);
PW_CHECK(sensor_running, "Sensor failed to start; code: %s", msg);
int temperature_c = ReadSensorCelcius();
PW_CHECK_INT_LE(temperature_c, 100,
"System is way out of heat spec; state=%s",
ReadSensorStateString());
}
.. tip::
All macros have both a ``CHECK`` and ``DCHECK`` variant. The ``CHECK``
variant is always enabled, even in production. Generally, we advise making
most asserts ``CHECK`` rather than ``DCHECK``, unless there is a critical
performance or code size reason to use ``DCHECK``.
.. code-block:: cpp
// This assert is always enabled, even in production.
PW_CHECK_INT_LE(ItemCount(), 100);
// This assert disabled for release builds, where NDEBUG is defined.
// The functions ItemCount() and GetStateStr() are never called.
PW_DCHECK_INT_LE(ItemCount(), 100, "System state: %s", GetStateStr());
Structure of assert modules
---------------------------
The module is split into two components:
1. The **facade** (this module) which is only a macro interface layer, and
performs the actual checks for the conditions.
2. The **backend**, provided elsewhere, that handles the consequences of an
assert failing. Example backends include ``pw_assert_basic``, which prints a
useful message and either quits the application (on host) or hangs in a
while loop (on device). In the future, there will be a tokenized assert
backend. This is also where application or product specific crash handling
would go.
.. blockdiag::
blockdiag {
default_fontsize = 16;
facade [label = "facade"];
backend [label = "backend"];
facade -> backend
}
See the Backend API section below for more details.
--------------------
Facade API reference
--------------------
The below functions describe the assert API functions that applications should
invoke to assert.
.. cpp:function:: PW_CRASH(format, ...)
Trigger a crash with a message. Replaces LOG_FATAL() in other systems. Can
include a message with format arguments; for example:
.. code-block:: cpp
PW_CRASH("Unexpected: frobnitz in state: %s", frobnitz_state);
Note: ``PW_CRASH`` is the equivalent of ``LOG_FATAL`` in other systems, where
a device crash is triggered with a message. In Pigweed, logging and
crashing/asserting are separated. There is a ``LOG_CRITICAL`` level in the
logging module, but it does not have side effects; for ``LOG_FATAL``, instead
use this macro (``PW_CRASH``).
.. cpp:function:: PW_CHECK(condition)
.. cpp:function:: PW_CHECK(condition, format, ...)
.. cpp:function:: PW_DCHECK(condition)
.. cpp:function:: PW_DCHECK(condition, format, ...)
Assert that a condition is true, optionally including a message with
arguments to report if the codition is false.
The ``DCHECK`` variants only run if ``NDEBUG`` is defined; otherwise, the
entire statement is removed (and the expression not evaluated).
Example:
.. code-block:: cpp
PW_CHECK(StartTurbines());
PW_CHECK(StartWarpDrive(), "Oddly warp drive couldn't start; ruh-roh!");
PW_CHECK(RunSelfTest(), "Failure in self test; try %d", TestAttempts());
.. attention::
Don't use use ``PW_CHECK`` for binary comparisons or status checks!
Instead, use the ``PW_CHECK_<TYPE>_<OP>`` macros. These macros enable
capturing the value of the operands, and also tokenizing them if using a
tokenizing assert backend. For example, if ``x`` and ``b`` are integers,
use instead ``PW_CHECK_INT_LT(x, b)``.
Additionally, use ``PW_CHECK_OK(status)`` when checking for a
``Status::OK``, since it enables showing a human-readable status string
rather than an integer (e.g. ``status == RESOURCE_EXHAUSTED`` instead of
``status == 5``.
+------------------------------------+-------------------------------------+
| **Do NOT do this** | **Do this instead** |
+------------------------------------+-------------------------------------+
| ``PW_CHECK(a_int < b_int)`` | ``PW_CHECK_INT_LT(a_int, b_int)`` |
+------------------------------------+-------------------------------------+
| ``PW_CHECK(a_ptr <= b_ptr)`` | ``PW_CHECK_PTR_LE(a_ptr, b_ptr)`` |
+------------------------------------+-------------------------------------+
| ``PW_CHECK(Temp() <= 10.0)`` | ``PW_CHECK_FLOAT_EXACT_LE(`` |
| | `` Temp(), 10.0)`` |
+------------------------------------+-------------------------------------+
| ``PW_CHECK(Foo() == Status::OK)`` | ``PW_CHECK_OK(Foo())`` |
+------------------------------------+-------------------------------------+
.. cpp:function:: PW_CHECK_NOTNULL(ptr)
.. cpp:function:: PW_CHECK_NOTNULL(ptr, format, ...)
.. cpp:function:: PW_DCHECK_NOTNULL(ptr)
.. cpp:function:: PW_DCHECK_NOTNULL(ptr, format, ...)
Assert that the given pointer is not ``NULL``, optionally including a message
with arguments to report if the pointer is ``NULL``.
The ``DCHECK`` variants only run if ``NDEBUG`` is defined; otherwise, the
entire statement is removed (and the expression not evaluated).
.. code-block:: cpp
Foo* foo = GetTheFoo()
PW_CHECK_NOTNULL(foo);
Bar* bar = GetSomeBar();
PW_CHECK_NOTNULL(bar, "Weirdly got NULL bar; state: %d", MyState());
.. cpp:function:: PW_CHECK_TYPE_OP(a, b)
.. cpp:function:: PW_CHECK_TYPE_OP(a, b, format, ...)
.. cpp:function:: PW_DCHECK_TYPE_OP(a, b)
.. cpp:function:: PW_DCHECK_TYPE_OP(a, b, format, ...)
Asserts that ``a OP b`` is true, where ``a`` and ``b`` are converted to
``TYPE``; with ``OP`` and ``TYPE`` described below.
If present, the optional format message is reported on failure. Depending on
the backend, values of ``a`` and ``b`` will also be reported.
The ``DCHECK`` variants only run if ``NDEBUG`` is defined; otherwise, the
entire statement is removed (and the expression not evaluated).
Example, with no message:
.. code-block:: cpp
PW_CHECK_INT_LE(CurrentTemperature(), 100);
PW_CHECK_INT_LE(ItemCount(), 100);
Example, with an included message and arguments:
.. code-block:: cpp
PW_CHECK_FLOAT_EXACT_GE(BatteryVoltage(), 3.2,
"System state=%s", SysState());
Below is the full list of binary comparison assert macros, along with the
type specifier. The specifier is irrelevant to application authors but is
needed for backend implementers.
+-------------------------+--------------+-----------+-----------------------+
| Macro | a, b type | condition | a, b format specifier |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_INT_LE | int | a <= b | %d |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_INT_LT | int | a < b | %d |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_INT_GE | int | a >= b | %d |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_INT_GT | int | a > b | %d |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_INT_EQ | int | a == b | %d |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_INT_NE | int | a != b | %d |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_UINT_LE | unsigned int | a <= b | %u |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_UINT_LT | unsigned int | a < b | %u |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_UINT_GE | unsigned int | a >= b | %u |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_UINT_GT | unsigned int | a > b | %u |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_UINT_EQ | unsigned int | a == b | %u |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_UINT_NE | unsigned int | a != b | %u |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_PTR_LE | void* | a <= b | %p |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_PTR_LT | void* | a < b | %p |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_PTR_GE | void* | a >= b | %p |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_PTR_GT | void* | a > b | %p |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_PTR_EQ | void* | a == b | %p |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_PTR_NE | void* | a != b | %p |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_FLOAT_EXACT_LE | float | a <= b | %f |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_FLOAT_EXACT_LT | float | a < b | %f |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_FLOAT_EXACT_GE | float | a >= b | %f |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_FLOAT_EXACT_GT | float | a > b | %f |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_FLOAT_EXACT_EQ | float | a == b | %f |
+-------------------------+--------------+-----------+-----------------------+
| PW_CHECK_FLOAT_EXACT_NE | float | a != b | %f |
+-------------------------+--------------+-----------+-----------------------+
The above ``CHECK_*_*()`` are also available in DCHECK variants, which will
only evaluate their arguments and trigger if the ``NDEBUG`` macro is defined.
+--------------------------+--------------+-----------+----------------------+
| Macro | a, b type | condition | a, b format |
| | | | specifier |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_INT_LE | int | a <= b | %d |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_INT_LT | int | a < b | %d |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_INT_GE | int | a >= b | %d |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_INT_GT | int | a > b | %d |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_INT_EQ | int | a == b | %d |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_INT_NE | int | a != b | %d |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_UINT_LE | unsigned int | a <= b | %u |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_UINT_LT | unsigned int | a < b | %u |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_UINT_GE | unsigned int | a >= b | %u |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_UINT_GT | unsigned int | a > b | %u |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_UINT_EQ | unsigned int | a == b | %u |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_UINT_NE | unsigned int | a != b | %u |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_PTR_LE | void* | a <= b | %p |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_PTR_LT | void* | a < b | %p |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_PTR_GE | void* | a >= b | %p |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_PTR_GT | void* | a > b | %p |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_PTR_EQ | void* | a == b | %p |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_PTR_NE | void* | a != b | %p |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_FLOAT_EXACT_LE | float | a <= b | %f |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_FLOAT_EXACT_LT | float | a < b | %f |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_FLOAT_EXACT_GE | float | a >= b | %f |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_FLOAT_EXACT_GT | float | a > b | %f |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_FLOAT_EXACT_EQ | float | a == b | %f |
+--------------------------+--------------+-----------+----------------------+
| PW_DCHECK_FLOAT_EXACT_NE | float | a != b | %f |
+--------------------------+--------------+-----------+----------------------+
.. attention::
For float, proper comparator checks which take floating point
precision and ergo error accumulation into account are not provided on
purpose as this comes with some complexity and requires application
specific tolerances in terms of Units of Least Precision (ULP). Instead,
we recommend developers carefully consider how floating point precision and
error impact the data they are bounding and whether checks are appropriate.
.. cpp:function:: PW_CHECK_FLOAT_NEAR(a, b, abs_tolerance)
.. cpp:function:: PW_CHECK_FLOAT_NEAR(a, b, abs_tolerance, format, ...)
.. cpp:function:: PW_DCHECK_FLOAT_NEAR(a, b, abs_tolerance)
.. cpp:function:: PW_DCHECK_FLOAT_NEAR(a, b, abs_tolerance, format, ...)
Asserts that ``(a >= b - abs_tolerance) && (a <= b + abs_tolerance)`` is true,
where ``a``, ``b``, and ``abs_tolerance`` are converted to ``float``.
.. note::
This also asserts that ``abs_tolerance >= 0``.
The ``DCHECK`` variants only run if ``NDEBUG`` is defined; otherwise, the
entire statement is removed (and the expression not evaluated).
Example, with no message:
.. code-block:: cpp
PW_CHECK_FLOAT_NEAR(cos(0.0f), 1, 0.001);
Example, with an included message and arguments:
.. code-block:: cpp
PW_CHECK_FLOAT_NEAR(FirstOperation(), RedundantOperation(), 0.1,
"System state=%s", SysState());
.. cpp:function:: PW_CHECK_OK(status)
.. cpp:function:: PW_CHECK_OK(status, format, ...)
.. cpp:function:: PW_DCHECK_OK(status)
.. cpp:function:: PW_DCHECK_OK(status, format, ...)
Assert that ``status`` evaluates to ``pw::Status::OK`` (in C++) or
``PW_STATUS_OK`` (in C). Optionally include a message with arguments to
report.
The ``DCHECK`` variants only run if ``NDEBUG`` is defined; otherwise, the
entire statement is removed (and the expression not evaluated).
.. code-block:: cpp
pw::Status operation_status = DoSomeOperation();
PW_CHECK_OK(operation_status);
// Any expression that evaluates to a pw::Status or pw_Status works.
PW_CHECK_OK(DoTheThing(), "System state: %s", SystemState());
// C works too.
pw_Status c_status = DoMoreThings();
PW_CHECK_OK(c_status, "System state: %s", SystemState());
.. note::
Using ``PW_CHECK_OK(status)`` instead of ``PW_CHECK(status == Status::OK)``
enables displaying an error message with a string version of the error
code; for example ``status == RESOURCE_EXHAUSTED`` instead of ``status ==
5``.
---------------------
Backend API reference
---------------------
The backend controls what to do in the case of an assertion failure. In the
most basic cases, the backend could display the assertion failure on something
like sys_io and halt in a while loop waiting for a debugger. In other cases,
the backend could store crash details like the current thread's stack to flash.
This facade module (``pw_assert``) does not provide a backend. See
:ref:`chapter-pw-assert-basic` for a basic implementation.
.. attention::
The facade macros (``PW_CRASH`` and related) are expected to behave like they
have the ``[[ noreturn ]]`` attribute set. This implies that the backend
handler functions, ``PW_HANDLE_*`` defined by the backend, must not return.
In other words, the device must reboot.
The backend must provide the header
``pw_assert_backend/backend.h``
and that header must define the following macros:
.. cpp:function:: PW_HANDLE_CRASH(message, ...)
Trigger a system crash or halt, and if possible, deliver the specified
message and arguments to the user or developer.
.. cpp:function:: PW_HANDLE_ASSERT_FAILURE(condition_str, message, ...)
Trigger a system crash or halt, and if possible, deliver the condition string
(indicating what expression was false) and the message with format arguments,
to the user or developer.
This macro is invoked from the ``PW_CHECK`` facade macro if condition is
false.
.. cpp:function:: PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE( \
a_str, a_val, op_str, b_str, b_val, type_fmt, message, ...)
Trigger a system crash or halt for a failed binary comparison assert (e.g.
any of the ``PW_CHECK_<type>_<op>`` macros). The handler should combine the
assert components into a useful message for the user; though in some cases
this may not be possible.
Consider the following example:
.. code-block:: cpp
int temp = 16;
int max_temp = 15;
PW_CHECK_INT_LE(temp, MAX_TEMP, "Got too hot; state: %s", GetSystemState());
In this block, the assert will trigger, which will cause the facade to invoke
the handler macro. Below is the meaning of the arguments, referencing to the
example:
- ``a_str`` - Stringified first operand. In the example: ``"temp"``.
- ``a_val`` - The value of the first operand. In the example: ``16``.
- ``op_str`` - The string version of the operator. In the example: "<=".
- ``b_str`` - Stringified second operand. In the example: ``"max_temp"``.
- ``b_val`` - The value of the second operand. In the example: ``15``.
- ``type_fmt`` - The format code for the type. In the example: ``"%d"``.
- ``message, ...`` - A formatted message to go with the assert. In the
example: ``"Got too hot; state: %s", "ON_FIRE"``.
.. tip::
See :ref:`chapter-pw-assert-basic` for one way to combine these arguments
into a meaningful error message.
--------------------------
Frequently asked questions
--------------------------
When should DCHECK_* be used instead of CHECK_* and vice versa?
---------------------------------------------------------------
There is no hard and fast rule for when to use one or the other.
In theory, ``DCHECK_*`` macros should never be used and all the asserts should
remain active in production. In practice, **assert statements come at a binary
size and runtime cost**, even when using extensions like a tokenized assert
backend that strips the stringified assert expression from the binary. Each
assert is **at least a branch with a function call**; depending on the assert
backend, that function call may take several arguments (like the message, the
file line number, the module, etc). These function calls can take 10-20 bytes
or more of ROM each. Thus, there is a balance to be struct between ``DCHECK_*``
and ``CHECK_*``.
Pigweed uses these conventions to decide between ``CHECK_*`` and ``DCHECK_*``:
- **Prefer to use CHECK_* at public API boundaries** of modules, where an
invalid value is a clear programmer bug. In certain cases use ``DCHECK_*`` to
keep binary size small when in production; for example, in modules with a
large public API surface, or modules with many inlined functions in headers.
- **Avoid using CHECK_* macros in headers.** It is still OK to use ``CHECK_*``
macros in headers, but carefully consider the cost, since inlined use of the
``CHECK_*`` macros in headers will expand to the full assert cost for every
translation unit that includes the header and calls the function with the
``CHECK_*`` instance. ``DCHECK_*`` macros are are better, but even they come
at a cost, since it is preferable to be able to compile a binary in debug
mode for as long as possible on the road to production.
- **Prefer to use DCHECK_* variants for internal asserts** that attempt to
catch module-author level programming errors. For example, use DCHECKs to
verify internal function preconditions, or other invariants that should
always be true but will likely never fire in production. In some cases using
``CHECK_*`` macros for internal consistency checking can make sense, if the
runtime cost is low and there are only a couple of instances.
.. tip::
**Do not return error status codes for obvious API misuse**
Returning an error code may **mask the earliest sign of a bug** because
notifying the developer of the problem depends on correct propagation of the
error to upper levels of the system. Instead, prefer to use the ``CHECK_*``
or ``DCHECK_*`` macros to ensure a prompt termination and warning to the
developer.
**Error status codes should be reserved for system misbehaviour or expected
exceptional cases**, like a sensor is not yet ready, or a storage subsystem
is full when writing. Doing ``CHECK_*`` assertions in those cases would be a
mistake; so use error codes in those cases instead.
How should objects be asserted against or compared?
---------------------------------------------------
Unfortunatly, there is no native mechanism for this, and instead the way to
assert object states or comparisons is with the normal ``PW_CHECK_*`` macros
that operate on booleans, ints, and floats.
This is due to the requirement of supporting C and also tokenization. It may be
possible support rich object comparions by defining a convention for
stringifying objects; however, this hasn't been added yet. Additionally, such a
mechanism would not work well with tokenization. In particular, it would
require runtime stringifying arguments and rendering them with ``%s``, which
leads to binary bloat even with tokenization. So it is likely that a rich
object assert API won't be added.
Why was the assert facade designed this way?
--------------------------------------------
The Pigweed assert API was designed taking into account the needs of several
past projects the team members were involved with. Based on those experiences,
the following were key requirements for the API:
1. **C compatibility** - Since asserts are typically invoked from arbitrary
contexts, including from vendor or third party code, the assert system must
have a C-compatible API. Some API functions working only in C++ is
acceptable, as long as the key functions work in C.
2. **Capturing both expressions and values** - Since asserts can trigger in
ways that are not repeatable, it is important to capture rich diagnostic
information to help identifying the root cause of the fault. For asserts,
this means including the failing expression text, and optionally also
capturing failing expression values. For example, instead of capturing an
error with the expression (``x < y``), capturing an error with the
expression and values(``x < y, with x = 10, y = 0``).
3. **Tokenization compatible** - It's important that the assert expressions
support tokenization; both the expression itself (e.g. ``a < b``) and the
message attached to the expression. For example: ``PW_CHECK(ItWorks(), "Ruh
roh: %d", some_int)``.
4. **Customizable assert handling** - Most products need to support custom
handling of asserts. In some cases, an assert might trigger printing out
details to a UART; in other cases, it might trigger saving a log entry to
flash. The assert system must support this customization.
The combination of #1, #2, and #3 led to the structure of the API. In
particular, the need to support tokenized asserts and the need to support
capturing values led to the choice of having ``PW_CHECK_INT_LE(a, b)`` instead
of ``PW_CHECK(a <= b)``. Needing to support tokenization is what drove the
facade & backend arrangement, since the backend must provide the raw macros for
asserting in that case, rather than terminating at a C-style API.
Why isn't there a ``PW_CHECK_LE``? Why is the type (e.g. ``INT``) needed?
-------------------------------------------------------------------------
The problem with asserts like ``PW_CHECK_LE(a, b)`` instead of
``PW_CHECK_INT_LE(a, b)`` or ``PW_CHECK_FLOAT_EXACT_LE(a, b)`` is that to
capture the arguments with the tokenizer, we need to know the types. Using the
preprocessor, it is impossible to dispatch based on the types of ``a`` and
``b``, so unfortunately having a separate macro for each of the types commonly
asserted on is necessary.
-------------
Compatibility
-------------
The facade is compatible with both C and C++.
----------------
Roadmap & Status
----------------
The Pigweed assert subsystem consiststs of several modules that work in
coordination. In particular, there is the facade (this module), then a number
of backends to handle assert failures. In some cases, the backends will have
backends (like log_tokenized). Not all of those modules are ready today. Below
is a brief summary of what modules are ready now, and which need to be written
or need more work:
Currently implemented modules
-----------------------------
- ``pw_assert`` - **Stable** - The assert facade (this module). This module is
stable, and in production use. The documentation is comprehensive and covers
the functionality. There are (a) tests for the facade macro processing logic,
using a fake assert backend; and (b) compile tests to verify that the
selected backend compiles with all supported assert constructions and types.
- ``pw_assert_basic`` - **Stable** - The assert basic module is a simple assert
handler that displays the failed assert line and the values of captured
arguments. Output is directed to ``pw_sys_io``. This module is a great
ready-to-roll module when bringing up a system, but is likely not the best
choice for production.
Not yet written modules
-----------------------
- ``pw_assert_tokenized`` - **Not started** - Just like there is a
``pw_log_tokenized`` module that leverages the tokenizer to strip log string
literals from binaries, there will be an implementation that tokenizes the
assert contents (such as the captured source expression string). We may
combine ``pw_log_tokenized`` and ``pw_assert_tokenized`` into
``pw_log_assert_tokenized`` since much of the details would be shared between
them.
- ``pw_assert_log`` - **Not started** - This would use ``pw_log`` on assert
failure to communicate the failure information. Instead of being a separate
module, this might be an optional flag added to ``pw_assert_basic``.
Missing functionality
---------------------
- **Stack traces** - Pigweed doesn't have a reliable stack walker, which makes
displaying a stack trace on crash harder. We plan to add this eventually.
- **Snapshot integration** - Pigweed doesn't yet have a rich system state
capture system that can capture state like number of tasks, available memory,
and so on. Snapshot facilities are the obvious ones to run inside an assert
handler. It'll happen someday.
- **Light asserts to break circular dependencies** - The Pigweed assert API is
flexible thanks to the facade structure; however, this can trigger circular
header dependencies due to facade directly including the backend. This can
happen when adding asserts to low-level functionality in headers, like for
example inside ``<span>``. Our polyfill span is used extensively in low level
components (like malloc and vector), including in some assert backends, which
leads to circular dependencies when span itself tries to assert.
There is no solution to this problem that doesn't require a compromise. The
easiest solution to enabling asserts everywhere is to shift to a C ABI with
link time dependency resolution; this breaks the header cycles. However, such
a structure would prevent custom compile-time filtering and tokenization.
Our current plan is to offer a simple C-function based assert API, with the
backend provided at link time rather than through header redirection. For
example, the API could be as simple as ``void pw_assert(bool condition)`` or
``pw_assert_crash()`` if the condition check was done in a macro. This assert
API is intended for contexts where including the backend header would trigger
these problems, for use in common low level headers like ``<span>``.