diagnostics: make experimental-html sink prettier [PR116792]

This patch to the "experimental-html" diagnostic sink:
* adds use of the PatternFly 3 CSS library (via an optional link
  in the generated html to a copy in a CDN)
* uses PatternFly's "alert" pattern to show severities for diagnostics,
  properly nesting "note" diagnostics for diagnostic groups.
  Example:
    before: https://dmalcolm.fedorapeople.org/gcc/2025-06-10/before/diagnostic-ranges.c.html
     after: https://dmalcolm.fedorapeople.org/gcc/2025-06-10/after/diagnostic-ranges.c.html

* adds initial support for logical locations and physical locations
* adds initial support for multi-level nested diagnostics such as those
  for C++ concepts diagnostics.  Ideally this would show a clickable
  disclosure widget to expand/collapse a level, but for now it uses
  nested <ul> elements with <li> for the child diagnostics.
  Example:
    before: https://dmalcolm.fedorapeople.org/gcc/2025-06-10/before/nested-diagnostics-1.C.html
     after: https://dmalcolm.fedorapeople.org/gcc/2025-06-10/after/nested-diagnostics-1.C.html

gcc/ChangeLog:
	PR other/116792
	* diagnostic-format-html.cc: Include "diagnostic-path.h" and
	"diagnostic-client-data-hooks.h".
	(html_builder::m_logical_loc_mgr): New field.
	(html_builder::m_cur_nesting_levels): New field.
	(html_builder::m_last_logical_location): New field.
	(html_builder::m_last_location): New field.
	(html_builder::m_last_expanded_location): New field.
	(HTML_STYLE): Add "white-space: pre;" to .source and .annotation.
	Add "gcc-quoted-text" CSS class.
	(html_builder::html_builder): Initialize the new fields.  If CSS
	is enabled, add CDN links to PatternFly 3 stylesheets.
	(html_builder::add_stylesheet): New.
	(html_builder::on_report_diagnostic): Add "alert" param to
	make_element_for_diagnostic, setting it by default, but unsetting
	it for nested diagnostics below the top level.  Use
	add_at_nesting_level for nested diagnostics.
	(add_nesting_level_attr): New.
	(html_builder::add_at_nesting_level): New.
	(get_pf_class_for_alert_div): New.
	(get_pf_class_for_alert_icon): New.
	(get_label_for_logical_location_kind): New.
	(add_labelled_value): New.
	(html_builder::make_element_for_diagnostic): Add leading comment.
	Add "alert" param.  Drop class="gcc-diagnostic" from <div> tag,
	instead adding the class for a PatternFly 3 alert if "alert" is
	true, and adding a <span> with an alert icon, both according to
	the diagnostic severity.  Add a severity prefix to the message for
	alerts.  Add any metadata/option text as suffixes to the message.
	Show any logical location.  Show any physical location.  Don't
	show the locus if the last location is unchanged within the
	diagnostic_group.  Wrap any execution path element in a
	<div id="execution-path"> and add a label to it.  Wrap any
	generated patch in a <div id="suggested-fix"> and add a label
	to it.
	(selftest::test_simple_log): Update expected HTML.

gcc/testsuite/ChangeLog:
	PR other/116792
	* gcc.dg/html-output/missing-semicolon.py: Update for changes
	to diagnostic elements.
	* gcc.dg/format/diagnostic-ranges-html.py: Likewise.
	* gcc.dg/plugin/diagnostic-test-metadata-html.py: Likewise.  Drop
	out-of-date comment.
	* gcc.dg/plugin/diagnostic-test-paths-2.py: Likewise.
	* gcc.dg/plugin/diagnostic-test-paths-4.py: Likewise.  Drop
	out-of-date comment.
	* gcc.dg/plugin/diagnostic-test-show-locus.py: Likewise.
	* lib/htmltest.py (get_diag_by_index): Update to use search by id.
	(get_message_within_diag): Update to use search by class.

libcpp/ChangeLog:
	PR other/116792
	* include/line-map.h (typedef expanded_location): Convert to...
	(struct expanded_location): ...this.
	(operator==): New decl, for expanded_location.
	(operator!=): Likewise.
	* line-map.cc (operator==): New decl, for expanded_location.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
This commit is contained in:
David Malcolm 2025-06-10 20:06:38 -04:00
parent 896edb1d0a
commit cb1d203445
10 changed files with 549 additions and 227 deletions

View File

