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 : 21 : html_generation_options::html_generation_options ()
57 : 21 : : m_css (true),
58 : 21 : m_javascript (true),
59 : 21 : m_show_state_diagrams (false),
60 : 21 : m_show_state_diagrams_sarif (false),
61 : 21 : m_show_state_diagrams_dot_src (false)
62 : : {
63 : 21 : }
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_state_diagrams_sarif);
72 : 0 : DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_show_state_diagrams_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_state_diagrams_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_state_diagrams_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_state_diagrams_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 : if (auto dot_graph = dg.make_dot_graph ())
1282 : 3 : if (auto svg_element = dot::make_svg_from_graph (*dot_graph))
1283 : : {
1284 : 3 : auto div = std::make_unique<xml::element> ("div", false);
1285 : 3 : div->set_attr ("class", "gcc-directed-graph");
1286 : 3 : xml::printer xp (*div);
1287 : 3 : if (const char *description = dg.get_description ())
1288 : : {
1289 : 3 : xp.push_tag ("h2", true);
1290 : 3 : xp.add_text (description);
1291 : 3 : xp.pop_tag ("h2");
1292 : : }
1293 : 3 : xp.append (std::move (svg_element));
1294 : 3 : parent_element.add_child (std::move (div));
1295 : 9 : }
1296 : 3 : }
1297 : :
1298 : : void
1299 : 1 : html_builder::emit_global_graph (const lazily_created<digraphs::digraph> &ldg)
1300 : : {
1301 : 1 : auto &dg = ldg.get_or_create ();
1302 : 1 : gcc_assert (m_body_element);
1303 : 1 : add_graph (dg, *m_body_element);
1304 : 1 : }
1305 : :
1306 : : /* Implementation of "end_group_cb" for HTML output. */
1307 : :
1308 : : void
1309 : 77 : html_builder::end_group ()
1310 : : {
1311 : 77 : if (m_cur_diagnostic_element)
1312 : 77 : m_diagnostics_element->add_child (std::move (m_cur_diagnostic_element));
1313 : 77 : }
1314 : :
1315 : : /* Create a top-level object, and add it to all the results
1316 : : (and other entities) we've seen so far.
1317 : :
1318 : : Flush it all to OUTF. */
1319 : :
1320 : : void
1321 : 13 : html_builder::flush_to_file (FILE *outf)
1322 : : {
1323 : 13 : DIAGNOSTICS_LOG_SCOPE_PRINTF0 (m_context.get_logger (),
1324 : 13 : "diagnostics::html_builder::flush_to_file");
1325 : 13 : if (m_html_gen_opts.m_javascript)
1326 : : {
1327 : 0 : gcc_assert (m_head_element);
1328 : 0 : xml::printer xp (*m_head_element);
1329 : : /* Add an initialization of the global js variable "focus_ids"
1330 : : using the array of IDs we saved as we went. */
1331 : 0 : xp.push_tag ("script");
1332 : 0 : pretty_printer pp;
1333 : 0 : pp_string (&pp, "focus_ids = ");
1334 : 0 : m_ui_focus_ids.print (&pp, true);
1335 : 0 : pp_string (&pp, ";\n");
1336 : 0 : xp.add_raw (pp_formatted_text (&pp));
1337 : 0 : xp.pop_tag ("script");
1338 : 0 : }
1339 : 13 : auto top = m_document.get ();
1340 : 13 : top->dump (outf);
1341 : 13 : fprintf (outf, "\n");
1342 : 13 : }
1343 : :
1344 : : class html_sink : public sink
1345 : : {
1346 : : public:
1347 : 21 : ~html_sink ()
1348 : 21 : {
1349 : : /* Any diagnostics should have been handled by now.
1350 : : If not, then something's gone wrong with diagnostic
1351 : : groupings. */
1352 : 21 : std::unique_ptr<xml::element> pending_diag
1353 : 21 : = m_builder.take_current_diagnostic ();
1354 : 21 : gcc_assert (!pending_diag);
1355 : 21 : }
1356 : :
1357 : 0 : void dump (FILE *out, int indent) const override
1358 : : {
1359 : 0 : sink::dump (out, indent);
1360 : 0 : dumping::emit_heading (out, indent, "html_builder");
1361 : 0 : m_builder.dump (out, indent + 2);
1362 : 0 : }
1363 : :
1364 : : void
1365 : 21 : set_main_input_filename (const char *name) final override
1366 : : {
1367 : 21 : m_builder.set_main_input_filename (name);
1368 : 13 : }
1369 : :
1370 : : std::unique_ptr<per_sink_buffer>
1371 : 0 : make_per_sink_buffer () final override
1372 : : {
1373 : 0 : return std::make_unique<html_sink_buffer> (m_builder);
1374 : : }
1375 : 0 : void set_buffer (per_sink_buffer *base_buffer) final override
1376 : : {
1377 : 0 : html_sink_buffer *buffer
1378 : : = static_cast<html_sink_buffer *> (base_buffer);
1379 : 0 : m_buffer = buffer;
1380 : 0 : }
1381 : :
1382 : 77 : void on_begin_group () final override
1383 : : {
1384 : : /* No-op, */
1385 : 77 : }
1386 : 77 : void on_end_group () final override
1387 : : {
1388 : 77 : m_builder.end_group ();
1389 : 77 : }
1390 : : void
1391 : 106 : on_report_diagnostic (const diagnostic_info &diagnostic,
1392 : : enum kind orig_diag_kind) final override
1393 : : {
1394 : 106 : DIAGNOSTICS_LOG_SCOPE_PRINTF0
1395 : : (get_logger (),
1396 : 106 : "diagnostics::html_sink::on_report_diagnostic");
1397 : 106 : m_builder.on_report_diagnostic (diagnostic, orig_diag_kind, m_buffer);
1398 : 106 : }
1399 : 0 : void on_diagram (const diagram &d) final override
1400 : : {
1401 : 0 : m_builder.emit_diagram (d);
1402 : 0 : }
1403 : 103 : void after_diagnostic (const diagnostic_info &) final override
1404 : : {
1405 : : /* No-op, but perhaps could show paths here. */
1406 : 103 : }
1407 : 0 : bool follows_reference_printer_p () const final override
1408 : : {
1409 : 0 : return false;
1410 : : }
1411 : 21 : void update_printer () final override
1412 : : {
1413 : 21 : m_printer = m_context.clone_printer ();
1414 : :
1415 : : /* Don't colorize the text. */
1416 : 21 : pp_show_color (m_printer.get ()) = false;
1417 : :
1418 : : /* No textual URLs. */
1419 : 21 : m_printer->set_url_format (URL_FORMAT_NONE);
1420 : :
1421 : : /* Update the builder to use the new printer. */
1422 : 21 : m_builder.set_printer (*get_printer ());
1423 : 21 : }
1424 : :
1425 : : void
1426 : 1 : report_global_digraph (const lazily_created<digraphs::digraph> &ldg)
1427 : : final override
1428 : : {
1429 : 1 : m_builder.emit_global_graph (ldg);
1430 : 1 : }
1431 : :
1432 : 4 : const xml::document &get_document () const
1433 : : {
1434 : 4 : return m_builder.get_document ();
1435 : : }
1436 : :
1437 : 4 : html_builder &get_builder () { return m_builder; }
1438 : :
1439 : : protected:
1440 : 21 : html_sink (context &dc,
1441 : : const line_maps *line_maps,
1442 : : const html_generation_options &html_gen_opts)
1443 : 21 : : sink (dc),
1444 : 21 : m_builder (dc, *get_printer (), line_maps, html_gen_opts),
1445 : 21 : m_buffer (nullptr)
1446 : 21 : {}
1447 : :
1448 : : html_builder m_builder;
1449 : : html_sink_buffer *m_buffer;
1450 : : };
1451 : :
1452 : : class html_file_sink : public html_sink
1453 : : {
1454 : : public:
1455 : 13 : html_file_sink (context &dc,
1456 : : const line_maps *line_maps,
1457 : : const html_generation_options &html_gen_opts,
1458 : : output_file output_file_)
1459 : 13 : : html_sink (dc, line_maps, html_gen_opts),
1460 : 13 : m_output_file (std::move (output_file_))
1461 : : {
1462 : 13 : gcc_assert (m_output_file.get_open_file ());
1463 : 13 : gcc_assert (m_output_file.get_filename ());
1464 : 13 : }
1465 : 26 : ~html_file_sink ()
1466 : 13 : {
1467 : 13 : m_builder.flush_to_file (m_output_file.get_open_file ());
1468 : 26 : }
1469 : 0 : void dump_kind (FILE *out) const override
1470 : : {
1471 : 0 : fprintf (out, "html_file_sink: %s",
1472 : : m_output_file.get_filename ());
1473 : 0 : }
1474 : 7 : bool machine_readable_stderr_p () const final override
1475 : : {
1476 : 7 : return false;
1477 : : }
1478 : :
1479 : : private:
1480 : : output_file m_output_file;
1481 : : };
1482 : :
1483 : : /* Attempt to open BASE_FILE_NAME.html for writing.
1484 : : Return a non-null output_file,
1485 : : or return a null output_file and complain to DC
1486 : : using LINE_MAPS. */
1487 : :
1488 : : output_file
1489 : 13 : open_html_output_file (context &dc,
1490 : : line_maps *line_maps,
1491 : : const char *base_file_name)
1492 : : {
1493 : 13 : if (!base_file_name)
1494 : : {
1495 : 0 : rich_location richloc (line_maps, UNKNOWN_LOCATION);
1496 : 0 : dc.emit_diagnostic_with_group
1497 : 0 : (kind::error, richloc, nullptr, 0,
1498 : : "unable to determine filename for HTML output");
1499 : 0 : return output_file ();
1500 : 0 : }
1501 : :
1502 : 13 : label_text filename = label_text::take (concat (base_file_name,
1503 : : ".html",
1504 : 13 : nullptr));
1505 : 13 : FILE *outf = fopen (filename.get (), "w");
1506 : 13 : if (!outf)
1507 : : {
1508 : 0 : rich_location richloc (line_maps, UNKNOWN_LOCATION);
1509 : 0 : dc.emit_diagnostic_with_group
1510 : 0 : (kind::error, richloc, nullptr, 0,
1511 : : "unable to open %qs for HTML output: %m",
1512 : : filename.get ());
1513 : 0 : return output_file ();
1514 : 0 : }
1515 : 13 : return output_file (outf, true, std::move (filename));
1516 : 13 : }
1517 : :
1518 : : std::unique_ptr<sink>
1519 : 13 : make_html_sink (context &dc,
1520 : : const line_maps &line_maps,
1521 : : const html_generation_options &html_gen_opts,
1522 : : output_file output_file_)
1523 : : {
1524 : 13 : auto sink
1525 : : = std::make_unique<html_file_sink> (dc,
1526 : 26 : &line_maps,
1527 : : html_gen_opts,
1528 : 13 : std::move (output_file_));
1529 : 13 : sink->update_printer ();
1530 : 13 : return sink;
1531 : 13 : }
1532 : :
1533 : : #if CHECKING_P
1534 : :
1535 : : namespace selftest {
1536 : :
1537 : : /* Helper for writing tests of html_token_printer.
1538 : : Printing to m_pp will appear as HTML within m_top_element, a <div>. */
1539 : :
1540 : : struct token_printer_test
1541 : : {
1542 : 12 : token_printer_test ()
1543 : 12 : : m_top_element ("div", true),
1544 : 12 : m_tok_printer (m_top_element)
1545 : : {
1546 : 12 : m_pp.set_token_printer (&m_tok_printer);
1547 : 12 : }
1548 : :
1549 : : xml::element m_top_element;
1550 : : html_token_printer m_tok_printer;
1551 : : pretty_printer m_pp;
1552 : : };
1553 : :
1554 : : static void
1555 : 4 : test_token_printer ()
1556 : : {
1557 : 4 : {
1558 : 4 : token_printer_test t;
1559 : 4 : pp_printf (&t.m_pp, "hello world");
1560 : 4 : ASSERT_XML_PRINT_EQ
1561 : : (t.m_top_element,
1562 : : "<div>hello world</div>\n");
1563 : 4 : }
1564 : :
1565 : 4 : {
1566 : 4 : token_printer_test t;
1567 : 4 : pp_printf (&t.m_pp, "%qs: %qs", "foo", "bar");
1568 : 4 : ASSERT_XML_PRINT_EQ
1569 : : (t.m_top_element,
1570 : : "<div>"
1571 : : "`"
1572 : : "<span class=\"gcc-quoted-text\">"
1573 : : "foo"
1574 : : "</span>"
1575 : : "': `"
1576 : : "<span class=\"gcc-quoted-text\">"
1577 : : "bar"
1578 : : "</span>"
1579 : : "'"
1580 : : "</div>\n");
1581 : 4 : }
1582 : :
1583 : 4 : {
1584 : 4 : token_printer_test t;
1585 : 4 : paths::event_id_t event_id (0);
1586 : 4 : pp_printf (&t.m_pp, "foo %@ bar", &event_id);
1587 : 4 : ASSERT_XML_PRINT_EQ
1588 : : (t.m_top_element,
1589 : : "<div>foo (1) bar</div>\n");
1590 : 4 : }
1591 : 4 : }
1592 : :
1593 : : /* A subclass of html_sink for writing selftests.
1594 : : The XML output is cached internally, rather than written
1595 : : out to a file. */
1596 : :
1597 : 16 : class test_html_context : public test_context
1598 : : {
1599 : : public:
1600 : 8 : test_html_context ()
1601 : 8 : {
1602 : 8 : html_generation_options html_gen_opts;
1603 : 8 : html_gen_opts.m_css = false;
1604 : 8 : html_gen_opts.m_javascript = false;
1605 : 8 : auto sink = std::make_unique<html_buffered_sink> (*this,
1606 : : line_table,
1607 : 8 : html_gen_opts);
1608 : 8 : sink->update_printer ();
1609 : 8 : sink->set_main_input_filename ("(main input filename)");
1610 : 8 : m_format = sink.get (); // borrowed
1611 : :
1612 : 8 : set_sink (std::move (sink));
1613 : 8 : }
1614 : :
1615 : 4 : const xml::document &get_document () const
1616 : : {
1617 : 4 : return m_format->get_document ();
1618 : : }
1619 : :
1620 : 4 : html_builder &get_builder () const
1621 : : {
1622 : 4 : return m_format->get_builder ();
1623 : : }
1624 : :
1625 : : private:
1626 : : class html_buffered_sink : public html_sink
1627 : : {
1628 : : public:
1629 : 8 : html_buffered_sink (context &dc,
1630 : : const line_maps *line_maps,
1631 : : const html_generation_options &html_gen_opts)
1632 : 8 : : html_sink (dc, line_maps, html_gen_opts)
1633 : : {
1634 : : }
1635 : 0 : void dump_kind (FILE *out) const final override
1636 : : {
1637 : 0 : fprintf (out, "html_buffered_sink");
1638 : 0 : }
1639 : 0 : bool machine_readable_stderr_p () const final override
1640 : : {
1641 : 0 : return true;
1642 : : }
1643 : : };
1644 : :
1645 : : html_sink *m_format; // borrowed
1646 : : };
1647 : :
1648 : : /* Test of reporting a diagnostic at UNKNOWN_LOCATION to a
1649 : : diagnostics::context and examining the generated XML document.
1650 : : Verify various basic properties. */
1651 : :
1652 : : static void
1653 : 4 : test_simple_log ()
1654 : : {
1655 : 4 : test_html_context dc;
1656 : :
1657 : 4 : rich_location richloc (line_table, UNKNOWN_LOCATION);
1658 : 4 : dc.report (kind::error, richloc, nullptr, 0, "this is a test: %qs", "foo");
1659 : :
1660 : 4 : const xml::document &doc = dc.get_document ();
1661 : :
1662 : 4 : ASSERT_XML_PRINT_EQ
1663 : : (doc,
1664 : : ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1665 : : "<!DOCTYPE html\n"
1666 : : " PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
1667 : : " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
1668 : : "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
1669 : : " <head>\n"
1670 : : " <title>(main input filename)</title>\n"
1671 : : " </head>\n"
1672 : : " <body>\n"
1673 : : " <div class=\"gcc-diagnostic-list\">\n"
1674 : : " <div class=\"alert alert-danger\" id=\"gcc-diag-0\">\n"
1675 : : " <span class=\"pficon pficon-error-circle-o\"> </span>\n"
1676 : : " <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"
1677 : : " </div>\n"
1678 : : " </div>\n"
1679 : : " </body>\n"
1680 : : "</html>\n"));
1681 : 4 : }
1682 : :
1683 : : static void
1684 : 4 : test_metadata ()
1685 : : {
1686 : 4 : test_html_context dc;
1687 : 4 : html_builder &b = dc.get_builder ();
1688 : :
1689 : 4 : {
1690 : 4 : metadata m;
1691 : 4 : m.add_cwe (415);
1692 : 4 : auto element = b.make_element_for_metadata (m);
1693 : 4 : ASSERT_XML_PRINT_EQ
1694 : : (*element,
1695 : : "<span class=\"gcc-metadata\">"
1696 : : "<span class=\"gcc-metadata-item\">"
1697 : : "["
1698 : : "<a href=\"https://cwe.mitre.org/data/definitions/415.html\">"
1699 : : "CWE-415"
1700 : : "</a>"
1701 : : "]"
1702 : : "</span>"
1703 : : "</span>\n");
1704 : 4 : }
1705 : :
1706 : 4 : {
1707 : 4 : metadata m;
1708 : 4 : metadata::precanned_rule rule ("MISC-42",
1709 : 4 : "http://example.com");
1710 : 4 : m.add_rule (rule);
1711 : 4 : auto element = b.make_element_for_metadata (m);
1712 : 4 : ASSERT_XML_PRINT_EQ
1713 : : (*element,
1714 : : "<span class=\"gcc-metadata\">"
1715 : : "<span class=\"gcc-metadata-item\">"
1716 : : "["
1717 : : "<a href=\"http://example.com\">"
1718 : : "MISC-42"
1719 : : "</a>"
1720 : : "]"
1721 : : "</span>"
1722 : : "</span>\n");
1723 : 4 : }
1724 : 4 : }
1725 : :
1726 : : /* Run all of the selftests within this file. */
1727 : :
1728 : : void
1729 : 4 : html_sink_cc_tests ()
1730 : : {
1731 : 4 : ::selftest::auto_fix_quotes fix_quotes;
1732 : 4 : test_token_printer ();
1733 : 4 : test_simple_log ();
1734 : 4 : test_metadata ();
1735 : 4 : }
1736 : :
1737 : : } // namespace selftest
1738 : :
1739 : : #endif /* CHECKING_P */
1740 : :
1741 : : } // namespace diagnostics
|