blob: 9147171ad6f51b3e688f3a8cf68ca748bb9baecb [file] [log] [blame]
.. Copyright (C) 2024-2025 Free Software Foundation, Inc.
Originally contributed by David Malcolm <dmalcolm@redhat.com>
This is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see
<https://www.gnu.org/licenses/>.
.. default-domain:: c
Tutorial part 7: execution paths
================================
A :type:`diagnostic` can optionally have a :type:`diagnostic_execution_path`
describing a path of execution through code.
For example, let's pretend we're writing a static analyis tool for finding
bugs in `CPython extension code <https://docs.python.org/3/c-api/index.html>`_.
Let's say we're analyzing this code:
.. literalinclude:: ../../../testsuite/libgdiagnostics.dg/test-warning-with-path.c
:language: c
:start-after: begin fake source
:end-before: end fake source
This code attempts to take an Python integer parameter and then build a
list of that length, containing random integers. However, there are
**numerous bugs** in this code: a type mismatch, mistakes in
reference-counting, and an almost total lack of error-handling.
For example, ``PyList_Append`` requires a non-NULL first parameter (``list``),
but ``PyList_New`` can fail, returning NULL, and this isn't checked for,
which would lead to a segfault if ``PyList_New`` fails.
We can add a :type:`diagnostic_execution_path` to the :type:`diagnostic`
via :func:`diagnostic_add_execution_path`, and then add events to it
using :func:`diagnostic_execution_path_add_event`.
For example, with::
diagnostic_event_id alloc_event_id
= diagnostic_execution_path_add_event (path,
loc_call_to_PyList_New,
logical_loc, 0,
"when %qs fails, returning NULL",
"PyList_New");
we create an event that will be worded as::
(1) when `PyList_New' fails, returning NULL
Note that :func:`diagnostic_execution_path_add_event` returns a
:type:`diagnostic_event_id`. We can use this to refer to this event
in another event using the ``%@`` format code in its message, which
takes the address of a :type:`diagnostic_event_id`::
diagnostic_execution_path_add_event (path,
loc_call_to_PyList_Append,
logical_loc, 0,
"when calling %qs, passing NULL from %@ as argument %i",
"PyList_Append", &alloc_event_id, 1);
where the latter event will be worded as::
(2) when calling `PyList_Append', passing NULL from (1) as argument 1
where the ``%@`` reference to the other event has been printed as ``(1)``.
In SARIF output the text "(1)" will have a embedded link referring within the sarif
log to the ``threadFlowLocation`` object for the other event, via JSON
pointer (see `§3.10.3 "URIs that use the sarif scheme" <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790707>`_).
Let's add an event between these describing control flow, creating three
events in all:
.. literalinclude:: ../../../testsuite/libgdiagnostics.dg/test-warning-with-path.c
:language: c
:start-after: begin path creation
:end-before: end path creation
Assuming we also gave it :type:`diagnostic_logical_location` with:
.. literalinclude:: ../../../testsuite/libgdiagnostics.dg/test-warning-with-path.c
:language: c
:start-after: begin create logical locs
:end-before: end create logical locs
and finish the :type:`diagnostic` with :func:`diagnostic_finish` like this::
diagnostic_finish (d,
"passing NULL as argument %i to %qs"
" which requires a non-NULL parameter",
1, "PyList_Append");
then we should get output to text sinks similar to the following::
In function 'make_a_list_of_random_ints_badly':
test-warning-with-path.c:30:5: warning: passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter"
30 | PyList_Append(list, item);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
make_a_list_of_random_ints_badly': events 1-3
26 | list = PyList_New(0);
| ^~~~~~~~~~~~~
| |
| (1) when 'PyList_New' fails, returning NULL
27 |
28 | for (i = 0; i < count; i++) {
| ~~~~~~~~~
| |
| (2) when 'i < count'
29 | item = PyLong_FromLong(random());
30 | PyList_Append(list, item);
| ~~~~~~~~~~~~~~~~~~~~~~~~~
| |
| (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
and for SARIF sinks the path will be added as a ``codeFlow`` object
(see SARIF 2.1.0 `§3.36 codeFlow object <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790990>`_).
Here's the above example in full:
.. literalinclude:: ../../../testsuite/libgdiagnostics.dg/test-warning-with-path.c
:language: c
:start-after: begin full example
:end-before: end full example
See the :doc:`guide to execution paths <../topics/execution-paths>`
for more information, or go on to
:doc:`the next section of the tutorial <08-message-buffers>`.