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 “logical” location, which refers to semantic constructs in a programming language).

Creating location information

The diagnostic_manager manages various objects relating to locations.

type diagnostic_file

A diagnostic_file is an opaque type describing a particular input file.

diagnostic_file *diagnostic_manager_new_file(diagnostic_manager *diag_mgr, const char *name, const char *sarif_source_language)

Create a new 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 SARIF (SARIF v2.1.0 §3.24.10). See SARIF v2.1.0 Appendix J for suggested values for various programmming languages.

For example, this creates a 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 */);
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 diagnostic_line_num_t is used for representing line numbers within text files. libgdiagnostics treats the first line of a text file as line 1.

type diagnostic_column_num_t

A diagnostic_column_num_t is used for representing column numbers within text files. libgdiagnostics treats the first column of a text line as column 1, not column 0.

Note

Both libgdiagnostics and Emacs number source lines starting at 1, but they have differing conventions for columns.

libgdiagnostics 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 libgdiagnostics 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 libgdiagnostics corresponds to (3, 0) in Emacs.

type diagnostic_physical_location

A diagnostic_physical_location is an opaque type representing a key into a database of source locations within a diagnostic_manager.

diagnostic_physical_location instances 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.

A NULL value means “unknown”, and can be returned by the diagnostic_manager as a fallback when a problem occurs (e.g. too many locations).

A 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.

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 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.

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 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.

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.

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 diagnostic has an optional primary physical location and zero or more secondary physical locations. For example:

a = (foo && bar)
    ~~~~~^~~~~~~

This diagnostic has a single 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”.

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.

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 Message formatting for details of how to use fmt.

TODO: example of use

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.

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,

  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 *");

might give this text output:

test-labelled-ranges.c:9:6: error: mismatching types: 'int' and 'const char *'
   19 |   42 + "foo"
      |   ~~ ^ ~~~~~
      |   |    |
      |   int  const char *