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>
This commit is contained in:
David Malcolm
2025-05-27 12:22:26 -04:00
parent 7fda941722
commit 04871e7488
25 changed files with 2441 additions and 472 deletions

View File

@@ -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,
@@ -309,15 +377,14 @@ public:
std::unique_ptr<xml::element>
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"));
{
{
auto head_element = std::make_unique<xml::element> ("head", false);
{
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));
}
html_element->add_child (std::move (head_element));
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));
auto body_element = std::make_unique<xml::element> ("body", false);
{
xml::auto_print_element head (xp, "head");
m_head_element = xp.get_insertion_point ();
{
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 diagnostics_element
= make_div (label_text::borrow ("gcc-diagnostic-list"));
m_diagnostics_element = diagnostics_element.get ();
body_element->add_child (std::move (diagnostics_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 (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));
@@ -625,62 +868,24 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
return diag_element;
}
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>&apos;</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>&apos;</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

View File

@@ -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 */

View File

@@ -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,13 +1137,148 @@ 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. */
class element_event_desc : public pp_element
@@ -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 {

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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 *,

View File

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

View File

@@ -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);

View File

@@ -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;
}

View File

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

View File

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

View File

@@ -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. */

View File

@@ -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')

View File

@@ -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);

View File

@@ -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 &apos;<span class="gcc-quoted-text">gets</span>&apos;</span>
<span class="gcc-metadata"><span class="gcc-metadata-item">[<a href="https://cwe.mitre.org/data/definitions/242.html">CWE-242</a>]</span><span class="gcc-metadata-item">[<a href="https://example.com/">STR34-C</a>]</span></span>
...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>

View File

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

View File

@@ -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])

View File

@@ -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" } } */

View File

@@ -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>
"""

View File

@@ -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" } } */

View File

@@ -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], ' ^~~~~~~~')

View File

@@ -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);
}

View File

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

84
gcc/xml-printer.h Normal file
View File

@@ -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. */

113
gcc/xml.h Normal file
View File

@@ -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. */