blob: 560804329e8070cbbc386a0e6a5711351a58dca5 [file] [log] [blame]
.. _chapter-pw-assert:
.. default-domain:: cpp
.. highlight:: cpp
=========
pw_assert
=========
--------
Overview
--------
Pigweed's assert module provides facilities for applications to check
preconditions. The module is split into two components:
1. The facade (this module) which is only a macro interface layer
2. The backend, provided elsewhere, that implements the low level checks
All behaviour is controlled by the backend.
The ``pw_assert`` public 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());
Design discussion
-----------------
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_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.
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.
---------------------------
Assert 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!
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)``.
+---------------------------------+-------------------------------------+
| **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_LE(Temp(), 10.0)`` |
+---------------------------------+-------------------------------------+
.. 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_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_LE | float | a <= b | %f |
+-------------------+--------------+-----------+-----------------------+
| PW_CHECK_FLOAT_LT | float | a < b | %f |
+-------------------+--------------+-----------+-----------------------+
| PW_CHECK_FLOAT_GE | float | a >= b | %f |
+-------------------+--------------+-----------+-----------------------+
| PW_CHECK_FLOAT_GT | float | a > b | %f |
+-------------------+--------------+-----------+-----------------------+
| PW_CHECK_FLOAT_EQ | float | a == b | %f |
+-------------------+--------------+-----------+-----------------------+
| PW_CHECK_FLOAT_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_LE | float | a <= b | %f |
+--------------------+--------------+-----------+-----------------------+
| PW_DCHECK_FLOAT_LT | float | a < b | %f |
+--------------------+--------------+-----------+-----------------------+
| PW_DCHECK_FLOAT_GE | float | a >= b | %f |
+--------------------+--------------+-----------+-----------------------+
| PW_DCHECK_FLOAT_GT | float | a > b | %f |
+--------------------+--------------+-----------+-----------------------+
| PW_DCHECK_FLOAT_EQ | float | a == b | %f |
+--------------------+--------------+-----------+-----------------------+
| PW_DCHECK_FLOAT_NE | float | a != b | %f |
+--------------------+--------------+-----------+-----------------------+
----------------------------
Assert 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.
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.
.. 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.
-------------
Compatibility
-------------
The facade is compatible with C and C++.