@ -31,6 +31,8 @@ along with GCC; see the file COPYING3. If not see
#include "diagnostic-format-text.h"
#include "diagnostic-output-file.h"
#include "diagnostic-buffer.h"
#include "diagnostic-path.h"
#include "diagnostic-client-data-hooks.h"
#include "selftest.h"
#include "selftest-diagnostic.h"
#include "pretty-print-format-impl.h"
@ -139,26 +141,45 @@ public:
}
private:
void
add_stylesheet (std::string url);
std::unique_ptr<xml::element>
make_element_for_diagnostic (const diagnostic_info &diagnostic,
diagnostic_t orig_diag_kind);
diagnostic_t orig_diag_kind,
bool alert);
std::unique_ptr<xml::element>
make_metadata_element (label_text label,
label_text url);
void
add_at_nesting_level (size_t nesting_level,
std::unique_ptr<xml::element> child_diag_element);
void
push_nesting_level ();
void
pop_nesting_level ();
diagnostic_context &m_context;
pretty_printer *m_printer;
const line_maps *m_line_maps;
html_generation_options m_html_gen_opts;
const logical_location_manager *m_logical_loc_mgr;
std::unique_ptr<xml::document> m_document;
xml::element *m_head_element;
xml::element *m_title_element;
xml::element *m_diagnostics_element;
std::unique_ptr<xml::element> m_cur_diagnostic_element;
std::vector<xml::element *> m_cur_nesting_levels;
int m_next_diag_id; // for handing out unique IDs
json::array m_ui_focus_ids;
logical_location m_last_logical_location;
location_t m_last_location;
expanded_location m_last_expanded_location;
};
static std::unique_ptr<xml::element>
@ -237,8 +258,10 @@ static const char * const HTML_STYLE
" .ruler { color: red;\n"
" white-space: pre; }\n"
" .source { color: blue;\n"
" background-color: white;\n"
" white-space: pre; }\n"
" .annotation { color: green;\n"
" background-color: white;\n"
" white-space: pre; }\n"
" .linenum-gap { text-align: center;\n"
" border-top: 1px solid black;\n"
@ -269,6 +292,8 @@ static const char * const HTML_STYLE
" font-weight: bold; }\n"
" .highlight-b { color: #3f9c35;\n" // pf-green-400
" font-weight: bold; }\n"
" .gcc-quoted-text { font-weight: bold;\n"
" font-family: mono; }\n"
" </style>\n");
/* A little JavaScript for ease of navigation.
@ -351,13 +376,19 @@ html_builder::html_builder (diagnostic_context &context,
m_printer (&pp),
m_line_maps (line_maps),
m_html_gen_opts (html_gen_opts),
m_logical_loc_mgr (nullptr),
m_head_element (nullptr),
m_title_element (nullptr),
m_diagnostics_element (nullptr),
m_next_diag_id (0)
m_next_diag_id (0),
m_last_location (UNKNOWN_LOCATION),
m_last_expanded_location ({})
{
gcc_assert (m_line_maps);
if (auto client_data_hooks = context.get_client_data_hooks ())
m_logical_loc_mgr = client_data_hooks->get_logical_location_manager ();
m_document = std::make_unique<xml::document> ();
m_document->m_doctypedecl = std::make_unique<html_doctypedecl> ();
{
@ -374,8 +405,13 @@ html_builder::html_builder (diagnostic_context &context,
xml::auto_print_element title (xp, "title", true);
m_title_element = xp.get_insertion_point ();
}
if (m_html_gen_opts.m_css)
xp.add_raw (HTML_STYLE);
{
add_stylesheet ("https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly.min.css");
add_stylesheet ("https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly-additions.min.css");
xp.add_raw (HTML_STYLE);
}
if (m_html_gen_opts.m_javascript)
{
xp.push_tag ("script");
@ -408,6 +444,18 @@ html_builder::set_main_input_filename (const char *name)
}
}
void
html_builder::add_stylesheet (std::string url)
{
gcc_assert (m_head_element);
xml::printer xp (*m_head_element);
xp.push_tag ("link", false);
xp.set_attr ("rel", "stylesheet");
xp.set_attr ("type", "text/css");
xp.set_attr ("href", std::move (url));
}
/* Implementation of "on_report_diagnostic" for HTML output. */
void
@ -425,8 +473,14 @@ html_builder::on_report_diagnostic (const diagnostic_info &diagnostic,
fnotice (stderr, "Internal compiler error:\n");
}
const int nesting_level = m_context.get_diagnostic_nesting_level ();
bool alert = true;
if (m_cur_diagnostic_element && nesting_level > 0)
alert = false;
if (!m_cur_diagnostic_element)
m_last_logical_location = logical_location ();
auto diag_element
= make_element_for_diagnostic (diagnostic, orig_diag_kind);
= make_element_for_diagnostic (diagnostic, orig_diag_kind, alert);
if (buffer)
{
gcc_assert (!m_cur_diagnostic_element);
@ -435,14 +489,79 @@ html_builder::on_report_diagnostic (const diagnostic_info &diagnostic,
else
{
if (m_cur_diagnostic_element)
/* Nested diagnostic. */
m_cur_diagnostic_element->add_child (std::move (diag_element));
{
/* Nested diagnostic. */
gcc_assert (nesting_level >= 0);
add_at_nesting_level (nesting_level, std::move (diag_element));
}
else
/* Top-level diagnostic. */
m_cur_diagnostic_element = std::move (diag_element);
{
m_cur_diagnostic_element = std::move (diag_element);
m_cur_nesting_levels.clear ();
}
}
}
// For ease of comparison with experimental-nesting-show-levels=yes
static void
add_nesting_level_attr (xml::element &element,
int nesting_level)
{
element.set_attr ("nesting-level", std::to_string (nesting_level));
}
void
html_builder::
add_at_nesting_level (size_t nesting_level,
std::unique_ptr<xml::element> child_diag_element)
{
gcc_assert (m_cur_diagnostic_element);
while (nesting_level > m_cur_nesting_levels.size ())
push_nesting_level ();
while (nesting_level < m_cur_nesting_levels.size ())
pop_nesting_level ();
if (nesting_level > 0)
{
gcc_assert (!m_cur_nesting_levels.empty ());
auto current_nesting_level = m_cur_nesting_levels.back ();
xml::printer xp (*current_nesting_level);
xp.push_tag ("li");
add_nesting_level_attr (*xp.get_insertion_point (),
m_cur_nesting_levels.size ());
xp.append (std::move (child_diag_element));
xp.pop_tag ("li");
}
else
m_cur_diagnostic_element->add_child (std::move (child_diag_element));
}
void
html_builder::push_nesting_level ()
{
gcc_assert (m_cur_diagnostic_element);
auto new_nesting_level = std::make_unique<xml::element> ("ul", false);
add_nesting_level_attr (*new_nesting_level,
m_cur_nesting_levels.size () + 1);
xml::element *current_nesting_level = nullptr;
if (!m_cur_nesting_levels.empty ())
current_nesting_level = m_cur_nesting_levels.back ();
m_cur_nesting_levels.push_back (new_nesting_level.get ());
if (current_nesting_level)
current_nesting_level->add_child (std::move (new_nesting_level));
else
m_cur_diagnostic_element->add_child (std::move (new_nesting_level));
}
void
html_builder::pop_nesting_level ()
{
gcc_assert (m_cur_diagnostic_element);
m_cur_nesting_levels.pop_back ();
}
/* Custom subclass of html_label_writer.
Wrap labels within a <span> element, supplying them with event IDs.
Add the IDs to the list of focus IDs. */
@ -482,9 +601,148 @@ private:
int m_next_event_idx;
};
/* See https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts */
static const char *
get_pf_class_for_alert_div (diagnostic_t diag_kind)
{
switch (diag_kind)
{
case DK_DEBUG:
case DK_NOTE:
return "alert alert-info";
case DK_ANACHRONISM:
case DK_WARNING:
return "alert alert-warning";
case DK_ERROR:
case DK_SORRY:
case DK_ICE:
case DK_ICE_NOBT:
case DK_FATAL:
return "alert alert-danger";
default:
gcc_unreachable ();
}
}
static const char *
get_pf_class_for_alert_icon (diagnostic_t diag_kind)
{
switch (diag_kind)
{
case DK_DEBUG:
case DK_NOTE:
return "pficon pficon-info";
case DK_ANACHRONISM:
case DK_WARNING:
return "pficon pficon-warning-triangle-o";
case DK_ERROR:
case DK_SORRY:
case DK_ICE:
case DK_ICE_NOBT:
case DK_FATAL:
return "pficon pficon-error-circle-o";
default:
gcc_unreachable ();
}
}
static const char *
get_label_for_logical_location_kind (enum logical_location_kind kind)
{
switch (kind)
{
default:
gcc_unreachable ();
case logical_location_kind::unknown:
return nullptr;
/* Kinds within executable code. */
case logical_location_kind::function:
return "Function";
case logical_location_kind::member:
return "Member";
case logical_location_kind::module_:
return "Module";
case logical_location_kind::namespace_:
return "Namespace";
case logical_location_kind::type:
return "Type";
case logical_location_kind::return_type:
return "Return type";
case logical_location_kind::parameter:
return "Parameter";
case logical_location_kind::variable:
return "Variable";
/* Kinds within XML or HTML documents. */
case logical_location_kind::element:
return "Element";
case logical_location_kind::attribute:
return "Attribute";
case logical_location_kind::text:
return "Text";
case logical_location_kind::comment:
return "Comment";
case logical_location_kind::processing_instruction:
return "Processing Instruction";
case logical_location_kind::dtd:
return "DTD";
case logical_location_kind::declaration:
return "Declaration";
/* Kinds within JSON documents. */
case logical_location_kind::object:
return "Object";
case logical_location_kind::array:
return "Array";
case logical_location_kind::property:
return "Property";
case logical_location_kind::value:
return "Value";
}
}
static void
add_labelled_value (xml::printer &xp,
std::string id,
std::string label,
std::string value,
bool quote_value)
{
xp.push_tag ("div", true);
xp.set_attr ("id", id);
xp.push_tag ("span");
xp.add_text (label);
xp.add_text (" ");
xp.pop_tag ("span");
xp.push_tag ("span");
if (quote_value)
xp.set_attr ("class", "gcc-quoted-text");
xp.add_text (std::move (value));
xp.pop_tag ("span");
xp.pop_tag ("div");
}
/* Make a <div class="gcc-diagnostic"> for DIAGNOSTIC.
If ALERT is true, make it be a PatternFly alert (see
https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts) and
show severity text (e.g. "error: ").
If ALERT is false, don't show the severity text and don't show
the filename if it's the same as the previous diagnostic within the
diagnostic group. */
std::unique_ptr<xml::element>
html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
diagnostic_t orig_diag_kind)
diagnostic_t orig_diag_kind,
bool alert)
{
class html_token_printer : public token_printer
{
@ -562,8 +820,6 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
xml::printer m_xp;
};
auto diag_element = make_div ("gcc-diagnostic");
const int diag_idx = m_next_diag_id++;
std::string diag_id;
{
@ -571,30 +827,75 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
pp_printf (&pp, "gcc-diag-%i", diag_idx);
diag_id = pp_formatted_text (&pp);
}
diag_element->set_attr ("id", diag_id);
// TODO: might be nice to emulate the text output format, but colorize it
auto message_span = make_span ("gcc-message");
std::string message_span_id (diag_id + "-message");
message_span->set_attr ("id", message_span_id);
add_focus_id (message_span_id);
/* See https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts
which has this example:
<div class="alert alert-danger">
<span class="pficon pficon-error-circle-o"></span>
<strong>Hey there is a problem!</strong> Yeah this is really messed up and you should <a href="#" class="alert-link">know about it</a>.
</div>
*/
auto diag_element = make_div ("gcc-diagnostic");
diag_element->set_attr ("id", diag_id);
if (alert)
diag_element->set_attr ("class",
get_pf_class_for_alert_div (diagnostic.kind));
xml::printer xp (*message_span.get ());
xml::printer xp (*diag_element.get ());
const size_t depth_within_alert_div = 1;
gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
if (alert)
{
xp.push_tag_with_class ("span",
get_pf_class_for_alert_icon (diagnostic.kind),
true);
xp.add_text (" ");
xp.pop_tag ("span");
}
// The rest goes in the <div>...
gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
xp.push_tag_with_class ("div", "gcc-message", true);
std::string message_alert_id (diag_id + "-message");
xp.set_attr ("id", message_alert_id);
add_focus_id (message_alert_id);
const size_t depth_within_message_div = depth_within_alert_div + 1;
gcc_assert (xp.get_num_open_tags () == depth_within_message_div);
// Severity e.g. "warning: "
bool show_severity = true;
if (!alert)
show_severity = false;
if (show_severity)
{
xp.push_tag ("strong");
xp.add_text (_(get_diagnostic_kind_text (diagnostic.kind)));
xp.pop_tag ("strong");
xp.add_text (" ");
}
// Add the message itself:
html_token_printer tok_printer (*xp.get_insertion_point ());
m_printer->set_token_printer (&tok_printer);
pp_output_formatted_text (m_printer, m_context.get_urlifier ());
m_printer->set_token_printer (nullptr);
pp_clear_output_area (m_printer);
diag_element->add_child (std::move (message_span));
// Add any metadata as a suffix to the message
if (diagnostic.metadata)
{
diag_element->add_text (" ");
diag_element->add_child
(make_element_for_metadata (*diagnostic.metadata));
xp.add_text (" ");
xp.append (make_element_for_metadata (*diagnostic.metadata));
}
// Add any option as a suffix to the message
label_text option_text = label_text::take
(m_context.make_option_name (diagnostic.option_id,
orig_diag_kind, diagnostic.kind));
@ -603,7 +904,7 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
label_text option_url = label_text::take
(m_context.make_option_url (diagnostic.option_id));
diag_element->add_text (" ");
xp.add_text (" ");
auto option_span = make_span ("gcc-option");
option_span->add_text ("[");
{
@ -618,35 +919,110 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
option_span->add_text (option_text.get ());
option_span->add_text ("]");
}
diag_element->add_child (std::move (option_span));
xp.append (std::move (option_span));
}
gcc_assert (xp.get_num_open_tags () == depth_within_message_div);
xp.pop_tag ("div");
gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
/* Show any logical location. */
if (m_logical_loc_mgr)
if (auto client_data_hooks = m_context.get_client_data_hooks ())
if (auto logical_loc = client_data_hooks->get_current_logical_location ())
if (logical_loc != m_last_logical_location)
{
enum logical_location_kind kind
= m_logical_loc_mgr->get_kind (logical_loc);;
if (const char *label = get_label_for_logical_location_kind (kind))
if (const char *name_with_scope
= m_logical_loc_mgr->get_name_with_scope (logical_loc))
add_labelled_value (xp, "logical-location",
label, name_with_scope, true);
m_last_logical_location = logical_loc;
}
/* Show any physical location. */
const expanded_location s
= diagnostic_expand_location (&diagnostic);
if (s != m_last_expanded_location
|| alert)
{
if (s.file
&& (s.file != m_last_expanded_location.file
|| alert))
add_labelled_value (xp, "file", "File", s.file, false);
if (s.line)
{
add_labelled_value (xp, "line", "Line", std::to_string (s.line), false);
diagnostic_column_policy column_policy (m_context);
int converted_column = column_policy.converted_column (s);
if (converted_column >= 0)
add_labelled_value (xp, "column", "Column",
std::to_string (converted_column),
false);
}
if (s.file)
m_last_expanded_location = s;
}
/* Source (and fix-it hints). */
{
xml::printer xp (*diag_element);
m_context.m_last_location = UNKNOWN_LOCATION;
// TODO: m_context.m_last_location should be moved into the sink
location_t saved = m_context.m_last_location;
m_context.m_last_location = m_last_location;
m_context.maybe_show_locus_as_html (*diagnostic.richloc,
m_context.m_source_printing,
diagnostic.kind,
xp,
nullptr,
nullptr);
m_context.m_last_location = saved;
m_last_location = m_context.m_last_location;
}
gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
/* Execution path. */
if (auto path = diagnostic.richloc->get_path ())
{
xml::printer xp (*diag_element);
xp.push_tag ("div");
xp.set_attr ("id", "execution-path");
xp.push_tag ("label", true);
const int num_events = path->num_events ();
pretty_printer pp;
pp_printf_n (&pp, num_events,
"Execution path with %i event",
"Execution path with %i events",
num_events);
xp.add_text_from_pp (pp);
xp.pop_tag ("label");
std::string event_id_prefix (diag_id + "-event-");
html_path_label_writer event_label_writer (xp, *this,
event_id_prefix);
diagnostic_source_print_policy dspp (m_context);
print_path_as_html (xp, *path, m_context, &event_label_writer,
dspp);
xp.pop_tag ("div");
}
gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
if (auto patch_element = make_element_for_patch (diagnostic))
diag_element->add_child (std::move (patch_element));
{
xp.push_tag ("div");
xp.set_attr ("id", "suggested-fix");
xp.push_tag ("label", true);
xp.add_text ("Suggested fix");
xp.pop_tag ("label");
xp.append (std::move (patch_element));
xp.pop_tag ("div");
}
return diag_element;
}
@ -1032,8 +1408,9 @@ test_simple_log ()
" </head>\n"
" <body>\n"
" <div class=\"gcc-diagnostic-list\">\n"
" <div class=\"gcc-diagnostic\" id=\"gcc-diag-0\">\n"
" <span class=\"gcc-message\" id=\"gcc-diag-0-message\">this is a test: `<span class=\"gcc-quoted-text\">foo</span>&apos;</span>\n"
" <div class=\"alert alert-danger\" id=\"gcc-diag-0\">\n"
" <span class=\"pficon pficon-error-circle-o\"> </span>\n"
" <div class=\"gcc-message\" id=\"gcc-diag-0-message\"><strong>error: </strong> this is a test: `<span class=\"gcc-quoted-text\">foo</span>&apos;</div>\n"
" </div>\n"
" </div>\n"
" </body>\n"

