Add libdiagnostics (v4)

This patch adds a new libdiagnostics shared library available as
part of the GCC build via --enable-libdiagnostics when
configuring GCC.

It combines the following patches from:
  https://gcc.gnu.org/pipermail/gcc-patches/2024-November/668632.html
    [PATCH 1/8] libdiagnostics v4: header
    [PATCH 2/8] libdiagnostics v4: implementation
    [PATCH 3/8] libdiagnostics: add API docs
    [PATCH 4/8] libdiagnostics v4: add C++ wrapper API
    [PATCH 6/8] libdiagnostics v4: test suite

ChangeLog:
	* configure.ac (--enable-libdiagnostics): New.
	* configure: Regenerate.

gcc/ChangeLog:
	* configure.ac (check_languages): Add check-libdiagnostics.
	(--enable-libdiagnostics): New.
	* configure: Regenerate.
	* Makefile.in (enable_libdiagnostics): New.
	(lang_checks): If libdiagnostics is enabled, add
	check-libdiagnostics.
	(ALL_HOST_OBJS): If libdiagnostics is enabled, add
	$(libdiagnostics_OBJS).
	(start.encap): Add LIBDIAGNOSTICS.
	(libdiagnostics_OBJS): New.
	(LIBDIAGNOSTICS_VERSION_NUM): New, adapted from code in
	jit/Make-lang.in.
	(LIBDIAGNOSTICS_MINOR_NUM): Likewise.
	(LIBDIAGNOSTICS_RELEASE_NUM): Likewise.
	(LIBDIAGNOSTICS_FILENAME): Likewise.
	(LIBDIAGNOSTICS_IMPORT_LIB): Likewise.
	(libdiagnostics): Likewise.
	(LIBDIAGNOSTICS_AGE): Likewise.
	(LIBDIAGNOSTICS_BASENAME): Likewise.
	(LIBDIAGNOSTICS_SONAME): Likewise.
	(LIBDIAGNOSTICS_LINKER_NAME): Likewise.
	(LIBDIAGNOSTICS_COMMA): Likewise.
	(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION): Likewise.
	(LIBDIAGNOSTICS_SONAME_OPTION): Likewise.
	(LIBDIAGNOSTICS_SONAME_SYMLINK): Likewise.
	(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK): Likewise.
	(LIBDIAGNOSTICS_FILENAME): Likewise.
	(libdiagnostics.serial): Likewise.
	(LIBDIAGNOSTICS_EXTRA_OPTS): Likewise.
	(install): If libdiagnostics is enabled, add
	install-libdiagnostics.
	(libdiagnostics.install-headers): New.
	(libdiagnostics.install-common): New, adapted from code in
	jit/Make-lang.in.
	(install-libdiagnostics): New.
	* diagnostic-format-text.h
	(diagnostic_text_output_format::get_location_text): Make public.
	* doc/install.texi (--enable-libdiagnostics): New.
	* doc/libdiagnostics/Makefile: New file.
	* doc/libdiagnostics/conf.py: New file.
	* doc/libdiagnostics/index.rst: New file.
	* doc/libdiagnostics/make.bat: New file.
	* doc/libdiagnostics/topics/diagnostic-manager.rst: New file.
	* doc/libdiagnostics/topics/diagnostics.rst: New file.
	* doc/libdiagnostics/topics/execution-paths.rst: New file.
	* doc/libdiagnostics/topics/fix-it-hints.rst: New file.
	* doc/libdiagnostics/topics/index.rst: New file.
	* doc/libdiagnostics/topics/logical-locations.rst: New file.
	* doc/libdiagnostics/topics/message-formatting.rst: New file.
	* doc/libdiagnostics/topics/metadata.rst: New file.
	* doc/libdiagnostics/topics/physical-locations.rst: New file.
	* doc/libdiagnostics/topics/retrofitting.rst: New file.
	* doc/libdiagnostics/topics/sarif.rst: New file.
	* doc/libdiagnostics/topics/text-output.rst: New file.
	* doc/libdiagnostics/topics/ux.rst: New file.
	* doc/libdiagnostics/tutorial/01-hello-world.rst: New file.
	* doc/libdiagnostics/tutorial/02-physical-locations.rst: New file.
	* doc/libdiagnostics/tutorial/03-logical-locations.rst: New file.
	* doc/libdiagnostics/tutorial/04-notes.rst: New file.
	* doc/libdiagnostics/tutorial/05-warnings.rst: New file.
	* doc/libdiagnostics/tutorial/06-fix-it-hints.rst: New file.
	* doc/libdiagnostics/tutorial/07-execution-paths.rst: New file.
	* doc/libdiagnostics/tutorial/index.rst: New file.
	* libdiagnostics++.h: New file.
	* libdiagnostics.cc: New file.
	* libdiagnostics.h: New file.
	* libdiagnostics.map: New file.

