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