diff options
author | David Malcolm <dmalcolm@redhat.com> | 2025-05-27 12:22:26 -0400 |
---|---|---|
committer | David Malcolm <dmalcolm@redhat.com> | 2025-05-27 12:31:19 -0400 |
commit | 04871e748862fc48fac739f0ce9769e566ca7cc3 (patch) | |
tree | 53bc6889c985648bd8426eab6f7a25a3846f477a | |
parent | 7fda941722b8e71bab118fb46198c9db9d90fb04 (diff) | |
download | gcc-04871e748862fc48fac739f0ce9769e566ca7cc3.zip gcc-04871e748862fc48fac739f0ce9769e566ca7cc3.tar.gz gcc-04871e748862fc48fac739f0ce9769e566ca7cc3.tar.bz2 |
diagnostics: rework experimental-html output [PR116792]
This patch reworks the HTML output from the the option
-fdiagnostics-add-output=experimental-html
so that for source quoting and path printing, rather than simply adding
the textual output inside <pre> elements, it breaks up the output
into HTML tags reflecting the structure of the output, using CSS, SVG
and a little javascript to help the user navigate the diagnostics and
the events within any paths.
This uses ideas from the patch I posted in:
https://gcc.gnu.org/pipermail/gcc-patches/2020-November/558603.html
but reworks the above patch so that:
* rather than printing source to a pretty_printer, the HTML is created
by building a DOM tree in memory, using a new xml::printer class. This
should be less error-prone than the pretty_printer approach, since it
ought to solve escaping issues. Instead of a text vs html boolean,
the code is generalized via templates with to_text vs to_html sinks.
This templatization applies both to path-printing and
diagnostic-show-locus.cc.
* the HTML output can have multiple diagnostics and multiple paths rather
than just a single path. The javascript keyboard controls now cycle
through all diagnostics and all events within them
An example of the output can be seen at:
https://dmalcolm.fedorapeople.org/gcc/2025-05-27/malloc-1.c.html
where the keys "j" and "k" cycle through diagnostics and events
within them.
gcc/ChangeLog:
PR other/116792
* diagnostic-format-html.cc: Define INCLUDE_STRING.
Include "xml.h", "xml-printer.h", and "json.h".
(html_generation_options::html_generation_options): New.
(namespace xml): Move decls to xml.h and convert from using
label_text to std::string.
(xml::text::write_as_xml): Reimplement indentation so it is done
by this node, rather than the parent.
(xml::node_with_children::add_text): Convert from label_text to
std::string. Consolidate runs of text into a single node.
(xml::document::write_as_xml): Reimplement indentation.
(xml::element::write_as_xml): Reimplement indentation so it is
done by this node, rather than the parent. Convert from
label_text to std::string. Update attribute-printing to new
representation to preserve insertion order.
(xml::element::set_attr): Convert from label_text to std::string.
Record insertion order.
(xml::raw::write_as_xml): New.
(xml::printer::printer): New.
(xml::printer::push_tag): New.
(xml::printer::push_tag_with_class): New.
(xml::printer::pop_tag): New.
(xml::printer::set_attr): New.
(xml::printer::add_text): New.
(xml::printer::add_raw): New.
(xml::printer::push_element): New.
(xml::printer::append): New.
(xml::printer::get_insertion_point): New.
(html_builder::add_focus_id): New.
(html_builder::m_html_gen_opts): New field.
(html_builder::m_head_element): New field.
(html_builder::m_next_diag_id): New field.
(html_builder::m_ui_focus_ids): New field.
(make_div): Convert from label_text to std::string.
(make_span): Likewise.
(HTML_STYLE): New.
(HTML_SCRIPT): New.
(html_builder::html_builder): Fix indentation. Add
"html_gen_opts" param. Initialize new fields. Reimplement
using xml::printer. Optionally add style and script tags.
(class html_path_label_writer): New.
(html_builder::make_element_for_diagnostic): Convert from
label_text to std::string. Set "id" on "gcc-diagnostic" and
"gcc-message" <div> elements; add the latter to the focus ids.
Use diagnostic_context::maybe_show_locus_as_html rather than
html_builder::make_element_for_source. Use print_path_as_html
rather than html_builder::make_element_for_path.
(html_builder::make_element_for_source): Drop.
(html_builder::make_element_for_path): Drop.
(html_builder::make_element_for_patch): Convert from label_text to
std::string.
(html_builder::make_metadata_element): Likewise. Use
xml::printer.
(html_builder::make_element_for_metadata): Convert from label_text
to std::string.
(html_builder::emit_diagram): Expand comment.
(html_builder::flush_to_file): Write out initializer for
"focus_ids" into javascript.
(html_output_format::html_output_format): Add param
"html_gen_opts" and use it to initialize m_builder.
(html_file_output_format::html_file_output_format): Likewise, to
initialize base class.
(make_html_sink): Likewise, to pass to ctor.
(selftest::test_html_diagnostic_context::test_html_diagnostic_context):
Set up html_generation_options.
(selftest::html_buffered_output_format::html_buffered_output_format):
Add html_gen_opts param.
(selftest::test_simple_log): Add id attributes to expected text
for "gcc-diagnostic" and "gcc-message" elements. Update
whitespace for indentation fixes.
(selftest::test_metadata): Update whitespace for indentation
fixes.
(selftest::test_printer): New selftest.
(selftest::test_attribute_ordering): New selftest.
(selftest::diagnostic_format_html_cc_tests): Call the new
selftests.
* diagnostic-format-html.h (struct html_generation_options): New.
(make_html_sink): Add "html_gen_opts" param.
(print_path_as_html): New decl.
* diagnostic-path-output.cc: Define INCLUDE_MAP. Add includes of
"diagnostic-format-html.h", "xml.h", and "xml-printer.h".
(path_print_policy::path_print_policy): Add ctor.
(path_print_policy::get_diagram_theme): Fix whitespace.
(struct stack_frame): New.
(begin_html_stack_frame): New function.
(end_html_stack_frame): New function.
(emit_svg_arrow): New function.
(event_range::print): Rename to...
(event_range::print_as_text): ...this. Update call to
diagnostic_start_span.
(event_range::print_as_html): New, based on the above, but ported
from pretty_printer to xml::printer.
(thread_event_printer::print_swimlane_for_event_range): Rename
to...
(thread_event_printer::print_swimlane_for_event_range_as_text):
...this. Update for renaming of event_range::print to
event_range::print_as_text.
(thread_event_printer::print_swimlane_for_event_range_as_html):
New.
(print_path_summary_as_text): Update for "_as_text" renaming.
(print_path_summary_as_html): New.
(print_path_as_html): New.
* diagnostic-show-locus.cc: Add defines of INCLUDE_MAP and
INCLUDE_STRING. Add includes of "xml.h" and "xml-printer.h".
(struct char_display_policy): Replace "m_print_cb" with
"m_print_text_cb" and "m_print_html_cb".
(struct to_text): New.
(struct to_html): New.
(get_printer): New.
(default_diagnostic_start_span_fn<to_text>): New.
(default_diagnostic_start_span_fn<to_html>): New.
(class layout): Update "friend class layout_printer;" for
template.
(enum class margin_kind): New.
(class layout_printer): Convert into a template.
(layout_printer::m_pp): Replace field with...
(layout_printer::m_sink): ...this.
(layout_printer::m_colorizer): Drop field in favor of a pointer
in the "to_text" sink.
(default_print_decoded_ch): Convert into a template.
(escape_as_bytes_print): Likewise.
(escape_as_unicode_print): Likewise.
(make_char_policy): Update to use both text and html callbacks.
(layout_printer::print_gap_in_line_numbering): Replace with...
(layout_printer<to_text>::print_gap_in_line_numbering): ...this
(layout_printer<to_html>::print_gap_in_line_numbering): ...and
this.
(layout_printer::print_source_line): Convert to template, using
m_sink.
(layout_printer::print_leftmost_column): Likewise.
(layout_printer::start_annotation_line): Likewise.
(layout_printer<to_text>::end_line): New.
(layout_printer<to_html>::end_line): New.
(layout_printer::print_annotation_line): Convert to template,
using m_sink.
(class line_label): Add field m_original_range_idx.
(layout_printer<to_text>::begin_label): New.
(layout_printer<to_html>::begin_label): New.
(layout_printer<to_text>::end_label): New.
(layout_printer<to_html>::end_label): New.
(layout_printer::print_any_labels): Convert to template, using
m_sink.
(layout_printer::print_leading_fixits): Likewise.
(layout_printer::print_trailing_fixits): Likewise.
(layout_printer::print_newline): Drop.
(layout_printer::move_to_column): Convert to template, using
m_sink.
(layout_printer::show_ruler): Likewise.
(layout_printer::print_line): Likewise.
(layout_printer::print_any_right_to_left_edge_lines): Likewise.
(layout_printer::layout_printer): Likewise.
(diagnostic_context::maybe_show_locus_as_html): New.
(diagnostic_source_print_policy::diagnostic_source_print_policy):
Update for split of start_span_cb into text vs html variants.
(diagnostic_source_print_policy::print): Update for use of
templates; use to_text.
(diagnostic_source_print_policy::print_as_html): New.
(layout_printer::print): Convert to template, using m_sink.
(selftest::make_element_for_locus): New.
(selftest::make_raw_html_for_locus): New.
(selftest::test_layout_x_offset_display_utf8): Update for use of
templates.
(selftest::test_layout_x_offset_display_tab): Likewise.
(selftest::test_one_liner_caret_and_range): Add test coverage of
HTML output.
(selftest::test_one_liner_labels): Likewise.
* diagnostic.cc (diagnostic_context::initialize): Update for split
of start_span_cb into text vs html variants.
(default_diagnostic_start_span_fn): Move to
diagnostic-show-locus.cc, converting to template.
* diagnostic.h (class xml::printer): New forward decl.
(diagnostic_start_span_fn): Replace typedef with "using",
converting to a template.
(struct to_text): New forward decl.
(struct to_html): New forward decl.
(get_printer): New decl.
(diagnostic_location_print_policy::print_text_span_start): New
decl.
(diagnostic_location_print_policy::print_html_span_start): New
decl.
(class html_label_writer): New.
(diagnostic_source_print_policy::print_as_html): New decl.
(diagnostic_source_print_policy::get_start_span_fn): Replace
with...
(diagnostic_source_print_policy::get_text_start_span_fn): ...this
(diagnostic_source_print_policy::get_html_start_span_fn): ...and
this
(diagnostic_source_print_policy::m_start_span_cb): Replace with...
(diagnostic_source_print_policy::m_text_start_span_cb): ...this
(diagnostic_source_print_policy::m_html_start_span_cb): ...and
this.
(diagnostic_context::maybe_show_locus_as_html): New decl.
(diagnostic_context::m_text_callbacks::m_start_span): Replace
with...
(diagnostic_context::m_text_callbacks::m_text_start_span): ...this
(diagnostic_context::m_text_callbacks::m_html_start_span): ...and
this.
(diagnostic_start_span): Update for template change.
(diagnostic_show_locus_as_html): New inline function.
(default_diagnostic_start_span_fn): Convert to template.
* doc/invoke.texi (experimental-html): Add "css" and "javascript"
keys.
* opts-diagnostic.cc (html_scheme_handler::make_sink): Likewise.
* selftest-diagnostic.cc
(selftest::test_diagnostic_context::start_span_cb): Update for
template changes.
* selftest-diagnostic.h
(selftest::test_diagnostic_context::start_span_cb): Likewise.
* xml-printer.h: New file.
* xml.h: New file, based on material in diagnostic-format-html.cc,
but using std::string rather than label_text.
(xml::element::m_key_insertion_order): New field.
(struct xml::raw): New.
gcc/fortran/ChangeLog
PR other/116792
* error.cc (gfc_diagnostic_start_span): Update for diagnostic.h
changes.
gcc/testsuite/ChangeLog:
PR other/116792
* gcc.dg/html-output/missing-semicolon.c: Add ":javascript=no" to
html output.
* gcc.dg/html-output/missing-semicolon.py: Move repeated
definitions into lib/htmltest.py.
* gcc.dg/plugin/diagnostic_group_plugin.cc: Update for template
changes.
* gcc.dg/plugin/diagnostic-test-metadata-html.c: Add
":javascript=no" to html output. Add
"-fdiagnostics-show-line-numbers".
* gcc.dg/plugin/diagnostic-test-metadata-html.py: Move repeated
definitions into lib/htmltest.py. Add checks of annotated source.
* gcc.dg/plugin/diagnostic-test-paths-2.c: Add ":javascript=no" to
html output.
* gcc.dg/plugin/diagnostic-test-paths-2.py: Move repeated
definitions into lib/htmltest.py. Add checks of execution path.
* gcc.dg/plugin/diagnostic-test-paths-4.c: Add
-fdiagnostics-add-output=experimental-html:javascript=no. Add
invocation ot diagnostic-test-paths-4.py.
* gcc.dg/plugin/diagnostic-test-paths-4.py: New test script.
* gcc.dg/plugin/diagnostic-test-show-locus-bw-line-numbers.c: Add
-fdiagnostics-add-output=experimental-html:javascript=no. Add
invocation of diagnostic-test-show-locus.py.
* gcc.dg/plugin/diagnostic-test-show-locus.py: New test script.
* lib/htmltest.py: New test support script.
Signed-off-by: David Malcolm <dmalcolm@redhat.com>
25 files changed, 2437 insertions, 468 deletions
diff --git a/gcc/diagnostic-format-html.cc b/gcc/diagnostic-format-html.cc index 6bb1caf..f2b255b 100644 --- a/gcc/diagnostic-format-html.cc +++ b/gcc/diagnostic-format-html.cc @@ -20,6 +20,7 @@ along with GCC; see the file COPYING3. If not see #include "config.h" #define INCLUDE_MAP +#define INCLUDE_STRING #define INCLUDE_VECTOR #include "system.h" #include "coretypes.h" @@ -36,6 +37,17 @@ along with GCC; see the file COPYING3. If not see #include "pretty-print-urlifier.h" #include "edit-context.h" #include "intl.h" +#include "xml.h" +#include "xml-printer.h" +#include "json.h" + +// struct html_generation_options + +html_generation_options::html_generation_options () +: m_css (true), + m_javascript (true) +{ +} namespace xml { @@ -46,57 +58,6 @@ namespace xml { # pragma GCC diagnostic ignored "-Wformat-diag" #endif -struct node -{ - virtual ~node () {} - virtual void write_as_xml (pretty_printer *pp, - int depth, bool indent) const = 0; - void dump (FILE *out) const; - void DEBUG_FUNCTION dump () const { dump (stderr); } -}; - -struct text : public node -{ - text (label_text str) - : m_str (std::move (str)) - {} - - void write_as_xml (pretty_printer *pp, - int depth, bool indent) const final override; - - label_text m_str; -}; - -struct node_with_children : public node -{ - void add_child (std::unique_ptr<node> node); - void add_text (label_text str); - - std::vector<std::unique_ptr<node>> m_children; -}; - -struct document : public node_with_children -{ - void write_as_xml (pretty_printer *pp, - int depth, bool indent) const final override; -}; - -struct element : public node_with_children -{ - element (const char *kind, bool preserve_whitespace) - : m_kind (kind), - m_preserve_whitespace (preserve_whitespace) - {} - - void write_as_xml (pretty_printer *pp, - int depth, bool indent) const final override; - - void set_attr (const char *name, label_text value); - - const char *m_kind; - bool m_preserve_whitespace; - std::map<const char *, label_text> m_attributes; -}; /* Implementation. */ @@ -146,9 +107,16 @@ node::dump (FILE *out) const /* struct text : public node. */ void -text::write_as_xml (pretty_printer *pp, int /*depth*/, bool /*indent*/) const +text::write_as_xml (pretty_printer *pp, int depth, bool indent) const { - write_escaped_text (pp, m_str.get ()); + if (indent) + { + for (int i = 0; i < depth; ++i) + pp_string (pp, " "); + } + write_escaped_text (pp, m_str.c_str ()); + if (indent) + pp_newline (pp); } /* struct node_with_children : public node. */ @@ -161,9 +129,15 @@ node_with_children::add_child (std::unique_ptr<node> node) } void -node_with_children::add_text (label_text str) +node_with_children::add_text (std::string str) { - gcc_assert (str.get ()); + // Consolidate runs of text + if (!m_children.empty ()) + if (text *t = m_children.back ()->dyn_cast_text ()) + { + t->m_str += std::move (str); + return; + } add_child (std::make_unique <text> (std::move (str))); } @@ -177,6 +151,8 @@ document::write_as_xml (pretty_printer *pp, int depth, bool indent) const pp_string (pp, "<!DOCTYPE html\n" " PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n" " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"); + if (indent) + pp_newline (pp); for (auto &iter : m_children) iter->write_as_xml (pp, depth, indent); } @@ -188,48 +164,139 @@ element::write_as_xml (pretty_printer *pp, int depth, bool indent) const { if (indent) { - pp_newline (pp); for (int i = 0; i < depth; ++i) pp_string (pp, " "); } - if (m_preserve_whitespace) - indent = false; - - pp_printf (pp, "<%s", m_kind); - for (auto &attr : m_attributes) + pp_printf (pp, "<%s", m_kind.c_str ()); + for (auto &key : m_key_insertion_order) { - pp_printf (pp, " %s=\"", attr.first); - write_escaped_text (pp, attr.second.get ()); - pp_string (pp, "\""); + auto iter = m_attributes.find (key); + if (iter != m_attributes.end ()) + { + pp_printf (pp, " %s=\"", key.c_str ()); + write_escaped_text (pp, iter->second.c_str ()); + pp_string (pp, "\""); + } } if (m_children.empty ()) - pp_string (pp, " />"); + pp_string (pp, "/>"); else { + const bool indent_children = m_preserve_whitespace ? false : indent; pp_string (pp, ">"); + if (indent_children) + pp_newline (pp); for (auto &child : m_children) - child->write_as_xml (pp, depth + 1, indent); - if (indent) + child->write_as_xml (pp, depth + 1, indent_children); + if (indent_children) { - pp_newline (pp); for (int i = 0; i < depth; ++i) pp_string (pp, " "); } - pp_printf (pp, "</%s>", m_kind); + pp_printf (pp, "</%s>", m_kind.c_str ()); } + + if (indent) + pp_newline (pp); } void -element::set_attr (const char *name, label_text value) +element::set_attr (const char *name, std::string value) { + auto iter = m_attributes.find (name); + if (iter == m_attributes.end ()) + m_key_insertion_order.push_back (name); m_attributes[name] = std::move (value); } +// struct raw : public node + +void +raw::write_as_xml (pretty_printer *pp, + int /*depth*/, bool /*indent*/) const +{ + pp_string (pp, m_xml_src.c_str ()); +} + #if __GNUC__ >= 10 # pragma GCC diagnostic pop #endif +// class printer + +printer::printer (element &insertion_point) +{ + m_open_tags.push_back (&insertion_point); +} + +void +printer::push_tag (std::string name, + bool preserve_whitespace) +{ + push_element + (std::make_unique<element> (std::move (name), + preserve_whitespace)); +} + +void +printer::push_tag_with_class (std::string name, std::string class_, + bool preserve_whitespace) +{ + auto new_element + = std::make_unique<element> (std::move (name), + preserve_whitespace); + new_element->set_attr ("class", class_); + push_element (std::move (new_element)); +} + +void +printer::pop_tag () +{ + m_open_tags.pop_back (); +} + +void +printer::set_attr (const char *name, std::string value) +{ + m_open_tags.back ()->set_attr (name, value); +} + +void +printer::add_text (std::string text) +{ + element *parent = m_open_tags.back (); + parent->add_text (std::move (text)); +} + +void +printer::add_raw (std::string text) +{ + element *parent = m_open_tags.back (); + parent->add_child (std::make_unique<xml::raw> (std::move (text))); +} + +void +printer::push_element (std::unique_ptr<element> new_element) +{ + element *parent = m_open_tags.back (); + m_open_tags.push_back (new_element.get ()); + parent->add_child (std::move (new_element)); +} + +void +printer::append (std::unique_ptr<node> new_node) +{ + element *parent = m_open_tags.back (); + parent->add_child (std::move (new_node)); +} + +element * +printer::get_insertion_point () const +{ + return m_open_tags.back (); +} + } // namespace xml class html_builder; @@ -284,7 +351,8 @@ public: html_builder (diagnostic_context &context, pretty_printer &pp, - const line_maps *line_maps); + const line_maps *line_maps, + const html_generation_options &html_gen_opts); void on_report_diagnostic (const diagnostic_info &diagnostic, diagnostic_t orig_diag_kind, @@ -310,14 +378,13 @@ public: make_element_for_metadata (const diagnostic_metadata &metadata); std::unique_ptr<xml::element> - make_element_for_source (const diagnostic_info &diagnostic); - - std::unique_ptr<xml::element> - make_element_for_path (const diagnostic_path &path); - - std::unique_ptr<xml::element> make_element_for_patch (const diagnostic_info &diagnostic); + void add_focus_id (std::string focus_id) + { + m_ui_focus_ids.append_string (focus_id.c_str ()); + } + private: std::unique_ptr<xml::element> make_element_for_diagnostic (const diagnostic_info &diagnostic, @@ -330,14 +397,18 @@ private: diagnostic_context &m_context; pretty_printer *m_printer; const line_maps *m_line_maps; + html_generation_options m_html_gen_opts; std::unique_ptr<xml::document> m_document; + xml::element *m_head_element; xml::element *m_diagnostics_element; std::unique_ptr<xml::element> m_cur_diagnostic_element; + int m_next_diag_id; // for handing out unique IDs + json::array m_ui_focus_ids; }; static std::unique_ptr<xml::element> -make_div (label_text class_) +make_div (std::string class_) { auto div = std::make_unique<xml::element> ("div", false); div->set_attr ("class", std::move (class_)); @@ -345,7 +416,7 @@ make_div (label_text class_) } static std::unique_ptr<xml::element> -make_span (label_text class_) +make_span (std::string class_) { auto span = std::make_unique<xml::element> ("span", true); span->set_attr ("class", std::move (class_)); @@ -400,45 +471,151 @@ diagnostic_html_format_buffer::flush () /* class html_builder. */ +/* Style information for writing out HTML paths. + Colors taken from https://www.patternfly.org/v3/styles/color-palette/ */ + +static const char * const HTML_STYLE + = (" <style>\n" + " .linenum { color: white;\n" + " background-color: #0088ce;\n" + " white-space: pre;\n" + " border-right: 1px solid black; }\n" + " .ruler { color: red;\n" + " white-space: pre; }\n" + " .source { color: blue;\n" + " white-space: pre; }\n" + " .annotation { color: green;\n" + " white-space: pre; }\n" + " .linenum-gap { text-align: center;\n" + " border-top: 1px solid black;\n" + " border-right: 1px solid black;\n" + " background-color: #ededed; }\n" + " .source-gap { border-bottom: 1px dashed black;\n" + " border-top: 1px dashed black;\n" + " background-color: #ededed; }\n" + " .no-locus-event { font-family: monospace;\n" + " color: green;\n" + " white-space: pre; }\n" + " .funcname { font-weight: bold; }\n" + " .events-hdr { color: white;\n" + " background-color: #030303; }\n" + " .event-range { border: 1px solid black;\n" + " padding: 0px; }\n" + " .event-range-with-margin { border-spacing: 0; }\n" + " .locus { font-family: monospace;\n" + " border-spacing: 0px; }\n" + " .selected { color: white;\n" + " background-color: #0088ce; }\n" + " .stack-frame-with-margin { border-spacing: 0; }\n" + " .stack-frame { padding: 5px;\n" + " box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.5); }\n" + " .frame-funcname { text-align: right;\n" + " font-style: italic; } \n" + " </style>\n"); + +/* A little JavaScript for ease of navigation. + Keys j/k move forward and backward cyclically through a list + of focus ids (written out in another <script> tag as the HTML + is flushed). */ + +const char * const HTML_SCRIPT + = (" var current_focus_idx = 0;\n" + "\n" + " function get_focus_span (focus_idx)\n" + " {\n" + " const element_id = focus_ids[focus_idx];\n" + " return document.getElementById(element_id);\n" + " }\n" + " function unhighlight_current_focus_idx ()\n" + " {\n" + " get_focus_span (current_focus_idx).classList.remove ('selected');\n" + " }\n" + " function highlight_current_focus_idx ()\n" + " {\n" + " const el = get_focus_span (current_focus_idx);\n" + " el.classList.add ('selected');\n" + " // Center the element on the screen\n" + " const top_y = el.getBoundingClientRect ().top + window.pageYOffset;\n" + " const middle = top_y - (window.innerHeight / 2);\n" + " window.scrollTo (0, middle);\n" + " }\n" + " function select_prev_focus_idx ()\n" + " {\n" + " unhighlight_current_focus_idx ();\n" + " if (current_focus_idx > 0)\n" + " current_focus_idx -= 1;\n" + " else\n" + " current_focus_idx = focus_ids.length - 1;\n" + " highlight_current_focus_idx ();\n" + " }\n" + " function select_next_focus_idx ()\n" + " {\n" + " unhighlight_current_focus_idx ();\n" + " if (current_focus_idx < focus_ids.length - 1)\n" + " current_focus_idx += 1;\n" + " else\n" + " current_focus_idx = 0;\n" + " highlight_current_focus_idx ();\n" + " }\n" + " document.addEventListener('keydown', function (ev) {\n" + " if (ev.key == 'j')\n" + " select_next_focus_idx ();\n" + " else if (ev.key == 'k')\n" + " select_prev_focus_idx ();\n" + " });\n" + " highlight_current_focus_idx ();\n"); + /* html_builder's ctor. */ html_builder::html_builder (diagnostic_context &context, - pretty_printer &pp, - const line_maps *line_maps) + pretty_printer &pp, + const line_maps *line_maps, + const html_generation_options &html_gen_opts) : m_context (context), m_printer (&pp), - m_line_maps (line_maps) + m_line_maps (line_maps), + m_html_gen_opts (html_gen_opts), + m_head_element (nullptr), + m_diagnostics_element (nullptr), + m_next_diag_id (0) { gcc_assert (m_line_maps); m_document = std::make_unique<xml::document> (); { auto html_element = std::make_unique<xml::element> ("html", false); - html_element->set_attr - ("xmlns", - label_text::borrow ("http://www.w3.org/1999/xhtml")); + html_element->set_attr ("xmlns", + "http://www.w3.org/1999/xhtml"); + xml::printer xp (*html_element.get ()); + m_document->add_child (std::move (html_element)); + { + xml::auto_print_element head (xp, "head"); + m_head_element = xp.get_insertion_point (); { - auto head_element = std::make_unique<xml::element> ("head", false); + xml::auto_print_element title (xp, "title", true); + xp.add_text ("Title goes here"); + } + if (m_html_gen_opts.m_css) + xp.add_raw (HTML_STYLE); + if (m_html_gen_opts.m_javascript) { - auto title_element = std::make_unique<xml::element> ("title", true); - label_text title (label_text::borrow ("Title goes here")); // TODO - title_element->add_text (std::move (title)); - head_element->add_child (std::move (title_element)); + xp.push_tag ("script"); + /* Escaping rules are different for HTML <script> elements, + so add the script "raw" for now. */ + xp.add_raw (HTML_SCRIPT); + xp.pop_tag (); // script } - html_element->add_child (std::move (head_element)); + } - auto body_element = std::make_unique<xml::element> ("body", false); - { - auto diagnostics_element - = make_div (label_text::borrow ("gcc-diagnostic-list")); - m_diagnostics_element = diagnostics_element.get (); - body_element->add_child (std::move (diagnostics_element)); - } - html_element->add_child (std::move (body_element)); + { + xml::auto_print_element body (xp, "body"); + { + auto diagnostics_element = make_div ("gcc-diagnostic-list"); + m_diagnostics_element = diagnostics_element.get (); + xp.append (std::move (diagnostics_element)); } } - m_document->add_child (std::move (html_element)); } } @@ -477,6 +654,45 @@ html_builder::on_report_diagnostic (const diagnostic_info &diagnostic, } } +/* 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. */ + +class html_path_label_writer : public html_label_writer +{ +public: + html_path_label_writer (xml::printer &xp, + html_builder &builder, + const std::string &event_id_prefix) + : m_xp (xp), + m_html_builder (builder), + m_event_id_prefix (event_id_prefix), + m_next_event_idx (0) + { + } + + void begin_label () final override + { + m_xp.push_tag_with_class ("span", "event", true); + pretty_printer pp; + pp_printf (&pp, "%s%i", + m_event_id_prefix.c_str (), m_next_event_idx++); + m_xp.set_attr ("id", pp_formatted_text (&pp)); + m_html_builder.add_focus_id (pp_formatted_text (&pp)); + } + + void end_label () final override + { + m_xp.pop_tag (); // span + } + +private: + xml::printer &m_xp; + html_builder &m_html_builder; + const std::string &m_event_id_prefix; + int m_next_event_idx; +}; + std::unique_ptr<xml::element> html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, diagnostic_t orig_diag_kind) @@ -506,8 +722,7 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, pp_token_text *sub = as_a <pp_token_text *> (iter); /* The value might be in the obstack, so we may need to copy it. */ - insertion_element ().add_text - (label_text::take (xstrdup (sub->m_value.get ()))); + insertion_element ().add_text (sub->m_value.get ()); } break; @@ -518,14 +733,14 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, case pp_token::kind::begin_quote: { - insertion_element ().add_text (label_text::borrow (open_quote)); - push_element (make_span (label_text::borrow ("gcc-quoted-text"))); + insertion_element ().add_text (open_quote); + push_element (make_span ("gcc-quoted-text")); } break; case pp_token::kind::end_quote: { pop_element (); - insertion_element ().add_text (label_text::borrow (close_quote)); + insertion_element ().add_text (close_quote); } break; @@ -533,7 +748,7 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, { pp_token_begin_url *sub = as_a <pp_token_begin_url *> (iter); auto anchor = std::make_unique<xml::element> ("a", true); - anchor->set_attr ("href", std::move (sub->m_value)); + anchor->set_attr ("href", sub->m_value.get ()); push_element (std::move (anchor)); } break; @@ -565,11 +780,24 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, std::vector<xml::element *> m_open_elements; }; - auto diag_element = make_div (label_text::borrow ("gcc-diagnostic")); + auto diag_element = make_div ("gcc-diagnostic"); + + const int diag_idx = m_next_diag_id++; + std::string diag_id; + { + pretty_printer pp; + 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 (label_text::borrow ("gcc-message")); + 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); + html_token_printer tok_printer (*this, *message_span.get ()); m_printer->set_token_printer (&tok_printer); pp_output_formatted_text (m_printer, m_context.get_urlifier ()); @@ -579,7 +807,7 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, if (diagnostic.metadata) { - diag_element->add_text (label_text::borrow (" ")); + diag_element->add_text (" "); diag_element->add_child (make_element_for_metadata (*diagnostic.metadata)); } @@ -592,32 +820,47 @@ 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 (label_text::borrow (" ")); - auto option_span = make_span (label_text::borrow ("gcc-option")); - option_span->add_text (label_text::borrow ("[")); + diag_element->add_text (" "); + auto option_span = make_span ("gcc-option"); + option_span->add_text ("["); { if (option_url.get ()) { auto anchor = std::make_unique<xml::element> ("a", true); - anchor->set_attr ("href", std::move (option_url)); - anchor->add_text (std::move (option_text)); + anchor->set_attr ("href", option_url.get ()); + anchor->add_text (option_text.get ()); option_span->add_child (std::move (anchor)); } else - option_span->add_text (std::move (option_text)); - option_span->add_text (label_text::borrow ("]")); + option_span->add_text (option_text.get ()); + option_span->add_text ("]"); } diag_element->add_child (std::move (option_span)); } /* Source (and fix-it hints). */ - if (auto source_element = make_element_for_source (diagnostic)) - diag_element->add_child (std::move (source_element)); + { + xml::printer xp (*diag_element); + m_context.m_last_location = UNKNOWN_LOCATION; + m_context.maybe_show_locus_as_html (*diagnostic.richloc, + m_context.m_source_printing, + diagnostic.kind, + xp, + nullptr, + nullptr); + } /* Execution path. */ if (auto path = diagnostic.richloc->get_path ()) - if (auto path_element = make_element_for_path (*path)) - diag_element->add_child (std::move (path_element)); + { + xml::printer xp (*diag_element); + 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); + } if (auto patch_element = make_element_for_patch (diagnostic)) diag_element->add_child (std::move (patch_element)); @@ -626,61 +869,23 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, } std::unique_ptr<xml::element> -html_builder::make_element_for_source (const diagnostic_info &diagnostic) -{ - // TODO: ideally we'd like to capture elements within the following: - m_context.m_last_location = UNKNOWN_LOCATION; - pp_clear_output_area (m_printer); - diagnostic_show_locus (&m_context, - m_context.m_source_printing, - diagnostic.richloc, diagnostic.kind, - m_printer); - auto text = label_text::take (xstrdup (pp_formatted_text (m_printer))); - pp_clear_output_area (m_printer); - - if (strlen (text.get ()) == 0) - return nullptr; - - auto pre = std::make_unique<xml::element> ("pre", true); - pre->set_attr ("class", label_text::borrow ("gcc-annotated-source")); - pre->add_text (std::move (text)); - return pre; -} - -std::unique_ptr<xml::element> -html_builder::make_element_for_path (const diagnostic_path &path) -{ - m_context.m_last_location = UNKNOWN_LOCATION; - diagnostic_text_output_format text_format (m_context); - pp_show_color (text_format.get_printer ()) = false; - pp_buffer (text_format.get_printer ())->m_flush_p = false; - // TODO: ideally we'd like to capture elements within the following: - text_format.print_path (path); - auto text = label_text::take - (xstrdup (pp_formatted_text (text_format.get_printer ()))); - - if (strlen (text.get ()) == 0) - return nullptr; - - auto pre = std::make_unique<xml::element> ("pre", true); - pre->set_attr ("class", label_text::borrow ("gcc-execution-path")); - pre->add_text (std::move (text)); - return pre; -} - -std::unique_ptr<xml::element> html_builder::make_element_for_patch (const diagnostic_info &diagnostic) { edit_context ec (m_context.get_file_cache ()); ec.add_fixits (diagnostic.richloc); if (char *diff = ec.generate_diff (true)) - if (strlen (diff) > 0) - { - auto element = std::make_unique<xml::element> ("pre", true); - element->set_attr ("class", label_text::borrow ("gcc-generated-patch")); - element->add_text (label_text::take (diff)); - return element; - } + { + if (strlen (diff) > 0) + { + auto element = std::make_unique<xml::element> ("pre", true); + element->set_attr ("class", "gcc-generated-patch"); + element->add_text (diff); + free (diff); + return element; + } + else + free (diff); + } return nullptr; } @@ -688,22 +893,23 @@ std::unique_ptr<xml::element> html_builder::make_metadata_element (label_text label, label_text url) { - auto item = make_span (label_text::borrow ("gcc-metadata-item")); - item->add_text (label_text::borrow ("[")); + auto item = make_span ("gcc-metadata-item"); + xml::printer xp (*item.get ()); + xp.add_text ("["); { - auto anchor = std::make_unique<xml::element> ("a", true); - anchor->set_attr ("href", std::move (url)); - anchor->add_child (std::make_unique<xml::text> (std::move (label))); - item->add_child (std::move (anchor)); + xp.push_tag ("a", true); + xp.set_attr ("href", url.get ()); + xp.add_text (label.get ()); + xp.pop_tag (); } - item->add_text (label_text::borrow ("]")); + xp.add_text ("]"); return item; } std::unique_ptr<xml::element> html_builder::make_element_for_metadata (const diagnostic_metadata &metadata) { - auto span_metadata = make_span (label_text::borrow ("gcc-metadata")); + auto span_metadata = make_span ("gcc-metadata"); int cwe = metadata.get_cwe (); if (cwe) @@ -737,7 +943,7 @@ html_builder::emit_diagram (const diagnostic_diagram &/*diagram*/) /* We must be within the emission of a top-level diagnostic. */ gcc_assert (m_cur_diagnostic_element); - // TODO + // TODO: currently a no-op } /* Implementation of "end_group_cb" for HTML output. */ @@ -757,6 +963,20 @@ html_builder::end_group () void html_builder::flush_to_file (FILE *outf) { + if (m_html_gen_opts.m_javascript) + { + gcc_assert (m_head_element); + xml::printer xp (*m_head_element); + /* Add an initialization of the global js variable "focus_ids" + using the array of IDs we saved as we went. */ + xp.push_tag ("script"); + pretty_printer pp; + pp_string (&pp, "focus_ids = "); + m_ui_focus_ids.print (&pp, true); + pp_string (&pp, ";\n"); + xp.add_raw (pp_formatted_text (&pp)); + xp.pop_tag (); // script + } auto top = m_document.get (); top->dump (outf); fprintf (outf, "\n"); @@ -842,9 +1062,10 @@ public: protected: html_output_format (diagnostic_context &context, - const line_maps *line_maps) + const line_maps *line_maps, + const html_generation_options &html_gen_opts) : diagnostic_output_format (context), - m_builder (context, *get_printer (), line_maps), + m_builder (context, *get_printer (), line_maps, html_gen_opts), m_buffer (nullptr) {} @@ -857,8 +1078,9 @@ class html_file_output_format : public html_output_format public: html_file_output_format (diagnostic_context &context, const line_maps *line_maps, + const html_generation_options &html_gen_opts, diagnostic_output_file output_file) - : html_output_format (context, line_maps), + : html_output_format (context, line_maps, html_gen_opts), m_output_file (std::move (output_file)) { gcc_assert (m_output_file.get_open_file ()); @@ -922,11 +1144,13 @@ diagnostic_output_format_open_html_file (diagnostic_context &context, std::unique_ptr<diagnostic_output_format> make_html_sink (diagnostic_context &context, const line_maps &line_maps, + const html_generation_options &html_gen_opts, diagnostic_output_file output_file) { auto sink = std::make_unique<html_file_output_format> (context, &line_maps, + html_gen_opts, std::move (output_file)); sink->update_printer (); return sink; @@ -945,8 +1169,12 @@ class test_html_diagnostic_context : public test_diagnostic_context public: test_html_diagnostic_context () { + html_generation_options html_gen_opts; + html_gen_opts.m_css = false; + html_gen_opts.m_javascript = false; auto sink = std::make_unique<html_buffered_output_format> (*this, - line_table); + line_table, + html_gen_opts); sink->update_printer (); m_format = sink.get (); // borrowed @@ -968,8 +1196,9 @@ private: { public: html_buffered_output_format (diagnostic_context &context, - const line_maps *line_maps) - : html_output_format (context, line_maps) + const line_maps *line_maps, + const html_generation_options &html_gen_opts) + : html_output_format (context, line_maps, html_gen_opts) { } bool machine_readable_stderr_p () const final override @@ -1009,12 +1238,12 @@ test_simple_log () " </head>\n" " <body>\n" " <div class=\"gcc-diagnostic-list\">\n" - " <div class=\"gcc-diagnostic\">\n" - " <span class=\"gcc-message\">this is a test: `<span class=\"gcc-quoted-text\">foo</span>'</span>\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>\n" " </div>\n" " </body>\n" - "</html>")); + "</html>\n")); } static void @@ -1031,7 +1260,6 @@ test_metadata () element->write_as_xml (&pp, 0, true); ASSERT_STREQ (pp_formatted_text (&pp), - "\n" "<span class=\"gcc-metadata\">" "<span class=\"gcc-metadata-item\">" "[" @@ -1040,7 +1268,7 @@ test_metadata () "</a>" "]" "</span>" - "</span>"); + "</span>\n"); } { @@ -1053,7 +1281,6 @@ test_metadata () element->write_as_xml (&pp, 0, true); ASSERT_STREQ (pp_formatted_text (&pp), - "\n" "<span class=\"gcc-metadata\">" "<span class=\"gcc-metadata-item\">" "[" @@ -1062,10 +1289,71 @@ test_metadata () "</a>" "]" "</span>" - "</span>"); + "</span>\n"); } } +static void +test_printer () +{ + xml::element top ("top", false); + xml::printer xp (top); + xp.push_tag ("foo"); + xp.add_text ("hello"); + xp.push_tag ("bar"); + xp.set_attr ("size", "3"); + xp.set_attr ("color", "red"); + xp.add_text ("world"); + xp.push_tag ("baz"); + xp.pop_tag (); + xp.pop_tag (); + xp.pop_tag (); + + pretty_printer pp; + top.write_as_xml (&pp, 0, true); + ASSERT_STREQ + (pp_formatted_text (&pp), + "<top>\n" + " <foo>\n" + " hello\n" + " <bar size=\"3\" color=\"red\">\n" + " world\n" + " <baz/>\n" + " </bar>\n" + " </foo>\n" + "</top>\n"); +} + +// Verify that element attributes preserve insertion order. + +static void +test_attribute_ordering () +{ + xml::element top ("top", false); + xml::printer xp (top); + xp.push_tag ("chronological"); + xp.set_attr ("maldon", "991"); + xp.set_attr ("hastings", "1066"); + xp.set_attr ("edgehill", "1642"); + xp.set_attr ("naseby", "1645"); + xp.pop_tag (); + xp.push_tag ("alphabetical"); + xp.set_attr ("edgehill", "1642"); + xp.set_attr ("hastings", "1066"); + xp.set_attr ("maldon", "991"); + xp.set_attr ("naseby", "1645"); + xp.pop_tag (); + + pretty_printer pp; + top.write_as_xml (&pp, 0, true); + ASSERT_STREQ + (pp_formatted_text (&pp), + "<top>\n" + " <chronological maldon=\"991\" hastings=\"1066\" edgehill=\"1642\" naseby=\"1645\"/>\n" + " <alphabetical edgehill=\"1642\" hastings=\"1066\" maldon=\"991\" naseby=\"1645\"/>\n" + "</top>\n"); +} + /* Run all of the selftests within this file. */ void @@ -1074,6 +1362,8 @@ diagnostic_format_html_cc_tests () auto_fix_quotes fix_quotes; test_simple_log (); test_metadata (); + test_printer (); + test_attribute_ordering (); } } // namespace selftest diff --git a/gcc/diagnostic-format-html.h b/gcc/diagnostic-format-html.h index ff5edca..9ca43f2 100644 --- a/gcc/diagnostic-format-html.h +++ b/gcc/diagnostic-format-html.h @@ -24,6 +24,14 @@ along with GCC; see the file COPYING3. If not see #include "diagnostic-format.h" #include "diagnostic-output-file.h" +struct html_generation_options +{ + html_generation_options (); + + bool m_css; + bool m_javascript; +}; + extern diagnostic_output_file diagnostic_output_format_open_html_file (diagnostic_context &context, line_maps *line_maps, @@ -32,6 +40,14 @@ diagnostic_output_format_open_html_file (diagnostic_context &context, extern std::unique_ptr<diagnostic_output_format> make_html_sink (diagnostic_context &context, const line_maps &line_maps, + const html_generation_options &html_gen_opts, diagnostic_output_file output_file); +extern void +print_path_as_html (xml::printer &xp, + const diagnostic_path &path, + diagnostic_context &dc, + html_label_writer *event_label_writer, + const diagnostic_source_print_policy &dspp); + #endif /* ! GCC_DIAGNOSTIC_FORMAT_HTML_H */ diff --git a/gcc/diagnostic-path-output.cc b/gcc/diagnostic-path-output.cc index 3fe6a0d..4c17865 100644 --- a/gcc/diagnostic-path-output.cc +++ b/gcc/diagnostic-path-output.cc @@ -20,6 +20,7 @@ along with GCC; see the file COPYING3. If not see #include "config.h" #define INCLUDE_ALGORITHM +#define INCLUDE_MAP #define INCLUDE_STRING #define INCLUDE_VECTOR #include "system.h" @@ -38,6 +39,9 @@ along with GCC; see the file COPYING3. If not see #include "selftest-diagnostic-path.h" #include "text-art/theme.h" #include "diagnostic-format-text.h" +#include "diagnostic-format-html.h" +#include "xml.h" +#include "xml-printer.h" /* Disable warnings about missing quoting in GCC diagnostics for the print calls below. */ @@ -60,10 +64,15 @@ public: { } + path_print_policy (const diagnostic_context &dc) + : m_source_policy (dc) + { + } + text_art::theme * get_diagram_theme () const { - return m_source_policy.get_diagram_theme (); + return m_source_policy.get_diagram_theme (); } const diagnostic_source_print_policy & @@ -276,6 +285,119 @@ private: int m_max_depth; }; +/* A stack frame for use in HTML output, holding child stack frames, + and event ranges. */ + +struct stack_frame +{ + stack_frame (std::unique_ptr<stack_frame> parent, + logical_location logical_loc, + int stack_depth) + : m_parent (std::move (parent)), + m_logical_loc (logical_loc), + m_stack_depth (stack_depth) + {} + + std::unique_ptr<stack_frame> m_parent; + logical_location m_logical_loc; + const int m_stack_depth; +}; + +/* Begin emitting content relating to a new stack frame within PARENT. + Allocated a new stack_frame and return it. */ + +static std::unique_ptr<stack_frame> +begin_html_stack_frame (xml::printer &xp, + std::unique_ptr<stack_frame> parent, + logical_location logical_loc, + int stack_depth, + const logical_location_manager *logical_loc_mgr) +{ + if (logical_loc) + { + gcc_assert (logical_loc_mgr); + xp.push_tag_with_class ("table", "stack-frame-with-margin", false); + xp.push_tag ("tr", false); + { + xp.push_tag_with_class ("td", "interprocmargin", false); + xp.set_attr ("style", "padding-left: 100px"); + xp.pop_tag (); + } + xp.push_tag_with_class ("td", "stack-frame", false); + label_text funcname + = logical_loc_mgr->get_name_for_path_output (logical_loc); + if (funcname.get ()) + { + xp.push_tag_with_class ("div", "frame-funcname", false); + xp.push_tag ("span", true); + xp.add_text (funcname.get ()); + xp.pop_tag (); // span + xp.pop_tag (); // div + } + } + return std::make_unique<stack_frame> (std::move (parent), + logical_loc, + stack_depth); +} + +/* Finish emitting content for FRAME and delete it. + Return parent. */ + +static std::unique_ptr<stack_frame> +end_html_stack_frame (xml::printer &xp, + std::unique_ptr<stack_frame> frame) +{ + auto parent = std::move (frame->m_parent); + if (frame->m_logical_loc) + { + xp.pop_tag (); // td + xp.pop_tag (); // tr + xp.pop_tag (); // table + } + return parent; +} + +/* Append an HTML <div> element to XP containing an SVG arrow representing + a change in stack depth from OLD_DEPTH to NEW_DEPTH. */ + +static void +emit_svg_arrow (xml::printer &xp, int old_depth, int new_depth) +{ + const int pixels_per_depth = 100; + const int min_depth = MIN (old_depth, new_depth); + const int base_x = 20; + const int excess = 30; + const int last_x + = base_x + (old_depth - min_depth) * pixels_per_depth; + const int this_x + = base_x + (new_depth - min_depth) * pixels_per_depth; + pretty_printer tmp_pp; + pretty_printer *pp = &tmp_pp; + pp_printf (pp, "<div class=\"%s\">\n", + old_depth < new_depth + ? "between-ranges-call" : "between-ranges-return"); + pp_printf (pp, " <svg height=\"30\" width=\"%i\">\n", + MAX (last_x, this_x) + excess); + pp_string + (pp, + " <defs>\n" + " <marker id=\"arrowhead\" markerWidth=\"10\" markerHeight=\"7\"\n" + " refX=\"0\" refY=\"3.5\" orient=\"auto\" stroke=\"#0088ce\" fill=\"#0088ce\">\n" + " <polygon points=\"0 0, 10 3.5, 0 7\"/>\n" + " </marker>\n" + " </defs>\n"); + pp_printf (pp, + " <polyline points=\"%i,0 %i,10 %i,10 %i,20\"\n", + last_x, last_x, this_x, this_x); + pp_string + (pp, + " style=\"fill:none;stroke: #0088ce\"\n" + " marker-end=\"url(#arrowhead)\"/>\n" + " </svg>\n" + "</div>\n\n"); + xp.add_raw (pp_formatted_text (pp)); +} + /* A range of consecutive events within a diagnostic_path, all within the same thread, and with the same fndecl and stack_depth, and which are suitable to print with a single call to diagnostic_show_locus. */ @@ -468,9 +590,9 @@ struct event_range /* Print the events in this range to PP, typically as a single call to diagnostic_show_locus. */ - void print (pretty_printer &pp, - diagnostic_text_output_format &text_output, - diagnostic_source_effect_info *effect_info) + void print_as_text (pretty_printer &pp, + diagnostic_text_output_format &text_output, + diagnostic_source_effect_info *effect_info) { location_t initial_loc = m_initial_event.get_location (); @@ -487,7 +609,7 @@ struct event_range if (exploc.file != LOCATION_FILE (dc.m_last_location)) { diagnostic_location_print_policy loc_policy (text_output); - diagnostic_start_span (&dc) (loc_policy, &pp, exploc); + loc_policy.print_text_span_start (dc, pp, exploc); } } @@ -524,6 +646,65 @@ struct event_range } } + /* Print the events in this range to XP, typically as a single + call to diagnostic_show_locus_as_html. */ + + void print_as_html (xml::printer &xp, + diagnostic_context &dc, + diagnostic_source_effect_info *effect_info, + html_label_writer *event_label_writer) + { + location_t initial_loc = m_initial_event.get_location (); + + /* Emit a span indicating the filename (and line/column) if the + line has changed relative to the last call to + diagnostic_show_locus. */ + if (dc.m_source_printing.enabled) + { + expanded_location exploc + = linemap_client_expand_location_to_spelling_point + (line_table, initial_loc, LOCATION_ASPECT_CARET); + if (exploc.file != LOCATION_FILE (dc.m_last_location)) + { + diagnostic_location_print_policy loc_policy (dc); + loc_policy.print_html_span_start (dc, xp, exploc); + } + } + + /* If we have an UNKNOWN_LOCATION (or BUILTINS_LOCATION) as the + primary location for an event, diagnostic_show_locus_as_html won't print + anything. + + In particular the label for the event won't get printed. + Fail more gracefully in this case by showing the event + index and text, at no particular location. */ + if (get_pure_location (initial_loc) <= BUILTINS_LOCATION) + { + for (unsigned i = m_start_idx; i <= m_end_idx; i++) + { + const diagnostic_event &iter_event = m_path.get_event (i); + diagnostic_event_id_t event_id (i); + pretty_printer pp; + pp_printf (&pp, " %@: ", &event_id); + iter_event.print_desc (pp); + if (event_label_writer) + event_label_writer->begin_label (); + xp.add_text (pp_formatted_text (&pp)); + if (event_label_writer) + event_label_writer->end_label (); + } + return; + } + + /* Call diagnostic_show_locus_as_html to show the source, + showing events using labels. */ + diagnostic_show_locus_as_html (&dc, dc.m_source_printing, + &m_richloc, DK_DIAGNOSTIC_PATH, xp, + effect_info, event_label_writer); + + // TODO: show macro expansions + } + const diagnostic_path &m_path; const diagnostic_event &m_initial_event; logical_location m_logical_loc; @@ -700,11 +881,11 @@ public: } void - print_swimlane_for_event_range (diagnostic_text_output_format &text_output, - pretty_printer *pp, - const logical_location_manager &logical_loc_mgr, - event_range *range, - diagnostic_source_effect_info *effect_info) + print_swimlane_for_event_range_as_text (diagnostic_text_output_format &text_output, + pretty_printer *pp, + const logical_location_manager &logical_loc_mgr, + event_range *range, + diagnostic_source_effect_info *effect_info) { gcc_assert (pp); const char *const line_color = "path"; @@ -785,7 +966,7 @@ public: } pp_set_prefix (pp, prefix); pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; - range->print (*pp, text_output, effect_info); + range->print_as_text (*pp, text_output, effect_info); pp_set_prefix (pp, saved_prefix); write_indent (pp, m_cur_indent + per_frame_indent); @@ -795,7 +976,7 @@ public: pp_newline (pp); } else - range->print (*pp, text_output, effect_info); + range->print_as_text (*pp, text_output, effect_info); if (const event_range *next_range = get_any_next_range ()) { @@ -859,6 +1040,17 @@ public: m_num_printed++; } + void + print_swimlane_for_event_range_as_html (diagnostic_context &dc, + xml::printer &xp, + html_label_writer *event_label_writer, + event_range *range, + diagnostic_source_effect_info *effect_info) + { + range->print_as_html (xp, dc, effect_info, event_label_writer); + m_num_printed++; + } + int get_cur_indent () const { return m_cur_indent; } private: @@ -945,11 +1137,146 @@ print_path_summary_as_text (const path_summary &ps, of this range. */ diagnostic_source_effect_info effect_info; effect_info.m_leading_in_edge_column = last_out_edge_column; - tep.print_swimlane_for_event_range (text_output, pp, - ps.get_logical_location_manager (), - range, &effect_info); + tep.print_swimlane_for_event_range_as_text + (text_output, pp, + ps.get_logical_location_manager (), + range, &effect_info); + last_out_edge_column = effect_info.m_trailing_out_edge_column; + } +} + +/* Print PS as HTML to XP, using DC and, if non-null EVENT_LABEL_WRITER. */ + +static void +print_path_summary_as_html (const path_summary &ps, + diagnostic_context &dc, + xml::printer &xp, + html_label_writer *event_label_writer, + bool show_depths) +{ + std::vector<thread_event_printer> thread_event_printers; + for (auto t : ps.m_per_thread_summary) + thread_event_printers.push_back (thread_event_printer (*t, show_depths)); + + const logical_location_manager *logical_loc_mgr + = dc.get_logical_location_manager (); + + xp.push_tag_with_class ("div", "event-ranges", false); + + /* Group the ranges into stack frames. */ + std::unique_ptr<stack_frame> curr_frame; + unsigned i; + event_range *range; + int last_out_edge_column = -1; + FOR_EACH_VEC_ELT (ps.m_ranges, i, range) + { + const int swimlane_idx + = range->m_per_thread_summary.get_swimlane_index (); + + const logical_location this_logical_loc = range->m_logical_loc; + const int this_depth = range->m_stack_depth; + if (curr_frame) + { + int old_stack_depth = curr_frame->m_stack_depth; + if (this_depth > curr_frame->m_stack_depth) + { + emit_svg_arrow (xp, old_stack_depth, this_depth); + curr_frame + = begin_html_stack_frame (xp, + std::move (curr_frame), + range->m_logical_loc, + range->m_stack_depth, + logical_loc_mgr); + } + else + { + while (this_depth < curr_frame->m_stack_depth + || this_logical_loc != curr_frame->m_logical_loc) + { + curr_frame = end_html_stack_frame (xp, std::move (curr_frame)); + if (curr_frame == NULL) + { + curr_frame + = begin_html_stack_frame (xp, + nullptr, + range->m_logical_loc, + range->m_stack_depth, + logical_loc_mgr); + break; + } + } + emit_svg_arrow (xp, old_stack_depth, this_depth); + } + } + else + { + curr_frame = begin_html_stack_frame (xp, + NULL, + range->m_logical_loc, + range->m_stack_depth, + logical_loc_mgr); + } + + xp.push_tag_with_class ("table", "event-range-with-margin", false); + xp.push_tag ("tr", false); + xp.push_tag_with_class ("td", "event-range", false); + xp.push_tag_with_class ("div", "events-hdr", true); + if (range->m_logical_loc) + { + gcc_assert (logical_loc_mgr); + label_text funcname + = logical_loc_mgr->get_name_for_path_output (range->m_logical_loc); + if (funcname.get ()) + { + xp.push_tag_with_class ("span", "funcname", true); + xp.add_text (funcname.get ()); + xp.pop_tag (); //span + xp.add_text (": "); + } + } + { + xp.push_tag_with_class ("span", "event-ids", true); + pretty_printer pp; + if (range->m_start_idx == range->m_end_idx) + pp_printf (&pp, "event %i", + range->m_start_idx + 1); + else + pp_printf (&pp, "events %i-%i", + range->m_start_idx + 1, range->m_end_idx + 1); + xp.add_text (pp_formatted_text (&pp)); + xp.pop_tag (); // span + } + if (show_depths) + { + xp.add_text (" "); + xp.push_tag_with_class ("span", "depth", true); + pretty_printer pp; + pp_printf (&pp, "(depth %i)", range->m_stack_depth); + xp.add_text (pp_formatted_text (&pp)); + xp.pop_tag (); //span + } + xp.pop_tag (); // div + + /* Print a run of events. */ + thread_event_printer &tep = thread_event_printers[swimlane_idx]; + /* Wire up any trailing out-edge from previous range to leading in-edge + of this range. */ + diagnostic_source_effect_info effect_info; + effect_info.m_leading_in_edge_column = last_out_edge_column; + tep.print_swimlane_for_event_range_as_html (dc, xp, event_label_writer, + range, &effect_info); last_out_edge_column = effect_info.m_trailing_out_edge_column; + + xp.pop_tag (); // td + xp.pop_tag (); // tr + xp.pop_tag (); // table } + + /* Close outstanding frames. */ + while (curr_frame) + curr_frame = end_html_stack_frame (xp, std::move (curr_frame)); + + xp.pop_tag (); // div } } /* end of anonymous namespace for path-printing code. */ @@ -1049,6 +1376,32 @@ diagnostic_text_output_format::print_path (const diagnostic_path &path) } } +/* Print PATH as HTML to XP, using DC and DSPP for settings. + If non-null, use EVENT_LABEL_WRITER when writing events. */ + +void +print_path_as_html (xml::printer &xp, + const diagnostic_path &path, + diagnostic_context &dc, + html_label_writer *event_label_writer, + const diagnostic_source_print_policy &dspp) +{ + path_print_policy policy (dc); + const bool check_rich_locations = true; + const bool colorize = false; + const diagnostic_source_printing_options &source_printing_opts + = dspp.get_options (); + const bool show_event_links = source_printing_opts.show_event_links_p; + path_summary summary (policy, + *dc.get_reference_printer (), + path, + check_rich_locations, + colorize, + show_event_links); + print_path_summary_as_html (summary, dc, xp, event_label_writer, + dc.show_path_depths_p ()); +} + #if CHECKING_P namespace selftest { diff --git a/gcc/diagnostic-show-locus.cc b/gcc/diagnostic-show-locus.cc index 898efe7..397fffb 100644 --- a/gcc/diagnostic-show-locus.cc +++ b/gcc/diagnostic-show-locus.cc @@ -19,6 +19,8 @@ along with GCC; see the file COPYING3. If not see <http://www.gnu.org/licenses/>. */ #include "config.h" +#define INCLUDE_MAP +#define INCLUDE_STRING #define INCLUDE_VECTOR #include "system.h" #include "coretypes.h" @@ -37,6 +39,8 @@ along with GCC; see the file COPYING3. If not see #include "text-art/types.h" #include "text-art/theme.h" #include "diagnostic-label-effects.h" +#include "xml.h" +#include "xml-printer.h" #ifdef HAVE_TERMIOS_H # include <termios.h> @@ -70,7 +74,8 @@ struct point_state bool draw_caret_p; }; -/* A class to inject colorization codes when printing the diagnostic locus. +/* A class to inject colorization codes when printing the diagnostic locus + as text. It has one kind of colorization for each of: - normal text @@ -369,17 +374,348 @@ struct char_display_policy : public cpp_char_column_policy public: char_display_policy (int tabstop, int (*width_cb) (cppchar_t c), - void (*print_cb) (pretty_printer *pp, - const cpp_decoded_char &cp)) + void (*print_text_cb) (to_text &sink, + const cpp_decoded_char &cp), + void (*print_html_cb) (to_html &sink, + const cpp_decoded_char &cp)) : cpp_char_column_policy (tabstop, width_cb), - m_print_cb (print_cb) + m_print_text_cb (print_text_cb), + m_print_html_cb (print_html_cb) { } - void (*m_print_cb) (pretty_printer *pp, - const cpp_decoded_char &cp); + void (*m_print_text_cb) (to_text &sink, + const cpp_decoded_char &cp); + void (*m_print_html_cb) (to_html &sink, + const cpp_decoded_char &cp); }; +template <typename Sink> class layout_printer; + +} // anonymous namespace + +/* This code is written generically to write either: + - text, to a pretty_printer, potentially with colorization codes, or + - html, to an xml::printer, with nested HTML tags. + + This is handled via a "Sink" template, which is either to_text + or to_html. */ + +/* Writing text output. */ + +struct to_text +{ + friend class layout_printer<to_text>; + + to_text (pretty_printer &pp, + colorizer &colorizer) + : m_pp (pp), + m_colorizer (&colorizer) + { + m_saved_rule = pp_prefixing_rule (&m_pp); + pp_prefixing_rule (&m_pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + } + to_text (pretty_printer &pp, + colorizer *colorizer) + : m_pp (pp), + m_colorizer (colorizer) + { + m_saved_rule = pp_prefixing_rule (&m_pp); + pp_prefixing_rule (&m_pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + } + ~to_text () + { + + pp_prefixing_rule (&m_pp) = m_saved_rule; + } + + static bool is_text () { return true; } + static bool is_html () { return false; } + + void emit_text_prefix () + { + pp_emit_prefix (&m_pp); + } + + void push_html_tag (std::string, bool) + { + // no-op for text + } + void push_html_tag_with_class (std::string, std::string, bool) + { + // no-op for text + } + void pop_html_tag (std::string) + { + // no-op for text + } + + void add_html_tag_with_class (std::string, std::string, bool) + { + // no-op for text + } + + void add_space () + { + pp_space (&m_pp); + } + + void add_character (cppchar_t ch) + { + pp_unicode_character (&m_pp, ch); + } + + void add_utf8_byte (char b) + { + pp_character (&m_pp, b); + } + + void add_text (const char *text) + { + pp_string (&m_pp, text); + } + + void print_decoded_char (const char_display_policy &char_policy, + cpp_decoded_char cp) + { + char_policy.m_print_text_cb (*this, cp); + } + + void colorize_text_ensure_normal () + { + gcc_assert (m_colorizer); + m_colorizer->set_normal_text (); + } + + void colorize_text_for_range_idx (int range_idx) + { + gcc_assert (m_colorizer); + m_colorizer->set_range (range_idx); + } + + void colorize_text_for_cfg_edge () + { + gcc_assert (m_colorizer); + m_colorizer->set_cfg_edge (); + } + + void colorize_text_for_fixit_insert () + { + gcc_assert (m_colorizer); + m_colorizer->set_fixit_insert (); + } + + void colorize_text_for_fixit_delete () + { + gcc_assert (m_colorizer); + m_colorizer->set_fixit_delete (); + } + + void + invoke_start_span_fn (const diagnostic_source_print_policy &source_policy, + const diagnostic_location_print_policy &loc_policy, + const expanded_location &exploc) + { + source_policy.get_text_start_span_fn () (loc_policy, *this, exploc); + } + + // Text-specific functions + void add_newline () + { + pp_newline (&m_pp); + } + + pretty_printer &m_pp; +private: + colorizer *m_colorizer; + diagnostic_prefixing_rule_t m_saved_rule; +}; + +/* Writing HTML output. */ + +struct to_html +{ + friend class layout_printer<to_html>; + + to_html (xml::printer &xp, + html_label_writer *html_label_writer) + : m_xp (xp), + m_html_label_writer (html_label_writer) + {} + + static bool is_text () { return false; } + static bool is_html () { return true; } + + void emit_text_prefix () + { + // no-op for HTML + } + + void push_html_tag (std::string name, + bool preserve_whitespace) + { + m_xp.push_tag (std::move (name), preserve_whitespace); + } + + void push_html_tag_with_class (std::string name, + std::string class_, + bool preserve_whitespace) + { + m_xp.push_tag_with_class (std::move (name), + std::move (class_), + preserve_whitespace); + } + + void pop_html_tag (std::string /*name*/) + { + m_xp.pop_tag (); + } + + void add_html_tag_with_class (std::string name, + std::string class_, + bool preserve_whitespace) + { + auto element = std::make_unique<xml::element> (std::move (name), + preserve_whitespace); + element->set_attr ("class", std::move (class_)); + m_xp.append (std::move (element)); + } + + void add_raw_html (const char *src) + { + m_xp.add_raw (src); + } + + void add_space () + { + m_xp.add_text (" "); + } + + void add_character (cppchar_t ch) + { + pp_clear_output_area (&m_scratch_pp); + pp_unicode_character (&m_scratch_pp, ch); + m_xp.add_text (pp_formatted_text (&m_scratch_pp)); + } + + void add_utf8_byte (char b) + { + m_xp.add_text (std::string (1, b)); + } + + void add_text (const char *text) + { + m_xp.add_text (text); + } + + void print_decoded_char (const char_display_policy &char_policy, + cpp_decoded_char cp) + { + char_policy.m_print_html_cb (*this, cp); + } + + void colorize_text_ensure_normal () + { + // no-op for HTML + } + + void colorize_text_for_range_idx (int) + { + // no-op for HTML + } + + void colorize_text_for_cfg_edge () + { + // no-op for HTML + } + + void colorize_text_for_fixit_insert () + { + // no-op for HTML + } + + void colorize_text_for_fixit_delete () + { + // no-op for HTML + } + + void + invoke_start_span_fn (const diagnostic_source_print_policy &source_policy, + const diagnostic_location_print_policy &loc_policy, + const expanded_location &exploc) + { + source_policy.get_html_start_span_fn () (loc_policy, *this, exploc); + } + + xml::printer &m_xp; +private: + html_label_writer *m_html_label_writer; + pretty_printer m_scratch_pp; +}; + +void +diagnostic_location_print_policy:: +print_text_span_start (const diagnostic_context &dc, + pretty_printer &pp, + const expanded_location &exploc) +{ + to_text sink (pp, nullptr); + diagnostic_source_print_policy source_policy (dc); + source_policy.get_text_start_span_fn () (*this, sink, exploc); +} + +void +diagnostic_location_print_policy:: +print_html_span_start (const diagnostic_context &dc, + xml::printer &xp, + const expanded_location &exploc) +{ + to_html sink (xp, nullptr); + diagnostic_source_print_policy source_policy (dc); + source_policy.get_html_start_span_fn () (*this, sink, exploc); +} + +pretty_printer * +get_printer (to_text &sink) +{ + return &sink.m_pp; +} + +template<> +void +default_diagnostic_start_span_fn<to_text> (const diagnostic_location_print_policy &loc_policy, + to_text &sink, + expanded_location exploc) +{ + const diagnostic_column_policy &column_policy + = loc_policy.get_column_policy (); + label_text text + = column_policy.get_location_text (exploc, + loc_policy.show_column_p (), + pp_show_color (&sink.m_pp)); + pp_string (&sink.m_pp, text.get ()); + pp_newline (&sink.m_pp); +} + +template<> +void +default_diagnostic_start_span_fn<to_html> (const diagnostic_location_print_policy &loc_policy, + to_html &sink, + expanded_location exploc) +{ + const diagnostic_column_policy &column_policy + = loc_policy.get_column_policy (); + label_text text + = column_policy.get_location_text (exploc, + loc_policy.show_column_p (), + false); + sink.m_xp.push_tag_with_class ("span", "location", true); + sink.m_xp.add_text (text.get ()); + sink.m_xp.pop_tag (); // span +} + +namespace { + /* A class to control the overall layout when printing a diagnostic. The layout is determined within the constructor. @@ -392,7 +728,8 @@ struct char_display_policy : public cpp_char_column_policy class layout { public: - friend class layout_printer; + friend class layout_printer<to_text>; + friend class layout_printer<to_html>; layout (const diagnostic_source_print_policy &source_policy, const rich_location &richloc, @@ -458,16 +795,24 @@ class layout bool m_escape_on_output; }; -/* A bundle of state for printing a particular layout - to a particular pretty_printer. */ +class line_label; +enum class margin_kind +{ + normal, + insertion, + ruler +}; + +/* A bundle of state for printing a particular layout + to a particular Sink (either to_text or to_html). */ +template <typename Sink> class layout_printer { public: - layout_printer (pretty_printer &pp, + layout_printer (Sink &sink, const layout &layout, - const rich_location &richloc, - diagnostic_t diagnostic_kind); + bool is_diagnostic_path); void print (const diagnostic_source_print_policy &source_policy); @@ -485,11 +830,13 @@ private: line_bounds print_source_line (linenum_type row, const char *line, int line_bytes); void print_leftmost_column (); - void start_annotation_line (char margin_char = ' '); + void start_annotation_line (enum margin_kind); + void end_line (); void print_annotation_line (linenum_type row, const line_bounds lbounds); void print_any_labels (linenum_type row); + void begin_label (const line_label &label); + void end_label (); void print_trailing_fixits (linenum_type row); - void print_newline (); void move_to_column (int *column, int dest_column, bool add_left_margin); @@ -497,9 +844,8 @@ private: void print_any_right_to_left_edge_lines (); private: - pretty_printer &m_pp; + Sink &m_sink; const layout &m_layout; - colorizer m_colorizer; bool m_is_diagnostic_path; /* Fields for handling links between labels (e.g. for showing CFG edges @@ -1178,8 +1524,9 @@ fixit_cmp (const void *p_a, const void *p_b) /* Callback for char_display_policy::m_print_cb for printing source chars when not escaping the source. */ +template <class Sink> static void -default_print_decoded_ch (pretty_printer *pp, +default_print_decoded_ch (Sink &sink, const cpp_decoded_char &decoded_ch) { for (const char *ptr = decoded_ch.m_start_byte; @@ -1187,11 +1534,11 @@ default_print_decoded_ch (pretty_printer *pp, { if (*ptr == '\0' || *ptr == '\r') { - pp_space (pp); + sink.add_space (); continue; } - pp_character (pp, *ptr); + sink.add_utf8_byte (*ptr); } } @@ -1219,8 +1566,9 @@ escape_as_bytes_width (cppchar_t ch) /* Callback for char_display_policy::m_print_cb for printing source chars when escaping with DIAGNOSTICS_ESCAPE_FORMAT_BYTES. */ +template <typename Sink> static void -escape_as_bytes_print (pretty_printer *pp, +escape_as_bytes_print (Sink &sink, const cpp_decoded_char &decoded_ch) { if (!decoded_ch.m_valid_ch) @@ -1230,14 +1578,14 @@ escape_as_bytes_print (pretty_printer *pp, { char buf[16]; sprintf (buf, "<%02x>", (unsigned char)*iter); - pp_string (pp, buf); + sink.add_text (buf); } return; } cppchar_t ch = decoded_ch.m_ch; if (ch < 0x80 && ISPRINT (ch)) - pp_character (pp, ch); + sink.add_character (ch); else { for (const char *iter = decoded_ch.m_start_byte; @@ -1245,7 +1593,7 @@ escape_as_bytes_print (pretty_printer *pp, { char buf[16]; sprintf (buf, "<%02x>", (unsigned char)*iter); - pp_string (pp, buf); + sink.add_text (buf); } } } @@ -1275,24 +1623,25 @@ escape_as_unicode_width (cppchar_t ch) /* Callback for char_display_policy::m_print_cb for printing source chars when escaping with DIAGNOSTICS_ESCAPE_FORMAT_UNICODE. */ +template <typename Sink> static void -escape_as_unicode_print (pretty_printer *pp, +escape_as_unicode_print (Sink &sink, const cpp_decoded_char &decoded_ch) { if (!decoded_ch.m_valid_ch) { - escape_as_bytes_print (pp, decoded_ch); + escape_as_bytes_print<Sink> (sink, decoded_ch); return; } cppchar_t ch = decoded_ch.m_ch; if (ch < 0x80 && ISPRINT (ch)) - pp_character (pp, ch); + sink.add_character (ch); else { char buf[16]; sprintf (buf, "<U+%04X>", ch); - pp_string (pp, buf); + sink.add_text (buf); } } @@ -1306,7 +1655,8 @@ make_char_policy (const diagnostic_source_print_policy &source_policy, char_display_policy result (source_policy.get_column_policy ().get_tabstop (), cpp_wcwidth, - default_print_decoded_ch); + default_print_decoded_ch<to_text>, + default_print_decoded_ch<to_html>); /* If the diagnostic suggests escaping non-ASCII bytes, then use policy from user-supplied options. */ @@ -1319,11 +1669,13 @@ make_char_policy (const diagnostic_source_print_policy &source_policy, gcc_unreachable (); case DIAGNOSTICS_ESCAPE_FORMAT_UNICODE: result.m_width_cb = escape_as_unicode_width; - result.m_print_cb = escape_as_unicode_print; + result.m_print_text_cb = escape_as_unicode_print<to_text>; + result.m_print_html_cb = escape_as_unicode_print<to_html>; break; case DIAGNOSTICS_ESCAPE_FORMAT_BYTES: result.m_width_cb = escape_as_bytes_width; - result.m_print_cb = escape_as_bytes_print; + result.m_print_text_cb = escape_as_bytes_print<to_text>; + result.m_print_html_cb = escape_as_bytes_print<to_html>; break; } } @@ -1530,17 +1882,32 @@ layout::will_show_line_p (linenum_type row) const /* Print a line showing a gap in the line numbers, for showing the boundary between two line spans. */ +template<> void -layout_printer::print_gap_in_line_numbering () +layout_printer<to_text>::print_gap_in_line_numbering () { gcc_assert (m_layout.m_options.show_line_numbers_p); - pp_emit_prefix (&m_pp); + m_sink.emit_text_prefix (); for (int i = 0; i < m_layout.get_linenum_width () + 1; i++) - pp_character (&m_pp, '.'); + m_sink.add_character ('.'); - pp_newline (&m_pp); + m_sink.add_newline (); +} + +template<> +void +layout_printer<to_html>::print_gap_in_line_numbering () +{ + gcc_assert (m_layout.m_options.show_line_numbers_p); + + m_sink.add_raw_html + ("<tbody class=\"line-span-jump\">\n" + "<tr class=\"line-span-jump-row\">" + "<td class=\"linenum-gap\">[...]</td>" + "<td class=\"source-gap\"/></tr>\n" + "</tbody>\n"); } /* Return true iff we should print a heading when starting the @@ -1854,21 +2221,32 @@ layout::calculate_x_offset_display () colorization and tab expansion, this function tracks the line position in both byte and display column units. */ +template<typename Sink> line_bounds -layout_printer::print_source_line (linenum_type row, const char *line, int line_bytes) +layout_printer<Sink>::print_source_line (linenum_type row, + const char *line, + int line_bytes) { - m_colorizer.set_normal_text (); - - pp_emit_prefix (&m_pp); + m_sink.colorize_text_ensure_normal (); + m_sink.push_html_tag ("tr", true); + m_sink.emit_text_prefix (); if (m_layout.m_options.show_line_numbers_p) { + m_sink.push_html_tag_with_class ("td", "linenum", true); int width = num_digits (row); for (int i = 0; i < m_layout.get_linenum_width () - width; i++) - pp_space (&m_pp); - pp_printf (&m_pp, "%i |", row); + m_sink.add_space (); + char buf[20]; + sprintf (buf, "%i", row); + m_sink.add_text (buf); + if (Sink::is_text ()) + m_sink.add_text (" |"); + m_sink.pop_html_tag ("td"); } + m_sink.push_html_tag_with_class ("td", "left-margin", true); print_leftmost_column (); + m_sink.pop_html_tag ("td"); /* We will stop printing the source line at any trailing whitespace. */ line_bytes = get_line_bytes_without_trailing_whitespace (line, @@ -1879,6 +2257,8 @@ layout_printer::print_source_line (linenum_type row, const char *line, int line_ tab expansion, and for implementing m_x_offset_display. */ cpp_display_width_computation dw (line, line_bytes, m_layout.m_char_policy); + m_sink.push_html_tag_with_class ("td", "source", true); + /* Skip the first m_x_offset_display display columns. In case the leading portion that will be skipped ends with a character with wcwidth > 1, then it is possible we skipped too much, so account for that by padding with @@ -1889,7 +2269,7 @@ layout_printer::print_source_line (linenum_type row, const char *line, int line_ for (int skipped_display_cols = dw.advance_display_cols (m_layout.m_x_offset_display); skipped_display_cols > m_layout.m_x_offset_display; --skipped_display_cols) - pp_space (&m_pp); + m_sink.add_space (); /* Print the line and compute the line_bounds. */ line_bounds lbounds; @@ -1917,9 +2297,9 @@ layout_printer::print_source_line (linenum_type row, const char *line, int line_ CU_BYTES, &state); if (in_range_p) - m_colorizer.set_range (state.range_idx); + m_sink.colorize_text_for_range_idx (state.range_idx); else - m_colorizer.set_normal_text (); + m_sink.colorize_text_ensure_normal (); } /* Get the display width of the next character to be output, expanding @@ -1933,7 +2313,7 @@ layout_printer::print_source_line (linenum_type row, const char *line, int line_ /* The returned display width is the number of spaces into which the tab should be expanded. */ for (int i = 0; i != this_display_width; ++i) - pp_space (&m_pp); + m_sink.add_space (); continue; } @@ -1947,10 +2327,10 @@ layout_printer::print_source_line (linenum_type row, const char *line, int line_ } /* Output the character. */ - m_layout.m_char_policy.m_print_cb (&m_pp, cp); + m_sink.print_decoded_char (m_layout.m_char_policy, cp); c = dw.next_byte (); } - print_newline (); + end_line (); return lbounds; } @@ -1975,8 +2355,9 @@ layout::should_print_annotation_line_p (linenum_type row) const /* Print the leftmost column after the margin, which is used for showing links between labels (e.g. for CFG edges in execution paths). */ +template<typename Sink> void -layout_printer::print_leftmost_column () +layout_printer<Sink>::print_leftmost_column () { if (!get_options ().show_event_links_p) gcc_assert (m_link_lhs_state == link_lhs_state::none); @@ -1986,76 +2367,124 @@ layout_printer::print_leftmost_column () default: gcc_unreachable (); case link_lhs_state::none: - pp_space (&m_pp); + m_sink.add_space (); break; case link_lhs_state::rewinding_to_lhs: { - m_colorizer.set_cfg_edge (); + m_sink.colorize_text_for_cfg_edge (); const cppchar_t ch = get_theme ().get_cppchar (text_art::theme::cell_kind::CFG_FROM_LEFT_TO_DOWN); - pp_unicode_character (&m_pp, ch); - m_colorizer.set_normal_text (); + m_sink.add_character (ch); + m_sink.colorize_text_ensure_normal (); } break; case link_lhs_state::at_lhs: { - m_colorizer.set_cfg_edge (); + m_sink.colorize_text_for_cfg_edge (); const cppchar_t ch = get_theme ().get_cppchar (text_art::theme::cell_kind::CFG_DOWN); - pp_unicode_character (&m_pp, ch); - m_colorizer.set_normal_text (); + m_sink.add_character (ch); + m_sink.colorize_text_ensure_normal (); } break; case link_lhs_state::indenting_to_dest: { - m_colorizer.set_cfg_edge (); + m_sink.colorize_text_for_cfg_edge (); const cppchar_t ch = get_theme ().get_cppchar (text_art::theme::cell_kind::CFG_FROM_DOWN_TO_RIGHT); - pp_unicode_character (&m_pp, ch); - m_colorizer.set_normal_text (); + m_sink.add_character (ch); + m_sink.colorize_text_ensure_normal (); } break; } } -/* Begin an annotation line. If m_show_line_numbers_p, print the left - margin, which is empty for annotation lines. +/* Begin an annotation line for either text or html output + + If m_show_line_numbers_p, print the left margin, which is empty + for annotation lines. After any left margin, print a leftmost column, which is used for - showing links between labels (e.g. for CFG edges in execution paths). */ + showing links between labels (e.g. for CFG edges in execution paths). + + For text sinks, this also first prints the text prefix. + For html sinks, this also pushes <tr> and <td> open tags, where the + <td> is for the coming annotations. */ +template<typename Sink> void -layout_printer::start_annotation_line (char margin_char) +layout_printer<Sink>::start_annotation_line (enum margin_kind margin) { - pp_emit_prefix (&m_pp); + m_sink.emit_text_prefix (); + m_sink.push_html_tag ("tr", true); + + char margin_char = (margin == margin_kind::insertion + ? '+' + : ' '); + if (get_options ().show_line_numbers_p) { /* Print the margin. If MARGIN_CHAR != ' ', then print up to 3 of it, right-aligned, padded with spaces. */ + m_sink.push_html_tag_with_class ("td", "linenum", true); int i; for (i = 0; i < m_layout.m_linenum_width - 3; i++) - pp_space (&m_pp); + m_sink.add_space (); for (; i < m_layout.m_linenum_width; i++) - pp_character (&m_pp, margin_char); - pp_string (&m_pp, " |"); + m_sink.add_character (margin_char); + if (Sink::is_text ()) + m_sink.add_text (" |"); + m_sink.pop_html_tag ("td"); } - if (margin_char == ' ') - print_leftmost_column (); + + m_sink.push_html_tag_with_class ("td", "left-margin", true); + if (margin == margin_kind::insertion) + m_sink.add_character (margin_char); else - pp_character (&m_pp, margin_char); + print_leftmost_column (); + m_sink.pop_html_tag ("td"); + + m_sink.push_html_tag_with_class ("td", + (margin == margin_kind::ruler + ? "ruler" + : "annotation"), + true); +} + +/* End a source or annotation line: text implementation. + Reset any colorization and emit a newline. */ + +template<> +void +layout_printer<to_text>::end_line () +{ + m_sink.colorize_text_ensure_normal (); + m_sink.add_newline (); +} + +/* End a source or annotation line: HTML implementation. + Close the <td> and <tr> tags. */ + +template<> +void +layout_printer<to_html>::end_line () +{ + m_sink.pop_html_tag ("td"); + m_sink.pop_html_tag ("tr"); } /* Print a line consisting of the caret/underlines for the given source line. */ +template<typename Sink> void -layout_printer::print_annotation_line (linenum_type row, - const line_bounds lbounds) +layout_printer<Sink>::print_annotation_line (linenum_type row, + const line_bounds lbounds) { int x_bound = m_layout.get_x_bound_for_row (row, m_layout.m_exploc.m_display_col, lbounds.m_last_non_ws_disp_col); - start_annotation_line (); + start_annotation_line (margin_kind::normal); for (int column = 1 + m_layout.m_x_offset_display; column < x_bound; column++) { @@ -2069,7 +2498,7 @@ layout_printer::print_annotation_line (linenum_type row, if (in_range_p) { /* Within a range. Draw either the caret or an underline. */ - m_colorizer.set_range (state.range_idx); + m_sink.colorize_text_for_range_idx (state.range_idx); if (state.draw_caret_p) { /* Draw the caret. */ @@ -2078,19 +2507,20 @@ layout_printer::print_annotation_line (linenum_type row, caret_char = get_options ().caret_chars[state.range_idx]; else caret_char = '^'; - pp_character (&m_pp, caret_char); + m_sink.add_character (caret_char); } else - pp_character (&m_pp, '~'); + m_sink.add_character ('~'); } else { /* Not in a range. */ - m_colorizer.set_normal_text (); - pp_character (&m_pp, ' '); + m_sink.colorize_text_ensure_normal (); + m_sink.add_character (' '); } } - print_newline (); + + end_line (); } /* A version of label_text that can live inside a vec. @@ -2126,11 +2556,13 @@ struct pod_label_text class line_label { public: - line_label (int state_idx, int column, + line_label (unsigned original_range_idx, + int state_idx, int column, label_text text, bool has_in_edge, bool has_out_edge) - : m_state_idx (state_idx), m_column (column), + : m_original_range_idx (original_range_idx), + m_state_idx (state_idx), m_column (column), m_text (std::move (text)), m_label_line (0), m_has_vbar (true), m_has_in_edge (has_in_edge), m_has_out_edge (has_out_edge) @@ -2158,6 +2590,7 @@ public: return -compare (ll1->m_state_idx, ll2->m_state_idx); } + unsigned m_original_range_idx; int m_state_idx; int m_column; pod_label_text m_text; @@ -2168,9 +2601,43 @@ public: bool m_has_out_edge; }; +template<> +void +layout_printer<to_text>::begin_label (const line_label &label) +{ + /* Colorize the text, unless it's for events in a + diagnostic_path. */ + if (!m_is_diagnostic_path) + m_sink.colorize_text_for_range_idx (label.m_state_idx); +} + +template<> +void +layout_printer<to_html>::begin_label (const line_label &) +{ + if (m_sink.m_html_label_writer) + m_sink.m_html_label_writer->begin_label (); +} + +template<> +void +layout_printer<to_text>::end_label () +{ + m_sink.colorize_text_ensure_normal (); +} + +template<> +void +layout_printer<to_html>::end_label () +{ + if (m_sink.m_html_label_writer) + m_sink.m_html_label_writer->end_label (); +} + /* Print any labels in this row. */ +template <typename Sink> void -layout_printer::print_any_labels (linenum_type row) +layout_printer<Sink>::print_any_labels (linenum_type row) { int i; auto_vec<line_label> labels; @@ -2203,7 +2670,8 @@ layout_printer::print_any_labels (linenum_type row) if (text.get () == NULL) continue; - labels.safe_push (line_label (i, disp_col, std::move (text), + labels.safe_push (line_label (range->m_original_idx, + i, disp_col, std::move (text), range->has_in_edge (), range->has_out_edge ())); } @@ -2288,7 +2756,7 @@ layout_printer::print_any_labels (linenum_type row) gcc_assert (get_options ().show_event_links_p); m_link_lhs_state = link_lhs_state::indenting_to_dest; } - start_annotation_line (); + start_annotation_line (margin_kind::normal); int column = 1 + m_layout.m_x_offset_display; line_label *label; @@ -2310,32 +2778,31 @@ layout_printer::print_any_labels (linenum_type row) . ^~~~~~~~~~~~~ . this text. */ gcc_assert (get_options ().show_event_links_p); - m_colorizer.set_cfg_edge (); + m_sink.colorize_text_for_cfg_edge (); const cppchar_t right= get_theme ().get_cppchar (text_art::theme::cell_kind::CFG_RIGHT); while (column < label->m_column - 1) { - pp_unicode_character (&m_pp, right); + m_sink.add_character (right); column++; } if (column == label->m_column - 1) { - pp_character (&m_pp, '>'); + m_sink.add_character ('>'); column++; } - m_colorizer.set_normal_text (); + m_sink.colorize_text_ensure_normal (); m_link_lhs_state = link_lhs_state::none; label_line_with_in_edge = -1; } else move_to_column (&column, label->m_column, true); gcc_assert (column == label->m_column); - /* Colorize the text, unless it's for events in a - diagnostic_path. */ - if (!m_is_diagnostic_path) - m_colorizer.set_range (label->m_state_idx); - pp_string (&m_pp, label->m_text.m_buffer); - m_colorizer.set_normal_text (); + + begin_label (*label); + m_sink.add_text (label->m_text.m_buffer); + end_label (); + column += label->m_display_width; if (get_options ().show_event_links_p && label->m_has_out_edge) { @@ -2350,13 +2817,13 @@ layout_printer::print_any_labels (linenum_type row) (text_art::theme::cell_kind::CFG_RIGHT); const cppchar_t from_right_to_down= get_theme ().get_cppchar (text_art::theme::cell_kind::CFG_FROM_RIGHT_TO_DOWN); - m_colorizer.set_cfg_edge (); - pp_space (&m_pp); - pp_unicode_character (&m_pp, right); - pp_unicode_character (&m_pp, '>'); - pp_unicode_character (&m_pp, right); - pp_unicode_character (&m_pp, from_right_to_down); - m_colorizer.set_normal_text (); + m_sink.colorize_text_for_cfg_edge (); + m_sink.add_space (); + m_sink.add_character (right); + m_sink.add_character ('>'); + m_sink.add_character (right); + m_sink.add_character (from_right_to_down); + m_sink.colorize_text_ensure_normal (); column += 5; m_link_rhs_column = column - 1; } @@ -2365,9 +2832,9 @@ layout_printer::print_any_labels (linenum_type row) { gcc_assert (column <= label->m_column); move_to_column (&column, label->m_column, true); - m_colorizer.set_range (label->m_state_idx); - pp_character (&m_pp, '|'); - m_colorizer.set_normal_text (); + m_sink.colorize_text_for_range_idx (label->m_state_idx); + m_sink.add_character ('|'); + m_sink.colorize_text_ensure_normal (); column++; } } @@ -2377,14 +2844,14 @@ layout_printer::print_any_labels (linenum_type row) if (m_link_rhs_column != -1 && column < m_link_rhs_column) { move_to_column (&column, m_link_rhs_column, true); - m_colorizer.set_cfg_edge (); + m_sink.colorize_text_for_cfg_edge (); const cppchar_t down= get_theme ().get_cppchar (text_art::theme::cell_kind::CFG_DOWN); - pp_unicode_character (&m_pp, down); - m_colorizer.set_normal_text (); + m_sink.add_character (down); + m_sink.colorize_text_ensure_normal (); } - print_newline (); + end_line (); } } @@ -2393,14 +2860,13 @@ layout_printer::print_any_labels (linenum_type row) if (m_link_rhs_column != -1) { int column = 1 + m_layout.m_x_offset_display; - start_annotation_line (); + start_annotation_line (margin_kind::normal); move_to_column (&column, m_link_rhs_column, true); - m_colorizer.set_cfg_edge (); + m_sink.colorize_text_for_cfg_edge (); const cppchar_t down= get_theme ().get_cppchar (text_art::theme::cell_kind::CFG_DOWN); - pp_unicode_character (&m_pp, down); - m_colorizer.set_normal_text (); - print_newline (); + m_sink.add_character (down); + end_line (); } /* Clean up. */ @@ -2417,8 +2883,9 @@ layout_printer::print_any_labels (linenum_type row) They are printed on lines of their own, before the source line itself, with a leading '+'. */ +template <typename Sink> void -layout_printer::print_leading_fixits (linenum_type row) +layout_printer<Sink>::print_leading_fixits (linenum_type row) { for (unsigned int i = 0; i < m_layout.m_fixit_hints.length (); i++) { @@ -2438,16 +2905,15 @@ layout_printer::print_leading_fixits (linenum_type row) and the inserted line with "insert" colorization helps them stand out from each other, and from the surrounding text. */ - m_colorizer.set_normal_text (); - start_annotation_line ('+'); - m_colorizer.set_fixit_insert (); + m_sink.colorize_text_ensure_normal (); + start_annotation_line (margin_kind::insertion); + m_sink.colorize_text_for_fixit_insert (); /* Print all but the trailing newline of the fix-it hint. We have to print the newline separately to avoid getting additional pp prefixes printed. */ for (size_t i = 0; i < hint->get_length () - 1; i++) - pp_character (&m_pp, hint->get_string ()[i]); - m_colorizer.set_normal_text (); - pp_newline (&m_pp); + m_sink.add_character (hint->get_string ()[i]); + end_line (); } } } @@ -2877,8 +3343,9 @@ line_corrections::add_hint (const fixit_hint *hint) Fix-it hints that insert new lines are handled separately, in layout::print_leading_fixits. */ +template<typename Sink> void -layout_printer::print_trailing_fixits (linenum_type row) +layout_printer<Sink>::print_trailing_fixits (linenum_type row) { /* Build a list of correction instances for the line, potentially consolidating hints (for the sake of readability). */ @@ -2904,7 +3371,7 @@ layout_printer::print_trailing_fixits (linenum_type row) int column = 1 + m_layout.m_x_offset_display; if (!corrections.m_corrections.is_empty ()) - start_annotation_line (); + start_annotation_line (margin_kind::normal); FOR_EACH_VEC_ELT (corrections.m_corrections, i, c) { @@ -2914,9 +3381,9 @@ layout_printer::print_trailing_fixits (linenum_type row) /* This assumes the insertion just affects one line. */ int start_column = c->m_printed_columns.start; move_to_column (&column, start_column, true); - m_colorizer.set_fixit_insert (); - pp_string (&m_pp, c->m_text); - m_colorizer.set_normal_text (); + m_sink.colorize_text_for_fixit_insert (); + m_sink.add_text (c->m_text); + m_sink.colorize_text_ensure_normal (); column += c->m_display_cols; } else @@ -2932,10 +3399,10 @@ layout_printer::print_trailing_fixits (linenum_type row) || c->m_byte_length == 0) { move_to_column (&column, start_column, true); - m_colorizer.set_fixit_delete (); + m_sink.colorize_text_for_fixit_delete (); for (; column <= finish_column; column++) - pp_character (&m_pp, '-'); - m_colorizer.set_normal_text (); + m_sink.add_character ('-'); + m_sink.colorize_text_ensure_normal (); } /* Print the replacement text. REPLACE also covers removals, so only do this extra work (potentially starting @@ -2943,27 +3410,21 @@ layout_printer::print_trailing_fixits (linenum_type row) if (c->m_byte_length > 0) { move_to_column (&column, start_column, true); - m_colorizer.set_fixit_insert (); - pp_string (&m_pp, c->m_text); - m_colorizer.set_normal_text (); + m_sink.colorize_text_for_fixit_insert (); + m_sink.add_text (c->m_text); + m_sink.colorize_text_ensure_normal (); column += c->m_display_cols; } } } + if (!corrections.m_corrections.is_empty ()) + m_sink.pop_html_tag ("td"); + /* Add a trailing newline, if necessary. */ move_to_column (&column, 1 + m_layout.m_x_offset_display, false); } -/* Disable any colorization and emit a newline. */ - -void -layout_printer::print_newline () -{ - m_colorizer.set_normal_text (); - pp_newline (&m_pp); -} - /* Return true if (ROW/COLUMN) is within a range of the layout. If it returns true, OUT_STATE is written to, with the range index, and whether we should draw the caret at @@ -3060,27 +3521,28 @@ layout::get_x_bound_for_row (linenum_type row, int caret_column, and updating *COLUMN. If ADD_LEFT_MARGIN, then print the (empty) left margin after any newline. */ +template<typename Sink> void -layout_printer::move_to_column (int *column, - int dest_column, - bool add_left_margin) +layout_printer<Sink>::move_to_column (int *column, + int dest_column, + bool add_left_margin) { /* Start a new line if we need to. */ if (*column > dest_column) { - print_newline (); + end_line (); if (add_left_margin) - start_annotation_line (); + start_annotation_line (margin_kind::normal); *column = 1 + m_layout.m_x_offset_display; } while (*column < dest_column) { /* For debugging column issues, it can be helpful to replace this - pp_space call with - pp_character (&m_pp, '0' + (*column % 10)); + add_space call with + m_sink.add_character ('0' + (*column % 10)); to visualize the changing value of "*column". */ - pp_space (&m_pp); + m_sink.add_space (); (*column)++; } } @@ -3088,49 +3550,55 @@ layout_printer::move_to_column (int *column, /* For debugging layout issues, render a ruler giving column numbers (after the 1-column indent). */ +template<typename Sink> void -layout_printer::show_ruler (int max_column) +layout_printer<Sink>::show_ruler (int max_column) { + m_sink.push_html_tag_with_class("thead", "ruler", false); + /* Hundreds. */ if (max_column > 99) { - start_annotation_line (); + start_annotation_line (margin_kind::ruler); for (int column = 1 + m_layout.m_x_offset_display; column <= max_column; ++column) if (column % 10 == 0) - pp_character (&m_pp, '0' + (column / 100) % 10); + m_sink.add_character ('0' + (column / 100) % 10); else - pp_space (&m_pp); - pp_newline (&m_pp); + m_sink.add_space (); + end_line (); } /* Tens. */ - start_annotation_line (); + start_annotation_line (margin_kind::ruler); for (int column = 1 + m_layout.m_x_offset_display; column <= max_column; ++column) if (column % 10 == 0) - pp_character (&m_pp, '0' + (column / 10) % 10); + m_sink.add_character ('0' + (column / 10) % 10); else - pp_space (&m_pp); - pp_newline (&m_pp); + m_sink.add_space (); + end_line (); /* Units. */ - start_annotation_line (); + start_annotation_line (margin_kind::ruler); for (int column = 1 + m_layout.m_x_offset_display; column <= max_column; ++column) - pp_character (&m_pp, '0' + (column % 10)); - pp_newline (&m_pp); + m_sink.add_character ('0' + (column % 10)); + end_line (); + + m_sink.pop_html_tag("thead"); // thead } /* Print leading fix-its (for new lines inserted before the source line) then the source line, followed by an annotation line consisting of any caret/underlines, then any fixits. If the source line can't be read, print nothing. */ +template<typename Sink> void -layout_printer::print_line (linenum_type row) +layout_printer<Sink>::print_line (linenum_type row) { char_span line = m_layout.m_file_cache.get_source_line (m_layout.m_exploc.file, row); @@ -3154,8 +3622,9 @@ layout_printer::print_line (linenum_type row) showing the link entering at the top right and emerging at the bottom left. */ +template<typename Sink> void -layout_printer::print_any_right_to_left_edge_lines () +layout_printer<Sink>::print_any_right_to_left_edge_lines () { if (m_link_rhs_column == -1) /* Can also happen if the out-edge had UNKNOWN_LOCATION. */ @@ -3164,31 +3633,30 @@ layout_printer::print_any_right_to_left_edge_lines () gcc_assert (get_options ().show_event_links_p); /* Print the line with "|". */ - start_annotation_line (); + start_annotation_line (margin_kind::normal); + int column = 1 + m_layout.m_x_offset_display; move_to_column (&column, m_link_rhs_column, true); - m_colorizer.set_cfg_edge (); + m_sink.colorize_text_for_cfg_edge (); const cppchar_t down= get_theme ().get_cppchar (text_art::theme::cell_kind::CFG_DOWN); - pp_unicode_character (&m_pp, down); - m_colorizer.set_normal_text (); - pp_newline (&m_pp); + m_sink.add_character (down); + end_line (); /* Print the line with "┌──────────────────────────────────────────┘". */ m_link_lhs_state = link_lhs_state::rewinding_to_lhs; - start_annotation_line (); - m_colorizer.set_cfg_edge (); + start_annotation_line (margin_kind::normal); + m_sink.colorize_text_for_cfg_edge (); const cppchar_t left= get_theme ().get_cppchar (text_art::theme::cell_kind::CFG_LEFT); for (int column = 1 + m_layout.m_x_offset_display; column < m_link_rhs_column; ++column) - pp_unicode_character (&m_pp, left); + m_sink.add_character (left); const cppchar_t from_down_to_left = get_theme ().get_cppchar (text_art::theme::cell_kind::CFG_FROM_DOWN_TO_LEFT); - pp_unicode_character (&m_pp, from_down_to_left); - m_colorizer.set_normal_text (); - pp_newline (&m_pp); + m_sink.add_character (from_down_to_left); + end_line (); /* We now have a link line on the LHS, and no longer have one on the RHS. */ @@ -3196,14 +3664,13 @@ layout_printer::print_any_right_to_left_edge_lines () m_link_rhs_column = -1; } -layout_printer::layout_printer (pretty_printer &pp, - const layout &layout, - const rich_location &richloc, - diagnostic_t diagnostic_kind) -: m_pp (pp), +template<typename Sink> +layout_printer<Sink>::layout_printer (Sink &sink, + const layout &layout, + bool is_diagnostic_path) +: m_sink (sink), m_layout (layout), - m_colorizer (m_pp, richloc, diagnostic_kind), - m_is_diagnostic_path (diagnostic_kind == DK_DIAGNOSTIC_PATH), + m_is_diagnostic_path (is_diagnostic_path), m_link_lhs_state (link_lhs_state::none), m_link_rhs_column (-1) { @@ -3293,11 +3760,47 @@ diagnostic_context::maybe_show_locus (const rich_location &richloc, source_policy.print (pp, richloc, diagnostic_kind, effects); } +/* As above, but print in HTML form to XP. + If non-null, use LABEL_WRITER when writing labelled ranges. */ + +void +diagnostic_context::maybe_show_locus_as_html (const rich_location &richloc, + const diagnostic_source_printing_options &opts, + diagnostic_t diagnostic_kind, + xml::printer &xp, + diagnostic_source_effect_info *effects, + html_label_writer *label_writer) +{ + const location_t loc = richloc.get_loc (); + /* Do nothing if source-printing has been disabled. */ + if (!opts.enabled) + return; + + /* Don't attempt to print source for UNKNOWN_LOCATION and for builtins. */ + if (loc <= BUILTINS_LOCATION) + return; + + /* Don't print the same source location twice in a row, unless we have + fix-it hints, or multiple locations, or a label. */ + if (loc == m_last_location + && richloc.get_num_fixit_hints () == 0 + && richloc.get_num_locations () == 1 + && richloc.get_range (0)->m_label == NULL) + return; + + m_last_location = loc; + + diagnostic_source_print_policy source_policy (*this, opts); + source_policy.print_as_html (xp, richloc, diagnostic_kind, effects, + label_writer); +} + diagnostic_source_print_policy:: diagnostic_source_print_policy (const diagnostic_context &dc) : m_options (dc.m_source_printing), m_location_policy (dc), - m_start_span_cb (dc.m_text_callbacks.m_start_span), + m_text_start_span_cb (dc.m_text_callbacks.m_text_start_span), + m_html_start_span_cb (dc.m_text_callbacks.m_html_start_span), m_file_cache (dc.get_file_cache ()), m_diagram_theme (dc.get_diagram_theme ()), m_escape_format (dc.get_escape_format ()) @@ -3309,7 +3812,8 @@ diagnostic_source_print_policy (const diagnostic_context &dc, const diagnostic_source_printing_options &opts) : m_options (opts), m_location_policy (dc), - m_start_span_cb (dc.m_text_callbacks.m_start_span), + m_text_start_span_cb (dc.m_text_callbacks.m_text_start_span), + m_html_start_span_cb (dc.m_text_callbacks.m_html_start_span), m_file_cache (dc.get_file_cache ()), m_diagram_theme (dc.get_diagram_theme ()), m_escape_format (dc.get_escape_format ()) @@ -3329,15 +3833,36 @@ diagnostic_source_print_policy::print (pretty_printer &pp, const { layout layout (*this, richloc, effects); - layout_printer lp (pp, layout, richloc, diagnostic_kind); + colorizer col (pp, richloc, diagnostic_kind); + to_text sink (pp, col); + layout_printer<to_text> lp (sink, layout, + diagnostic_kind == DK_DIAGNOSTIC_PATH); lp.print (*this); } +/* As above, but print in HTML form to XP. + If non-null, use LABEL_WRITER when writing labelled ranges. */ + void -layout_printer::print (const diagnostic_source_print_policy &source_policy) +diagnostic_source_print_policy::print_as_html (xml::printer &xp, + const rich_location &richloc, + diagnostic_t diagnostic_kind, + diagnostic_source_effect_info *effects, + html_label_writer *label_writer) + const { - diagnostic_prefixing_rule_t saved_rule = pp_prefixing_rule (&m_pp); - pp_prefixing_rule (&m_pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + layout layout (*this, richloc, effects); + to_html sink (xp, label_writer); + layout_printer<to_html> lp (sink, layout, + diagnostic_kind == DK_DIAGNOSTIC_PATH); + lp.print (*this); +} + +template <typename Sink> +void +layout_printer<Sink>::print (const diagnostic_source_print_policy &source_policy) +{ + m_sink.push_html_tag_with_class ("table", "locus", false); if (get_options ().show_ruler_p) show_ruler (m_layout.m_x_offset_display + get_options ().max_width); @@ -3362,27 +3887,65 @@ layout_printer::print (const diagnostic_source_print_policy &source_policy) = m_layout.get_expanded_location (line_span); const diagnostic_location_print_policy & loc_policy = source_policy.get_location_policy (); - source_policy.get_start_span_fn () (loc_policy, &m_pp, exploc); + m_sink.invoke_start_span_fn (source_policy, loc_policy, exploc); } } + + m_sink.push_html_tag_with_class ("tbody", "line-span", false); + /* Iterate over the lines within this span (using linenum_arith_t to avoid overflow with 0xffffffff causing an infinite loop). */ linenum_arith_t last_line = line_span->get_last_line (); for (linenum_arith_t row = line_span->get_first_line (); row <= last_line; row++) print_line (row); + + m_sink.pop_html_tag ("tbody"); } if (auto effect_info = m_layout.m_effect_info) effect_info->m_trailing_out_edge_column = m_link_rhs_column; - pp_prefixing_rule (&m_pp) = saved_rule; + m_sink.pop_html_tag ("table"); } #if CHECKING_P namespace selftest { +static std::unique_ptr<xml::node> +make_element_for_locus (const rich_location &rich_loc, + diagnostic_t kind, + diagnostic_context &dc) +{ + dc.m_last_location = UNKNOWN_LOCATION; + + xml::element wrapper ("wrapper", false); + xml::printer xp (wrapper); + dc.maybe_show_locus_as_html (rich_loc, + dc.m_source_printing, + kind, + xp, + nullptr, + nullptr); // label_writer + if (wrapper.m_children.size () > 0) + return std::move (wrapper.m_children[0]); + else + return nullptr; +} + +static label_text +make_raw_html_for_locus (const rich_location &rich_loc, + diagnostic_t kind, + diagnostic_context &dc) +{ + auto node = make_element_for_locus (rich_loc, kind, dc); + pretty_printer pp; + if (node) + node->write_as_xml (&pp, 0, true); + return label_text::take (xstrdup (pp_formatted_text (&pp))); +} + /* Selftests for diagnostic_show_locus. */ diagnostic_show_locus_fixture:: @@ -3600,7 +4163,10 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_) linemap_position_for_column (line_table, emoji_col)); layout test_layout (policy, richloc, nullptr); - layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR); + colorizer col (*dc.get_reference_printer (), + richloc, DK_ERROR); + to_text sink (*dc.get_reference_printer (), col); + layout_printer<to_text> lp (sink, test_layout, false); lp.print (policy); ASSERT_STREQ (" | 1 \n" " | 1 \n" @@ -3627,7 +4193,10 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_) linemap_position_for_column (line_table, emoji_col + 2)); layout test_layout (dc, richloc, nullptr); - layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR); + colorizer col (*dc.get_reference_printer (), + richloc, DK_ERROR); + to_text sink (*dc.get_reference_printer (), col); + layout_printer<to_text> lp (sink, test_layout, false); lp.print (policy); ASSERT_STREQ (" | 1 1 \n" " | 1 2 \n" @@ -3708,7 +4277,10 @@ test_layout_x_offset_display_tab (const line_table_case &case_) dc.m_tabstop = tabstop; diagnostic_source_print_policy policy (dc); layout test_layout (policy, richloc, nullptr); - layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR); + colorizer col (*dc.get_reference_printer (), + richloc, DK_ERROR); + to_text sink (*dc.get_reference_printer (), col); + layout_printer<to_text> lp (sink, test_layout, false); lp.print (policy); const char *out = pp_formatted_text (dc.get_reference_printer ()); ASSERT_EQ (NULL, strchr (out, '\t')); @@ -3733,7 +4305,10 @@ test_layout_x_offset_display_tab (const line_table_case &case_) dc.m_source_printing.show_line_numbers_p = true; diagnostic_source_print_policy policy (dc); layout test_layout (policy, richloc, nullptr); - layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR); + colorizer col (*dc.get_reference_printer (), + richloc, DK_ERROR); + to_text sink (*dc.get_reference_printer (), col); + layout_printer<to_text> lp (sink, test_layout, false); lp.print (policy); /* We have arranged things so that two columns will be printed before @@ -3814,6 +4389,32 @@ test_one_liner_caret_and_range () ASSERT_STREQ (" foo = bar.field;\n" " ~~~^~~~~~\n", dc.test_show_locus (richloc)); + + { + test_diagnostic_context dc; + auto out = make_raw_html_for_locus (richloc, DK_ERROR, dc); + ASSERT_STREQ + ("<table class=\"locus\">\n" + " <tbody class=\"line-span\">\n" + " <tr><td class=\"left-margin\"> </td><td class=\"source\">foo = bar.field;</td></tr>\n" + " <tr><td class=\"left-margin\"> </td><td class=\"annotation\"> ~~~^~~~~~</td></tr>\n" + " </tbody>\n" + "</table>\n", + out.get ()); + } + { + test_diagnostic_context dc; + dc.m_source_printing.show_line_numbers_p = true; + auto out = make_raw_html_for_locus (richloc, DK_ERROR, dc); + ASSERT_STREQ + ("<table class=\"locus\">\n" + " <tbody class=\"line-span\">\n" + " <tr><td class=\"linenum\"> 1</td><td class=\"left-margin\"> </td><td class=\"source\">foo = bar.field;</td></tr>\n" + " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\"> ~~~^~~~~~</td></tr>\n" + " </tbody>\n" + "</table>\n", + out.get ()); + } } /* Multiple ranges and carets. */ @@ -4231,6 +4832,24 @@ test_one_liner_labels () " | label 1\n" " label 0\n", dc.test_show_locus (richloc)); + + { + test_diagnostic_context dc; + dc.m_source_printing.show_line_numbers_p = true; + auto out = make_raw_html_for_locus (richloc, DK_ERROR, dc); + ASSERT_STREQ + ("<table class=\"locus\">\n" + " <tbody class=\"line-span\">\n" + " <tr><td class=\"linenum\"> 1</td><td class=\"left-margin\"> </td><td class=\"source\">foo = bar.field;</td></tr>\n" + " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">^~~ ~~~ ~~~~~</td></tr>\n" + " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">| | |</td></tr>\n" + " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">| | label 2</td></tr>\n" + " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">| label 1</td></tr>\n" + " <tr><td class=\"linenum\"> </td><td class=\"left-margin\"> </td><td class=\"annotation\">label 0</td></tr>\n" + " </tbody>\n" + "</table>\n", + out.get ()); + } } /* Example of boundary conditions: label 0 and 1 have just enough clearance, diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc index b43fc90..20582b1 100644 --- a/gcc/diagnostic.cc +++ b/gcc/diagnostic.cc @@ -250,7 +250,10 @@ diagnostic_context::initialize (int n_opts) m_internal_error = nullptr; m_adjust_diagnostic_info = nullptr; m_text_callbacks.m_begin_diagnostic = default_diagnostic_text_starter; - m_text_callbacks.m_start_span = default_diagnostic_start_span_fn; + m_text_callbacks.m_text_start_span + = default_diagnostic_start_span_fn<to_text>; + m_text_callbacks.m_html_start_span + = default_diagnostic_start_span_fn<to_html>; m_text_callbacks.m_end_diagnostic = default_diagnostic_text_finalizer; m_option_mgr = nullptr; m_urlifier_stack = new auto_vec<urlifier_stack_node> (); @@ -1071,21 +1074,6 @@ logical_location_manager::function_p (key k) const } } -void -default_diagnostic_start_span_fn (const diagnostic_location_print_policy &loc_policy, - pretty_printer *pp, - expanded_location exploc) -{ - const diagnostic_column_policy &column_policy - = loc_policy.get_column_policy (); - label_text text - = column_policy.get_location_text (exploc, - loc_policy.show_column_p (), - pp_show_color (pp)); - pp_string (pp, text.get ()); - pp_newline (pp); -} - /* Interface to specify diagnostic kind overrides. Returns the previous setting, or DK_UNSPECIFIED if the parameters are out of range. If OPTION_ID is zero, the new setting is for all the diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h index 00d7812..cdd6f26 100644 --- a/gcc/diagnostic.h +++ b/gcc/diagnostic.h @@ -31,6 +31,11 @@ namespace text_art class theme; } // namespace text_art +namespace xml +{ + class printer; +} // namespace xml + /* An enum for controlling what units to use for the column number when diagnostics are output, used by the -fdiagnostics-column-unit option. Tabs will be expanded or not according to the value of -ftabstop. The origin @@ -177,10 +182,15 @@ class diagnostic_source_print_policy; typedef void (*diagnostic_text_starter_fn) (diagnostic_text_output_format &, const diagnostic_info *); -typedef void -(*diagnostic_start_span_fn) (const diagnostic_location_print_policy &, - pretty_printer *, - expanded_location); +struct to_text; +struct to_html; + +extern pretty_printer *get_printer (to_text &); + +template <typename Sink> +using diagnostic_start_span_fn = void (*) (const diagnostic_location_print_policy &, + Sink &sink, + expanded_location); typedef void (*diagnostic_text_finalizer_fn) (diagnostic_text_output_format &, const diagnostic_info *, @@ -389,11 +399,32 @@ public: const diagnostic_column_policy & get_column_policy () const { return m_column_policy; } + void + print_text_span_start (const diagnostic_context &dc, + pretty_printer &pp, + const expanded_location &exploc); + + void + print_html_span_start (const diagnostic_context &dc, + xml::printer &xp, + const expanded_location &exploc); + private: diagnostic_column_policy m_column_policy; bool m_show_column; }; +/* Abstract base class for optionally supplying extra tags when writing + out annotation labels in HTML output. */ + +class html_label_writer +{ +public: + virtual ~html_label_writer () {} + virtual void begin_label () = 0; + virtual void end_label () = 0; +}; + /* A bundle of state for printing source within a diagnostic, to isolate the interactions between diagnostic_context and the implementation of diagnostic_show_locus. */ @@ -411,11 +442,21 @@ public: diagnostic_t diagnostic_kind, diagnostic_source_effect_info *effect_info) const; + void + print_as_html (xml::printer &xp, + const rich_location &richloc, + diagnostic_t diagnostic_kind, + diagnostic_source_effect_info *effect_info, + html_label_writer *label_writer) const; + const diagnostic_source_printing_options & get_options () const { return m_options; } - diagnostic_start_span_fn - get_start_span_fn () const { return m_start_span_cb; } + diagnostic_start_span_fn<to_text> + get_text_start_span_fn () const { return m_text_start_span_cb; } + + diagnostic_start_span_fn<to_html> + get_html_start_span_fn () const { return m_html_start_span_cb; } file_cache & get_file_cache () const { return m_file_cache; } @@ -442,7 +483,8 @@ public: private: const diagnostic_source_printing_options &m_options; class diagnostic_location_print_policy m_location_policy; - diagnostic_start_span_fn m_start_span_cb; + diagnostic_start_span_fn<to_text> m_text_start_span_cb; + diagnostic_start_span_fn<to_html> m_html_start_span_cb; file_cache &m_file_cache; /* Other data copied from diagnostic_context. */ @@ -508,7 +550,7 @@ public: /* Give access to m_text_callbacks. */ friend diagnostic_text_starter_fn & diagnostic_text_starter (diagnostic_context *context); - friend diagnostic_start_span_fn & + friend diagnostic_start_span_fn<to_text> & diagnostic_start_span (diagnostic_context *context); friend diagnostic_text_finalizer_fn & diagnostic_text_finalizer (diagnostic_context *context); @@ -602,6 +644,12 @@ public: diagnostic_t diagnostic_kind, pretty_printer &pp, diagnostic_source_effect_info *effect_info); + void maybe_show_locus_as_html (const rich_location &richloc, + const diagnostic_source_printing_options &opts, + diagnostic_t diagnostic_kind, + xml::printer &xp, + diagnostic_source_effect_info *effect_info, + html_label_writer *label_writer); void emit_diagram (const diagnostic_diagram &diagram); @@ -882,7 +930,8 @@ private: /* This function is called by diagnostic_show_locus in between disjoint spans of source code, so that the context can print something to indicate that a new span of source code has begun. */ - diagnostic_start_span_fn m_start_span; + diagnostic_start_span_fn<to_text> m_text_start_span; + diagnostic_start_span_fn<to_html> m_html_start_span; /* This function is called after the diagnostic message is printed. */ diagnostic_text_finalizer_fn m_end_diagnostic; @@ -1040,10 +1089,10 @@ diagnostic_text_starter (diagnostic_context *context) /* Client supplied function called between disjoint spans of source code, so that the context can print something to indicate that a new span of source code has begun. */ -inline diagnostic_start_span_fn & +inline diagnostic_start_span_fn<to_text> & diagnostic_start_span (diagnostic_context *context) { - return context->m_text_callbacks.m_start_span; + return context->m_text_callbacks.m_text_start_span; } /* Client supplied function called after a diagnostic message is @@ -1128,6 +1177,21 @@ diagnostic_show_locus (diagnostic_context *context, context->maybe_show_locus (*richloc, opts, diagnostic_kind, *pp, effect_info); } +inline void +diagnostic_show_locus_as_html (diagnostic_context *context, + const diagnostic_source_printing_options &opts, + rich_location *richloc, + diagnostic_t diagnostic_kind, + xml::printer &xp, + diagnostic_source_effect_info *effect_info = nullptr, + html_label_writer *label_writer = nullptr) +{ + gcc_assert (context); + gcc_assert (richloc); + context->maybe_show_locus_as_html (*richloc, opts, diagnostic_kind, xp, + effect_info, label_writer); +} + /* Because we read source files a second time after the frontend did it the first time, we need to know how the frontend handled things like character set conversion and UTF-8 BOM stripping, in order to make everything @@ -1201,8 +1265,9 @@ extern void diagnostic_set_info_translated (diagnostic_info *, const char *, #endif void default_diagnostic_text_starter (diagnostic_text_output_format &, const diagnostic_info *); +template <typename Sink> void default_diagnostic_start_span_fn (const diagnostic_location_print_policy &, - pretty_printer *, + Sink &sink, expanded_location); void default_diagnostic_text_finalizer (diagnostic_text_output_format &, const diagnostic_info *, diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index e3bc833..0150ad0 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -6119,18 +6119,25 @@ in this release. @item experimental-html Emit diagnostics to a file in HTML format. This scheme is experimental, -and may go away in future GCC releases. The details of the output are -also subject to change. +and may go away in future GCC releases. The keys and details of the output +are also subject to change. Supported keys are: @table @gcctabopt +@item css=@r{[}yes@r{|}no@r{]} +Add an embedded <style> to the generated HTML. Defaults to yes. + @item file=@var{FILENAME} Specify the filename to write the HTML output to, potentially with a leading absolute or relative path. If not specified, it defaults to @file{@var{source}.html}. +@item javascript=@r{[}yes@r{|}no@r{]} +Add an embedded <script> to the generated HTML providing a barebones UI +for viewing results. Defaults to yes. + @end table @end table diff --git a/gcc/fortran/error.cc b/gcc/fortran/error.cc index f89d41d..004a4b2 100644 --- a/gcc/fortran/error.cc +++ b/gcc/fortran/error.cc @@ -618,9 +618,10 @@ gfc_diagnostic_text_starter (diagnostic_text_output_format &text_output, static void gfc_diagnostic_start_span (const diagnostic_location_print_policy &loc_policy, - pretty_printer *pp, + to_text &sink, expanded_location exploc) { + pretty_printer *pp = get_printer (sink); const bool colorize = pp_show_color (pp); char *locus_prefix = gfc_diagnostic_build_locus_prefix (loc_policy, exploc, colorize); diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc index 34c3906..3a1dc52 100644 --- a/gcc/opts-diagnostic.cc +++ b/gcc/opts-diagnostic.cc @@ -545,21 +545,40 @@ html_scheme_handler::make_sink (const context &ctxt, const char *unparsed_arg, const scheme_name_and_params &parsed_arg) const { + bool css = true; label_text filename; + bool javascript = true; for (auto& iter : parsed_arg.m_kvs) { const std::string &key = iter.first; const std::string &value = iter.second; + if (key == "css") + { + if (!parse_bool_value (ctxt, unparsed_arg, key, value, + css)) + return nullptr; + continue; + } if (key == "file") { filename = label_text::take (xstrdup (value.c_str ())); continue; } + if (key == "javascript") + { + if (!parse_bool_value (ctxt, unparsed_arg, key, value, + javascript)) + return nullptr; + continue; + } /* Key not found. */ auto_vec<const char *> known_keys; + known_keys.safe_push ("css"); known_keys.safe_push ("file"); - ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (), known_keys); + known_keys.safe_push ("javascript"); + ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (), + known_keys); return nullptr; } @@ -579,8 +598,13 @@ html_scheme_handler::make_sink (const context &ctxt, if (!output_file) return nullptr; + html_generation_options html_gen_opts; + html_gen_opts.m_css = css; + html_gen_opts.m_javascript = javascript; + auto sink = make_html_sink (ctxt.m_dc, *line_table, + html_gen_opts, std::move (output_file)); return sink; } diff --git a/gcc/selftest-diagnostic.cc b/gcc/selftest-diagnostic.cc index 1a10807..eeee2eb 100644 --- a/gcc/selftest-diagnostic.cc +++ b/gcc/selftest-diagnostic.cc @@ -58,11 +58,11 @@ test_diagnostic_context::~test_diagnostic_context () void test_diagnostic_context:: start_span_cb (const diagnostic_location_print_policy &loc_policy, - pretty_printer *pp, + to_text &sink, expanded_location exploc) { exploc.file = "FILENAME"; - default_diagnostic_start_span_fn (loc_policy, pp, exploc); + default_diagnostic_start_span_fn<to_text> (loc_policy, sink, exploc); } bool diff --git a/gcc/selftest-diagnostic.h b/gcc/selftest-diagnostic.h index c8f67a0..4a43310 100644 --- a/gcc/selftest-diagnostic.h +++ b/gcc/selftest-diagnostic.h @@ -40,7 +40,7 @@ class test_diagnostic_context : public diagnostic_context real filename (to avoid printing the names of tempfiles). */ static void start_span_cb (const diagnostic_location_print_policy &, - pretty_printer *, + to_text &sink, expanded_location exploc); /* Report a diagnostic to this context. For a selftest, this diff --git a/gcc/testsuite/gcc.dg/html-output/missing-semicolon.c b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.c index c211f4f..fe5f081 100644 --- a/gcc/testsuite/gcc.dg/html-output/missing-semicolon.c +++ b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.c @@ -1,5 +1,5 @@ /* { dg-do compile } */ -/* { dg-options "-fdiagnostics-add-output=experimental-html" } */ +/* { dg-options "-fdiagnostics-add-output=experimental-html:javascript=no" } */ /* Verify that basics of HTML output work. */ diff --git a/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py index 8ac1f14..02ff84c 100644 --- a/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py +++ b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py @@ -18,12 +18,6 @@ import pytest def html_tree(): return html_tree_from_env() -XHTML = 'http://www.w3.org/1999/xhtml' -ns = {'xhtml': XHTML} - -def make_tag(local_name): - return f'{{{XHTML}}}' + local_name - def test_basics(html_tree): root = html_tree.getroot () assert root.tag == make_tag('html') diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.c index 2499e8d..df57b25 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.c +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.c @@ -1,6 +1,6 @@ /* { dg-do compile } */ -/* { dg-options "-fdiagnostics-set-output=experimental-html" } */ -/* { dg-additional-options "-fdiagnostics-show-caret" } */ +/* { dg-options "-fdiagnostics-set-output=experimental-html:javascript=no" } */ +/* { dg-additional-options "-fdiagnostics-show-caret -fdiagnostics-show-line-numbers" } */ extern char *gets (char *s); 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 e475e95..b4c75b2 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py @@ -8,12 +8,6 @@ import pytest def html_tree(): return html_tree_from_env() -XHTML = 'http://www.w3.org/1999/xhtml' -ns = {'xhtml': XHTML} - -def make_tag(local_name): - return f'{{{XHTML}}}' + local_name - def test_metadata(html_tree): root = html_tree.getroot () assert root.tag == make_tag('html') @@ -48,11 +42,21 @@ def test_metadata(html_tree): assert metadata[1][0].text == 'STR34-C' assert metadata[1][0].tail == ']' - src = diag.find('xhtml:pre', ns) - assert src.attrib['class'] == 'gcc-annotated-source' - assert src.text == ( - ' gets (buf);\n' - ' ^~~~~~~~~~\n') + src = diag.find('xhtml:table', ns) + assert src.attrib['class'] == 'locus' + + tbody = src.find('xhtml:tbody', ns) + assert tbody.attrib['class'] == 'line-span' + + rows = tbody.findall('xhtml:tr', ns) + + quoted_src_tr = rows[0] + assert_quoted_line(quoted_src_tr, + ' 10', ' gets (buf);') + + annotation_tr = rows[1] + assert_annotation_line(annotation_tr, + ' ^~~~~~~~~~') # For reference, here's the generated HTML: """ @@ -60,8 +64,13 @@ def test_metadata(html_tree): <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> - ...etc... + <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.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c index 26605f7..dab9c38 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c @@ -1,5 +1,5 @@ /* { dg-do compile } */ -/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-show-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-add-output=experimental-html" } */ +/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-show-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-add-output=experimental-html:javascript=no" } */ #include <stddef.h> #include <stdlib.h> 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 c212e49..59bee24 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py @@ -8,12 +8,6 @@ import pytest def html_tree(): return html_tree_from_env() -XHTML = 'http://www.w3.org/1999/xhtml' -ns = {'xhtml': XHTML} - -def make_tag(local_name): - return f'{{{XHTML}}}' + local_name - def test_paths(html_tree): root = html_tree.getroot () assert root.tag == make_tag('html') @@ -29,7 +23,19 @@ def test_paths(html_tree): assert diag is not None assert diag.attrib['class'] == 'gcc-diagnostic' - pre = diag.findall('xhtml:pre', ns) - assert pre[0].attrib['class'] == 'gcc-annotated-source' - assert pre[1].attrib['class'] == 'gcc-execution-path' - assert pre[1].text.startswith(" 'make_a_list_of_random_ints_badly': events 1-3") + event_ranges = diag.find('xhtml:div', ns) + assert_class(event_ranges, 'event-ranges') + + frame_margin = event_ranges.find('xhtml:table', ns) + assert_class(frame_margin, 'stack-frame-with-margin') + + tr = frame_margin.find('xhtml:tr', ns) + assert tr is not None + tds = tr.findall('xhtml:td', ns) + assert len(tds) == 2 + + assert_class(tds[0], 'interprocmargin') + + test_frame = tds[1] + assert_frame(test_frame, 'make_a_list_of_random_ints_badly') + assert_event_range_with_margin(test_frame[1]) diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.c index 847b6d4..7eb0c50 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.c +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.c @@ -1,5 +1,5 @@ /* { dg-do compile } */ -/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fdiagnostics-show-line-numbers" } */ +/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fdiagnostics-show-line-numbers -fdiagnostics-add-output=experimental-html:javascript=no" } */ /* { dg-enable-nn-line-numbers "" } */ #include <stdio.h> @@ -82,3 +82,7 @@ void test (void) | | (9) calling 'fprintf' | { dg-end-multiline-output "" } */ + +/* Use a Python script to verify various properties about the generated + HTML file: + { dg-final { run-html-pytest diagnostic-test-paths-4.c "diagnostic-test-paths-4.py" } } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.py b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.py new file mode 100644 index 0000000..e738729 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.py @@ -0,0 +1,190 @@ +# Verify that interprocedural execution paths work in HTML output. + +from htmltest import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def html_tree(): + return html_tree_from_env() + +def test_paths(html_tree): + diag = get_diag_by_index(html_tree, 0) + src = get_locus_within_diag (diag) + + tbody = src.find('xhtml:tbody', ns) + assert_class(tbody, 'line-span') + + rows = tbody.findall('xhtml:tr', ns) + + quoted_src_tr = rows[0] + assert_quoted_line(quoted_src_tr, + ' 13', ' fprintf(stderr, "LOG: %s", msg); /* { dg-warning "call to \'fprintf\' from within signal handler" } */') + + annotation_tr = rows[1] + assert_annotation_line(annotation_tr, + ' ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') + + event_ranges = diag.find('xhtml:div', ns) + assert_class(event_ranges, 'event-ranges') + + test_frame_margin = event_ranges.find('xhtml:table', ns) + assert_class(test_frame_margin, 'stack-frame-with-margin') + + tr = test_frame_margin.find('xhtml:tr', ns) + assert tr is not None + tds = tr.findall('xhtml:td', ns) + assert len(tds) == 2 + + assert_class(tds[0], 'interprocmargin') + + 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-bw-line-numbers.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-bw-line-numbers.c index 1e8f73b..e81856a 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-bw-line-numbers.c +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-bw-line-numbers.c @@ -1,5 +1,5 @@ /* { dg-do compile } */ -/* { dg-options "-O -fdiagnostics-show-caret -fdiagnostics-show-line-numbers" } */ +/* { dg-options "-O -fdiagnostics-show-caret -fdiagnostics-show-line-numbers -fdiagnostics-add-output=experimental-html:javascript=no" } */ /* This is a collection of unittests for diagnostic_show_locus; see the overview in diagnostic_plugin_test_show_locus.c. @@ -118,3 +118,7 @@ void test_fixit_insert_newline (void) { dg-end-multiline-output "" } */ #endif } + +/* Use a Python script to verify various properties about the generated + HTML file: + { dg-final { run-html-pytest diagnostic-test-show-locus-bw-line-numbers.c "diagnostic-test-show-locus.py" } } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus.py b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus.py new file mode 100644 index 0000000..d963b29 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus.py @@ -0,0 +1,111 @@ +# Verify that diagnostic-show-locus.cc works with HTML output. + +from htmltest import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def html_tree(): + return html_tree_from_env() + +#def get_tr_within_thead(thead, idx) + +def get_ruler_text(thead, idx): + trs = thead.findall('xhtml:tr', ns) + tr = trs[idx] + tds = tr.findall('xhtml:td', ns) + assert len(tds) == 3 + assert_class(tds[2], 'ruler') + return tds[2].text + +def test_very_wide_line(html_tree): + diag = get_diag_by_index(html_tree, 2) + src = get_locus_within_diag(diag) + + # Check ruler + thead = src.find('xhtml:thead', ns) + assert_class(thead, 'ruler') + trs = thead.findall('xhtml:tr', ns) + assert len(trs) == 3 + + assert get_ruler_text(thead, 0) == ' 0 0 0 0 0 1 1 ' + assert get_ruler_text(thead, 1) == ' 5 6 7 8 9 0 1 ' + assert get_ruler_text(thead, 2) == '34567890123456789012345678901234567890123456789012345678901234567890123' + + # Check quoted source + tbody = src.find('xhtml:tbody', ns) + assert_class(tbody, 'line-span') + trs = tbody.findall('xhtml:tr', ns) + assert len(trs) == 5 + assert_quoted_line(trs[0], ' 43', ' float f = foo * bar; /* { dg-warning "95: test" } */') + assert_annotation_line(trs[1], ' ~~~~^~~~~') + assert_annotation_line(trs[2], ' |') + assert_annotation_line(trs[3], ' label 0') + assert_annotation_line(trs[4], ' bar * foo') + +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' + + src = get_locus_within_diag(diag) + + # Check quoted source + tbody = src.find('xhtml:tbody', ns) + assert_class(tbody, 'line-span') + trs = tbody.findall('xhtml:tr', ns) + assert len(trs) == 3 + assert_quoted_line(trs[0], ' 63', ' int a[2][2] = { 0, 1 , 2, 3 }; /* { dg-warning "insertion hints" } */') + assert_annotation_line(trs[1], ' ^~~~') + assert_annotation_line(trs[2], ' { }') + +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' + + src = get_locus_within_diag(diag) + + # Check quoted source + tbody = src.find('xhtml:tbody', ns) + assert_class(tbody, 'line-span') + trs = tbody.findall('xhtml:tr', ns) + assert len(trs) == 3 + assert_quoted_line(trs[0], ' 77', ' int a;; /* { dg-warning "example of a removal hint" } */') + assert_annotation_line(trs[1], ' ^') + assert_annotation_line(trs[2], ' -') + +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' + + src = get_locus_within_diag(diag) + + # Check quoted source + tbody = src.find('xhtml:tbody', ns) + assert_class(tbody, 'line-span') + trs = tbody.findall('xhtml:tr', ns) + assert len(trs) == 3 + assert_quoted_line(trs[0], ' 91', ' gtk_widget_showall (dlg); /* { dg-warning "example of a replacement hint" } */') + assert_annotation_line(trs[1], ' ^~~~~~~~~~~~~~~~~~') + assert_annotation_line(trs[2], ' gtk_widget_show_all') + +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' + + src = get_locus_within_diag(diag) + + # Check quoted source + tbody = src.find('xhtml:tbody', ns) + assert_class(tbody, 'line-span') + trs = tbody.findall('xhtml:tr', ns) + assert len(trs) == 4 + assert_quoted_line(trs[0], ' 109', ' x = a;') + assert_annotation_line(trs[1], ' break;', + expected_line_num=' +++', + expected_left_margin='+') + assert_quoted_line(trs[2], ' 110', " case 'b': /* { dg-warning \"newline insertion\" } */") + assert_annotation_line(trs[3], ' ^~~~~~~~') diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_group_plugin.cc b/gcc/testsuite/gcc.dg/plugin/diagnostic_group_plugin.cc index 5ec3418..4ade232 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic_group_plugin.cc +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_group_plugin.cc @@ -176,9 +176,10 @@ test_diagnostic_text_starter (diagnostic_text_output_format &text_output, void test_diagnostic_start_span_fn (const diagnostic_location_print_policy &, - pretty_printer *pp, + to_text &sink, expanded_location) { + pretty_printer *pp = get_printer (sink); pp_string (pp, "START_SPAN_FN: "); pp_newline (pp); } diff --git a/gcc/testsuite/lib/htmltest.py b/gcc/testsuite/lib/htmltest.py index b249ea6..8e42a8c 100644 --- a/gcc/testsuite/lib/htmltest.py +++ b/gcc/testsuite/lib/htmltest.py @@ -7,3 +7,93 @@ def html_tree_from_env(): html_filename += '.html' print('html_filename: %r' % html_filename) return ET.parse(html_filename) + +XHTML = 'http://www.w3.org/1999/xhtml' +ns = {'xhtml': XHTML} + +def make_tag(local_name): + return f'{{{XHTML}}}' + local_name + +def assert_tag(element, expected): + assert element.tag == make_tag(expected) + +def assert_class(element, expected): + assert element.attrib['class'] == expected + +def assert_quoted_line(tr, expected_line_num, expected_src): + """Verify that tr is a line of quoted source.""" + tds = tr.findall('xhtml:td', ns) + assert len(tds) == 3 + assert_class(tds[0], 'linenum') + assert tds[0].text == expected_line_num + assert_class(tds[1], 'left-margin') + assert tds[1].text == ' ' + assert_class(tds[2], 'source') + assert tds[2].text == expected_src + +def assert_annotation_line(tr, expected_src, + expected_line_num=' ', + expected_left_margin=' '): + """Verify that tr is an annotation line.""" + tds = tr.findall('xhtml:td', ns) + assert len(tds) == 3 + assert_class(tds[0], 'linenum') + assert tds[0].text == expected_line_num + assert_class(tds[1], 'left-margin') + assert tds[1].text == expected_left_margin + assert_class(tds[2], 'annotation') + assert tds[2].text == expected_src + +def assert_frame(frame, expected_fnname): + """ + Assert that frame is of class 'stack-frame' + and has a child showing the expected fnname. + """ + assert_class(frame, 'stack-frame') + funcname = frame[0] + assert_class(funcname, 'frame-funcname') + span = funcname[0] + assert_tag(span, 'span') + assert span.text == expected_fnname + +def assert_event_range_with_margin(element): + """ + Verify that "element" is an event-range-with-margin + """ + assert_tag(element, 'table') + assert_class(element, 'event-range-with-margin') + tr = element.find('xhtml:tr', ns) + assert tr is not None + td = tr.find('xhtml:td', ns) + assert_class(td, 'event-range') + + events_hdr = td.find('xhtml:div', ns) + assert_class(events_hdr, 'events-hdr') + + #...etc + +def get_diag_by_index(html_tree, index): + root = html_tree.getroot () + assert root.tag == make_tag('html') + + body = root.find('xhtml:body', ns) + assert body is not None + + diag_list = body.find('xhtml:div', ns) + 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') + return diag + +def get_message_within_diag(diag_element): + msg = diag_element.find('xhtml:span', ns) + assert_class(msg, 'gcc-message') + return msg + +def get_locus_within_diag(diag_element): + src = diag_element.find('xhtml:table', ns) + assert_class(src, 'locus') + return src diff --git a/gcc/xml-printer.h b/gcc/xml-printer.h new file mode 100644 index 0000000..b90390c --- /dev/null +++ b/gcc/xml-printer.h @@ -0,0 +1,84 @@ +/* Classes for creating XML trees by appending. + Copyright (C) 2025 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_XML_PRINTER_H +#define GCC_XML_PRINTER_H + +namespace xml { + +class node; + class element; + +/* A class for creating XML trees by appending to an insertion + point, with a stack of open tags. */ + +class printer +{ +public: + printer (element &insertion_point); + + void push_tag (std::string name, + bool preserve_whitespace = false); + void push_tag_with_class (std::string name, + std::string class_, + bool preserve_whitespace = false); + void pop_tag (); + + void set_attr (const char *name, std::string value); + + void add_text (std::string text); + + void add_raw (std::string text); + + void push_element (std::unique_ptr<element> new_element); + + void append (std::unique_ptr<node> new_node); + + element *get_insertion_point () const; + +private: + // borrowed ptrs: + std::vector<element *> m_open_tags; +}; + +// RAII for push/pop element on xml::printer + +class auto_print_element +{ +public: + auto_print_element (printer &printer, + std::string name, + bool preserve_whitespace = false) + : m_printer (printer) + { + m_printer.push_tag (name, preserve_whitespace); + } + ~auto_print_element () + { + m_printer.pop_tag (); + } + +private: + printer &m_printer; +}; + +} // namespace xml + +#endif /* GCC_XML_PRINTER_H. */ diff --git a/gcc/xml.h b/gcc/xml.h new file mode 100644 index 0000000..523a44d --- /dev/null +++ b/gcc/xml.h @@ -0,0 +1,113 @@ +/* Classes for representing XML trees. + Copyright (C) 2024-2025 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_XML_H +#define GCC_XML_H + +namespace xml { + +// Forward decls; indentation reflects inheritance +struct node; + struct text; + struct node_with_children; + struct document; + struct element; + +struct node +{ + virtual ~node () {} + virtual void write_as_xml (pretty_printer *pp, + int depth, bool indent) const = 0; + virtual text *dyn_cast_text () + { + return 0; + } + void dump (FILE *out) const; + void DEBUG_FUNCTION dump () const { dump (stderr); } +}; + +struct text : public node +{ + text (std::string str) + : m_str (std::move (str)) + {} + + void write_as_xml (pretty_printer *pp, + int depth, bool indent) const final override; + + text *dyn_cast_text () final override + { + return this; + } + + std::string m_str; +}; + +struct node_with_children : public node +{ + void add_child (std::unique_ptr<node> node); + void add_text (std::string str); + + std::vector<std::unique_ptr<node>> m_children; +}; + +struct document : public node_with_children +{ + void write_as_xml (pretty_printer *pp, + int depth, bool indent) const final override; +}; + +struct element : public node_with_children +{ + element (std::string kind, bool preserve_whitespace) + : m_kind (std::move (kind)), + m_preserve_whitespace (preserve_whitespace) + {} + + void write_as_xml (pretty_printer *pp, + int depth, bool indent) const final override; + + void set_attr (const char *name, std::string value); + + std::string m_kind; + bool m_preserve_whitespace; + std::map<std::string, std::string> m_attributes; + std::vector<std::string> m_key_insertion_order; +}; + +/* A fragment of raw XML source, to be spliced in directly. + Use sparingly. */ + +struct raw : public node +{ + raw (std::string xml_src) + : m_xml_src (xml_src) + { + } + + void write_as_xml (pretty_printer *pp, + int depth, bool indent) const final override; + + std::string m_xml_src; +}; + +} // namespace xml + +#endif /* GCC_XML_H. */ |