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