1/* Printing paths through the code associated with a diagnostic.
2 Copyright (C) 2019-2025 Free Software Foundation, Inc.
3 Contributed by David Malcolm <dmalcolm@redhat.com>
4
5This file is part of GCC.
6
7GCC is free software; you can redistribute it and/or modify it under
8the terms of the GNU General Public License as published by the Free
9Software Foundation; either version 3, or (at your option) any later
10version.
11
12GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13WARRANTY; without even the implied warranty of MERCHANTABILITY or
14FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15for more details.
16
17You should have received a copy of the GNU General Public License
18along with GCC; see the file COPYING3. If not see
19<http://www.gnu.org/licenses/>. */
20
21#include "config.h"
22#define INCLUDE_ALGORITHM
23#define INCLUDE_MAP
24#define INCLUDE_STRING
25#define INCLUDE_VECTOR
26#include "system.h"
27#include "coretypes.h"
28#include "diagnostic.h"
29#include "diagnostic-macro-unwinding.h"
30#include "intl.h"
31#include "diagnostic-path.h"
32#include "gcc-rich-location.h"
33#include "diagnostic-color.h"
34#include "diagnostic-event-id.h"
35#include "diagnostic-label-effects.h"
36#include "pretty-print-markup.h"
37#include "selftest.h"
38#include "selftest-diagnostic.h"
39#include "selftest-diagnostic-path.h"
40#include "text-art/theme.h"
41#include "diagnostic-format-text.h"
42#include "diagnostic-format-html.h"
43#include "xml.h"
44#include "xml-printer.h"
45
46/* Disable warnings about missing quoting in GCC diagnostics for the print
47 calls below. */
48#if __GNUC__ >= 10
49# pragma GCC diagnostic push
50# pragma GCC diagnostic ignored "-Wformat-diag"
51#endif
52
53/* Anonymous namespace for path-printing code. */
54
55namespace {
56
57/* A bundle of state for printing a path. */
58
59class path_print_policy
60{
61public:
62 path_print_policy (const diagnostic_text_output_format &text_output)
63 : m_source_policy (text_output.get_context ())
64 {
65 }
66
67 path_print_policy (const diagnostic_context &dc)
68 : m_source_policy (dc)
69 {
70 }
71
72 text_art::theme *
73 get_diagram_theme () const
74 {
75 return m_source_policy.get_diagram_theme ();
76 }
77
78 const diagnostic_source_print_policy &
79 get_source_policy () const { return m_source_policy; }
80
81private:
82 diagnostic_source_print_policy m_source_policy;
83};
84
85/* Subclass of range_label for showing a particular event
86 when showing a consecutive run of events within a diagnostic_path as
87 labelled ranges within one gcc_rich_location. */
88
89class path_label : public range_label
90{
91 public:
92 path_label (const diagnostic_path &path,
93 const pretty_printer &ref_pp,
94 unsigned start_idx,
95 bool colorize,
96 bool allow_emojis)
97 : m_path (path),
98 m_ref_pp (ref_pp),
99 m_start_idx (start_idx), m_effects (*this),
100 m_colorize (colorize), m_allow_emojis (allow_emojis)
101 {}
102
103 label_text get_text (unsigned range_idx) const final override
104 {
105 unsigned event_idx = m_start_idx + range_idx;
106 const diagnostic_event &event = m_path.get_event (idx: event_idx);
107
108 const diagnostic_event::meaning meaning (event.get_meaning ());
109
110 auto pp = m_ref_pp.clone ();
111 pp_show_color (pp: pp.get ()) = m_colorize;
112 diagnostic_event_id_t event_id (event_idx);
113
114 pp_printf (pp.get (), "%@", &event_id);
115 pp_space (pp.get ());
116
117 if (meaning.m_verb == diagnostic_event::VERB_danger
118 && m_allow_emojis)
119 {
120 pp_unicode_character (pp.get (), 0x26A0); /* U+26A0 WARNING SIGN. */
121 /* Append U+FE0F VARIATION SELECTOR-16 to select the emoji
122 variation of the char. */
123 pp_unicode_character (pp.get (), 0xFE0F);
124 /* U+26A0 WARNING SIGN has East_Asian_Width == Neutral, but in its
125 emoji variant is printed (by vte at least) with a 2nd half
126 overlapping the next char. Hence we add two spaces here: a space
127 to be covered by this overlap, plus another space of padding. */
128 pp_string (pp.get (), " ");
129 }
130
131 event.print_desc (pp&: *pp.get ());
132
133 label_text result
134 = label_text::take (buffer: xstrdup (pp_formatted_text (pp.get ())));
135 return result;
136 }
137
138 const label_effects *get_effects (unsigned /*range_idx*/) const
139 {
140 return &m_effects;
141 }
142
143 private:
144 class path_label_effects : public label_effects
145 {
146 public:
147 path_label_effects (const path_label &path_label)
148 : m_path_label (path_label)
149 {
150 }
151 bool has_in_edge (unsigned range_idx) const final override
152 {
153 if (const diagnostic_event *prev_event
154 = m_path_label.get_prev_event (range_idx))
155 return prev_event->connect_to_next_event_p ();
156 return false;
157 }
158 bool has_out_edge (unsigned range_idx) const final override
159 {
160 const diagnostic_event &event = m_path_label.get_event (range_idx);
161 return event.connect_to_next_event_p ();
162 }
163
164 private:
165 const path_label &m_path_label;
166 };
167
168 const diagnostic_event &get_event (unsigned range_idx) const
169 {
170 unsigned event_idx = m_start_idx + range_idx;
171 return m_path.get_event (idx: event_idx);
172 }
173
174 const diagnostic_event *get_prev_event (unsigned range_idx) const
175 {
176 if (m_start_idx + range_idx == 0)
177 return nullptr;
178 unsigned event_idx = m_start_idx + range_idx - 1;
179 return &m_path.get_event (idx: event_idx);
180 }
181
182 const diagnostic_path &m_path;
183 const pretty_printer &m_ref_pp;
184 unsigned m_start_idx;
185 path_label_effects m_effects;
186 const bool m_colorize;
187 const bool m_allow_emojis;
188};
189
190/* Return true if E1 and E2 can be consolidated into the same run of events
191 when printing a diagnostic_path. */
192
193static bool
194can_consolidate_events (const diagnostic_path &path,
195 const diagnostic_event &e1,
196 unsigned ev1_idx,
197 const diagnostic_event &e2,
198 unsigned ev2_idx,
199 bool check_locations)
200{
201 if (e1.get_thread_id () != e2.get_thread_id ())
202 return false;
203
204 if (!path.same_function_p (event_idx_a: ev1_idx, event_idx_b: ev2_idx))
205 return false;
206
207 if (e1.get_stack_depth () != e2.get_stack_depth ())
208 return false;
209
210 if (check_locations)
211 {
212 location_t loc1 = e1.get_location ();
213 location_t loc2 = e2.get_location ();
214
215 if (loc1 < RESERVED_LOCATION_COUNT
216 || loc2 < RESERVED_LOCATION_COUNT)
217 return false;
218
219 /* Neither can be macro-based. */
220 if (linemap_location_from_macro_expansion_p (line_table, loc1))
221 return false;
222 if (linemap_location_from_macro_expansion_p (line_table, loc2))
223 return false;
224 }
225
226 /* Passed all the tests. */
227 return true;
228}
229
230struct event_range;
231struct path_summary;
232class thread_event_printer;
233
234/* A bundle of information about all of the events in a diagnostic_path
235 relating to a specific path, for use by path_summary. */
236
237class per_thread_summary
238{
239public:
240 per_thread_summary (const diagnostic_path &path,
241 const logical_location_manager &logical_loc_mgr,
242 label_text name, unsigned swimlane_idx)
243 : m_path (path),
244 m_logical_loc_mgr (logical_loc_mgr),
245 m_name (std::move (name)),
246 m_swimlane_idx (swimlane_idx),
247 m_last_event (nullptr),
248 m_min_depth (INT_MAX),
249 m_max_depth (INT_MIN)
250 {}
251
252 void update_depth_limits (int stack_depth)
253 {
254 if (stack_depth < m_min_depth)
255 m_min_depth = stack_depth;
256 if (stack_depth > m_max_depth)
257 m_max_depth = stack_depth;
258 }
259
260 const char *get_name () const { return m_name.get (); }
261 unsigned get_swimlane_index () const { return m_swimlane_idx; }
262
263 bool interprocedural_p () const;
264
265private:
266 friend struct path_summary;
267 friend class thread_event_printer;
268 friend struct event_range;
269
270 const diagnostic_path &m_path;
271 const logical_location_manager &m_logical_loc_mgr;
272
273 const label_text m_name;
274
275 /* The "swimlane index" is the order in which this per_thread_summary
276 was created, for use when printing the events. */
277 const unsigned m_swimlane_idx;
278
279 // The event ranges specific to this thread:
280 auto_vec<event_range *> m_event_ranges;
281
282 const diagnostic_event *m_last_event;
283
284 int m_min_depth;
285 int m_max_depth;
286};
287
288/* A stack frame for use in HTML output, holding child stack frames,
289 and event ranges. */
290
291struct stack_frame
292{
293 stack_frame (std::unique_ptr<stack_frame> parent,
294 logical_location logical_loc,
295 int stack_depth)
296 : m_parent (std::move (parent)),
297 m_logical_loc (logical_loc),
298 m_stack_depth (stack_depth)
299 {}
300
301 std::unique_ptr<stack_frame> m_parent;
302 logical_location m_logical_loc;
303 const int m_stack_depth;
304};
305
306/* Begin emitting content relating to a new stack frame within PARENT.
307 Allocated a new stack_frame and return it. */
308
309static std::unique_ptr<stack_frame>
310begin_html_stack_frame (xml::printer &xp,
311 std::unique_ptr<stack_frame> parent,
312 logical_location logical_loc,
313 int stack_depth,
314 const logical_location_manager *logical_loc_mgr)
315{
316 if (logical_loc)
317 {
318 gcc_assert (logical_loc_mgr);
319 xp.push_tag_with_class (name: "table", class_: "stack-frame-with-margin", preserve_whitespace: false);
320 xp.push_tag (name: "tr", preserve_whitespace: false);
321 {
322 xp.push_tag_with_class (name: "td", class_: "interprocmargin", preserve_whitespace: false);
323 xp.set_attr (name: "style", value: "padding-left: 100px");
324 xp.pop_tag (expected_name: "td");
325 }
326 xp.push_tag_with_class (name: "td", class_: "stack-frame", preserve_whitespace: false);
327 label_text funcname
328 = logical_loc_mgr->get_name_for_path_output (k: logical_loc);
329 if (funcname.get ())
330 {
331 xp.push_tag_with_class (name: "div", class_: "frame-funcname", preserve_whitespace: false);
332 xp.push_tag (name: "span", preserve_whitespace: true);
333 xp.add_text (text: funcname.get ());
334 xp.pop_tag (expected_name: "span");
335 xp.pop_tag (expected_name: "div");
336 }
337 }
338 return std::make_unique<stack_frame> (args: std::move (parent),
339 args&: logical_loc,
340 args&: stack_depth);
341}
342
343/* Finish emitting content for FRAME and delete it.
344 Return parent. */
345
346static std::unique_ptr<stack_frame>
347end_html_stack_frame (xml::printer &xp,
348 std::unique_ptr<stack_frame> frame)
349{
350 auto parent = std::move (frame->m_parent);
351 if (frame->m_logical_loc)
352 {
353 xp.pop_tag (expected_name: "td");
354 xp.pop_tag (expected_name: "tr");
355 xp.pop_tag (expected_name: "table");
356 }
357 return parent;
358}
359
360/* Append an HTML <div> element to XP containing an SVG arrow representing
361 a change in stack depth from OLD_DEPTH to NEW_DEPTH. */
362
363static void
364emit_svg_arrow (xml::printer &xp, int old_depth, int new_depth)
365{
366 const int pixels_per_depth = 100;
367 const int min_depth = MIN (old_depth, new_depth);
368 const int base_x = 20;
369 const int excess = 30;
370 const int last_x
371 = base_x + (old_depth - min_depth) * pixels_per_depth;
372 const int this_x
373 = base_x + (new_depth - min_depth) * pixels_per_depth;
374 pretty_printer tmp_pp;
375 pretty_printer *pp = &tmp_pp;
376 pp_printf (pp, "<div class=\"%s\">\n",
377 old_depth < new_depth
378 ? "between-ranges-call" : "between-ranges-return");
379 pp_printf (pp, " <svg height=\"30\" width=\"%i\">\n",
380 MAX (last_x, this_x) + excess);
381 pp_string
382 (pp,
383 " <defs>\n"
384 " <marker id=\"arrowhead\" markerWidth=\"10\" markerHeight=\"7\"\n"
385 " refX=\"0\" refY=\"3.5\" orient=\"auto\" stroke=\"#0088ce\" fill=\"#0088ce\">\n"
386 " <polygon points=\"0 0, 10 3.5, 0 7\"/>\n"
387 " </marker>\n"
388 " </defs>\n");
389 pp_printf (pp,
390 " <polyline points=\"%i,0 %i,10 %i,10 %i,20\"\n",
391 last_x, last_x, this_x, this_x);
392 pp_string
393 (pp,
394 " style=\"fill:none;stroke: #0088ce\"\n"
395 " marker-end=\"url(#arrowhead)\"/>\n"
396 " </svg>\n"
397 "</div>\n\n");
398 xp.add_raw (text: pp_formatted_text (pp));
399}
400
401/* A range of consecutive events within a diagnostic_path, all within the
402 same thread, and with the same fndecl and stack_depth, and which are suitable
403 to print with a single call to diagnostic_show_locus. */
404struct event_range
405{
406 /* A struct for tracking the mergability of labels on a particular
407 source line. In particular, track information about links between
408 labels to ensure that we only consolidate events involving links
409 that the source-printing code is able to handle (splitting them
410 otherwise). */
411 struct per_source_line_info
412 {
413 void init (int line)
414 {
415 m_line = line;
416 m_has_in_edge = false;
417 m_has_out_edge = false;
418 m_min_label_source_column = INT_MAX;
419 m_max_label_source_column = INT_MIN;
420 }
421
422 /* Return true if our source-printing/labelling/linking code can handle
423 the events already on this source line, *and* a new event at COLUMN. */
424 bool
425 can_add_label_for_event_p (bool has_in_edge,
426 const diagnostic_event *prev_event,
427 bool has_out_edge,
428 int column) const
429 {
430 /* Any existing in-edge has to be the left-most label on its
431 source line. */
432 if (m_has_in_edge && column < m_min_label_source_column)
433 return false;
434 /* Any existing out-edge has to be the right-most label on its
435 source line. */
436 if (m_has_out_edge && column > m_max_label_source_column)
437 return false;
438 /* Can't have more than one in-edge. */
439 if (m_has_in_edge && has_in_edge)
440 return false;
441 /* Can't have more than one out-edge. */
442 if (m_has_out_edge && has_out_edge)
443 return false;
444
445 if (has_in_edge)
446 {
447 /* Any new in-edge needs to be the left-most label on its
448 source line. */
449 if (column > m_min_label_source_column)
450 return false;
451
452 gcc_assert (prev_event);
453 const location_t prev_loc = prev_event->get_location ();
454 expanded_location prev_exploc
455 = linemap_client_expand_location_to_spelling_point
456 (line_table, prev_loc, LOCATION_ASPECT_CARET);
457 /* The destination in-edge's line number has to be <= the
458 source out-edge's line number (if any). */
459 if (prev_exploc.line >= m_line)
460 return false;
461 }
462
463 /* Any new out-edge needs to be the right-most label on its
464 source line. */
465 if (has_out_edge)
466 if (column < m_max_label_source_column)
467 return false;
468
469 /* All checks passed; we can add the new event at COLUMN. */
470 return true;
471 }
472
473 void
474 add_label_for_event (bool has_in_edge, bool has_out_edge, int column)
475 {
476 if (has_in_edge)
477 m_has_in_edge = true;
478 if (has_out_edge)
479 m_has_out_edge = true;
480 m_min_label_source_column = std::min (a: m_min_label_source_column, b: column);
481 m_max_label_source_column = std::max (a: m_max_label_source_column, b: column);
482 }
483
484 int m_line;
485 bool m_has_in_edge;
486 bool m_has_out_edge;
487 int m_min_label_source_column;
488 int m_max_label_source_column;
489 };
490
491 event_range (const diagnostic_path &path,
492 const pretty_printer &ref_pp,
493 unsigned start_idx,
494 const diagnostic_event &initial_event,
495 per_thread_summary &t,
496 bool show_event_links,
497 bool colorize_labels,
498 bool allow_emojis)
499 : m_path (path),
500 m_initial_event (initial_event),
501 m_logical_loc (initial_event.get_logical_location ()),
502 m_stack_depth (initial_event.get_stack_depth ()),
503 m_start_idx (start_idx), m_end_idx (start_idx),
504 m_path_label (path, ref_pp,
505 start_idx, colorize_labels, allow_emojis),
506 m_richloc (initial_event.get_location (), &m_path_label, nullptr),
507 m_thread_id (initial_event.get_thread_id ()),
508 m_per_thread_summary (t),
509 m_show_event_links (show_event_links)
510 {
511 if (m_show_event_links)
512 {
513 expanded_location exploc
514 = linemap_client_expand_location_to_spelling_point
515 (line_table, initial_event.get_location (), LOCATION_ASPECT_CARET);
516 per_source_line_info &source_line_info
517 = get_per_source_line_info (source_line: exploc.line);
518
519 const diagnostic_event *prev_thread_event = t.m_last_event;
520 const bool has_in_edge
521 = (prev_thread_event
522 ? prev_thread_event->connect_to_next_event_p ()
523 : false);
524 const bool has_out_edge = initial_event.connect_to_next_event_p ();
525
526 source_line_info.add_label_for_event
527 (has_in_edge, has_out_edge, column: exploc.column);
528 }
529 }
530
531 per_source_line_info &
532 get_per_source_line_info (int source_line)
533 {
534 bool existed = false;
535 per_source_line_info &result
536 = m_source_line_info_map.get_or_insert (k: source_line, existed: &existed);
537 if (!existed)
538 result.init (line: source_line);
539 return result;
540 }
541
542 bool maybe_add_event (const path_print_policy &policy,
543 const diagnostic_event &new_ev,
544 unsigned new_ev_idx,
545 bool check_rich_locations)
546 {
547 if (!can_consolidate_events (path: m_path,
548 e1: m_initial_event, ev1_idx: m_start_idx,
549 e2: new_ev, ev2_idx: new_ev_idx,
550 check_locations: check_rich_locations))
551 return false;
552
553 /* Verify compatibility of the new label and existing labels
554 with respect to the link-printing code. */
555 expanded_location exploc
556 = linemap_client_expand_location_to_spelling_point
557 (line_table, new_ev.get_location (), LOCATION_ASPECT_CARET);
558 per_source_line_info &source_line_info
559 = get_per_source_line_info (source_line: exploc.line);
560 const diagnostic_event *prev_event = nullptr;
561 if (new_ev_idx > 0)
562 prev_event = &m_path.get_event (idx: new_ev_idx - 1);
563 const bool has_in_edge = (prev_event
564 ? prev_event->connect_to_next_event_p ()
565 : false);
566 const bool has_out_edge = new_ev.connect_to_next_event_p ();
567 if (m_show_event_links)
568 if (!source_line_info.can_add_label_for_event_p
569 (has_in_edge, prev_event,
570 has_out_edge, column: exploc.column))
571 return false;
572
573 /* Potentially verify that the locations are sufficiently close. */
574 if (check_rich_locations)
575 if (!m_richloc.add_location_if_nearby (policy: policy.get_source_policy (),
576 loc: new_ev.get_location (),
577 restrict_to_current_line_spans: false, label: &m_path_label))
578 return false;
579
580 m_end_idx = new_ev_idx;
581 m_per_thread_summary.m_last_event = &new_ev;
582
583 if (m_show_event_links)
584 source_line_info.add_label_for_event
585 (has_in_edge, has_out_edge, column: exploc.column);
586
587 return true;
588 }
589
590 /* Print the events in this range to PP, typically as a single
591 call to diagnostic_show_locus. */
592
593 void print_as_text (pretty_printer &pp,
594 diagnostic_text_output_format &text_output,
595 diagnostic_source_effect_info *effect_info)
596 {
597 location_t initial_loc = m_initial_event.get_location ();
598
599 diagnostic_context &dc = text_output.get_context ();
600
601 /* Emit a span indicating the filename (and line/column) if the
602 line has changed relative to the last call to
603 diagnostic_show_locus. */
604 if (dc.m_source_printing.enabled)
605 {
606 expanded_location exploc
607 = linemap_client_expand_location_to_spelling_point
608 (line_table, initial_loc, LOCATION_ASPECT_CARET);
609 if (exploc.file != LOCATION_FILE (dc.m_last_location))
610 {
611 diagnostic_location_print_policy loc_policy (text_output);
612 loc_policy.print_text_span_start (dc, pp, exploc);
613 }
614 }
615
616 /* If we have an UNKNOWN_LOCATION (or BUILTINS_LOCATION) as the
617 primary location for an event, diagnostic_show_locus won't print
618 anything.
619
620 In particular the label for the event won't get printed.
621 Fail more gracefully in this case by showing the event
622 index and text, at no particular location. */
623 if (get_pure_location (loc: initial_loc) <= BUILTINS_LOCATION)
624 {
625 for (unsigned i = m_start_idx; i <= m_end_idx; i++)
626 {
627 const diagnostic_event &iter_event = m_path.get_event (idx: i);
628 diagnostic_event_id_t event_id (i);
629 pp_printf (&pp, " %@: ", &event_id);
630 iter_event.print_desc (pp);
631 pp_newline (&pp);
632 }
633 return;
634 }
635
636 /* Call diagnostic_show_locus to show the events using labels. */
637 diagnostic_show_locus (context: &dc, opts: text_output.get_source_printing_options (),
638 richloc: &m_richloc, diagnostic_kind: DK_DIAGNOSTIC_PATH, pp: &pp,
639 effect_info);
640
641 /* If we have a macro expansion, show the expansion to the user. */
642 if (linemap_location_from_macro_expansion_p (line_table, initial_loc))
643 {
644 gcc_assert (m_start_idx == m_end_idx);
645 maybe_unwind_expanded_macro_loc (text_output, where: initial_loc);
646 }
647 }
648
649 /* Print the events in this range to XP, typically as a single
650 call to diagnostic_show_locus_as_html. */
651
652 void print_as_html (xml::printer &xp,
653 diagnostic_context &dc,
654 diagnostic_source_effect_info *effect_info,
655 html_label_writer *event_label_writer)
656 {
657 location_t initial_loc = m_initial_event.get_location ();
658
659 /* Emit a span indicating the filename (and line/column) if the
660 line has changed relative to the last call to
661 diagnostic_show_locus. */
662 if (dc.m_source_printing.enabled)
663 {
664 expanded_location exploc
665 = linemap_client_expand_location_to_spelling_point
666 (line_table, initial_loc, LOCATION_ASPECT_CARET);
667 if (exploc.file != LOCATION_FILE (dc.m_last_location))
668 {
669 diagnostic_location_print_policy loc_policy (dc);
670 loc_policy.print_html_span_start (dc, xp, exploc);
671 }
672 }
673
674 /* If we have an UNKNOWN_LOCATION (or BUILTINS_LOCATION) as the
675 primary location for an event, diagnostic_show_locus_as_html won't print
676 anything.
677
678 In particular the label for the event won't get printed.
679 Fail more gracefully in this case by showing the event
680 index and text, at no particular location. */
681 if (get_pure_location (loc: initial_loc) <= BUILTINS_LOCATION)
682 {
683 for (unsigned i = m_start_idx; i <= m_end_idx; i++)
684 {
685 const diagnostic_event &iter_event = m_path.get_event (idx: i);
686 diagnostic_event_id_t event_id (i);
687 pretty_printer pp;
688 pp_printf (&pp, " %@: ", &event_id);
689 iter_event.print_desc (pp);
690 if (event_label_writer)
691 event_label_writer->begin_label ();
692 xp.add_text_from_pp (pp);
693 if (event_label_writer)
694 event_label_writer->end_label ();
695 }
696 return;
697 }
698
699 /* Call diagnostic_show_locus_as_html to show the source,
700 showing events using labels. */
701 diagnostic_show_locus_as_html (context: &dc, opts: dc.m_source_printing,
702 richloc: &m_richloc, diagnostic_kind: DK_DIAGNOSTIC_PATH, xp,
703 effect_info, label_writer: event_label_writer);
704
705 // TODO: show macro expansions
706 }
707
708 const diagnostic_path &m_path;
709 const diagnostic_event &m_initial_event;
710 logical_location m_logical_loc;
711 int m_stack_depth;
712 unsigned m_start_idx;
713 unsigned m_end_idx;
714 path_label m_path_label;
715 gcc_rich_location m_richloc;
716 diagnostic_thread_id_t m_thread_id;
717 per_thread_summary &m_per_thread_summary;
718 hash_map<int_hash<int, -1, -2>,
719 per_source_line_info> m_source_line_info_map;
720 bool m_show_event_links;
721};
722
723/* A struct for grouping together the events in a diagnostic_path into
724 ranges of events, partitioned by thread and by stack frame (i.e. by fndecl
725 and stack depth). */
726
727struct path_summary
728{
729 path_summary (const path_print_policy &policy,
730 const pretty_printer &ref_pp,
731 const diagnostic_path &path,
732 bool check_rich_locations,
733 bool colorize = false,
734 bool show_event_links = true);
735
736 const logical_location_manager &get_logical_location_manager () const
737 {
738 return m_logical_loc_mgr;
739 }
740 unsigned get_num_ranges () const { return m_ranges.length (); }
741 bool multithreaded_p () const { return m_per_thread_summary.length () > 1; }
742
743 const per_thread_summary &get_events_for_thread_id (diagnostic_thread_id_t tid)
744 {
745 per_thread_summary **slot = m_thread_id_to_events.get (k: tid);
746 gcc_assert (slot);
747 gcc_assert (*slot);
748 return **slot;
749 }
750
751 const logical_location_manager &m_logical_loc_mgr;
752 auto_delete_vec <event_range> m_ranges;
753 auto_delete_vec <per_thread_summary> m_per_thread_summary;
754 hash_map<int_hash<diagnostic_thread_id_t, -1, -2>,
755 per_thread_summary *> m_thread_id_to_events;
756
757private:
758 per_thread_summary &
759 get_or_create_events_for_thread_id (const diagnostic_path &path,
760 diagnostic_thread_id_t tid)
761 {
762 if (per_thread_summary **slot = m_thread_id_to_events.get (k: tid))
763 return **slot;
764
765 const diagnostic_thread &thread = path.get_thread (tid);
766 per_thread_summary *pts
767 = new per_thread_summary (path,
768 m_logical_loc_mgr,
769 thread.get_name (can_colorize: false),
770 m_per_thread_summary.length ());
771 m_thread_id_to_events.put (k: tid, v: pts);
772 m_per_thread_summary.safe_push (obj: pts);
773 return *pts;
774 }
775};
776
777/* Return true iff there is more than one stack frame used by the events
778 of this thread. */
779
780bool
781per_thread_summary::interprocedural_p () const
782{
783 if (m_event_ranges.is_empty ())
784 return false;
785 int first_stack_depth = m_event_ranges[0]->m_stack_depth;
786 for (auto range : m_event_ranges)
787 {
788 if (!m_path.same_function_p (event_idx_a: m_event_ranges[0]->m_start_idx,
789 event_idx_b: range->m_start_idx))
790 return true;
791 if (range->m_stack_depth != first_stack_depth)
792 return true;
793 }
794 return false;
795}
796
797/* path_summary's ctor. */
798
799path_summary::path_summary (const path_print_policy &policy,
800 const pretty_printer &ref_pp,
801 const diagnostic_path &path,
802 bool check_rich_locations,
803 bool colorize,
804 bool show_event_links)
805: m_logical_loc_mgr (path.get_logical_location_manager ())
806{
807 const unsigned num_events = path.num_events ();
808
809 event_range *cur_event_range = NULL;
810 for (unsigned idx = 0; idx < num_events; idx++)
811 {
812 const diagnostic_event &event = path.get_event (idx);
813 const diagnostic_thread_id_t thread_id = event.get_thread_id ();
814 per_thread_summary &pts
815 = get_or_create_events_for_thread_id (path, tid: thread_id);
816
817 pts.update_depth_limits (stack_depth: event.get_stack_depth ());
818
819 if (cur_event_range)
820 if (cur_event_range->maybe_add_event (policy,
821 new_ev: event,
822 new_ev_idx: idx, check_rich_locations))
823 continue;
824
825 auto theme = policy.get_diagram_theme ();
826 const bool allow_emojis = theme ? theme->emojis_p () : false;
827 cur_event_range = new event_range (path, ref_pp,
828 idx, event, pts,
829 show_event_links,
830 colorize,
831 allow_emojis);
832 m_ranges.safe_push (obj: cur_event_range);
833 pts.m_event_ranges.safe_push (obj: cur_event_range);
834 pts.m_last_event = &event;
835 }
836}
837
838/* Write SPACES to PP. */
839
840static void
841write_indent (pretty_printer *pp, int spaces)
842{
843 for (int i = 0; i < spaces; i++)
844 pp_space (pp);
845}
846
847static const int base_indent = 2;
848static const int per_frame_indent = 2;
849
850/* A bundle of state for printing event_range instances for a particular
851 thread. */
852
853class thread_event_printer
854{
855public:
856 thread_event_printer (const per_thread_summary &t, bool show_depths)
857 : m_per_thread_summary (t),
858 m_show_depths (show_depths),
859 m_cur_indent (base_indent),
860 m_vbar_column_for_depth (),
861 m_num_printed (0)
862 {
863 }
864
865 /* Get the previous event_range within this thread, if any. */
866 const event_range *get_any_prev_range () const
867 {
868 if (m_num_printed > 0)
869 return m_per_thread_summary.m_event_ranges[m_num_printed - 1];
870 else
871 return nullptr;
872 }
873
874 /* Get the next event_range within this thread, if any. */
875 const event_range *get_any_next_range () const
876 {
877 if (m_num_printed < m_per_thread_summary.m_event_ranges.length () - 1)
878 return m_per_thread_summary.m_event_ranges[m_num_printed + 1];
879 else
880 return nullptr;
881 }
882
883 void
884 print_swimlane_for_event_range_as_text (diagnostic_text_output_format &text_output,
885 pretty_printer *pp,
886 const logical_location_manager &logical_loc_mgr,
887 event_range *range,
888 diagnostic_source_effect_info *effect_info)
889 {
890 gcc_assert (pp);
891 const char *const line_color = "path";
892 const char *start_line_color
893 = colorize_start (show_color: pp_show_color (pp), name: line_color);
894 const char *end_line_color = colorize_stop (pp_show_color (pp));
895
896 text_art::ascii_theme fallback_theme;
897 text_art::theme *theme = text_output.get_diagram_theme ();
898 if (!theme)
899 theme = &fallback_theme;
900
901 cppchar_t depth_marker_char = theme->get_cppchar
902 (kind: text_art::theme::cell_kind::INTERPROCEDURAL_DEPTH_MARKER);
903 /* e.g. "|". */
904
905 const bool interprocedural_p = m_per_thread_summary.interprocedural_p ();
906
907 write_indent (pp, spaces: m_cur_indent);
908 if (const event_range *prev_range = get_any_prev_range ())
909 {
910 if (range->m_stack_depth > prev_range->m_stack_depth)
911 {
912 gcc_assert (interprocedural_p);
913 /* Show pushed stack frame(s). */
914 cppchar_t left = theme->get_cppchar
915 (kind: text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_LEFT);
916 cppchar_t middle = theme->get_cppchar
917 (kind: text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_MIDDLE);
918 cppchar_t right = theme->get_cppchar
919 (kind: text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_RIGHT);
920 /* e.g. "+--> ". */
921 pp_string (pp, start_line_color);
922 pp_unicode_character (pp, left);
923 pp_unicode_character (pp, middle);
924 pp_unicode_character (pp, middle);
925 pp_unicode_character (pp, right);
926 pp_space (pp);
927 pp_string (pp, end_line_color);
928 m_cur_indent += 5;
929 }
930 }
931 if (range->m_logical_loc)
932 {
933 label_text name
934 (logical_loc_mgr.get_name_for_path_output (k: range->m_logical_loc));
935 if (name.get ())
936 pp_printf (pp, "%qs: ", name.get ());
937 }
938 if (range->m_start_idx == range->m_end_idx)
939 pp_printf (pp, "event %i",
940 range->m_start_idx + 1);
941 else
942 pp_printf (pp, "events %i-%i",
943 range->m_start_idx + 1, range->m_end_idx + 1);
944 if (m_show_depths)
945 pp_printf (pp, " (depth %i)", range->m_stack_depth);
946 pp_newline (pp);
947
948 /* Print a run of events. */
949 if (interprocedural_p)
950 {
951 write_indent (pp, spaces: m_cur_indent + per_frame_indent);
952 pp_string (pp, start_line_color);
953 pp_unicode_character (pp, depth_marker_char);
954 pp_string (pp, end_line_color);
955 pp_newline (pp);
956
957 char *saved_prefix = pp_take_prefix (pp);
958 char *prefix;
959 {
960 pretty_printer tmp_pp;
961 write_indent (pp: &tmp_pp, spaces: m_cur_indent + per_frame_indent);
962 pp_string (&tmp_pp, start_line_color);
963 pp_unicode_character (&tmp_pp, depth_marker_char);
964 pp_string (&tmp_pp, end_line_color);
965 prefix = xstrdup (pp_formatted_text (&tmp_pp));
966 }
967 pp_set_prefix (pp, prefix);
968 pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
969 range->print_as_text (pp&: *pp, text_output, effect_info);
970 pp_set_prefix (pp, prefix: saved_prefix);
971
972 write_indent (pp, spaces: m_cur_indent + per_frame_indent);
973 pp_string (pp, start_line_color);
974 pp_unicode_character (pp, depth_marker_char);
975 pp_string (pp, end_line_color);
976 pp_newline (pp);
977 }
978 else
979 range->print_as_text (pp&: *pp, text_output, effect_info);
980
981 if (const event_range *next_range = get_any_next_range ())
982 {
983 if (range->m_stack_depth > next_range->m_stack_depth)
984 {
985 if (m_vbar_column_for_depth.get (k: next_range->m_stack_depth))
986 {
987 /* Show returning from stack frame(s), by printing
988 something like:
989 " |\n"
990 " <-------------+\n"
991 " |\n". */
992 gcc_assert (interprocedural_p);
993 cppchar_t left = theme->get_cppchar
994 (kind: text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_LEFT);
995 cppchar_t middle = theme->get_cppchar
996 (kind: text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_MIDDLE);
997 cppchar_t right = theme->get_cppchar
998 (kind: text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_RIGHT);
999 int vbar_for_next_frame
1000 = *m_vbar_column_for_depth.get (k: next_range->m_stack_depth);
1001
1002 int indent_for_next_frame
1003 = vbar_for_next_frame - per_frame_indent;
1004 write_indent (pp, spaces: vbar_for_next_frame);
1005 pp_string (pp, start_line_color);
1006 pp_unicode_character (pp, left);
1007 for (int i = indent_for_next_frame + per_frame_indent;
1008 i < m_cur_indent + per_frame_indent - 1; i++)
1009 pp_unicode_character (pp, middle);
1010 pp_unicode_character (pp, right);
1011 pp_string (pp, end_line_color);
1012 pp_newline (pp);
1013 m_cur_indent = indent_for_next_frame;
1014
1015 write_indent (pp, spaces: vbar_for_next_frame);
1016 pp_string (pp, start_line_color);
1017 pp_unicode_character (pp, depth_marker_char);
1018 pp_string (pp, end_line_color);
1019 pp_newline (pp);
1020 }
1021 else
1022 {
1023 /* Handle disjoint paths (e.g. a callback at some later
1024 time). */
1025 m_cur_indent = base_indent;
1026 }
1027 }
1028 else if (range->m_stack_depth < next_range->m_stack_depth)
1029 {
1030 /* Prepare to show pushed stack frame. */
1031 gcc_assert (interprocedural_p);
1032 gcc_assert (range->m_stack_depth != EMPTY);
1033 gcc_assert (range->m_stack_depth != DELETED);
1034 m_vbar_column_for_depth.put (k: range->m_stack_depth,
1035 v: m_cur_indent + per_frame_indent);
1036 m_cur_indent += per_frame_indent;
1037 }
1038 }
1039
1040 m_num_printed++;
1041 }
1042
1043 void
1044 print_swimlane_for_event_range_as_html (diagnostic_context &dc,
1045 xml::printer &xp,
1046 html_label_writer *event_label_writer,
1047 event_range *range,
1048 diagnostic_source_effect_info *effect_info)
1049 {
1050 range->print_as_html (xp, dc, effect_info, event_label_writer);
1051 m_num_printed++;
1052 }
1053
1054 int get_cur_indent () const { return m_cur_indent; }
1055
1056private:
1057 const per_thread_summary &m_per_thread_summary;
1058 bool m_show_depths;
1059
1060 /* Print the ranges. */
1061 int m_cur_indent;
1062
1063 /* Keep track of column numbers of existing '|' characters for
1064 stack depths we've already printed. */
1065 static const int EMPTY = -1;
1066 static const int DELETED = -2;
1067 typedef int_hash <int, EMPTY, DELETED> vbar_hash;
1068 hash_map <vbar_hash, int> m_vbar_column_for_depth;
1069
1070 /* How many event ranges within this swimlane have we printed.
1071 This is the index of the next event_range to print. */
1072 unsigned m_num_printed;
1073};
1074
1075/* Print path_summary PS to TEXT_OUTPUT, giving an overview of the
1076 interprocedural calls and returns.
1077
1078 Print the event descriptions in a nested form, printing the event
1079 descriptions within calls to diagnostic_show_locus, using labels to
1080 show the events:
1081
1082 'foo' (events 1-2)
1083 | NN |
1084 | |
1085 +--> 'bar' (events 3-4)
1086 | NN |
1087 | |
1088 +--> 'baz' (events 5-6)
1089 | NN |
1090 | |
1091 <------------ +
1092 |
1093 'foo' (events 7-8)
1094 | NN |
1095 | |
1096 +--> 'bar' (events 9-10)
1097 | NN |
1098 | |
1099 +--> 'baz' (events 11-12)
1100 | NN |
1101 | |
1102
1103 If SHOW_DEPTHS is true, append " (depth N)" to the header of each run
1104 of events.
1105
1106 For events with UNKNOWN_LOCATION, print a summary of each the event. */
1107
1108static void
1109print_path_summary_as_text (const path_summary &ps,
1110 diagnostic_text_output_format &text_output,
1111 bool show_depths)
1112{
1113 pretty_printer *const pp = text_output.get_printer ();
1114
1115 std::vector<thread_event_printer> thread_event_printers;
1116 for (auto t : ps.m_per_thread_summary)
1117 thread_event_printers.push_back (x: thread_event_printer (*t, show_depths));
1118
1119 unsigned i;
1120 event_range *range;
1121 int last_out_edge_column = -1;
1122 FOR_EACH_VEC_ELT (ps.m_ranges, i, range)
1123 {
1124 const int swimlane_idx
1125 = range->m_per_thread_summary.get_swimlane_index ();
1126 if (ps.multithreaded_p ())
1127 if (i == 0 || ps.m_ranges[i - 1]->m_thread_id != range->m_thread_id)
1128 {
1129 if (i > 0)
1130 pp_newline (pp);
1131 pp_printf (pp, "Thread: %qs",
1132 range->m_per_thread_summary.get_name ());
1133 pp_newline (pp);
1134 }
1135 thread_event_printer &tep = thread_event_printers[swimlane_idx];
1136 /* Wire up any trailing out-edge from previous range to leading in-edge
1137 of this range. */
1138 diagnostic_source_effect_info effect_info;
1139 effect_info.m_leading_in_edge_column = last_out_edge_column;
1140 tep.print_swimlane_for_event_range_as_text
1141 (text_output, pp,
1142 logical_loc_mgr: ps.get_logical_location_manager (),
1143 range, effect_info: &effect_info);
1144 last_out_edge_column = effect_info.m_trailing_out_edge_column;
1145 }
1146}
1147
1148/* Print PS as HTML to XP, using DC and, if non-null EVENT_LABEL_WRITER. */
1149
1150static void
1151print_path_summary_as_html (const path_summary &ps,
1152 diagnostic_context &dc,
1153 xml::printer &xp,
1154 html_label_writer *event_label_writer,
1155 bool show_depths)
1156{
1157 std::vector<thread_event_printer> thread_event_printers;
1158 for (auto t : ps.m_per_thread_summary)
1159 thread_event_printers.push_back (x: thread_event_printer (*t, show_depths));
1160
1161 const logical_location_manager *logical_loc_mgr
1162 = dc.get_logical_location_manager ();
1163
1164 xp.push_tag_with_class (name: "div", class_: "event-ranges", preserve_whitespace: false);
1165
1166 /* Group the ranges into stack frames. */
1167 std::unique_ptr<stack_frame> curr_frame;
1168 unsigned i;
1169 event_range *range;
1170 int last_out_edge_column = -1;
1171 FOR_EACH_VEC_ELT (ps.m_ranges, i, range)
1172 {
1173 const int swimlane_idx
1174 = range->m_per_thread_summary.get_swimlane_index ();
1175
1176 const logical_location this_logical_loc = range->m_logical_loc;
1177 const int this_depth = range->m_stack_depth;
1178 if (curr_frame)
1179 {
1180 int old_stack_depth = curr_frame->m_stack_depth;
1181 if (this_depth > curr_frame->m_stack_depth)
1182 {
1183 emit_svg_arrow (xp, old_depth: old_stack_depth, new_depth: this_depth);
1184 curr_frame
1185 = begin_html_stack_frame (xp,
1186 parent: std::move (curr_frame),
1187 logical_loc: range->m_logical_loc,
1188 stack_depth: range->m_stack_depth,
1189 logical_loc_mgr);
1190 }
1191 else
1192 {
1193 while (this_depth < curr_frame->m_stack_depth
1194 || this_logical_loc != curr_frame->m_logical_loc)
1195 {
1196 curr_frame = end_html_stack_frame (xp, frame: std::move (curr_frame));
1197 if (curr_frame == NULL)
1198 {
1199 curr_frame
1200 = begin_html_stack_frame (xp,
1201 parent: nullptr,
1202 logical_loc: range->m_logical_loc,
1203 stack_depth: range->m_stack_depth,
1204 logical_loc_mgr);
1205 break;
1206 }
1207 }
1208 emit_svg_arrow (xp, old_depth: old_stack_depth, new_depth: this_depth);
1209 }
1210 }
1211 else
1212 {
1213 curr_frame = begin_html_stack_frame (xp,
1214 NULL,
1215 logical_loc: range->m_logical_loc,
1216 stack_depth: range->m_stack_depth,
1217 logical_loc_mgr);
1218 }
1219
1220 xp.push_tag_with_class (name: "table", class_: "event-range-with-margin", preserve_whitespace: false);
1221 xp.push_tag (name: "tr", preserve_whitespace: false);
1222 xp.push_tag_with_class (name: "td", class_: "event-range", preserve_whitespace: false);
1223 xp.push_tag_with_class (name: "div", class_: "events-hdr", preserve_whitespace: true);
1224 if (range->m_logical_loc)
1225 {
1226 gcc_assert (logical_loc_mgr);
1227 label_text funcname
1228 = logical_loc_mgr->get_name_for_path_output (k: range->m_logical_loc);
1229 if (funcname.get ())
1230 {
1231 xp.push_tag_with_class (name: "span", class_: "funcname", preserve_whitespace: true);
1232 xp.add_text (text: funcname.get ());
1233 xp.pop_tag (expected_name: "span");
1234 xp.add_text (text: ": ");
1235 }
1236 }
1237 {
1238 xp.push_tag_with_class (name: "span", class_: "event-ids", preserve_whitespace: true);
1239 pretty_printer pp;
1240 if (range->m_start_idx == range->m_end_idx)
1241 pp_printf (&pp, "event %i",
1242 range->m_start_idx + 1);
1243 else
1244 pp_printf (&pp, "events %i-%i",
1245 range->m_start_idx + 1, range->m_end_idx + 1);
1246 xp.add_text_from_pp (pp);
1247 xp.pop_tag (expected_name: "span");
1248 }
1249 if (show_depths)
1250 {
1251 xp.add_text (text: " ");
1252 xp.push_tag_with_class (name: "span", class_: "depth", preserve_whitespace: true);
1253 pretty_printer pp;
1254 pp_printf (&pp, "(depth %i)", range->m_stack_depth);
1255 xp.add_text_from_pp (pp);
1256 xp.pop_tag (expected_name: "span");
1257 }
1258 xp.pop_tag (expected_name: "div");
1259
1260 /* Print a run of events. */
1261 thread_event_printer &tep = thread_event_printers[swimlane_idx];
1262 /* Wire up any trailing out-edge from previous range to leading in-edge
1263 of this range. */
1264 diagnostic_source_effect_info effect_info;
1265 effect_info.m_leading_in_edge_column = last_out_edge_column;
1266 tep.print_swimlane_for_event_range_as_html (dc, xp, event_label_writer,
1267 range, effect_info: &effect_info);
1268 last_out_edge_column = effect_info.m_trailing_out_edge_column;
1269
1270 xp.pop_tag (expected_name: "td");
1271 xp.pop_tag (expected_name: "tr");
1272 xp.pop_tag (expected_name: "table");
1273 }
1274
1275 /* Close outstanding frames. */
1276 while (curr_frame)
1277 curr_frame = end_html_stack_frame (xp, frame: std::move (curr_frame));
1278
1279 xp.pop_tag (expected_name: "div");
1280}
1281
1282} /* end of anonymous namespace for path-printing code. */
1283
1284class element_event_desc : public pp_element
1285{
1286public:
1287 element_event_desc (const diagnostic_event &event)
1288 : m_event (event)
1289 {
1290 }
1291
1292 void add_to_phase_2 (pp_markup::context &ctxt) final override
1293 {
1294 auto pp = ctxt.m_pp.clone ();
1295 m_event.print_desc (pp&: *pp.get ());
1296 pp_string (&ctxt.m_pp, pp_formatted_text (pp.get ()));
1297 }
1298
1299private:
1300 const diagnostic_event &m_event;
1301};
1302
1303/* Print PATH according to the context's path_format. */
1304
1305void
1306diagnostic_text_output_format::print_path (const diagnostic_path &path)
1307{
1308 const unsigned num_events = path.num_events ();
1309
1310 switch (get_context ().get_path_format ())
1311 {
1312 case DPF_NONE:
1313 /* Do nothing. */
1314 return;
1315
1316 case DPF_SEPARATE_EVENTS:
1317 {
1318 /* A note per event. */
1319 auto &logical_loc_mgr = path.get_logical_location_manager ();
1320 for (unsigned i = 0; i < num_events; i++)
1321 {
1322 const diagnostic_event &event = path.get_event (idx: i);
1323 element_event_desc e_event_desc (event);
1324 diagnostic_event_id_t event_id (i);
1325 if (get_context ().show_path_depths_p ())
1326 {
1327 int stack_depth = event.get_stack_depth ();
1328 /* -fdiagnostics-path-format=separate-events doesn't print
1329 fndecl information, so with -fdiagnostics-show-path-depths
1330 print the fndecls too, if any. */
1331 if (logical_location logical_loc
1332 = event.get_logical_location ())
1333 {
1334 label_text name
1335 (logical_loc_mgr.get_name_for_path_output (k: logical_loc));
1336 inform (event.get_location (),
1337 "%@ %e (fndecl %qs, depth %i)",
1338 &event_id, &e_event_desc,
1339 name.get (), stack_depth);
1340 }
1341 else
1342 inform (event.get_location (),
1343 "%@ %e (depth %i)",
1344 &event_id, &e_event_desc,
1345 stack_depth);
1346 }
1347 else
1348 inform (event.get_location (),
1349 "%@ %e", &event_id, &e_event_desc);
1350 }
1351 }
1352 break;
1353
1354 case DPF_INLINE_EVENTS:
1355 {
1356 /* Consolidate related events. */
1357 path_print_policy policy (*this);
1358 pretty_printer *const pp = get_printer ();
1359 const bool check_rich_locations = true;
1360 const bool colorize = pp_show_color (pp);
1361 const bool show_event_links = m_source_printing.show_event_links_p;
1362 path_summary summary (policy,
1363 *pp,
1364 path,
1365 check_rich_locations,
1366 colorize,
1367 show_event_links);
1368 char *saved_prefix = pp_take_prefix (pp);
1369 pp_set_prefix (pp, NULL);
1370 print_path_summary_as_text (ps: summary, text_output&: *this,
1371 show_depths: get_context ().show_path_depths_p ());
1372 pp_flush (pp);
1373 pp_set_prefix (pp, prefix: saved_prefix);
1374 }
1375 break;
1376 }
1377}
1378
1379/* Print PATH as HTML to XP, using DC and DSPP for settings.
1380 If non-null, use EVENT_LABEL_WRITER when writing events. */
1381
1382void
1383print_path_as_html (xml::printer &xp,
1384 const diagnostic_path &path,
1385 diagnostic_context &dc,
1386 html_label_writer *event_label_writer,
1387 const diagnostic_source_print_policy &dspp)
1388{
1389 path_print_policy policy (dc);
1390 const bool check_rich_locations = true;
1391 const bool colorize = false;
1392 const diagnostic_source_printing_options &source_printing_opts
1393 = dspp.get_options ();
1394 const bool show_event_links = source_printing_opts.show_event_links_p;
1395 path_summary summary (policy,
1396 *dc.get_reference_printer (),
1397 path,
1398 check_rich_locations,
1399 colorize,
1400 show_event_links);
1401 print_path_summary_as_html (ps: summary, dc, xp, event_label_writer,
1402 show_depths: dc.show_path_depths_p ());
1403}
1404
1405#if CHECKING_P
1406
1407namespace selftest {
1408
1409/* Return true iff all events in PATH have locations for which column data
1410 is available, so that selftests that require precise string output can
1411 bail out for awkward line_table cases. */
1412
1413static bool
1414path_events_have_column_data_p (const diagnostic_path &path)
1415{
1416 for (unsigned idx = 0; idx < path.num_events (); idx++)
1417 {
1418 location_t event_loc = path.get_event (idx).get_location ();
1419 if (line_table->get_pure_location (loc: event_loc)
1420 > LINE_MAP_MAX_LOCATION_WITH_COLS)
1421 return false;
1422 if (line_table->get_start (loc: event_loc) > LINE_MAP_MAX_LOCATION_WITH_COLS)
1423 return false;
1424 if (line_table->get_finish (loc: event_loc) > LINE_MAP_MAX_LOCATION_WITH_COLS)
1425 return false;
1426 }
1427 return true;
1428}
1429
1430/* Verify that empty paths are handled gracefully. */
1431
1432static void
1433test_empty_path (pretty_printer *event_pp)
1434{
1435 test_diagnostic_path path (event_pp);
1436 ASSERT_FALSE (path.interprocedural_p ());
1437
1438 test_diagnostic_context dc;
1439 diagnostic_text_output_format text_output (dc);
1440 path_print_policy policy (text_output);
1441 path_summary summary (policy, *event_pp, path, false);
1442 ASSERT_EQ (summary.get_num_ranges (), 0);
1443
1444 print_path_summary_as_text (ps: summary, text_output, show_depths: true);
1445 ASSERT_STREQ ("",
1446 pp_formatted_text (text_output.get_printer ()));
1447}
1448
1449/* Verify that print_path_summary works on a purely intraprocedural path. */
1450
1451static void
1452test_intraprocedural_path (pretty_printer *event_pp)
1453{
1454 test_diagnostic_path path (event_pp);
1455 const char *const funcname = "foo";
1456 path.add_event (UNKNOWN_LOCATION, funcname, depth: 0, fmt: "first %qs", "free");
1457 path.add_event (UNKNOWN_LOCATION, funcname, depth: 0, fmt: "double %qs", "free");
1458
1459 ASSERT_FALSE (path.interprocedural_p ());
1460
1461 test_diagnostic_context dc;
1462 diagnostic_text_output_format text_output (dc);
1463 path_print_policy policy (text_output);
1464 path_summary summary (policy, *event_pp, path, false, false, false);
1465 ASSERT_EQ (summary.get_num_ranges (), 1);
1466
1467 print_path_summary_as_text (ps: summary, text_output, show_depths: true);
1468 ASSERT_STREQ (" `foo': events 1-2 (depth 0)\n"
1469 " (1): first `free'\n"
1470 " (2): double `free'\n",
1471 pp_formatted_text (text_output.get_printer ()));
1472}
1473
1474/* Verify that print_path_summary works on an interprocedural path. */
1475
1476static void
1477test_interprocedural_path_1 (pretty_printer *event_pp)
1478{
1479 test_diagnostic_path path (event_pp);
1480 path.add_entry (callee_name: "test", stack_depth: 0);
1481 path.add_call (caller_name: "test", caller_stack_depth: 0, callee_name: "make_boxed_int");
1482 path.add_call (caller_name: "make_boxed_int", caller_stack_depth: 1, callee_name: "wrapped_malloc");
1483 path.add_event (UNKNOWN_LOCATION,
1484 funcname: "wrapped_malloc", depth: 2, fmt: "calling malloc");
1485 path.add_return (caller_name: "test", stack_depth: 0);
1486 path.add_call (caller_name: "test", caller_stack_depth: 0, callee_name: "free_boxed_int");
1487 path.add_call (caller_name: "free_boxed_int", caller_stack_depth: 1, callee_name: "wrapped_free");
1488 path.add_event (UNKNOWN_LOCATION, funcname: "wrapped_free", depth: 2, fmt: "calling free");
1489 path.add_return (caller_name: "test", stack_depth: 0);
1490 path.add_call (caller_name: "test", caller_stack_depth: 0, callee_name: "free_boxed_int");
1491 path.add_call (caller_name: "free_boxed_int", caller_stack_depth: 1, callee_name: "wrapped_free");
1492 path.add_event (UNKNOWN_LOCATION, funcname: "wrapped_free", depth: 2, fmt: "calling free");
1493 ASSERT_EQ (path.num_events (), 18);
1494
1495 ASSERT_TRUE (path.interprocedural_p ());
1496
1497 {
1498 test_diagnostic_context dc;
1499 diagnostic_text_output_format text_output (dc, nullptr, false);
1500 path_print_policy policy (text_output);
1501 path_summary summary (policy, *event_pp, path, false);
1502 ASSERT_EQ (summary.get_num_ranges (), 9);
1503
1504 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
1505 print_path_summary_as_text (ps: summary, text_output, show_depths: true);
1506 ASSERT_STREQ
1507 (" `test': events 1-2 (depth 0)\n"
1508 " |\n"
1509 " | (1): entering `test'\n"
1510 " | (2): calling `make_boxed_int'\n"
1511 " |\n"
1512 " +--> `make_boxed_int': events 3-4 (depth 1)\n"
1513 " |\n"
1514 " | (3): entering `make_boxed_int'\n"
1515 " | (4): calling `wrapped_malloc'\n"
1516 " |\n"
1517 " +--> `wrapped_malloc': events 5-6 (depth 2)\n"
1518 " |\n"
1519 " | (5): entering `wrapped_malloc'\n"
1520 " | (6): calling malloc\n"
1521 " |\n"
1522 " <-------------+\n"
1523 " |\n"
1524 " `test': events 7-8 (depth 0)\n"
1525 " |\n"
1526 " | (7): returning to `test'\n"
1527 " | (8): calling `free_boxed_int'\n"
1528 " |\n"
1529 " +--> `free_boxed_int': events 9-10 (depth 1)\n"
1530 " |\n"
1531 " | (9): entering `free_boxed_int'\n"
1532 " | (10): calling `wrapped_free'\n"
1533 " |\n"
1534 " +--> `wrapped_free': events 11-12 (depth 2)\n"
1535 " |\n"
1536 " | (11): entering `wrapped_free'\n"
1537 " | (12): calling free\n"
1538 " |\n"
1539 " <-------------+\n"
1540 " |\n"
1541 " `test': events 13-14 (depth 0)\n"
1542 " |\n"
1543 " | (13): returning to `test'\n"
1544 " | (14): calling `free_boxed_int'\n"
1545 " |\n"
1546 " +--> `free_boxed_int': events 15-16 (depth 1)\n"
1547 " |\n"
1548 " | (15): entering `free_boxed_int'\n"
1549 " | (16): calling `wrapped_free'\n"
1550 " |\n"
1551 " +--> `wrapped_free': events 17-18 (depth 2)\n"
1552 " |\n"
1553 " | (17): entering `wrapped_free'\n"
1554 " | (18): calling free\n"
1555 " |\n",
1556 pp_formatted_text (text_output.get_printer ()));
1557 }
1558 {
1559 test_diagnostic_context dc;
1560 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
1561 diagnostic_text_output_format text_output (dc);
1562 path_print_policy policy (text_output);
1563 path_summary summary (policy, *event_pp, path, false);
1564 print_path_summary_as_text (ps: summary, text_output, show_depths: true);
1565 ASSERT_STREQ
1566 (" `test': events 1-2 (depth 0)\n"
1567 " │\n"
1568 " │ (1): entering `test'\n"
1569 " │ (2): calling `make_boxed_int'\n"
1570 " │\n"
1571 " └──> `make_boxed_int': events 3-4 (depth 1)\n"
1572 " │\n"
1573 " │ (3): entering `make_boxed_int'\n"
1574 " │ (4): calling `wrapped_malloc'\n"
1575 " │\n"
1576 " └──> `wrapped_malloc': events 5-6 (depth 2)\n"
1577 " │\n"
1578 " │ (5): entering `wrapped_malloc'\n"
1579 " │ (6): calling malloc\n"
1580 " │\n"
1581 " <─────────────┘\n"
1582 " │\n"
1583 " `test': events 7-8 (depth 0)\n"
1584 " │\n"
1585 " │ (7): returning to `test'\n"
1586 " │ (8): calling `free_boxed_int'\n"
1587 " │\n"
1588 " └──> `free_boxed_int': events 9-10 (depth 1)\n"
1589 " │\n"
1590 " │ (9): entering `free_boxed_int'\n"
1591 " │ (10): calling `wrapped_free'\n"
1592 " │\n"
1593 " └──> `wrapped_free': events 11-12 (depth 2)\n"
1594 " │\n"
1595 " │ (11): entering `wrapped_free'\n"
1596 " │ (12): calling free\n"
1597 " │\n"
1598 " <─────────────┘\n"
1599 " │\n"
1600 " `test': events 13-14 (depth 0)\n"
1601 " │\n"
1602 " │ (13): returning to `test'\n"
1603 " │ (14): calling `free_boxed_int'\n"
1604 " │\n"
1605 " └──> `free_boxed_int': events 15-16 (depth 1)\n"
1606 " │\n"
1607 " │ (15): entering `free_boxed_int'\n"
1608 " │ (16): calling `wrapped_free'\n"
1609 " │\n"
1610 " └──> `wrapped_free': events 17-18 (depth 2)\n"
1611 " │\n"
1612 " │ (17): entering `wrapped_free'\n"
1613 " │ (18): calling free\n"
1614 " │\n",
1615 pp_formatted_text (text_output.get_printer ()));
1616 }
1617
1618}
1619
1620/* Example where we pop the stack to an intermediate frame, rather than the
1621 initial one. */
1622
1623static void
1624test_interprocedural_path_2 (pretty_printer *event_pp)
1625{
1626 test_diagnostic_path path (event_pp);
1627 path.add_entry (callee_name: "foo", stack_depth: 0);
1628 path.add_call (caller_name: "foo", caller_stack_depth: 0, callee_name: "bar");
1629 path.add_call (caller_name: "bar", caller_stack_depth: 1, callee_name: "baz");
1630 path.add_return (caller_name: "bar", stack_depth: 1);
1631 path.add_call (caller_name: "bar", caller_stack_depth: 1, callee_name: "baz");
1632 ASSERT_EQ (path.num_events (), 8);
1633
1634 ASSERT_TRUE (path.interprocedural_p ());
1635
1636 {
1637 test_diagnostic_context dc;
1638 diagnostic_text_output_format text_output (dc);
1639 path_print_policy policy (text_output);
1640 path_summary summary (policy, *event_pp, path, false);
1641 ASSERT_EQ (summary.get_num_ranges (), 5);
1642 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
1643 print_path_summary_as_text (ps: summary, text_output, show_depths: true);
1644 ASSERT_STREQ
1645 (" `foo': events 1-2 (depth 0)\n"
1646 " |\n"
1647 " | (1): entering `foo'\n"
1648 " | (2): calling `bar'\n"
1649 " |\n"
1650 " +--> `bar': events 3-4 (depth 1)\n"
1651 " |\n"
1652 " | (3): entering `bar'\n"
1653 " | (4): calling `baz'\n"
1654 " |\n"
1655 " +--> `baz': event 5 (depth 2)\n"
1656 " |\n"
1657 " | (5): entering `baz'\n"
1658 " |\n"
1659 " <------+\n"
1660 " |\n"
1661 " `bar': events 6-7 (depth 1)\n"
1662 " |\n"
1663 " | (6): returning to `bar'\n"
1664 " | (7): calling `baz'\n"
1665 " |\n"
1666 " +--> `baz': event 8 (depth 2)\n"
1667 " |\n"
1668 " | (8): entering `baz'\n"
1669 " |\n",
1670 pp_formatted_text (text_output.get_printer ()));
1671 }
1672 {
1673 test_diagnostic_context dc;
1674 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
1675 diagnostic_text_output_format text_output (dc);
1676 path_print_policy policy (text_output);
1677 path_summary summary (policy, *event_pp, path, false);
1678 print_path_summary_as_text (ps: summary, text_output, show_depths: true);
1679 ASSERT_STREQ
1680 (" `foo': events 1-2 (depth 0)\n"
1681 " │\n"
1682 " │ (1): entering `foo'\n"
1683 " │ (2): calling `bar'\n"
1684 " │\n"
1685 " └──> `bar': events 3-4 (depth 1)\n"
1686 " │\n"
1687 " │ (3): entering `bar'\n"
1688 " │ (4): calling `baz'\n"
1689 " │\n"
1690 " └──> `baz': event 5 (depth 2)\n"
1691 " │\n"
1692 " │ (5): entering `baz'\n"
1693 " │\n"
1694 " <──────┘\n"
1695 " │\n"
1696 " `bar': events 6-7 (depth 1)\n"
1697 " │\n"
1698 " │ (6): returning to `bar'\n"
1699 " │ (7): calling `baz'\n"
1700 " │\n"
1701 " └──> `baz': event 8 (depth 2)\n"
1702 " │\n"
1703 " │ (8): entering `baz'\n"
1704 " │\n",
1705 pp_formatted_text (text_output.get_printer ()));
1706 }
1707}
1708
1709/* Verify that print_path_summary is sane in the face of a recursive
1710 diagnostic_path. */
1711
1712static void
1713test_recursion (pretty_printer *event_pp)
1714{
1715 test_diagnostic_path path (event_pp);
1716 path.add_entry (callee_name: "factorial", stack_depth: 0);
1717 for (int depth = 0; depth < 3; depth++)
1718 path.add_call (caller_name: "factorial", caller_stack_depth: depth, callee_name: "factorial");
1719 ASSERT_EQ (path.num_events (), 7);
1720
1721 ASSERT_TRUE (path.interprocedural_p ());
1722
1723 {
1724 test_diagnostic_context dc;
1725 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
1726
1727 diagnostic_text_output_format text_output (dc);
1728 path_print_policy policy (text_output);
1729 path_summary summary (policy, *event_pp, path, false);
1730 ASSERT_EQ (summary.get_num_ranges (), 4);
1731
1732 print_path_summary_as_text (ps: summary, text_output, show_depths: true);
1733 ASSERT_STREQ
1734 (" `factorial': events 1-2 (depth 0)\n"
1735 " |\n"
1736 " | (1): entering `factorial'\n"
1737 " | (2): calling `factorial'\n"
1738 " |\n"
1739 " +--> `factorial': events 3-4 (depth 1)\n"
1740 " |\n"
1741 " | (3): entering `factorial'\n"
1742 " | (4): calling `factorial'\n"
1743 " |\n"
1744 " +--> `factorial': events 5-6 (depth 2)\n"
1745 " |\n"
1746 " | (5): entering `factorial'\n"
1747 " | (6): calling `factorial'\n"
1748 " |\n"
1749 " +--> `factorial': event 7 (depth 3)\n"
1750 " |\n"
1751 " | (7): entering `factorial'\n"
1752 " |\n",
1753 pp_formatted_text (text_output.get_printer ()));
1754 }
1755 {
1756 test_diagnostic_context dc;
1757 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
1758
1759 diagnostic_text_output_format text_output (dc);
1760 path_print_policy policy (text_output);
1761 path_summary summary (policy, *event_pp, path, false);
1762 print_path_summary_as_text (ps: summary, text_output, show_depths: true);
1763 ASSERT_STREQ
1764 (" `factorial': events 1-2 (depth 0)\n"
1765 " │\n"
1766 " │ (1): entering `factorial'\n"
1767 " │ (2): calling `factorial'\n"
1768 " │\n"
1769 " └──> `factorial': events 3-4 (depth 1)\n"
1770 " │\n"
1771 " │ (3): entering `factorial'\n"
1772 " │ (4): calling `factorial'\n"
1773 " │\n"
1774 " └──> `factorial': events 5-6 (depth 2)\n"
1775 " │\n"
1776 " │ (5): entering `factorial'\n"
1777 " │ (6): calling `factorial'\n"
1778 " │\n"
1779 " └──> `factorial': event 7 (depth 3)\n"
1780 " │\n"
1781 " │ (7): entering `factorial'\n"
1782 " │\n",
1783 pp_formatted_text (text_output.get_printer ()));
1784 }
1785}
1786
1787/* Helper class for writing tests of control flow visualization. */
1788
1789class control_flow_test
1790{
1791public:
1792 control_flow_test (const location &loc,
1793 const line_table_case &case_,
1794 const char *content)
1795 : m_tmp_file (loc, ".c", content,
1796 /* gcc_rich_location::add_location_if_nearby implicitly
1797 uses global_dc's file_cache, so we need to evict
1798 tmp when we're done. */
1799 &global_dc->get_file_cache ()),
1800 m_ltt (case_)
1801 {
1802 m_ord_map
1803 = linemap_check_ordinary (map: linemap_add (line_table, LC_ENTER, sysp: false,
1804 to_file: m_tmp_file.get_filename (), to_line: 0));
1805 linemap_line_start (set: line_table, to_line: 1, max_column_hint: 100);
1806 }
1807
1808 location_t get_line_and_column (int line, int column)
1809 {
1810 return linemap_position_for_line_and_column (set: line_table, m_ord_map,
1811 line, column);
1812 }
1813
1814 location_t get_line_and_columns (int line, int first_column, int last_column)
1815 {
1816 return get_line_and_columns (line,
1817 first_column, caret_column: first_column, last_column);
1818 }
1819
1820 location_t get_line_and_columns (int line,
1821 int first_column,
1822 int caret_column,
1823 int last_column)
1824 {
1825 return make_location (caret: get_line_and_column (line, column: caret_column),
1826 start: get_line_and_column (line, column: first_column),
1827 finish: get_line_and_column (line, column: last_column));
1828 }
1829
1830private:
1831 temp_source_file m_tmp_file;
1832 line_table_test m_ltt;
1833 const line_map_ordinary *m_ord_map;
1834};
1835
1836/* Example of event edges where all events can go in the same layout,
1837 testing the 6 combinations of:
1838 - ASCII vs Unicode vs event links off
1839 - line numbering on and off. */
1840
1841static void
1842test_control_flow_1 (const line_table_case &case_,
1843 pretty_printer *event_pp)
1844{
1845 /* Create a tempfile and write some text to it.
1846 ...000000000111111111122222222223333333333.
1847 ...123456789012345678901234567890123456789. */
1848 const char *content
1849 = ("int test (int *p)\n" /* line 1. */
1850 "{\n" /* line 2. */
1851 " if (p)\n" /* line 3. */
1852 " return 0;\n" /* line 4. */
1853 " return *p;\n" /* line 5. */
1854 "}\n"); /* line 6. */
1855
1856 control_flow_test t (SELFTEST_LOCATION, case_, content);
1857
1858 const location_t conditional = t.get_line_and_column (line: 3, column: 7);
1859 const location_t cfg_dest = t.get_line_and_column (line: 5, column: 10);
1860
1861 test_diagnostic_path path (event_pp);
1862 path.add_event (loc: conditional, funcname: nullptr, depth: 0,
1863 fmt: "following %qs branch (when %qs is NULL)...",
1864 "false", "p");
1865 path.connect_to_next_event ();
1866
1867 path.add_event (loc: cfg_dest, funcname: nullptr, depth: 0,
1868 fmt: "...to here");
1869 path.add_event (loc: cfg_dest, funcname: nullptr, depth: 0,
1870 fmt: "dereference of NULL %qs",
1871 "p");
1872
1873 if (!path_events_have_column_data_p (path))
1874 return;
1875
1876
1877 {
1878 test_diagnostic_context dc;
1879 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
1880 dc.m_source_printing.show_event_links_p = true;
1881 diagnostic_text_output_format text_output (dc);
1882 path_print_policy policy (text_output);
1883 path_summary summary (policy, *event_pp, path, true);
1884 print_path_summary_as_text (ps: summary, text_output, show_depths: false);
1885 ASSERT_STREQ
1886 (" events 1-3\n"
1887 "FILENAME:3:7:\n"
1888 " if (p)\n"
1889 " ^\n"
1890 " |\n"
1891 " (1) following `false' branch (when `p' is NULL)... ->-+\n"
1892 " |\n"
1893 "FILENAME:5:10:\n"
1894 " |\n"
1895 "+------------------------------------------------------------+\n"
1896 "| return *p;\n"
1897 "| ~\n"
1898 "| |\n"
1899 "+-------->(2) ...to here\n"
1900 " (3) dereference of NULL `p'\n",
1901 pp_formatted_text (text_output.get_printer ()));
1902 }
1903 {
1904 test_diagnostic_context dc;
1905 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
1906 dc.m_source_printing.show_event_links_p = false;
1907 diagnostic_text_output_format text_output (dc);
1908 path_print_policy policy (text_output);
1909 path_summary summary (policy, *event_pp, path, true);
1910 print_path_summary_as_text (ps: summary, text_output, show_depths: false);
1911 ASSERT_STREQ
1912 (" events 1-3\n"
1913 "FILENAME:3:7:\n"
1914 " if (p)\n"
1915 " ^\n"
1916 " |\n"
1917 " (1) following `false' branch (when `p' is NULL)...\n"
1918 "FILENAME:5:10:\n"
1919 " return *p;\n"
1920 " ~\n"
1921 " |\n"
1922 " (2) ...to here\n"
1923 " (3) dereference of NULL `p'\n",
1924 pp_formatted_text (text_output.get_printer ()));
1925 }
1926 {
1927 test_diagnostic_context dc;
1928 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
1929 dc.m_source_printing.show_line_numbers_p = true;
1930 dc.m_source_printing.show_event_links_p = true;
1931 diagnostic_text_output_format text_output (dc);
1932 path_print_policy policy (text_output);
1933 path_summary summary (policy, *event_pp, path, true);
1934 print_path_summary_as_text (ps: summary, text_output, show_depths: false);
1935 ASSERT_STREQ
1936 (" events 1-3\n"
1937 "FILENAME:3:7:\n"
1938 " 3 | if (p)\n"
1939 " | ^\n"
1940 " | |\n"
1941 " | (1) following `false' branch (when `p' is NULL)... ->-+\n"
1942 " | |\n"
1943 " | |\n"
1944 " |+------------------------------------------------------------+\n"
1945 " 4 || return 0;\n"
1946 " 5 || return *p;\n"
1947 " || ~\n"
1948 " || |\n"
1949 " |+-------->(2) ...to here\n"
1950 " | (3) dereference of NULL `p'\n",
1951 pp_formatted_text (text_output.get_printer ()));
1952 }
1953 {
1954 test_diagnostic_context dc;
1955 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
1956 dc.m_source_printing.show_line_numbers_p = true;
1957 dc.m_source_printing.show_event_links_p = false;
1958 diagnostic_text_output_format text_output (dc);
1959 path_print_policy policy (text_output);
1960 path_summary summary (policy, *event_pp, path, true);
1961 print_path_summary_as_text (ps: summary, text_output, show_depths: false);
1962 ASSERT_STREQ
1963 (" events 1-3\n"
1964 "FILENAME:3:7:\n"
1965 " 3 | if (p)\n"
1966 " | ^\n"
1967 " | |\n"
1968 " | (1) following `false' branch (when `p' is NULL)...\n"
1969 " 4 | return 0;\n"
1970 " 5 | return *p;\n"
1971 " | ~\n"
1972 " | |\n"
1973 " | (2) ...to here\n"
1974 " | (3) dereference of NULL `p'\n",
1975 pp_formatted_text (text_output.get_printer ()));
1976 }
1977 {
1978 test_diagnostic_context dc;
1979 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
1980 dc.m_source_printing.show_event_links_p = true;
1981 diagnostic_text_output_format text_output (dc);
1982 path_print_policy policy (text_output);
1983 path_summary summary (policy, *event_pp, path, true);
1984 print_path_summary_as_text (ps: summary, text_output, show_depths: false);
1985 ASSERT_STREQ
1986 (" events 1-3\n"
1987 "FILENAME:3:7:\n"
1988 " if (p)\n"
1989 " ^\n"
1990 " |\n"
1991 " (1) following `false' branch (when `p' is NULL)... ─>─┐\n"
1992 " │\n"
1993 "FILENAME:5:10:\n"
1994 " │\n"
1995 "┌────────────────────────────────────────────────────────────┘\n"
1996 "│ return *p;\n"
1997 "│ ~\n"
1998 "│ |\n"
1999 "└────────>(2) ...to here\n"
2000 " (3) dereference of NULL `p'\n",
2001 pp_formatted_text (text_output.get_printer ()));
2002 }
2003 {
2004 test_diagnostic_context dc;
2005 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
2006 dc.m_source_printing.show_event_links_p = true;
2007 dc.m_source_printing.show_line_numbers_p = true;
2008 diagnostic_text_output_format text_output (dc);
2009 path_print_policy policy (text_output);
2010 path_summary summary (policy, *event_pp, path, true);
2011 print_path_summary_as_text (ps: summary, text_output, show_depths: false);
2012 ASSERT_STREQ
2013 (" events 1-3\n"
2014 "FILENAME:3:7:\n"
2015 " 3 | if (p)\n"
2016 " | ^\n"
2017 " | |\n"
2018 " | (1) following `false' branch (when `p' is NULL)... ─>─┐\n"
2019 " | │\n"
2020 " | │\n"
2021 " |┌────────────────────────────────────────────────────────────┘\n"
2022 " 4 |│ return 0;\n"
2023 " 5 |│ return *p;\n"
2024 " |│ ~\n"
2025 " |│ |\n"
2026 " |└────────>(2) ...to here\n"
2027 " | (3) dereference of NULL `p'\n",
2028 pp_formatted_text (text_output.get_printer ()));
2029 }
2030}
2031
2032/* Complex example involving a backedge. */
2033
2034static void
2035test_control_flow_2 (const line_table_case &case_,
2036 pretty_printer *event_pp)
2037{
2038 /* Create a tempfile and write some text to it.
2039 ...000000000111111111122222222223333333333.
2040 ...123456789012345678901234567890123456789. */
2041 const char *content
2042 = ("int for_loop_noop_next (struct node *n)\n" /* <--------- line 1. */
2043 "{\n" /* <----------------------------------------------- line 2. */
2044 " int sum = 0;\n" /* <---------------------------------- line 3. */
2045 " for (struct node *iter = n; iter; iter->next)\n" /* <- line 4. */
2046 " sum += n->val;\n" /* <------------------------------ line 5. */
2047 " return sum;\n" /* <----------------------------------- line 6. */
2048 "}\n"); /* <-------------------------------------------- line 7. */
2049 /* Adapted from infinite-loop-linked-list.c where
2050 "iter->next" should be "iter = iter->next". */
2051
2052 control_flow_test t (SELFTEST_LOCATION, case_, content);
2053
2054 const location_t iter_test = t.get_line_and_columns (line: 4, first_column: 31, last_column: 34);
2055 const location_t loop_body_start = t.get_line_and_columns (line: 5, first_column: 12, last_column: 17);
2056 const location_t loop_body_end = t.get_line_and_columns (line: 5, first_column: 5, caret_column: 9, last_column: 17);
2057
2058 test_diagnostic_path path (event_pp);
2059 path.add_event (loc: iter_test, funcname: nullptr, depth: 0, fmt: "infinite loop here");
2060
2061 path.add_event (loc: iter_test, funcname: nullptr, depth: 0, fmt: "looping from here...");
2062 path.connect_to_next_event ();
2063
2064 path.add_event (loc: loop_body_start, funcname: nullptr, depth: 0, fmt: "...to here");
2065
2066 path.add_event (loc: loop_body_end, funcname: nullptr, depth: 0, fmt: "looping back...");
2067 path.connect_to_next_event ();
2068
2069 path.add_event (loc: iter_test, funcname: nullptr, depth: 0, fmt: "...to here");
2070
2071 if (!path_events_have_column_data_p (path))
2072 return;
2073
2074 {
2075 test_diagnostic_context dc;
2076 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
2077 dc.m_source_printing.show_event_links_p = true;
2078 dc.m_source_printing.show_line_numbers_p = true;
2079 diagnostic_text_output_format text_output (dc);
2080 path_print_policy policy (text_output);
2081 path_summary summary (policy, *event_pp, path, true);
2082 print_path_summary_as_text (ps: summary, text_output, show_depths: false);
2083 ASSERT_STREQ
2084 (" events 1-3\n"
2085 "FILENAME:4:31:\n"
2086 " 4 | for (struct node *iter = n; iter; iter->next)\n"
2087 " | ^~~~\n"
2088 " | |\n"
2089 " | (1) infinite loop here\n"
2090 " | (2) looping from here... ->-+\n"
2091 " | |\n"
2092 " | |\n"
2093 " |+----------------------------------------------------------+\n"
2094 " 5 || sum += n->val;\n"
2095 " || ~~~~~~ \n"
2096 " || |\n"
2097 " |+---------->(3) ...to here\n"
2098 /* We need to start an new event_range here as event (4) is to the
2099 left of event (3), and thus (4) would mess up the in-edge to (3). */
2100 " event 4\n"
2101 " 5 | sum += n->val;\n"
2102 " | ~~~~^~~~~~~~~\n"
2103 " | |\n"
2104 " | (4) looping back... ->-+\n"
2105 " | |\n"
2106 /* We need to start an new event_range here as event (4) with an
2107 out-edge is on a later line (line 5) than its destination event (5),
2108 on line 4. */
2109 " event 5\n"
2110 " | |\n"
2111 " |+-------------------------------+\n"
2112 " 4 || for (struct node *iter = n; iter; iter->next)\n"
2113 " || ^~~~\n"
2114 " || |\n"
2115 " |+----------------------------->(5) ...to here\n",
2116 pp_formatted_text (text_output.get_printer ()));
2117 }
2118}
2119
2120/* Complex example involving a backedge and both an in-edge and out-edge
2121 on the same line. */
2122
2123static void
2124test_control_flow_3 (const line_table_case &case_,
2125 pretty_printer *event_pp)
2126{
2127 /* Create a tempfile and write some text to it.
2128 ...000000000111111111122222222223333333333.
2129 ...123456789012345678901234567890123456789. */
2130 const char *content
2131 = ("void test_missing_comparison_in_for_condition_1 (int n)\n"
2132 "{\n" /* <------------------------- line 2. */
2133 " for (int i = 0; n; i++)\n" /* <- line 3. */
2134 " {\n" /* <--------------------- line 4. */
2135 " }\n" /* <--------------------- line 5. */
2136 "}\n"); /* <----------------------- line 6. */
2137 /* Adapted from infinite-loop-1.c where the condition should have been
2138 "i < n", rather than just "n". */
2139
2140 control_flow_test t (SELFTEST_LOCATION, case_, content);
2141
2142 const location_t iter_test = t.get_line_and_column (line: 3, column: 19);
2143 const location_t iter_next = t.get_line_and_columns (line: 3, first_column: 22, last_column: 24);
2144
2145 test_diagnostic_path path (event_pp);
2146 path.add_event (loc: iter_test, funcname: nullptr, depth: 0, fmt: "infinite loop here");
2147
2148 path.add_event (loc: iter_test, funcname: nullptr, depth: 0, fmt: "looping from here...");
2149 path.connect_to_next_event ();
2150
2151 path.add_event (loc: iter_next, funcname: nullptr, depth: 0, fmt: "...to here");
2152
2153 path.add_event (loc: iter_next, funcname: nullptr, depth: 0, fmt: "looping back...");
2154 path.connect_to_next_event ();
2155
2156 path.add_event (loc: iter_test, funcname: nullptr, depth: 0, fmt: "...to here");
2157
2158 if (!path_events_have_column_data_p (path))
2159 return;
2160
2161 {
2162 test_diagnostic_context dc;
2163 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
2164 dc.m_source_printing.show_event_links_p = true;
2165 dc.m_source_printing.show_line_numbers_p = true;
2166 diagnostic_text_output_format text_output (dc);
2167 path_print_policy policy (text_output);
2168 path_summary summary (policy, *event_pp, path, true);
2169 print_path_summary_as_text (ps: summary, text_output, show_depths: false);
2170 ASSERT_STREQ
2171 (" events 1-2\n"
2172 "FILENAME:3:19:\n"
2173 " 3 | for (int i = 0; n; i++)\n"
2174 " | ^\n"
2175 " | |\n"
2176 " | (1) infinite loop here\n"
2177 " | (2) looping from here... ->-+\n"
2178 " | |\n"
2179 " events 3-4\n"
2180 " | |\n"
2181 " |+----------------------------------------------+\n"
2182 " 3 || for (int i = 0; n; i++)\n"
2183 " || ^~~\n"
2184 " || |\n"
2185 " |+-------------------->(3) ...to here\n"
2186 " | (4) looping back... ->-+\n"
2187 " | |\n"
2188 /* We need to start an new event_range here as event (4) with an
2189 out-edge is on the same line as its destination event (5), but
2190 to the right, which we can't handle as a single event_range. */
2191 " event 5\n"
2192 " | |\n"
2193 " |+--------------------------------------------+\n"
2194 " 3 || for (int i = 0; n; i++)\n"
2195 " || ^\n"
2196 " || |\n"
2197 " |+----------------->(5) ...to here\n",
2198 pp_formatted_text (text_output.get_printer ()));
2199 }
2200}
2201
2202/* Implementation of ASSERT_CFG_EDGE_PATH_STREQ. */
2203
2204static void
2205assert_cfg_edge_path_streq (const location &loc,
2206 pretty_printer *event_pp,
2207 const location_t src_loc,
2208 const location_t dst_loc,
2209 const char *expected_str)
2210{
2211 test_diagnostic_path path (event_pp);
2212 path.add_event (loc: src_loc, funcname: nullptr, depth: 0, fmt: "from here...");
2213 path.connect_to_next_event ();
2214
2215 path.add_event (loc: dst_loc, funcname: nullptr, depth: 0, fmt: "...to here");
2216
2217 if (!path_events_have_column_data_p (path))
2218 return;
2219
2220 test_diagnostic_context dc;
2221 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
2222 dc.m_source_printing.show_event_links_p = true;
2223 dc.m_source_printing.show_line_numbers_p = true;
2224 diagnostic_text_output_format text_output (dc);
2225 path_print_policy policy (text_output);
2226 path_summary summary (policy, *event_pp, path, true);
2227 print_path_summary_as_text (ps: summary, text_output, show_depths: false);
2228 ASSERT_STREQ_AT (loc, expected_str,
2229 pp_formatted_text (text_output.get_printer ()));
2230}
2231
2232/* Assert that if we make a path with an event with "from here..." at SRC_LOC
2233 leading to an event "...to here" at DST_LOC that we print the path
2234 as EXPECTED_STR. */
2235
2236#define ASSERT_CFG_EDGE_PATH_STREQ(SRC_LOC, DST_LOC, EXPECTED_STR) \
2237 assert_cfg_edge_path_streq ((SELFTEST_LOCATION), (event_pp), \
2238 (SRC_LOC), (DST_LOC), (EXPECTED_STR))
2239
2240/* Various examples of edge, trying to cover all combinations of:
2241 - relative x positive of src label and dst label
2242 - relative y position of labels:
2243 - on same line
2244 - on next line
2245 - on line after next
2246 - big gap, where src is before dst
2247 - big gap, where src is after dst
2248 and other awkward cases. */
2249
2250static void
2251test_control_flow_4 (const line_table_case &case_,
2252 pretty_printer *event_pp)
2253{
2254 std::string many_lines;
2255 for (int i = 1; i <= 100; i++)
2256 /* ............000000000111
2257 ............123456789012. */
2258 many_lines += "LHS RHS\n";
2259 control_flow_test t (SELFTEST_LOCATION, case_, many_lines.c_str ());
2260
2261 /* Same line. */
2262 {
2263 /* LHS -> RHS. */
2264 ASSERT_CFG_EDGE_PATH_STREQ
2265 (t.get_line_and_columns (3, 1, 3),
2266 t.get_line_and_columns (3, 10, 12),
2267 (" event 1\n"
2268 "FILENAME:3:1:\n"
2269 " 3 | LHS RHS\n"
2270 " | ^~~\n"
2271 " | |\n"
2272 " | (1) from here... ->-+\n"
2273 " | |\n"
2274 " event 2\n"
2275 " | |\n"
2276 " |+--------------------+\n"
2277 " 3 ||LHS RHS\n"
2278 " || ^~~\n"
2279 " || |\n"
2280 " |+-------->(2) ...to here\n"));
2281
2282 /* RHS -> LHS. */
2283 ASSERT_CFG_EDGE_PATH_STREQ
2284 (t.get_line_and_columns (3, 10, 12),
2285 t.get_line_and_columns (3, 1, 3),
2286 (" event 1\n"
2287 "FILENAME:3:10:\n"
2288 " 3 | LHS RHS\n"
2289 " | ^~~\n"
2290 " | |\n"
2291 " | (1) from here... ->-+\n"
2292 " | |\n"
2293 " event 2\n"
2294 " | |\n"
2295 " |+-----------------------------+\n"
2296 " 3 ||LHS RHS\n"
2297 " ||^~~\n"
2298 " |||\n"
2299 " |+(2) ...to here\n"));
2300 }
2301
2302 /* Next line. */
2303 {
2304 /* LHS -> RHS. */
2305 ASSERT_CFG_EDGE_PATH_STREQ
2306 (t.get_line_and_columns (3, 1, 3),
2307 t.get_line_and_columns (4, 5, 7),
2308 (" events 1-2\n"
2309 "FILENAME:3:1:\n"
2310 " 3 | LHS RHS\n"
2311 " | ^~~\n"
2312 " | |\n"
2313 " | (1) from here... ->-+\n"
2314 " | |\n"
2315 " | |\n"
2316 " |+--------------------+\n"
2317 " 4 ||LHS RHS\n"
2318 " || ~~~\n"
2319 " || |\n"
2320 " |+--->(2) ...to here\n"));
2321
2322 /* RHS -> LHS. */
2323 ASSERT_CFG_EDGE_PATH_STREQ
2324 (t.get_line_and_columns (3, 10, 12),
2325 t.get_line_and_columns (4, 1, 3),
2326 (" events 1-2\n"
2327 "FILENAME:3:10:\n"
2328 " 3 | LHS RHS\n"
2329 " | ^~~\n"
2330 " | |\n"
2331 " | (1) from here... ->-+\n"
2332 " | |\n"
2333 " | |\n"
2334 " |+-----------------------------+\n"
2335 " 4 ||LHS RHS\n"
2336 " ||~~~ \n"
2337 " |||\n"
2338 " |+(2) ...to here\n"));
2339 }
2340
2341 /* Line after next. */
2342 {
2343 /* LHS -> RHS. */
2344 ASSERT_CFG_EDGE_PATH_STREQ
2345 (t.get_line_and_columns (3, 1, 3),
2346 t.get_line_and_columns (5, 10, 12),
2347 (" events 1-2\n"
2348 "FILENAME:3:1:\n"
2349 " 3 | LHS RHS\n"
2350 " | ^~~\n"
2351 " | |\n"
2352 " | (1) from here... ->-+\n"
2353 " | |\n"
2354 " | |\n"
2355 " |+--------------------+\n"
2356 " 4 ||LHS RHS\n"
2357 " 5 ||LHS RHS\n"
2358 " || ~~~\n"
2359 " || |\n"
2360 " |+-------->(2) ...to here\n"));
2361
2362 /* RHS -> LHS. */
2363 ASSERT_CFG_EDGE_PATH_STREQ
2364 (t.get_line_and_columns (3, 10, 12),
2365 t.get_line_and_columns (5, 1, 3),
2366 (" events 1-2\n"
2367 "FILENAME:3:10:\n"
2368 " 3 | LHS RHS\n"
2369 " | ^~~\n"
2370 " | |\n"
2371 " | (1) from here... ->-+\n"
2372 " | |\n"
2373 " | |\n"
2374 " |+-----------------------------+\n"
2375 " 4 ||LHS RHS\n"
2376 " 5 ||LHS RHS\n"
2377 " ||~~~ \n"
2378 " |||\n"
2379 " |+(2) ...to here\n"));
2380 }
2381
2382 /* Big gap, increasing line number. */
2383 {
2384 /* LHS -> RHS. */
2385 ASSERT_CFG_EDGE_PATH_STREQ
2386 (t.get_line_and_columns (3, 1, 3),
2387 t.get_line_and_columns (97, 10, 12),
2388 (" events 1-2\n"
2389 "FILENAME:3:1:\n"
2390 " 3 | LHS RHS\n"
2391 " | ^~~\n"
2392 " | |\n"
2393 " | (1) from here... ->-+\n"
2394 " | |\n"
2395 "......\n"
2396 " | |\n"
2397 " |+--------------------+\n"
2398 " 97 ||LHS RHS\n"
2399 " || ~~~\n"
2400 " || |\n"
2401 " |+-------->(2) ...to here\n"));
2402
2403 /* RHS -> LHS. */
2404 ASSERT_CFG_EDGE_PATH_STREQ
2405 (t.get_line_and_columns (3, 10, 12),
2406 t.get_line_and_columns (97, 1, 3),
2407 (" events 1-2\n"
2408 "FILENAME:3:10:\n"
2409 " 3 | LHS RHS\n"
2410 " | ^~~\n"
2411 " | |\n"
2412 " | (1) from here... ->-+\n"
2413 " | |\n"
2414 "......\n"
2415 " | |\n"
2416 " |+-----------------------------+\n"
2417 " 97 ||LHS RHS\n"
2418 " ||~~~ \n"
2419 " |||\n"
2420 " |+(2) ...to here\n"));
2421 }
2422
2423 /* Big gap, decreasing line number. */
2424 {
2425 /* LHS -> RHS. */
2426 ASSERT_CFG_EDGE_PATH_STREQ
2427 (t.get_line_and_columns (97, 1, 3),
2428 t.get_line_and_columns (3, 10, 12),
2429 (" event 1\n"
2430 "FILENAME:97:1:\n"
2431 " 97 | LHS RHS\n"
2432 " | ^~~\n"
2433 " | |\n"
2434 " | (1) from here... ->-+\n"
2435 " | |\n"
2436 " event 2\n"
2437 " | |\n"
2438 " |+--------------------+\n"
2439 " 3 ||LHS RHS\n"
2440 " || ^~~\n"
2441 " || |\n"
2442 " |+-------->(2) ...to here\n"));
2443
2444 /* RHS -> LHS. */
2445 ASSERT_CFG_EDGE_PATH_STREQ
2446 (t.get_line_and_columns (97, 10, 12),
2447 t.get_line_and_columns (3, 1, 3),
2448 (" event 1\n"
2449 "FILENAME:97:10:\n"
2450 " 97 | LHS RHS\n"
2451 " | ^~~\n"
2452 " | |\n"
2453 " | (1) from here... ->-+\n"
2454 " | |\n"
2455 " event 2\n"
2456 " | |\n"
2457 " |+-----------------------------+\n"
2458 " 3 ||LHS RHS\n"
2459 " ||^~~\n"
2460 " |||\n"
2461 " |+(2) ...to here\n"));
2462 }
2463
2464 /* Unknown src. */
2465 {
2466 ASSERT_CFG_EDGE_PATH_STREQ
2467 (UNKNOWN_LOCATION,
2468 t.get_line_and_columns (3, 10, 12),
2469 (" event 1\n"
2470 " (1): from here...\n"
2471 " event 2\n"
2472 "FILENAME:3:10:\n"
2473 " 3 | LHS RHS\n"
2474 " | ^~~\n"
2475 " | |\n"
2476 " |+-------->(2) ...to here\n"));
2477 }
2478
2479 /* Unknown dst. */
2480 {
2481 ASSERT_CFG_EDGE_PATH_STREQ
2482 (t.get_line_and_columns (3, 1, 3),
2483 UNKNOWN_LOCATION,
2484 (" event 1\n"
2485 "FILENAME:3:1:\n"
2486 " 3 | LHS RHS\n"
2487 " | ^~~\n"
2488 " | |\n"
2489 " | (1) from here... ->-+\n"
2490 " | |\n"
2491 " event 2\n"
2492 "FILENAME:\n"
2493 " (2): ...to here\n"));
2494 }
2495}
2496
2497/* Another complex example, adapted from data-model-20.c. */
2498
2499static void
2500test_control_flow_5 (const line_table_case &case_,
2501 pretty_printer *event_pp)
2502{
2503 /* Create a tempfile and write some text to it.
2504 ...000000000111111111122222222223333333333444444444455555555556666666666.
2505 ...123456789012345678901234567890123456789012345678901234567890123456789. */
2506 const char *content
2507 = (" if ((arr = (struct foo **)malloc(n * sizeof(struct foo *))) == NULL)\n"
2508 " return NULL;\n" /* <------------------------- line 2. */
2509 "\n" /* <----------------------------------------- line 3. */
2510 " for (i = 0; i < n; i++) {\n" /* <-------------- line 4. */
2511 " if ((arr[i] = (struct foo *)malloc(sizeof(struct foo))) == NULL) {\n");
2512
2513 control_flow_test t (SELFTEST_LOCATION, case_, content);
2514
2515 test_diagnostic_path path (event_pp);
2516 /* (1) */
2517 path.add_event (loc: t.get_line_and_column (line: 1, column: 6), funcname: nullptr, depth: 0,
2518 fmt: "following %qs branch (when %qs is non-NULL)...",
2519 "false", "arr");
2520 path.connect_to_next_event ();
2521
2522 /* (2) */
2523 path.add_event (loc: t.get_line_and_columns (line: 4, first_column: 8, caret_column: 10, last_column: 12), funcname: nullptr, depth: 0,
2524 fmt: "...to here");
2525
2526 /* (3) */
2527 path.add_event (loc: t.get_line_and_columns (line: 4, first_column: 15, caret_column: 17, last_column: 19), funcname: nullptr, depth: 0,
2528 fmt: "following %qs branch (when %qs)...",
2529 "true", "i < n");
2530 path.connect_to_next_event ();
2531
2532 /* (4) */
2533 path.add_event (loc: t.get_line_and_column (line: 5, column: 13), funcname: nullptr, depth: 0,
2534 fmt: "...to here");
2535
2536 /* (5) */
2537 path.add_event (loc: t.get_line_and_columns (line: 5, first_column: 33, last_column: 58), funcname: nullptr, depth: 0,
2538 fmt: "allocated here");
2539
2540 if (!path_events_have_column_data_p (path))
2541 return;
2542
2543 {
2544 test_diagnostic_context dc;
2545 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
2546 dc.m_source_printing.show_event_links_p = true;
2547 dc.m_source_printing.show_line_numbers_p = true;
2548 diagnostic_text_output_format text_output (dc);
2549 path_print_policy policy (text_output);
2550 path_summary summary (policy, *event_pp, path, true);
2551 print_path_summary_as_text (ps: summary, text_output, show_depths: false);
2552 ASSERT_STREQ
2553 (" events 1-5\n"
2554 "FILENAME:1:6:\n"
2555 " 1 | if ((arr = (struct foo **)malloc(n * sizeof(struct foo *))) == NULL)\n"
2556 " | ^\n"
2557 " | |\n"
2558 " | (1) following `false' branch (when `arr' is non-NULL)... ->-+\n"
2559 " | |\n"
2560 "......\n"
2561 " | |\n"
2562 " |+-----------------------------------------------------------------+\n"
2563 " 4 || for (i = 0; i < n; i++) {\n"
2564 " || ~~~~~ ~~~~~\n"
2565 " || | |\n"
2566 " || | (3) following `true' branch (when `i < n')... ->-+\n"
2567 " |+-------->(2) ...to here |\n"
2568 " | |\n"
2569 " | |\n"
2570 " |+-----------------------------------------------------------------+\n"
2571 " 5 || if ((arr[i] = (struct foo *)malloc(sizeof(struct foo))) == NULL) {\n"
2572 " || ~ ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
2573 " || | |\n"
2574 " |+----------->(4) ...to here (5) allocated here\n",
2575 pp_formatted_text (text_output.get_printer ()));
2576 }
2577}
2578
2579/* Another complex example, adapted from loop-3.c. */
2580
2581static void
2582test_control_flow_6 (const line_table_case &case_,
2583 pretty_printer *event_pp)
2584{
2585 /* Create a tempfile and write some text to it.
2586 ...000000000111111111122222222223333333.
2587 ...123456789012345678901234567890123456. */
2588 const char *content
2589 = ("#include <stdlib.h>\n" /* <------------------ line 1. */
2590 "\n" /* <------------------------------------- line 2. */
2591 "void test(int c)\n" /* <--------------------- line 3. */
2592 "{\n" /* <------------------------------------ line 4. */
2593 " int i;\n" /* <----------------------------- line 5. */
2594 " char *buffer = (char*)malloc(256);\n" /* <- line 6. */
2595 "\n" /* <------------------------------------- line 7. */
2596 " for (i=0; i<255; i++) {\n" /* <------------ line 8. */
2597 " buffer[i] = c;\n" /* <------------------- line 9. */
2598 "\n" /* <------------------------------------- line 10. */
2599 " free(buffer);\n" /* <-------------------- line 11. */
2600 " }\n"); /* <-------------------------------- line 12. */
2601
2602 control_flow_test t (SELFTEST_LOCATION, case_, content);
2603
2604 test_diagnostic_path path (event_pp);
2605 /* (1) */
2606 path.add_event (loc: t.get_line_and_columns (line: 6, first_column: 25, last_column: 35), funcname: nullptr, depth: 0,
2607 fmt: "allocated here");
2608
2609 /* (2) */
2610 path.add_event (loc: t.get_line_and_columns (line: 8, first_column: 13, caret_column: 14, last_column: 17), funcname: nullptr, depth: 0,
2611 fmt: "following %qs branch (when %qs)...",
2612 "true", "i <= 254");
2613 path.connect_to_next_event ();
2614
2615 /* (3) */
2616 path.add_event (loc: t.get_line_and_columns (line: 9, first_column: 5, caret_column: 15, last_column: 17), funcname: nullptr, depth: 0,
2617 fmt: "...to here");
2618
2619 /* (4) */
2620 path.add_event (loc: t.get_line_and_columns (line: 8, first_column: 13, caret_column: 14, last_column: 17), funcname: nullptr, depth: 0,
2621 fmt: "following %qs branch (when %qs)...",
2622 "true", "i <= 254");
2623 path.connect_to_next_event ();
2624
2625 /* (5) */
2626 path.add_event (loc: t.get_line_and_columns (line: 9, first_column: 5, caret_column: 15, last_column: 17), funcname: nullptr, depth: 0,
2627 fmt: "...to here");
2628
2629 if (!path_events_have_column_data_p (path))
2630 return;
2631
2632 {
2633 test_diagnostic_context dc;
2634 dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
2635 dc.m_source_printing.show_event_links_p = true;
2636 dc.m_source_printing.show_line_numbers_p = true;
2637 diagnostic_text_output_format text_output (dc);
2638 path_print_policy policy (text_output);
2639 path_summary summary (policy, *event_pp, path, true);
2640 print_path_summary_as_text (ps: summary, text_output, show_depths: false);
2641 ASSERT_STREQ
2642 (" events 1-3\n"
2643 "FILENAME:6:25:\n"
2644 " 6 | char *buffer = (char*)malloc(256);\n"
2645 " | ^~~~~~~~~~~\n"
2646 " | |\n"
2647 " | (1) allocated here\n"
2648 " 7 | \n"
2649 " 8 | for (i=0; i<255; i++) {\n"
2650 " | ~~~~~ \n"
2651 " | |\n"
2652 " | (2) following `true' branch (when `i <= 254')... ->-+\n"
2653 " | |\n"
2654 " | |\n"
2655 " |+-----------------------------------------------------------------+\n"
2656 " 9 || buffer[i] = c;\n"
2657 " || ~~~~~~~~~~~~~ \n"
2658 " || |\n"
2659 " |+------------->(3) ...to here\n"
2660 " events 4-5\n"
2661 " 8 | for (i=0; i<255; i++) {\n"
2662 " | ~^~~~\n"
2663 " | |\n"
2664 " | (4) following `true' branch (when `i <= 254')... ->-+\n"
2665 " | |\n"
2666 " | |\n"
2667 " |+-----------------------------------------------------------------+\n"
2668 " 9 || buffer[i] = c;\n"
2669 " || ~~~~~~~~~~~~~\n"
2670 " || |\n"
2671 " |+------------->(5) ...to here\n",
2672 pp_formatted_text (text_output.get_printer ()));
2673 }
2674}
2675
2676static void
2677control_flow_tests (const line_table_case &case_)
2678{
2679 pretty_printer pp;
2680 pp_show_color (pp: &pp) = false;
2681
2682 test_control_flow_1 (case_, event_pp: &pp);
2683 test_control_flow_2 (case_, event_pp: &pp);
2684 test_control_flow_3 (case_, event_pp: &pp);
2685 test_control_flow_4 (case_, event_pp: &pp);
2686 test_control_flow_5 (case_, event_pp: &pp);
2687 test_control_flow_6 (case_, event_pp: &pp);
2688}
2689
2690/* Run all of the selftests within this file. */
2691
2692void
2693diagnostic_path_output_cc_tests ()
2694{
2695 pretty_printer pp;
2696 pp_show_color (pp: &pp) = false;
2697
2698 auto_fix_quotes fix_quotes;
2699 test_empty_path (event_pp: &pp);
2700 test_intraprocedural_path (event_pp: &pp);
2701 test_interprocedural_path_1 (event_pp: &pp);
2702 test_interprocedural_path_2 (event_pp: &pp);
2703 test_recursion (event_pp: &pp);
2704 for_each_line_table_case (testcase: control_flow_tests);
2705}
2706
2707} // namespace selftest
2708
2709#if __GNUC__ >= 10
2710# pragma GCC diagnostic pop
2711#endif
2712
2713#endif /* #if CHECKING_P */
2714

source code of gcc/diagnostic-path-output.cc