1 /* Verify that we can write a non-trivial diagnostic output format
3 Copyright (C) 2018-2024 Free Software Foundation, Inc.
4 Contributed by David Malcolm <dmalcolm@redhat.com>.
6 This file is part of GCC.
8 GCC is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 3, or (at your option) any later
13 GCC is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
18 You should have received a copy of the GNU General Public License
19 along with GCC; see the file COPYING3. If not see
20 <http://www.gnu.org/licenses/>. */
26 #define INCLUDE_MEMORY
27 #define INCLUDE_VECTOR
29 #include "coretypes.h"
30 #include "diagnostic.h"
31 #include "diagnostic-metadata.h"
32 #include "diagnostic-path.h"
34 #include "logical-location.h"
35 #include "diagnostic-client-data-hooks.h"
36 #include "diagnostic-diagram.h"
37 #include "text-art/canvas.h"
38 #include "diagnostic-format.h"
39 #include "ordered-hash-map.h"
41 #include "make-unique.h"
43 #include "selftest-diagnostic.h"
44 #include "selftest-diagnostic-show-locus.h"
45 #include "text-range-label.h"
46 #include "pretty-print-format-impl.h"
47 #include "pretty-print-urlifier.h"
49 #include "gcc-plugin.h"
50 #include "plugin-version.h"
54 /* Disable warnings about quoting issues in the pp_xxx calls below
55 that (intentionally) don't follow GCC diagnostic conventions. */
57 # pragma GCC diagnostic push
58 # pragma GCC diagnostic ignored "-Wformat-diag"
61 static void write_escaped_text (const char *text
);
66 virtual void write_as_xml (pretty_printer
*pp
,
67 int depth
, bool indent
) const = 0;
68 void dump (FILE *out
) const;
69 void DEBUG_FUNCTION
dump () const { dump (stderr
); }
72 struct text
: public node
75 : m_str (std::move (str
))
78 void write_as_xml (pretty_printer
*pp
,
79 int depth
, bool indent
) const final override
;
84 struct node_with_children
: public node
86 void add_child (std::unique_ptr
<node
> node
);
87 void add_text (label_text str
);
89 std::vector
<std::unique_ptr
<node
>> m_children
;
92 struct document
: public node_with_children
94 void write_as_xml (pretty_printer
*pp
,
95 int depth
, bool indent
) const final override
;
98 struct element
: public node_with_children
100 element (const char *kind
, bool preserve_whitespace
)
102 m_preserve_whitespace (preserve_whitespace
)
105 void write_as_xml (pretty_printer
*pp
,
106 int depth
, bool indent
) const final override
;
108 void set_attr (const char *name
, label_text value
);
111 bool m_preserve_whitespace
;
112 std::map
<const char *, label_text
> m_attributes
;
115 /* Implementation. */
118 write_escaped_text (pretty_printer
*pp
, const char *text
)
122 for (const char *p
= text
; *p
; ++p
)
128 pp_character (pp
, ch
);
131 pp_string (pp
, "'");
134 pp_string (pp
, """);
137 pp_string (pp
, "&");
140 pp_string (pp
, "<");
143 pp_string (pp
, ">");
152 node::dump (FILE *out
) const
155 pp
.set_output_stream (out
);
156 write_as_xml (&pp
, 0, true);
160 /* struct text : public node. */
163 text::write_as_xml (pretty_printer
*pp
, int /*depth*/, bool /*indent*/) const
165 write_escaped_text (pp
, m_str
.get ());
168 /* struct node_with_children : public node. */
171 node_with_children::add_child (std::unique_ptr
<node
> node
)
173 gcc_assert (node
.get ());
174 m_children
.push_back (std::move (node
));
178 node_with_children::add_text (label_text str
)
180 gcc_assert (str
.get ());
181 add_child (::make_unique
<text
> (std::move (str
)));
185 /* struct document : public node_with_children. */
188 document::write_as_xml (pretty_printer
*pp
, int depth
, bool indent
) const
190 pp_string (pp
, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
191 pp_string (pp
, "<!DOCTYPE html\n"
192 " PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
193 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
194 for (auto &iter
: m_children
)
195 iter
->write_as_xml (pp
, depth
, indent
);
198 /* struct element : public node_with_children. */
201 element::write_as_xml (pretty_printer
*pp
, int depth
, bool indent
) const
206 for (int i
= 0; i
< depth
; ++i
)
210 if (m_preserve_whitespace
)
213 pp_printf (pp
, "<%s", m_kind
);
214 for (auto &attr
: m_attributes
)
216 pp_printf (pp
, " %s=\"", attr
.first
);
217 write_escaped_text (pp
, attr
.second
.get ());
218 pp_string (pp
, "\"");
220 if (m_children
.empty ())
221 pp_string (pp
, " />");
225 for (auto &child
: m_children
)
226 child
->write_as_xml (pp
, depth
+ 1, indent
);
230 for (int i
= 0; i
< depth
; ++i
)
233 pp_printf (pp
, "</%s>", m_kind
);
238 element::set_attr (const char *name
, label_text value
)
240 m_attributes
[name
] = std::move (value
);
244 # pragma GCC diagnostic pop
249 /* A class for managing XHTML output of diagnostics.
254 Known limitations/missing functionality:
267 xhtml_builder (diagnostic_context
&context
,
269 const line_maps
*line_maps
);
271 void on_report_diagnostic (const diagnostic_info
&diagnostic
,
272 diagnostic_t orig_diag_kind
);
273 void emit_diagram (const diagnostic_diagram
&diagram
);
276 std::unique_ptr
<xml::element
> take_current_diagnostic ()
278 return std::move (m_cur_diagnostic_element
);
281 void flush_to_file (FILE *outf
);
283 const xml::document
&get_document () const { return *m_document
; }
286 std::unique_ptr
<xml::element
>
287 make_element_for_diagnostic (const diagnostic_info
&diagnostic
,
288 diagnostic_t orig_diag_kind
);
290 diagnostic_context
&m_context
;
291 pretty_printer
&m_printer
;
292 const line_maps
*m_line_maps
;
294 std::unique_ptr
<xml::document
> m_document
;
295 xml::element
*m_diagnostics_element
;
296 std::unique_ptr
<xml::element
> m_cur_diagnostic_element
;
299 static std::unique_ptr
<xml::element
>
300 make_div (label_text class_
)
302 auto div
= ::make_unique
<xml::element
> ("div", false);
303 div
->set_attr ("class", std::move (class_
));
307 static std::unique_ptr
<xml::element
>
308 make_span (label_text class_
)
310 auto span
= ::make_unique
<xml::element
> ("span", true);
311 span
->set_attr ("class", std::move (class_
));
315 /* class xhtml_builder. */
317 /* xhtml_builder's ctor. */
319 xhtml_builder::xhtml_builder (diagnostic_context
&context
,
321 const line_maps
*line_maps
)
322 : m_context (context
),
324 m_line_maps (line_maps
)
326 gcc_assert (m_line_maps
);
328 m_document
= ::make_unique
<xml::document
> ();
330 auto html_element
= ::make_unique
<xml::element
> ("html", false);
331 html_element
->set_attr
333 label_text::borrow ("http://www.w3.org/1999/xhtml"));
336 auto head_element
= ::make_unique
<xml::element
> ("head", false);
338 auto title_element
= ::make_unique
<xml::element
> ("title", true);
339 label_text
title (label_text::borrow ("Title goes here")); // TODO
340 title_element
->add_text (std::move (title
));
341 head_element
->add_child (std::move (title_element
));
343 html_element
->add_child (std::move (head_element
));
345 auto body_element
= ::make_unique
<xml::element
> ("body", false);
347 auto diagnostics_element
348 = make_div (label_text::borrow ("gcc-diagnostic-list"));
349 m_diagnostics_element
= diagnostics_element
.get ();
350 body_element
->add_child (std::move (diagnostics_element
));
352 html_element
->add_child (std::move (body_element
));
355 m_document
->add_child (std::move (html_element
));
359 /* Implementation of "on_report_diagnostic" for XHTML output. */
362 xhtml_builder::on_report_diagnostic (const diagnostic_info
&diagnostic
,
363 diagnostic_t orig_diag_kind
)
365 // TODO: handle (diagnostic.kind == DK_ICE || diagnostic.kind == DK_ICE_NOBT)
368 = make_element_for_diagnostic (diagnostic
, orig_diag_kind
);
369 if (m_cur_diagnostic_element
)
370 /* Nested diagnostic. */
371 m_cur_diagnostic_element
->add_child (std::move (diag_element
));
373 /* Top-level diagnostic. */
374 m_cur_diagnostic_element
= std::move (diag_element
);
377 std::unique_ptr
<xml::element
>
378 xhtml_builder::make_element_for_diagnostic (const diagnostic_info
&diagnostic
,
379 diagnostic_t orig_diag_kind
)
381 class xhtml_token_printer
: public token_printer
384 xhtml_token_printer (xhtml_builder
&builder
,
385 xml::element
&parent_element
)
386 : m_builder (builder
)
388 m_open_elements
.push_back (&parent_element
);
390 void print_tokens (pretty_printer */
*pp*/
,
391 const pp_token_list
&tokens
) final override
393 /* Implement print_tokens by adding child elements to
395 for (auto iter
= tokens
.m_first
; iter
; iter
= iter
->m_next
)
396 switch (iter
->m_kind
)
401 case pp_token::kind::text
:
403 pp_token_text
*sub
= as_a
<pp_token_text
*> (iter
);
404 /* The value might be in the obstack, so we may need to
406 insertion_element ().add_text
407 (label_text::take (xstrdup (sub
->m_value
.get ())));
411 case pp_token::kind::begin_color
:
412 case pp_token::kind::end_color
:
413 /* These are no-ops. */
416 case pp_token::kind::begin_quote
:
418 insertion_element ().add_text (label_text::borrow (open_quote
));
419 push_element (make_span (label_text::borrow ("gcc-quoted-text")));
422 case pp_token::kind::end_quote
:
425 insertion_element ().add_text (label_text::borrow (close_quote
));
429 case pp_token::kind::begin_url
:
431 pp_token_begin_url
*sub
= as_a
<pp_token_begin_url
*> (iter
);
432 auto anchor
= ::make_unique
<xml::element
> ("a", true);
433 anchor
->set_attr ("href", std::move (sub
->m_value
));
434 push_element (std::move (anchor
));
437 case pp_token::kind::end_url
:
444 xml::element
&insertion_element () const
446 return *m_open_elements
.back ();
448 void push_element (std::unique_ptr
<xml::element
> new_element
)
450 xml::element
¤t_top
= insertion_element ();
451 m_open_elements
.push_back (new_element
.get ());
452 current_top
.add_child (std::move (new_element
));
456 m_open_elements
.pop_back ();
459 xhtml_builder
&m_builder
;
460 /* We maintain a stack of currently "open" elements.
461 Children are added to the topmost open element. */
462 std::vector
<xml::element
*> m_open_elements
;
465 auto diag_element
= make_div (label_text::borrow ("gcc-diagnostic"));
467 // TODO: might be nice to emulate the text output format, but colorize it
469 auto message_span
= make_span (label_text::borrow ("gcc-message"));
470 xhtml_token_printer
tok_printer (*this, *message_span
.get ());
471 m_printer
.set_token_printer (&tok_printer
);
472 pp_output_formatted_text (&m_printer
, m_context
.get_urlifier ());
473 m_printer
.set_token_printer (nullptr);
474 pp_clear_output_area (&m_printer
);
475 diag_element
->add_child (std::move (message_span
));
477 if (diagnostic
.metadata
)
479 int cwe
= diagnostic
.metadata
->get_cwe ();
482 diag_element
->add_text (label_text::borrow (" "));
483 auto cwe_span
= make_span (label_text::borrow ("gcc-cwe-metadata"));
484 cwe_span
->add_text (label_text::borrow ("["));
486 auto anchor
= ::make_unique
<xml::element
> ("a", true);
487 anchor
->set_attr ("href", label_text::take (get_cwe_url (cwe
)));
489 pp_printf (&pp
, "CWE-%i", cwe
);
491 (label_text::take (xstrdup (pp_formatted_text (&pp
))));
492 cwe_span
->add_child (std::move (anchor
));
494 cwe_span
->add_text (label_text::borrow ("]"));
495 diag_element
->add_child (std::move (cwe_span
));
499 // TODO: show any rules
501 label_text option_text
= label_text::take
502 (m_context
.make_option_name (diagnostic
.option_id
,
503 orig_diag_kind
, diagnostic
.kind
));
504 if (option_text
.get ())
506 label_text option_url
= label_text::take
507 (m_context
.make_option_url (diagnostic
.option_id
));
509 diag_element
->add_text (label_text::borrow (" "));
510 auto option_span
= make_span (label_text::borrow ("gcc-option"));
511 option_span
->add_text (label_text::borrow ("["));
513 if (option_url
.get ())
515 auto anchor
= ::make_unique
<xml::element
> ("a", true);
516 anchor
->set_attr ("href", std::move (option_url
));
517 anchor
->add_text (std::move (option_text
));
518 option_span
->add_child (std::move (anchor
));
521 option_span
->add_text (std::move (option_text
));
522 option_span
->add_text (label_text::borrow ("]"));
524 diag_element
->add_child (std::move (option_span
));
528 auto pre
= ::make_unique
<xml::element
> ("pre", true);
529 pre
->set_attr ("class", label_text::borrow ("gcc-annotated-source"));
530 // TODO: ideally we'd like to capture elements within the following:
531 diagnostic_show_locus (&m_context
, diagnostic
.richloc
, diagnostic
.kind
,
534 (label_text::take (xstrdup (pp_formatted_text (&m_printer
))));
535 pp_clear_output_area (&m_printer
);
536 diag_element
->add_child (std::move (pre
));
542 /* Implementation of diagnostic_context::m_diagrams.m_emission_cb
546 xhtml_builder::emit_diagram (const diagnostic_diagram
&/*diagram*/)
548 /* We must be within the emission of a top-level diagnostic. */
549 gcc_assert (m_cur_diagnostic_element
);
554 /* Implementation of "end_group_cb" for XHTML output. */
557 xhtml_builder::end_group ()
559 if (m_cur_diagnostic_element
)
560 m_diagnostics_element
->add_child (std::move (m_cur_diagnostic_element
));
563 /* Create a top-level object, and add it to all the results
564 (and other entities) we've seen so far.
566 Flush it all to OUTF. */
569 xhtml_builder::flush_to_file (FILE *outf
)
571 auto top
= m_document
.get ();
573 fprintf (outf
, "\n");
576 /* Callback for diagnostic_context::ice_handler_cb for when an ICE
580 xhtml_ice_handler (diagnostic_context
*context
)
582 /* Attempt to ensure that a .xhtml file is written out. */
583 diagnostic_finish (context
);
585 /* Print a header for the remaining output to stderr, and
586 return, attempting to print the usual ICE messages to
587 stderr. Hopefully this will be helpful to the user in
588 indicating what's gone wrong (also for DejaGnu, for pruning
590 fnotice (stderr
, "Internal compiler error:\n");
593 class xhtml_output_format
: public diagnostic_output_format
596 ~xhtml_output_format ()
598 /* Any diagnostics should have been handled by now.
599 If not, then something's gone wrong with diagnostic
601 std::unique_ptr
<xml::element
> pending_diag
602 = m_builder
.take_current_diagnostic ();
603 gcc_assert (!pending_diag
);
606 void on_begin_group () final override
610 void on_end_group () final override
612 m_builder
.end_group ();
615 on_report_diagnostic (const diagnostic_info
&diagnostic
,
616 diagnostic_t orig_diag_kind
) final override
618 m_builder
.on_report_diagnostic (diagnostic
, orig_diag_kind
);
620 void on_diagram (const diagnostic_diagram
&diagram
) final override
622 m_builder
.emit_diagram (diagram
);
624 void after_diagnostic (const diagnostic_info
&)
626 /* No-op, but perhaps could show paths here. */
629 const xml::document
&get_document () const
631 return m_builder
.get_document ();
635 xhtml_output_format (diagnostic_context
&context
,
636 const line_maps
*line_maps
)
637 : diagnostic_output_format (context
),
638 m_builder (context
, *get_printer (), line_maps
)
641 xhtml_builder m_builder
;
644 class xhtml_stream_output_format
: public xhtml_output_format
647 xhtml_stream_output_format (diagnostic_context
&context
,
648 const line_maps
*line_maps
,
650 : xhtml_output_format (context
, line_maps
),
654 ~xhtml_stream_output_format ()
656 m_builder
.flush_to_file (m_stream
);
658 bool machine_readable_stderr_p () const final override
660 return m_stream
== stderr
;
666 class xhtml_file_output_format
: public xhtml_output_format
669 xhtml_file_output_format (diagnostic_context
&context
,
670 const line_maps
*line_maps
,
671 const char *base_file_name
)
672 : xhtml_output_format (context
, line_maps
),
673 m_base_file_name (xstrdup (base_file_name
))
676 ~xhtml_file_output_format ()
678 char *filename
= concat (m_base_file_name
, ".xhtml", nullptr);
679 free (m_base_file_name
);
680 m_base_file_name
= nullptr;
681 FILE *outf
= fopen (filename
, "w");
684 const char *errstr
= xstrerror (errno
);
685 fnotice (stderr
, "error: unable to open '%s' for writing: %s\n",
690 m_builder
.flush_to_file (outf
);
694 bool machine_readable_stderr_p () const final override
700 char *m_base_file_name
;
703 /* Populate CONTEXT in preparation for XHTML output (either to stderr, or
707 diagnostic_output_format_init_xhtml (diagnostic_context
&context
,
708 std::unique_ptr
<xhtml_output_format
> fmt
)
710 /* Override callbacks. */
711 context
.set_ice_handler_callback (xhtml_ice_handler
);
713 /* Don't colorize the text. */
714 pp_show_color (fmt
->get_printer ()) = false;
715 context
.set_show_highlight_colors (false);
717 context
.set_output_format (fmt
.release ());
720 /* Populate CONTEXT in preparation for XHTML output to stderr. */
723 diagnostic_output_format_init_xhtml_stderr (diagnostic_context
&context
,
724 const line_maps
*line_maps
)
726 gcc_assert (line_maps
);
727 auto format
= ::make_unique
<xhtml_stream_output_format
> (context
,
730 diagnostic_output_format_init_xhtml (context
, std::move (format
));
733 /* Populate CONTEXT in preparation for XHTML output to a file named
734 BASE_FILE_NAME.xhtml. */
737 diagnostic_output_format_init_xhtml_file (diagnostic_context
&context
,
738 const line_maps
*line_maps
,
739 const char *base_file_name
)
741 gcc_assert (line_maps
);
742 auto format
= ::make_unique
<xhtml_file_output_format
> (context
,
745 diagnostic_output_format_init_xhtml (context
, std::move (format
));
752 /* A subclass of xhtml_output_format for writing selftests.
753 The XML output is cached internally, rather than written
756 class test_xhtml_diagnostic_context
: public test_diagnostic_context
759 test_xhtml_diagnostic_context ()
761 auto format
= ::make_unique
<xhtml_buffered_output_format
> (*this,
763 m_format
= format
.get (); // borrowed
764 diagnostic_output_format_init_xhtml (*this, std::move (format
));
767 const xml::document
&get_document () const
769 return m_format
->get_document ();
773 class xhtml_buffered_output_format
: public xhtml_output_format
776 xhtml_buffered_output_format (diagnostic_context
&context
,
777 const line_maps
*line_maps
)
778 : xhtml_output_format (context
, line_maps
)
781 bool machine_readable_stderr_p () const final override
787 xhtml_output_format
*m_format
; // borrowed
790 /* Test of reporting a diagnostic at UNKNOWN_LOCATION to a
791 diagnostic_context and examining the generated XML document.
792 Verify various basic properties. */
797 test_xhtml_diagnostic_context dc
;
799 rich_location
richloc (line_table
, UNKNOWN_LOCATION
);
800 dc
.report (DK_ERROR
, richloc
, nullptr, 0, "this is a test: %i", 42);
802 const xml::document
&doc
= dc
.get_document ();
805 doc
.write_as_xml (&pp
, 0, true);
807 (pp_formatted_text (&pp
),
808 ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
810 " PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
811 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
812 "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
814 " <title>Title goes here</title>\n"
817 " <div class=\"gcc-diagnostic-list\">\n"
818 " <div class=\"gcc-diagnostic\">\n"
819 " <span class=\"gcc-message\">this is a test: 42</span>\n"
820 " <pre class=\"gcc-annotated-source\"></pre>\n"
827 /* Run all of the selftests within this file. */
830 xhtml_format_selftests ()
835 } // namespace selftest
837 #endif /* CHECKING_P */
841 int plugin_is_GPL_compatible
;
843 /* Entrypoint for the plugin. */
846 plugin_init (struct plugin_name_args
*plugin_info
,
847 struct plugin_gcc_version
*version
)
849 const char *plugin_name
= plugin_info
->base_name
;
850 int argc
= plugin_info
->argc
;
851 struct plugin_argument
*argv
= plugin_info
->argv
;
853 if (!plugin_default_version_check (version
, &gcc_version
))
856 global_dc
->set_output_format (new xhtml_stream_output_format (*global_dc
,
861 selftest::xhtml_format_selftests ();