| 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 | html_generation_options::html_generation_options () |
| 50 | : m_css (true), |
| 51 | m_javascript (true) |
| 52 | { |
| 53 | } |
| 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 | diagnostic_html_format_buffer (html_builder &builder) |
| 66 | : 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 (x: 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 | std::unique_ptr<xml::element> take_current_diagnostic () |
| 120 | { |
| 121 | return std::move (m_cur_diagnostic_element); |
| 122 | } |
| 123 | |
| 124 | void flush_to_file (FILE *outf); |
| 125 | |
| 126 | const xml::document &get_document () const { return *m_document; } |
| 127 | |
| 128 | void set_printer (pretty_printer &pp) |
| 129 | { |
| 130 | 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 | void add_focus_id (std::string focus_id) |
| 140 | { |
| 141 | m_ui_focus_ids.append_string (utf8_value: 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 | make_div (std::string class_) |
| 188 | { |
| 189 | auto div = std::make_unique<xml::element> (args: "div" , args: false); |
| 190 | div->set_attr (name: "class" , value: std::move (class_)); |
| 191 | return div; |
| 192 | } |
| 193 | |
| 194 | static std::unique_ptr<xml::element> |
| 195 | make_span (std::string class_) |
| 196 | { |
| 197 | auto span = std::make_unique<xml::element> (args: "span" , args: true); |
| 198 | span->set_attr (name: "class" , value: std::move (class_)); |
| 199 | return span; |
| 200 | } |
| 201 | |
| 202 | /* class diagnostic_html_format_buffer : public diagnostic_per_format_buffer. */ |
| 203 | |
| 204 | void |
| 205 | diagnostic_html_format_buffer::dump (FILE *out, int indent) const |
| 206 | { |
| 207 | fprintf (stream: out, format: "%*sdiagnostic_html_format_buffer:\n" , indent, "" ); |
| 208 | int idx = 0; |
| 209 | for (auto &result : m_results) |
| 210 | { |
| 211 | fprintf (stream: out, format: "%*sresult[%i]:\n" , indent + 2, "" , idx); |
| 212 | result->dump (out); |
| 213 | fprintf (stream: out, format: "\n" ); |
| 214 | ++idx; |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | bool |
| 219 | diagnostic_html_format_buffer::empty_p () const |
| 220 | { |
| 221 | return m_results.empty (); |
| 222 | } |
| 223 | |
| 224 | void |
| 225 | diagnostic_html_format_buffer::move_to (diagnostic_per_format_buffer &base) |
| 226 | { |
| 227 | diagnostic_html_format_buffer &dest |
| 228 | = static_cast<diagnostic_html_format_buffer &> (base); |
| 229 | for (auto &&result : m_results) |
| 230 | dest.m_results.push_back (x: std::move (result)); |
| 231 | m_results.clear (); |
| 232 | } |
| 233 | |
| 234 | void |
| 235 | diagnostic_html_format_buffer::clear () |
| 236 | { |
| 237 | m_results.clear (); |
| 238 | } |
| 239 | |
| 240 | void |
| 241 | diagnostic_html_format_buffer::flush () |
| 242 | { |
| 243 | for (auto &&result : m_results) |
| 244 | m_builder.m_diagnostics_element->add_child (node: std::move (result)); |
| 245 | m_results.clear (); |
| 246 | } |
| 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 | struct html_doctypedecl : public xml::doctypedecl |
| 353 | { |
| 354 | void write_as_xml (pretty_printer *pp, |
| 355 | int depth, bool indent) const final override |
| 356 | { |
| 357 | if (indent) |
| 358 | { |
| 359 | for (int i = 0; i < depth; ++i) |
| 360 | pp_string (pp, " " ); |
| 361 | } |
| 362 | 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 | if (indent) |
| 366 | pp_newline (pp); |
| 367 | } |
| 368 | }; |
| 369 | |
| 370 | /* html_builder's ctor. */ |
| 371 | |
| 372 | html_builder::html_builder (diagnostic_context &context, |
| 373 | pretty_printer &pp, |
| 374 | const line_maps *line_maps, |
| 375 | const html_generation_options &html_gen_opts) |
| 376 | : m_context (context), |
| 377 | m_printer (&pp), |
| 378 | m_line_maps (line_maps), |
| 379 | m_html_gen_opts (html_gen_opts), |
| 380 | m_logical_loc_mgr (nullptr), |
| 381 | m_head_element (nullptr), |
| 382 | m_title_element (nullptr), |
| 383 | m_diagnostics_element (nullptr), |
| 384 | m_next_diag_id (0), |
| 385 | m_last_location (UNKNOWN_LOCATION), |
| 386 | m_last_expanded_location ({}) |
| 387 | { |
| 388 | gcc_assert (m_line_maps); |
| 389 | |
| 390 | if (auto client_data_hooks = context.get_client_data_hooks ()) |
| 391 | m_logical_loc_mgr = client_data_hooks->get_logical_location_manager (); |
| 392 | |
| 393 | m_document = std::make_unique<xml::document> (); |
| 394 | m_document->m_doctypedecl = std::make_unique<html_doctypedecl> (); |
| 395 | { |
| 396 | auto html_element = std::make_unique<xml::element> (args: "html" , args: false); |
| 397 | html_element->set_attr (name: "xmlns" , |
| 398 | value: "http://www.w3.org/1999/xhtml" ); |
| 399 | xml::printer xp (*html_element.get ()); |
| 400 | m_document->add_child (node: std::move (html_element)); |
| 401 | |
| 402 | { |
| 403 | xml::auto_print_element head (xp, "head" ); |
| 404 | m_head_element = xp.get_insertion_point (); |
| 405 | { |
| 406 | xml::auto_print_element title (xp, "title" , true); |
| 407 | m_title_element = xp.get_insertion_point (); |
| 408 | } |
| 409 | |
| 410 | if (m_html_gen_opts.m_css) |
| 411 | { |
| 412 | add_stylesheet (url: "https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly.min.css" ); |
| 413 | add_stylesheet (url: "https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly-additions.min.css" ); |
| 414 | xp.add_raw (text: HTML_STYLE); |
| 415 | } |
| 416 | if (m_html_gen_opts.m_javascript) |
| 417 | { |
| 418 | xp.push_tag (name: "script" ); |
| 419 | /* Escaping rules are different for HTML <script> elements, |
| 420 | so add the script "raw" for now. */ |
| 421 | xp.add_raw (text: HTML_SCRIPT); |
| 422 | xp.pop_tag (expected_name: "script" ); |
| 423 | } |
| 424 | } |
| 425 | |
| 426 | { |
| 427 | xml::auto_print_element body (xp, "body" ); |
| 428 | { |
| 429 | auto diagnostics_element = make_div (class_: "gcc-diagnostic-list" ); |
| 430 | m_diagnostics_element = diagnostics_element.get (); |
| 431 | xp.append (new_node: std::move (diagnostics_element)); |
| 432 | } |
| 433 | } |
| 434 | } |
| 435 | } |
| 436 | |
| 437 | void |
| 438 | html_builder::set_main_input_filename (const char *name) |
| 439 | { |
| 440 | gcc_assert (m_title_element); |
| 441 | if (name) |
| 442 | { |
| 443 | m_title_element->m_children.clear (); |
| 444 | m_title_element->add_text (str: name); |
| 445 | } |
| 446 | } |
| 447 | |
| 448 | void |
| 449 | html_builder::add_stylesheet (std::string url) |
| 450 | { |
| 451 | gcc_assert (m_head_element); |
| 452 | |
| 453 | xml::printer xp (*m_head_element); |
| 454 | xp.push_tag (name: "link" , preserve_whitespace: false); |
| 455 | xp.set_attr (name: "rel" , value: "stylesheet" ); |
| 456 | xp.set_attr (name: "type" , value: "text/css" ); |
| 457 | xp.set_attr (name: "href" , value: std::move (url)); |
| 458 | } |
| 459 | |
| 460 | /* Implementation of "on_report_diagnostic" for HTML output. */ |
| 461 | |
| 462 | void |
| 463 | html_builder::on_report_diagnostic (const diagnostic_info &diagnostic, |
| 464 | diagnostic_t orig_diag_kind, |
| 465 | diagnostic_html_format_buffer *buffer) |
| 466 | { |
| 467 | 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 | fnotice (stderr, "Internal compiler error:\n" ); |
| 475 | } |
| 476 | |
| 477 | const int nesting_level = m_context.get_diagnostic_nesting_level (); |
| 478 | bool alert = true; |
| 479 | if (m_cur_diagnostic_element && nesting_level > 0) |
| 480 | alert = false; |
| 481 | if (!m_cur_diagnostic_element) |
| 482 | m_last_logical_location = logical_location (); |
| 483 | auto diag_element |
| 484 | = make_element_for_diagnostic (diagnostic, orig_diag_kind, alert); |
| 485 | if (buffer) |
| 486 | { |
| 487 | gcc_assert (!m_cur_diagnostic_element); |
| 488 | buffer->m_results.push_back (x: std::move (diag_element)); |
| 489 | } |
| 490 | else |
| 491 | { |
| 492 | if (m_cur_diagnostic_element) |
| 493 | { |
| 494 | /* Nested diagnostic. */ |
| 495 | gcc_assert (nesting_level >= 0); |
| 496 | add_at_nesting_level (nesting_level, child_diag_element: std::move (diag_element)); |
| 497 | } |
| 498 | else |
| 499 | /* Top-level diagnostic. */ |
| 500 | { |
| 501 | m_cur_diagnostic_element = std::move (diag_element); |
| 502 | m_cur_nesting_levels.clear (); |
| 503 | } |
| 504 | } |
| 505 | } |
| 506 | |
| 507 | // For ease of comparison with experimental-nesting-show-levels=yes |
| 508 | |
| 509 | static void |
| 510 | add_nesting_level_attr (xml::element &element, |
| 511 | int nesting_level) |
| 512 | { |
| 513 | element.set_attr (name: "nesting-level" , value: std::to_string (val: nesting_level)); |
| 514 | } |
| 515 | |
| 516 | void |
| 517 | html_builder:: |
| 518 | add_at_nesting_level (size_t nesting_level, |
| 519 | std::unique_ptr<xml::element> child_diag_element) |
| 520 | { |
| 521 | gcc_assert (m_cur_diagnostic_element); |
| 522 | while (nesting_level > m_cur_nesting_levels.size ()) |
| 523 | push_nesting_level (); |
| 524 | while (nesting_level < m_cur_nesting_levels.size ()) |
| 525 | pop_nesting_level (); |
| 526 | |
| 527 | if (nesting_level > 0) |
| 528 | { |
| 529 | gcc_assert (!m_cur_nesting_levels.empty ()); |
| 530 | auto current_nesting_level = m_cur_nesting_levels.back (); |
| 531 | xml::printer xp (*current_nesting_level); |
| 532 | xp.push_tag (name: "li" ); |
| 533 | add_nesting_level_attr (element&: *xp.get_insertion_point (), |
| 534 | nesting_level: m_cur_nesting_levels.size ()); |
| 535 | xp.append (new_node: std::move (child_diag_element)); |
| 536 | xp.pop_tag (expected_name: "li" ); |
| 537 | } |
| 538 | else |
| 539 | m_cur_diagnostic_element->add_child (node: std::move (child_diag_element)); |
| 540 | } |
| 541 | |
| 542 | void |
| 543 | html_builder::push_nesting_level () |
| 544 | { |
| 545 | gcc_assert (m_cur_diagnostic_element); |
| 546 | auto new_nesting_level = std::make_unique<xml::element> (args: "ul" , args: false); |
| 547 | add_nesting_level_attr (element&: *new_nesting_level, |
| 548 | nesting_level: m_cur_nesting_levels.size () + 1); |
| 549 | xml::element *current_nesting_level = nullptr; |
| 550 | if (!m_cur_nesting_levels.empty ()) |
| 551 | current_nesting_level = m_cur_nesting_levels.back (); |
| 552 | m_cur_nesting_levels.push_back (x: new_nesting_level.get ()); |
| 553 | if (current_nesting_level) |
| 554 | current_nesting_level->add_child (node: std::move (new_nesting_level)); |
| 555 | else |
| 556 | m_cur_diagnostic_element->add_child (node: std::move (new_nesting_level)); |
| 557 | } |
| 558 | |
| 559 | void |
| 560 | html_builder::pop_nesting_level () |
| 561 | { |
| 562 | gcc_assert (m_cur_diagnostic_element); |
| 563 | m_cur_nesting_levels.pop_back (); |
| 564 | } |
| 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 | class html_path_label_writer : public html_label_writer |
| 571 | { |
| 572 | public: |
| 573 | html_path_label_writer (xml::printer &xp, |
| 574 | html_builder &builder, |
| 575 | const std::string &event_id_prefix) |
| 576 | : m_xp (xp), |
| 577 | m_html_builder (builder), |
| 578 | m_event_id_prefix (event_id_prefix), |
| 579 | m_next_event_idx (0) |
| 580 | { |
| 581 | } |
| 582 | |
| 583 | void begin_label () final override |
| 584 | { |
| 585 | m_xp.push_tag_with_class (name: "span" , class_: "event" , preserve_whitespace: true); |
| 586 | pretty_printer pp; |
| 587 | pp_printf (&pp, "%s%i" , |
| 588 | m_event_id_prefix.c_str (), m_next_event_idx++); |
| 589 | m_xp.set_attr (name: "id" , value: pp_formatted_text (&pp)); |
| 590 | m_html_builder.add_focus_id (focus_id: pp_formatted_text (&pp)); |
| 591 | } |
| 592 | |
| 593 | void end_label () final override |
| 594 | { |
| 595 | m_xp.pop_tag (expected_name: "span" ); // from begin_label |
| 596 | } |
| 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 | get_pf_class_for_alert_div (diagnostic_t diag_kind) |
| 608 | { |
| 609 | switch (diag_kind) |
| 610 | { |
| 611 | case DK_DEBUG: |
| 612 | case DK_NOTE: |
| 613 | return "alert alert-info" ; |
| 614 | |
| 615 | case DK_ANACHRONISM: |
| 616 | case DK_WARNING: |
| 617 | return "alert alert-warning" ; |
| 618 | |
| 619 | case DK_ERROR: |
| 620 | case DK_SORRY: |
| 621 | case DK_ICE: |
| 622 | case DK_ICE_NOBT: |
| 623 | case DK_FATAL: |
| 624 | return "alert alert-danger" ; |
| 625 | |
| 626 | default: |
| 627 | gcc_unreachable (); |
| 628 | } |
| 629 | } |
| 630 | |
| 631 | static const char * |
| 632 | get_pf_class_for_alert_icon (diagnostic_t diag_kind) |
| 633 | { |
| 634 | switch (diag_kind) |
| 635 | { |
| 636 | case DK_DEBUG: |
| 637 | case DK_NOTE: |
| 638 | return "pficon pficon-info" ; |
| 639 | |
| 640 | case DK_ANACHRONISM: |
| 641 | case DK_WARNING: |
| 642 | return "pficon pficon-warning-triangle-o" ; |
| 643 | |
| 644 | case DK_ERROR: |
| 645 | case DK_SORRY: |
| 646 | case DK_ICE: |
| 647 | case DK_ICE_NOBT: |
| 648 | case DK_FATAL: |
| 649 | return "pficon pficon-error-circle-o" ; |
| 650 | |
| 651 | default: |
| 652 | gcc_unreachable (); |
| 653 | } |
| 654 | } |
| 655 | |
| 656 | static const char * |
| 657 | get_label_for_logical_location_kind (enum logical_location_kind kind) |
| 658 | { |
| 659 | switch (kind) |
| 660 | { |
| 661 | default: |
| 662 | gcc_unreachable (); |
| 663 | case logical_location_kind::unknown: |
| 664 | return nullptr; |
| 665 | |
| 666 | /* Kinds within executable code. */ |
| 667 | case logical_location_kind::function: |
| 668 | return "Function" ; |
| 669 | case logical_location_kind::member: |
| 670 | return "Member" ; |
| 671 | case logical_location_kind::module_: |
| 672 | return "Module" ; |
| 673 | case logical_location_kind::namespace_: |
| 674 | return "Namespace" ; |
| 675 | case logical_location_kind::type: |
| 676 | return "Type" ; |
| 677 | case logical_location_kind::return_type: |
| 678 | return "Return type" ; |
| 679 | case logical_location_kind::parameter: |
| 680 | return "Parameter" ; |
| 681 | case logical_location_kind::variable: |
| 682 | return "Variable" ; |
| 683 | |
| 684 | /* Kinds within XML or HTML documents. */ |
| 685 | case logical_location_kind::element: |
| 686 | return "Element" ; |
| 687 | case logical_location_kind::attribute: |
| 688 | return "Attribute" ; |
| 689 | case logical_location_kind::text: |
| 690 | return "Text" ; |
| 691 | case logical_location_kind::comment: |
| 692 | return "Comment" ; |
| 693 | case logical_location_kind::processing_instruction: |
| 694 | return "Processing Instruction" ; |
| 695 | case logical_location_kind::dtd: |
| 696 | return "DTD" ; |
| 697 | case logical_location_kind::declaration: |
| 698 | return "Declaration" ; |
| 699 | |
| 700 | /* Kinds within JSON documents. */ |
| 701 | case logical_location_kind::object: |
| 702 | return "Object" ; |
| 703 | case logical_location_kind::array: |
| 704 | return "Array" ; |
| 705 | case logical_location_kind::property: |
| 706 | return "Property" ; |
| 707 | case logical_location_kind::value: |
| 708 | return "Value" ; |
| 709 | } |
| 710 | } |
| 711 | |
| 712 | static void |
| 713 | add_labelled_value (xml::printer &xp, |
| 714 | std::string id, |
| 715 | std::string label, |
| 716 | std::string value, |
| 717 | bool quote_value) |
| 718 | { |
| 719 | xp.push_tag (name: "div" , preserve_whitespace: true); |
| 720 | xp.set_attr (name: "id" , value: id); |
| 721 | xp.push_tag (name: "span" ); |
| 722 | xp.add_text (text: label); |
| 723 | xp.add_text (text: " " ); |
| 724 | xp.pop_tag (expected_name: "span" ); |
| 725 | xp.push_tag (name: "span" ); |
| 726 | if (quote_value) |
| 727 | xp.set_attr (name: "class" , value: "gcc-quoted-text" ); |
| 728 | xp.add_text (text: std::move (value)); |
| 729 | xp.pop_tag (expected_name: "span" ); |
| 730 | xp.pop_tag (expected_name: "div" ); |
| 731 | } |
| 732 | |
| 733 | class html_token_printer : public token_printer |
| 734 | { |
| 735 | public: |
| 736 | 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 | : m_xp (parent_element, |
| 743 | /* Similarly we don't check that the popped tags match. */ |
| 744 | false) |
| 745 | { |
| 746 | } |
| 747 | 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 | for (auto iter = tokens.m_first; iter; iter = iter->m_next) |
| 753 | switch (iter->m_kind) |
| 754 | { |
| 755 | default: |
| 756 | gcc_unreachable (); |
| 757 | |
| 758 | case pp_token::kind::text: |
| 759 | { |
| 760 | pp_token_text *sub = as_a <pp_token_text *> (p: iter); |
| 761 | /* The value might be in the obstack, so we may need to |
| 762 | copy it. */ |
| 763 | m_xp.add_text (text: sub->m_value.get ()); |
| 764 | } |
| 765 | break; |
| 766 | |
| 767 | case pp_token::kind::begin_color: |
| 768 | { |
| 769 | pp_token_begin_color *sub = as_a <pp_token_begin_color *> (p: iter); |
| 770 | gcc_assert (sub->m_value.get ()); |
| 771 | m_xp.push_tag_with_class (name: "span" , class_: sub->m_value.get ()); |
| 772 | } |
| 773 | break; |
| 774 | |
| 775 | case pp_token::kind::end_color: |
| 776 | m_xp.pop_tag (expected_name: "span" ); |
| 777 | break; |
| 778 | |
| 779 | case pp_token::kind::begin_quote: |
| 780 | { |
| 781 | m_xp.add_text (text: open_quote); |
| 782 | m_xp.push_tag_with_class (name: "span" , class_: "gcc-quoted-text" ); |
| 783 | } |
| 784 | break; |
| 785 | case pp_token::kind::end_quote: |
| 786 | { |
| 787 | m_xp.pop_tag (expected_name: "span" ); |
| 788 | m_xp.add_text (text: close_quote); |
| 789 | } |
| 790 | break; |
| 791 | |
| 792 | case pp_token::kind::begin_url: |
| 793 | { |
| 794 | pp_token_begin_url *sub = as_a <pp_token_begin_url *> (p: iter); |
| 795 | m_xp.push_tag (name: "a" , preserve_whitespace: true); |
| 796 | m_xp.set_attr (name: "href" , value: sub->m_value.get ()); |
| 797 | } |
| 798 | break; |
| 799 | case pp_token::kind::end_url: |
| 800 | m_xp.pop_tag (expected_name: "a" ); |
| 801 | break; |
| 802 | } |
| 803 | } |
| 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 | html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, |
| 821 | diagnostic_t orig_diag_kind, |
| 822 | bool alert) |
| 823 | { |
| 824 | const int diag_idx = m_next_diag_id++; |
| 825 | std::string diag_id; |
| 826 | { |
| 827 | pretty_printer pp; |
| 828 | pp_printf (&pp, "gcc-diag-%i" , diag_idx); |
| 829 | diag_id = pp_formatted_text (&pp); |
| 830 | } |
| 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 | auto diag_element = make_div (class_: "gcc-diagnostic" ); |
| 842 | diag_element->set_attr (name: "id" , value: diag_id); |
| 843 | if (alert) |
| 844 | diag_element->set_attr (name: "class" , |
| 845 | value: get_pf_class_for_alert_div (diag_kind: diagnostic.kind)); |
| 846 | |
| 847 | xml::printer xp (*diag_element.get ()); |
| 848 | const size_t depth_within_alert_div = 1; |
| 849 | |
| 850 | gcc_assert (xp.get_num_open_tags () == depth_within_alert_div); |
| 851 | |
| 852 | if (alert) |
| 853 | { |
| 854 | xp.push_tag_with_class (name: "span" , |
| 855 | class_: get_pf_class_for_alert_icon (diag_kind: diagnostic.kind), |
| 856 | preserve_whitespace: true); |
| 857 | xp.add_text (text: " " ); |
| 858 | xp.pop_tag (expected_name: "span" ); |
| 859 | } |
| 860 | |
| 861 | // The rest goes in the <div>... |
| 862 | gcc_assert (xp.get_num_open_tags () == depth_within_alert_div); |
| 863 | |
| 864 | xp.push_tag_with_class (name: "div" , class_: "gcc-message" , preserve_whitespace: true); |
| 865 | std::string message_alert_id (diag_id + "-message" ); |
| 866 | xp.set_attr (name: "id" , value: message_alert_id); |
| 867 | add_focus_id (focus_id: message_alert_id); |
| 868 | |
| 869 | const size_t depth_within_message_div = depth_within_alert_div + 1; |
| 870 | gcc_assert (xp.get_num_open_tags () == depth_within_message_div); |
| 871 | |
| 872 | // Severity e.g. "warning: " |
| 873 | bool show_severity = true; |
| 874 | if (!alert) |
| 875 | show_severity = false; |
| 876 | if (show_severity) |
| 877 | { |
| 878 | xp.push_tag (name: "strong" ); |
| 879 | xp.add_text (_(get_diagnostic_kind_text (diagnostic.kind))); |
| 880 | xp.pop_tag (expected_name: "strong" ); |
| 881 | xp.add_text (text: " " ); |
| 882 | } |
| 883 | |
| 884 | // Add the message itself: |
| 885 | html_token_printer tok_printer (*xp.get_insertion_point ()); |
| 886 | m_printer->set_token_printer (&tok_printer); |
| 887 | pp_output_formatted_text (m_printer, m_context.get_urlifier ()); |
| 888 | m_printer->set_token_printer (nullptr); |
| 889 | pp_clear_output_area (m_printer); |
| 890 | |
| 891 | // Add any metadata as a suffix to the message |
| 892 | if (diagnostic.metadata) |
| 893 | { |
| 894 | xp.add_text (text: " " ); |
| 895 | xp.append (new_node: make_element_for_metadata (metadata: *diagnostic.metadata)); |
| 896 | } |
| 897 | |
| 898 | // Add any option as a suffix to the message |
| 899 | |
| 900 | label_text option_text = label_text::take |
| 901 | (buffer: m_context.make_option_name (option_id: diagnostic.option_id, |
| 902 | orig_diag_kind, diag_kind: diagnostic.kind)); |
| 903 | if (option_text.get ()) |
| 904 | { |
| 905 | label_text option_url = label_text::take |
| 906 | (buffer: m_context.make_option_url (option_id: diagnostic.option_id)); |
| 907 | |
| 908 | xp.add_text (text: " " ); |
| 909 | auto option_span = make_span (class_: "gcc-option" ); |
| 910 | option_span->add_text (str: "[" ); |
| 911 | { |
| 912 | if (option_url.get ()) |
| 913 | { |
| 914 | auto anchor = std::make_unique<xml::element> (args: "a" , args: true); |
| 915 | anchor->set_attr (name: "href" , value: option_url.get ()); |
| 916 | anchor->add_text (str: option_text.get ()); |
| 917 | option_span->add_child (node: std::move (anchor)); |
| 918 | } |
| 919 | else |
| 920 | option_span->add_text (str: option_text.get ()); |
| 921 | option_span->add_text (str: "]" ); |
| 922 | } |
| 923 | xp.append (new_node: std::move (option_span)); |
| 924 | } |
| 925 | |
| 926 | gcc_assert (xp.get_num_open_tags () == depth_within_message_div); |
| 927 | |
| 928 | xp.pop_tag (expected_name: "div" ); |
| 929 | |
| 930 | gcc_assert (xp.get_num_open_tags () == depth_within_alert_div); |
| 931 | |
| 932 | /* Show any logical location. */ |
| 933 | if (m_logical_loc_mgr) |
| 934 | if (auto client_data_hooks = m_context.get_client_data_hooks ()) |
| 935 | if (auto logical_loc = client_data_hooks->get_current_logical_location ()) |
| 936 | if (logical_loc != m_last_logical_location) |
| 937 | { |
| 938 | enum logical_location_kind kind |
| 939 | = m_logical_loc_mgr->get_kind (k: logical_loc);; |
| 940 | if (const char *label = get_label_for_logical_location_kind (kind)) |
| 941 | if (const char *name_with_scope |
| 942 | = m_logical_loc_mgr->get_name_with_scope (k: logical_loc)) |
| 943 | add_labelled_value (xp, id: "logical-location" , |
| 944 | label, value: name_with_scope, quote_value: true); |
| 945 | m_last_logical_location = logical_loc; |
| 946 | } |
| 947 | |
| 948 | /* Show any physical location. */ |
| 949 | const expanded_location s |
| 950 | = diagnostic_expand_location (diagnostic: &diagnostic); |
| 951 | if (s != m_last_expanded_location |
| 952 | || alert) |
| 953 | { |
| 954 | if (s.file |
| 955 | && (s.file != m_last_expanded_location.file |
| 956 | || alert)) |
| 957 | add_labelled_value (xp, id: "file" , label: "File" , value: s.file, quote_value: false); |
| 958 | if (s.line) |
| 959 | { |
| 960 | add_labelled_value (xp, id: "line" , label: "Line" , value: std::to_string (val: s.line), quote_value: false); |
| 961 | diagnostic_column_policy column_policy (m_context); |
| 962 | int converted_column = column_policy.converted_column (s); |
| 963 | if (converted_column >= 0) |
| 964 | add_labelled_value (xp, id: "column" , label: "Column" , |
| 965 | value: std::to_string (val: converted_column), |
| 966 | quote_value: false); |
| 967 | } |
| 968 | if (s.file) |
| 969 | m_last_expanded_location = s; |
| 970 | } |
| 971 | |
| 972 | /* Source (and fix-it hints). */ |
| 973 | { |
| 974 | // TODO: m_context.m_last_location should be moved into the sink |
| 975 | location_t saved = m_context.m_last_location; |
| 976 | m_context.m_last_location = m_last_location; |
| 977 | m_context.maybe_show_locus_as_html (richloc: *diagnostic.richloc, |
| 978 | opts: m_context.m_source_printing, |
| 979 | diagnostic_kind: diagnostic.kind, |
| 980 | xp, |
| 981 | effect_info: nullptr, |
| 982 | label_writer: nullptr); |
| 983 | m_context.m_last_location = saved; |
| 984 | m_last_location = m_context.m_last_location; |
| 985 | } |
| 986 | |
| 987 | gcc_assert (xp.get_num_open_tags () == depth_within_alert_div); |
| 988 | |
| 989 | /* Execution path. */ |
| 990 | if (auto path = diagnostic.richloc->get_path ()) |
| 991 | { |
| 992 | xp.push_tag (name: "div" ); |
| 993 | xp.set_attr (name: "id" , value: "execution-path" ); |
| 994 | |
| 995 | xp.push_tag (name: "label" , preserve_whitespace: true); |
| 996 | const int num_events = path->num_events (); |
| 997 | pretty_printer pp; |
| 998 | pp_printf_n (&pp, n: num_events, |
| 999 | "Execution path with %i event" , |
| 1000 | "Execution path with %i events" , |
| 1001 | num_events); |
| 1002 | xp.add_text_from_pp (pp); |
| 1003 | xp.pop_tag (expected_name: "label" ); |
| 1004 | |
| 1005 | std::string event_id_prefix (diag_id + "-event-" ); |
| 1006 | html_path_label_writer event_label_writer (xp, *this, |
| 1007 | event_id_prefix); |
| 1008 | diagnostic_source_print_policy dspp (m_context); |
| 1009 | print_path_as_html (xp, path: *path, dc&: m_context, event_label_writer: &event_label_writer, |
| 1010 | dspp); |
| 1011 | |
| 1012 | xp.pop_tag (expected_name: "div" ); |
| 1013 | } |
| 1014 | |
| 1015 | gcc_assert (xp.get_num_open_tags () == depth_within_alert_div); |
| 1016 | |
| 1017 | if (auto patch_element = make_element_for_patch (diagnostic)) |
| 1018 | { |
| 1019 | xp.push_tag (name: "div" ); |
| 1020 | xp.set_attr (name: "id" , value: "suggested-fix" ); |
| 1021 | xp.push_tag (name: "label" , preserve_whitespace: true); |
| 1022 | xp.add_text (text: "Suggested fix" ); |
| 1023 | xp.pop_tag (expected_name: "label" ); |
| 1024 | xp.append (new_node: std::move (patch_element)); |
| 1025 | xp.pop_tag (expected_name: "div" ); |
| 1026 | } |
| 1027 | |
| 1028 | return diag_element; |
| 1029 | } |
| 1030 | |
| 1031 | std::unique_ptr<xml::element> |
| 1032 | html_builder::make_element_for_patch (const diagnostic_info &diagnostic) |
| 1033 | { |
| 1034 | edit_context ec (m_context.get_file_cache ()); |
| 1035 | ec.add_fixits (richloc: diagnostic.richloc); |
| 1036 | if (char *diff = ec.generate_diff (show_filenames: true)) |
| 1037 | { |
| 1038 | if (strlen (s: diff) > 0) |
| 1039 | { |
| 1040 | auto element = std::make_unique<xml::element> (args: "pre" , args: true); |
| 1041 | element->set_attr (name: "class" , value: "gcc-generated-patch" ); |
| 1042 | element->add_text (str: diff); |
| 1043 | free (ptr: diff); |
| 1044 | return element; |
| 1045 | } |
| 1046 | else |
| 1047 | free (ptr: diff); |
| 1048 | } |
| 1049 | return nullptr; |
| 1050 | } |
| 1051 | |
| 1052 | std::unique_ptr<xml::element> |
| 1053 | html_builder::make_metadata_element (label_text label, |
| 1054 | label_text url) |
| 1055 | { |
| 1056 | auto item = make_span (class_: "gcc-metadata-item" ); |
| 1057 | xml::printer xp (*item.get ()); |
| 1058 | xp.add_text (text: "[" ); |
| 1059 | { |
| 1060 | if (url.get ()) |
| 1061 | { |
| 1062 | xp.push_tag (name: "a" , preserve_whitespace: true); |
| 1063 | xp.set_attr (name: "href" , value: url.get ()); |
| 1064 | } |
| 1065 | xp.add_text (text: label.get ()); |
| 1066 | if (url.get ()) |
| 1067 | xp.pop_tag (expected_name: "a" ); |
| 1068 | } |
| 1069 | xp.add_text (text: "]" ); |
| 1070 | return item; |
| 1071 | } |
| 1072 | |
| 1073 | std::unique_ptr<xml::element> |
| 1074 | html_builder::make_element_for_metadata (const diagnostic_metadata &metadata) |
| 1075 | { |
| 1076 | auto span_metadata = make_span (class_: "gcc-metadata" ); |
| 1077 | |
| 1078 | int cwe = metadata.get_cwe (); |
| 1079 | if (cwe) |
| 1080 | { |
| 1081 | pretty_printer pp; |
| 1082 | pp_printf (&pp, "CWE-%i" , cwe); |
| 1083 | label_text label = label_text::take (buffer: xstrdup (pp_formatted_text (&pp))); |
| 1084 | label_text url = label_text::take (buffer: get_cwe_url (cwe)); |
| 1085 | span_metadata->add_child |
| 1086 | (node: make_metadata_element (label: std::move (label), url: std::move (url))); |
| 1087 | } |
| 1088 | |
| 1089 | for (unsigned idx = 0; idx < metadata.get_num_rules (); ++idx) |
| 1090 | { |
| 1091 | auto &rule = metadata.get_rule (idx); |
| 1092 | label_text label = label_text::take (buffer: rule.make_description ()); |
| 1093 | label_text url = label_text::take (buffer: rule.make_url ()); |
| 1094 | span_metadata->add_child |
| 1095 | (node: make_metadata_element (label: std::move (label), url: std::move (url))); |
| 1096 | } |
| 1097 | |
| 1098 | return span_metadata; |
| 1099 | } |
| 1100 | |
| 1101 | /* Implementation of diagnostic_context::m_diagrams.m_emission_cb |
| 1102 | for HTML output. */ |
| 1103 | |
| 1104 | void |
| 1105 | html_builder::emit_diagram (const diagnostic_diagram &/*diagram*/) |
| 1106 | { |
| 1107 | /* We must be within the emission of a top-level diagnostic. */ |
| 1108 | gcc_assert (m_cur_diagnostic_element); |
| 1109 | |
| 1110 | // TODO: currently a no-op |
| 1111 | } |
| 1112 | |
| 1113 | /* Implementation of "end_group_cb" for HTML output. */ |
| 1114 | |
| 1115 | void |
| 1116 | html_builder::end_group () |
| 1117 | { |
| 1118 | if (m_cur_diagnostic_element) |
| 1119 | m_diagnostics_element->add_child (node: std::move (m_cur_diagnostic_element)); |
| 1120 | } |
| 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 | html_builder::flush_to_file (FILE *outf) |
| 1129 | { |
| 1130 | if (m_html_gen_opts.m_javascript) |
| 1131 | { |
| 1132 | gcc_assert (m_head_element); |
| 1133 | 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 | xp.push_tag (name: "script" ); |
| 1137 | pretty_printer pp; |
| 1138 | pp_string (&pp, "focus_ids = " ); |
| 1139 | m_ui_focus_ids.print (pp: &pp, formatted: true); |
| 1140 | pp_string (&pp, ";\n" ); |
| 1141 | xp.add_raw (text: pp_formatted_text (&pp)); |
| 1142 | xp.pop_tag (expected_name: "script" ); |
| 1143 | } |
| 1144 | auto top = m_document.get (); |
| 1145 | top->dump (out: outf); |
| 1146 | fprintf (stream: outf, format: "\n" ); |
| 1147 | } |
| 1148 | |
| 1149 | class html_output_format : public diagnostic_output_format |
| 1150 | { |
| 1151 | public: |
| 1152 | ~html_output_format () |
| 1153 | { |
| 1154 | /* Any diagnostics should have been handled by now. |
| 1155 | If not, then something's gone wrong with diagnostic |
| 1156 | groupings. */ |
| 1157 | std::unique_ptr<xml::element> pending_diag |
| 1158 | = m_builder.take_current_diagnostic (); |
| 1159 | gcc_assert (!pending_diag); |
| 1160 | } |
| 1161 | |
| 1162 | void dump (FILE *out, int indent) const override |
| 1163 | { |
| 1164 | fprintf (stream: out, format: "%*shtml_output_format\n" , indent, "" ); |
| 1165 | diagnostic_output_format::dump (out, indent); |
| 1166 | } |
| 1167 | |
| 1168 | void |
| 1169 | set_main_input_filename (const char *name) final override |
| 1170 | { |
| 1171 | m_builder.set_main_input_filename (name); |
| 1172 | } |
| 1173 | |
| 1174 | std::unique_ptr<diagnostic_per_format_buffer> |
| 1175 | make_per_format_buffer () final override |
| 1176 | { |
| 1177 | return std::make_unique<diagnostic_html_format_buffer> (args&: m_builder); |
| 1178 | } |
| 1179 | void set_buffer (diagnostic_per_format_buffer *base_buffer) final override |
| 1180 | { |
| 1181 | diagnostic_html_format_buffer *buffer |
| 1182 | = static_cast<diagnostic_html_format_buffer *> (base_buffer); |
| 1183 | m_buffer = buffer; |
| 1184 | } |
| 1185 | |
| 1186 | void on_begin_group () final override |
| 1187 | { |
| 1188 | /* No-op, */ |
| 1189 | } |
| 1190 | void on_end_group () final override |
| 1191 | { |
| 1192 | m_builder.end_group (); |
| 1193 | } |
| 1194 | void |
| 1195 | on_report_diagnostic (const diagnostic_info &diagnostic, |
| 1196 | diagnostic_t orig_diag_kind) final override |
| 1197 | { |
| 1198 | m_builder.on_report_diagnostic (diagnostic, orig_diag_kind, buffer: m_buffer); |
| 1199 | } |
| 1200 | void on_diagram (const diagnostic_diagram &diagram) final override |
| 1201 | { |
| 1202 | m_builder.emit_diagram (diagram); |
| 1203 | } |
| 1204 | void after_diagnostic (const diagnostic_info &) |
| 1205 | { |
| 1206 | /* No-op, but perhaps could show paths here. */ |
| 1207 | } |
| 1208 | bool follows_reference_printer_p () const final override |
| 1209 | { |
| 1210 | return false; |
| 1211 | } |
| 1212 | void update_printer () final override |
| 1213 | { |
| 1214 | m_printer = m_context.clone_printer (); |
| 1215 | |
| 1216 | /* Don't colorize the text. */ |
| 1217 | pp_show_color (pp: m_printer.get ()) = false; |
| 1218 | |
| 1219 | /* No textual URLs. */ |
| 1220 | m_printer->set_url_format (URL_FORMAT_NONE); |
| 1221 | |
| 1222 | /* Update the builder to use the new printer. */ |
| 1223 | m_builder.set_printer (*get_printer ()); |
| 1224 | } |
| 1225 | |
| 1226 | const xml::document &get_document () const |
| 1227 | { |
| 1228 | return m_builder.get_document (); |
| 1229 | } |
| 1230 | |
| 1231 | html_builder &get_builder () { return m_builder; } |
| 1232 | |
| 1233 | protected: |
| 1234 | html_output_format (diagnostic_context &context, |
| 1235 | const line_maps *line_maps, |
| 1236 | const html_generation_options &html_gen_opts) |
| 1237 | : diagnostic_output_format (context), |
| 1238 | m_builder (context, *get_printer (), line_maps, html_gen_opts), |
| 1239 | m_buffer (nullptr) |
| 1240 | {} |
| 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 | 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 | : html_output_format (context, line_maps, html_gen_opts), |
| 1254 | m_output_file (std::move (output_file)) |
| 1255 | { |
| 1256 | gcc_assert (m_output_file.get_open_file ()); |
| 1257 | gcc_assert (m_output_file.get_filename ()); |
| 1258 | } |
| 1259 | ~html_file_output_format () |
| 1260 | { |
| 1261 | m_builder.flush_to_file (outf: m_output_file.get_open_file ()); |
| 1262 | } |
| 1263 | void dump (FILE *out, int indent) const override |
| 1264 | { |
| 1265 | fprintf (stream: out, format: "%*shtml_file_output_format: %s\n" , |
| 1266 | indent, "" , |
| 1267 | m_output_file.get_filename ()); |
| 1268 | diagnostic_output_format::dump (out, indent); |
| 1269 | } |
| 1270 | bool machine_readable_stderr_p () const final override |
| 1271 | { |
| 1272 | 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 | diagnostic_output_format_open_html_file (diagnostic_context &context, |
| 1286 | line_maps *line_maps, |
| 1287 | const char *base_file_name) |
| 1288 | { |
| 1289 | if (!base_file_name) |
| 1290 | { |
| 1291 | rich_location richloc (line_maps, UNKNOWN_LOCATION); |
| 1292 | context.emit_diagnostic_with_group |
| 1293 | (kind: DK_ERROR, richloc, metadata: nullptr, option_id: 0, |
| 1294 | gmsgid: "unable to determine filename for HTML output" ); |
| 1295 | return diagnostic_output_file (); |
| 1296 | } |
| 1297 | |
| 1298 | label_text filename = label_text::take (buffer: concat (base_file_name, |
| 1299 | ".html" , |
| 1300 | nullptr)); |
| 1301 | FILE *outf = fopen (filename: filename.get (), modes: "w" ); |
| 1302 | if (!outf) |
| 1303 | { |
| 1304 | rich_location richloc (line_maps, UNKNOWN_LOCATION); |
| 1305 | context.emit_diagnostic_with_group |
| 1306 | (kind: DK_ERROR, richloc, metadata: nullptr, option_id: 0, |
| 1307 | gmsgid: "unable to open %qs for HTML output: %m" , |
| 1308 | filename.get ()); |
| 1309 | return diagnostic_output_file (); |
| 1310 | } |
| 1311 | return diagnostic_output_file (outf, true, std::move (filename)); |
| 1312 | } |
| 1313 | |
| 1314 | std::unique_ptr<diagnostic_output_format> |
| 1315 | 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 | auto sink |
| 1321 | = std::make_unique<html_file_output_format> (args&: context, |
| 1322 | args: &line_maps, |
| 1323 | args: html_gen_opts, |
| 1324 | args: std::move (output_file)); |
| 1325 | sink->update_printer (); |
| 1326 | return sink; |
| 1327 | } |
| 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 | token_printer_test () |
| 1339 | : m_top_element ("div" , true), |
| 1340 | m_tok_printer (m_top_element) |
| 1341 | { |
| 1342 | m_pp.set_token_printer (&m_tok_printer); |
| 1343 | } |
| 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 | test_token_printer () |
| 1352 | { |
| 1353 | { |
| 1354 | token_printer_test t; |
| 1355 | pp_printf (&t.m_pp, "hello world" ); |
| 1356 | ASSERT_XML_PRINT_EQ |
| 1357 | (t.m_top_element, |
| 1358 | "<div>hello world</div>\n" ); |
| 1359 | } |
| 1360 | |
| 1361 | { |
| 1362 | token_printer_test t; |
| 1363 | pp_printf (&t.m_pp, "%qs: %qs" , "foo" , "bar" ); |
| 1364 | 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 | } |
| 1378 | } |
| 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 | class test_html_diagnostic_context : public test_diagnostic_context |
| 1385 | { |
| 1386 | public: |
| 1387 | test_html_diagnostic_context () |
| 1388 | { |
| 1389 | html_generation_options html_gen_opts; |
| 1390 | html_gen_opts.m_css = false; |
| 1391 | html_gen_opts.m_javascript = false; |
| 1392 | auto sink = std::make_unique<html_buffered_output_format> (args&: *this, |
| 1393 | args&: line_table, |
| 1394 | args&: html_gen_opts); |
| 1395 | sink->update_printer (); |
| 1396 | sink->set_main_input_filename ("(main input filename)" ); |
| 1397 | m_format = sink.get (); // borrowed |
| 1398 | |
| 1399 | set_output_format (std::move (sink)); |
| 1400 | } |
| 1401 | |
| 1402 | const xml::document &get_document () const |
| 1403 | { |
| 1404 | return m_format->get_document (); |
| 1405 | } |
| 1406 | |
| 1407 | html_builder &get_builder () const |
| 1408 | { |
| 1409 | return m_format->get_builder (); |
| 1410 | } |
| 1411 | |
| 1412 | private: |
| 1413 | class html_buffered_output_format : public html_output_format |
| 1414 | { |
| 1415 | public: |
| 1416 | html_buffered_output_format (diagnostic_context &context, |
| 1417 | const line_maps *line_maps, |
| 1418 | const html_generation_options &html_gen_opts) |
| 1419 | : html_output_format (context, line_maps, html_gen_opts) |
| 1420 | { |
| 1421 | } |
| 1422 | bool machine_readable_stderr_p () const final override |
| 1423 | { |
| 1424 | 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 | test_simple_log () |
| 1437 | { |
| 1438 | test_html_diagnostic_context dc; |
| 1439 | |
| 1440 | rich_location richloc (line_table, UNKNOWN_LOCATION); |
| 1441 | dc.report (kind: DK_ERROR, richloc, metadata: nullptr, option: 0, fmt: "this is a test: %qs" , "foo" ); |
| 1442 | |
| 1443 | const xml::document &doc = dc.get_document (); |
| 1444 | |
| 1445 | 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 | } |
| 1465 | |
| 1466 | static void |
| 1467 | test_metadata () |
| 1468 | { |
| 1469 | test_html_diagnostic_context dc; |
| 1470 | html_builder &b = dc.get_builder (); |
| 1471 | |
| 1472 | { |
| 1473 | diagnostic_metadata metadata; |
| 1474 | metadata.add_cwe (cwe: 415); |
| 1475 | auto element = b.make_element_for_metadata (metadata); |
| 1476 | 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 | } |
| 1488 | |
| 1489 | { |
| 1490 | diagnostic_metadata metadata; |
| 1491 | diagnostic_metadata::precanned_rule rule ("MISC-42" , |
| 1492 | "http://example.com" ); |
| 1493 | metadata.add_rule (r: rule); |
| 1494 | auto element = b.make_element_for_metadata (metadata); |
| 1495 | 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 | } |
| 1507 | } |
| 1508 | |
| 1509 | /* Run all of the selftests within this file. */ |
| 1510 | |
| 1511 | void |
| 1512 | diagnostic_format_html_cc_tests () |
| 1513 | { |
| 1514 | auto_fix_quotes fix_quotes; |
| 1515 | test_token_printer (); |
| 1516 | test_simple_log (); |
| 1517 | test_metadata (); |
| 1518 | } |
| 1519 | |
| 1520 | } // namespace selftest |
| 1521 | |
| 1522 | #endif /* CHECKING_P */ |
| 1523 | |