new file mode 100644
@@ -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
new file mode 100644
@@ -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']
new file mode 100644
@@ -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;
+};
new file mode 100644
@@ -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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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 '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(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'
new file mode 100644
@@ -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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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'"
+ 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'] == 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'
+
+ 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'] == 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'
+ assert note['message']['text'] == 'have you looked behind the couch?'
new file mode 100644
@@ -0,0 +1,74 @@
+/* Example of emitting an error with an associated note.
+
+ Intended output is similar to:
+
+PATH/test-error-with-note.c:18:8: error: can't find 'foo'
+ 6 | PRINT "hello world!";
+ | ^~~~~~~~~~~~
+PATH/test-error-with-note.c:18:8: note: have you looked behind the couch?
+
+ 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-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,
+ 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);
+
+ 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");
+
+ 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);
+
+ return end_test ();
+};
+
+/* Verify the output from the text sink.
+ { dg-regexp "\[^\n\r\]+test-error-with-note.c:18:8: error: can't find 'foo'" }
+ { dg-begin-multiline-output "" }
+ 18 | PRINT "hello world!";
+ | ^~~~~~~~~~~~
+ { dg-end-multiline-output "" }
+ { dg-regexp "\[^\n\r\]+test-error-with-note.c:18:8: 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" } } */
new file mode 100644
@@ -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\\\?" } */
new file mode 100644
@@ -0,0 +1,59 @@
+/* Example of emitting an error.
+
+ Intended output is similar to:
+
+PATH/test-error-with-note.c:6: error: can't find 'foo'
+ 6 | 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-error.c.exe",
+ "test-error.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);
+
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_set_location (d, loc_range);
+
+ diagnostic_finish (d, "can't find %qs", "foo");
+
+ return end_test ();
+};
+
+/* Verify the output from the text sink.
+ { dg-regexp "\[^\n\r\]+test-error.c:17:8: error: can't find 'foo'" }
+ { 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-error.c "test-error-c.py" } } */
new file mode 100644
@@ -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 "" } */
new file mode 100644
@@ -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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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'
new file mode 100644
@@ -0,0 +1,81 @@
+/* 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);
+
+ 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");
+
+ 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" } } */
new file mode 100644
@@ -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
new file mode 100644
@@ -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 */
new file mode 100644
@@ -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 validat
+ 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 */
new file mode 100644
@@ -0,0 +1,69 @@
+/* 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);
+
+ 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 *");
+
+ 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" } } */
new file mode 100644
@@ -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" } } */
new file mode 100644
@@ -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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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 *'
new file mode 100644
@@ -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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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'
new file mode 100644
@@ -0,0 +1,79 @@
+/* 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);
+
+ 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");
+
+ 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" } } */
new file mode 100644
@@ -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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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'"
new file mode 100644
@@ -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" } } */
new file mode 100644
@@ -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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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
new file mode 100644
@@ -0,0 +1,76 @@
+/* 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");
+
+ 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");
+
+ 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" } } */
new file mode 100644
@@ -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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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'] \
+ == 'PRINT "hello world!";\n'
new file mode 100644
@@ -0,0 +1,52 @@
+/* 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 | 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-no-column.c.exe",
+ "test-no-column.c.sarif",
+ __FILE__, "c");
+
+ 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");
+
+ return end_test ();
+}
+
+/* Verify the output from the text sink.
+ { dg-regexp "\[^\n\r\]+test-no-column.c:16: error: can't find 'foo'" }
+ { dg-begin-multiline-output "" }
+ 16 | 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-no-column.c "test-no-column-c.py" } } */
new file mode 100644
@@ -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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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
new file mode 100644
@@ -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" } } */
new file mode 100644
@@ -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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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
new file mode 100644
@@ -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" } } */
new file mode 100644
@@ -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 "" }
+[m[K [01;31m[Kerror: [m[Kcan't find '[01m[Kfoo[m[K'
+ 10 | PRINT "hello world!";
+ | [01;31m[K^~~~~~~~~~~~[m[K
+ { dg-end-multiline-output "" } */
new file mode 100644
@@ -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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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'
new file mode 100644
@@ -0,0 +1,108 @@
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+final_line_num = 33
+
+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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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
new file mode 100644
@@ -0,0 +1,125 @@
+/* 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
+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;
+*/
+const int final_line_num = __LINE__ - 2;
+
+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;
+
+int
+main ()
+{
+ begin_test ("test-warning-with-path.c.exe",
+ "test-warning-with-path.c.sarif",
+ __FILE__, "c");
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ diagnostic_finish (d,
+ "passing NULL as argument %i to %qs"
+ " which requires a non-NULL parameter",
+ 1, "PyList_Append");
+
+ 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:30:5: warning: passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" }
+ { dg-begin-multiline-output "" }
+ 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
+ { 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" } } */
new file mode 100644
@@ -0,0 +1,65 @@
+/* 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);
+
+ diagnostic *d = diagnostic_begin (diag_mgr,
+ DIAGNOSTIC_LEVEL_WARNING);
+ diagnostic_set_location (d, loc_range);
+
+ diagnostic_finish (d, "this is a warning");
+
+ 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" } } */
new file mode 100644
@@ -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://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/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'
new file mode 100644
@@ -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" } } */
new file mode 100644
@@ -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;
+};
Changed in v3: * split out the C and C++ API tests into this patch * heavily rewritten libdiagnostics.exp; added support for Python tests * tests updated for API changes, rewritten and extended 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-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> --- .../libdiagnostics.dg/libdiagnostics.exp | 296 ++++++++++++++++++ gcc/testsuite/libdiagnostics.dg/sarif.py | 23 ++ gcc/testsuite/libdiagnostics.dg/test-dump.c | 69 ++++ .../libdiagnostics.dg/test-error-c.py | 54 ++++ .../test-error-with-note-c.py | 50 +++ .../libdiagnostics.dg/test-error-with-note.c | 74 +++++ .../libdiagnostics.dg/test-error-with-note.cc | 55 ++++ gcc/testsuite/libdiagnostics.dg/test-error.c | 59 ++++ gcc/testsuite/libdiagnostics.dg/test-error.cc | 47 +++ .../libdiagnostics.dg/test-fix-it-hint-c.py | 46 +++ .../libdiagnostics.dg/test-fix-it-hint.c | 81 +++++ .../libdiagnostics.dg/test-fix-it-hint.cc | 74 +++++ .../libdiagnostics.dg/test-helpers++.h | 28 ++ .../libdiagnostics.dg/test-helpers.h | 72 +++++ .../libdiagnostics.dg/test-labelled-ranges.c | 69 ++++ .../libdiagnostics.dg/test-labelled-ranges.cc | 64 ++++ .../libdiagnostics.dg/test-labelled-ranges.py | 48 +++ .../test-logical-location-c.py | 37 +++ .../libdiagnostics.dg/test-logical-location.c | 79 +++++ .../libdiagnostics.dg/test-metadata-c.py | 45 +++ .../libdiagnostics.dg/test-metadata.c | 61 ++++ .../test-multiple-lines-c.py | 83 +++++ .../libdiagnostics.dg/test-multiple-lines.c | 76 +++++ .../libdiagnostics.dg/test-no-column-c.py | 35 +++ .../libdiagnostics.dg/test-no-column.c | 52 +++ .../test-no-diagnostics-c.py | 42 +++ .../libdiagnostics.dg/test-no-diagnostics.c | 25 ++ .../test-note-with-fix-it-hint-c.py | 54 ++++ .../test-note-with-fix-it-hint.c | 69 ++++ .../test-text-sink-options.c | 59 ++++ .../libdiagnostics.dg/test-warning-c.py | 54 ++++ .../test-warning-with-path-c.py | 108 +++++++ .../test-warning-with-path.c | 125 ++++++++ .../libdiagnostics.dg/test-warning.c | 65 ++++ .../test-write-sarif-to-file-c.py | 55 ++++ .../test-write-sarif-to-file.c | 55 ++++ .../test-write-text-to-file.c | 47 +++ 37 files changed, 2435 insertions(+) create mode 100644 gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp create mode 100644 gcc/testsuite/libdiagnostics.dg/sarif.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-dump.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.cc create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers++.h create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers.h create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-logical-location-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-logical-location.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-metadata-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-metadata.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-multiple-lines-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-column-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-column.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-diagnostics-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-diagnostics.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning-with-path-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning-with-path.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c