gcc/testsuite/ChangeLog:
	* libdiagnostics.dg/libdiagnostics.exp: New, adapted from jit.exp.
	* libdiagnostics.dg/sarif.py: New.
	* libdiagnostics.dg/test-dump.c: New test.
	* libdiagnostics.dg/test-error-c.py: New test.
	* libdiagnostics.dg/test-error-with-note-c.py: New test.
	* libdiagnostics.dg/test-error-with-note.c: New test.
	* libdiagnostics.dg/test-error-with-note.cc: New test.
	* libdiagnostics.dg/test-error.c: New test.
	* libdiagnostics.dg/test-error.cc: New test.
	* libdiagnostics.dg/test-example-1.c: New test.
	* libdiagnostics.dg/test-fix-it-hint-c.py: New test.
	* libdiagnostics.dg/test-fix-it-hint.c: New test.
	* libdiagnostics.dg/test-fix-it-hint.cc: New test.
	* libdiagnostics.dg/test-helpers++.h: New test.
	* libdiagnostics.dg/test-helpers.h: New test.
	* libdiagnostics.dg/test-labelled-ranges.c: New test.
	* libdiagnostics.dg/test-labelled-ranges.cc: New test.
	* libdiagnostics.dg/test-labelled-ranges.py: New test.
	* libdiagnostics.dg/test-logical-location-c.py: New test.
	* libdiagnostics.dg/test-logical-location.c: New test.
	* libdiagnostics.dg/test-metadata-c.py: New test.
	* libdiagnostics.dg/test-metadata.c: New test.
	* libdiagnostics.dg/test-multiple-lines-c.py: New test.
	* libdiagnostics.dg/test-multiple-lines.c: New test.
	* libdiagnostics.dg/test-no-column-c.py: New test.
	* libdiagnostics.dg/test-no-column.c: New test.
	* libdiagnostics.dg/test-no-diagnostics-c.py: New test.
	* libdiagnostics.dg/test-no-diagnostics.c: New test.
	* libdiagnostics.dg/test-note-with-fix-it-hint-c.py: New test.
	* libdiagnostics.dg/test-note-with-fix-it-hint.c: New test.
	* libdiagnostics.dg/test-text-sink-options.c: New test.
	* libdiagnostics.dg/test-warning-c.py: New test.
	* libdiagnostics.dg/test-warning-with-path-c.py: New test.
	* libdiagnostics.dg/test-warning-with-path.c: New test.
	* libdiagnostics.dg/test-warning.c: New test.
	* libdiagnostics.dg/test-write-sarif-to-file-c.py: New test.
	* libdiagnostics.dg/test-write-sarif-to-file.c: New test.
	* libdiagnostics.dg/test-write-text-to-file.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
This commit is contained in:
David Malcolm 2024-11-18 17:08:36 -05:00
parent c9d21e19df
commit 99a909aba3
75 changed files with 8285 additions and 4 deletions

42
configure vendored
View File

@ -691,6 +691,7 @@ extra_host_libiberty_configure_flags
stage1_languages stage1_languages
host_libs_picflag host_libs_picflag
CRAB1_LIBS CRAB1_LIBS
enable_libdiagnostics
PICFLAG PICFLAG
host_shared host_shared
gcc_host_pie gcc_host_pie
@ -844,6 +845,7 @@ enable_linker_plugin_configure_flags
enable_linker_plugin_flags enable_linker_plugin_flags
enable_host_pie enable_host_pie
enable_host_shared enable_host_shared
enable_libdiagnostics
enable_stage1_languages enable_stage1_languages
enable_objc_gc enable_objc_gc
with_target_bdw_gc with_target_bdw_gc
@ -1578,6 +1580,7 @@ Optional Features:
plugins [none] plugins [none]
--enable-host-pie build position independent host executables --enable-host-pie build position independent host executables
--enable-host-shared build host code as shared libraries --enable-host-shared build host code as shared libraries
--enable-libdiagnostics build libdiagnostics shared library
--enable-stage1-languages[=all] --enable-stage1-languages[=all]
choose additional languages to build during stage1. choose additional languages to build during stage1.
Mostly useful for compiler development Mostly useful for compiler development
@ -9603,6 +9606,45 @@ fi
# 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 # 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 # 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 # We are doing the test here and not in the gcc/configure to be able to nicely disable the

