stage1_languages
host_libs_picflag
CRAB1_LIBS
+enable_libdiagnostics
PICFLAG
host_shared
gcc_host_pie
enable_linker_plugin_flags
enable_host_pie
enable_host_shared
+enable_libdiagnostics
enable_stage1_languages
enable_objc_gc
with_target_bdw_gc
plugins [none]
--enable-host-pie build position independent host executables
--enable-host-shared build host code as shared libraries
+ --enable-libdiagnostics build libdiagnostics shared library
--enable-stage1-languages[=all]
choose additional languages to build during stage1.
Mostly useful for compiler development
+
+# Check for libdiagnostics support.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable libdiagnostics" >&5
+$as_echo_n "checking whether to enable libdiagnostics... " >&6; }
+# Check whether --enable-libdiagnostics was given.
+if test "${enable_libdiagnostics+set}" = set; then :
+ enableval=$enable_libdiagnostics; enable_libdiagnostics=$enableval
+else
+ enable_libdiagnostics=no
+fi
+
+
+if test x$enable_libdiagnostics = xyes; then
+ # Disable libdiagnostics if -enable-host-shared not specified
+ # but not if building for Mingw. All code in Windows
+ # is position independent code (PIC).
+ case $target in
+ *mingw*) ;;
+ *)
+ if test x$host_shared != xyes; then
+ as_fn_error $? "
+Enabling libdiagnostics requires --enable-host-shared.
+
+--enable-host-shared typically slows the rest of the compiler down by
+a few %, so you must explicitly enable it.
+
+If you want to build both libdiagnostics and the regular compiler, it is often
+best to do this via two separate configure/builds, in separate
+directories, to avoid imposing the performance cost of
+--enable-host-shared on the regular compiler." "$LINENO" 5
+ fi
+ ;;
+ esac
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_libdiagnostics" >&5
+$as_echo "$enable_libdiagnostics" >&6; }
+
+
+
# Rust requires -ldl and -lpthread if you are using an old glibc that does not include them by
# default, so we check for them here
# We are doing the test here and not in the gcc/configure to be able to nicely disable the
AC_SUBST(PICFLAG)
+
+# Check for libdiagnostics support.
+AC_MSG_CHECKING([whether to enable libdiagnostics])
+AC_ARG_ENABLE(libdiagnostics,
+[AS_HELP_STRING([--enable-libdiagnostics],
+ [build libdiagnostics shared library])],
+enable_libdiagnostics=$enableval,
+enable_libdiagnostics=no)
+
+if test x$enable_libdiagnostics = xyes; then
+ # Disable libdiagnostics if -enable-host-shared not specified
+ # but not if building for Mingw. All code in Windows
+ # is position independent code (PIC).
+ case $target in
+ *mingw*) ;;
+ *)
+ if test x$host_shared != xyes; then
+ AC_MSG_ERROR([
+Enabling libdiagnostics requires --enable-host-shared.
+
+--enable-host-shared typically slows the rest of the compiler down by
+a few %, so you must explicitly enable it.
+
+If you want to build both libdiagnostics and the regular compiler, it is often
+best to do this via two separate configure/builds, in separate
+directories, to avoid imposing the performance cost of
+--enable-host-shared on the regular compiler.])
+ fi
+ ;;
+ esac
+fi
+AC_MSG_RESULT($enable_libdiagnostics)
+AC_SUBST(enable_libdiagnostics)
+
+
# Rust requires -ldl and -lpthread if you are using an old glibc that does not include them by
# default, so we check for them here
# We are doing the test here and not in the gcc/configure to be able to nicely disable the
enable_host_shared = @enable_host_shared@
+enable_libdiagnostics = @enable_libdiagnostics@
+
enable_as_accelerator = @enable_as_accelerator@
CPPLIB = ../libcpp/libcpp.a
xm_defines=@xm_defines@
lang_checks=
lang_checks_parallelized=
+ifeq (@enable_libdiagnostics@,yes)
+lang_checks += check-libdiagnostics
+endif
lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt $(srcdir)/analyzer/analyzer.opt
lang_specs_files=@lang_specs_files@
lang_tree_files=@lang_tree_files@
# compilation or not.
ALL_HOST_OBJS = $(ALL_HOST_FRONTEND_OBJS) $(ALL_HOST_BACKEND_OBJS)
+ifeq (@enable_libdiagnostics@,yes)
+ALL_HOST_OBJS += $(libdiagnostics_OBJS)
+endif
+
BACKEND = libbackend.a main.o libcommon-target.a libcommon.a \
$(CPPLIB) $(LIBDECNUMBER)
libgcc-support lang.all.cross doc selftest @GENINSRC@ srcextra
# This is what must be made before installing GCC and converting libraries.
start.encap: native xgcc$(exeext) cpp$(exeext) specs \
- libgcc-support lang.start.encap @GENINSRC@ srcextra
+ libgcc-support lang.start.encap @LIBDIAGNOSTICS@ @GENINSRC@ srcextra
# These can't be made until after GCC can run.
rest.encap: lang.rest.encap
# This is what is made with the host's compiler
c-family/cppspec.o $(EXTRA_GCC_OBJS) libcommon-target.a \
$(EXTRA_GCC_LIBS) $(LIBS)
+
+libdiagnostics_OBJS = libdiagnostics.o \
+ libcommon.a
+
+# libdiagnostics
+
+LIBDIAGNOSTICS_VERSION_NUM = 0
+LIBDIAGNOSTICS_MINOR_NUM = 0
+LIBDIAGNOSTICS_RELEASE_NUM = 1
+
+ifneq (,$(findstring mingw,$(target)))
+LIBDIAGNOSTICS_FILENAME = libdiagnostics-$(LIBDIAGNOSTICS_VERSION_NUM).dll
+LIBDIAGNOSTICS_IMPORT_LIB = libdiagnostics.dll.a
+
+libdiagnostics: $(LIBDIAGNOSTICS_FILENAME)
+
+else
+
+ifneq (,$(findstring darwin,$(host)))
+
+LIBDIAGNOSTICS_AGE = 1
+LIBDIAGNOSTICS_BASENAME = libdiagnostics
+
+LIBDIAGNOSTICS_SONAME = \
+ ${libdir}/$(LIBDIAGNOSTICS_BASENAME).$(LIBDIAGNOSTICS_VERSION_NUM).dylib
+LIBDIAGNOSTICS_FILENAME = $(LIBDIAGNOSTICS_BASENAME).$(LIBDIAGNOSTICS_VERSION_NUM).dylib
+LIBDIAGNOSTICS_LINKER_NAME = $(LIBDIAGNOSTICS_BASENAME).dylib
+
+# Conditionalize the use of the LD_VERSION_SCRIPT_OPTION and
+# LD_SONAME_OPTION depending if configure found them, using $(if)
+# We have to define a LIBDIAGNOSTICS_COMMA here, otherwise the commas in the "true"
+# result are treated as separators by the $(if).
+LIBDIAGNOSTICS_COMMA := ,
+LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION = \
+ $(if $(LD_VERSION_SCRIPT_OPTION),\
+ -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_VERSION_SCRIPT_OPTION)$(LIBDIAGNOSTICS_COMMA)$(srcdir)/libdiagnostics.map)
+
+LIBDIAGNOSTICS_SONAME_OPTION = \
+ $(if $(LD_SONAME_OPTION), \
+ -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_SONAME_OPTION)$(LIBDIAGNOSTICS_COMMA)$(LIBDIAGNOSTICS_SONAME))
+
+LIBDIAGNOSTICS_SONAME_SYMLINK = $(LIBDIAGNOSTICS_FILENAME)
+LIBDIAGNOSTICS_LINKER_NAME_SYMLINK = $(LIBDIAGNOSTICS_LINKER_NAME)
+
+libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
+ $(LIBDIAGNOSTICS_SYMLINK) \
+ $(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
+
+else
+
+LIBDIAGNOSTICS_LINKER_NAME = libdiagnostics.so
+LIBDIAGNOSTICS_SONAME = $(LIBDIAGNOSTICS_LINKER_NAME).$(LIBDIAGNOSTICS_VERSION_NUM)
+LIBDIAGNOSTICS_FILENAME = \
+ $(LIBDIAGNOSTICS_SONAME).$(LIBDIAGNOSTICS_MINOR_NUM).$(LIBDIAGNOSTICS_RELEASE_NUM)
+
+LIBDIAGNOSTICS_LINKER_NAME_SYMLINK = $(LIBDIAGNOSTICS_LINKER_NAME)
+LIBDIAGNOSTICS_SONAME_SYMLINK = $(LIBDIAGNOSTICS_SONAME)
+
+# Conditionalize the use of the LD_VERSION_SCRIPT_OPTION and
+# LD_SONAME_OPTION depending if configure found them, using $(if)
+# We have to define a LIBDIAGNOSTICS_COMMA here, otherwise the commas in the "true"
+# result are treated as separators by the $(if).
+LIBDIAGNOSTICS_COMMA := ,
+LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION = \
+ $(if $(LD_VERSION_SCRIPT_OPTION),\
+ -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_VERSION_SCRIPT_OPTION)$(LIBDIAGNOSTICS_COMMA)$(srcdir)/libdiagnostics.map)
+
+LIBDIAGNOSTICS_SONAME_OPTION = \
+ $(if $(LD_SONAME_OPTION), \
+ -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_SONAME_OPTION)$(LIBDIAGNOSTICS_COMMA)$(LIBDIAGNOSTICS_SONAME))
+
+libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
+ $(LIBDIAGNOSTICS_SYMLINK) \
+ $(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
+
+endif
+endif
+
+libdiagnostics.serial = $(LIBDIAGNOSTICS_FILENAME)
+
+# Tell GNU make to ignore these if they exist.
+.PHONY: libdiagnostics
+
+ifneq (,$(findstring mingw,$(target)))
+# Create import library
+LIBDIAGNOSTICS_EXTRA_OPTS = -Wl,--out-implib,$(LIBDIAGNOSTICS_IMPORT_LIB)
+else
+
+ifneq (,$(findstring darwin,$(host)))
+# TODO : Construct a Darwin-style symbol export file.
+LIBDIAGNOSTICS_EXTRA_OPTS = -Wl,-compatibility_version,$(LIBDIAGNOSTICS_VERSION_NUM) \
+ -Wl,-current_version,$(LIBDIAGNOSTICS_VERSION_NUM).$(LIBDIAGNOSTICS_MINOR_NUM).$(LIBDIAGNOSTICS_AGE) \
+ $(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION) \
+ $(LIBDIAGNOSTICS_SONAME_OPTION)
+else
+
+LIBDIAGNOSTICS_EXTRA_OPTS = $(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION) \
+ $(LIBDIAGNOSTICS_SONAME_OPTION)
+endif
+endif
+
+$(LIBDIAGNOSTICS_FILENAME): $(libdiagnostics_OBJS) $(CPPLIB) $(EXTRA_GCC_LIBS) $(LIBS) \
+ $(LIBDEPS) $(srcdir)/libdiagnostics.map
+ @$(call LINK_PROGRESS,$(INDEX.libdiagnostics),start)
+ +$(LLINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) -o $@ -shared \
+ $(libdiagnostics_OBJS) \
+ $(CPPLIB) $(EXTRA_GCC_LIBS) $(LIBS) \
+ $(LIBDIAGNOSTICS_EXTRA_OPTS)
+ @$(call LINK_PROGRESS,$(INDEX.libdiagnostics),end)
+
+# Create symlinks when not building for Windows
+ifeq (,$(findstring mingw,$(target)))
+
+ifeq (,$(findstring darwin,$(host)))
+# but only one level for Darwin, version info is embedded.
+$(LIBDIAGNOSTICS_SONAME_SYMLINK): $(LIBDIAGNOSTICS_FILENAME)
+ ln -sf $(LIBDIAGNOSTICS_FILENAME) $(LIBDIAGNOSTICS_SONAME_SYMLINK)
+endif
+
+$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK): $(LIBDIAGNOSTICS_SONAME_SYMLINK)
+ ln -sf $(LIBDIAGNOSTICS_SONAME_SYMLINK) $(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
+endif
+
# Dump a specs file to make -B./ read these specs over installed ones.
$(SPECS): xgcc$(exeext)
$(GCC_FOR_TARGET) -dumpspecs > tmp-specs
install: install-plugin
endif
+ifeq ($(enable_libdiagnostics),yes)
+install: install-libdiagnostics
+endif
+
install-strip: override INSTALL_PROGRAM = $(INSTALL_STRIP_PROGRAM)
ifneq ($(STRIP),)
install-strip: STRIPPROG = $(STRIP)
fi; \
fi
+libdiagnostics.install-headers: installdirs
+ $(INSTALL_DATA) $(srcdir)/libdiagnostics.h \
+ $(DESTDIR)$(includedir)/libdiagnostics.h
+ $(INSTALL_DATA) $(srcdir)/libdiagnostics++.h \
+ $(DESTDIR)$(includedir)/libdiagnostics++.h
+
+ifneq (,$(findstring mingw,$(target)))
+libdiagnostics.install-common: installdirs libdiagnostics.install-headers
+# Install import library
+ $(INSTALL_PROGRAM) $(LIBDIAGNOSTICS_IMPORT_LIB) \
+ $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_IMPORT_LIB)
+# Install DLL file
+ $(INSTALL_PROGRAM) $(LIBDIAGNOSTICS_FILENAME) \
+ $(DESTDIR)$(bindir)/$(LIBDIAGNOSTICS_FILENAME)
+
+else
+ifneq (,$(findstring darwin,$(host)))
+# but only one level for Darwin
+
+libdiagnostics.install-common: installdirs libdiagnostics.install-headers
+ $(INSTALL_PROGRAM) $(LIBDIAGNOSTICS_FILENAME) \
+ $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_FILENAME)
+ ln -sf \
+ $(LIBDIAGNOSTICS_SONAME_SYMLINK)\
+ $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
+
+else
+libdiagnostics.install-common: installdirs libdiagnostics.install-headers
+ $(INSTALL_PROGRAM) $(LIBDIAGNOSTICS_FILENAME) \
+ $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_FILENAME)
+ ln -sf \
+ $(LIBDIAGNOSTICS_FILENAME) \
+ $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_SONAME_SYMLINK)
+ ln -sf \
+ $(LIBDIAGNOSTICS_SONAME_SYMLINK)\
+ $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
+endif
+endif
+
+install-libdiagnostics: libdiagnostics.install-common
+
# Install the info files.
# $(INSTALL_DATA) might be a relative pathname, so we can't cd into srcdir
# to do the install.
PICFLAG
enable_default_pie
enable_host_bind_now
+LIBDIAGNOSTICS
+enable_libdiagnostics
enable_host_pie
enable_host_shared
enable_plugin
enable_plugin
enable_host_shared
enable_host_pie
+enable_libdiagnostics
enable_host_bind_now
enable_libquadmath_support
with_linker_hash_style
--enable-plugin enable plugin support
--enable-host-shared build host code as shared libraries
--enable-host-pie build host code as PIE
+ --enable-libdiagnostics build libdiagnostics shared library
--enable-host-bind-now link host code as BIND_NOW
--disable-libquadmath-support
disable libquadmath support for Fortran
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<_LT_EOF
-#line 21459 "configure"
+#line 21463 "configure"
#include "confdefs.h"
#if HAVE_DLFCN_H
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<_LT_EOF
-#line 21565 "configure"
+#line 21569 "configure"
#include "confdefs.h"
#if HAVE_DLFCN_H
do
check_languages="$check_languages check-$language"
done
+if test x$enable_libdiagnostics = xyes; then
+ check_languages="$check_languages check-libdiagnostics"
+fi
selftest_languages=
for language in $all_selected_languages
+# Check whether --enable-libdiagnostics was given.
+if test "${enable_libdiagnostics+set}" = set; then :
+ enableval=$enable_libdiagnostics;
+fi
+
+
+
+if test "$enable_libdiagnostics" = "yes"; then
+ LIBDIAGNOSTICS='libdiagnostics'
+else
+ LIBDIAGNOSTICS=''
+fi
+
+
+
# Enable --enable-host-bind-now
# Check whether --enable-host-bind-now was given.
if test "${enable_host_bind_now+set}" = set; then :
do
check_languages="$check_languages check-$language"
done
+if test x$enable_libdiagnostics = xyes; then
+ check_languages="$check_languages check-libdiagnostics"
+fi
selftest_languages=
for language in $all_selected_languages
[build host code as PIE])])
AC_SUBST(enable_host_pie)
+AC_ARG_ENABLE(libdiagnostics,
+[AS_HELP_STRING([--enable-libdiagnostics],
+ [build libdiagnostics shared library])])
+AC_SUBST(enable_libdiagnostics)
+
+if test "$enable_libdiagnostics" = "yes"; then
+ LIBDIAGNOSTICS='libdiagnostics'
+else
+ LIBDIAGNOSTICS=''
+fi
+AC_SUBST(LIBDIAGNOSTICS)
+
+
# Enable --enable-host-bind-now
AC_ARG_ENABLE(host-bind-now,
[AS_HELP_STRING([--enable-host-bind-now],
m_show_nesting_levels = show_nesting_levels;
}
+ label_text get_location_text (const expanded_location &s) const;
+
protected:
void print_any_cwe (const diagnostic_info &diagnostic);
void print_any_rules (const diagnostic_info &diagnostic);
void print_option_information (const diagnostic_info &diagnostic,
diagnostic_t orig_diag_kind);
- label_text get_location_text (const expanded_location &s) const;
bool includes_seen_p (const line_map_ordinary *map);
/* For handling diagnostic_buffer. */
still be built (see @option{--disable-libvtv} to turn off building libvtv).
@option{--disable-vtable-verify} is the default.
+@item --enable-libdiagnostics
+Specify whether to build @code{libdiagnostics}, a shared library exposing
+GCC's diagnostics capabilities via a C API, and a C++ wrapper API adding
+``syntactic sugar''.
+
+This option requires @option{--enable-host-shared} on non-Windows hosts.
+
@item --disable-gcov
Specify that the run-time library used for coverage analysis
and associated host tools should not be built.
--- /dev/null
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
--- /dev/null
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = 'libdiagnostics'
+copyright = '2024, David Malcolm'
+author = 'David Malcolm'
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = []
+
+templates_path = ['_templates']
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = 'alabaster'
+html_static_path = ['_static']
--- /dev/null
+.. Copyright (C) 2024 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/>.
+
+libdiagnostics
+==============
+
+This document describes `libdiagnostics <https://gcc.gnu.org/wiki/libdiagnostics>`_,
+an API for programs to use to emit diagnostics (such as for "lint"-style checker
+tools), supporting:
+
+* text output similar to GCC's errors and warnings::
+
+ test-typo.c:19:13: error: unknown field 'colour'
+ 19 | return p->colour;
+ | ^~~~~~
+
+ quoting pertinent source code (with a cache), and underlining
+ :doc:`points and ranges in the files being tested <tutorial/02-physical-locations>`,
+ possibly with labels::
+
+ test-labelled-ranges.c:9:6: error: mismatching types: 'int' and 'const char *'
+ 19 | 42 + "foo"
+ | ~~ ^ ~~~~~
+ | | |
+ | int const char *
+
+* emitting :doc:`fix-it hints <tutorial/06-fix-it-hints>`::
+
+ test-fix-it-hint.c:19:13: error: unknown field 'colour'; did you mean 'color'
+ 19 | return p->colour;
+ | ^~~~~~
+ | color
+
+ and generating patches from them::
+
+ @@ -16,7 +16,7 @@
+ struct rgb
+ get_color (struct object *p)
+ {
+ - return p->colour;
+ + return p->color;
+ }
+
+* capturing :doc:`execution paths<tutorial/07-execution-paths>` through code::
+
+ 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
+
+* support for emitting machine-readable representations of the above
+ using the :doc:`SARIF file format <topics/sarif>`
+
+There are actually two APIs for the library:
+
+* a pure C API: ``libdiagnostics.h``
+
+* a C++ wrapper API: ``libdiagnostics+.h``. This is a header-only
+ collection of wrapper classes around the C API to give a less
+ verbose API.
+
+This documentation covers the C API.
+
+Contents
+********
+
+.. toctree::
+ :maxdepth: 2
+
+ tutorial/index.rst
+ topics/index.rst
+
+libdiagnostics 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.
+
+
+Indices and tables
+******************
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
--- /dev/null
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.https://www.sphinx-doc.org/
+ exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
--- /dev/null
+.. Copyright (C) 2024 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
+
+Diagnostic Managers
+===================
+
+.. type:: diagnostic_manager;
+
+A :type:`diagnostic_manager` is an opaque bundle of state for a client of
+libdiagnostics.
+
+It has zero of more "output sinks" to which diagnostics are emitted.
+
+Responsibilities include:
+
+* location-management
+
+* caching of source file content
+
+* patch generation
+
+.. function:: diagnostic_manager *diagnostic_manager_new (void)
+
+ Create a new diagnostic_manager.
+ The caller will need to call :func:`diagnostic_release_manager`
+ on it at some point.
+
+ .. note:: No output sinks are created by default; so you will want
+ to create one with something like:
+
+ .. code-block::
+
+ diagnostic_manager_add_text_sink (diag_mgr, stderr,
+ DIAGNOSTIC_COLORIZE_IF_TTY);
+
+.. function:: void diagnostic_manager_release (diagnostic_manager *diag_mgr)
+
+ Release a diagnostic_manager.
+
+ This will flush output to all of the output sinks, and clean up.
+
+ The parameter must be non-NULL.
--- /dev/null
+.. Copyright (C) 2024 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
+
+Diagnostics
+===========
+
+.. type:: diagnostic
+
+A :type:`diagnostic` is an opaque bundle of state for a particular
+diagnostic that is being constructed in memory.
+
+
+Lifecycle of a diagnostic
+*************************
+
+Diagnostics are
+
+* *created* from a :type:`diagnostic_manager` by using
+ :func:`diagnostic_begin`, then
+
+* *populated* with data, such as physical locations, logical locations,
+ metadata, execution paths, or fix-it hints, then
+
+* *finished*, in which a formatting string and arguments are given,
+ via a call to :func:`diagnostic_finish` or :func:`diagnostic_finish_va`.
+ The :type:`diagnostic_manager` will emit the diagnostic to all of the
+ manager's output sinks (either immediately, or at some later time,
+ depending on the sink).
+
+ Once a :type:`diagnostic` has had one of these "finish" functions called
+ on it, it is freed, and is no longer valid for use.
+
+ The formatting strings use their own syntax; see :doc:`message-formatting`.
+
+.. function:: diagnostic *diagnostic_begin (diagnostic_manager *diag_mgr, \
+ enum diagnostic_level level)
+
+ Create a new :type:`diagnostic` associated with the given
+ :type:`diagnostic_manager`.
+
+ The parameter ``diag_mgr`` must be non-NULL.
+
+ The parameter ``level`` describes the severity of the diagnostic.
+
+.. enum:: diagnostic_level
+
+ This enum describes the severity of a particular diagnostic.
+
+ .. macro:: DIAGNOSTIC_LEVEL_ERROR
+
+ A problem sufficiently severe that the program cannot successfully
+ complete, or where the input being analyzed is definitely wrong
+ (e.g. malformed).
+
+ .. macro:: DIAGNOSTIC_LEVEL_WARNING
+
+ A problem where the input is technically correct, but is likely
+ not what the user intended, such as common mistakes, or other
+ unusual conditions that *may* indicate trouble, such as use of
+ obsolete features.
+
+ .. macro:: DIAGNOSTIC_LEVEL_NOTE
+
+ A supplementary message added to another :type:`diagnostic`, giving
+ extra information that may help the user understand it.
+
+ .. macro:: DIAGNOSTIC_LEVEL_SORRY
+
+ A problem where the input is valid, but the tool isn't
+ able to handle it.
+
+.. function:: void diagnostic_finish (diagnostic *diag, const char *fmt, ...)
+
+ Emit ``diag`` to all sinks of its manager, and release ``diag``. It is not
+ valid to use ``diag`` after this call.
+
+ Use parameter ``fmt`` for the message.
+ Note that this uses gcc's pretty-print format, which is *not* printf.
+ See :doc:`message-formatting`.
+
+ Both ``diag`` and ``fmt`` must be non-NULL.
+
+ TODO: who is responsible for putting FMT through gettext?
+
+.. function:: void diagnostic_finish_va (diagnostic *diag, const char *fmt, va_list *args)
+
+ This is equivalent to :func:`diagnostic_finish`, but using a
+ :type:`va_list` rather than directly taking variadic arguments.
+
+ All three parameters must be non-NULL.
+
+
+Diagnostic groups
+*****************
+
+See :doc:`the "adding notes" section of the tutorial <../tutorial/04-notes>`
+for an example of a diagnostic group.
+
+.. function:: void diagnostic_manager_begin_group (diagnostic_manager *diag_mgr)
+
+ Begin a diagnostic group. All diagnostics emitted within
+ ``diag_mgr`` after the first one will be treated as additional information
+ relating to the initial diagnostic.
+
+ The parameter ``diag_mgr`` must be non-NULL.
+
+.. function:: void diagnostic_manager_end_group (diagnostic_manager *diag_mgr)
+
+ Finish a diagnostic group.
+
+ The parameter ``diag_mgr`` must be non-NULL.
--- /dev/null
+.. Copyright (C) 2024 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
+
+Execution paths
+===============
+
+.. type:: diagnostic_execution_path
+
+A :type:`diagnostic` can optionally contain a :type:`diagnostic_execution_path`
+describing a path of execution through code.
+
+.. function:: diagnostic_execution_path * diagnostic_add_execution_path (diagnostic *diag)
+
+ Create and borrow a pointer to an execution path for ``diag``.
+
+ The path is automatically cleaned up when ``diag`` is finished.
+
+ ``diag`` must be non-NULL.
+
+.. function:: diagnostic_execution_path * diagnostic_manager_new_execution_path (diagnostic_manager *diag_mgr)
+
+ Create a new execution path. This is owned by the caller and must have either
+ :func:`diagnostic_take_execution_path` or
+ :func:`diagnostic_execution_path_release` called on it.
+
+ ``diag_mgr`` must be non-NULL.
+
+.. function:: void diagnostic_take_execution_path (diagnostic *diag, diagnostic_execution_path *path)
+
+ Set ``diag`` to use ``path`` as its execution path, taking ownership of ``path``.
+
+ Both parameters must be non-NULL.
+
+.. function:: void diagnostic_execution_path_release (diagnostic_execution_path *path)
+
+ Release ownership of ``path``, which must not have been taken by a diagnostic.
+
+.. type:: diagnostic_event_id
+
+A :type:`diagnostic_event_id` identifies a particular event within a
+:type:`diagnostic_execution_path` and can be used for expressing
+cross-references between events. In particular FIXME
+
+.. function:: diagnostic_event_id diagnostic_execution_path_add_event (diagnostic_execution_path *path, \
+ const diagnostic_physical_location *physical_loc, \
+ const diagnostic_logical_location *logical_loc, \
+ unsigned stack_depth, \
+ const char *fmt, ...)
+
+ Append an event to the end of ``path``, which must be non-NULL.
+
+ ``physical_loc`` can be NULL, or non-NULL to associate the event
+ with a :type:`diagnostic_physical_location`.
+
+ ``logical_loc`` can be NULL, or non-NULL to associate the event
+ with a :type:`diagnostic_logical_location`.
+
+ ``stack_depth`` is for use in interprocedural paths and identifies the
+ depth of the stack at the event. Purely intraprocedural paths should
+ use a stack depth of 1 for their events
+
+ ``fmt`` must be non-NULL. See :doc:`message-formatting` for details of
+ how to use it.
+
+.. function:: diagnostic_event_id diagnostic_execution_path_add_event_va (diagnostic_execution_path *path, \
+ const diagnostic_physical_location *physical_loc, \
+ const diagnostic_logical_location *logical_loc, \
+ unsigned stack_depth, \
+ const char *fmt, \
+ va_list *args)
+
+ Equivalent to :func:`diagnostic_execution_path_add_event`, but using a
+ :type:`va_list` rather than directly taking variadic arguments.
+
+Paths are printed to text sinks, and for SARIF sinks each path is 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>`_).
--- /dev/null
+.. Copyright (C) 2024 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
+
+Fix-it hints
+============
+
+Adding fix-it hints to a diagnostic
+***********************************
+
+A :type:`diagnostic` can contain "fix-it hints", giving suggestions
+for the user on how to edit their code to fix a problem. These
+can be expressed as insertions, replacements, and removals of text.
+
+There is only limited support for newline characters in fix-it hints:
+only hints with newlines which insert an entire new line are permitted,
+inserting at the start of a line, and finishing with a newline
+(with no interior newline characters). Other attempts to add
+fix-it hints containing newline characters will fail.
+Similarly, attempts to delete or replace a range *affecting* multiple
+lines will fail.
+
+The API handles these failures gracefully, so that diagnostics can attempt
+to add fix-it hints without each needing extensive checking.
+
+Fix-it hints are printed to text sinks, and are emitted by SARIF sinks
+as ``fix`` objects (see SARIF 2.1.0
+`3.55 fix object <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141791131>`_).
+
+Fix-it hints within a :type:`diagnostic` are "atomic": if any hints can't
+be applied, none of them will be, and no fix-its hints will be displayed
+for that diagnostic. This implies that diagnostic messages need to be worded
+in such a way that they make sense whether or not the fix-it hints
+are displayed.
+
+All fix-it hints within one :type:`diagnostic` must affect the same
+:type:`diagnostic_file`.
+
+.. function:: void diagnostic_add_fix_it_hint_insert_before (diagnostic *diag, \
+ const diagnostic_physical_location *loc, \
+ const char *addition)
+
+ Add a fix-it hint to ``diag`` suggesting the insertion of the string
+ ``addition`` before ``LOC``.
+
+ For example::
+
+ ptr = arr[0];
+ ^~~~~~
+ &
+
+ This :type:`diagnostic` has a single location covering ``arr[0]``,
+ with the caret at the start. It has a single insertion fix-it hint,
+ inserting ``&`` before the start of ``loc``.
+
+.. function:: void diagnostic_add_fix_it_hint_insert_after (diagnostic *diag, \
+ const diagnostic_physical_location *loc, \
+ const char *addition)
+
+ Add a fix-it hint to ``diag`` suggesting the insertion of the string
+ ``addition`` after the end of ``LOC``.
+
+ For example, in::
+
+ #define FN(ARG0, ARG1, ARG2) fn(ARG0, ARG1, ARG2)
+ ^~~~ ^~~~ ^~~~
+ ( ) ( ) ( )
+
+
+ the :type:`diagnostic` has three physical locations, covering ``ARG0``,
+ ``ARG1``, and ``ARG2``, and 6 insertion fix-it hints: each arg
+ has a pair of insertion fix-it hints, suggesting wrapping
+ them with parentheses: one a '(' inserted before,
+ the other a ')' inserted after.
+
+.. function:: void diagnostic_add_fix_it_hint_replace (diagnostic *diag, \
+ const diagnostic_physical_location *loc, \
+ const char *replacement)
+
+ Add a fix-it hint to ``diag`` suggesting the replacement of the text
+ at ``LOC`` with the string ``replacement``.
+
+ For example, in::
+
+ c = s.colour;
+ ^~~~~~
+ color
+
+ This :type:`diagnostic` has a single physical location covering ``colour``,
+ and a single "replace" fix-it hint, covering the same range, suggesting
+ replacing it with ``color``.
+
+.. function:: void diagnostic_add_fix_it_hint_delete (diagnostic *diag, \
+ const diagnostic_physical_location *loc)
+
+ Add a fix-it hint to ``diag`` suggesting the deletion of the text
+ at ``LOC``.
+
+
+ For example, in::
+
+ struct s {int i};;
+ ^
+ -
+
+ This :type:`diagnostic` has a single physical location at the stray
+ trailing semicolon, along with a single removal fix-it hint, covering
+ the same location.
+
+
+Generating patches
+******************
+
+.. function:: void diagnostic_manager_write_patch (diagnostic_manager *diag_mgr, \
+ FILE *dst_stream)
+
+ Write a patch to ``dst_stream`` consisting of the effect of all fix-it hints
+ on all diagnostics that have been finished on ``diag_mgr``.
+
+ Both parameters must be non-NULL.
--- /dev/null
+.. Copyright (C) 2024 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
+
+
+Topic reference
+===============
+
+.. toctree::
+ :maxdepth: 2
+
+ retrofitting.rst
+ diagnostic-manager.rst
+ diagnostics.rst
+ message-formatting.rst
+ physical-locations.rst
+ logical-locations.rst
+ metadata.rst
+ fix-it-hints.rst
+ execution-paths.rst
+ text-output.rst
+ sarif.rst
+ ux.rst
--- /dev/null
+.. Copyright (C) 2024 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
+
+Logical locations
+=================
+
+A "logical" location is a location expressed in terms of
+construct in a programming language, such as ``within function 'foo'``
+(as opposed to a :doc:`"physical" location <physical-locations>`, which
+refers to a specific file, and line(s) and/or column(s))
+
+Creating location information
+*****************************
+
+.. type:: diagnostic_logical_location
+
+A :type:`diagnostic_logical_location` is an opaque type describing a "logical"
+source location
+
+.. function:: const diagnostic_logical_location * diagnostic_manager_new_logical_location (diagnostic_manager *diag_mgr, \
+ enum diagnostic_logical_location_kind_t kind, \
+ const diagnostic_logical_location *parent, \
+ const char *short_name, \
+ const char *fully_qualified_name, \
+ const char *decorated_name)
+
+ Create a :type:`diagnostic_logical_location`.
+
+ ``diag_mgr`` must be non-NULL.
+
+ ``kind`` describes the kind of logical location:
+
+ .. enum:: diagnostic_logical_location_kind_t
+
+ This roughly corresponds to the ``kind`` property in SARIF v2.1.0
+ (`§3.33.7 <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790976>`_).
+
+ .. macro:: DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION
+
+ .. macro:: DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER
+
+ .. macro:: DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE
+
+ .. macro:: DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE
+
+ .. macro:: DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE
+
+ .. macro:: DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE
+
+ .. macro:: DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER
+
+ .. macro:: DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE
+
+ ``parent`` can be NULL; if non-NULL it can be used to express tree-like
+ nesting of logical locations, such as in::
+
+ namespace foo { namespace bar { class baz { baz (); }; } }
+
+ where a diagnostic within ``baz``'s constructor could be reported
+ as being within ``foo::bar::baz::baz`` where the logical locations
+ are two namespaces, a type, and a member, respectively.
+
+ ``short_name`` can be NULL, or else a string suitable for use by
+ the SARIF logicalLocation ``name`` property
+ (SARIF v2.1.0 `§3.33.4 <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790973>`_).
+
+ ``fully_qualified_name`` can be NULL or else a string suitable for use by
+ the SARIF logicalLocation ``fullyQualifiedName`` property
+ (SARIF v2.1.0 `§3.33.5 <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790974>`_).
+
+ ``decorated_name`` can be NULL or else a string suitable for use by
+ the SARIF logicalLocation ``decoratedName`` property
+ (SARIF v2.1.0 `§3.33.6 <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790975>`_).
+
+.. function:: void diagnostic_manager_debug_dump_logical_location (const diagnostic_manager *diag_mgr, \
+ const diagnostic_logical_location *loc, \
+ FILE *out)
+
+ Write a representation of ``file`` to ``out``, for debugging.
+ Both ``diag_mgr`` and ``out`` must be non-NULL.
+ ``file`` may be NULL.
+
+ TODO: example of output
+
+Associating diagnostics with locations
+**************************************
+
+.. function:: void diagnostic_set_logical_location (diagnostic *diag, \
+ const diagnostic_logical_location *logical_loc)
+
+ Set the logical location of ``diag``.
+
+ ``diag`` must be non-NULL; ``logical_loc`` can be NULL.
--- /dev/null
+.. Copyright (C) 2024 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
+
+Message formatting
+==================
+
+Various libdiagnostics entrypoints take a format string and
+variadic arguments.
+
+The format strings take codes prefixed by ``%``, or ``%q`` to put
+the result in quotes. For example::
+
+ "hello %s", "world"
+
+would print::
+
+ hello world
+
+whereas::
+
+ "hello %qs", "world"
+
+would print::
+
+ hello `world'
+
+where ```world'`` would be displayed in bold if colorization were enabled
+in the terminal.
+
+The following format specifiers are accepted:
+
+
+Numbers
+*******
+
+``d`` and ``i`` (``signed int``), ``u`` (``unsigned int``)
+ ``%d``, ``%i``, and ``%u`` print integers in base ten. For example::
+
+ "the answer is %i", 42
+
+ would print::
+
+ the answer is 42
+
+``o`` (``unsigned int``)
+ Print the integer in base eight
+
+``x`` (``unsigned int``)
+ Print the integer in base sixteen
+
+The above can be prefixed with ``l`` and ``ll`` prefixes to take
+``long`` and ``long long`` values of the appropriate signedness.
+
+For example::
+
+ "address: %lx", (unsigned long)0x108b516
+
+would print::
+
+ address: 108b516
+
+Similarly, the prefix ``z`` can be used for ``size_t``::
+
+ "size: %zd", sizeof(struct foo)
+ size: 32
+
+and ``t`` for ptrdiff_t.
+
+``f`` (``double``)
+ ``%f`` prints a floating-point value. For example::
+
+ "value: %f", 1.0
+
+ might print::
+
+ value: 1.000000
+
+
+Strings
+*******
+
+``c`` (``char``)
+ ``%c`` prints a single character.
+
+``s`` (``const char *``)
+ ``%s`` prints a string.
+
+ Note that if the string refers to something that might
+ appear in the input file (such as the name of a function), it's better
+ to quote the value; for example::
+
+ "unrecognized identifier: %qs", "foo"
+
+ might print::
+
+ unrecognized identifier: `foo'
+
+``m`` (no argument)
+ Prints ``strerror(errno)``, for example::
+
+ "can't open %qs: %m"
+
+ might print::
+
+ can't open `foo.txt': No such file or directory
+
+``%`` (no argument)
+ ``%%`` prints a `%` character, for example::
+
+ "8%% of 75 is 75%% of 8, and is thus 6"
+
+ prints::
+
+ 8% of 75 is 75% of 8, and is thus 6
+
+``'`` (no argument)
+ ``%'`` prints an apostrophe. This should only be used in untranslated messages;
+ translations should use appropriate punctuation directly.
+
+
+Other format specifiers
+***********************
+
+``p`` (pointer)
+ ``%p`` prints a pointer, although the precise format is
+ implementation-defined.
+
+``r`` (``const char *``)
+ ``%r`` starts colorization on suitable text sinks, where the argument
+ specifies the name of the kind of entity to be colored, such as ``error``.
+
+``R`` (no argument)
+ ``%R`` stops colorization
+
+``<`` and ``>`` (no arguments)
+ ``%<`` adds an opening quote and ``%>`` a closing quote, such as::
+
+ "missing element %<%s:%s%>", ns, name
+
+ which might be printed as::
+
+ missing element `xhtml:head'
+
+ If the thing to be quoted can be handled with another format specifier,
+ then it's simpler to use ``q`` with it. For example, it's much
+ simpler to print a ``const char *`` in quotes via::
+
+ "%qs", str
+
+ rather than the error-prone::
+
+ "%<%s%>", str
+
+``{`` (``const char *``)
+ ``%{`` starts a link; the argument is the URL. This will be displayed
+ in a suitably-capable terminal if a text sink is directly connected to
+ a tty, and will be captured in SARIF output.
+
+``}`` (no argument)
+ ``%}`` stops a link started with ``%{``.
+
+ For example::
+
+ "for more information see %{the documentation%}", "https://example.com"
+
+ would be printed as::
+
+ for more information see the documentation
+
+ with the URL emitted in suitable output sinks.
+
+``@`` (``diagnostic_event_id *``)
+ ``%@`` prints a reference to an event in a
+ :type:`diagnostic_execution_path`, where the :type:`diagnostic_event_id`
+ is passed by pointer.
+
+ For example, if ``event_id`` refers to the first event in a path, then::
+
+ "double-%qs of %qs; first %qs was at %@",
+ function, ptr, function, &event_id
+
+ might print::
+
+ double-`free' of `p'; first `free` was at (1)
+
+.. :
+
+ TODO:
+
+ %.*s: a substring the length of which is specified by an argument
+ integer.
+ %Ns: likewise, but length specified as constant in the format string.
+ %Z: Requires two arguments - array of int, and len. Prints elements
+ of the array.
+
+ %e: Consumes a pp_element * argument.
+
+ Arguments can be used sequentially, or through %N$ resp. *N$
+ notation Nth argument after the format string. If %N$ / *N$
+ notation is used, it must be used for all arguments, except %m, %%,
+ %<, %>, %} and %', which may not have a number, as they do not consume
+ an argument. When %M$.*N$s is used, M must be N + 1. (This may
+ also be written %M$.*s, provided N is not otherwise used.) The
+ format string must have conversion specifiers with argument numbers
+ 1 up to highest argument; each argument may only be used once.
+ A format string can have at most 30 arguments. */
+
+
--- /dev/null
+.. Copyright (C) 2024 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
+
+Adding metadata
+===============
+
+Tool metadata
+*************
+
+It's possible to set up various metadata on the :type:`diagnostic_manager`
+as a whole, describing the program creating the diagnostics.
+
+.. note::
+
+ It's not required to set up any of this up on a
+ :type:`diagnostic_manager`. However, if you are doing
+ :doc:`SARIF output <sarif>`, then you need to at least call
+ :func:`diagnostic_manager_set_tool_name` or the generated ``.sarif``
+ file will not validate against the schema.
+
+.. function:: void diagnostic_manager_set_tool_name (diagnostic_manager *diag_mgr, \
+ const char *value)
+
+ Set a string for the name of the tool emitting the diagnostics.
+
+ Both parameters must be non-NULL.
+
+ If set, this string will be used
+
+ * by :doc:`text output sinks <text-output>` as a prefix for output
+ when no physical location is available, replacing ``progname``
+ in the following:
+
+ .. code-block:: console
+
+ $ ./tut01-hello-world
+ progname: error: I'm sorry Dave, I'm afraid I can't do that
+
+ * by :doc:`SARIF output sinks <sarif>` as the value for the
+ ``name`` property of the ``driver``
+ (`SARIF v2.1.0 §3.19.8 <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790791>`_).
+
+.. function:: void diagnostic_manager_set_full_name (diagnostic_manager *diag_mgr, \
+ const char *value)
+
+ Set a string giving the name of the tool along with the its version and
+ other useful information::
+
+ diagnostic_manager_set_full_name (diag_mgr, "FooChecker 0.1 (en_US)");
+
+ If set, this string will be used by :doc:`SARIF output sinks <sarif>` as
+ the value for the ``fullName`` property of the ``driver``
+ (`SARIF v2.1.0 §3.19.9 <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790792>`_).
+
+ Both parameters must be non-NULL.
+
+.. function:: void diagnostic_manager_set_version_string (diagnostic_manager *diag_mgr, \
+ const char *value)
+
+ Set a string suitable for use as the value of the SARIF ``version`` property
+ of the ``driver``.
+ (`SARIF v2.1.0 §3.19.13 <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790796>`_)::
+
+ diagnostic_manager_set_version_string (diag_mgr, "0.1");
+
+ Both parameters must be non-NULL.
+
+.. function:: void diagnostic_manager_set_version_url (diagnostic_manager *diag_mgr, \
+ const char *value)
+
+ Set a string suitable for use as the value of the SARIF ``informationUri``
+ property of the ``driver``.
+ (`SARIF v2.1.0 §3.19.17 <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790800>`_)::
+
+ diagnostic_manager_set_version_url (diag_mgr,
+ "https://www.example.com/foo-checker/releases/0.1/");
+
+ Both parameters must be non-NULL.
+
+Adding metadata to a diagnostic
+*******************************
+
+.. function:: void diagnostic_set_cwe (diagnostic *diag, \
+ unsigned cwe_id)
+
+ Associate ``diag`` with the given ID within
+ the `Common Weakness Enumeration <https://cwe.mitre.org/>`_::
+
+ /* CWE-242: Use of Inherently Dangerous Function. */
+ diagnostic_set_cwe (d, 242);
+
+ ``diag`` must be non-NULL.
+
+ The CWE value will be printed by text sinks after the message::
+
+ test-metadata.c:21:3: warning: never use 'gets' [CWE-242]
+
+ and in a sufficiently-capable terminal will be a link to
+ documentation about the CWE.
+
+.. function:: void diagnostic_add_rule (diagnostic *diag, \
+ const char *title, \
+ const char *url)
+
+ Associate this :type:`diagnostic` with a particular rule that has been
+ violated (such as in a coding standard, or within a specification).
+
+ A diagnostic can be associated with zero or more rules.
+
+ ``diag`` must be non-NULL. The rule must have at least one of a
+ title and a URL, but these can be NULL.
+
+ For example, given::
+
+ diagnostic_add_rule (d,
+ "MSC24-C",
+ "https://wiki.sei.cmu.edu/confluence/display/c/MSC24-C.+Do+not+use+deprecated+or+obsolescent+functions");
+
+ the rule name will be printed by text sinks after the message::
+
+ test-metadata.c:21:3: warning: never use 'gets' [MSC24-C]
+ 21 | gets (buf);
+ | ^~~~~~~~~~
+
+ and if so, the URL will be available in a sufficiently capable
+ terminal.
+
+ This can be used in conjunction with :func:`diagnostic_set_cwe`,
+ giving output like this::
+
+ test-metadata.c:21:3: warning: never use 'gets' [CWE-242] [MSC24-C]
+ 21 | gets (buf);
+ | ^~~~~~~~~~
--- /dev/null
+.. Copyright (C) 2024 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
+
+Physical locations
+==================
+
+A "physical" source location is a location expressed in terms of
+a specific file, and line(s) and column(s) (as opposed to a
+:doc:`"logical" location <logical-locations>`,
+which refers to semantic constructs in a programming language).
+
+Creating location information
+*****************************
+
+The :type:`diagnostic_manager` manages various objects relating to
+locations.
+
+.. type:: diagnostic_file
+
+ A :type:`diagnostic_file` is an opaque type describing a particular input file.
+
+.. function:: const diagnostic_file * diagnostic_manager_new_file (diagnostic_manager *diag_mgr, \
+ const char *name, \
+ const char *sarif_source_language)
+
+ Create a new :type:`diagnostic_file` for file ``name``. Repeated calls
+ with strings that match ``name`` will return the same object.
+
+ Both ``diag_mgr`` and ``name`` must be non-NULL.
+
+ If ``sarif_source_language`` is non-NULL, it specifies a
+ ``sourceLanguage`` value for the file for use when writing
+ :doc:`SARIF <sarif>`
+ (`SARIF v2.1.0 §3.24.10 <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790871>`_).
+ See
+ `SARIF v2.1.0 Appendix J <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141791197>`_
+ for suggested values for various programmming languages.
+
+ For example, this creates a :type:`diagnostic_file` for ``foo.c``
+ and identifies it as C source code::
+
+ foo_c = diagnostic_manager_new_file (diag_mgr,
+ "foo.c",
+ "c" /* source_language */);
+
+.. function:: void diagnostic_manager_debug_dump_file (diagnostic_manager *diag_mgr, \
+ const diagnostic_file *file, \
+ FILE *out)
+
+ Write a representation of ``file`` to ``out``, for debugging.
+ Both ``diag_mgr`` and ``out`` must be non-NULL.
+ `file`` may be NULL.
+
+ For example::
+
+ diagnostic_manager_debug_dump_file (diag_mgr, foo_c, stderr);
+
+ might lead to this output::
+
+ file(name="foo.c", sarif_source_language="c")
+
+.. type:: diagnostic_line_num_t
+
+A :type:`diagnostic_line_num_t` is used for representing line numbers
+within text files. libdiagnostics treats the first line of a text file
+as line 1.
+
+.. type:: diagnostic_column_num_t
+
+A :type:`diagnostic_column_num_t` is used for representing column numbers
+within text files. libdiagnostics treats the first column of a text line
+as column 1, **not** column 0.
+
+.. note::
+
+ Both libdiagnostics and Emacs number source *lines* starting at 1, but
+ they have differing conventions for *columns*.
+
+ libdiagnostics uses a 1-based convention for source columns,
+ whereas Emacs's ``M-x column-number-mode`` uses a 0-based convention.
+
+ For example, an error in the initial, left-hand
+ column of source line 3 is reported by libdiagnostics as::
+
+ some-file.c:3:1: error: ...etc...
+
+ On navigating to the location of that error in Emacs
+ (e.g. via ``next-error``),
+ the locus is reported in the Mode Line
+ (assuming ``M-x column-number-mode``) as::
+
+ some-file.c 10% (3, 0)
+
+ i.e. ``3:1:`` in libdiagnostics corresponds to ``(3, 0)`` in Emacs.
+
+.. type:: diagnostic_physical_location
+
+A :type:`diagnostic_physical_location` is an opaque type representing a
+key into a database of source locations within a :type:`diagnostic_manager`.
+
+:type:`diagnostic_physical_location` instances are created by various API
+calls into the :type:`diagnostic_manager` expressing source code points
+and ranges.
+
+They persist until the :type:`diagnostic_manager` is released, which
+cleans them up.
+
+A ``NULL`` value means "unknown", and can be returned by the
+:type:`diagnostic_manager` as a fallback when a problem occurs
+(e.g. too many locations).
+
+A :type:`diagnostic_physical_location` can be a single point within the
+source code, such as here (at the the '"' at the start of the string literal)::
+
+ int i = "foo";
+ ^
+
+or be a range with a start and finish, and a "caret" location::
+
+ a = (foo && bar)
+ ~~~~~^~~~~~~
+
+where the caret here is at the first "&", and the start and finish
+are at the parentheses.
+
+.. function:: const diagnostic_physical_location *diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr, \
+ const diagnostic_file *file, \
+ diagnostic_line_num_t line_num)
+
+ Attempt to create a :type:`diagnostic_physical_location` representing
+ ``FILENAME:LINE_NUM``, with no column information (thus representing
+ the whole of the given line.
+
+ Both ``diag_mgr`` and ``file`` must be non-NULL.
+
+.. function:: const diagnostic_physical_location * diagnostic_manager_new_location_from_file_line_column (diagnostic_manager *diag_mgr, \
+ const diagnostic_file *file, \
+ diagnostic_line_num_t line_num, \
+ diagnostic_column_num_t column_num)
+
+ Attempt to create a :type:`diagnostic_physical_location` for
+ ``FILENAME:LINE_NUM:COLUMN_NUM`` representing a particular point
+ in the source file.
+
+ Both ``diag_mgr`` and ``file`` must be non-NULL.
+
+.. function:: const diagnostic_physical_location *diagnostic_manager_new_location_from_range (diagnostic_manager *diag_mgr,\
+ const diagnostic_physical_location *loc_caret,\
+ const diagnostic_physical_location *loc_start,\
+ const diagnostic_physical_location *loc_end)
+
+ Attempt to create a diagnostic_physical_location representing a
+ range within a source file, with a highlighted "caret" location.
+
+ All must be within the same file, but they can be on different lines.
+
+ For example, consider the location of the binary expression below::
+
+ ...|__________1111111112222222
+ ...|12345678901234567890123456
+ ...|
+ 521|int sum (int foo, int bar)
+ 522|{
+ 523| return foo + bar;
+ ...| ~~~~^~~~~
+ 524|}
+
+ The location's caret is at the "+", line 523 column 15, but starts
+ earlier, at the "f" of "foo" at column 11. The finish is at the "r"
+ of "bar" at column 19.
+
+ ``diag_mgr`` must be non-NULL.
+
+.. function:: void diagnostic_manager_debug_dump_location (const diagnostic_manager *diag_mgr,\
+ const diagnostic_physical_location *loc, \
+ FILE *out)
+
+ Write a representation of ``loc`` to ``out``, for debugging.
+
+ Both ``diag_mgr`` and ``out`` must be non-NULL.
+ `loc`` may be NULL.
+
+ TODO: example of output
+
+Associating diagnostics with locations
+**************************************
+
+A :type:`diagnostic` has an optional primary physical location
+and zero or more secondary physical locations. For example::
+
+ a = (foo && bar)
+ ~~~~~^~~~~~~
+
+This diagnostic has a single :type:`diagnostic_physical_location`,
+with the caret at the first "&", and the start/finish at the parentheses.
+
+Contrast with::
+
+ a = (foo && bar)
+ ~~~ ^~ ~~~
+
+This diagnostic has three locations
+
+* The primary location (at "&&") has its caret and start location at
+ the first "&" and end at the second "&.
+
+* The secondary location for "foo" has its start and finish at the "f"
+ and "o" of "foo"; the caret is not displayed, but is perhaps at
+ the "f" of "foo".
+
+* Similarly, the other secondary location (for "bar") has its start and
+ finish at the "b" and "r" of "bar"; the caret is not displayed, but
+ is perhaps at the"b" of "bar".
+
+.. function:: void diagnostic_set_location (diagnostic *diag, \
+ const diagnostic_physical_location * loc)
+
+ Set the primary location of ``diag``.
+
+ ``diag`` must be non-NULL; ``loc`` can be NULL.
+
+.. function:: void diagnostic_set_location_with_label (diagnostic *diag, \
+ const diagnostic_physical_location *loc, \
+ const char *fmt, ...)
+
+ Set the primary location of ``diag``, with a label. The label is
+ formatted as per the rules FIXME
+
+ ``diag`` and ``fmt`` must be non-NULL; ``loc`` can be NULL.
+
+ See :doc:`message-formatting` for details of how to use ``fmt``.
+
+ TODO: example of use
+
+.. function:: void diagnostic_add_location (diagnostic *diag, \
+ const diagnostic_physical_location * loc)
+
+ Add a secondary location to ``diag``.
+
+ ``diag`` must be non-NULL; ``loc`` can be NULL.
+
+
+.. function:: void diagnostic_add_location_with_label (diagnostic *diag, \
+ const diagnostic_physical_location *loc, \
+ const char *text)
+
+ Add a secondary location to ``diag``, with a label. The label is
+ formatted as per the rules FIXME
+
+ ``diag`` and ``fmt`` must be non-NULL; ``loc`` can be NULL.
+
+ For example,
+
+ .. literalinclude:: ../../../testsuite/libdiagnostics.dg/test-labelled-ranges.c
+ :language: c
+ :start-after: /* begin quoted source */
+ :end-before: /* end quoted source */
+
+ might give this text output::
+
+ test-labelled-ranges.c:9:6: error: mismatching types: 'int' and 'const char *'
+ 19 | 42 + "foo"
+ | ~~ ^ ~~~~~
+ | | |
+ | int const char *
--- /dev/null
+.. Copyright (C) 2024 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
+
+Adding libdiagnostics to an existing project
+============================================
+
+TODO
--- /dev/null
+.. Copyright (C) 2024 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
+
+SARIF support
+=============
+
+`SARIF <https://www.sarif.info/>`_ is a machine-readable format, originally
+designed for the output of static analysis tools, but which can be used
+for diagnostics in general.
+
+.. function:: void diagnostic_manager_add_sarif_sink (diagnostic_manager *diag_mgr, \
+ FILE *dst_stream, \
+ const diagnostic_file *main_input_file, \
+ enum diagnostic_sarif_version version)
+
+ Add a new output sink to ``diag_mgr``, which writes SARIF of the given
+ version to ``dst_stream``.
+
+ The output is not written until ``diag_mgr`` is released.
+
+ ``dst_stream`` is borrowed, and must outlive ``diag_mgr``.
+
+ For the result to be a valid SARIF file according to the schema,
+ ``diag_mgr`` must have had :func:`diagnostic_manager_set_tool_name`
+ called on it.
+
+ ``diag_mgr``, ``dst_stream``, and ``main_input_file`` must all be non-NULL.
+
+ .. enum:: diagnostic_sarif_version
+
+ An enum for choosing the SARIF version for a SARIF output sink.
+
+ .. macro:: DIAGNOSTIC_SARIF_VERSION_2_1_0
+
+ .. macro:: DIAGNOSTIC_SARIF_VERSION_2_2_PRERELEASE
--- /dev/null
+.. Copyright (C) 2024 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
+
+Text output
+===========
+
+.. type:: diagnostic_text_sink
+
+.. function:: diagnostic_text_sink * diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr,\
+ FILE *dst_stream, \
+ enum diagnostic_colorize colorize)
+
+ Add a new output sink to ``diag_mgr``, which writes GCC-style diagnostics
+ to ``dst_stream``.
+ Return a borrowed pointer to the sink, which is cleaned up when ``diag_mgr``
+ is released.
+
+ ``diag_mgr`` must be non-NULL.
+
+ ``dst_stream`` must be non-NULL. It is borrowed and must outlive ``DIAG_MGR``.
+
+ The output for each diagnostic is written and flushed as each
+ :type:`diagnostic` is finished.
+
+ .. enum:: diagnostic_colorize
+
+ An enum for determining if we should colorize a text output sink.
+
+ .. macro:: DIAGNOSTIC_COLORIZE_IF_TTY
+
+ Diagnostics should be colorized if the destination stream is
+ directly connected to a tty.
+
+ .. macro:: DIAGNOSTIC_COLORIZE_NO
+
+ Diagnostics should not be colorized.
+
+ .. macro:: DIAGNOSTIC_COLORIZE_YES
+
+ Diagnostics should be colorized.
+
+.. function:: void diagnostic_text_sink_set_source_printing_enabled (diagnostic_text_sink *text_sink, \
+ int value)
+
+ Enable or disable printing of source text in the text sink.
+
+ ``text_sink`` must be non-NULL.
+
+ Default: enabled.
+
+.. function:: void diagnostic_text_sink_set_colorize (diagnostic_text_sink *text_sink, \
+ enum diagnostic_colorize colorize)
+
+ Update colorization of text sink.
+
+ ``text_sink`` must be non-NULL.
+
+.. function:: void diagnostic_text_sink_set_labelled_source_colorization_enabled (diagnostic_text_sink *text_sink, \
+ int value)
+
+ ``text_sink`` must be non-NULL.
+
+ Enable or disable colorization of the characters of source text
+ that are underlined.
+
+ This should be true for clients that generate range information
+ (so that the ranges of code are colorized), and false for clients that
+ merely specify points within the source code (to avoid e.g. colorizing
+ just the first character in a token, which would look strange).
+
+ Default: enabled.
--- /dev/null
+.. Copyright (C) 2024 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
+
+User Experience
+===============
+
+Refer to
+`GCC's user experience guidelines <https://gcc.gnu.org/onlinedocs/gccint/User-Experience-Guidelines.html>`_
+for notes on
+`what makes a good diagnostic <https://gcc.gnu.org/onlinedocs/gccint/Guidelines-for-Diagnostics.html>`_.
--- /dev/null
+.. Copyright (C) 2024 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 1: "Hello world"
+==============================
+
+Before we look at the details of the API, let's look at building and
+running programs that use the library.
+
+Here's a toy program that uses libdiagnostics to emit an error message
+to stderr.
+
+ .. literalinclude:: ../../../testsuite/libdiagnostics.dg/test-example-1.c
+ :language: c
+ :start-after: /* begin quoted source */
+ :end-before: /* end quoted source */
+
+Copy the above to `tut01-hello-world.c`.
+
+Assuming you have libdiagnostics installed, build the test program
+using:
+
+.. code-block:: console
+
+ $ gcc \
+ tut01-hello-world.c \
+ -o tut01-hello-world \
+ -ldiagnostics
+
+You should then be able to run the built program:
+
+.. code-block:: console
+
+ $ ./tut01-hello-world
+ progname: error: I'm sorry Dave, I'm afraid I can't do that
+
+If stderr is connected to a terminal, you should get colorized output
+(using `SGR control codes <https://en.wikipedia.org/wiki/ANSI_escape_code>`_).
+
+.. image:: example-1.png
+
+Otherwise, the output will be plain text.
+
+Obviously a trivial example like the above could be done using ``fprintf``
+on stderr, and it's fairly easy to colorize text at the terminal.
+
+In :doc:`the next part of the tutorial <02-physical-locations>` we'll add
+file/location information to our error messages, and libdiagnostics will
+quote the pertinent parts of the file, underlining them, which is less trivial
+to reimplement. libdiagnostics gives us many other such abilities, such as
+fix-it hints and execution paths, which we'll cover in the following
+tutorials. Also, once a program's diagnostics are using libdiagnostics,
+it is trivial to add support for outputting them in
+machine-readable form as :doc:`SARIF <../topics/sarif>`.
+
+
+Structure
+*********
+
+The above example shows the typical structure of a program using
+libdiagnostics:
+
+* **initialization**: create a :type:`diagnostic_manager` instance,
+ and create an output sink for it, and other one-time initialization
+
+* **emission**: create various :type:`diagnostic` instances, populating
+ them with data, and calling "finish" once they're ready to be emitted.
+ :doc:`Text sinks <../topics/text-output>` emit their diagnostics as soon
+ as "finish" is called on them.
+
+* **cleanup**: call :func:`diagnostic_manager_release` on the
+ :type:`diagnostic_manager` to finish and free up resources.
+ :doc:`SARIF sinks <../topics/sarif>` write their output when
+ :func:`diagnostic_manager_release` is called on the manager.
+
+For non-trivial examples we'll also want to create location information,
+which could happen during initialization, or during a parsing phase of
+the program using libdiagnostics. See :doc:`02-physical-locations` for
+more information.
+
+
+Formatted messages
+******************
+
+The above example uses :func:`diagnostic_finish`, which takes a format
+string and arguments. libdiagnostics has its own style of format
+string arguments used for :func:`diagnostic_finish` and some other
+entrypoints.
+
+.. note:: The format syntax is *not* the same as ``printf``; see
+ :doc:`supported formatting options <../topics/message-formatting>`.
+
+You can use the ``q`` modifier on arguments
+to quote them, so, for example ``%qs`` is a quoted string, consuming a
+``const char *`` argument::
+
+ diagnostic_finish (d, "can't find %qs", "foo");
+
+This gives output like this:
+
+.. code-block:: console
+
+ progname: error: can't find ‘foo’
+
+where the quoted string will appear in bold in a suitably-capable
+terminal, and the quotes will be internationalized, so that e.g. with
+``LANG=fr_FR.UTF8`` we might get:
+
+.. code-block:: console
+
+ progname: erreur: can't find « free »
+
+Note that:
+
+* the string ``error`` has been localized by libdiagnostics to
+ ``erreur``,
+
+* locale-specific quoting has been used (``«`` and ``»`` rather than
+ ``‘`` and ``’``),
+
+* ``foo`` hasn't been localized - you would typically use quoted strings
+ for referring to identifiers in the input language (such as function names
+ in code, property names in JSON, etc),
+
+* the message itself hasn't been localized: you are responsible for
+ passing a translated format string to :func:`diagnostic_finish` if you
+ want to internationalize the output.
+
+There are many :doc:`supported formatting options <../topics/message-formatting>`.
+
+
+Naming the program
+******************
+
+In the above output the message was preceded with ``progname``. This
+appears for diagnostics that don't have any location information associated
+with them. We'll look at setting up location information in the
+:doc:`next tutorial <02-physical-locations>`, but we can override this
+default name via :func:`diagnostic_manager_set_tool_name`::
+
+ diagnostic_manager_set_tool_name (diag_mgr, "my-awesome-checker");
+
+leading to output like this::
+
+ my-awesome-checker: error: can't find ‘foo’
+
+There are various other functions for
+:doc:`supplying metadata to libdiagnostics <../../topics/metadata>`.
+
+
+Moving beyond trivial examples
+******************************
+
+Obviously it's not very useful if we can't refer to specific files and
+specific locations in those files in our diagnostics, so read
+:doc:`part 2 of the tutorial <02-physical-locations>` for information on
+how to do this.
--- /dev/null
+.. Copyright (C) 2024 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 2: physical locations
+===================================
+
+libdiagnostics has two kinds of location:
+
+* *physical locations* expressed in terms of a specific file, and line(s)
+ and perhaps column(s), such as ``some-file.c:3:1``, or a range of
+ columns, such as in::
+
+ test-typo.c:19:13: error: unknown field 'colour'
+ 19 | return p->colour;
+ | ^~~~~~
+
+ or even a range spanning multiple lines of a file.
+
+ All of these are instances of :type:`diagnostic_physical_location`.
+
+* *logical locations* which refers to semantic constructs
+ in the input, such as ``within function 'foo'``, or within
+ namespace ``foo``'s class ``bar``'s member function ``get_color``.
+
+ These are instances of :type:`diagnostic_logical_location`,
+
+A :type:`diagnostic` can have zero or more physical locations,
+and optionally have a logical location.
+
+Let's extend the previous example to add a physical location to the
+:type:`diagnostic`; we'll cover logical locations in the
+:doc:`next section <03-logical-locations>`.
+
+
+Source files
+************
+
+Given these declarations::
+
+ static diagnostic_manager *diag_mgr;
+ static const diagnostic_file *main_file;
+
+we can create a :type:`diagnostic_file` describing an input file ``foo.c``
+via :func:`diagnostic_manager_new_file`::
+
+ foo_c = diagnostic_manager_new_file (diag_mgr,
+ "foo.c",
+ "c" /* source_language */);
+
+You can use :func:`diagnostic_manager_debug_dump_file` to print a
+representation of a :type:`diagnostic_file` for debugging.
+For example::
+
+ diagnostic_manager_debug_dump_file (diag_mgr, foo_c, stderr);
+
+might lead to this output on ``stderr``::
+
+ file(name="foo.c", sarif_source_language="c")
+
+Once we have a :type:`diagnostic_file` we can use it to create instances
+of :type:`diagnostic_physical_location` within the :type:`diagnostic_manager`.
+These are owned by the :type:`diagnostic_manager` and cleaned up
+automatically when :func:`diagnostic_manager_release` is called.
+
+Instances of :type:`diagnostic_physical_location` can refer to
+
+* a source line as a whole, created via
+ :func:`diagnostic_manager_new_location_from_file_and_line`.
+
+* a particular point within a source file (line/column), created via
+ :func:`diagnostic_manager_new_location_from_file_line_column`.
+
+* a range of text within of source file, created via
+ :func:`diagnostic_manager_new_location_from_range`.
+
+
+Diagnostics affecting a whole source line
+*****************************************
+
+If we want a diagnostic to refer to an entire source line,
+we can use :func:`diagnostic_manager_new_location_from_file_and_line`.
+
+For example, given this example input where the tool can't find the header::
+
+ #include <foo.h>
+
+we could complain about it via libdiagnostics via:
+
+.. literalinclude:: ../../../testsuite/libdiagnostics.dg/test-no-column.c
+ :language: c
+ :start-after: /* begin quoted source */
+ :end-before: /* end quoted source */
+
+leading to output like this::
+
+ foo.c:17: error: can't find 'foo.h'"
+ 17 | #include <foo.h>
+
+where libdiagnostics will attempt to load the source file and
+quote the pertinent line.
+
+If libdiagnostics cannot open the file, it will merely print::
+
+ foo.c:17: error: can't find 'foo.h'
+
+You can use :func:`diagnostic_manager_debug_dump_location` to dump a
+:type:`diagnostic_physical_location`. For the above example::
+
+ diagnostic_manager_debug_dump_location (diag_mgr, loc, stderr);
+
+might print::
+
+ foo.c:17
+
+to stderr.
+
+
+Columns and ranges
+******************
+
+If we want to generate output like this::
+
+ foo.c:17:11: error: can't find 'foo'"
+ 17 | #include <foo.h>
+ | ^~~~~
+
+where the diagnostic is marked as relating to the above range of
+characters in line 17, we need to express the range of characters
+within the line of interest.
+
+We can do this by creating a :type:`diagnostic_physical_location` for the
+start of the range, another one for the end of the range, and then using
+these two to create a :type:`diagnostic_physical_location` for the
+range as a whole:
+
+.. literalinclude:: ../../../testsuite/libdiagnostics.dg/test-error.c
+ :language: c
+ :start-after: /* begin quoted source */
+ :end-before: /* end quoted source */
+
+On compiling and running the program, we should get this output::
+
+ foo.c:17:11: error: can't find 'foo.h'
+ 17 | #include <foo.h>
+ | ^~~~~
+
+where libdiagnostics will attempt to load the source file and
+underling the pertinent part of the given line.
+
+If libdiagnostics cannot open the file, it will merely print::
+
+ foo.c:17:8: error: can't find 'foo'
+
+A range can span multiple lines within the same file.
+
+As before, you can use :func:`diagnostic_manager_debug_dump_location` to
+dump the locations. For the above example::
+
+ diagnostic_manager_debug_dump_location (diag_mgr, loc_start, stderr);
+
+and::
+
+ diagnostic_manager_debug_dump_location (diag_mgr, loc_range, stderr);
+
+might print::
+
+ foo.c:17:11
+
+to stderr, whereas::
+
+ diagnostic_manager_debug_dump_location (diag_mgr, loc_end, stderr);
+
+might print::
+
+ foo.c:17:15
+
+
+Multiple locations
+******************
+
+As well as the primary physical location seen above, a :type:`diagnostic`
+can have additional physical locations. You can add these secondary
+locations via :func:`diagnostic_add_location`.
+
+For example, for this valid but suspicious-looking C code::
+
+ const char *strs[3] = {"foo",
+ "bar"
+ "baz"};
+
+the following :type:`diagnostic` has its primary location where the missing
+comma should be, and secondary locations for each of the string literals
+``"foo"``, ``"bar"``, and ``"baz"``, added via :func:`diagnostic_add_location`:
+
+.. literalinclude:: ../../../testsuite/libdiagnostics.dg/test-multiple-lines.c
+ :language: c
+ :start-after: /* begin quoted source */
+ :end-before: /* end quoted source */
+
+where the text output might be::
+
+ test-multiple-lines.c:23:29: warning: missing comma
+ 22 | const char *strs[3] = {"foo",
+ | ~~~~~
+ 23 | "bar"
+ | ~~~~~^
+ 24 | "baz"};
+ | ~~~~~
+
+
+Labelling locations
+*******************
+
+You can give the locations labels using
+:func:`diagnostic_set_location_with_label` and
+:func:`diagnostic_add_location_with_label`.
+
+Consider emitting a "type mismatch" diagnostic for::
+
+ 42 + "foo"
+
+where the primary location is on the ``+``, with secondary locations on the``42``
+and the ``"foo"``:
+
+.. literalinclude:: ../../../testsuite/libdiagnostics.dg/test-labelled-ranges.c
+ :language: c
+ :start-after: /* begin quoted source */
+ :end-before: /* end quoted source */
+
+giving this text output::
+
+ test-labelled-ranges.c:9:6: error: mismatching types: 'int' and 'const char *'
+ 19 | 42 + "foo"
+ | ~~ ^ ~~~~~
+ | | |
+ | int const char *
+
+
+More on locations
+*****************
+
+For more details on the above, see :doc:`../topics/physical-locations`.
+Otherwise the :doc:`next part of the tutorial <03-logical-locations>`
+covers logical locations.
--- /dev/null
+.. Copyright (C) 2024 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 3: logical locations
+==================================
+
+Let's extend the previous example to add a
+:doc:`logical location <../topics/logical-locations>` to the
+:type:`diagnostic`.
+
+First we create a :type:`diagnostic_logical_location` representing a
+particular function::
+
+ const diagnostic_logical_location *logical_loc
+ = diagnostic_manager_new_logical_location (diag_mgr,
+ DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION,
+ NULL, /* parent */
+ "foo",
+ NULL,
+ NULL);
+
+In this simple example we specify that it is a function, and just give
+it a name (``foo``). For more complicated cases we can set up tree-like
+hierarchies of logical locations, set qualified names, "mangled" names,
+and so on; see :func:`diagnostic_manager_new_logical_location` for details.
+
+Once we have :type:`diagnostic_logical_location` we can associate it with
+a :type:`diagnostic` with :func:`diagnostic_set_logical_location`::
+
+ diagnostic_set_logical_location (d, logical_loc);
+
+The logical location will be printed by text output sinks like this::
+
+ In function 'foo':
+
+and will be captured in :doc:`SARIF <../topics/sarif>` output.
+
+
+Find out more
+*************
+
+For more details on the above, see :doc:`../topics/logical-locations`.
+Otherwise the :doc:`next part of the tutorial <04-notes>` covers adding
+supplementary "notes" to a :type:`diagnostic`.
--- /dev/null
+.. Copyright (C) 2024 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 4: adding notes
+=============================
+
+Let's further extend the previous example to add a "note" to it.
+
+We want to generate output like this::
+
+ test-with-note.c:17:11: error: can't find 'foo'
+ 17 | #include <foo.h>
+ | ^~~~~
+ test-with-note.c:17:11: note: have you looked behind the couch?
+
+The "error" and "note" are both instances of :type:`diagnostic`.
+We want to let libdiagnostics know that they are grouped together.
+The way to do this is to use :func:`diagnostic_manager_begin_group`
+and :func:`diagnostic_manager_end_group` around the "finish" calls
+to the diagnostics.
+
+.. literalinclude:: ../../../testsuite/libdiagnostics.dg/test-error-with-note.c
+ :language: c
+ :start-after: /* begin quoted source */
+ :end-before: /* end quoted source */
+
+On compiling and running the program, we should get the desired output::
+
+ test-with-note.c:17:11: error: can't find 'foo'
+ 17 | #include <foo.h>
+ | ^~~~~
+ test-with-note.c:17:11: note: have you looked behind the couch?
+
+The grouping doesn't affect text output sinks, but a
+:doc:`SARIF sink <../topics/sarif>` will group the note within the error
+(via the ``relatedLocations`` property of ``result`` objects; see SARIF v2.1.0
+`§3.27.22 <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790910>`_).
+
+In the above, the note had the same physical location as the error
+(``loc_range``). This can be useful for splitting up a message into two
+parts to make localization easier, but they could have different locations, such
+as in::
+
+ test.xml:10:2: error: 'foo' is not valid here
+ test.xml:5:1: note: within element 'bar'
+
+where each :type:`diagnostic` had its own :type:`diagnostic_physical_location`.
+
+In :doc:`the next tutorial <05-warnings>` we'll look at issuing warnings,
+rather than errors.
--- /dev/null
+.. Copyright (C) 2024 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 5: warnings
+=========================
+
+So far we've only emitted errors, but other kinds of diagnostic are possible,
+such as warnings.
+
+We can select different kinds of diagnostic via :enum:`diagnostic_level`
+when calling :func:`diagnostic_begin`:
+
+.. literalinclude:: ../../../testsuite/libdiagnostics.dg/test-warning.c
+ :language: c
+ :start-after: /* begin quoted source */
+ :end-before: /* end quoted source */
+
+On compiling and running the program, we should get output similar to::
+
+ test-warning.c:17:11: warning: this is a warning
+ 17 | #include <foo.h>
+ | ^~~~~
+
+Various severities are possible, see :enum:`diagnostic_level` for more
+information.
+
+In :doc:`the next section of the tutorial <06-fix-it-hints>` we'll look
+at adding fix-it hints to diagnostics.
--- /dev/null
+.. Copyright (C) 2024 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 6: fix-it hints
+=============================
+
+libdiagnostics supports adding "fix-it hints" to a :type:`diagnostic`:
+suggestions for the user on how to edit their code to fix a problem. These
+can be expressed as insertions, replacements, and removals of text.
+
+For example, here we add a replacement fix-it hint to a diagnostic:
+
+.. literalinclude:: ../../../testsuite/libdiagnostics.dg/test-fix-it-hint.c
+ :language: c
+ :start-after: /* begin quoted source */
+ :end-before: /* end quoted source */
+
+On compiling and running the program, we should get output similar to::
+
+ test-fix-it-hint.c:19:13: error: unknown field 'colour'; did you mean 'color'
+ 19 | return p->colour;
+ | ^~~~~~
+ | color
+
+We can also add a call to :func:`diagnostic_manager_write_patch` to the
+program cleanup code::
+
+ diagnostic_manager_write_patch (diag_mgr, stderr);
+
+This will write a patch to the stream (here ``stderr``) giving the effect
+of all fix-it hints on all diagnostics emitted by the
+:type:`diagnostic_manager`, giving something like::
+
+ @@ -16,7 +16,7 @@
+ struct rgb
+ get_color (struct object *p)
+ {
+ - return p->colour;
+ + return p->color;
+ }
+
+
+See the :doc:`guide to fix-it hints <../topics/fix-it-hints>`
+for more information, or go on to
+:doc:`the next section of the tutorial <07-execution-paths>`.
--- /dev/null
+.. Copyright (C) 2024 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/libdiagnostics.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/libdiagnostics.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/libdiagnostics.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/libdiagnostics.dg/test-warning-with-path.c
+ :language: c
+ :start-after: begin full example
+ :end-before: end full example
+
+
+Moving on
+*********
+
+That's the end of the tutorial. For more information on libdiagnostics, see
+the :doc:`topic guide <../topics/index>`.
--- /dev/null
+.. Copyright (C) 2024 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/>.
+
+Tutorial
+========
+
+The following tutorial gives an overview of how to use libdiagnostics.
+
+.. toctree::
+ :maxdepth: 2
+
+ 01-hello-world.rst
+ 02-physical-locations.rst
+ 03-logical-locations.rst
+ 04-notes.rst
+ 05-warnings.rst
+ 06-fix-it-hints.rst
+ 07-execution-paths.rst
--- /dev/null
+/* A C++ wrapper API around libdiagnostics.h for emitting diagnostics.
+ Copyright (C) 2023-2024 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC 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, or (at your option)
+any later version.
+
+GCC 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 GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef LIBDIAGNOSTICSPP_H
+#define LIBDIAGNOSTICSPP_H
+
+#include "libdiagnostics.h"
+
+namespace libdiagnostics {
+
+typedef diagnostic_line_num_t line_num_t;
+typedef diagnostic_column_num_t column_num_t;
+
+class file;
+class physical_location;
+class logical_location;
+class execution_path;
+class group;
+class manager;
+class diagnostic;
+
+/* Wrapper around a borrowed diagnostic_text_sink *. */
+
+class text_sink
+{
+public:
+ text_sink (diagnostic_text_sink *inner)
+ : m_inner (inner)
+ {
+ }
+
+ void
+ set_source_printing_enabled (int value)
+ {
+ diagnostic_text_sink_set_source_printing_enabled (m_inner, value);
+ }
+
+ void
+ set_colorize (enum diagnostic_colorize colorize)
+ {
+ diagnostic_text_sink_set_colorize (m_inner, colorize);
+ }
+
+ void
+ set_labelled_source_colorization_enabled (int value)
+ {
+ diagnostic_text_sink_set_labelled_source_colorization_enabled (m_inner,
+ value);
+ }
+
+ diagnostic_text_sink *m_inner;
+};
+
+/* Wrapper around a const diagnostic_file *. */
+
+class file
+{
+public:
+ file () : m_inner (nullptr) {}
+ file (const diagnostic_file *file) : m_inner (file) {}
+ file (const file &other) : m_inner (other.m_inner) {}
+ file &operator= (const file &other) { m_inner = other.m_inner; return *this; }
+
+ const diagnostic_file * m_inner;
+};
+
+/* Wrapper around a const diagnostic_physical_location *. */
+
+class physical_location
+{
+public:
+ physical_location () : m_inner (nullptr) {}
+
+ physical_location (const diagnostic_physical_location *location)
+ : m_inner (location)
+ {}
+
+ const diagnostic_physical_location *m_inner;
+};
+
+/* Wrapper around a const diagnostic_logical_location *. */
+
+class logical_location
+{
+public:
+ logical_location () : m_inner (nullptr) {}
+
+ logical_location (const diagnostic_logical_location *logical_loc)
+ : m_inner (logical_loc)
+ {}
+
+ const diagnostic_logical_location *m_inner;
+};
+
+/* RAII class around a diagnostic_execution_path *. */
+
+class execution_path
+{
+public:
+ execution_path () : m_inner (nullptr), m_owned (false) {}
+
+ execution_path (diagnostic_execution_path *path)
+ : m_inner (path), m_owned (true)
+ {}
+
+ execution_path (const diagnostic_execution_path *path)
+ : m_inner (const_cast<diagnostic_execution_path *> (path)),
+ m_owned (false)
+ {}
+
+ execution_path (const execution_path &other) = delete;
+ execution_path &operator= (const execution_path &other) = delete;
+
+ execution_path (execution_path &&other)
+ : m_inner (other.m_inner),
+ m_owned (other.m_owned)
+ {
+ other.m_inner = nullptr;
+ other.m_owned = false;
+ }
+
+ execution_path &operator= (execution_path &&other)
+ {
+ m_inner = other.m_inner;
+ m_owned = other.m_owned;
+ other.m_inner = nullptr;
+ other.m_owned = false;
+ return *this;
+ }
+
+ ~execution_path ()
+ {
+ if (m_owned)
+ diagnostic_execution_path_release (m_inner);
+ }
+
+ diagnostic_event_id
+ add_event (physical_location physical_loc,
+ logical_location logical_loc,
+ unsigned stack_depth,
+ const char *fmt, ...)
+ LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (5, 6);
+
+ diagnostic_event_id
+ add_event_va (physical_location physical_loc,
+ logical_location logical_loc,
+ unsigned stack_depth,
+ const char *fmt,
+ va_list *args)
+ LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (5, 0);
+
+ diagnostic_execution_path *m_inner;
+ bool m_owned;
+};
+
+/* RAII class for starting/ending a group within a diagnostic_manager. */
+
+class group
+{
+public:
+ group (manager &mgr);
+ ~group ();
+
+private:
+ manager &m_mgr;
+};
+
+/* Wrapper around a diagnostic *. */
+
+class diagnostic
+{
+public:
+ diagnostic (::diagnostic *d) : m_inner (d) {}
+
+ void
+ set_cwe (unsigned cwe_id);
+
+ void
+ add_rule (const char *title, const char *url);
+
+ void
+ set_location (physical_location loc);
+
+ void
+ add_location_with_label (physical_location loc,
+ const char *text);
+
+ void
+ set_logical_location (logical_location loc);
+
+ void
+ add_fix_it_hint_insert_before (physical_location loc,
+ const char *addition);
+ void
+ add_fix_it_hint_insert_after (physical_location loc,
+ const char *addition);
+ void
+ add_fix_it_hint_replace (physical_location loc,
+ const char *replacement);
+ void
+ add_fix_it_hint_delete (physical_location loc);
+
+ void
+ take_execution_path (execution_path path);
+
+ void
+ finish (const char *fmt, ...)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+ LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 3);
+
+ void
+ finish_va (const char *fmt, va_list *args)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+ LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 0);
+
+ ::diagnostic * const m_inner;
+};
+
+/* Wrapper around a diagnostic_manager *, possibly with ownership. */
+
+class manager
+{
+public:
+ manager ()
+ : m_inner (diagnostic_manager_new ()),
+ m_owned (true)
+ {
+ }
+ manager (diagnostic_manager *inner, bool owned)
+ : m_inner (inner),
+ m_owned (owned)
+ {
+ }
+ ~manager ()
+ {
+ if (m_owned)
+ diagnostic_manager_release (m_inner);
+ }
+
+ manager (const manager &other) = delete;
+ manager (manager &&other)
+ : m_inner (other.m_inner),
+ m_owned (other.m_owned)
+ {
+ other.m_inner = nullptr;
+ }
+
+ void
+ set_tool_name (const char *value)
+ {
+ diagnostic_manager_set_tool_name (m_inner, value);
+ }
+
+ void
+ set_full_name (const char *value)
+ {
+ diagnostic_manager_set_full_name (m_inner, value);
+ }
+
+ void
+ set_version_string (const char *value)
+ {
+ diagnostic_manager_set_version_string (m_inner, value);
+ }
+
+ void
+ set_version_url (const char *value)
+ {
+ diagnostic_manager_set_version_url (m_inner, value);
+ }
+
+ text_sink
+ add_text_sink (FILE *dst_stream,
+ enum diagnostic_colorize colorize)
+ {
+ return text_sink
+ (diagnostic_manager_add_text_sink (m_inner, dst_stream, colorize));
+ }
+
+ void
+ add_sarif_sink (FILE *dst_stream,
+ file main_input_file,
+ enum diagnostic_sarif_version version)
+ {
+ diagnostic_manager_add_sarif_sink (m_inner, dst_stream,
+ main_input_file.m_inner,
+ version);
+ }
+
+ void
+ write_patch (FILE *dst_stream)
+ {
+ diagnostic_manager_write_patch (m_inner, dst_stream);
+ }
+
+ /* Location management. */
+
+ file
+ new_file (const char *name,
+ const char *sarif_source_language)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3);
+
+ void
+ debug_dump (file f,
+ FILE *out);
+
+ physical_location
+ new_location_from_file_and_line (file f, diagnostic_line_num_t line_num);
+
+ physical_location
+ new_location_from_file_line_column (file f,
+ line_num_t line_num,
+ column_num_t column_num);
+
+ physical_location
+ new_location_from_range (physical_location loc_caret,
+ physical_location loc_start,
+ physical_location loc_end);
+
+ void
+ debug_dump (physical_location loc,
+ FILE *out);
+
+ logical_location
+ new_logical_location (enum diagnostic_logical_location_kind_t kind,
+ logical_location parent,
+ const char *short_name,
+ const char *fully_qualified_name,
+ const char *decorated_name);
+
+ void
+ debug_dump (logical_location loc,
+ FILE *out);
+
+ execution_path
+ new_execution_path ();
+
+ diagnostic
+ begin_diagnostic (enum diagnostic_level level);
+
+
+ diagnostic_manager *m_inner;
+ bool m_owned;
+};
+
+// Implementation
+
+// class execution_path
+
+inline diagnostic_event_id
+execution_path::add_event (physical_location physical_loc,
+ logical_location logical_loc,
+ unsigned stack_depth,
+ const char *fmt, ...)
+{
+ va_list args;
+ va_start (args, fmt);
+ diagnostic_event_id result = add_event_va (physical_loc,
+ logical_loc,
+ stack_depth,
+ fmt, &args);
+ va_end (args);
+
+ return result;
+}
+
+inline diagnostic_event_id
+execution_path::add_event_va (physical_location physical_loc,
+ logical_location logical_loc,
+ unsigned stack_depth,
+ const char *fmt,
+ va_list *args)
+{
+ return diagnostic_execution_path_add_event_va (m_inner,
+ physical_loc.m_inner,
+ logical_loc.m_inner,
+ stack_depth,
+ fmt,
+ args);
+}
+
+// class group
+
+inline
+group::group (manager &mgr)
+: m_mgr (mgr)
+{
+ diagnostic_manager_begin_group (m_mgr.m_inner);
+}
+
+inline
+group::~group ()
+{
+ diagnostic_manager_end_group (m_mgr.m_inner);
+}
+
+// class diagnostic
+
+inline void
+diagnostic::set_cwe (unsigned cwe_id)
+{
+ diagnostic_set_cwe (m_inner, cwe_id);
+}
+
+inline void
+diagnostic::add_rule (const char *title, const char *url)
+{
+ diagnostic_add_rule (m_inner, title, url);
+}
+
+inline void
+diagnostic::set_location (physical_location loc)
+{
+ diagnostic_set_location (m_inner, loc.m_inner);
+}
+
+inline void
+diagnostic::add_location_with_label (physical_location loc,
+ const char *text)
+{
+ diagnostic_add_location_with_label (m_inner, loc.m_inner, text);
+}
+
+inline void
+diagnostic::set_logical_location (logical_location loc)
+{
+ diagnostic_set_logical_location (m_inner, loc.m_inner);
+}
+
+inline void
+diagnostic::add_fix_it_hint_insert_before (physical_location loc,
+ const char *addition)
+{
+ diagnostic_add_fix_it_hint_insert_before (m_inner,
+ loc.m_inner,
+ addition);
+}
+
+inline void
+diagnostic::add_fix_it_hint_insert_after (physical_location loc,
+ const char *addition)
+{
+ diagnostic_add_fix_it_hint_insert_after (m_inner,
+ loc.m_inner,
+ addition);
+}
+
+inline void
+diagnostic::add_fix_it_hint_replace (physical_location loc,
+ const char *replacement)
+{
+ diagnostic_add_fix_it_hint_replace (m_inner,
+ loc.m_inner,
+ replacement);
+}
+
+inline void
+diagnostic::add_fix_it_hint_delete (physical_location loc)
+{
+ diagnostic_add_fix_it_hint_delete (m_inner,
+ loc.m_inner);
+}
+
+inline void
+diagnostic::take_execution_path (execution_path path)
+{
+ diagnostic_take_execution_path (m_inner,
+ path.m_inner);
+ path.m_owned = false;
+}
+
+inline void
+diagnostic::finish (const char *fmt, ...)
+{
+ va_list ap;
+ va_start (ap, fmt);
+ diagnostic_finish_va (m_inner, fmt, &ap);
+ va_end (ap);
+}
+
+inline void
+diagnostic::finish_va (const char *fmt, va_list *args)
+{
+ diagnostic_finish_va (m_inner, fmt, args);
+}
+
+// class manager
+
+inline file
+manager::new_file (const char *name,
+ const char *sarif_source_language)
+{
+ return file
+ (diagnostic_manager_new_file (m_inner, name, sarif_source_language));
+}
+
+inline physical_location
+manager::new_location_from_file_and_line (file f,
+ diagnostic_line_num_t line_num)
+{
+ return physical_location
+ (diagnostic_manager_new_location_from_file_and_line (m_inner,
+ f.m_inner,
+ line_num));
+}
+
+inline physical_location
+manager::new_location_from_file_line_column (file f,
+ line_num_t line_num,
+ column_num_t column_num)
+{
+ return physical_location
+ (diagnostic_manager_new_location_from_file_line_column (m_inner,
+ f.m_inner,
+ line_num,
+ column_num));
+}
+
+inline physical_location
+manager::new_location_from_range (physical_location loc_caret,
+ physical_location loc_start,
+ physical_location loc_end)
+{
+ return physical_location
+ (diagnostic_manager_new_location_from_range (m_inner,
+ loc_caret.m_inner,
+ loc_start.m_inner,
+ loc_end.m_inner));
+}
+
+inline void
+manager::debug_dump (physical_location loc,
+ FILE *out)
+{
+ diagnostic_manager_debug_dump_location (m_inner,
+ loc.m_inner,
+ out);
+}
+inline logical_location
+manager::new_logical_location (enum diagnostic_logical_location_kind_t kind,
+ logical_location parent,
+ const char *short_name,
+ const char *fully_qualified_name,
+ const char *decorated_name)
+{
+ return logical_location
+ (diagnostic_manager_new_logical_location (m_inner,
+ kind,
+ parent.m_inner,
+ short_name,
+ fully_qualified_name,
+ decorated_name));
+}
+
+inline void
+manager::debug_dump (logical_location loc,
+ FILE *out)
+{
+ diagnostic_manager_debug_dump_logical_location (m_inner,
+ loc.m_inner,
+ out);
+}
+
+inline execution_path
+manager::new_execution_path ()
+{
+ return execution_path (diagnostic_manager_new_execution_path (m_inner));
+}
+
+inline diagnostic
+manager::begin_diagnostic (enum diagnostic_level level)
+{
+ return diagnostic (diagnostic_begin (m_inner, level));
+}
+
+} // namespace libdiagnostics
+
+#endif // #ifndef LIBDIAGNOSTICSPP_H
--- /dev/null
+/* C++ implementation of a pure C API for emitting diagnostics.
+ Copyright (C) 2023-2024 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC 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, or (at your option) any later
+version.
+
+GCC 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 GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "diagnostic-color.h"
+#include "diagnostic-url.h"
+#include "diagnostic-metadata.h"
+#include "diagnostic-path.h"
+#include "diagnostic-client-data-hooks.h"
+#include "diagnostic-format-sarif.h"
+#include "diagnostic-format-text.h"
+#include "logical-location.h"
+#include "edit-context.h"
+#include "make-unique.h"
+#include "libdiagnostics.h"
+
+class owned_nullable_string
+{
+public:
+ owned_nullable_string () : m_str (nullptr) {}
+ owned_nullable_string (const char *str)
+ : m_str (str ? ::xstrdup (str) : nullptr)
+ {
+ }
+
+ ~owned_nullable_string ()
+ {
+ free (m_str);
+ }
+
+ void set (const char *str)
+ {
+ free (m_str);
+ m_str = str ? ::xstrdup (str) : nullptr;
+ }
+
+ const char *get_str () const { return m_str; }
+
+ char *xstrdup () const
+ {
+ return m_str ? ::xstrdup (m_str) : nullptr;
+ }
+
+private:
+ char *m_str;
+};
+
+/* This has to be a "struct" as it is exposed in the C API. */
+
+struct diagnostic_file
+{
+ diagnostic_file (const char *name, const char *sarif_source_language)
+ : m_name (name), m_sarif_source_language (sarif_source_language)
+ {
+ }
+
+ const char *get_name () const { return m_name.get_str (); }
+ const char *get_sarif_source_language () const
+ {
+ return m_sarif_source_language.get_str ();
+ }
+
+private:
+ owned_nullable_string m_name;
+ owned_nullable_string m_sarif_source_language;
+};
+
+/* This has to be a "struct" as it is exposed in the C API. */
+
+struct diagnostic_physical_location
+{
+ diagnostic_physical_location (diagnostic_manager *mgr,
+ location_t inner)
+ : m_mgr (mgr),
+ m_inner (inner)
+ {}
+
+ diagnostic_manager *m_mgr;
+ location_t m_inner;
+};
+
+static location_t
+as_location_t (const diagnostic_physical_location *loc)
+{
+ if (!loc)
+ return UNKNOWN_LOCATION;
+ return loc->m_inner;
+}
+
+/* This has to be a "struct" as it is exposed in the C API. */
+
+struct diagnostic_logical_location : public logical_location
+{
+ diagnostic_logical_location (enum diagnostic_logical_location_kind_t kind,
+ const diagnostic_logical_location *parent,
+ const char *short_name,
+ const char *fully_qualified_name,
+ const char *decorated_name)
+ : m_kind (kind),
+ m_parent (parent),
+ m_short_name (short_name),
+ m_fully_qualified_name (fully_qualified_name),
+ m_decorated_name (decorated_name)
+ {
+ }
+
+ const char *get_short_name () const final override
+ {
+ return m_short_name.get_str ();
+ }
+ const char *get_name_with_scope () const final override
+ {
+ return m_fully_qualified_name.get_str ();
+ }
+ const char *get_internal_name () const final override
+ {
+ return m_decorated_name.get_str ();
+ }
+ enum logical_location_kind get_kind () const final override
+ {
+ switch (m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION:
+ return LOGICAL_LOCATION_KIND_FUNCTION;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER:
+ return LOGICAL_LOCATION_KIND_MEMBER;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE:
+ return LOGICAL_LOCATION_KIND_MODULE;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE:
+ return LOGICAL_LOCATION_KIND_NAMESPACE;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE:
+ return LOGICAL_LOCATION_KIND_TYPE;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE:
+ return LOGICAL_LOCATION_KIND_RETURN_TYPE;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER:
+ return LOGICAL_LOCATION_KIND_PARAMETER;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE:
+ return LOGICAL_LOCATION_KIND_VARIABLE;
+ }
+ }
+
+ enum diagnostic_logical_location_kind_t get_external_kind () const
+ {
+ return m_kind;
+ }
+
+ const diagnostic_logical_location *get_parent () const { return m_parent; }
+
+ label_text get_name_for_path_output () const
+ {
+ return label_text::borrow (m_short_name.get_str ());
+ }
+
+private:
+ enum diagnostic_logical_location_kind_t m_kind;
+ const diagnostic_logical_location *m_parent;
+ owned_nullable_string m_short_name;
+ owned_nullable_string m_fully_qualified_name;
+ owned_nullable_string m_decorated_name;
+};
+
+static diagnostic_event_id
+as_diagnostic_event_id (diagnostic_event_id_t id)
+{
+ return id.zero_based ();
+}
+
+class sink
+{
+public:
+ virtual ~sink ();
+
+ void begin_group ()
+ {
+ m_dc.begin_group ();
+ }
+ void end_group ()
+ {
+ m_dc.end_group ();
+ }
+
+ void emit (diagnostic &diag, const char *msgid, va_list *args)
+ LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (3, 0);
+
+protected:
+ sink (diagnostic_manager &mgr);
+
+ diagnostic_manager &m_mgr;
+
+ /* One context per sink. */
+ diagnostic_context m_dc;
+};
+
+/* This has to be a "struct" as it is exposed in the C API. */
+
+struct diagnostic_text_sink : public sink
+{
+public:
+ diagnostic_text_sink (diagnostic_manager &mgr,
+ FILE *dst_stream,
+ enum diagnostic_colorize colorize);
+
+ void
+ on_begin_text_diagnostic (diagnostic_text_output_format &text_format,
+ const diagnostic_info *info);
+
+ diagnostic_source_printing_options &get_source_printing_options ()
+ {
+ return m_dc.m_source_printing;
+ }
+
+ void
+ set_colorize (enum diagnostic_colorize colorize);
+
+private:
+ const diagnostic_logical_location *m_current_logical_loc;
+};
+
+class sarif_sink : public sink
+{
+public:
+ sarif_sink (diagnostic_manager &mgr,
+ FILE *dst_stream,
+ const diagnostic_file *main_input_file,
+ enum sarif_version version);
+};
+
+/* Helper for the linemap code. */
+
+static size_t
+round_alloc_size (size_t s)
+{
+ return s;
+}
+
+class impl_diagnostic_client_data_hooks : public diagnostic_client_data_hooks
+{
+public:
+ impl_diagnostic_client_data_hooks (diagnostic_manager &mgr)
+ : m_mgr (mgr)
+ {}
+
+ const client_version_info *get_any_version_info () const final override;
+ const logical_location *get_current_logical_location () const final override;
+ const char * maybe_get_sarif_source_language (const char *filename)
+ const final override;
+ void add_sarif_invocation_properties (sarif_object &invocation_obj)
+ const final override;
+
+private:
+ diagnostic_manager &m_mgr;
+};
+
+class impl_client_version_info : public client_version_info
+{
+public:
+ const char *get_tool_name () const final override
+ {
+ return m_name.get_str ();
+ }
+
+ char *maybe_make_full_name () const final override
+ {
+ return m_full_name.xstrdup ();
+ }
+
+ const char *get_version_string () const final override
+ {
+ return m_version.get_str ();
+ }
+
+ char *maybe_make_version_url () const final override
+ {
+ return m_version_url.xstrdup ();
+ }
+
+ void for_each_plugin (plugin_visitor &) const final override
+ {
+ // No-op.
+ }
+
+ owned_nullable_string m_name;
+ owned_nullable_string m_full_name;
+ owned_nullable_string m_version;
+ owned_nullable_string m_version_url;
+};
+
+/* This has to be a "struct" as it is exposed in the C API. */
+
+struct diagnostic_manager
+{
+public:
+ diagnostic_manager ()
+ : m_current_diag (nullptr),
+ m_edit_context (m_file_cache)
+ {
+ linemap_init (&m_line_table, BUILTINS_LOCATION);
+ m_line_table.m_reallocator = xrealloc;
+ m_line_table.m_round_alloc_size = round_alloc_size;
+ m_line_table.default_range_bits = 5;
+ }
+ ~diagnostic_manager ()
+ {
+ /* Clean up sinks first, as they can use other fields. */
+ for (size_t i = 0; i < m_sinks.size (); i++)
+ m_sinks[i] = nullptr;
+
+ for (auto iter : m_str_to_file_map)
+ delete iter.second;
+
+ for (auto iter :m_location_t_map)
+ delete iter.second;
+
+ free (m_line_table.m_location_adhoc_data_map.data);
+ free (m_line_table.info_ordinary.maps);
+ }
+
+ line_maps *get_line_table () { return &m_line_table; }
+ file_cache *get_file_cache () { return &m_file_cache; }
+
+ void write_patch (FILE *dst_stream);
+
+ void add_sink (std::unique_ptr<sink> sink)
+ {
+ m_sinks.push_back (std::move (sink));
+ }
+
+ void emit (diagnostic &diag, const char *msgid, va_list *args)
+ LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 0);
+
+ const diagnostic_file *
+ new_file (const char *name,
+ const char *sarif_source_language)
+ {
+ if (diagnostic_file **slot = m_str_to_file_map.get (name))
+ return *slot;
+ diagnostic_file *file = new diagnostic_file (name, sarif_source_language);
+ m_str_to_file_map.put (file->get_name (), file);
+ return file;
+ }
+
+ const diagnostic_physical_location *
+ new_location_from_file_and_line (const diagnostic_file *file,
+ diagnostic_line_num_t line_num)
+ {
+ ensure_linemap_for_file_and_line (file, line_num);
+ location_t loc = linemap_position_for_column (&m_line_table, 0);
+ return new_location (loc);
+ }
+
+ const diagnostic_physical_location *
+ new_location_from_file_line_column (const diagnostic_file *file,
+ diagnostic_line_num_t line_num,
+ diagnostic_column_num_t column_num)
+ {
+ ensure_linemap_for_file_and_line (file, line_num);
+ location_t loc = linemap_position_for_column (&m_line_table, column_num);
+ return new_location (loc);
+ }
+
+ const diagnostic_physical_location *
+ new_location_from_range (const diagnostic_physical_location *loc_caret,
+ const diagnostic_physical_location *loc_start,
+ const diagnostic_physical_location *loc_end)
+ {
+ return new_location
+ (m_line_table.make_location (as_location_t (loc_caret),
+ as_location_t (loc_start),
+ as_location_t (loc_end)));
+ }
+
+ const diagnostic_logical_location *
+ new_logical_location (enum diagnostic_logical_location_kind_t kind,
+ const diagnostic_logical_location *parent,
+ const char *short_name,
+ const char *fully_qualified_name,
+ const char *decorated_name)
+ {
+ std::unique_ptr<diagnostic_logical_location> logical_loc
+ = ::make_unique<diagnostic_logical_location> (kind,
+ parent,
+ short_name,
+ fully_qualified_name,
+ decorated_name);
+ const diagnostic_logical_location *result = logical_loc.get ();
+ m_logical_locs.push_back (std::move (logical_loc));
+ return result;
+ }
+
+ diagnostic_execution_path *
+ new_execution_path ();
+
+ void begin_group ()
+ {
+ for (auto &sink : m_sinks)
+ sink->begin_group ();
+ }
+
+ void end_group ()
+ {
+ for (auto &sink : m_sinks)
+ sink->end_group ();
+ }
+
+ const char *
+ maybe_get_sarif_source_language (const char *filename)
+ {
+ if (diagnostic_file **slot = m_str_to_file_map.get (filename))
+ {
+ gcc_assert (*slot);
+ return (*slot)->get_sarif_source_language ();
+ }
+ return nullptr;
+ }
+
+ const diagnostic *get_current_diag () { return m_current_diag; }
+
+ const client_version_info *get_client_version_info () const
+ {
+ return &m_client_version_info;
+ }
+ impl_client_version_info *get_client_version_info ()
+ {
+ return &m_client_version_info;
+ }
+
+ void
+ assert_valid_diagnostic_physical_location (const diagnostic_physical_location *loc) const
+ {
+ if (!loc)
+ return;
+ gcc_assert (loc->m_mgr == this);
+ }
+
+ /* TODO: Various things still use the "line_table" global variable.
+ Set it to be this diagnostic_manager's m_line_table.
+ Ideally we should eliminate this global (and this function). */
+ void set_line_table_global () const
+ {
+ line_table = const_cast<line_maps *> (&m_line_table);
+ }
+
+private:
+ void
+ ensure_linemap_for_file_and_line (const diagnostic_file *file,
+ diagnostic_line_num_t linenum)
+ {
+ /* Build a simple linemap describing some locations. */
+ if (LINEMAPS_ORDINARY_USED (&m_line_table) == 0)
+ linemap_add (&m_line_table, LC_ENTER, false, file->get_name (), 0);
+ else
+ {
+ line_map *map
+ = const_cast<line_map *>
+ (linemap_add (&m_line_table, LC_RENAME_VERBATIM, false,
+ file->get_name (), 0));
+ ((line_map_ordinary *)map)->included_from = UNKNOWN_LOCATION;
+ }
+ linemap_line_start (&m_line_table, linenum, 100);
+ }
+
+ const diagnostic_physical_location *
+ new_location (location_t loc)
+ {
+ if (loc == UNKNOWN_LOCATION)
+ return nullptr;
+ if (diagnostic_physical_location **slot = m_location_t_map.get (loc))
+ return *slot;
+ diagnostic_physical_location *phys_loc
+ = new diagnostic_physical_location (this, loc);
+ m_location_t_map.put (loc, phys_loc);
+ return phys_loc;
+ }
+
+ line_maps m_line_table;
+ file_cache m_file_cache;
+ impl_client_version_info m_client_version_info;
+ std::vector<std::unique_ptr<sink>> m_sinks;
+ hash_map<nofree_string_hash, diagnostic_file *> m_str_to_file_map;
+ hash_map<int_hash<location_t, UNKNOWN_LOCATION, UINT_MAX>,
+ diagnostic_physical_location *> m_location_t_map;
+ std::vector<std::unique_ptr<diagnostic_logical_location>> m_logical_locs;
+ const diagnostic *m_current_diag;
+ edit_context m_edit_context;
+};
+
+class impl_rich_location : public rich_location
+{
+public:
+ impl_rich_location (line_maps *set)
+ : rich_location (set, UNKNOWN_LOCATION)
+ {}
+};
+
+class impl_range_label : public range_label
+{
+public:
+ impl_range_label (const char *text)
+ : m_text (xstrdup (text))
+ {}
+
+ ~impl_range_label () { free (m_text); }
+
+ label_text get_text (unsigned) const final override
+ {
+ return label_text::borrow (m_text);
+ }
+
+private:
+ char *m_text;
+};
+
+class impl_rule : public diagnostic_metadata::rule
+{
+public:
+ impl_rule (const char *title, const char *url)
+ : m_title (title),
+ m_url (url)
+ {
+ }
+
+ virtual ~impl_rule () {}
+
+ char *make_description () const final override
+ {
+ return m_title.xstrdup ();
+ }
+
+ char *make_url () const final override
+ {
+ return m_url.xstrdup ();
+ }
+
+private:
+ owned_nullable_string m_title;
+ owned_nullable_string m_url;
+};
+
+class libdiagnostics_path_event : public diagnostic_event
+{
+public:
+ libdiagnostics_path_event (const diagnostic_physical_location *physical_loc,
+ const diagnostic_logical_location *logical_loc,
+ unsigned stack_depth,
+ const char *gmsgid,
+ va_list *args)
+ : m_physical_loc (physical_loc),
+ m_logical_loc (logical_loc),
+ m_stack_depth (stack_depth)
+ {
+ m_desc_uncolored = make_desc (gmsgid, args, false);
+ m_desc_colored = make_desc (gmsgid, args, true);
+ }
+
+ /* diagnostic_event vfunc implementations. */
+
+ location_t get_location () const final override
+ {
+ return as_location_t (m_physical_loc);
+ }
+
+ int get_stack_depth () const final override
+ {
+ return m_stack_depth;
+ }
+
+ void print_desc (pretty_printer &pp) const final override
+ {
+ if (pp_show_color (&pp))
+ pp_string (&pp, m_desc_colored.get ());
+ else
+ pp_string (&pp, m_desc_uncolored.get ());
+ }
+
+ const logical_location *get_logical_location () const
+ {
+ return m_logical_loc;
+ }
+
+ meaning get_meaning () const final override
+ {
+ return meaning ();
+ }
+
+ bool connect_to_next_event_p () const final override
+ {
+ return false; // TODO
+ }
+
+ diagnostic_thread_id_t get_thread_id () const final override
+ {
+ return 0;
+ }
+
+private:
+ static label_text make_desc (const char *gmsgid,
+ va_list *args,
+ bool colorize)
+ {
+ va_list copy_of_args;
+ va_copy (copy_of_args, *args);
+
+ // TODO: when should localization happen?
+ text_info text (gmsgid, ©_of_args, errno);
+ pretty_printer pp;
+ pp_show_color (&pp) = colorize;
+ pp.set_output_stream (nullptr);
+ pp_format (&pp, &text);
+ pp_output_formatted_text (&pp, nullptr);
+ label_text result = label_text::take (xstrdup (pp_formatted_text (&pp)));
+
+ va_end (copy_of_args);
+
+ return result;
+ }
+
+ const diagnostic_physical_location *m_physical_loc;
+ const diagnostic_logical_location *m_logical_loc;
+ unsigned m_stack_depth;
+ label_text m_desc_uncolored;
+ label_text m_desc_colored;
+};
+
+class libdiagnostics_path_thread : public diagnostic_thread
+{
+public:
+ libdiagnostics_path_thread (const char *name) : m_name (name) {}
+ label_text get_name (bool) const final override
+ {
+ return label_text::borrow (m_name);
+ }
+
+private:
+ const char *m_name; // has been i18n-ed and formatted
+};
+
+/* This has to be a "struct" as it is exposed in the C API. */
+
+struct diagnostic_execution_path : public diagnostic_path
+{
+ diagnostic_execution_path ()
+ : m_thread ("")
+ {
+ }
+
+ diagnostic_event_id_t
+ add_event_va (const diagnostic_physical_location *physical_loc,
+ const diagnostic_logical_location *logical_loc,
+ unsigned stack_depth,
+ const char *gmsgid,
+ va_list *args)
+ {
+ m_events.push_back (::make_unique<libdiagnostics_path_event> (physical_loc,
+ logical_loc,
+ stack_depth,
+ gmsgid,
+ args));
+ return m_events.size () - 1;
+ }
+
+ /* diagnostic_path vfunc implementations. */
+
+ unsigned num_events () const final override
+ {
+ return m_events.size ();
+ }
+ const diagnostic_event & get_event (int idx) const final override
+ {
+ return *m_events[idx];
+ }
+ unsigned num_threads () const final override { return 1; }
+ const diagnostic_thread &
+ get_thread (diagnostic_thread_id_t) const final override
+ {
+ return m_thread;
+ }
+
+ bool
+ same_function_p (int event_idx_a,
+ int event_idx_b) const final override
+ {
+ const logical_location *logical_loc_a
+ = m_events[event_idx_a]->get_logical_location ();
+ const logical_location *logical_loc_b
+ = m_events[event_idx_b]->get_logical_location ();
+
+ // TODO:
+ /* Pointer equality, so we may want to uniqify logical loc ptrs. */
+ return logical_loc_a == logical_loc_b;
+ }
+
+private:
+ libdiagnostics_path_thread m_thread;
+ std::vector<std::unique_ptr<libdiagnostics_path_event>> m_events;
+};
+
+/* This has to be a "struct" as it is exposed in the C API. */
+
+struct diagnostic
+{
+public:
+ diagnostic (diagnostic_manager &diag_mgr,
+ enum diagnostic_level level)
+ : m_diag_mgr (diag_mgr),
+ m_level (level),
+ m_rich_loc (diag_mgr.get_line_table ()),
+ m_logical_loc (nullptr),
+ m_path (nullptr)
+ {}
+
+ diagnostic_manager &get_manager () const
+ {
+ return m_diag_mgr;
+ }
+
+ enum diagnostic_level get_level () const { return m_level; }
+
+ rich_location *get_rich_location () { return &m_rich_loc; }
+ const diagnostic_metadata *get_metadata () { return &m_metadata; }
+
+ void set_cwe (unsigned cwe_id)
+ {
+ m_metadata.add_cwe (cwe_id);
+ }
+
+ void add_rule (const char *title,
+ const char *url)
+ {
+ std::unique_ptr<impl_rule> rule = ::make_unique<impl_rule> (title, url);
+ m_metadata.add_rule (*rule.get ());
+ m_rules.push_back (std::move (rule));
+ }
+
+ void set_location (const diagnostic_physical_location *loc)
+ {
+ m_rich_loc.set_range (0, as_location_t (loc), SHOW_RANGE_WITH_CARET);
+ }
+
+ void
+ add_location (const diagnostic_physical_location *loc)
+ {
+ m_rich_loc.add_range (as_location_t (loc),
+ SHOW_RANGE_WITHOUT_CARET);
+ }
+
+ void
+ add_location_with_label (const diagnostic_physical_location *loc,
+ const char *text)
+ {
+ std::unique_ptr<range_label> label
+ = ::make_unique <impl_range_label> (text);
+ m_rich_loc.add_range (as_location_t (loc),
+ SHOW_RANGE_WITHOUT_CARET,
+ label.get ());
+ m_labels.push_back (std::move (label));
+ }
+
+ void
+ set_logical_location (const diagnostic_logical_location *logical_loc)
+ {
+ m_logical_loc = logical_loc;
+ }
+ const diagnostic_logical_location *get_logical_location () const
+ {
+ return m_logical_loc;
+ }
+
+ diagnostic_execution_path *
+ add_execution_path ()
+ {
+ m_path = ::make_unique<diagnostic_execution_path> ();
+ m_rich_loc.set_path (m_path.get ());
+ return m_path.get ();
+ }
+
+ void
+ take_execution_path (diagnostic_execution_path *path)
+ {
+ m_path = std::unique_ptr<diagnostic_execution_path> (path);
+ m_rich_loc.set_path (path);
+ }
+
+private:
+ diagnostic_manager &m_diag_mgr;
+ enum diagnostic_level m_level;
+ impl_rich_location m_rich_loc;
+ const diagnostic_logical_location *m_logical_loc;
+ diagnostic_metadata m_metadata;
+ std::vector<std::unique_ptr<range_label>> m_labels;
+ std::vector<std::unique_ptr<impl_rule>> m_rules;
+ std::unique_ptr<diagnostic_execution_path> m_path;
+};
+
+static diagnostic_t
+diagnostic_t_from_diagnostic_level (enum diagnostic_level level)
+{
+ switch (level)
+ {
+ default:
+ gcc_unreachable ();
+ case DIAGNOSTIC_LEVEL_ERROR:
+ return DK_ERROR;
+ case DIAGNOSTIC_LEVEL_WARNING:
+ return DK_WARNING;
+ case DIAGNOSTIC_LEVEL_NOTE:
+ return DK_NOTE;
+ case DIAGNOSTIC_LEVEL_SORRY:
+ return DK_SORRY;
+ }
+}
+
+/* class impl_diagnostic_client_data_hooks. */
+
+const client_version_info *
+impl_diagnostic_client_data_hooks::get_any_version_info () const
+{
+ return m_mgr.get_client_version_info ();
+}
+
+const logical_location *
+impl_diagnostic_client_data_hooks::get_current_logical_location () const
+{
+ gcc_assert (m_mgr.get_current_diag ());
+
+ return m_mgr.get_current_diag ()->get_logical_location ();
+}
+
+const char *
+impl_diagnostic_client_data_hooks::
+maybe_get_sarif_source_language (const char *filename) const
+{
+ return m_mgr.maybe_get_sarif_source_language (filename);
+}
+
+void
+impl_diagnostic_client_data_hooks::
+add_sarif_invocation_properties (sarif_object &) const
+{
+ // No-op.
+}
+
+/* class sink. */
+
+void
+sink::emit (diagnostic &diag, const char *msgid, va_list *args)
+{
+ diagnostic_info info;
+GCC_DIAGNOSTIC_PUSH_IGNORED(-Wsuggest-attribute=format)
+ diagnostic_set_info (&info, msgid, args, diag.get_rich_location (),
+ diagnostic_t_from_diagnostic_level (diag.get_level ()));
+GCC_DIAGNOSTIC_POP
+ info.metadata = diag.get_metadata ();
+ diagnostic_report_diagnostic (&m_dc, &info);
+}
+
+sink::sink (diagnostic_manager &mgr)
+: m_mgr (mgr)
+{
+ diagnostic_initialize (&m_dc, 0);
+ m_dc.m_client_aux_data = this;
+ m_dc.set_client_data_hooks
+ (::make_unique<impl_diagnostic_client_data_hooks> (mgr));
+}
+
+sink::~sink ()
+{
+ diagnostic_finish (&m_dc);
+}
+
+/* struct diagnostic_text_sink : public sink. */
+
+static diagnostic_color_rule_t
+get_color_rule (enum diagnostic_colorize colorize)
+{
+ switch (colorize)
+ {
+ default:
+ gcc_unreachable ();
+ case DIAGNOSTIC_COLORIZE_IF_TTY:
+ return DIAGNOSTICS_COLOR_AUTO;
+ break;
+ case DIAGNOSTIC_COLORIZE_NO:
+ return DIAGNOSTICS_COLOR_NO;
+ break;
+ case DIAGNOSTIC_COLORIZE_YES:
+ return DIAGNOSTICS_COLOR_YES;
+ break;
+ }
+}
+
+diagnostic_text_sink::diagnostic_text_sink (diagnostic_manager &mgr,
+ FILE *dst_stream,
+ enum diagnostic_colorize colorize)
+: sink (mgr),
+ m_current_logical_loc (nullptr)
+{
+ m_dc.set_show_cwe (true);
+ m_dc.set_show_rules (true);
+
+ diagnostic_color_init (&m_dc, get_color_rule (colorize));
+ diagnostic_urls_init (&m_dc);
+
+ auto text_format = ::make_unique<diagnostic_text_output_format> (m_dc, true);
+ text_format->get_printer ()->set_output_stream (dst_stream);
+ m_dc.set_output_format (std::move (text_format));
+ diagnostic_text_starter (&m_dc)
+ = [] (diagnostic_text_output_format &text_format,
+ const diagnostic_info *info)
+ {
+ diagnostic_context &dc = text_format.get_context ();
+ diagnostic_text_sink *sink
+ = static_cast<diagnostic_text_sink *> (dc.m_client_aux_data);
+ sink->on_begin_text_diagnostic (text_format, info);
+ };
+ m_dc.set_show_cwe (true);
+ m_dc.set_show_rules (true);
+ m_dc.m_show_column = true;
+ m_dc.m_source_printing.enabled = true;
+ m_dc.m_source_printing.colorize_source_p = true;
+
+ /* We don't currently expose a way for clients to manipulate the
+ following. */
+ m_dc.m_source_printing.show_labels_p = true;
+ m_dc.m_source_printing.show_line_numbers_p = true;
+ m_dc.m_source_printing.min_margin_width = 6;
+ m_dc.set_path_format (DPF_INLINE_EVENTS);
+}
+
+void
+diagnostic_text_sink::set_colorize (enum diagnostic_colorize colorize)
+{
+ diagnostic_color_init (&m_dc, get_color_rule (colorize));
+}
+
+void
+diagnostic_text_sink::
+on_begin_text_diagnostic (diagnostic_text_output_format &text_format,
+ const diagnostic_info *info)
+{
+ const diagnostic *diag = m_mgr.get_current_diag ();
+ gcc_assert (diag);
+ pretty_printer *pp = text_format.get_printer ();
+ const diagnostic_logical_location *diag_logical_loc
+ = diag->get_logical_location ();
+ if (m_current_logical_loc != diag_logical_loc)
+ {
+ m_current_logical_loc = diag_logical_loc;
+ if (m_current_logical_loc)
+ {
+ pp_set_prefix (pp, nullptr);
+ switch (m_current_logical_loc->get_kind ())
+ {
+ default:
+ break;
+ case LOGICAL_LOCATION_KIND_FUNCTION:
+ if (const char *name
+ = m_current_logical_loc->get_name_with_scope ())
+ {
+ pp_printf (pp, _("In function %qs"), name);
+ pp_character (pp, ':');
+ pp_newline (pp);
+ }
+ break;
+ // TODO: handle other cases
+ }
+ }
+ }
+ pp_set_prefix (pp,
+ text_format.build_prefix (*info));
+}
+
+/* class sarif_sink : public sink. */
+
+sarif_sink::sarif_sink (diagnostic_manager &mgr,
+ FILE *dst_stream,
+ const diagnostic_file *main_input_file,
+ enum sarif_version version)
+: sink (mgr)
+{
+ const char *main_input_filename = main_input_file->get_name ();
+ diagnostic_output_format_init_sarif_stream (m_dc,
+ mgr.get_line_table (),
+ main_input_filename,
+ true,
+ version,
+ dst_stream);
+}
+
+/* struct diagnostic_manager. */
+
+void
+diagnostic_manager::write_patch (FILE *dst_stream)
+{
+ pretty_printer pp;
+ pp.set_output_stream (dst_stream);
+ m_edit_context.print_diff (&pp, true);
+ pp_flush (&pp);
+}
+
+void
+diagnostic_manager::emit (diagnostic &diag, const char *msgid, va_list *args)
+{
+ set_line_table_global ();
+
+ m_current_diag = &diag;
+ for (auto &sink : m_sinks)
+ {
+ va_list arg_copy;
+ va_copy (arg_copy, *args);
+ sink->emit (diag, msgid, &arg_copy);
+ }
+
+ rich_location *rich_loc = diag.get_rich_location ();
+ if (rich_loc->fixits_can_be_auto_applied_p ())
+ m_edit_context.add_fixits (rich_loc);
+
+ m_current_diag = nullptr;
+}
+
+diagnostic_execution_path *
+diagnostic_manager::new_execution_path ()
+{
+ return new diagnostic_execution_path ();
+}
+
+/* Error-checking at the API boundary. */
+
+#define FAIL_IF_NULL(PTR_ARG) \
+ do { \
+ volatile const void *p = (PTR_ARG); \
+ if (!p) { \
+ fprintf (stderr, "%s: %s must be non-NULL\n", \
+ __func__, #PTR_ARG); \
+ abort (); \
+ } \
+ } while (0)
+
+/* Public entrypoints. */
+
+/* Public entrypoint for clients to acquire a diagnostic_manager. */
+
+diagnostic_manager *
+diagnostic_manager_new (void)
+{
+ return new diagnostic_manager ();
+}
+
+/* Public entrypoint for clients to release a diagnostic_manager. */
+
+void
+diagnostic_manager_release (diagnostic_manager *diag_mgr)
+{
+ delete diag_mgr;
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_manager_set_tool_name (diagnostic_manager *diag_mgr,
+ const char *value)
+{
+ FAIL_IF_NULL (diag_mgr);
+ FAIL_IF_NULL (value);
+
+ diag_mgr->get_client_version_info ()->m_name.set (value);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_manager_set_full_name (diagnostic_manager *diag_mgr,
+ const char *value)
+{
+ FAIL_IF_NULL (diag_mgr);
+ FAIL_IF_NULL (value);
+
+ diag_mgr->get_client_version_info ()->m_full_name.set (value);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_manager_set_version_string (diagnostic_manager *diag_mgr,
+ const char *value)
+{
+ FAIL_IF_NULL (diag_mgr);
+ FAIL_IF_NULL (value);
+
+ diag_mgr->get_client_version_info ()->m_version.set (value);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_manager_set_version_url (diagnostic_manager *diag_mgr,
+ const char *value)
+{
+ FAIL_IF_NULL (diag_mgr);
+ FAIL_IF_NULL (value);
+
+ diag_mgr->get_client_version_info ()->m_version_url.set (value);
+}
+
+/* Public entrypoint. */
+
+diagnostic_text_sink *
+diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr,
+ FILE *dst_stream,
+ enum diagnostic_colorize colorize)
+{
+ FAIL_IF_NULL (diag_mgr);
+ FAIL_IF_NULL (dst_stream);
+
+ diagnostic_text_sink *result
+ = new diagnostic_text_sink (*diag_mgr, dst_stream, colorize);
+ diag_mgr->add_sink (std::unique_ptr<sink> (result));
+ return result;
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_text_sink_set_source_printing_enabled (diagnostic_text_sink *text_sink,
+ int value)
+{
+ FAIL_IF_NULL (text_sink);
+
+ text_sink->get_source_printing_options ().enabled = value;
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_text_sink_set_colorize (diagnostic_text_sink *text_sink,
+ enum diagnostic_colorize colorize)
+{
+ FAIL_IF_NULL (text_sink);
+
+ text_sink->set_colorize (colorize);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_text_sink_set_labelled_source_colorization_enabled (diagnostic_text_sink *text_sink,
+ int value)
+{
+ FAIL_IF_NULL (text_sink);
+
+ text_sink->get_source_printing_options ().colorize_source_p = value;
+}
+
+
+/* Public entrypoint. */
+
+void
+diagnostic_manager_add_sarif_sink (diagnostic_manager *diag_mgr,
+ FILE *dst_stream,
+ const diagnostic_file *main_input_file,
+ enum diagnostic_sarif_version version)
+{
+ FAIL_IF_NULL (diag_mgr);
+ FAIL_IF_NULL (dst_stream);
+ FAIL_IF_NULL (main_input_file);
+
+ enum sarif_version internal_version;
+ switch (version)
+ {
+ default:
+ fprintf (stderr, "%s: unrecognized value for version: %i\n",
+ __func__, (int)version);
+ abort ();
+ case DIAGNOSTIC_SARIF_VERSION_2_1_0:
+ internal_version = sarif_version::v2_1_0;
+ break;
+ case DIAGNOSTIC_SARIF_VERSION_2_2_PRERELEASE:
+ internal_version = sarif_version::v2_2_prerelease_2024_08_08;
+ break;
+ }
+
+ diag_mgr->add_sink (make_unique<sarif_sink> (*diag_mgr,
+ dst_stream,
+ main_input_file,
+ internal_version));
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_manager_write_patch (diagnostic_manager *diag_mgr,
+ FILE *dst_stream)
+{
+ FAIL_IF_NULL (diag_mgr);
+ FAIL_IF_NULL (dst_stream);
+
+ diag_mgr->write_patch (dst_stream);
+}
+
+/* Public entrypoint. */
+
+const diagnostic_file *
+diagnostic_manager_new_file (diagnostic_manager *diag_mgr,
+ const char *name,
+ const char *sarif_source_language)
+{
+ FAIL_IF_NULL (diag_mgr);
+ FAIL_IF_NULL (name);
+
+ return diag_mgr->new_file (name, sarif_source_language);
+}
+
+void
+diagnostic_manager_debug_dump_file (diagnostic_manager *,
+ const diagnostic_file *file,
+ FILE *out)
+{
+ FAIL_IF_NULL (out);
+ if (file)
+ {
+ if (file->get_sarif_source_language ())
+ {
+ fprintf (out, "file(name=\"%s\", sarif_source_language=\"%s\")",
+ file->get_name (),
+ file->get_sarif_source_language ());
+ }
+ else
+ {
+ fprintf (out, "file(name=\"%s\")",
+ file->get_name ());
+ }
+ }
+ else
+ fprintf (out, "(null)");
+}
+
+
+/* Public entrypoint. */
+
+const diagnostic_physical_location *
+diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr,
+ const diagnostic_file *file,
+ diagnostic_line_num_t linenum)
+{
+ FAIL_IF_NULL (diag_mgr);
+ FAIL_IF_NULL (file);
+
+ return diag_mgr->new_location_from_file_and_line (file, linenum);
+}
+
+/* Public entrypoint. */
+
+const diagnostic_physical_location *
+diagnostic_manager_new_location_from_file_line_column (diagnostic_manager *diag_mgr,
+ const diagnostic_file *file,
+ diagnostic_line_num_t line_num,
+ diagnostic_column_num_t column_num)
+{
+ FAIL_IF_NULL (diag_mgr);
+ FAIL_IF_NULL (file);
+
+ return diag_mgr->new_location_from_file_line_column (file,
+ line_num,
+ column_num);
+}
+
+/* Public entrypoint. */
+
+const diagnostic_physical_location *
+diagnostic_manager_new_location_from_range (diagnostic_manager *diag_mgr,
+ const diagnostic_physical_location *loc_caret,
+ const diagnostic_physical_location *loc_start,
+ const diagnostic_physical_location *loc_end)
+{
+ FAIL_IF_NULL (diag_mgr);
+
+ return diag_mgr->new_location_from_range (loc_caret,
+ loc_start,
+ loc_end);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_manager_debug_dump_location (const diagnostic_manager *diag_mgr,
+ const diagnostic_physical_location *loc,
+ FILE *out)
+{
+ FAIL_IF_NULL (diag_mgr);
+ FAIL_IF_NULL (out);
+
+ if (loc)
+ {
+ const location_t cpplib_loc = as_location_t (loc);
+ diag_mgr->set_line_table_global ();
+ const expanded_location exp_loc (expand_location (cpplib_loc));
+
+ diagnostic_context dc;
+ diagnostic_initialize (&dc, 0);
+ dc.m_show_column = true;
+
+ diagnostic_text_output_format text_format (dc);
+ label_text loc_text = text_format.get_location_text (exp_loc);
+ fprintf (out, "%s", loc_text.get ());
+
+ diagnostic_finish (&dc);
+ }
+ else
+ fprintf (out, "(null)");
+}
+
+/* Public entrypoint. */
+
+const diagnostic_logical_location *
+diagnostic_manager_new_logical_location (diagnostic_manager *diag_mgr,
+ enum diagnostic_logical_location_kind_t kind,
+ const diagnostic_logical_location *parent,
+ const char *short_name,
+ const char *fully_qualified_name,
+ const char *decorated_name)
+{
+ FAIL_IF_NULL (diag_mgr);
+
+ return diag_mgr->new_logical_location (kind,
+ parent,
+ short_name,
+ fully_qualified_name,
+ decorated_name);
+}
+
+void
+diagnostic_manager_debug_dump_logical_location (const diagnostic_manager *diag_mgr,
+ const diagnostic_logical_location *loc,
+ FILE *out)
+{
+ FAIL_IF_NULL (diag_mgr);
+ FAIL_IF_NULL (out);
+
+ if (loc)
+ {
+ fprintf (out, "logical_location(kind=");
+ switch (loc->get_external_kind ())
+ {
+ default:
+ gcc_unreachable ();
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION:
+ fprintf (out, "function");
+ break;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER:
+ fprintf (out, "member");
+ break;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE:
+ fprintf (out, "module");
+ break;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE:
+ fprintf (out, "namespace");
+ break;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE:
+ fprintf (out, "file");
+ break;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE:
+ fprintf (out, "return_type");
+ break;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER:
+ fprintf (out, "parameter");
+ break;
+ case DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE:
+ fprintf (out, "variable");
+ break;
+ }
+ if (const diagnostic_logical_location *parent = loc->get_parent ())
+ diagnostic_manager_debug_dump_logical_location (diag_mgr,
+ parent,
+ out);
+ if (const char *val = loc->get_short_name ())
+ fprintf (out, ", short_name=\"%s\"", val);
+ if (const char *val = loc->get_name_with_scope ())
+ fprintf (out, ", fully_qualified_name=\"%s\"", val);
+ if (const char *val = loc->get_internal_name ())
+ fprintf (out, ", decorated_name=\"%s\"", val);
+ fprintf (out, ")");
+ }
+ else
+ fprintf (out, "(null)");
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_manager_begin_group (diagnostic_manager *diag_mgr)
+{
+ FAIL_IF_NULL (diag_mgr);
+ diag_mgr->begin_group ();
+}
+
+/* Public entrypoint. */
+
+extern void
+diagnostic_manager_end_group (diagnostic_manager *diag_mgr)
+{
+ FAIL_IF_NULL (diag_mgr);
+ diag_mgr->end_group ();
+}
+
+/* Public entrypoint. */
+
+diagnostic *
+diagnostic_begin (diagnostic_manager *diag_mgr,
+ enum diagnostic_level level)
+{
+ FAIL_IF_NULL (diag_mgr);
+
+ return new diagnostic (*diag_mgr, level);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_set_cwe (diagnostic *diag,
+ unsigned cwe_id)
+{
+ FAIL_IF_NULL (diag);
+
+ diag->set_cwe (cwe_id);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_add_rule (diagnostic *diag,
+ const char *title,
+ const char *url)
+{
+ FAIL_IF_NULL (diag);
+
+ diag->add_rule (title, url);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_set_location (diagnostic *diag,
+ const diagnostic_physical_location *loc)
+{
+ FAIL_IF_NULL (diag);
+ diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+
+ diag->set_location (loc);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_add_location (diagnostic *diag,
+ const diagnostic_physical_location *loc)
+{
+ FAIL_IF_NULL (diag);
+ diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+
+ diag->add_location (loc);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_add_location_with_label (diagnostic *diag,
+ const diagnostic_physical_location *loc,
+ const char *text)
+{
+ FAIL_IF_NULL (diag);
+ diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+ FAIL_IF_NULL (text);
+
+ diag->add_location_with_label (loc, text);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_set_logical_location (diagnostic *diag,
+ const diagnostic_logical_location *logical_loc)
+{
+ FAIL_IF_NULL (diag);
+
+ diag->set_logical_location (logical_loc);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_add_fix_it_hint_insert_before (diagnostic *diag,
+ const diagnostic_physical_location *loc,
+ const char *addition)
+{
+ FAIL_IF_NULL (diag);
+ diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+ FAIL_IF_NULL (addition);
+
+ diag->get_manager ().set_line_table_global ();
+ diag->get_rich_location ()->add_fixit_insert_before (as_location_t (loc),
+ addition);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_add_fix_it_hint_insert_after (diagnostic *diag,
+ const diagnostic_physical_location *loc,
+ const char *addition)
+{
+ FAIL_IF_NULL (diag);
+ diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+ FAIL_IF_NULL (addition);
+
+ diag->get_manager ().set_line_table_global ();
+ diag->get_rich_location ()->add_fixit_insert_after (as_location_t (loc),
+ addition);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_add_fix_it_hint_replace (diagnostic *diag,
+ const diagnostic_physical_location *loc,
+ const char *replacement)
+{
+ FAIL_IF_NULL (diag);
+ diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+ FAIL_IF_NULL (replacement);
+
+ diag->get_manager ().set_line_table_global ();
+ diag->get_rich_location ()->add_fixit_replace (as_location_t (loc),
+ replacement);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_add_fix_it_hint_delete (diagnostic *diag,
+ const diagnostic_physical_location *loc)
+{
+ FAIL_IF_NULL (diag);
+ diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+
+ diag->get_manager ().set_line_table_global ();
+ diag->get_rich_location ()->add_fixit_remove (as_location_t (loc));
+}
+
+/* Public entrypoint. */
+
+diagnostic_execution_path *
+diagnostic_add_execution_path (diagnostic *diag)
+{
+ FAIL_IF_NULL (diag);
+
+ return diag->add_execution_path ();
+}
+
+/* Public entrypoint. */
+
+diagnostic_execution_path *
+diagnostic_manager_new_execution_path (diagnostic_manager *manager)
+{
+ FAIL_IF_NULL (manager);
+
+ return manager->new_execution_path ();
+}
+
+/* Public entrypoint. */
+
+extern void
+diagnostic_take_execution_path (diagnostic *diag,
+ diagnostic_execution_path *path)
+{
+ FAIL_IF_NULL (diag);
+ FAIL_IF_NULL (path);
+
+ return diag->take_execution_path (path);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_execution_path_release (diagnostic_execution_path *path)
+{
+ delete path;
+}
+
+/* Public entrypoint. */
+
+diagnostic_event_id
+diagnostic_execution_path_add_event (diagnostic_execution_path *path,
+ const diagnostic_physical_location *physical_loc,
+ const diagnostic_logical_location *logical_loc,
+ unsigned stack_depth,
+ const char *gmsgid, ...)
+{
+ FAIL_IF_NULL (path);
+ FAIL_IF_NULL (gmsgid);
+
+ va_list args;
+ va_start (args, gmsgid);
+ diagnostic_event_id_t result = path->add_event_va (physical_loc,
+ logical_loc,
+ stack_depth,
+ gmsgid, &args);
+ va_end (args);
+
+ return as_diagnostic_event_id (result);
+}
+
+/* Public entrypoint. */
+
+diagnostic_event_id
+diagnostic_execution_path_add_event_va (diagnostic_execution_path *path,
+ const diagnostic_physical_location *physical_loc,
+ const diagnostic_logical_location *logical_loc,
+ unsigned stack_depth,
+ const char *gmsgid,
+ va_list *args)
+{
+ FAIL_IF_NULL (path);
+ FAIL_IF_NULL (gmsgid);
+
+ diagnostic_event_id_t result = path->add_event_va (physical_loc,
+ logical_loc,
+ stack_depth,
+ gmsgid, args);
+ return as_diagnostic_event_id (result);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_finish (diagnostic *diag, const char *gmsgid, ...)
+{
+ FAIL_IF_NULL (diag);
+
+ va_list args;
+ va_start (args, gmsgid);
+ diagnostic_finish_va (diag, gmsgid, &args);
+ va_end (args);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_finish_va (diagnostic *diag, const char *gmsgid, va_list *args)
+{
+ FAIL_IF_NULL (diag);
+
+ if (const char *tool_name
+ = diag->get_manager ().get_client_version_info ()->m_name.get_str ())
+ progname = tool_name;
+ else
+ progname = "progname";
+ auto_diagnostic_group d;
+ diag->get_manager ().emit (*diag, gmsgid, args);
+ delete diag;
+}
--- /dev/null
+/* A pure C API for emitting diagnostics.
+ Copyright (C) 2023-2024 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC 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, or (at your option)
+any later version.
+
+GCC 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 GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef LIBDIAGNOSTICS_H
+#define LIBDIAGNOSTICS_H
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**********************************************************************
+ Compatibility macros.
+ **********************************************************************/
+
+/* This macro simplifies testing whether we are using gcc, and if it
+ is of a particular minimum version. (Both major & minor numbers are
+ significant.) This macro will evaluate to 0 if we are not using
+ gcc at all. */
+#define LIBDIAGNOSTICS_GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__)
+
+/**********************************************************************
+ Macros for attributes.
+ **********************************************************************/
+
+# if (LIBDIAGNOSTICS_GCC_VERSION >= 3003)
+# define LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL(ARG_NUM) __attribute__ ((__nonnull__ (ARG_NUM)))
+# else
+# define LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL(ARG_NUM)
+# endif /* GNUC >= 3.3 */
+
+#define LIBDIAGNOSTICS_PARAM_CAN_BE_NULL(ARG_NUM)
+ /* empty; for the human reader */
+
+#define LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(FMT_ARG_NUM, ARGS_ARG_NUM) \
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (FMT_ARG_NUM)
+ /* In theory we'd also add
+ __attribute__ ((__format__ (__gcc_diag__, FMT_ARG_NUM, ARGS_ARG_NUM)))
+ if LIBDIAGNOSTICS_GCC_VERSION >= 4001
+ However, doing so leads to warnings from -Wformat-diag, which is part
+ of -Wall but undocumented, and much fussier than I'd want to inflict
+ on users of libdiagnostics. */
+
+/**********************************************************************
+ Data structures and types.
+ All structs within the API are opaque.
+ **********************************************************************/
+
+/* An opaque bundle of state for a client of the library.
+ Has zero of more "sinks" to which diagnostics are emitted.
+ Responsibilities:
+ - location-management
+ - caching of source file content
+ - patch generation. */
+typedef struct diagnostic_manager diagnostic_manager;
+
+/* Types relating to diagnostic output sinks. */
+
+typedef struct diagnostic_text_sink diagnostic_text_sink;
+
+/* An enum for determining if we should colorize a text output sink. */
+enum diagnostic_colorize
+{
+ DIAGNOSTIC_COLORIZE_IF_TTY,
+ DIAGNOSTIC_COLORIZE_NO,
+ DIAGNOSTIC_COLORIZE_YES
+};
+
+/* An enum for choosing the SARIF version for a SARIF output sink. */
+
+enum diagnostic_sarif_version
+{
+ DIAGNOSTIC_SARIF_VERSION_2_1_0,
+ DIAGNOSTIC_SARIF_VERSION_2_2_PRERELEASE
+};
+
+/* Types relating to "physical" source locations i.e. locations within
+ specific files expressed via line/column. */
+
+/* Opaque type describing a particular input file. */
+typedef struct diagnostic_file diagnostic_file;
+
+/* Opaque type representing a key into a database of source locations within
+ a diagnostic_manager. Locations are created by various API calls into
+ the diagnostic_manager expressing source code points and ranges. They
+ persist until the diagnostic_manager is released, which cleans them
+ up.
+
+ NULL means "UNKNOWN", and can be returned by the manager as a
+ fallback when a problem occurs (e.g. too many locations).
+
+ A diagnostic_location can be a single point within the source code,
+ such as here (at the the '"' at the start of the string literal):
+
+ | int i = "foo";
+ | ^
+
+ or be a range with a start and finish, and a "caret" location.
+
+ | a = (foo && bar)
+ | ~~~~~^~~~~~~
+
+ where the caret here is at the first "&", and the start and finish
+ are at the parentheses. */
+
+typedef struct diagnostic_physical_location diagnostic_physical_location;
+
+/* Types for storing line and column information in text files.
+
+ Both libdiagnostics and emacs number source *lines* starting at 1, but
+ they have differing conventions for *columns*.
+
+ libdiagnostics uses a 1-based convention for source columns,
+ whereas Emacs's M-x column-number-mode uses a 0-based convention.
+
+ For example, an error in the initial, left-hand
+ column of source line 3 is reported by libdiagnostics as:
+
+ some-file.c:3:1: error: ...etc...
+
+ On navigating to the location of that error in Emacs
+ (e.g. via "next-error"),
+ the locus is reported in the Mode Line
+ (assuming M-x column-number-mode) as:
+
+ some-file.c 10% (3, 0)
+
+ i.e. "3:1:" in libdiagnostics corresponds to "(3, 0)" in Emacs. */
+
+typedef unsigned int diagnostic_line_num_t;
+typedef unsigned int diagnostic_column_num_t;
+
+/* An opaque type describing a "logical" source location
+ e.g. "within function 'foo'". */
+
+typedef struct diagnostic_logical_location diagnostic_logical_location;
+
+/* An enum for discriminating between different kinds of logical location
+ for a diagnostic.
+
+ Roughly corresponds to logicalLocation's "kind" property in SARIF v2.1.0
+ (section 3.33.7). */
+
+enum diagnostic_logical_location_kind_t
+{
+ DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION,
+ DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER,
+ DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE,
+ DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE,
+ DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE,
+ DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE,
+ DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER,
+ DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE
+};
+
+/* A "diagnostic" is an opaque bundle of state for a particular
+ diagnostic that is being constructed in memory.
+
+ A diagnostic has a primary location and zero or more secondary
+ locations. For example:
+
+ | a = (foo && bar)
+ | ~~~~~^~~~~~~
+
+ This diagnostic has a single diagnostic_location, with the caret
+ at the first "&", and the start/finish at the parentheses.
+
+ Contrast with:
+
+ | a = (foo && bar)
+ | ~~~ ^~ ~~~
+
+ This diagnostic has three locations
+ - The primary location (at "&&") has its caret and start location at
+ the first "&" and end at the second "&.
+ - The secondary location for "foo" has its start and finish at the "f"
+ and "o" of "foo"; the caret is not flagged for display, but is perhaps at
+ the "f" of "foo".
+ - Similarly, the other secondary location (for "bar") has its start and
+ finish at the "b" and "r" of "bar"; the caret is not flagged for
+ display, but is perhaps at the"b" of "bar". */
+typedef struct diagnostic diagnostic;
+
+enum diagnostic_level
+{
+ DIAGNOSTIC_LEVEL_ERROR,
+ DIAGNOSTIC_LEVEL_WARNING,
+ DIAGNOSTIC_LEVEL_NOTE,
+
+ /* A problem where the input is valid, but the tool isn't
+ able to handle it. */
+ DIAGNOSTIC_LEVEL_SORRY
+};
+
+/* Types for working with execution paths. */
+typedef struct diagnostic_execution_path diagnostic_execution_path;
+typedef int diagnostic_event_id;
+
+/**********************************************************************
+ API entrypoints.
+ **********************************************************************/
+
+/* Create a new diagnostic_manager.
+ The client needs to call diagnostic_release_manager on it at some
+ point.
+ Note that no output sinks are created by default. */
+
+extern diagnostic_manager *
+diagnostic_manager_new (void);
+
+/* Release a diagnostic_manager.
+ This will flush output to all of the output sinks, and clean up. */
+
+extern void
+diagnostic_manager_release (diagnostic_manager *)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Optional metadata about the manager. */
+
+/* Set a string suitable for use as the value of the SARIF "name" property
+ (SARIF v2.1.0 section 3.19.8). */
+
+extern void
+diagnostic_manager_set_tool_name (diagnostic_manager *diag_mgr,
+ const char *value)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Set a string suitable for use as the value of the SARIF "fullName" property
+ (SARIF v2.1.0 section 3.19.9). */
+
+extern void
+diagnostic_manager_set_full_name (diagnostic_manager *diag_mgr,
+ const char *value)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Set a string suitable for use as the value of the SARIF "version" property
+ (SARIF v2.1.0 section 3.19.13). */
+
+extern void
+diagnostic_manager_set_version_string (diagnostic_manager *diag_mgr,
+ const char *value)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Set a string suitable for use as the value of the SARIF "informationUri"
+ property (SARIF v2.1.0 section 3.19.17). */
+
+extern void
+diagnostic_manager_set_version_url (diagnostic_manager *diag_mgr,
+ const char *value)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Destinations for diagnostics. */
+
+/* Add a new output sink to DIAG_MGR, which writes GCC-style diagnostics
+ to DST_STREAM.
+ Return a borrowed pointer to the sink, which is cleaned up when DIAG_MGR
+ is released.
+ DST_STREAM is borrowed, and must outlive DIAG_MGR.
+ The output for each diagnostic is written and flushed as each
+ diagnostic is finished. */
+
+extern diagnostic_text_sink *
+diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr,
+ FILE *dst_stream,
+ enum diagnostic_colorize colorize)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Functions to manipulate text sinks. */
+
+/* Enable/disable printing of source text in the text sink.
+ Default: enabled. */
+
+extern void
+diagnostic_text_sink_set_source_printing_enabled (diagnostic_text_sink *text_sink,
+ int value)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Update colorization of text sink. */
+
+extern void
+diagnostic_text_sink_set_colorize (diagnostic_text_sink *text_sink,
+ enum diagnostic_colorize colorize)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Enable/disable colorization of the characters of source text
+ that are underlined.
+ This should be true for clients that generate range information
+ (so that the ranges of code are colorized),
+ and false for clients that merely specify points within the
+ source code (to avoid e.g. colorizing just the first character in
+ a token, which would look strange).
+ Default: enabled. */
+
+extern void
+diagnostic_text_sink_set_labelled_source_colorization_enabled (diagnostic_text_sink *text_sink,
+ int value)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Add a new output sink to DIAG_MGR, which writes SARIF of the given
+ version to DST_STREAM.
+
+ The output is not written until DIAG_MGR is released.
+
+ DST_STREAM is borrowed, and must outlive DIAG_MGR.
+
+ For the result to be a valid SARIF file according to the schema,
+ DIAG_MGR must have had diagnostic_manager_set_tool_name called on it. */
+
+extern void
+diagnostic_manager_add_sarif_sink (diagnostic_manager *diag_mgr,
+ FILE *dst_stream,
+ const diagnostic_file *main_input_file,
+ enum diagnostic_sarif_version version)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* Write a patch to DST_STREAM consisting of all fix-it hints
+ on all diagnostics that have been finished on DIAG_MGR. */
+
+extern void
+diagnostic_manager_write_patch (diagnostic_manager *diag_mgr,
+ FILE *dst_stream)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Location management. */
+
+/* Create a new diagnostic_file * for file NAME.
+
+ Repeated calls with matching NAMEs will return the
+ same object.
+
+ If SARIF_SOURCE_LANGUAGE is non-NULL, it specifies a "sourceLanguage"
+ value for the file when use when writing SARIF.
+ See SARIF v2.1.0 Appendix J for suggested values for various
+ programmming languages. */
+
+extern const diagnostic_file *
+diagnostic_manager_new_file (diagnostic_manager *diag_mgr,
+ const char *name,
+ const char *sarif_source_language)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3);
+
+/* Write a representation of FILE to OUT, for debugging. */
+
+extern void
+diagnostic_manager_debug_dump_file (diagnostic_manager *diag_mgr,
+ const diagnostic_file *file,
+ FILE *out)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* Attempt to create a diagnostic_location representing
+ FILENAME:LINE_NUM, with no column information
+ (thus "the whole line"). */
+
+extern const diagnostic_physical_location *
+diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr,
+ const diagnostic_file *file,
+ diagnostic_line_num_t line_num)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Attempt to create a diagnostic_physical_location representing
+ FILENAME:LINE_NUM:COLUMN_NUM. */
+
+extern const diagnostic_physical_location *
+diagnostic_manager_new_location_from_file_line_column (diagnostic_manager *diag_mgr,
+ const diagnostic_file *file,
+ diagnostic_line_num_t line_num,
+ diagnostic_column_num_t column_num)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Attempt to create a diagnostic_physical_location representing a
+ range within a source file, with a highlighted "caret" location.
+
+ All must be within the same file, but they can be on different lines.
+
+ For example, consider the location of the binary expression below:
+
+ ...|__________1111111112222222
+ ...|12345678901234567890123456
+ ...|
+ 521|int sum (int foo, int bar)
+ 522|{
+ 523| return foo + bar;
+ ...| ~~~~^~~~~
+ 524|}
+
+ The location's caret is at the "+", line 523 column 15, but starts
+ earlier, at the "f" of "foo" at column 11. The finish is at the "r"
+ of "bar" at column 19. */
+
+extern const diagnostic_physical_location *
+diagnostic_manager_new_location_from_range (diagnostic_manager *diag_mgr,
+ const diagnostic_physical_location *loc_caret,
+ const diagnostic_physical_location *loc_start,
+ const diagnostic_physical_location *loc_end)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (4);
+
+/* Write a representation of LOC to OUT, for debugging. */
+
+extern void
+diagnostic_manager_debug_dump_location (const diagnostic_manager *diag_mgr,
+ const diagnostic_physical_location *loc,
+ FILE *out)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* A bundle of state describing a logical location in the user's source,
+ such as "in function 'foo'".
+
+ SHORT_NAME can be NULL, or else a string suitable for use by
+ the SARIF logicalLocation "name" property (SARIF v2.1.0 section 3.33.4).
+
+ FULLY_QUALIFIED_NAME can be NULL or else a string suitable for use by
+ the SARIF logicalLocation "fullyQualifiedName" property
+ (SARIF v2.1.0 section 3.33.5).
+
+ DECORATED_NAME can be NULL or else a string suitable for use by
+ the SARIF logicalLocation "decoratedName" property
+ (SARIF v2.1.0 section 3.33.6). */
+
+extern const diagnostic_logical_location *
+diagnostic_manager_new_logical_location (diagnostic_manager *diag_mgr,
+ enum diagnostic_logical_location_kind_t kind,
+ const diagnostic_logical_location *parent,
+ const char *short_name,
+ const char *fully_qualified_name,
+ const char *decorated_name)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (4)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (5)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (6);
+
+/* Write a representation of LOC to OUT, for debugging. */
+
+extern void
+diagnostic_manager_debug_dump_logical_location (const diagnostic_manager *diag_mgr,
+ const diagnostic_logical_location *loc,
+ FILE *out)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* Diagnostic groups. */
+
+/* Begin a diagnostic group. All diagnostics emitted within
+ DIAG_MGR after the first one will be treated as notes about
+ the initial diagnostic. */
+
+extern void
+diagnostic_manager_begin_group (diagnostic_manager *diag_mgr)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Finish a diagnostic group. */
+
+extern void
+diagnostic_manager_end_group (diagnostic_manager *diag_mgr)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Step-by-step creation of a diagnostic. */
+
+extern diagnostic *
+diagnostic_begin (diagnostic_manager *diag_mgr,
+ enum diagnostic_level level)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Associate this diagnostic with the given ID within
+ the Common Weakness Enumeration. */
+
+extern void
+diagnostic_set_cwe (diagnostic *diag,
+ unsigned cwe_id)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Associate this diagnostic with a particular rule that has been violated
+ (such as in a coding standard, or within a specification).
+ The rule must have at least one of a title and a URL, but these
+ can be NULL.
+ A diagnostic can be associated with zero or more rules. */
+
+extern void
+diagnostic_add_rule (diagnostic *diag,
+ const char *title,
+ const char *url)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3);
+
+/* Set the primary location of DIAG. */
+
+extern void
+diagnostic_set_location (diagnostic *diag,
+ const diagnostic_physical_location * loc)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2);
+
+/* Set the primary location of DIAG, with a label. */
+
+extern void
+diagnostic_set_location_with_label (diagnostic *diag,
+ const diagnostic_physical_location *loc,
+ const char *fmt, ...)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* Add a secondary location to DIAG. */
+
+extern void
+diagnostic_add_location (diagnostic *diag,
+ const diagnostic_physical_location * loc)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Add a secondary location to DIAG, with a label. */
+
+extern void
+diagnostic_add_location_with_label (diagnostic *diag,
+ const diagnostic_physical_location *loc,
+ const char *text)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* Set the logical location of DIAG. */
+
+extern void
+diagnostic_set_logical_location (diagnostic *diag,
+ const diagnostic_logical_location *logical_loc)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2);
+
+/* Fix-it hints. */
+
+extern void
+diagnostic_add_fix_it_hint_insert_before (diagnostic *diag,
+ const diagnostic_physical_location *loc,
+ const char *addition)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+extern void
+diagnostic_add_fix_it_hint_insert_after (diagnostic *diag,
+ const diagnostic_physical_location *loc,
+ const char *addition)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+extern void
+diagnostic_add_fix_it_hint_replace (diagnostic *diag,
+ const diagnostic_physical_location *loc,
+ const char *replacement)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+extern void
+diagnostic_add_fix_it_hint_delete (diagnostic *diag,
+ const diagnostic_physical_location *loc)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2);
+
+/* Create and borrow a pointer to an execution path for DIAG.
+ The path is automatically cleaned up when DIAG is finished. */
+
+extern diagnostic_execution_path *
+diagnostic_add_execution_path (diagnostic *diag)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Create a new execution path.
+ This is owned by the called and must have either
+ diagnostic_take_execution_path or diagnostic_execution_path_release
+ called on it. */
+
+extern diagnostic_execution_path *
+diagnostic_manager_new_execution_path (diagnostic_manager *manager)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Set DIAG to use PATH as its execution path, taking ownership of PATH. */
+
+extern void
+diagnostic_take_execution_path (diagnostic *diag,
+ diagnostic_execution_path *path)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Release ownership of PATH, which must not have been taken
+ by a diagnostic. */
+
+extern void
+diagnostic_execution_path_release (diagnostic_execution_path *path)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (1);
+
+/* Append an event to the end of PATH. */
+
+extern diagnostic_event_id
+diagnostic_execution_path_add_event (diagnostic_execution_path *path,
+ const diagnostic_physical_location *physical_loc,
+ const diagnostic_logical_location *logical_loc,
+ unsigned stack_depth,
+ const char *fmt, ...)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (5)
+ LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (5, 6);
+
+/* Append an event to the end of PATH. */
+
+extern diagnostic_event_id
+diagnostic_execution_path_add_event_va (diagnostic_execution_path *path,
+ const diagnostic_physical_location *physical_loc,
+ const diagnostic_logical_location *logical_loc,
+ unsigned stack_depth,
+ const char *fmt,
+ va_list *args)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (5)
+ LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (5, 0);
+
+/* Emit DIAG to all sinks of its manager, and release DIAG.
+ Use FMT for the message.
+ Note that this uses gcc's pretty-print format, which is *not* printf.
+ TODO: who is responsible for putting FMT through gettext? */
+
+extern void
+diagnostic_finish (diagnostic *diag, const char *fmt, ...)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+ LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 3);
+
+/* As diagnostic_finish, but with a va_list. */
+
+extern void
+diagnostic_finish_va (diagnostic *diag, const char *fmt, va_list *args)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+ LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 0);
+
+/* DEFERRED:
+ - thread-safety
+ - plural forms
+ - enum about what a "column number" means (bytes, unichars, etc)
+ - locations within binary files
+ - options and URLs for warnings
+ - enable/disable of warnings by kind
+ - plugin metadata. */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* LIBDIAGNOSTICS_H */
--- /dev/null
+# Linker script for libdiagnostics.so
+# Copyright (C) 2023-2024 Free Software Foundation, Inc.
+# Contributed by David Malcolm <dmalcolm@redhat.com>.
+#
+# This file is part of GCC.
+#
+# GCC 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, or (at your option)
+# any later version.
+#
+# GCC 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 GCC; see the file COPYING3. If not see
+# <http://www.gnu.org/licenses/>. */
+
+# The initial release of the library.
+LIBDIAGNOSTICS_ABI_0
+{
+ global:
+ # Keep this list in order of decls in header file.
+ diagnostic_manager_new;
+ diagnostic_manager_release;
+ diagnostic_manager_set_tool_name;
+ diagnostic_manager_set_full_name;
+ diagnostic_manager_set_version_string;
+ diagnostic_manager_set_version_url;
+ diagnostic_manager_add_text_sink;
+ diagnostic_text_sink_set_source_printing_enabled;
+ diagnostic_text_sink_set_colorize;
+ diagnostic_text_sink_set_labelled_source_colorization_enabled;
+ diagnostic_manager_add_sarif_sink;
+ diagnostic_manager_write_patch;
+ diagnostic_manager_new_file;
+ diagnostic_manager_debug_dump_file;
+ diagnostic_manager_new_location_from_file_and_line;
+ diagnostic_manager_new_location_from_file_line_column;
+ diagnostic_manager_new_location_from_range;
+ diagnostic_manager_debug_dump_location;
+ diagnostic_manager_new_logical_location;
+ diagnostic_manager_debug_dump_logical_location;
+ diagnostic_manager_begin_group;
+ diagnostic_manager_end_group;
+ diagnostic_begin;
+ diagnostic_set_cwe;
+ diagnostic_add_rule;
+ diagnostic_set_location;
+ diagnostic_set_location_with_label;
+ diagnostic_add_location;
+ diagnostic_add_location_with_label;
+ diagnostic_set_logical_location;
+ diagnostic_add_fix_it_hint_insert_before;
+ diagnostic_add_fix_it_hint_insert_after;
+ diagnostic_add_fix_it_hint_replace;
+ diagnostic_add_fix_it_hint_delete;
+
+ diagnostic_add_execution_path;
+ diagnostic_manager_new_execution_path;
+ diagnostic_take_execution_path;
+ diagnostic_execution_path_release;
+ diagnostic_execution_path_add_event;
+ diagnostic_execution_path_add_event_va;
+
+ diagnostic_finish;
+ diagnostic_finish_va;
+
+ local: *;
+};
--- /dev/null
+# Test code for libdiagnostics.so
+#
+# We will compile each of libdiagnostics.dg/test-*.{c,cc} into an executable
+# dynamically linked against libdiagnostics.so, and then run each
+# such executable.
+#
+# These executables call into the libdiagnostics.so API to emit diagnostics,
+# sometimes in text form, and other times in SARIF form.
+
+# Kludge alert:
+# We need g++_init so that it can find the stdlib include path.
+#
+# g++_init (in lib/g++.exp) uses g++_maybe_build_wrapper,
+# which normally comes from the definition of
+# ${tool}_maybe_build_wrapper within lib/wrapper.exp.
+#
+# However, for us, ${tool} is "libdiagnostics".
+# Hence we load wrapper.exp with tool == "g++", so that
+# g++_maybe_build_wrapper is defined.
+set tool g++
+load_lib wrapper.exp
+set tool libdiagnostics
+
+load_lib dg.exp
+load_lib prune.exp
+load_lib target-supports.exp
+load_lib gcc-defs.exp
+load_lib timeout.exp
+load_lib target-libpath.exp
+load_lib gcc.exp
+load_lib g++.exp
+load_lib dejagnu.exp
+load_lib target-supports-dg.exp
+load_lib valgrind.exp
+load_lib scansarif.exp
+load_lib dg-test-cleanup.exp
+
+# The default do-what keyword.
+set dg-do-what-default compile
+
+# Adapted from jit.exp.
+#
+# Execute the executable file.
+# Returns:
+# A "" (empty) string if everything worked, or an error message
+# if there was a problem.
+#
+proc fixed_host_execute {args} {
+ global env
+ global text
+ global spawn_id
+
+ verbose "fixed_host_execute: $args"
+
+ set timeoutmsg "Timed out: Never got started, "
+ set timeout 100
+ set file all
+ set timetol 0
+ set arguments ""
+
+ if { [llength $args] == 0} {
+ set executable $args
+ } else {
+ set executable [lindex $args 0]
+ set params [lindex $args 1]
+ }
+
+ verbose "The executable is $executable" 2
+ if {![file exists ${executable}]} {
+ perror "The executable, \"$executable\" is missing" 0
+ return "No source file found"
+ } elseif {![file executable ${executable}]} {
+ perror "The executable, \"$executable\" is not usable" 0
+ return "Bad executable found"
+ }
+
+ verbose "params: $params" 2
+
+ # spawn the executable and look for the DejaGnu output messages from the
+ # test case.
+ # spawn -noecho -open [open "|./${executable}" "r"]
+
+ # Run under valgrind if RUN_UNDER_VALGRIND is present in the environment.
+ # Note that it's best to configure gcc with --enable-valgrind-annotations
+ # when testing under valgrind.
+ set run_under_valgrind [info exists env(RUN_UNDER_VALGRIND)]
+ if $run_under_valgrind {
+ set valgrind_logfile "${executable}.valgrind.txt"
+ set valgrind_params {"valgrind"}
+ lappend valgrind_params "--leak-check=full"
+ lappend valgrind_params "--log-file=${valgrind_logfile}"
+ } else {
+ set valgrind_params {}
+ }
+ verbose "valgrind_params: $valgrind_params" 2
+
+ set args ${valgrind_params}
+ lappend args "./${executable}"
+ set args [concat $args ${params}]
+ verbose "args: $args" 2
+
+ set status [catch "exec -keepnewline $args" exe_output]
+ verbose "Test program returned $exe_output" 2
+
+ if $run_under_valgrind {
+ upvar 2 name name
+ parse_valgrind_logfile $name $valgrind_logfile fail
+ }
+
+ # We don't do prune_gcc_output here, as we want
+ # to check *exactly* what we get from libdiagnostics
+
+ return $exe_output
+}
+
+# (end of code from dejagnu.exp)
+
+# GCC_UNDER_TEST is needed by gcc_target_compile
+global GCC_UNDER_TEST
+if ![info exists GCC_UNDER_TEST] {
+ set GCC_UNDER_TEST "[find_gcc]"
+}
+
+g++_init
+
+# Initialize dg.
+dg-init
+
+# Gather a list of all tests.
+
+# C and C++ tests within the testsuite: gcc/testsuite/libdiagnostics.dg/test-*.{c,c++}
+set c_tests [find $srcdir/$subdir test-*.c]
+set cxx_tests [find $srcdir/$subdir test-*.cc]
+set tests [concat $c_tests $cxx_tests]
+
+verbose "tests: $tests"
+
+# Expand "SRCDIR" within ARG to the location of the top-level
+# src directory
+
+proc diagnostics-expand-vars {arg} {
+ verbose "diagnostics-expand-vars: $arg"
+ global srcdir
+ verbose " srcdir: $srcdir"
+ # "srcdir" is that of the gcc/testsuite directory, so
+ # we need to go up two levels.
+ set arg [string map [list "SRCDIR" $srcdir/../..] $arg]
+ verbose " new arg: $arg"
+ return $arg
+}
+
+# Parameters used when invoking the executables built from the test cases.
+
+global diagnostics-exe-params
+set diagnostics-exe-params {}
+
+# Set "diagnostics-exe-params", expanding "SRCDIR" in each arg to the location of
+# the top-level srcdir.
+
+proc dg-diagnostics-set-exe-params { args } {
+ verbose "dg-diagnostics-set-exe-params: $args"
+
+ global diagnostics-exe-params
+ set diagnostics-exe-params {}
+ # Skip initial arg (line number)
+ foreach arg [lrange $args 1 [llength $args] ] {
+ lappend diagnostics-exe-params [diagnostics-expand-vars $arg]
+ }
+}
+
+proc libdiagnostics-dg-test { prog do_what extra_tool_flags } {
+ verbose "within libdiagnostics-dg-test..."
+ verbose " prog: $prog"
+ verbose " do_what: $do_what"
+ verbose " extra_tool_flags: $extra_tool_flags"
+
+ global dg-do-what-default
+ set dg-do-what [list ${dg-do-what-default} "" P]
+
+ # If we're not supposed to try this test on this target, we're done.
+ if { [lindex ${dg-do-what} 1] == "N" } {
+ unsupported "$name"
+ verbose "$name not supported on this target, skipping it" 3
+ return
+ }
+
+ # Determine what to name the built executable.
+ #
+ # We simply append .exe to the filename, e.g.
+ # "test-foo.c.exe"
+ # since some testcases exist in both
+ # "test-foo.c" and
+ # "test-foo.cc"
+ # variants, and we don't want them to clobber each other's
+ # executables.
+ #
+ # This also ensures that the source name makes it into the
+ # pass/fail output, so that we can distinguish e.g. which test-foo
+ # is failing.
+ set output_file "[file tail $prog].exe"
+ verbose "output_file: $output_file"
+
+ # Create the test executable:
+ set extension [file extension $prog]
+ if {$extension == ".cc"} {
+ set compilation_function "g++_target_compile"
+ } else {
+ set compilation_function "gcc_target_compile"
+ }
+ set options "{additional_flags=$extra_tool_flags}"
+ verbose "compilation_function=$compilation_function"
+ verbose "options=$options"
+
+ set comp_output [$compilation_function $prog $output_file \
+ "executable" $options]
+ upvar 1 name name
+ if ![libdiagnostics_check_compile "$name" "initial compilation" \
+ $output_file $comp_output] then {
+ return
+ }
+
+ # Run the test executable.
+
+ # We need to set LD_LIBRARY_PATH so that the test files can find
+ # libdiagnostics.so
+ # Do this using set_ld_library_path_env_vars from target-libpath.exp
+ # We will restore the old value later using
+ # restore_ld_library_path_env_vars.
+
+ # Unfortunately this API only supports a single saved value, rather
+ # than a stack, and g++_init has already called into this API,
+ # injecting the appropriate value for LD_LIBRARY_PATH for finding
+ # the built copy of libstdc++.
+ # Hence the call to restore_ld_library_path_env_vars would restore
+ # the *initial* value of LD_LIBRARY_PATH, and attempts to run
+ # a C++ testcase after running any prior testcases would thus look
+ # in the wrong place for libstdc++. This led to failures at startup
+ # of the form:
+ # ./tut01-hello-world.cc.exe: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./tut01-hello-world.cc.exe)
+ # when the built libstdc++ is more recent that the system libstdc++.
+ #
+ # As a workaround, reset the variable "orig_environment_saved" within
+ # target-libpath.exp, so that the {set|restore}_ld_library_path_env_vars
+ # API saves/restores the current value of LD_LIBRARY_PATH (as set up
+ # by g++_init).
+ global orig_environment_saved
+ set orig_environment_saved 0
+
+ global ld_library_path
+ global base_dir
+ set ld_library_path "$base_dir/../../"
+ set_ld_library_path_env_vars
+
+ global diagnostics-exe-params
+ set args ${diagnostics-exe-params}
+ set diagnostics-exe-params {}
+
+ set exe_output [fixed_host_execute $output_file $args ]
+ verbose "exe_output: $exe_output"
+
+ restore_ld_library_path_env_vars
+
+ # Analyze the output from the executable. To some what extent this
+ # is duplicating prune_gcc_output, but we're looking for *precise*
+ # output, so we can't reuse prune_gcc_output.
+
+ global testname_with_flags
+ set testname_with_flags $name
+
+ # Handle any freeform regexps.
+ set exe_output [handle-dg-regexps $exe_output]
+
+ # Call into multiline.exp to handle any multiline output directives.
+ set exe_output [handle-multiline-outputs $exe_output]
+
+ # Normally we would return $exe_output and $output_file to the
+ # caller, which would delete $output_file, the generated executable.
+ # If we need to debug, it's handy to be able to suppress this behavior,
+ # keeping the executable around.
+
+ global env
+ set preserve_executables [info exists env(PRESERVE_EXECUTABLES)]
+ if $preserve_executables {
+ set output_file ""
+ }
+
+ return [list $exe_output $output_file]
+}
+
+set DEFAULT_CFLAGS "-I$srcdir/.. -ldiagnostics -g -Wall -Werror"
+
+# Main loop. This will invoke jig-dg-test on each test-*.c file.
+dg-runtest $tests "" $DEFAULT_CFLAGS
+
+# All done.
+dg-finish
--- /dev/null
+import json
+import os
+
+def sarif_from_env():
+ # return parsed JSON content a SARIF_PATH file
+ json_filename = os.environ['SARIF_PATH']
+ json_filename += '.sarif'
+ print('json_filename: %r' % json_filename)
+ with open(json_filename) as f:
+ json_data = f.read()
+ return json.loads(json_data)
+
+def get_location_artifact_uri(location):
+ return location['physicalLocation']['artifactLocation']['uri']
+
+def get_location_physical_region(location):
+ return location['physicalLocation']['region']
+
+def get_location_snippet_text(location):
+ return location['physicalLocation']['contextRegion']['snippet']['text']
+
+def get_location_relationships(location):
+ return location['relationships']
--- /dev/null
+/* Usage example of dump API. */
+
+#include "libdiagnostics.h"
+
+const int line_num = 42;
+
+int
+main ()
+{
+ diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+ const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, "foo.c", "c");
+
+ fprintf (stderr, "file: ");
+ diagnostic_manager_debug_dump_file (diag_mgr, file, stderr);
+ fprintf (stderr, "\n");
+ /* { dg-begin-multiline-output "" }
+file: file(name="foo.c", sarif_source_language="c")
+ { dg-end-multiline-output "" } */
+
+ const diagnostic_physical_location *loc_start
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+ const diagnostic_physical_location *loc_end
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+ const diagnostic_physical_location *loc_range
+ = diagnostic_manager_new_location_from_range (diag_mgr,
+ loc_start,
+ loc_start,
+ loc_end);
+
+ fprintf (stderr, "loc_start: ");
+ diagnostic_manager_debug_dump_location (diag_mgr, loc_start, stderr);
+ fprintf (stderr, "\n");
+ /* { dg-begin-multiline-output "" }
+loc_start: foo.c:42:8:
+ { dg-end-multiline-output "" } */
+
+ fprintf (stderr, "loc_end: ");
+ diagnostic_manager_debug_dump_location (diag_mgr, loc_end, stderr);
+ fprintf (stderr, "\n");
+ /* { dg-begin-multiline-output "" }
+loc_end: foo.c:42:19:
+ { dg-end-multiline-output "" } */
+
+ fprintf (stderr, "loc_range: ");
+ diagnostic_manager_debug_dump_location (diag_mgr, loc_range, stderr);
+ fprintf (stderr, "\n");
+ /* { dg-begin-multiline-output "" }
+loc_range: foo.c:42:8:
+ { dg-end-multiline-output "" } */
+
+ const diagnostic_logical_location *logical_loc
+ = diagnostic_manager_new_logical_location (diag_mgr,
+ DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION,
+ NULL, /* parent */
+ "test_short_name",
+ "test_qualified_name",
+ "test_decorated_name");
+
+ fprintf (stderr, "logical_loc: ");
+ diagnostic_manager_debug_dump_logical_location (diag_mgr, logical_loc, stderr);
+ fprintf (stderr, "\n");
+ /* { dg-begin-multiline-output "" }
+logical_loc: logical_location(kind=function, short_name="test_short_name", fully_qualified_name="test_qualified_name", decorated_name="test_decorated_name")
+ { dg-end-multiline-output "" } */
+
+ diagnostic_manager_release (diag_mgr);
+ return 0;
+};
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+expected_line_num = 17
+expected_file_name = 'test-error.c'
+
+def test_sarif_output(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ tool = run['tool']
+ assert tool['driver']['name'] == expected_file_name + '.exe'
+
+ invocations = run['invocations']
+ assert len(invocations) == 1
+ assert 'workingDirectory' in invocations[0]
+ assert 'startTimeUtc' in invocations[0]
+ assert invocations[0]['executionSuccessful'] == False
+ assert invocations[0]['toolExecutionNotifications'] == []
+ assert 'endTimeUtc' in invocations[0]
+
+ artifacts = run['artifacts']
+ assert len(artifacts) == 1
+ assert artifacts[0]['location']['uri'].endswith(expected_file_name)
+ assert artifacts[0]['sourceLanguage'] == 'c'
+ assert '#include <foo.h>' in artifacts[0]['contents']['text']
+ assert artifacts[0]['roles'] == ["analysisTarget"]
+
+ results = run['results']
+ assert len(results) == 1
+ assert results[0]['ruleId'] == 'error'
+ assert results[0]['level'] == 'error'
+ assert results[0]['message']['text'] == "can't find 'foo.h'"
+ assert len(results[0]['locations']) == 1
+ location = results[0]['locations'][0]
+ phys_loc = location['physicalLocation']
+ assert phys_loc['artifactLocation']['uri'].endswith(expected_file_name)
+ assert phys_loc['region']['startLine'] == expected_line_num
+ assert phys_loc['region']['startColumn'] == 11
+ assert phys_loc['region']['endColumn'] == 16
+ assert phys_loc['contextRegion']['startLine'] == expected_line_num
+ assert phys_loc['contextRegion']['snippet']['text'] \
+ == '#include <foo.h>\n'
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+expected_line_num = 18
+
+def test_sarif_output_for_note(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ tool = run['tool']
+ assert tool['driver']['name'] == 'test-error-with-note.c.exe'
+
+ results = run['results']
+ assert len(results) == 1
+ assert results[0]['ruleId'] == 'error'
+ assert results[0]['level'] == 'error'
+ assert results[0]['message']['text'] == "can't find 'foo.h'"
+ assert len(results[0]['locations']) == 1
+ location = results[0]['locations'][0]
+ phys_loc = location['physicalLocation']
+ assert phys_loc['artifactLocation']['uri'].endswith('test-error-with-note.c')
+ assert phys_loc['region']['startLine'] == expected_line_num
+ assert phys_loc['region']['startColumn'] == 11
+ assert phys_loc['region']['endColumn'] == 16
+ assert phys_loc['contextRegion']['startLine'] == expected_line_num
+ assert phys_loc['contextRegion']['snippet']['text'] \
+ == '#include <foo.h>\n'
+
+ assert len(results[0]['relatedLocations']) == 1
+ note = results[0]['relatedLocations'][0]
+ phys_loc = note['physicalLocation']
+ assert phys_loc['artifactLocation']['uri'].endswith('test-error-with-note.c')
+ assert phys_loc['region']['startLine'] == expected_line_num
+ assert phys_loc['region']['startColumn'] == 11
+ assert phys_loc['region']['endColumn'] == 16
+ assert phys_loc['contextRegion']['startLine'] == expected_line_num
+ assert phys_loc['contextRegion']['snippet']['text'] \
+ == '#include <foo.h>\n'
+ assert note['message']['text'] == 'have you looked behind the couch?'
--- /dev/null
+/* Example of emitting an error with an associated note.
+
+ Intended output is similar to:
+
+PATH/test-error-with-note.c:18:11: error: can't find 'foo.h'
+ 6 | #include <foo.h>
+ | ^~~~~
+PATH/test-error-with-note.c:18:11: note: have you looked behind the couch?
+
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/*
+_________111111111122
+123456789012345678901
+#include <foo.h>
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ begin_test ("test-error-with-note.c.exe",
+ "test-error-with-note.c.sarif",
+ __FILE__, "c");
+
+ const diagnostic_physical_location *loc_start
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+ main_file,
+ line_num,
+ 11);
+ const diagnostic_physical_location *loc_end
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+ main_file,
+ line_num,
+ 15);
+ const diagnostic_physical_location *loc_range
+ = diagnostic_manager_new_location_from_range (diag_mgr,
+ loc_start,
+ loc_start,
+ loc_end);
+
+ /* begin quoted source */
+ diagnostic_manager_begin_group (diag_mgr);
+
+ diagnostic *err = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_set_location (err, loc_range);
+ diagnostic_finish (err, "can't find %qs", "foo.h");
+
+ diagnostic *note = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_NOTE);
+ diagnostic_set_location (note, loc_range);
+ diagnostic_finish (note, "have you looked behind the couch?");
+
+ diagnostic_manager_end_group (diag_mgr);
+ /* end quoted source */
+
+ return end_test ();
+};
+
+/* Verify the output from the text sink.
+ { dg-regexp "\[^\n\r\]+test-error-with-note.c:18:11: error: can't find 'foo.h'" }
+ { dg-begin-multiline-output "" }
+ 18 | #include <foo.h>
+ | ^~~~~
+ { dg-end-multiline-output "" }
+ { dg-regexp "\[^\n\r\]+test-error-with-note.c:18:11: note: have you looked behind the couch." } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-error-with-note.c "test-error-with-note-c.py" } } */
--- /dev/null
+/* C++ example of emitting an error with an associated note.
+
+ Intended output is similar to:
+
+PATH/test-error-with-note.c:17:8: error: can't find 'foo'
+ 17 | PRINT "hello world!";
+ | ^~~~~~~~~~~~
+PATH/test-error-with-note.c:17:8: note: have you looked behind the couch?
+
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics++.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ libdiagnostics::manager mgr;
+
+ auto file = mgr.new_file (__FILE__, "c");
+
+ mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY);
+
+ auto loc_start = mgr.new_location_from_file_line_column (file, line_num, 8);
+ auto loc_end = mgr.new_location_from_file_line_column (file, line_num, 19);
+ auto loc_range = mgr.new_location_from_range (loc_start,
+ loc_start,
+ loc_end);
+
+ libdiagnostics::group g (mgr);
+
+ auto err (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR));
+ err.set_location (loc_range);
+ err.finish ("can't find %qs", "foo");
+
+ auto note = mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE);
+ note.set_location (loc_range);
+ note.finish ("have you looked behind the couch?");
+
+ return 0;
+};
+
+/* Verify the output from the text sink.
+ { dg-regexp "\[^\n\r\]+test-error-with-note.cc:17:8: error: can't find 'foo'" }
+ { dg-begin-multiline-output "" }
+ 17 | PRINT "hello world!";
+ | ^~~~~~~~~~~~
+ { dg-end-multiline-output "" }
+ { dg-regexp "\[^\n\r\]+test-error-with-note.cc:17:8: note: have you looked behind the couch\\\?" } */
--- /dev/null
+/* Example of emitting an error.
+
+ Intended output is similar to:
+
+PATH/test-error-with-note.c:6:11: error: can't find 'foo.h'
+ 6 | #include <foo.h>
+ | ^~~~~
+
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/*
+_________1111111
+1234567890123456
+#include <foo.h>
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ begin_test ("test-error.c.exe",
+ "test-error.c.sarif",
+ __FILE__, "c");
+
+ /* begin quoted source */
+ const diagnostic_physical_location *loc_start
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr, main_file, line_num, 11);
+ const diagnostic_physical_location *loc_end
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr, main_file, line_num, 15);
+ const diagnostic_physical_location *loc_range
+ = diagnostic_manager_new_location_from_range (diag_mgr,
+ loc_start,
+ loc_start,
+ loc_end);
+
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_set_location (d, loc_range);
+
+ diagnostic_finish (d, "can't find %qs", "foo.h");
+ /* end quoted source */
+
+ return end_test ();
+};
+
+/* Verify the output from the text sink.
+ { dg-regexp "\[^\n\r\]+test-error.c:17:11: error: can't find 'foo.h'" }
+ { dg-begin-multiline-output "" }
+ 17 | #include <foo.h>
+ | ^~~~~
+ { dg-end-multiline-output "" } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-error.c "test-error-c.py" } } */
--- /dev/null
+/* C++ example of emitting an error.
+
+ Intended output is similar to:
+
+PATH/test-error.cc:16:8: error: can't find 'foo'
+ 16 | PRINT "hello world!";
+ | ^~~~~~~~~~~~
+
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics++.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ libdiagnostics::manager mgr;
+
+ auto file = mgr.new_file (__FILE__, "c");
+
+ mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY);
+
+ auto loc_start = mgr.new_location_from_file_line_column (file, line_num, 8);
+ auto loc_end = mgr.new_location_from_file_line_column (file, line_num, 19);
+ auto loc_range = mgr.new_location_from_range (loc_start,
+ loc_start,
+ loc_end);
+
+ libdiagnostics::diagnostic d (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR));
+ d.set_location (loc_range);
+ d.finish ("can't find %qs", "foo");
+
+ return 0;
+};
+
+/* Verify the output from the text sink.
+ { dg-regexp "\[^\n\r\]+test-error.cc:16:8: error: can't find 'foo'" }
+ { dg-begin-multiline-output "" }
+ 16 | PRINT "hello world!";
+ | ^~~~~~~~~~~~
+ { dg-end-multiline-output "" } */
--- /dev/null
+/* begin quoted source */
+/* Minimal usage example. */
+#include "libdiagnostics.h"
+
+static diagnostic_manager *diag_mgr;
+
+static void
+init_diagnostics (void)
+{
+ diag_mgr = diagnostic_manager_new ();
+ diagnostic_manager_add_text_sink (diag_mgr, stderr,
+ DIAGNOSTIC_COLORIZE_IF_TTY);
+}
+
+static void
+finish_diagnostics (void)
+{
+ diagnostic_manager_release (diag_mgr);
+}
+
+static void
+do_stuff (void)
+{
+ const char *username = "Dave";
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_finish (d,
+ "I'm sorry %s, I'm afraid I can't do that",
+ username);
+}
+
+int
+main ()
+{
+ init_diagnostics ();
+
+ do_stuff ();
+
+ finish_diagnostics ();
+};
+/* end quoted source */
+
+/* { dg-regexp "progname: error: I'm sorry Dave, I'm afraid I can't do that" } */
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def test_sarif_output_with_fixes(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ tool = run['tool']
+ assert tool['driver']['name'] == 'test-fix-it-hint.c.exe'
+
+ results = run['results']
+ assert len(results) == 1
+ assert results[0]['ruleId'] == 'error'
+ assert results[0]['level'] == 'error'
+ assert results[0]['message']['text'] == "unknown field 'colour'; did you mean 'color'"
+ assert len(results[0]['locations']) == 1
+ location = results[0]['locations'][0]
+ phys_loc = location['physicalLocation']
+ assert phys_loc['artifactLocation']['uri'].endswith('test-fix-it-hint.c')
+ assert phys_loc['region']['startLine'] == 19
+ assert phys_loc['region']['startColumn'] == 13
+ assert phys_loc['region']['endColumn'] == 19
+ assert phys_loc['contextRegion']['startLine'] == 19
+ assert phys_loc['contextRegion']['snippet']['text'] \
+ == ' return p->colour;\n'
+
+ assert len(results[0]['fixes']) == 1
+ fix = results[0]['fixes'][0]
+ assert len(fix['artifactChanges']) == 1
+ change = fix['artifactChanges'][0]
+ assert change['artifactLocation']['uri'].endswith('test-fix-it-hint.c')
+ assert len(change['replacements']) == 1
+ replacement = change['replacements'][0]
+ assert replacement['deletedRegion'] == phys_loc['region']
+ assert replacement['insertedContent']['text'] == 'color'
--- /dev/null
+/* Example of a fix-it hint, including patch generation.
+
+ Intended output is similar to:
+
+PATH/test-fix-it-hint.c:19:13: error: unknown field 'colour'; did you mean 'color'
+ 19 | return p->colour;
+ | ^~~~~~
+ | color
+
+ along with the equivalent in SARIF, and a generated patch (on stderr) to
+ make the change. */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/*
+_________11111111112
+12345678901234567890
+ return p->colour;
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ begin_test ("test-fix-it-hint.c.exe",
+ "test-fix-it-hint.c.sarif",
+ __FILE__, "c");
+
+ const diagnostic_physical_location *loc_token
+ = make_range (diag_mgr, main_file, line_num, 13, 18);
+
+ /* begin quoted source */
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_set_location (d, loc_token);
+
+ diagnostic_add_fix_it_hint_replace (d, loc_token, "color");
+
+ diagnostic_finish (d, "unknown field %qs; did you mean %qs", "colour", "color");
+ /* end quoted source */
+
+ diagnostic_manager_write_patch (diag_mgr, stderr);
+
+ return end_test ();
+}
+
+/* Verify the output from the text sink.
+ { dg-regexp "\[^\n\r\]+test-fix-it-hint.c:19:13: error: unknown field 'colour'; did you mean 'color'" }
+ { dg-begin-multiline-output "" }
+ 19 | return p->colour;
+ | ^~~~~~
+ | color
+ { dg-end-multiline-output "" } */
+
+/* Verify the output from diagnostic_manager_write_patch.
+ We expect the patch to begin with a header, containing this
+ source filename, via an absolute path.
+ Given the path, we can only capture it via regexps. */
+/* { dg-regexp "\\-\\-\\- .*" } */
+/* { dg-regexp "\\+\\+\\+ .*" } */
+/* Use #if 0/#endif rather than comments, to allow the text to contain
+ a comment. */
+#if 0
+{ dg-begin-multiline-output "" }
+@@ -16,7 +16,7 @@
+ /*
+ _________11111111112
+ 12345678901234567890
+- return p->colour;
++ return p->color;
+ */
+ const int line_num = __LINE__ - 2;
+
+{ dg-end-multiline-output "" }
+#endif
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-fix-it-hint.c "test-fix-it-hint-c.py" } } */
--- /dev/null
+/* C++ example of a fix-it hint, including patch generation.
+
+ Intended output is similar to:
+
+PATH/test-fix-it-hint.cc:19:13: error: unknown field 'colour'; did you mean 'color'
+ 19 | return p->colour;
+ | ^~~~~~
+ | color
+
+ along with the equivalent in SARIF, and a generated patch (on stderr) to
+ make the change. */
+
+#include "libdiagnostics++.h"
+#include "test-helpers++.h"
+
+/*
+_________11111111112
+12345678901234567890
+ return p->colour;
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ libdiagnostics::manager mgr;
+
+ auto file = mgr.new_file (__FILE__, "c");
+
+ mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY);
+
+ auto loc_token = make_range (mgr, file, line_num, 13, 18);
+
+ auto d = mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR);
+ d.set_location (loc_token);
+
+ d.add_fix_it_hint_replace (loc_token, "color");
+
+ d.finish ("unknown field %qs; did you mean %qs", "colour", "color");
+
+ mgr.write_patch (stderr);
+
+ return 0;
+}
+
+/* Verify the output from the text sink.
+ { dg-regexp "\[^\n\r\]+test-fix-it-hint.cc:19:13: error: unknown field 'colour'; did you mean 'color'" }
+ { dg-begin-multiline-output "" }
+ 19 | return p->colour;
+ | ^~~~~~
+ | color
+ { dg-end-multiline-output "" } */
+
+/* Verify the output from diagnostic_manager_write_patch.
+ We expect the patch to begin with a header, containing this
+ source filename, via an absolute path.
+ Given the path, we can only capture it via regexps. */
+/* { dg-regexp "\\-\\-\\- .*" } */
+/* { dg-regexp "\\+\\+\\+ .*" } */
+/* Use #if 0/#endif rather than comments, to allow the text to contain
+ a comment. */
+#if 0
+{ dg-begin-multiline-output "" }
+@@ -16,7 +16,7 @@
+ /*
+ _________11111111112
+ 12345678901234567890
+- return p->colour;
++ return p->color;
+ */
+ const int line_num = __LINE__ - 2;
+
+{ dg-end-multiline-output "" }
+#endif
--- /dev/null
+/* Common utility code shared between test cases. */
+
+#ifndef TEST_HELPERSPP_H
+#define TEST_HELPERSPP_H
+
+namespace libdiagnostics {
+
+inline physical_location
+make_range (manager &mgr,
+ file f,
+ line_num_t line_num,
+ column_num_t start_column,
+ column_num_t end_column)
+{
+ auto loc_start = mgr.new_location_from_file_line_column (f,
+ line_num,
+ start_column);
+ auto loc_end = mgr.new_location_from_file_line_column (f,
+ line_num,
+ end_column);
+ return mgr.new_location_from_range (loc_start,
+ loc_start,
+ loc_end);
+}
+
+} // namespace libdiagnostics
+
+#endif /* #ifndef TEST_HELPERSPP_H */
--- /dev/null
+/* Common utility code shared between test cases. */
+
+#ifndef TEST_HELPERS_H
+#define TEST_HELPERS_H
+
+const diagnostic_physical_location *
+make_range (diagnostic_manager *diag_mgr,
+ const diagnostic_file *file,
+ diagnostic_line_num_t line_num,
+ diagnostic_column_num_t start_column,
+ diagnostic_column_num_t end_column)
+{
+ const diagnostic_physical_location *loc_start
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+ file,
+ line_num,
+ start_column);
+ const diagnostic_physical_location *loc_end
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+ file,
+ line_num,
+ end_column);
+ return diagnostic_manager_new_location_from_range (diag_mgr,
+ loc_start,
+ loc_start,
+ loc_end);
+}
+
+/* A begin_test/end_test pair to consolidate the code shared by tests:
+ create a diagnostic_manager, a main file, a text sink, and a SARIF sink,
+ and clean these up after emitting zero or more diagnostics. */
+
+static diagnostic_manager *diag_mgr;
+static const diagnostic_file *main_file;
+static FILE *sarif_outfile;
+
+static void
+begin_test (const char *tool_name,
+ const char *sarif_output_name,
+ const char *main_file_name,
+ const char *source_language)
+{
+ diag_mgr = diagnostic_manager_new ();
+
+ /* We need to set this for generated .sarif files to validate
+ against the schema. */
+ diagnostic_manager_set_tool_name (diag_mgr, tool_name);
+
+ main_file = diagnostic_manager_new_file (diag_mgr,
+ main_file_name,
+ source_language);
+
+ diagnostic_manager_add_text_sink (diag_mgr, stderr,
+ DIAGNOSTIC_COLORIZE_IF_TTY);
+ sarif_outfile = fopen (sarif_output_name, "w");
+ if (sarif_outfile)
+ diagnostic_manager_add_sarif_sink (diag_mgr,
+ sarif_outfile,
+ main_file,
+ DIAGNOSTIC_SARIF_VERSION_2_1_0);
+}
+
+static int
+end_test (void)
+{
+ diagnostic_manager_release (diag_mgr);
+ if (sarif_outfile)
+ fclose (sarif_outfile);
+ return 0;
+}
+
+#endif /* #ifndef TEST_HELPERS_H */
--- /dev/null
+/* Example of multiple locations, with labelling of ranges.
+
+ Intended output is similar to:
+
+PATH/test-labelled-ranges.c:9:6: error: mismatching types: 'int' and 'const char *'
+ 19 | 42 + "foo"
+ | ~~ ^ ~~~~~
+ | | |
+ | int const char *
+
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/*
+_________11111111112
+12345678901234567890
+ 42 + "foo"
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ begin_test ("test-labelled-ranges.c.exe",
+ "test-labelled-ranges.c.sarif",
+ __FILE__, "c");
+
+ const diagnostic_physical_location *loc_operator
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+ main_file,
+ line_num,
+ 6);
+
+ /* begin quoted source */
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_set_location (d, loc_operator);
+ diagnostic_add_location_with_label (d,
+ make_range (diag_mgr,
+ main_file,
+ line_num, 3, 4),
+ "int");
+ diagnostic_add_location_with_label (d,
+ make_range (diag_mgr,
+ main_file,
+ line_num, 8, 12),
+ "const char *");
+
+ diagnostic_finish (d, "mismatching types: %qs and %qs", "int", "const char *");
+ /* end quoted source */
+
+ return end_test ();
+}
+
+/* Check the output from the text sink. */
+/* { dg-regexp "\[^\n\r\]+test-labelled-ranges.c:19:6: error: mismatching types: 'int' and 'const char \\*'" } */
+/* { dg-begin-multiline-output "" }
+ 19 | 42 + "foo"
+ | ~~ ^ ~~~~~
+ | | |
+ | int const char *
+ { dg-end-multiline-output "" } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-labelled-ranges.c "test-labelled-ranges.py" } } */
--- /dev/null
+/* C++ example of multiple locations, with labelling of ranges.
+
+ Intended output is similar to:
+
+PATH/test-labelled-ranges.cc:19:6: error: mismatching types: 'int' and 'const char *'
+ 19 | 42 + "foo"
+ | ~~ ^ ~~~~~
+ | | |
+ | int const char *
+
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics++.h"
+#include "test-helpers++.h"
+
+/*
+_________11111111112
+12345678901234567890
+ 42 + "foo"
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ FILE *sarif_outfile;
+ libdiagnostics::manager mgr;
+ mgr.set_tool_name ("test-labelled-ranges.cc.exe");
+
+ libdiagnostics::file file = mgr.new_file (__FILE__, "c");
+
+ mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY);
+ sarif_outfile = fopen ("test-labelled-ranges.cc.sarif", "w");
+ if (sarif_outfile)
+ mgr.add_sarif_sink (sarif_outfile, file, DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+ auto loc_operator = mgr.new_location_from_file_line_column (file, line_num, 6);
+
+ auto d (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR));
+ d.set_location (loc_operator);
+ d.add_location_with_label (make_range (mgr, file, line_num, 3, 4),
+ "int");
+ d.add_location_with_label (make_range (mgr, file, line_num, 8, 12),
+ "const char *");
+ d.finish ("mismatching types: %qs and %qs", "int", "const char *");
+
+ return 0;
+}
+
+/* Check the output from the text sink. */
+/* { dg-regexp "\[^\n\r\]+test-labelled-ranges.cc:19:6: error: mismatching types: 'int' and 'const char \\*'" } */
+/* { dg-begin-multiline-output "" }
+ 19 | 42 + "foo"
+ | ~~ ^ ~~~~~
+ | | |
+ | int const char *
+ { dg-end-multiline-output "" } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-labelled-ranges.cc "test-labelled-ranges.py" } } */
--- /dev/null
+# Verify the SARIF output of test-labelled-ranges.{c,cc}
+
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def test_sarif_output(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ results = run['results']
+ assert len(results) == 1
+ assert results[0]['ruleId'] == 'error'
+ assert results[0]['level'] == 'error'
+ assert results[0]['message']['text'] \
+ == "mismatching types: 'int' and 'const char *'"
+ assert len(results[0]['locations']) == 1
+ location = results[0]['locations'][0]
+ phys_loc = location['physicalLocation']
+ assert phys_loc['region']['startLine'] == 19
+ assert phys_loc['region']['startColumn'] == 6
+ assert phys_loc['region']['endColumn'] == 7
+ assert phys_loc['contextRegion']['startLine'] == 19
+ assert phys_loc['contextRegion']['snippet']['text'] \
+ == ' 42 + "foo"\n'
+
+ annotations = location['annotations']
+ assert len(annotations) == 2
+
+ assert annotations[0]['startLine'] == 19
+ assert annotations[0]['startColumn'] == 3
+ assert annotations[0]['endColumn'] == 5
+ assert annotations[0]['message']['text'] == 'int'
+
+ assert annotations[1]['startLine'] == 19
+ assert annotations[1]['startColumn'] == 8
+ assert annotations[1]['endColumn'] == 13
+ assert annotations[1]['message']['text'] == 'const char *'
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def test_sarif_output_with_logical_location(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ tool = run['tool']
+ assert tool['driver']['name'] == 'test-logical-location.c.exe'
+
+ results = run['results']
+ assert len(results) == 1
+
+ result = results[0]
+ assert result['ruleId'] == 'error'
+ assert result['level'] == 'error'
+ assert result['message']['text'] == "can't find 'foo'"
+ assert len(result['locations']) == 1
+ location = result['locations'][0]
+
+ assert len(location['logicalLocations']) == 1
+ logical_loc = location['logicalLocations'][0]
+ assert logical_loc['name'] == 'test_short_name'
+ assert logical_loc['fullyQualifiedName'] == 'test_qualified_name'
+ assert logical_loc['decoratedName'] == 'test_decorated_name'
+ assert logical_loc['kind'] == 'function'
--- /dev/null
+/* Example of using a logical location.
+
+ Intended output is similar to:
+
+In function 'test_qualified_name':
+PATH/test-error-with-note.c:18:8: error: can't find 'foo'
+ 18 | PRINT "hello world!";
+ | ^~~~~~~~~~~~
+
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/* Placeholder source:
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ begin_test ("test-logical-location.c.exe",
+ "test-logical-location.c.sarif",
+ __FILE__, "c");
+
+ const diagnostic_physical_location *loc_start
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+ main_file,
+ line_num,
+ 8);
+ const diagnostic_physical_location *loc_end
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+ main_file,
+ line_num,
+ 19);
+ const diagnostic_physical_location *loc_range
+ = diagnostic_manager_new_location_from_range (diag_mgr,
+ loc_start,
+ loc_start,
+ loc_end);
+
+ /* begin quoted source */
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_set_location (d, loc_range);
+
+ const diagnostic_logical_location *logical_loc
+ = diagnostic_manager_new_logical_location (diag_mgr,
+ DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION,
+ NULL, /* parent */
+ "test_short_name",
+ "test_qualified_name",
+ "test_decorated_name");
+
+ diagnostic_set_logical_location (d, logical_loc);
+
+ diagnostic_finish (d, "can't find %qs", "foo");
+ /* end quoted source */
+
+ return end_test ();
+}
+
+/* Check the output from the text sink. */
+/* { dg-begin-multiline-output "" }
+In function 'test_qualified_name':
+ { dg-end-multiline-output "" } */
+/* { dg-regexp "\[^\n\r\]+test-logical-location.c:18:8: error: can't find 'foo'" } */
+/* { dg-begin-multiline-output "" }
+ 18 | PRINT "hello world!";
+ | ^~~~~~~~~~~~
+ { dg-end-multiline-output "" } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-logical-location.c "test-logical-location-c.py" } } */
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def test_sarif_output_metadata(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ tool = run['tool']
+ assert tool['driver']['name'] == 'FooChecker'
+ assert tool['driver']['fullName'] == 'FooChecker 0.1 (en_US)'
+ assert tool['driver']['version'] == '0.1'
+ assert tool['driver']['informationUri'] == 'https://www.example.com/0.1/'
+
+ taxonomies = run["taxonomies"]
+ assert len(taxonomies) == 1
+
+ cwe = taxonomies[0]
+ assert cwe['name'] == 'CWE'
+ assert cwe['version'] == '4.7'
+ assert cwe['organization'] == 'MITRE'
+ assert cwe['shortDescription']['text'] \
+ == 'The MITRE Common Weakness Enumeration'
+ assert len(cwe['taxa']) == 1
+ assert cwe['taxa'][0]['id'] == '242'
+ assert cwe['taxa'][0]['helpUri'] \
+ == 'https://cwe.mitre.org/data/definitions/242.html'
+
+ results = run['results']
+ assert len(results) == 1
+
+ result = results[0]
+ assert result['ruleId'] == 'warning'
+ assert result['level'] == 'warning'
+ assert result['message']['text'] == "never use 'gets'"
--- /dev/null
+/* Example of setting a CWE and adding extra metadata.
+
+ Intended output is similar to:
+
+PATH/test-metadata.c:21:3: warning: never use 'gets' [CWE-242] [STR34-C]
+ 21 | gets (buf);
+ | ^~~~~~~~~~
+
+ where the metadata tags are linkified in a sufficiently capable terminal,
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/* Placeholder source:
+_________11111111112
+12345678901234567890
+void test_cwe (void)
+{
+ char buf[1024];
+ gets (buf);
+}
+*/
+const int line_num = __LINE__ - 3;
+
+int
+main ()
+{
+ begin_test ("FooChecker",
+ "test-metadata.c.sarif",
+ __FILE__, "c");
+
+ diagnostic_manager_set_full_name (diag_mgr, "FooChecker 0.1 (en_US)");
+ diagnostic_manager_set_version_string (diag_mgr, "0.1");
+ diagnostic_manager_set_version_url (diag_mgr, "https://www.example.com/0.1/");
+
+ const diagnostic_physical_location *loc_token
+ = make_range (diag_mgr, main_file, line_num, 3, 12);
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_WARNING);
+ diagnostic_set_location (d, loc_token);
+ diagnostic_set_cwe (d, 242); /* CWE-242: Use of Inherently Dangerous Function. */
+ diagnostic_add_rule (d, "STR34-C", "https://example.com/");
+
+ diagnostic_finish (d, "never use %qs", "gets");
+
+ return end_test ();
+}
+
+/* { dg-regexp "\[^\n\r\]+test-metadata.c:21:3: warning: never use 'gets' \\\[CWE-242\\\] \\\[STR34-C\\\]" } */
+/* { dg-begin-multiline-output "" }
+ 21 | gets (buf);
+ | ^~~~~~~~~~
+ { dg-end-multiline-output "" } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-metadata.c "test-metadata-c.py" } } */
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def test_sarif_output(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ tool = run['tool']
+ assert tool['driver']['name'] == 'test-multiple-lines.c.exe'
+
+ results = run['results']
+ assert len(results) == 1
+ result = results[0]
+ assert result['ruleId'] == 'warning'
+ assert result['level'] == 'warning'
+ assert result['message']['text'] == "missing comma"
+ assert len(result['locations']) == 1
+
+ # The primary location should be that of the missing comma
+ location = result['locations'][0]
+ phys_loc = location['physicalLocation']
+ assert phys_loc['artifactLocation']['uri'].endswith('test-multiple-lines.c')
+ assert phys_loc['region']['startLine'] == 23
+ assert phys_loc['region']['startColumn'] == 29
+ assert phys_loc['region']['endColumn'] == 30
+ assert phys_loc['contextRegion']['startLine'] == 23
+ assert phys_loc['contextRegion']['snippet']['text'] \
+ == ' "bar"\n'
+
+ assert len(location['relationships']) == 3
+ location['relationships'][0]['target'] == 0
+ location['relationships'][0]['kinds'] == ['relevant']
+ location['relationships'][1]['target'] == 1
+ location['relationships'][1]['kinds'] == ['relevant']
+ location['relationships'][2]['target'] == 2
+ location['relationships'][2]['kinds'] == ['relevant']
+
+ # We should be capturing the secondary locations in relatedLocations
+ assert len(result['relatedLocations']) == 3
+
+ rel_loc_0 = result['relatedLocations'][0]
+ assert get_location_artifact_uri(rel_loc_0) \
+ .endswith('test-multiple-lines.c')
+ assert get_location_snippet_text(rel_loc_0) \
+ == 'const char *strs[3] = {"foo",\n'
+ assert get_location_physical_region(rel_loc_0)['startLine'] == 22
+ assert get_location_physical_region(rel_loc_0)['startColumn'] == 24
+ assert get_location_physical_region(rel_loc_0)['endColumn'] == 29
+ assert rel_loc_0['id'] == 0
+ assert 'relationships' not in rel_loc_0
+
+ rel_loc_1 = result['relatedLocations'][1]
+ assert get_location_artifact_uri(rel_loc_1) \
+ .endswith('test-multiple-lines.c')
+ assert get_location_snippet_text(rel_loc_1) \
+ == ' "bar"\n'
+ assert get_location_physical_region(rel_loc_1)['startLine'] == 23
+ assert get_location_physical_region(rel_loc_1)['startColumn'] == 24
+ assert get_location_physical_region(rel_loc_1)['endColumn'] == 29
+ assert rel_loc_1['id'] == 1
+ assert 'relationships' not in rel_loc_1
+
+ rel_loc_2 = result['relatedLocations'][2]
+ assert get_location_artifact_uri(rel_loc_2) \
+ .endswith('test-multiple-lines.c')
+ assert get_location_snippet_text(rel_loc_2) \
+ == ' "baz"};\n'
+ assert get_location_physical_region(rel_loc_2)['startLine'] == 24
+ assert get_location_physical_region(rel_loc_2)['startColumn'] == 24
+ assert get_location_physical_region(rel_loc_2)['endColumn'] == 29
+ assert rel_loc_2['id'] == 2
+ assert 'relationships' not in rel_loc_2
--- /dev/null
+/* Example of a warning with multiple locations in various source lines,
+ with an insertion fix-it hint.
+
+ Intended output is similar to:
+
+/PATH/test-multiple-lines.c:23:29: warning: missing comma
+ 22 | const char *strs[3] = {"foo",
+ | ~~~~~
+ 23 | "bar"
+ | ~~~~~^
+ 24 | "baz"};
+ | ~~~~~
+
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/* Placeholder source (missing comma after "bar"):
+_________11111111112222222222
+12345678901234567890123456789
+const char *strs[3] = {"foo",
+ "bar"
+ "baz"};
+*/
+const int foo_line_num = __LINE__ - 4;
+
+int
+main ()
+{
+ begin_test ("test-multiple-lines.c.exe",
+ "test-multiple-lines.c.sarif",
+ __FILE__, "c");
+
+ /* begin quoted source */
+ const diagnostic_physical_location *loc_comma
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+ main_file,
+ foo_line_num + 1,
+ 29);
+ const diagnostic_physical_location *loc_foo
+ = make_range (diag_mgr, main_file, foo_line_num, 24, 28);
+ const diagnostic_physical_location *loc_bar
+ = make_range (diag_mgr, main_file, foo_line_num + 1, 24, 28);
+ const diagnostic_physical_location *loc_baz
+ = make_range (diag_mgr, main_file, foo_line_num + 2, 24, 28);
+
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_WARNING);
+ diagnostic_set_location (d, loc_comma);
+ diagnostic_add_location (d, loc_foo);
+ diagnostic_add_location (d, loc_bar);
+ diagnostic_add_location (d, loc_baz);
+
+ diagnostic_add_fix_it_hint_insert_after (d, loc_bar, ",");
+
+ diagnostic_finish (d, "missing comma");
+ /* end quoted source */
+
+ return end_test ();
+};
+
+/* { dg-regexp "\[^\n\r\]+test-multiple-lines.c:23:29: warning: missing comma" } */
+/* { dg-begin-multiline-output "" }
+ 22 | const char *strs[3] = {"foo",
+ | ~~~~~
+ 23 | "bar"
+ | ~~~~~^
+ 24 | "baz"};
+ | ~~~~~
+ { dg-end-multiline-output "" } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-multiple-lines.c "test-multiple-lines-c.py" } } */
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+expected_line_num = 16
+
+def test_sarif_output(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ tool = run['tool']
+ assert tool['driver']['name'] == 'test-no-column.c.exe'
+
+ results = run['results']
+ assert len(results) == 1
+ location = results[0]['locations'][0]
+ phys_loc = location['physicalLocation']
+ assert phys_loc['artifactLocation']['uri'].endswith('test-no-column.c')
+ assert phys_loc['region']['startLine'] == expected_line_num
+ # We should have no column properties:
+ assert 'startColumn' not in phys_loc['region']
+ assert 'endColumn' not in phys_loc['region']
+ assert phys_loc['contextRegion']['startLine'] == expected_line_num
+ assert phys_loc['contextRegion']['snippet']['text'] \
+ == '#include <foo.h>\n'
--- /dev/null
+/* Example of emitting an error without a column number.
+
+ Intended output is similar to:
+
+PATH/test-error-with-note.c:6: error: can't find 'foo'
+ 6 | #include <foo.h>
+
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/*
+_________111111111122
+123456789012345678901
+#include <foo.h>
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ begin_test ("test-no-column.c.exe",
+ "test-no-column.c.sarif",
+ __FILE__, "c");
+
+ /* begin quoted source */
+ const diagnostic_physical_location *loc
+ = diagnostic_manager_new_location_from_file_and_line (diag_mgr,
+ main_file,
+ line_num);
+
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_set_location (d, loc);
+
+ diagnostic_finish (d, "can't find %qs", "foo.h");
+ /* end quoted source */
+
+ return end_test ();
+}
+
+/* Verify the output from the text sink.
+ { dg-regexp "\[^\n\r\]+test-no-column.c:16: error: can't find 'foo.h'" }
+ { dg-begin-multiline-output "" }
+ 16 | #include <foo.h>
+ { dg-end-multiline-output "" } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-no-column.c "test-no-column-c.py" } } */
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+expected_file_name = 'test-no-diagnostics.c'
+
+def test_sarif_output(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ tool = run['tool']
+ assert tool['driver']['name'] == expected_file_name + '.exe'
+
+ invocations = run['invocations']
+ assert len(invocations) == 1
+ assert 'workingDirectory' in invocations[0]
+ assert 'startTimeUtc' in invocations[0]
+ assert invocations[0]['executionSuccessful'] == True
+ assert invocations[0]['toolExecutionNotifications'] == []
+ assert 'endTimeUtc' in invocations[0]
+
+ artifacts = run['artifacts']
+ assert len(artifacts) == 1
+ assert artifacts[0]['location']['uri'].endswith(expected_file_name)
+ assert artifacts[0]['sourceLanguage'] == 'c'
+ # We don't bother capturing the contents if there are
+ # no diagnostics to display
+ assert 'contents' not in artifacts[0]
+ assert artifacts[0]['roles'] == ["analysisTarget"]
+
+ results = run['results']
+ assert len(results) == 0
--- /dev/null
+/* Test of the "no diagnostics are emitted" case. */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+int
+main ()
+{
+ begin_test ("test-no-diagnostics.c.exe",
+ "test-no-diagnostics.c.sarif",
+ __FILE__, "c");
+
+ /* No-op. */
+
+ return end_test ();
+};
+
+/* There should be no output from the text sink. */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-no-diagnostics.c "test-no-diagnostics-c.py" } } */
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+expected_line_num = 21
+
+def test_sarif_output_with_fixes(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ tool = run['tool']
+ assert tool['driver']['name'] == 'test-note-with-fix-it-hint.c.exe'
+
+ results = run['results']
+ assert len(results) == 1
+ result = results[0]
+ assert result['ruleId'] == 'error'
+ assert result['level'] == 'error'
+ assert result['message']['text'] == "unknown field 'colour'"
+ assert len(result['locations']) == 1
+ location = result['locations'][0]
+ phys_loc = location['physicalLocation']
+ assert phys_loc['artifactLocation']['uri'].endswith('test-note-with-fix-it-hint.c')
+ assert phys_loc['region']['startLine'] == expected_line_num
+ assert phys_loc['region']['startColumn'] == 13
+ assert phys_loc['region']['endColumn'] == 19
+ assert phys_loc['contextRegion']['startLine'] == expected_line_num
+ assert phys_loc['contextRegion']['snippet']['text'] \
+ == ' return p->colour;\n'
+
+ assert len(result['relatedLocations']) == 1
+ note = result['relatedLocations'][0]
+ phys_loc = note['physicalLocation']
+ assert phys_loc['artifactLocation']['uri'].endswith('test-note-with-fix-it-hint.c')
+ assert phys_loc['region']['startLine'] == expected_line_num
+ assert phys_loc['region']['startColumn'] == 13
+ assert phys_loc['region']['endColumn'] == 19
+ assert phys_loc['contextRegion']['startLine'] == expected_line_num
+ assert phys_loc['contextRegion']['snippet']['text'] \
+ == ' return p->colour;\n'
+ assert note['message']['text'] == "did you mean 'color'"
+
+ # TODO: we don't yet capture fix-it hints on notes (PR other/116164)
+ assert 'fixes' not in result
--- /dev/null
+/* Example of a grouped error and note, with a fix-it hint on the note.
+
+ Intended output is similar to:
+
+/PATH/test-note-with-fix-it-hint.c:21:13: error: unknown field 'colour'
+ 21 | return p->colour;
+ | ^~~~~~
+/PATH/test-note-with-fix-it-hint.c:21:13: note: did you mean 'color'
+ 21 | return p->colour;
+ | ^~~~~~
+ | color
+
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/* Placeholder source:
+_________11111111112
+12345678901234567890
+ return p->colour;
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ begin_test ("test-note-with-fix-it-hint.c.exe",
+ "test-note-with-fix-it-hint.c.sarif",
+ __FILE__, "c");
+
+ const diagnostic_physical_location *loc_token
+ = make_range (diag_mgr, main_file, line_num, 13, 18);
+
+ diagnostic_manager_begin_group (diag_mgr);
+
+ diagnostic *err = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_set_location (err, loc_token);
+ diagnostic_finish (err, "unknown field %qs", "colour");
+
+ diagnostic *n = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_NOTE);
+ diagnostic_set_location (n, loc_token);
+ diagnostic_add_fix_it_hint_replace (n, loc_token, "color");
+ diagnostic_finish (n, "did you mean %qs", "color");
+
+ diagnostic_manager_end_group (diag_mgr);
+
+ return end_test ();
+}
+
+/* Verify the output from the text sink.
+ { dg-regexp "\[^\n\r\]+test-note-with-fix-it-hint.c:21:13: error: unknown field 'colour'" }
+ { dg-begin-multiline-output "" }
+ 21 | return p->colour;
+ | ^~~~~~
+ { dg-end-multiline-output "" }
+ { dg-regexp "\[^\n\r\]+test-note-with-fix-it-hint.c:21:13: note: did you mean 'color'" }
+ { dg-begin-multiline-output "" }
+ 21 | return p->colour;
+ | ^~~~~~
+ | color
+ { dg-end-multiline-output "" } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-note-with-fix-it-hint.c "test-note-with-fix-it-hint-c.py" } } */
--- /dev/null
+/* Example of controlling options for text sinks,
+ with multiple text sinks,
+ and color output. */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+ diagnostic_text_sink *sink_1
+ = diagnostic_manager_add_text_sink (diag_mgr, stderr,
+ DIAGNOSTIC_COLORIZE_NO);
+ diagnostic_text_sink_set_source_printing_enabled (sink_1, 0);
+
+ diagnostic_text_sink *sink_2
+ = diagnostic_manager_add_text_sink (diag_mgr, stderr,
+ DIAGNOSTIC_COLORIZE_YES);
+ diagnostic_text_sink_set_labelled_source_colorization_enabled (sink_2, 0);
+
+ const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+ const diagnostic_physical_location *loc_start
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+ const diagnostic_physical_location *loc_end
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+ const diagnostic_physical_location *loc_range
+ = diagnostic_manager_new_location_from_range (diag_mgr,
+ loc_start,
+ loc_start,
+ loc_end);
+
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_set_location (d, loc_range);
+
+ diagnostic_finish (d, "can't find %qs", "foo");
+
+ diagnostic_manager_release (diag_mgr);
+ return 0;
+};
+
+/* Verify the output from text sink 1. */
+/* { dg-regexp "\[^\n\r\]+test-text-sink-options.c:10:8: error: can't find 'foo'" } */
+
+/* Verify the output from text sink 2.
+ { dg-regexp "\[^\n\r\]+test-text-sink-options.c:10:8:" }
+ { dg-begin-multiline-output "" }
+\e[m\e[K \e[01;31m\e[Kerror: \e[m\e[Kcan't find '\e[01m\e[Kfoo\e[m\e[K'
+ 10 | PRINT "hello world!";
+ | \e[01;31m\e[K^~~~~~~~~~~~\e[m\e[K
+ { dg-end-multiline-output "" } */
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+expected_line_num = 17
+expected_file_name = 'test-warning.c'
+
+def test_sarif_output(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ tool = run['tool']
+ assert tool['driver']['name'] == expected_file_name + '.exe'
+
+ invocations = run['invocations']
+ assert len(invocations) == 1
+ assert 'workingDirectory' in invocations[0]
+ assert 'startTimeUtc' in invocations[0]
+ assert invocations[0]['executionSuccessful'] == True
+ assert invocations[0]['toolExecutionNotifications'] == []
+ assert 'endTimeUtc' in invocations[0]
+
+ artifacts = run['artifacts']
+ assert len(artifacts) == 1
+ assert artifacts[0]['location']['uri'].endswith(expected_file_name)
+ assert artifacts[0]['sourceLanguage'] == 'c'
+ assert 'PRINT' in artifacts[0]['contents']['text']
+ assert artifacts[0]['roles'] == ["analysisTarget"]
+
+ results = run['results']
+ assert len(results) == 1
+ assert results[0]['ruleId'] == 'warning'
+ assert results[0]['level'] == 'warning'
+ assert results[0]['message']['text'] == "this is a warning"
+ assert len(results[0]['locations']) == 1
+ location = results[0]['locations'][0]
+ phys_loc = location['physicalLocation']
+ assert phys_loc['artifactLocation']['uri'].endswith(expected_file_name)
+ assert phys_loc['region']['startLine'] == expected_line_num
+ assert phys_loc['region']['startColumn'] == 8
+ assert phys_loc['region']['endColumn'] == 20
+ assert phys_loc['contextRegion']['startLine'] == expected_line_num
+ assert phys_loc['contextRegion']['snippet']['text'] \
+ == 'PRINT "hello world!";\n'
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+final_line_num = 34
+
+line_num_call_to_PyList_New = final_line_num - 7;
+line_num_for_loop = final_line_num - 5;
+line_num_call_to_PyList_Append = final_line_num - 3;
+
+expected_file_name = 'test-warning-with-path.c'
+
+def test_sarif_output_for_warning_with_path(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ tool = run['tool']
+ assert tool['driver']['name'] == expected_file_name + '.exe'
+
+ results = run['results']
+ assert len(results) == 1
+
+ result = results[0]
+ assert result['ruleId'] == 'warning'
+ assert result['level'] == 'warning'
+ assert result['message']['text'] \
+ == "passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter"
+ assert len(result['locations']) == 1
+ location = result['locations'][0]
+
+ phys_loc = location['physicalLocation']
+ assert phys_loc['artifactLocation']['uri'].endswith(expected_file_name)
+ assert phys_loc['region']['startLine'] \
+ == line_num_call_to_PyList_Append
+ assert phys_loc['region']['startColumn'] == 5
+ assert phys_loc['region']['endColumn'] == 30
+ assert phys_loc['contextRegion']['startLine'] \
+ == line_num_call_to_PyList_Append
+ assert phys_loc['contextRegion']['snippet']['text'] \
+ == ' PyList_Append(list, item);\n'
+
+ assert len(location['logicalLocations']) == 1
+ logical_loc = location['logicalLocations'][0]
+ assert logical_loc['name'] == 'make_a_list_of_random_ints_badly'
+ assert logical_loc['fullyQualifiedName'] == 'make_a_list_of_random_ints_badly'
+ assert logical_loc['decoratedName'] == 'make_a_list_of_random_ints_badly'
+ assert logical_loc['kind'] == 'function'
+
+ assert len(result['codeFlows']) == 1
+ assert len(result['codeFlows'][0]['threadFlows']) == 1
+ thread_flow = result['codeFlows'][0]['threadFlows'][0]
+
+ assert len(thread_flow['locations']) == 3
+
+ tfl_0 = thread_flow['locations'][0]
+ tfl_0_loc = tfl_0['location']
+ assert get_location_artifact_uri(tfl_0_loc).endswith(expected_file_name)
+ assert get_location_physical_region(tfl_0_loc)['startLine'] \
+ == line_num_call_to_PyList_New
+ assert get_location_physical_region(tfl_0_loc)['startColumn'] == 10
+ assert get_location_physical_region(tfl_0_loc)['endColumn'] == 23
+ assert get_location_snippet_text(tfl_0_loc) \
+ == ' list = PyList_New(0);\n'
+ assert tfl_0_loc['logicalLocations'] == location['logicalLocations']
+ assert tfl_0_loc['message']['text'] \
+ == "when 'PyList_New' fails, returning NULL"
+ assert tfl_0['nestingLevel'] == 0
+ assert tfl_0['executionOrder'] == 1
+
+ tfl_1 = thread_flow['locations'][1]
+ tfl_1_loc = tfl_1['location']
+ assert get_location_artifact_uri(tfl_1_loc).endswith(expected_file_name)
+ assert get_location_physical_region(tfl_1_loc)['startLine'] \
+ == line_num_for_loop
+ assert get_location_physical_region(tfl_1_loc)['startColumn'] == 15
+ assert get_location_physical_region(tfl_1_loc)['endColumn'] == 24
+ assert get_location_snippet_text(tfl_1_loc) \
+ == ' for (i = 0; i < count; i++) {\n'
+ assert tfl_1_loc['logicalLocations'] == location['logicalLocations']
+ assert tfl_1_loc['message']['text'] \
+ == "when 'i < count'"
+ assert tfl_1['nestingLevel'] == 0
+ assert tfl_1['executionOrder'] == 2
+
+ tfl_2 = thread_flow['locations'][2]
+ tfl_2_loc = tfl_2['location']
+ assert get_location_artifact_uri(tfl_2_loc).endswith(expected_file_name)
+ assert get_location_physical_region(tfl_2_loc)['startLine'] \
+ == line_num_call_to_PyList_Append
+ assert get_location_physical_region(tfl_2_loc)['startColumn'] == 5
+ assert get_location_physical_region(tfl_2_loc)['endColumn'] == 30
+ assert get_location_snippet_text(tfl_2_loc) \
+ == ' PyList_Append(list, item);\n'
+ assert tfl_2_loc['logicalLocations'] == location['logicalLocations']
+ assert tfl_2_loc['message']['text'] \
+ == "when calling 'PyList_Append', passing NULL from (1) as argument 1"
+ assert tfl_2['nestingLevel'] == 0
+ assert tfl_2['executionOrder'] == 3
--- /dev/null
+/* Example of emitting a warning with an execution path.
+
+TODO:
+
+ Intended output is similar to:
+
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/*
+_________111111111122222222223333333333444444444455555555556
+123456789012345678901234567890123456789012345678901234567890
+begin fake source
+PyObject *
+make_a_list_of_random_ints_badly(PyObject *self,
+ PyObject *args)
+{
+ PyObject *list, *item;
+ long count, i;
+
+ if (!PyArg_ParseTuple(args, "i", &count)) {
+ return NULL;
+ }
+
+ list = PyList_New(0);
+
+ for (i = 0; i < count; i++) {
+ item = PyLong_FromLong(random());
+ PyList_Append(list, item);
+ }
+
+ return list;
+}
+end fake source
+*/
+const int final_line_num = __LINE__ - 4; /* line of "return list;" */
+
+/* begin line consts */
+const int line_num_call_to_PyList_New = final_line_num - 7;
+const int line_num_for_loop = final_line_num - 5;
+const int line_num_call_to_PyList_Append = final_line_num - 3;
+/* end line consts */
+
+int
+main ()
+{
+ begin_test ("test-warning-with-path.c.exe",
+ "test-warning-with-path.c.sarif",
+ __FILE__, "c");
+
+ /* begin full example */
+ /* begin create phys locs */
+ const diagnostic_physical_location *loc_call_to_PyList_New
+ = make_range (diag_mgr, main_file, line_num_call_to_PyList_New, 10, 22);
+ const diagnostic_physical_location *loc_for_cond
+ = make_range (diag_mgr, main_file, line_num_for_loop, 15, 23);
+ const diagnostic_physical_location *loc_call_to_PyList_Append
+ = make_range (diag_mgr, main_file, line_num_call_to_PyList_Append, 5, 29);
+ /* end create phys locs */
+
+ /* begin create logical locs */
+ const char *funcname = "make_a_list_of_random_ints_badly";
+ const diagnostic_logical_location *logical_loc
+ = diagnostic_manager_new_logical_location (diag_mgr,
+ DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION,
+ NULL, /* parent */
+ funcname,
+ funcname,
+ funcname);
+ /* end create logical locs */
+
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_WARNING);
+ diagnostic_set_location (d, loc_call_to_PyList_Append);
+ diagnostic_set_logical_location (d, logical_loc);
+
+ /* begin path creation */
+ diagnostic_execution_path *path = diagnostic_add_execution_path (d);
+
+ 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");
+ diagnostic_execution_path_add_event (path,
+ loc_for_cond,
+ logical_loc, 0,
+ "when %qs", "i < count");
+ 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);
+ /* end path creation */
+
+ diagnostic_finish (d,
+ "passing NULL as argument %i to %qs"
+ " which requires a non-NULL parameter",
+ 1, "PyList_Append");
+ /* end full example */
+
+ return end_test ();
+};
+
+/* Check the output from the text sink.
+ { dg-begin-multiline-output "" }
+In function 'make_a_list_of_random_ints_badly':
+ { dg-end-multiline-output "" }
+ { dg-regexp "\[^\n\r\]+test-warning-with-path.c:31:5: warning: passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" }
+ { dg-begin-multiline-output "" }
+ 31 | PyList_Append(list, item);
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~
+ 'make_a_list_of_random_ints_badly': events 1-3
+ 27 | list = PyList_New(0);
+ | ^~~~~~~~~~~~~
+ | |
+ | (1) when 'PyList_New' fails, returning NULL
+ 28 |
+ 29 | for (i = 0; i < count; i++) {
+ | ~~~~~~~~~
+ | |
+ | (2) when 'i < count'
+ 30 | item = PyLong_FromLong(random());
+ 31 | PyList_Append(list, item);
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~
+ | |
+ | (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
+ { dg-end-multiline-output "" } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-warning-with-path.c "test-warning-with-path-c.py" } } */
--- /dev/null
+/* Example of emitting a warning.
+
+ Intended output is similar to:
+
+/PATH/test-warning.c:17:8: warning: this is a warning
+ 17 | PRINT "hello world!";
+ | ^~~~~~~~~~~~
+
+ along with the equivalent in SARIF. */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ begin_test ("test-warning.c.exe",
+ "test-warning.c.sarif",
+ __FILE__, "c");
+
+ const diagnostic_physical_location *loc_start
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+ main_file,
+ line_num,
+ 8);
+ const diagnostic_physical_location *loc_end
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+ main_file,
+ line_num,
+ 19);
+ const diagnostic_physical_location *loc_range
+ = diagnostic_manager_new_location_from_range (diag_mgr,
+ loc_start,
+ loc_start,
+ loc_end);
+
+ /* begin quoted source */
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_WARNING);
+ diagnostic_set_location (d, loc_range);
+
+ diagnostic_finish (d, "this is a warning");
+ /* end quoted source */
+
+ return end_test ();
+}
+
+/* Verify the output from the text sink.
+ { dg-regexp "\[^\n\r\]+test-warning.c:17:8: warning: this is a warning" }
+ { dg-begin-multiline-output "" }
+ 17 | PRINT "hello world!";
+ | ^~~~~~~~~~~~
+ { dg-end-multiline-output "" } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-warning.c "test-warning-c.py" } } */
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+expected_line_num = 8
+
+def test_sarif_output(sarif):
+ schema = sarif['$schema']
+ assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json'
+
+ version = sarif['version']
+ assert version == '2.1.0'
+
+ runs = sarif['runs']
+ run = runs[0]
+
+ tool = run['tool']
+ assert tool['driver']['name'] == 'test-write-sarif-to-file.c.exe'
+
+ invocations = run['invocations']
+ assert len(invocations) == 1
+ assert 'workingDirectory' in invocations[0]
+ assert 'startTimeUtc' in invocations[0]
+ assert invocations[0]['executionSuccessful'] == False
+ assert invocations[0]['toolExecutionNotifications'] == []
+ assert 'endTimeUtc' in invocations[0]
+
+ artifacts = run['artifacts']
+ assert len(artifacts) == 1
+ assert artifacts[0]['location']['uri'] \
+ .endswith('test-write-sarif-to-file.c')
+ assert artifacts[0]['sourceLanguage'] == 'c'
+ assert 'PRINT' in artifacts[0]['contents']['text']
+ assert artifacts[0]['roles'] == ["analysisTarget"]
+
+ results = run['results']
+ assert len(results) == 1
+ assert results[0]['ruleId'] == 'error'
+ assert results[0]['level'] == 'error'
+ assert results[0]['message']['text'] == "can't find 'foo'"
+ assert len(results[0]['locations']) == 1
+ location = results[0]['locations'][0]
+ phys_loc = location['physicalLocation']
+ assert phys_loc['artifactLocation']['uri'] \
+ .endswith('test-write-sarif-to-file.c')
+ assert phys_loc['region']['startLine'] == expected_line_num
+ assert phys_loc['region']['startColumn'] == 8
+ assert phys_loc['region']['endColumn'] == 20
+ assert phys_loc['contextRegion']['startLine'] == expected_line_num
+ assert phys_loc['contextRegion']['snippet']['text'] \
+ == 'PRINT "hello world!";\n'
--- /dev/null
+/* Example of writing diagnostics as SARIF to a file. */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ FILE *sarif_outfile = fopen ("test-write-sarif-to-file.c.sarif", "w");
+ if (!sarif_outfile)
+ return -1;
+
+ diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+ diagnostic_manager_set_tool_name (diag_mgr, "test-write-sarif-to-file.c.exe");
+
+ const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+
+ diagnostic_manager_add_sarif_sink (diag_mgr, sarif_outfile, file,
+ DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+ const diagnostic_physical_location *loc_start
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+ const diagnostic_physical_location *loc_end
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+ const diagnostic_physical_location *loc_range
+ = diagnostic_manager_new_location_from_range (diag_mgr,
+ loc_start,
+ loc_start,
+ loc_end);
+
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_set_location (d, loc_range);
+
+ diagnostic_finish (d, "can't find %qs", "foo");
+
+ diagnostic_manager_release (diag_mgr);
+
+ fclose (sarif_outfile);
+
+ return 0;
+};
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest test-write-sarif-to-file.c "test-write-sarif-to-file-c.py" } } */
--- /dev/null
+/* Example of writing diagnostics in text form, but to a file,
+ rather than stderr. */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+ FILE *outfile = fopen ("test.txt", "w");
+ if (!outfile)
+ return -1;
+
+ diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+ diagnostic_manager_add_text_sink (diag_mgr, outfile,
+ DIAGNOSTIC_COLORIZE_NO);
+
+ const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+ const diagnostic_physical_location *loc_start
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+ const diagnostic_physical_location *loc_end
+ = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+ const diagnostic_physical_location *loc_range
+ = diagnostic_manager_new_location_from_range (diag_mgr,
+ loc_start,
+ loc_start,
+ loc_end);
+
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_set_location (d, loc_range);
+
+ diagnostic_finish (d, "can't find %qs", "foo");
+
+ diagnostic_manager_release (diag_mgr);
+
+ fclose (outfile);
+
+ return 0;
+};