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