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 Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Pushed to trunk as r16-1406-gcb1d203445c923. 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 <dmalc...@redhat.com> --- gcc/diagnostic-format-html.cc | 433 ++++++++++++++++-- .../gcc.dg/format/diagnostic-ranges-html.py | 15 +- .../gcc.dg/html-output/missing-semicolon.py | 68 ++- .../plugin/diagnostic-test-metadata-html.py | 38 +- .../gcc.dg/plugin/diagnostic-test-paths-2.py | 11 +- .../gcc.dg/plugin/diagnostic-test-paths-4.py | 155 +------ .../plugin/diagnostic-test-show-locus.py | 12 +- gcc/testsuite/lib/htmltest.py | 7 +- libcpp/include/line-map.h | 15 +- libcpp/line-map.cc | 22 + 10 files changed, 549 insertions(+), 227 deletions(-) diff --git a/gcc/diagnostic-format-html.cc b/gcc/diagnostic-format-html.cc index 6010712b8a5b..22bf6b965df7 100644 --- a/gcc/diagnostic-format-html.cc +++ b/gcc/diagnostic-format-html.cc @@ -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,12 +489,77 @@ 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. @@ -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 (*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); - xml::printer xp (*message_span.get ()); + 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>'</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>'</div>\n" " </div>\n" " </div>\n" " </body>\n" diff --git a/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py b/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py index 1d798939e729..b0b59d951ca8 100644 --- a/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py +++ b/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py @@ -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): """ diff --git a/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py index 880fcc0231bc..3adaa52ee64d 100644 --- a/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py +++ b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py @@ -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" + + # 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' - pre = diag.find('xhtml:pre', ns) + # 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('--- ') diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py index b4c75b230ee2..67fb241d179c 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py @@ -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 '<span class="gcc-quoted-text">gets</span>'</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> -""" diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py index 59bee247103f..f0fed45626ef 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py @@ -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) diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.py b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.py index e738729a3472..d2bc67c52a29 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.py +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.py @@ -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> - """ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus.py b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus.py index d963b29830b5..aca1b6cdaccd 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus.py +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus.py @@ -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) diff --git a/gcc/testsuite/lib/htmltest.py b/gcc/testsuite/lib/htmltest.py index 8e42a8c2a17e..49ed4b035820 100644 --- a/gcc/testsuite/lib/htmltest.py +++ b/gcc/testsuite/lib/htmltest.py @@ -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): diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h index 75cc1ad2c275..83ebbde474df 100644 --- a/libcpp/include/line-map.h +++ b/libcpp/include/line-map.h @@ -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 diff --git a/libcpp/line-map.cc b/libcpp/line-map.cc index cf6557117c81..284af5781dcf 100644 --- a/libcpp/line-map.cc +++ b/libcpp/line-map.cc @@ -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 -- 2.26.3