View File

@ -19,17 +19,20 @@ def test_message(html_tree):
diag = get_diag_by_index(html_tree, 0)
msg = get_message_within_diag(diag)
assert_tag(msg[0], 'span')
assert_class(msg[0], 'gcc-quoted-text')
assert_highlighted_text(msg[0][0], 'highlight-a', '%i')
assert_tag(msg[0], 'strong')
assert msg[0].text == 'warning: '
assert_tag(msg[1], 'span')
assert_class(msg[1], 'gcc-quoted-text')
assert_highlighted_text(msg[1][0], 'highlight-a', 'int')
assert_highlighted_text(msg[1][0], 'highlight-a', '%i')
assert_tag(msg[2], 'span')
assert_class(msg[2], 'gcc-quoted-text')
assert_highlighted_text(msg[2][0], 'highlight-b', 'const char *')
assert_highlighted_text(msg[2][0], 'highlight-a', 'int')
assert_tag(msg[3], 'span')
assert_class(msg[3], 'gcc-quoted-text')
assert_highlighted_text(msg[3][0], 'highlight-b', 'const char *')
def test_annotations(html_tree):
"""

View File

@ -37,22 +37,68 @@ def test_basics(html_tree):
diag = diag_list.find('xhtml:div', ns)
assert diag is not None
assert diag.attrib['class'] == 'gcc-diagnostic'
assert diag.attrib['class'] == 'alert alert-danger'
assert diag.attrib['id'] == 'gcc-diag-0'
message = diag.find('xhtml:span', ns)
icon = diag.find('xhtml:span', ns)
assert icon.attrib['class'] == 'pficon pficon-error-circle-o'
# The message line:
message = diag.find("./xhtml:div[@class='gcc-message']", ns)
assert message is not None
assert message.attrib['class'] == 'gcc-message'
assert message.text == "expected '"
assert message[0].tag == make_tag('span')
assert message[0].attrib['class'] == 'gcc-quoted-text'
assert message[0].text == ';'
assert message[0].tail == "' before '"
# <html:div xmlns:html="http://www.w3.org/1999/xhtml" class="gcc-message" id="gcc-diag-0-message"><html:strong>error: </html:strong> expected '<html:span class="gcc-quoted-text">;</html:span>' before '<html:span class="gcc-quoted-text">}</html:span>' token</html:div>
assert message[0].tag == make_tag('strong')
assert message[0].text == 'error: '
assert message[0].tail == " expected '"
assert message[1].tag == make_tag('span')
assert message[1].attrib['class'] == 'gcc-quoted-text'
assert message[1].text == '}'
assert message[1].tail == "' token"
assert message[1].text == ';'
assert message[1].tail == "' before '"
assert message[2].tag == make_tag('span')
assert message[2].attrib['class'] == 'gcc-quoted-text'
assert message[2].text == '}'
assert message[2].tail == "' token"
pre = diag.find('xhtml:pre', ns)
# Logical location
logical_loc = diag.find("./xhtml:div[@id='logical-location']", ns)
assert logical_loc is not None
assert len(logical_loc) == 2
assert logical_loc[0].tag == make_tag('span')
assert logical_loc[0].text == 'Function '
assert logical_loc[1].tag == make_tag('span')
assert logical_loc[1].text == 'missing_semicolon'
assert logical_loc[1].attrib['class'] == 'gcc-quoted-text'
# Physical location
file_ = diag.find("./xhtml:div[@id='file']", ns)
assert file_ is not None
assert len(file_) == 2
assert file_[0].tag == make_tag('span')
assert file_[0].text == 'File '
assert file_[1].tag == make_tag('span')
assert file_[1].text.endswith('gcc/testsuite/gcc.dg/html-output/missing-semicolon.c')
line = diag.find("./xhtml:div[@id='line']", ns)
assert line is not None
assert len(line) == 2
assert line[0].tag == make_tag('span')
assert line[0].text == 'Line '
assert line[1].tag == make_tag('span')
assert line[1].text == '8'
column = diag.find("./xhtml:div[@id='column']", ns)
assert column is not None
assert len(column) == 2
assert column[0].tag == make_tag('span')
assert column[0].text == 'Column '
assert column[1].tag == make_tag('span')
assert column[1].text == '12'
# Suggested fix
fix = diag.find("./xhtml:div[@id='suggested-fix']", ns)
label = fix.find('xhtml:label', ns)
assert label.text == "Suggested fix"
pre = fix.find('xhtml:pre', ns)
assert pre is not None
assert pre.attrib['class'] == 'gcc-generated-patch'
assert pre.text.startswith('--- ')

View File

@ -21,10 +21,24 @@ def test_metadata(html_tree):
diag = diag_list.find('xhtml:div', ns)
assert diag is not None
assert diag.attrib['class'] == 'gcc-diagnostic'
assert diag.attrib['class'] == 'alert alert-warning'
spans = diag.findall('xhtml:span', ns)
metadata = spans[1]
icon = diag.find('xhtml:span', ns)
assert icon.attrib['class'] == 'pficon pficon-warning-triangle-o'
message = diag.find("./xhtml:div[@class='gcc-message']", ns)
assert message.attrib['id'] == 'gcc-diag-0-message'
assert message[0].tag == make_tag('strong')
assert message[0].text == 'warning: '
assert message[0].tail == " never use '"
assert message[1].tag == make_tag('span')
assert message[1].attrib['class'] == 'gcc-quoted-text'
assert message[1].text == 'gets'
assert message[1].tail == "' "
metadata = message[2]
assert metadata.attrib['class'] == 'gcc-metadata'
assert metadata[0].tag == make_tag('span')
assert metadata[0].attrib['class'] == 'gcc-metadata-item'
@ -57,21 +71,3 @@ def test_metadata(html_tree):
annotation_tr = rows[1]
assert_annotation_line(annotation_tr,
' ^~~~~~~~~~')
# For reference, here's the generated HTML:
"""
<body>
<div class="gcc-diagnostic-list">
<div class="gcc-diagnostic">
<span class="gcc-message">never use &apos;<span class="gcc-quoted-text">gets</span>&apos;</span>
<span class="gcc-metadata"><span class="gcc-metadata-item">[<a href="https://cwe.mitre.org/data/definitions/242.html">CWE-242</a>]</span><span class="gcc-metadata-item">[<a href="https://example.com/">STR34-C</a>]</span></span><table class="locus">
<tbody class="line-span">
<tr><td class="linenum"> 10</td> <td class="source"> gets (buf);</td></tr>
<tr><td class="linenum"/><td class="annotation"> ^~~~~~~~~~</td></tr>
</tbody>
</table>
</div>
</div>
</body>
"""

View File

@ -21,9 +21,16 @@ def test_paths(html_tree):
diag = diag_list.find('xhtml:div', ns)
assert diag is not None
assert diag.attrib['class'] == 'gcc-diagnostic'
assert diag.attrib['class'] == 'alert alert-danger'
assert diag.attrib['id'] == 'gcc-diag-0'
event_ranges = diag.find('xhtml:div', ns)
exec_path = diag.find("./xhtml:div[@id='execution-path']", ns)
assert exec_path is not None
label = exec_path.find('xhtml:label', ns)
assert label.text == 'Execution path with 3 events'
event_ranges = exec_path.find('xhtml:div', ns)
assert_class(event_ranges, 'event-ranges')
frame_margin = event_ranges.find('xhtml:table', ns)

View File

@ -25,7 +25,13 @@ def test_paths(html_tree):
assert_annotation_line(annotation_tr,
' ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
event_ranges = diag.find('xhtml:div', ns)
exec_path = diag.find("./xhtml:div[@id='execution-path']", ns)
assert exec_path is not None
label = exec_path.find('xhtml:label', ns)
assert label.text == 'Execution path with 9 events'
event_ranges = exec_path.find('xhtml:div', ns)
assert_class(event_ranges, 'event-ranges')
test_frame_margin = event_ranges.find('xhtml:table', ns)
@ -41,150 +47,3 @@ def test_paths(html_tree):
test_frame = tds[1]
assert_frame(test_frame, 'test')
assert_event_range_with_margin(test_frame[1])
# For reference, generated HTML looks like this:
"""
<table class="stack-frame-with-margin"><tr>
<td class="interprocmargin" style="padding-left: 100px"/>
<td class="stack-frame">
<div class="frame-funcname"><span>test</span></div><table class="event-range-with-margin"><tr>
<td class="event-range">
<div class="events-hdr"><span class="funcname">test</span>: <span class="event-ids">events 1-2</span></div>
<table class="locus">
<tbody class="line-span">
<tr><td class="linenum"> 27</td> <td class="source">{</td></tr>
<tr><td class="linenum"/><td class="annotation">^</td></tr>
<tr><td class="linenum"/><td class="annotation">|</td></tr>
<tr><td class="linenum"/><td class="annotation">(1) entering 'test'</td></tr>
<tr><td class="linenum"> 28</td> <td class="source"> register_handler ();</td></tr>
<tr><td class="linenum"/><td class="annotation"> ~~~~~~~~~~~~~~~~~~~</td></tr>
<tr><td class="linenum"/><td class="annotation"> |</td></tr>
<tr><td class="linenum"/><td class="annotation"> (2) calling 'register_handler'</td></tr>
</tbody>
</table>
</td></tr></table>
<div class="between-ranges-call">
<svg height="30" width="150">
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7"
refX="0" refY="3.5" orient="auto" stroke="#0088ce" fill="#0088ce">
<polygon points="0 0, 10 3.5, 0 7"/>
</marker>
</defs>
<polyline points="20,0 20,10 120,10 120,20"
style="fill:none;stroke: #0088ce"
marker-end="url(#arrowhead)"/>
</svg>
</div>
<table class="stack-frame-with-margin"><tr>
<td class="interprocmargin" style="padding-left: 100px"/>
<td class="stack-frame">
<div class="frame-funcname"><span>register_handler</span></div><table class="event-range-with-margin"><tr>
<td class="event-range">
<div class="events-hdr"><span class="funcname">register_handler</span>: <span class="event-ids">events 3-4</span></div>
<table class="locus">
<tbody class="line-span">
<tr><td class="linenum"> 22</td> <td class="source">{</td></tr>
<tr><td class="linenum"/><td class="annotation">^</td></tr>
<tr><td class="linenum"/><td class="annotation">|</td></tr>
<tr><td class="linenum"/><td class="annotation">(3) entering 'register_handler'</td></tr>
<tr><td class="linenum"> 23</td> <td class="source"> signal(SIGINT, int_handler);</td></tr>
<tr><td class="linenum"/><td class="annotation"> ~~~~~~~~~~~~~~~~~~~~~~~~~~~</td></tr>
<tr><td class="linenum"/><td class="annotation"> |</td></tr>
<tr><td class="linenum"/><td class="annotation"> (4) registering 'int_handler' as signal handler</td></tr>
</tbody>
</table>
</td></tr></table>
</td></tr></table>
</td></tr></table>
<div class="between-ranges-return">
<svg height="30" width="250">
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7"
refX="0" refY="3.5" orient="auto" stroke="#0088ce" fill="#0088ce">
<polygon points="0 0, 10 3.5, 0 7"/>
</marker>
</defs>
<polyline points="220,0 220,10 20,10 20,20"
style="fill:none;stroke: #0088ce"
marker-end="url(#arrowhead)"/>
</svg>
</div>
<table class="event-range-with-margin"><tr>
<td class="event-range">
<div class="events-hdr"><span class="event-ids">event 5</span></div>
(5): later on, when the signal is delivered to the process
</td></tr></table>
<div class="between-ranges-call">
<svg height="30" width="150">
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7"
refX="0" refY="3.5" orient="auto" stroke="#0088ce" fill="#0088ce">
<polygon points="0 0, 10 3.5, 0 7"/>
</marker>
</defs>
<polyline points="20,0 20,10 120,10 120,20"
style="fill:none;stroke: #0088ce"
marker-end="url(#arrowhead)"/>
</svg>
</div>
<table class="stack-frame-with-margin"><tr>
<td class="interprocmargin" style="padding-left: 100px"/>
<td class="stack-frame">
<div class="frame-funcname"><span>int_handler</span></div><table class="event-range-with-margin"><tr>
<td class="event-range">
<div class="events-hdr"><span class="funcname">int_handler</span>: <span class="event-ids">events 6-7</span></div>
<table class="locus">
<tbody class="line-span">
<tr><td class="linenum"> 17</td> <td class="source">{</td></tr>
<tr><td class="linenum"/><td class="annotation">^</td></tr>
<tr><td class="linenum"/><td class="annotation">|</td></tr>
<tr><td class="linenum"/><td class="annotation">(6) entering 'int_handler'</td></tr>
<tr><td class="linenum"> 18</td> <td class="source"> custom_logger("got signal");</td></tr>
<tr><td class="linenum"/><td class="annotation"> ~~~~~~~~~~~~~~~~~~~~~~~~~~~</td></tr>
<tr><td class="linenum"/><td class="annotation"> |</td></tr>
<tr><td class="linenum"/><td class="annotation"> (7) calling 'custom_logger'</td></tr>
</tbody>
</table>
</td></tr></table>
<div class="between-ranges-call">
<svg height="30" width="150">
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7"
refX="0" refY="3.5" orient="auto" stroke="#0088ce" fill="#0088ce">
<polygon points="0 0, 10 3.5, 0 7"/>
</marker>
</defs>
<polyline points="20,0 20,10 120,10 120,20"
style="fill:none;stroke: #0088ce"
marker-end="url(#arrowhead)"/>
</svg>
</div>
<table class="stack-frame-with-margin"><tr>
<td class="interprocmargin" style="padding-left: 100px"/>
<td class="stack-frame">
<div class="frame-funcname"><span>custom_logger</span></div><table class="event-range-with-margin"><tr>
<td class="event-range">
<div class="events-hdr"><span class="funcname">custom_logger</span>: <span class="event-ids">events 8-9</span></div>
<table class="locus">
<tbody class="line-span">
<tr><td class="linenum"> 12</td> <td class="source">{</td></tr>
<tr><td class="linenum"/><td class="annotation">^</td></tr>
<tr><td class="linenum"/><td class="annotation">|</td></tr>
<tr><td class="linenum"/><td class="annotation">(8) entering 'custom_logger'</td></tr>
<tr><td class="linenum"> 13</td> <td class="source"> fprintf(stderr, "LOG: %s", msg); /* { dg-warning "call to 'fprintf' from within signal handler" } */</td></tr>
<tr><td class="linenum"/><td class="annotation"> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~</td></tr>
<tr><td class="linenum"/><td class="annotation"> |</td></tr>
<tr><td class="linenum"/><td class="annotation"> (9) calling 'fprintf'</td></tr>
</tbody>
</table>
</td></tr></table>
</td></tr></table>
</td></tr></table>
</div>
"""

View File

@ -46,7 +46,8 @@ def test_very_wide_line(html_tree):
def test_fixit_insert(html_tree):
diag = get_diag_by_index(html_tree, 3)
msg = get_message_within_diag(diag)
assert msg.text == 'example of insertion hints'
assert msg[0].text == 'warning: '
assert msg[0].tail == ' example of insertion hints'
src = get_locus_within_diag(diag)
@ -62,7 +63,8 @@ def test_fixit_insert(html_tree):
def test_fixit_remove(html_tree):
diag = get_diag_by_index(html_tree, 4)
msg = get_message_within_diag(diag)
assert msg.text == 'example of a removal hint'
assert msg[0].text == 'warning: '
assert msg[0].tail == ' example of a removal hint'
src = get_locus_within_diag(diag)
@ -78,7 +80,8 @@ def test_fixit_remove(html_tree):
def test_fixit_replace(html_tree):
diag = get_diag_by_index(html_tree, 5)
msg = get_message_within_diag(diag)
assert msg.text == 'example of a replacement hint'
assert msg[0].text == 'warning: '
assert msg[0].tail == ' example of a replacement hint'
src = get_locus_within_diag(diag)
@ -94,7 +97,8 @@ def test_fixit_replace(html_tree):
def test_fixit_insert_newline(html_tree):
diag = get_diag_by_index(html_tree, 6)
msg = get_message_within_diag(diag)
assert msg.text == 'example of newline insertion hint'
assert msg[0].text == 'warning: '
assert msg[0].tail == ' example of newline insertion hint'
src = get_locus_within_diag(diag)

View File

@ -83,14 +83,11 @@ def get_diag_by_index(html_tree, index):
assert diag_list is not None
assert_class(diag_list, 'gcc-diagnostic-list')
diags = diag_list.findall('xhtml:div', ns)
diag = diags[index]
assert_class(diag, 'gcc-diagnostic')
diag = diag_list.find(f"xhtml:div[@id='gcc-diag-{index}']", ns)
return diag
def get_message_within_diag(diag_element):
msg = diag_element.find('xhtml:span', ns)
assert_class(msg, 'gcc-message')
msg = diag_element.find("xhtml:div[@class='gcc-message']", ns)
return msg
def get_locus_within_diag(diag_element):

View File

@ -1280,7 +1280,7 @@ linemap_location_before_p (const line_maps *set,
return linemap_compare_locations (set, loc_a, loc_b) >= 0;
}
typedef struct
struct expanded_location
{
/* The name of the source file involved. */
const char *file;
@ -1294,7 +1294,18 @@ typedef struct
/* In a system header?. */
bool sysp;
} expanded_location;
};
extern bool
operator== (const expanded_location &a,
const expanded_location &b);
inline bool
operator!= (const expanded_location &a,
const expanded_location &b)
{
return !(a == b);
}
/* This is enum is used by the function linemap_resolve_location
below. The meaning of the values is explained in the comment of

View File

@ -1949,6 +1949,28 @@ linemap_expand_location (const line_maps *set,
return xloc;
}
bool
operator== (const expanded_location &a,
const expanded_location &b)
{
/* "file" can be null; for them to be equal they must both
have either null or nonnull values, and if non-null
they must compare as equal. */
if ((a.file == nullptr) != (b.file == nullptr))
return false;
if (a.file && strcmp (a.file, b.file))
return false;
if (a.line != b.line)
return false;
if (a.column != b.column)
return false;
if (a.data != b.data)
return false;
if (a.sysp != b.sysp)
return false;
return true;
}
/* Dump line map at index IX in line table SET to STREAM. If STREAM
is NULL, use stderr. IS_MACRO is true if the caller wants to