libcpp, c, middle-end: Optimize initializers using #embed in C
[official-gcc.git] / gcc / testsuite / gcc.dg / plugin / diagnostic_plugin_xhtml_format.c
blob2ce8cc29660cdd3f1c83c83ed81dd585336e4e91
1 /* Verify that we can write a non-trivial diagnostic output format
2 as a plugin (XHTML).
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
11 version.
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
16 for more details.
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/>. */
23 #include "config.h"
24 #define INCLUDE_LIST
25 #define INCLUDE_MAP
26 #define INCLUDE_MEMORY
27 #define INCLUDE_VECTOR
28 #include "system.h"
29 #include "coretypes.h"
30 #include "diagnostic.h"
31 #include "diagnostic-metadata.h"
32 #include "diagnostic-path.h"
33 #include "cpplib.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"
40 #include "sbitmap.h"
41 #include "make-unique.h"
42 #include "selftest.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"
48 #include "intl.h"
49 #include "gcc-plugin.h"
50 #include "plugin-version.h"
52 namespace xml {
54 /* Disable warnings about quoting issues in the pp_xxx calls below
55 that (intentionally) don't follow GCC diagnostic conventions. */
56 #if __GNUC__ >= 10
57 # pragma GCC diagnostic push
58 # pragma GCC diagnostic ignored "-Wformat-diag"
59 #endif
61 static void write_escaped_text (const char *text);
63 struct node
65 virtual ~node () {}
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
74 text (label_text str)
75 : m_str (std::move (str))
78 void write_as_xml (pretty_printer *pp,
79 int depth, bool indent) const final override;
81 label_text m_str;
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)
101 : m_kind (kind),
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);
110 const char *m_kind;
111 bool m_preserve_whitespace;
112 std::map<const char *, label_text> m_attributes;
115 /* Implementation. */
117 static void
118 write_escaped_text (pretty_printer *pp, const char *text)
120 gcc_assert (text);
122 for (const char *p = text; *p; ++p)
124 char ch = *p;
125 switch (ch)
127 default:
128 pp_character (pp, ch);
129 break;
130 case '\'':
131 pp_string (pp, "&apos;");
132 break;
133 case '"':
134 pp_string (pp, "&quot;");
135 break;
136 case '&':
137 pp_string (pp, "&amp;");
138 break;
139 case '<':
140 pp_string (pp, "&lt;");
141 break;
142 case '>':
143 pp_string (pp, "&gt;");
144 break;
149 /* struct node. */
151 void
152 node::dump (FILE *out) const
154 pretty_printer pp;
155 pp.set_output_stream (out);
156 write_as_xml (&pp, 0, true);
157 pp_flush (&pp);
160 /* struct text : public node. */
162 void
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. */
170 void
171 node_with_children::add_child (std::unique_ptr<node> node)
173 gcc_assert (node.get ());
174 m_children.push_back (std::move (node));
177 void
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. */
187 void
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. */
200 void
201 element::write_as_xml (pretty_printer *pp, int depth, bool indent) const
203 if (indent)
205 pp_newline (pp);
206 for (int i = 0; i < depth; ++i)
207 pp_string (pp, " ");
210 if (m_preserve_whitespace)
211 indent = false;
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, " />");
222 else
224 pp_string (pp, ">");
225 for (auto &child : m_children)
226 child->write_as_xml (pp, depth + 1, indent);
227 if (indent)
229 pp_newline (pp);
230 for (int i = 0; i < depth; ++i)
231 pp_string (pp, " ");
233 pp_printf (pp, "</%s>", m_kind);
237 void
238 element::set_attr (const char *name, label_text value)
240 m_attributes[name] = std::move (value);
243 #if __GNUC__ >= 10
244 # pragma GCC diagnostic pop
245 #endif
247 } // namespace xml
249 /* A class for managing XHTML output of diagnostics.
251 Implemented:
252 - message text
254 Known limitations/missing functionality:
255 - title for page
256 - file/line/column
257 - error vs warning
258 - CWEs
259 - rules
260 - fix-it hints
261 - paths
264 class xhtml_builder
266 public:
267 xhtml_builder (diagnostic_context &context,
268 pretty_printer &pp,
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);
274 void end_group ();
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; }
285 private:
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_));
304 return div;
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_));
312 return span;
315 /* class xhtml_builder. */
317 /* xhtml_builder's ctor. */
319 xhtml_builder::xhtml_builder (diagnostic_context &context,
320 pretty_printer &pp,
321 const line_maps *line_maps)
322 : m_context (context),
323 m_printer (pp),
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
332 ("xmlns",
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. */
361 void
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)
367 auto diag_element
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));
372 else
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
383 public:
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
394 m_parent_element. */
395 for (auto iter = tokens.m_first; iter; iter = iter->m_next)
396 switch (iter->m_kind)
398 default:
399 gcc_unreachable ();
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
405 copy it. */
406 insertion_element ().add_text
407 (label_text::take (xstrdup (sub->m_value.get ())));
409 break;
411 case pp_token::kind::begin_color:
412 case pp_token::kind::end_color:
413 /* These are no-ops. */
414 break;
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")));
421 break;
422 case pp_token::kind::end_quote:
424 pop_element ();
425 insertion_element ().add_text (label_text::borrow (close_quote));
427 break;
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));
436 break;
437 case pp_token::kind::end_url:
438 pop_element ();
439 break;
443 private:
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 &current_top = insertion_element ();
451 m_open_elements.push_back (new_element.get ());
452 current_top.add_child (std::move (new_element));
454 void pop_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 ();
480 if (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)));
488 pretty_printer pp;
489 pp_printf (&pp, "CWE-%i", cwe);
490 anchor->add_text
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));
520 else
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,
532 &m_printer);
533 pre->add_text
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));
539 return diag_element;
542 /* Implementation of diagnostic_context::m_diagrams.m_emission_cb
543 for XHTML output. */
545 void
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);
551 // TODO
554 /* Implementation of "end_group_cb" for XHTML output. */
556 void
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. */
568 void
569 xhtml_builder::flush_to_file (FILE *outf)
571 auto top = m_document.get ();
572 top->dump (outf);
573 fprintf (outf, "\n");
576 /* Callback for diagnostic_context::ice_handler_cb for when an ICE
577 occurs. */
579 static void
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
589 those messages). */
590 fnotice (stderr, "Internal compiler error:\n");
593 class xhtml_output_format : public diagnostic_output_format
595 public:
596 ~xhtml_output_format ()
598 /* Any diagnostics should have been handled by now.
599 If not, then something's gone wrong with diagnostic
600 groupings. */
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
608 /* No-op, */
610 void on_end_group () final override
612 m_builder.end_group ();
614 void
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 ();
634 protected:
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
646 public:
647 xhtml_stream_output_format (diagnostic_context &context,
648 const line_maps *line_maps,
649 FILE *stream)
650 : xhtml_output_format (context, line_maps),
651 m_stream (stream)
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;
662 private:
663 FILE *m_stream;
666 class xhtml_file_output_format : public xhtml_output_format
668 public:
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");
682 if (!outf)
684 const char *errstr = xstrerror (errno);
685 fnotice (stderr, "error: unable to open '%s' for writing: %s\n",
686 filename, errstr);
687 free (filename);
688 return;
690 m_builder.flush_to_file (outf);
691 fclose (outf);
692 free (filename);
694 bool machine_readable_stderr_p () const final override
696 return false;
699 private:
700 char *m_base_file_name;
703 /* Populate CONTEXT in preparation for XHTML output (either to stderr, or
704 to a file). */
706 static void
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. */
722 void
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,
728 line_maps,
729 stderr);
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. */
736 void
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,
743 line_maps,
744 base_file_name);
745 diagnostic_output_format_init_xhtml (context, std::move (format));
748 #if CHECKING_P
750 namespace selftest {
752 /* A subclass of xhtml_output_format for writing selftests.
753 The XML output is cached internally, rather than written
754 out to a file. */
756 class test_xhtml_diagnostic_context : public test_diagnostic_context
758 public:
759 test_xhtml_diagnostic_context ()
761 auto format = ::make_unique<xhtml_buffered_output_format> (*this,
762 line_table);
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 ();
772 private:
773 class xhtml_buffered_output_format : public xhtml_output_format
775 public:
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
783 return true;
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. */
794 static void
795 test_simple_log ()
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 ();
804 pretty_printer pp;
805 doc.write_as_xml (&pp, 0, true);
806 ASSERT_STREQ
807 (pp_formatted_text (&pp),
808 ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
809 "<!DOCTYPE html\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"
813 " <head>\n"
814 " <title>Title goes here</title>\n"
815 " </head>\n"
816 " <body>\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"
821 " </div>\n"
822 " </div>\n"
823 " </body>\n"
824 "</html>"));
827 /* Run all of the selftests within this file. */
829 static void
830 xhtml_format_selftests ()
832 test_simple_log ();
835 } // namespace selftest
837 #endif /* CHECKING_P */
839 /* Plugin hooks. */
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))
854 return 1;
856 global_dc->set_output_format (new xhtml_stream_output_format (*global_dc,
857 line_table,
858 stderr));
860 #if CHECKING_P
861 selftest::xhtml_format_selftests ();
862 #endif
864 return 0;