View File

@ -2044,6 +2044,41 @@ fi
AC_SUBST(PICFLAG) 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 # 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 # 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 # We are doing the test here and not in the gcc/configure to be able to nicely disable the

View File

@ -436,6 +436,8 @@ endif
enable_host_shared = @enable_host_shared@ enable_host_shared = @enable_host_shared@
enable_libdiagnostics = @enable_libdiagnostics@
enable_as_accelerator = @enable_as_accelerator@ enable_as_accelerator = @enable_as_accelerator@
CPPLIB = ../libcpp/libcpp.a CPPLIB = ../libcpp/libcpp.a
@ -618,6 +620,9 @@ xm_include_list=@xm_include_list@
xm_defines=@xm_defines@ xm_defines=@xm_defines@
lang_checks= lang_checks=
lang_checks_parallelized= 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_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_specs_files=@lang_specs_files@
lang_tree_files=@lang_tree_files@ lang_tree_files=@lang_tree_files@
@ -1880,6 +1885,10 @@ endif
# compilation or not. # compilation or not.
ALL_HOST_OBJS = $(ALL_HOST_FRONTEND_OBJS) $(ALL_HOST_BACKEND_OBJS) 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 \ BACKEND = libbackend.a main.o libcommon-target.a libcommon.a \
$(CPPLIB) $(LIBDECNUMBER) $(CPPLIB) $(LIBDECNUMBER)
@ -2186,7 +2195,7 @@ all.cross: native gcc-cross$(exeext) cpp$(exeext) specs \
libgcc-support lang.all.cross doc selftest @GENINSRC@ srcextra libgcc-support lang.all.cross doc selftest @GENINSRC@ srcextra
# This is what must be made before installing GCC and converting libraries. # This is what must be made before installing GCC and converting libraries.
start.encap: native xgcc$(exeext) cpp$(exeext) specs \ 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. # These can't be made until after GCC can run.
rest.encap: lang.rest.encap rest.encap: lang.rest.encap
# This is what is made with the host's compiler # This is what is made with the host's compiler
@ -2275,6 +2284,129 @@ cpp$(exeext): $(GCC_OBJS) c-family/cppspec.o libcommon-target.a $(LIBDEPS) \
c-family/cppspec.o $(EXTRA_GCC_OBJS) libcommon-target.a \ c-family/cppspec.o $(EXTRA_GCC_OBJS) libcommon-target.a \
$(EXTRA_GCC_LIBS) $(LIBS) $(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. # Dump a specs file to make -B./ read these specs over installed ones.
$(SPECS): xgcc$(exeext) $(SPECS): xgcc$(exeext)
$(GCC_FOR_TARGET) -dumpspecs > tmp-specs $(GCC_FOR_TARGET) -dumpspecs > tmp-specs
@ -3816,6 +3948,10 @@ ifeq ($(enable_plugin),yes)
install: install-plugin install: install-plugin
endif endif
ifeq ($(enable_libdiagnostics),yes)
install: install-libdiagnostics
endif
install-strip: override INSTALL_PROGRAM = $(INSTALL_STRIP_PROGRAM) install-strip: override INSTALL_PROGRAM = $(INSTALL_STRIP_PROGRAM)
ifneq ($(STRIP),) ifneq ($(STRIP),)
install-strip: STRIPPROG = $(STRIP) install-strip: STRIPPROG = $(STRIP)
@ -3992,6 +4128,47 @@ install-driver: installdirs xgcc$(exeext)
fi; \ fi; \
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 the info files.
# $(INSTALL_DATA) might be a relative pathname, so we can't cd into srcdir # $(INSTALL_DATA) might be a relative pathname, so we can't cd into srcdir
# to do the install. # to do the install.

26
gcc/configure vendored
View File

@ -637,6 +637,8 @@ LD_PICFLAG
PICFLAG PICFLAG
enable_default_pie enable_default_pie
enable_host_bind_now enable_host_bind_now
LIBDIAGNOSTICS
enable_libdiagnostics
enable_host_pie enable_host_pie
enable_host_shared enable_host_shared
enable_plugin enable_plugin
@ -1051,6 +1053,7 @@ enable_version_specific_runtime_libs
enable_plugin enable_plugin
enable_host_shared enable_host_shared
enable_host_pie enable_host_pie
enable_libdiagnostics
enable_host_bind_now enable_host_bind_now
enable_libquadmath_support enable_libquadmath_support
with_linker_hash_style with_linker_hash_style
@ -1826,6 +1829,7 @@ Optional Features:
--enable-plugin enable plugin support --enable-plugin enable plugin support
--enable-host-shared build host code as shared libraries --enable-host-shared build host code as shared libraries
--enable-host-pie build host code as PIE --enable-host-pie build host code as PIE
--enable-libdiagnostics build libdiagnostics shared library
--enable-host-bind-now link host code as BIND_NOW --enable-host-bind-now link host code as BIND_NOW
--disable-libquadmath-support --disable-libquadmath-support
disable libquadmath support for Fortran disable libquadmath support for Fortran
@ -21456,7 +21460,7 @@ else
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<_LT_EOF cat > conftest.$ac_ext <<_LT_EOF
#line 21459 "configure" #line 21463 "configure"
#include "confdefs.h" #include "confdefs.h"
#if HAVE_DLFCN_H #if HAVE_DLFCN_H
@ -21562,7 +21566,7 @@ else
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<_LT_EOF cat > conftest.$ac_ext <<_LT_EOF
#line 21565 "configure" #line 21569 "configure"
#include "confdefs.h" #include "confdefs.h"
#if HAVE_DLFCN_H #if HAVE_DLFCN_H
@ -33838,6 +33842,9 @@ for language in $all_selected_languages
do do
check_languages="$check_languages check-$language" check_languages="$check_languages check-$language"
done done
if test x$enable_libdiagnostics = xyes; then
check_languages="$check_languages check-libdiagnostics"
fi
selftest_languages= selftest_languages=
for language in $all_selected_languages for language in $all_selected_languages
@ -34276,6 +34283,21 @@ fi
# 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 # Enable --enable-host-bind-now
# Check whether --enable-host-bind-now was given. # Check whether --enable-host-bind-now was given.
if test "${enable_host_bind_now+set}" = set; then : if test "${enable_host_bind_now+set}" = set; then :

View File

@ -7380,6 +7380,9 @@ for language in $all_selected_languages
do do
check_languages="$check_languages check-$language" check_languages="$check_languages check-$language"
done done
if test x$enable_libdiagnostics = xyes; then
check_languages="$check_languages check-libdiagnostics"
fi
selftest_languages= selftest_languages=
for language in $all_selected_languages for language in $all_selected_languages
@ -7612,6 +7615,19 @@ AC_ARG_ENABLE(host-pie,
[build host code as PIE])]) [build host code as PIE])])
AC_SUBST(enable_host_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 # Enable --enable-host-bind-now
AC_ARG_ENABLE(host-bind-now, AC_ARG_ENABLE(host-bind-now,
[AS_HELP_STRING([--enable-host-bind-now], [AS_HELP_STRING([--enable-host-bind-now],

View File

@ -103,13 +103,14 @@ public:
m_show_nesting_levels = show_nesting_levels; m_show_nesting_levels = show_nesting_levels;
} }
label_text get_location_text (const expanded_location &s) const;
protected: protected:
void print_any_cwe (const diagnostic_info &diagnostic); void print_any_cwe (const diagnostic_info &diagnostic);
void print_any_rules (const diagnostic_info &diagnostic); void print_any_rules (const diagnostic_info &diagnostic);
void print_option_information (const diagnostic_info &diagnostic, void print_option_information (const diagnostic_info &diagnostic,
diagnostic_t orig_diag_kind); diagnostic_t orig_diag_kind);
label_text get_location_text (const expanded_location &s) const;
bool includes_seen_p (const line_map_ordinary *map); bool includes_seen_p (const line_map_ordinary *map);
/* For handling diagnostic_buffer. */ /* For handling diagnostic_buffer. */

View File

@ -1231,6 +1231,13 @@ virtual calls in verifiable mode at all. However the libvtv library will
still be built (see @option{--disable-libvtv} to turn off building libvtv). still be built (see @option{--disable-libvtv} to turn off building libvtv).
@option{--disable-vtable-verify} is the default. @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 @item --disable-gcov
Specify that the run-time library used for coverage analysis Specify that the run-time library used for coverage analysis
and associated host tools should not be built. and associated host tools should not be built.

View File

@ -0,0 +1,20 @@
# 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)

View File

@ -0,0 +1,27 @@
# 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']

View File

@ -0,0 +1,113 @@
.. 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`

View File

@ -0,0 +1,35 @@
@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

View File

@ -0,0 +1,58 @@
.. 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.

View File

@ -0,0 +1,127 @@
.. 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.

View File

@ -0,0 +1,93 @@
.. 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>`_).

View File

@ -0,0 +1,135 @@
.. 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.

View File

@ -0,0 +1,38 @@
.. 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

View File

@ -0,0 +1,109 @@
.. 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.

View File

@ -0,0 +1,224 @@
.. 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. */

View File

@ -0,0 +1,149 @@
.. 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);
| ^~~~~~~~~~

View File

@ -0,0 +1,281 @@
.. 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 *

View File

@ -0,0 +1,23 @@
.. 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

View File

@ -0,0 +1,51 @@
.. 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

View File

@ -0,0 +1,87 @@
.. 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.

View File

@ -0,0 +1,26 @@
.. 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>`_.

View File

@ -0,0 +1,173 @@
.. 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.

View File

@ -0,0 +1,260 @@
.. 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.

View File

@ -0,0 +1,60 @@
.. 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`.

View File

@ -0,0 +1,66 @@
.. 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.

View File

@ -0,0 +1,44 @@
.. 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.

View File

@ -0,0 +1,61 @@
.. 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>`.

View File

@ -0,0 +1,141 @@
.. 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>`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -0,0 +1,32 @@
.. 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

595
gcc/libdiagnostics++.h Normal file
View File

@ -0,0 +1,595 @@
/* 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

1683
gcc/libdiagnostics.cc Normal file

File diff suppressed because it is too large Load Diff

691
gcc/libdiagnostics.h Normal file
View File

@ -0,0 +1,691 @@
/* 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 */

72
gcc/libdiagnostics.map Normal file
View File

@ -0,0 +1,72 @@
# 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: *;
};

View File

@ -0,0 +1,296 @@
# 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

View File

@ -0,0 +1,23 @@
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']

View File

@ -0,0 +1,69 @@
/* 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;
};

View File

@ -0,0 +1,54 @@
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'

View File

@ -0,0 +1,50 @@
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?'

View File

@ -0,0 +1,76 @@
/* 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" } } */

View File

@ -0,0 +1,55 @@
/* 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\\\?" } */

View File

@ -0,0 +1,61 @@
/* 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" } } */

View File

@ -0,0 +1,47 @@
/* 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 "" } */

View File

@ -0,0 +1,43 @@
/* 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" } */

View File

@ -0,0 +1,46 @@
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'

View File

@ -0,0 +1,83 @@
/* 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" } } */

View File

@ -0,0 +1,74 @@
/* 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

View File

@ -0,0 +1,28 @@
/* 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 */

View File

@ -0,0 +1,72 @@
/* 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 */

View File

@ -0,0 +1,71 @@
/* 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" } } */

View File

@ -0,0 +1,64 @@
/* 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" } } */

View File

@ -0,0 +1,48 @@
# 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 *'

View File

@ -0,0 +1,37 @@
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'

View File

@ -0,0 +1,81 @@
/* 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" } } */

View File

@ -0,0 +1,45 @@
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'"

View File

@ -0,0 +1,61 @@
/* 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" } } */

View File

@ -0,0 +1,83 @@
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

View File

@ -0,0 +1,78 @@
/* 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" } } */

View File

@ -0,0 +1,35 @@
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'

View File

@ -0,0 +1,54 @@
/* 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" } } */

View File

@ -0,0 +1,42 @@
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

View File

@ -0,0 +1,25 @@
/* 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" } } */

View File

@ -0,0 +1,54 @@
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

View File

@ -0,0 +1,69 @@
/* 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" } } */

View File

@ -0,0 +1,59 @@
/* 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 "" }
 error: can't find 'foo'
10 | PRINT "hello world!";
| ^~~~~~~~~~~~
{ dg-end-multiline-output "" } */

View File

@ -0,0 +1,54 @@
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'

View File

@ -0,0 +1,108 @@
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

View File

@ -0,0 +1,138 @@
/* 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" } } */

View File

@ -0,0 +1,67 @@
/* 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" } } */

View File

@ -0,0 +1,55 @@
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'

View File

@ -0,0 +1,55 @@
/* 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" } } */

View File

@ -0,0 +1,47 @@
/* 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;
};