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