1/* HTML output for diagnostics.
2 Copyright (C) 2024-2025 Free Software Foundation, Inc.
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
4
5This file is part of GCC.
6
7GCC is free software; you can redistribute it and/or modify it under
8the terms of the GNU General Public License as published by the Free
9Software Foundation; either version 3, or (at your option) any later
10version.
11
12GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13WARRANTY; without even the implied warranty of MERCHANTABILITY or
14FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15for more details.
16
17You should have received a copy of the GNU General Public License
18along with GCC; see the file COPYING3. If not see
19<http://www.gnu.org/licenses/>. */
20
21#include "config.h"
22#define INCLUDE_MAP
23#define INCLUDE_STRING
24#define INCLUDE_VECTOR
25#include "system.h"
26#include "coretypes.h"
27#include "diagnostic.h"
28#include "diagnostic-metadata.h"
29#include "diagnostic-format.h"
30#include "diagnostic-format-html.h"
31#include "diagnostic-format-text.h"
32#include "diagnostic-output-file.h"
33#include "diagnostic-buffer.h"
34#include "diagnostic-path.h"
35#include "diagnostic-client-data-hooks.h"
36#include "selftest.h"
37#include "selftest-diagnostic.h"
38#include "pretty-print-format-impl.h"
39#include "pretty-print-urlifier.h"
40#include "edit-context.h"
41#include "intl.h"
42#include "xml.h"
43#include "xml-printer.h"
44#include "json.h"
45#include "selftest-xml.h"
46
47// struct html_generation_options
48
49html_generation_options::html_generation_options ()
50: m_css (true),
51 m_javascript (true)
52{
53}
54
55class html_builder;
56
57/* Concrete buffering implementation subclass for HTML output. */
58
59class diagnostic_html_format_buffer : public diagnostic_per_format_buffer
60{
61public:
62 friend class html_builder;
63 friend class html_output_format;
64
65 diagnostic_html_format_buffer (html_builder &builder)
66 : m_builder (builder)
67 {}
68
69 void dump (FILE *out, int indent) const final override;
70 bool empty_p () const final override;
71 void move_to (diagnostic_per_format_buffer &dest) final override;
72 void clear () final override;
73 void flush () final override;
74
75 void add_result (std::unique_ptr<xml::element> result)
76 {
77 m_results.push_back (x: std::move (result));
78 }
79
80private:
81 html_builder &m_builder;
82 std::vector<std::unique_ptr<xml::element>> m_results;
83};
84
85/* A class for managing HTML output of diagnostics.
86
87 Implemented:
88 - message text
89
90 Known limitations/missing functionality:
91 - title for page
92 - file/line/column
93 - error vs warning
94 - CWEs
95 - rules
96 - fix-it hints
97 - paths
98*/
99
100class html_builder
101{
102public:
103 friend class diagnostic_html_format_buffer;
104
105 html_builder (diagnostic_context &context,
106 pretty_printer &pp,
107 const line_maps *line_maps,
108 const html_generation_options &html_gen_opts);
109
110 void
111 set_main_input_filename (const char *name);
112
113 void on_report_diagnostic (const diagnostic_info &diagnostic,
114 diagnostic_t orig_diag_kind,
115 diagnostic_html_format_buffer *buffer);
116 void emit_diagram (const diagnostic_diagram &diagram);
117 void end_group ();
118
119 std::unique_ptr<xml::element> take_current_diagnostic ()
120 {
121 return std::move (m_cur_diagnostic_element);
122 }
123
124 void flush_to_file (FILE *outf);
125
126 const xml::document &get_document () const { return *m_document; }
127
128 void set_printer (pretty_printer &pp)
129 {
130 m_printer = &pp;
131 }
132
133 std::unique_ptr<xml::element>
134 make_element_for_metadata (const diagnostic_metadata &metadata);
135
136 std::unique_ptr<xml::element>
137 make_element_for_patch (const diagnostic_info &diagnostic);
138
139 void add_focus_id (std::string focus_id)
140 {
141 m_ui_focus_ids.append_string (utf8_value: focus_id.c_str ());
142 }
143
144private:
145 void
146 add_stylesheet (std::string url);
147
148 std::unique_ptr<xml::element>
149 make_element_for_diagnostic (const diagnostic_info &diagnostic,
150 diagnostic_t orig_diag_kind,
151 bool alert);
152
153 std::unique_ptr<xml::element>
154 make_metadata_element (label_text label,
155 label_text url);
156
157 void
158 add_at_nesting_level (size_t nesting_level,
159 std::unique_ptr<xml::element> child_diag_element);
160
161 void
162 push_nesting_level ();
163
164 void
165 pop_nesting_level ();
166
167 diagnostic_context &m_context;
168 pretty_printer *m_printer;
169 const line_maps *m_line_maps;
170 html_generation_options m_html_gen_opts;
171 const logical_location_manager *m_logical_loc_mgr;
172
173 std::unique_ptr<xml::document> m_document;
174 xml::element *m_head_element;
175 xml::element *m_title_element;
176 xml::element *m_diagnostics_element;
177 std::unique_ptr<xml::element> m_cur_diagnostic_element;
178 std::vector<xml::element *> m_cur_nesting_levels;
179 int m_next_diag_id; // for handing out unique IDs
180 json::array m_ui_focus_ids;
181 logical_location m_last_logical_location;
182 location_t m_last_location;
183 expanded_location m_last_expanded_location;
184};
185
186static std::unique_ptr<xml::element>
187make_div (std::string class_)
188{
189 auto div = std::make_unique<xml::element> (args: "div", args: false);
190 div->set_attr (name: "class", value: std::move (class_));
191 return div;
192}
193
194static std::unique_ptr<xml::element>
195make_span (std::string class_)
196{
197 auto span = std::make_unique<xml::element> (args: "span", args: true);
198 span->set_attr (name: "class", value: std::move (class_));
199 return span;
200}
201
202/* class diagnostic_html_format_buffer : public diagnostic_per_format_buffer. */
203
204void
205diagnostic_html_format_buffer::dump (FILE *out, int indent) const
206{
207 fprintf (stream: out, format: "%*sdiagnostic_html_format_buffer:\n", indent, "");
208 int idx = 0;
209 for (auto &result : m_results)
210 {
211 fprintf (stream: out, format: "%*sresult[%i]:\n", indent + 2, "", idx);
212 result->dump (out);
213 fprintf (stream: out, format: "\n");
214 ++idx;
215 }
216}
217
218bool
219diagnostic_html_format_buffer::empty_p () const
220{
221 return m_results.empty ();
222}
223
224void
225diagnostic_html_format_buffer::move_to (diagnostic_per_format_buffer &base)
226{
227 diagnostic_html_format_buffer &dest
228 = static_cast<diagnostic_html_format_buffer &> (base);
229 for (auto &&result : m_results)
230 dest.m_results.push_back (x: std::move (result));
231 m_results.clear ();
232}
233
234void
235diagnostic_html_format_buffer::clear ()
236{
237 m_results.clear ();
238}
239
240void
241diagnostic_html_format_buffer::flush ()
242{
243 for (auto &&result : m_results)
244 m_builder.m_diagnostics_element->add_child (node: std::move (result));
245 m_results.clear ();
246}
247
248/* class html_builder. */
249
250/* Style information for writing out HTML paths.
251 Colors taken from https://pf3.patternfly.org/v3/styles/color-palette/ */
252
253static const char * const HTML_STYLE
254 = (" <style>\n"
255 " .linenum { color: white;\n"
256 " background-color: #0088ce;\n"
257 " white-space: pre;\n"
258 " border-right: 1px solid black; }\n"
259 " .ruler { color: red;\n"
260 " white-space: pre; }\n"
261 " .source { color: blue;\n"
262 " background-color: white;\n"
263 " white-space: pre; }\n"
264 " .annotation { color: green;\n"
265 " background-color: white;\n"
266 " white-space: pre; }\n"
267 " .linenum-gap { text-align: center;\n"
268 " border-top: 1px solid black;\n"
269 " border-right: 1px solid black;\n"
270 " background-color: #ededed; }\n"
271 " .source-gap { border-bottom: 1px dashed black;\n"
272 " border-top: 1px dashed black;\n"
273 " background-color: #ededed; }\n"
274 " .no-locus-event { font-family: monospace;\n"
275 " color: green;\n"
276 " white-space: pre; }\n"
277 " .funcname { font-weight: bold; }\n"
278 " .events-hdr { color: white;\n"
279 " background-color: #030303; }\n"
280 " .event-range { border: 1px solid black;\n"
281 " padding: 0px; }\n"
282 " .event-range-with-margin { border-spacing: 0; }\n"
283 " .locus { font-family: monospace;\n"
284 " border-spacing: 0px; }\n"
285 " .selected { color: white;\n"
286 " background-color: #0088ce; }\n"
287 " .stack-frame-with-margin { border-spacing: 0; }\n"
288 " .stack-frame { padding: 5px;\n"
289 " box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.5); }\n"
290 " .frame-funcname { text-align: right;\n"
291 " font-style: italic; } \n"
292 " .highlight-a { color: #703fec;\n" // pf-purple-400
293 " font-weight: bold; }\n"
294 " .highlight-b { color: #3f9c35;\n" // pf-green-400
295 " font-weight: bold; }\n"
296 " .gcc-quoted-text { font-weight: bold;\n"
297 " font-family: mono; }\n"
298 " </style>\n");
299
300/* A little JavaScript for ease of navigation.
301 Keys j/k move forward and backward cyclically through a list
302 of focus ids (written out in another <script> tag as the HTML
303 is flushed). */
304
305const char * const HTML_SCRIPT
306 = (" var current_focus_idx = 0;\n"
307 "\n"
308 " function get_focus_span (focus_idx)\n"
309 " {\n"
310 " const element_id = focus_ids[focus_idx];\n"
311 " return document.getElementById(element_id);\n"
312 " }\n"
313 " function unhighlight_current_focus_idx ()\n"
314 " {\n"
315 " get_focus_span (current_focus_idx).classList.remove ('selected');\n"
316 " }\n"
317 " function highlight_current_focus_idx ()\n"
318 " {\n"
319 " const el = get_focus_span (current_focus_idx);\n"
320 " el.classList.add ('selected');\n"
321 " // Center the element on the screen\n"
322 " const top_y = el.getBoundingClientRect ().top + window.pageYOffset;\n"
323 " const middle = top_y - (window.innerHeight / 2);\n"
324 " window.scrollTo (0, middle);\n"
325 " }\n"
326 " function select_prev_focus_idx ()\n"
327 " {\n"
328 " unhighlight_current_focus_idx ();\n"
329 " if (current_focus_idx > 0)\n"
330 " current_focus_idx -= 1;\n"
331 " else\n"
332 " current_focus_idx = focus_ids.length - 1;\n"
333 " highlight_current_focus_idx ();\n"
334 " }\n"
335 " function select_next_focus_idx ()\n"
336 " {\n"
337 " unhighlight_current_focus_idx ();\n"
338 " if (current_focus_idx < focus_ids.length - 1)\n"
339 " current_focus_idx += 1;\n"
340 " else\n"
341 " current_focus_idx = 0;\n"
342 " highlight_current_focus_idx ();\n"
343 " }\n"
344 " document.addEventListener('keydown', function (ev) {\n"
345 " if (ev.key == 'j')\n"
346 " select_next_focus_idx ();\n"
347 " else if (ev.key == 'k')\n"
348 " select_prev_focus_idx ();\n"
349 " });\n"
350 " highlight_current_focus_idx ();\n");
351
352struct html_doctypedecl : public xml::doctypedecl
353{
354 void write_as_xml (pretty_printer *pp,
355 int depth, bool indent) const final override
356 {
357 if (indent)
358 {
359 for (int i = 0; i < depth; ++i)
360 pp_string (pp, " ");
361 }
362 pp_string (pp, "<!DOCTYPE html\n"
363 " PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
364 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
365 if (indent)
366 pp_newline (pp);
367 }
368};
369
370/* html_builder's ctor. */
371
372html_builder::html_builder (diagnostic_context &context,
373 pretty_printer &pp,
374 const line_maps *line_maps,
375 const html_generation_options &html_gen_opts)
376: m_context (context),
377 m_printer (&pp),
378 m_line_maps (line_maps),
379 m_html_gen_opts (html_gen_opts),
380 m_logical_loc_mgr (nullptr),
381 m_head_element (nullptr),
382 m_title_element (nullptr),
383 m_diagnostics_element (nullptr),
384 m_next_diag_id (0),
385 m_last_location (UNKNOWN_LOCATION),
386 m_last_expanded_location ({})
387{
388 gcc_assert (m_line_maps);
389
390 if (auto client_data_hooks = context.get_client_data_hooks ())
391 m_logical_loc_mgr = client_data_hooks->get_logical_location_manager ();
392
393 m_document = std::make_unique<xml::document> ();
394 m_document->m_doctypedecl = std::make_unique<html_doctypedecl> ();
395 {
396 auto html_element = std::make_unique<xml::element> (args: "html", args: false);
397 html_element->set_attr (name: "xmlns",
398 value: "http://www.w3.org/1999/xhtml");
399 xml::printer xp (*html_element.get ());
400 m_document->add_child (node: std::move (html_element));
401
402 {
403 xml::auto_print_element head (xp, "head");
404 m_head_element = xp.get_insertion_point ();
405 {
406 xml::auto_print_element title (xp, "title", true);
407 m_title_element = xp.get_insertion_point ();
408 }
409
410 if (m_html_gen_opts.m_css)
411 {
412 add_stylesheet (url: "https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly.min.css");
413 add_stylesheet (url: "https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly-additions.min.css");
414 xp.add_raw (text: HTML_STYLE);
415 }
416 if (m_html_gen_opts.m_javascript)
417 {
418 xp.push_tag (name: "script");
419 /* Escaping rules are different for HTML <script> elements,
420 so add the script "raw" for now. */
421 xp.add_raw (text: HTML_SCRIPT);
422 xp.pop_tag (expected_name: "script");
423 }
424 }
425
426 {
427 xml::auto_print_element body (xp, "body");
428 {
429 auto diagnostics_element = make_div (class_: "gcc-diagnostic-list");
430 m_diagnostics_element = diagnostics_element.get ();
431 xp.append (new_node: std::move (diagnostics_element));
432 }
433 }
434 }
435}
436
437void
438html_builder::set_main_input_filename (const char *name)
439{
440 gcc_assert (m_title_element);
441 if (name)
442 {
443 m_title_element->m_children.clear ();
444 m_title_element->add_text (str: name);
445 }
446}
447
448void
449html_builder::add_stylesheet (std::string url)
450{
451 gcc_assert (m_head_element);
452
453 xml::printer xp (*m_head_element);
454 xp.push_tag (name: "link", preserve_whitespace: false);
455 xp.set_attr (name: "rel", value: "stylesheet");
456 xp.set_attr (name: "type", value: "text/css");
457 xp.set_attr (name: "href", value: std::move (url));
458}
459
460/* Implementation of "on_report_diagnostic" for HTML output. */
461
462void
463html_builder::on_report_diagnostic (const diagnostic_info &diagnostic,
464 diagnostic_t orig_diag_kind,
465 diagnostic_html_format_buffer *buffer)
466{
467 if (diagnostic.kind == DK_ICE || diagnostic.kind == DK_ICE_NOBT)
468 {
469 /* Print a header for the remaining output to stderr, and
470 return, attempting to print the usual ICE messages to
471 stderr. Hopefully this will be helpful to the user in
472 indicating what's gone wrong (also for DejaGnu, for pruning
473 those messages). */
474 fnotice (stderr, "Internal compiler error:\n");
475 }
476
477 const int nesting_level = m_context.get_diagnostic_nesting_level ();
478 bool alert = true;
479 if (m_cur_diagnostic_element && nesting_level > 0)
480 alert = false;
481 if (!m_cur_diagnostic_element)
482 m_last_logical_location = logical_location ();
483 auto diag_element
484 = make_element_for_diagnostic (diagnostic, orig_diag_kind, alert);
485 if (buffer)
486 {
487 gcc_assert (!m_cur_diagnostic_element);
488 buffer->m_results.push_back (x: std::move (diag_element));
489 }
490 else
491 {
492 if (m_cur_diagnostic_element)
493 {
494 /* Nested diagnostic. */
495 gcc_assert (nesting_level >= 0);
496 add_at_nesting_level (nesting_level, child_diag_element: std::move (diag_element));
497 }
498 else
499 /* Top-level diagnostic. */
500 {
501 m_cur_diagnostic_element = std::move (diag_element);
502 m_cur_nesting_levels.clear ();
503 }
504 }
505}
506
507// For ease of comparison with experimental-nesting-show-levels=yes
508
509static void
510add_nesting_level_attr (xml::element &element,
511 int nesting_level)
512{
513 element.set_attr (name: "nesting-level", value: std::to_string (val: nesting_level));
514}
515
516void
517html_builder::
518add_at_nesting_level (size_t nesting_level,
519 std::unique_ptr<xml::element> child_diag_element)
520{
521 gcc_assert (m_cur_diagnostic_element);
522 while (nesting_level > m_cur_nesting_levels.size ())
523 push_nesting_level ();
524 while (nesting_level < m_cur_nesting_levels.size ())
525 pop_nesting_level ();
526
527 if (nesting_level > 0)
528 {
529 gcc_assert (!m_cur_nesting_levels.empty ());
530 auto current_nesting_level = m_cur_nesting_levels.back ();
531 xml::printer xp (*current_nesting_level);
532 xp.push_tag (name: "li");
533 add_nesting_level_attr (element&: *xp.get_insertion_point (),
534 nesting_level: m_cur_nesting_levels.size ());
535 xp.append (new_node: std::move (child_diag_element));
536 xp.pop_tag (expected_name: "li");
537 }
538 else
539 m_cur_diagnostic_element->add_child (node: std::move (child_diag_element));
540}
541
542void
543html_builder::push_nesting_level ()
544{
545 gcc_assert (m_cur_diagnostic_element);
546 auto new_nesting_level = std::make_unique<xml::element> (args: "ul", args: false);
547 add_nesting_level_attr (element&: *new_nesting_level,
548 nesting_level: m_cur_nesting_levels.size () + 1);
549 xml::element *current_nesting_level = nullptr;
550 if (!m_cur_nesting_levels.empty ())
551 current_nesting_level = m_cur_nesting_levels.back ();
552 m_cur_nesting_levels.push_back (x: new_nesting_level.get ());
553 if (current_nesting_level)
554 current_nesting_level->add_child (node: std::move (new_nesting_level));
555 else
556 m_cur_diagnostic_element->add_child (node: std::move (new_nesting_level));
557}
558
559void
560html_builder::pop_nesting_level ()
561{
562 gcc_assert (m_cur_diagnostic_element);
563 m_cur_nesting_levels.pop_back ();
564}
565
566/* Custom subclass of html_label_writer.
567 Wrap labels within a <span> element, supplying them with event IDs.
568 Add the IDs to the list of focus IDs. */
569
570class html_path_label_writer : public html_label_writer
571{
572public:
573 html_path_label_writer (xml::printer &xp,
574 html_builder &builder,
575 const std::string &event_id_prefix)
576 : m_xp (xp),
577 m_html_builder (builder),
578 m_event_id_prefix (event_id_prefix),
579 m_next_event_idx (0)
580 {
581 }
582
583 void begin_label () final override
584 {
585 m_xp.push_tag_with_class (name: "span", class_: "event", preserve_whitespace: true);
586 pretty_printer pp;
587 pp_printf (&pp, "%s%i",
588 m_event_id_prefix.c_str (), m_next_event_idx++);
589 m_xp.set_attr (name: "id", value: pp_formatted_text (&pp));
590 m_html_builder.add_focus_id (focus_id: pp_formatted_text (&pp));
591 }
592
593 void end_label () final override
594 {
595 m_xp.pop_tag (expected_name: "span"); // from begin_label
596 }
597
598private:
599 xml::printer &m_xp;
600 html_builder &m_html_builder;
601 const std::string &m_event_id_prefix;
602 int m_next_event_idx;
603};
604
605/* See https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts */
606static const char *
607get_pf_class_for_alert_div (diagnostic_t diag_kind)
608{
609 switch (diag_kind)
610 {
611 case DK_DEBUG:
612 case DK_NOTE:
613 return "alert alert-info";
614
615 case DK_ANACHRONISM:
616 case DK_WARNING:
617 return "alert alert-warning";
618
619 case DK_ERROR:
620 case DK_SORRY:
621 case DK_ICE:
622 case DK_ICE_NOBT:
623 case DK_FATAL:
624 return "alert alert-danger";
625
626 default:
627 gcc_unreachable ();
628 }
629}
630
631static const char *
632get_pf_class_for_alert_icon (diagnostic_t diag_kind)
633{
634 switch (diag_kind)
635 {
636 case DK_DEBUG:
637 case DK_NOTE:
638 return "pficon pficon-info";
639
640 case DK_ANACHRONISM:
641 case DK_WARNING:
642 return "pficon pficon-warning-triangle-o";
643
644 case DK_ERROR:
645 case DK_SORRY:
646 case DK_ICE:
647 case DK_ICE_NOBT:
648 case DK_FATAL:
649 return "pficon pficon-error-circle-o";
650
651 default:
652 gcc_unreachable ();
653 }
654}
655
656static const char *
657get_label_for_logical_location_kind (enum logical_location_kind kind)
658{
659 switch (kind)
660 {
661 default:
662 gcc_unreachable ();
663 case logical_location_kind::unknown:
664 return nullptr;
665
666 /* Kinds within executable code. */
667 case logical_location_kind::function:
668 return "Function";
669 case logical_location_kind::member:
670 return "Member";
671 case logical_location_kind::module_:
672 return "Module";
673 case logical_location_kind::namespace_:
674 return "Namespace";
675 case logical_location_kind::type:
676 return "Type";
677 case logical_location_kind::return_type:
678 return "Return type";
679 case logical_location_kind::parameter:
680 return "Parameter";
681 case logical_location_kind::variable:
682 return "Variable";
683
684 /* Kinds within XML or HTML documents. */
685 case logical_location_kind::element:
686 return "Element";
687 case logical_location_kind::attribute:
688 return "Attribute";
689 case logical_location_kind::text:
690 return "Text";
691 case logical_location_kind::comment:
692 return "Comment";
693 case logical_location_kind::processing_instruction:
694 return "Processing Instruction";
695 case logical_location_kind::dtd:
696 return "DTD";
697 case logical_location_kind::declaration:
698 return "Declaration";
699
700 /* Kinds within JSON documents. */
701 case logical_location_kind::object:
702 return "Object";
703 case logical_location_kind::array:
704 return "Array";
705 case logical_location_kind::property:
706 return "Property";
707 case logical_location_kind::value:
708 return "Value";
709 }
710}
711
712static void
713add_labelled_value (xml::printer &xp,
714 std::string id,
715 std::string label,
716 std::string value,
717 bool quote_value)
718{
719 xp.push_tag (name: "div", preserve_whitespace: true);
720 xp.set_attr (name: "id", value: id);
721 xp.push_tag (name: "span");
722 xp.add_text (text: label);
723 xp.add_text (text: " ");
724 xp.pop_tag (expected_name: "span");
725 xp.push_tag (name: "span");
726 if (quote_value)
727 xp.set_attr (name: "class", value: "gcc-quoted-text");
728 xp.add_text (text: std::move (value));
729 xp.pop_tag (expected_name: "span");
730 xp.pop_tag (expected_name: "div");
731}
732
733class html_token_printer : public token_printer
734{
735public:
736 html_token_printer (xml::element &parent_element)
737 /* Ideally pp_token_lists that reach a token_printer should be
738 "balanced", but for now they can have mismatching pp_tokens
739 e.g. a begin_color without an end_color (PR other/120610).
740 Give html_token_printer its own xml::printer as a firewall to
741 limit the scope of the mismatches in the HTML. */
742 : m_xp (parent_element,
743 /* Similarly we don't check that the popped tags match. */
744 false)
745 {
746 }
747 void print_tokens (pretty_printer */*pp*/,
748 const pp_token_list &tokens) final override
749 {
750 /* Implement print_tokens by adding child elements to
751 m_parent_element. */
752 for (auto iter = tokens.m_first; iter; iter = iter->m_next)
753 switch (iter->m_kind)
754 {
755 default:
756 gcc_unreachable ();
757
758 case pp_token::kind::text:
759 {
760 pp_token_text *sub = as_a <pp_token_text *> (p: iter);
761 /* The value might be in the obstack, so we may need to
762 copy it. */
763 m_xp.add_text (text: sub->m_value.get ());
764 }
765 break;
766
767 case pp_token::kind::begin_color:
768 {
769 pp_token_begin_color *sub = as_a <pp_token_begin_color *> (p: iter);
770 gcc_assert (sub->m_value.get ());
771 m_xp.push_tag_with_class (name: "span", class_: sub->m_value.get ());
772 }
773 break;
774
775 case pp_token::kind::end_color:
776 m_xp.pop_tag (expected_name: "span");
777 break;
778
779 case pp_token::kind::begin_quote:
780 {
781 m_xp.add_text (text: open_quote);
782 m_xp.push_tag_with_class (name: "span", class_: "gcc-quoted-text");
783 }
784 break;
785 case pp_token::kind::end_quote:
786 {
787 m_xp.pop_tag (expected_name: "span");
788 m_xp.add_text (text: close_quote);
789 }
790 break;
791
792 case pp_token::kind::begin_url:
793 {
794 pp_token_begin_url *sub = as_a <pp_token_begin_url *> (p: iter);
795 m_xp.push_tag (name: "a", preserve_whitespace: true);
796 m_xp.set_attr (name: "href", value: sub->m_value.get ());
797 }
798 break;
799 case pp_token::kind::end_url:
800 m_xp.pop_tag (expected_name: "a");
801 break;
802 }
803 }
804
805private:
806 xml::printer m_xp;
807};
808
809/* Make a <div class="gcc-diagnostic"> for DIAGNOSTIC.
810
811 If ALERT is true, make it be a PatternFly alert (see
812 https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts) and
813 show severity text (e.g. "error: ").
814
815 If ALERT is false, don't show the severity text and don't show
816 the filename if it's the same as the previous diagnostic within the
817 diagnostic group. */
818
819std::unique_ptr<xml::element>
820html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
821 diagnostic_t orig_diag_kind,
822 bool alert)
823{
824 const int diag_idx = m_next_diag_id++;
825 std::string diag_id;
826 {
827 pretty_printer pp;
828 pp_printf (&pp, "gcc-diag-%i", diag_idx);
829 diag_id = pp_formatted_text (&pp);
830 }
831
832 // TODO: might be nice to emulate the text output format, but colorize it
833
834 /* See https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts
835 which has this example:
836<div class="alert alert-danger">
837 <span class="pficon pficon-error-circle-o"></span>
838 <strong>Hey there is a problem!</strong> Yeah this is really messed up and you should <a href="#" class="alert-link">know about it</a>.
839</div>
840 */
841 auto diag_element = make_div (class_: "gcc-diagnostic");
842 diag_element->set_attr (name: "id", value: diag_id);
843 if (alert)
844 diag_element->set_attr (name: "class",
845 value: get_pf_class_for_alert_div (diag_kind: diagnostic.kind));
846
847 xml::printer xp (*diag_element.get ());
848 const size_t depth_within_alert_div = 1;
849
850 gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
851
852 if (alert)
853 {
854 xp.push_tag_with_class (name: "span",
855 class_: get_pf_class_for_alert_icon (diag_kind: diagnostic.kind),
856 preserve_whitespace: true);
857 xp.add_text (text: " ");
858 xp.pop_tag (expected_name: "span");
859 }
860
861 // The rest goes in the <div>...
862 gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
863
864 xp.push_tag_with_class (name: "div", class_: "gcc-message", preserve_whitespace: true);
865 std::string message_alert_id (diag_id + "-message");
866 xp.set_attr (name: "id", value: message_alert_id);
867 add_focus_id (focus_id: message_alert_id);
868
869 const size_t depth_within_message_div = depth_within_alert_div + 1;
870 gcc_assert (xp.get_num_open_tags () == depth_within_message_div);
871
872 // Severity e.g. "warning: "
873 bool show_severity = true;
874 if (!alert)
875 show_severity = false;
876 if (show_severity)
877 {
878 xp.push_tag (name: "strong");
879 xp.add_text (_(get_diagnostic_kind_text (diagnostic.kind)));
880 xp.pop_tag (expected_name: "strong");
881 xp.add_text (text: " ");
882 }
883
884 // Add the message itself:
885 html_token_printer tok_printer (*xp.get_insertion_point ());
886 m_printer->set_token_printer (&tok_printer);
887 pp_output_formatted_text (m_printer, m_context.get_urlifier ());
888 m_printer->set_token_printer (nullptr);
889 pp_clear_output_area (m_printer);
890
891 // Add any metadata as a suffix to the message
892 if (diagnostic.metadata)
893 {
894 xp.add_text (text: " ");
895 xp.append (new_node: make_element_for_metadata (metadata: *diagnostic.metadata));
896 }
897
898 // Add any option as a suffix to the message
899
900 label_text option_text = label_text::take
901 (buffer: m_context.make_option_name (option_id: diagnostic.option_id,
902 orig_diag_kind, diag_kind: diagnostic.kind));
903 if (option_text.get ())
904 {
905 label_text option_url = label_text::take
906 (buffer: m_context.make_option_url (option_id: diagnostic.option_id));
907
908 xp.add_text (text: " ");
909 auto option_span = make_span (class_: "gcc-option");
910 option_span->add_text (str: "[");
911 {
912 if (option_url.get ())
913 {
914 auto anchor = std::make_unique<xml::element> (args: "a", args: true);
915 anchor->set_attr (name: "href", value: option_url.get ());
916 anchor->add_text (str: option_text.get ());
917 option_span->add_child (node: std::move (anchor));
918 }
919 else
920 option_span->add_text (str: option_text.get ());
921 option_span->add_text (str: "]");
922 }
923 xp.append (new_node: std::move (option_span));
924 }
925
926 gcc_assert (xp.get_num_open_tags () == depth_within_message_div);
927
928 xp.pop_tag (expected_name: "div");
929
930 gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
931
932 /* Show any logical location. */
933 if (m_logical_loc_mgr)
934 if (auto client_data_hooks = m_context.get_client_data_hooks ())
935 if (auto logical_loc = client_data_hooks->get_current_logical_location ())
936 if (logical_loc != m_last_logical_location)
937 {
938 enum logical_location_kind kind
939 = m_logical_loc_mgr->get_kind (k: logical_loc);;
940 if (const char *label = get_label_for_logical_location_kind (kind))
941 if (const char *name_with_scope
942 = m_logical_loc_mgr->get_name_with_scope (k: logical_loc))
943 add_labelled_value (xp, id: "logical-location",
944 label, value: name_with_scope, quote_value: true);
945 m_last_logical_location = logical_loc;
946 }
947
948 /* Show any physical location. */
949 const expanded_location s
950 = diagnostic_expand_location (diagnostic: &diagnostic);
951 if (s != m_last_expanded_location
952 || alert)
953 {
954 if (s.file
955 && (s.file != m_last_expanded_location.file
956 || alert))
957 add_labelled_value (xp, id: "file", label: "File", value: s.file, quote_value: false);
958 if (s.line)
959 {
960 add_labelled_value (xp, id: "line", label: "Line", value: std::to_string (val: s.line), quote_value: false);
961 diagnostic_column_policy column_policy (m_context);
962 int converted_column = column_policy.converted_column (s);
963 if (converted_column >= 0)
964 add_labelled_value (xp, id: "column", label: "Column",
965 value: std::to_string (val: converted_column),
966 quote_value: false);
967 }
968 if (s.file)
969 m_last_expanded_location = s;
970 }
971
972 /* Source (and fix-it hints). */
973 {
974 // TODO: m_context.m_last_location should be moved into the sink
975 location_t saved = m_context.m_last_location;
976 m_context.m_last_location = m_last_location;
977 m_context.maybe_show_locus_as_html (richloc: *diagnostic.richloc,
978 opts: m_context.m_source_printing,
979 diagnostic_kind: diagnostic.kind,
980 xp,
981 effect_info: nullptr,
982 label_writer: nullptr);
983 m_context.m_last_location = saved;
984 m_last_location = m_context.m_last_location;
985 }
986
987 gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
988
989 /* Execution path. */
990 if (auto path = diagnostic.richloc->get_path ())
991 {
992 xp.push_tag (name: "div");
993 xp.set_attr (name: "id", value: "execution-path");
994
995 xp.push_tag (name: "label", preserve_whitespace: true);
996 const int num_events = path->num_events ();
997 pretty_printer pp;
998 pp_printf_n (&pp, n: num_events,
999 "Execution path with %i event",
1000 "Execution path with %i events",
1001 num_events);
1002 xp.add_text_from_pp (pp);
1003 xp.pop_tag (expected_name: "label");
1004
1005 std::string event_id_prefix (diag_id + "-event-");
1006 html_path_label_writer event_label_writer (xp, *this,
1007 event_id_prefix);
1008 diagnostic_source_print_policy dspp (m_context);
1009 print_path_as_html (xp, path: *path, dc&: m_context, event_label_writer: &event_label_writer,
1010 dspp);
1011
1012 xp.pop_tag (expected_name: "div");
1013 }
1014
1015 gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
1016
1017 if (auto patch_element = make_element_for_patch (diagnostic))
1018 {
1019 xp.push_tag (name: "div");
1020 xp.set_attr (name: "id", value: "suggested-fix");
1021 xp.push_tag (name: "label", preserve_whitespace: true);
1022 xp.add_text (text: "Suggested fix");
1023 xp.pop_tag (expected_name: "label");
1024 xp.append (new_node: std::move (patch_element));
1025 xp.pop_tag (expected_name: "div");
1026 }
1027
1028 return diag_element;
1029}
1030
1031std::unique_ptr<xml::element>
1032html_builder::make_element_for_patch (const diagnostic_info &diagnostic)
1033{
1034 edit_context ec (m_context.get_file_cache ());
1035 ec.add_fixits (richloc: diagnostic.richloc);
1036 if (char *diff = ec.generate_diff (show_filenames: true))
1037 {
1038 if (strlen (s: diff) > 0)
1039 {
1040 auto element = std::make_unique<xml::element> (args: "pre", args: true);
1041 element->set_attr (name: "class", value: "gcc-generated-patch");
1042 element->add_text (str: diff);
1043 free (ptr: diff);
1044 return element;
1045 }
1046 else
1047 free (ptr: diff);
1048 }
1049 return nullptr;
1050}
1051
1052std::unique_ptr<xml::element>
1053html_builder::make_metadata_element (label_text label,
1054 label_text url)
1055{
1056 auto item = make_span (class_: "gcc-metadata-item");
1057 xml::printer xp (*item.get ());
1058 xp.add_text (text: "[");
1059 {
1060 if (url.get ())
1061 {
1062 xp.push_tag (name: "a", preserve_whitespace: true);
1063 xp.set_attr (name: "href", value: url.get ());
1064 }
1065 xp.add_text (text: label.get ());
1066 if (url.get ())
1067 xp.pop_tag (expected_name: "a");
1068 }
1069 xp.add_text (text: "]");
1070 return item;
1071}
1072
1073std::unique_ptr<xml::element>
1074html_builder::make_element_for_metadata (const diagnostic_metadata &metadata)
1075{
1076 auto span_metadata = make_span (class_: "gcc-metadata");
1077
1078 int cwe = metadata.get_cwe ();
1079 if (cwe)
1080 {
1081 pretty_printer pp;
1082 pp_printf (&pp, "CWE-%i", cwe);
1083 label_text label = label_text::take (buffer: xstrdup (pp_formatted_text (&pp)));
1084 label_text url = label_text::take (buffer: get_cwe_url (cwe));
1085 span_metadata->add_child
1086 (node: make_metadata_element (label: std::move (label), url: std::move (url)));
1087 }
1088
1089 for (unsigned idx = 0; idx < metadata.get_num_rules (); ++idx)
1090 {
1091 auto &rule = metadata.get_rule (idx);
1092 label_text label = label_text::take (buffer: rule.make_description ());
1093 label_text url = label_text::take (buffer: rule.make_url ());
1094 span_metadata->add_child
1095 (node: make_metadata_element (label: std::move (label), url: std::move (url)));
1096 }
1097
1098 return span_metadata;
1099}
1100
1101/* Implementation of diagnostic_context::m_diagrams.m_emission_cb
1102 for HTML output. */
1103
1104void
1105html_builder::emit_diagram (const diagnostic_diagram &/*diagram*/)
1106{
1107 /* We must be within the emission of a top-level diagnostic. */
1108 gcc_assert (m_cur_diagnostic_element);
1109
1110 // TODO: currently a no-op
1111}
1112
1113/* Implementation of "end_group_cb" for HTML output. */
1114
1115void
1116html_builder::end_group ()
1117{
1118 if (m_cur_diagnostic_element)
1119 m_diagnostics_element->add_child (node: std::move (m_cur_diagnostic_element));
1120}
1121
1122/* Create a top-level object, and add it to all the results
1123 (and other entities) we've seen so far.
1124
1125 Flush it all to OUTF. */
1126
1127void
1128html_builder::flush_to_file (FILE *outf)
1129{
1130 if (m_html_gen_opts.m_javascript)
1131 {
1132 gcc_assert (m_head_element);
1133 xml::printer xp (*m_head_element);
1134 /* Add an initialization of the global js variable "focus_ids"
1135 using the array of IDs we saved as we went. */
1136 xp.push_tag (name: "script");
1137 pretty_printer pp;
1138 pp_string (&pp, "focus_ids = ");
1139 m_ui_focus_ids.print (pp: &pp, formatted: true);
1140 pp_string (&pp, ";\n");
1141 xp.add_raw (text: pp_formatted_text (&pp));
1142 xp.pop_tag (expected_name: "script");
1143 }
1144 auto top = m_document.get ();
1145 top->dump (out: outf);
1146 fprintf (stream: outf, format: "\n");
1147}
1148
1149class html_output_format : public diagnostic_output_format
1150{
1151public:
1152 ~html_output_format ()
1153 {
1154 /* Any diagnostics should have been handled by now.
1155 If not, then something's gone wrong with diagnostic
1156 groupings. */
1157 std::unique_ptr<xml::element> pending_diag
1158 = m_builder.take_current_diagnostic ();
1159 gcc_assert (!pending_diag);
1160 }
1161
1162 void dump (FILE *out, int indent) const override
1163 {
1164 fprintf (stream: out, format: "%*shtml_output_format\n", indent, "");
1165 diagnostic_output_format::dump (out, indent);
1166 }
1167
1168 void
1169 set_main_input_filename (const char *name) final override
1170 {
1171 m_builder.set_main_input_filename (name);
1172 }
1173
1174 std::unique_ptr<diagnostic_per_format_buffer>
1175 make_per_format_buffer () final override
1176 {
1177 return std::make_unique<diagnostic_html_format_buffer> (args&: m_builder);
1178 }
1179 void set_buffer (diagnostic_per_format_buffer *base_buffer) final override
1180 {
1181 diagnostic_html_format_buffer *buffer
1182 = static_cast<diagnostic_html_format_buffer *> (base_buffer);
1183 m_buffer = buffer;
1184 }
1185
1186 void on_begin_group () final override
1187 {
1188 /* No-op, */
1189 }
1190 void on_end_group () final override
1191 {
1192 m_builder.end_group ();
1193 }
1194 void
1195 on_report_diagnostic (const diagnostic_info &diagnostic,
1196 diagnostic_t orig_diag_kind) final override
1197 {
1198 m_builder.on_report_diagnostic (diagnostic, orig_diag_kind, buffer: m_buffer);
1199 }
1200 void on_diagram (const diagnostic_diagram &diagram) final override
1201 {
1202 m_builder.emit_diagram (diagram);
1203 }
1204 void after_diagnostic (const diagnostic_info &)
1205 {
1206 /* No-op, but perhaps could show paths here. */
1207 }
1208 bool follows_reference_printer_p () const final override
1209 {
1210 return false;
1211 }
1212 void update_printer () final override
1213 {
1214 m_printer = m_context.clone_printer ();
1215
1216 /* Don't colorize the text. */
1217 pp_show_color (pp: m_printer.get ()) = false;
1218
1219 /* No textual URLs. */
1220 m_printer->set_url_format (URL_FORMAT_NONE);
1221
1222 /* Update the builder to use the new printer. */
1223 m_builder.set_printer (*get_printer ());
1224 }
1225
1226 const xml::document &get_document () const
1227 {
1228 return m_builder.get_document ();
1229 }
1230
1231 html_builder &get_builder () { return m_builder; }
1232
1233protected:
1234 html_output_format (diagnostic_context &context,
1235 const line_maps *line_maps,
1236 const html_generation_options &html_gen_opts)
1237 : diagnostic_output_format (context),
1238 m_builder (context, *get_printer (), line_maps, html_gen_opts),
1239 m_buffer (nullptr)
1240 {}
1241
1242 html_builder m_builder;
1243 diagnostic_html_format_buffer *m_buffer;
1244};
1245
1246class html_file_output_format : public html_output_format
1247{
1248public:
1249 html_file_output_format (diagnostic_context &context,
1250 const line_maps *line_maps,
1251 const html_generation_options &html_gen_opts,
1252 diagnostic_output_file output_file)
1253 : html_output_format (context, line_maps, html_gen_opts),
1254 m_output_file (std::move (output_file))
1255 {
1256 gcc_assert (m_output_file.get_open_file ());
1257 gcc_assert (m_output_file.get_filename ());
1258 }
1259 ~html_file_output_format ()
1260 {
1261 m_builder.flush_to_file (outf: m_output_file.get_open_file ());
1262 }
1263 void dump (FILE *out, int indent) const override
1264 {
1265 fprintf (stream: out, format: "%*shtml_file_output_format: %s\n",
1266 indent, "",
1267 m_output_file.get_filename ());
1268 diagnostic_output_format::dump (out, indent);
1269 }
1270 bool machine_readable_stderr_p () const final override
1271 {
1272 return false;
1273 }
1274
1275private:
1276 diagnostic_output_file m_output_file;
1277};
1278
1279/* Attempt to open BASE_FILE_NAME.html for writing.
1280 Return a non-null diagnostic_output_file,
1281 or return a null diagnostic_output_file and complain to CONTEXT
1282 using LINE_MAPS. */
1283
1284diagnostic_output_file
1285diagnostic_output_format_open_html_file (diagnostic_context &context,
1286 line_maps *line_maps,
1287 const char *base_file_name)
1288{
1289 if (!base_file_name)
1290 {
1291 rich_location richloc (line_maps, UNKNOWN_LOCATION);
1292 context.emit_diagnostic_with_group
1293 (kind: DK_ERROR, richloc, metadata: nullptr, option_id: 0,
1294 gmsgid: "unable to determine filename for HTML output");
1295 return diagnostic_output_file ();
1296 }
1297
1298 label_text filename = label_text::take (buffer: concat (base_file_name,
1299 ".html",
1300 nullptr));
1301 FILE *outf = fopen (filename: filename.get (), modes: "w");
1302 if (!outf)
1303 {
1304 rich_location richloc (line_maps, UNKNOWN_LOCATION);
1305 context.emit_diagnostic_with_group
1306 (kind: DK_ERROR, richloc, metadata: nullptr, option_id: 0,
1307 gmsgid: "unable to open %qs for HTML output: %m",
1308 filename.get ());
1309 return diagnostic_output_file ();
1310 }
1311 return diagnostic_output_file (outf, true, std::move (filename));
1312}
1313
1314std::unique_ptr<diagnostic_output_format>
1315make_html_sink (diagnostic_context &context,
1316 const line_maps &line_maps,
1317 const html_generation_options &html_gen_opts,
1318 diagnostic_output_file output_file)
1319{
1320 auto sink
1321 = std::make_unique<html_file_output_format> (args&: context,
1322 args: &line_maps,
1323 args: html_gen_opts,
1324 args: std::move (output_file));
1325 sink->update_printer ();
1326 return sink;
1327}
1328
1329#if CHECKING_P
1330
1331namespace selftest {
1332
1333/* Helper for writing tests of html_token_printer.
1334 Printing to m_pp will appear as HTML within m_top_element, a <div>. */
1335
1336struct token_printer_test
1337{
1338 token_printer_test ()
1339 : m_top_element ("div", true),
1340 m_tok_printer (m_top_element)
1341 {
1342 m_pp.set_token_printer (&m_tok_printer);
1343 }
1344
1345 xml::element m_top_element;
1346 html_token_printer m_tok_printer;
1347 pretty_printer m_pp;
1348};
1349
1350static void
1351test_token_printer ()
1352{
1353 {
1354 token_printer_test t;
1355 pp_printf (&t.m_pp, "hello world");
1356 ASSERT_XML_PRINT_EQ
1357 (t.m_top_element,
1358 "<div>hello world</div>\n");
1359 }
1360
1361 {
1362 token_printer_test t;
1363 pp_printf (&t.m_pp, "%qs: %qs", "foo", "bar");
1364 ASSERT_XML_PRINT_EQ
1365 (t.m_top_element,
1366 "<div>"
1367 "`"
1368 "<span class=\"gcc-quoted-text\">"
1369 "foo"
1370 "</span>"
1371 "&apos;: `"
1372 "<span class=\"gcc-quoted-text\">"
1373 "bar"
1374 "</span>"
1375 "&apos;"
1376 "</div>\n");
1377 }
1378}
1379
1380/* A subclass of html_output_format for writing selftests.
1381 The XML output is cached internally, rather than written
1382 out to a file. */
1383
1384class test_html_diagnostic_context : public test_diagnostic_context
1385{
1386public:
1387 test_html_diagnostic_context ()
1388 {
1389 html_generation_options html_gen_opts;
1390 html_gen_opts.m_css = false;
1391 html_gen_opts.m_javascript = false;
1392 auto sink = std::make_unique<html_buffered_output_format> (args&: *this,
1393 args&: line_table,
1394 args&: html_gen_opts);
1395 sink->update_printer ();
1396 sink->set_main_input_filename ("(main input filename)");
1397 m_format = sink.get (); // borrowed
1398
1399 set_output_format (std::move (sink));
1400 }
1401
1402 const xml::document &get_document () const
1403 {
1404 return m_format->get_document ();
1405 }
1406
1407 html_builder &get_builder () const
1408 {
1409 return m_format->get_builder ();
1410 }
1411
1412private:
1413 class html_buffered_output_format : public html_output_format
1414 {
1415 public:
1416 html_buffered_output_format (diagnostic_context &context,
1417 const line_maps *line_maps,
1418 const html_generation_options &html_gen_opts)
1419 : html_output_format (context, line_maps, html_gen_opts)
1420 {
1421 }
1422 bool machine_readable_stderr_p () const final override
1423 {
1424 return true;
1425 }
1426 };
1427
1428 html_output_format *m_format; // borrowed
1429};
1430
1431/* Test of reporting a diagnostic at UNKNOWN_LOCATION to a
1432 diagnostic_context and examining the generated XML document.
1433 Verify various basic properties. */
1434
1435static void
1436test_simple_log ()
1437{
1438 test_html_diagnostic_context dc;
1439
1440 rich_location richloc (line_table, UNKNOWN_LOCATION);
1441 dc.report (kind: DK_ERROR, richloc, metadata: nullptr, option: 0, fmt: "this is a test: %qs", "foo");
1442
1443 const xml::document &doc = dc.get_document ();
1444
1445 ASSERT_XML_PRINT_EQ
1446 (doc,
1447 ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1448 "<!DOCTYPE html\n"
1449 " PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
1450 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
1451 "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
1452 " <head>\n"
1453 " <title>(main input filename)</title>\n"
1454 " </head>\n"
1455 " <body>\n"
1456 " <div class=\"gcc-diagnostic-list\">\n"
1457 " <div class=\"alert alert-danger\" id=\"gcc-diag-0\">\n"
1458 " <span class=\"pficon pficon-error-circle-o\"> </span>\n"
1459 " <div class=\"gcc-message\" id=\"gcc-diag-0-message\"><strong>error: </strong> this is a test: `<span class=\"gcc-quoted-text\">foo</span>&apos;</div>\n"
1460 " </div>\n"
1461 " </div>\n"
1462 " </body>\n"
1463 "</html>\n"));
1464}
1465
1466static void
1467test_metadata ()
1468{
1469 test_html_diagnostic_context dc;
1470 html_builder &b = dc.get_builder ();
1471
1472 {
1473 diagnostic_metadata metadata;
1474 metadata.add_cwe (cwe: 415);
1475 auto element = b.make_element_for_metadata (metadata);
1476 ASSERT_XML_PRINT_EQ
1477 (*element,
1478 "<span class=\"gcc-metadata\">"
1479 "<span class=\"gcc-metadata-item\">"
1480 "["
1481 "<a href=\"https://cwe.mitre.org/data/definitions/415.html\">"
1482 "CWE-415"
1483 "</a>"
1484 "]"
1485 "</span>"
1486 "</span>\n");
1487 }
1488
1489 {
1490 diagnostic_metadata metadata;
1491 diagnostic_metadata::precanned_rule rule ("MISC-42",
1492 "http://example.com");
1493 metadata.add_rule (r: rule);
1494 auto element = b.make_element_for_metadata (metadata);
1495 ASSERT_XML_PRINT_EQ
1496 (*element,
1497 "<span class=\"gcc-metadata\">"
1498 "<span class=\"gcc-metadata-item\">"
1499 "["
1500 "<a href=\"http://example.com\">"
1501 "MISC-42"
1502 "</a>"
1503 "]"
1504 "</span>"
1505 "</span>\n");
1506 }
1507}
1508
1509/* Run all of the selftests within this file. */
1510
1511void
1512diagnostic_format_html_cc_tests ()
1513{
1514 auto_fix_quotes fix_quotes;
1515 test_token_printer ();
1516 test_simple_log ();
1517 test_metadata ();
1518}
1519
1520} // namespace selftest
1521
1522#endif /* CHECKING_P */
1523

source code of gcc/diagnostic-format-html.cc