1 | /* Paths through the code associated with a diagnostic. |
2 | Copyright (C) 2019-2023 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_VECTOR |
23 | #include "system.h" |
24 | #include "coretypes.h" |
25 | #include "tree.h" |
26 | #include "diagnostic.h" |
27 | #include "tree-pretty-print.h" |
28 | #include "gimple-pretty-print.h" |
29 | #include "tree-diagnostic.h" |
30 | #include "langhooks.h" |
31 | #include "intl.h" |
32 | #include "diagnostic-path.h" |
33 | #include "json.h" |
34 | #include "gcc-rich-location.h" |
35 | #include "diagnostic-color.h" |
36 | #include "diagnostic-event-id.h" |
37 | #include "selftest.h" |
38 | #include "selftest-diagnostic.h" |
39 | |
40 | /* Anonymous namespace for path-printing code. */ |
41 | |
42 | namespace { |
43 | |
44 | /* Subclass of range_label for showing a particular event |
45 | when showing a consecutive run of events within a diagnostic_path as |
46 | labelled ranges within one gcc_rich_location. */ |
47 | |
48 | class path_label : public range_label |
49 | { |
50 | public: |
51 | path_label (const diagnostic_path *path, unsigned start_idx) |
52 | : m_path (path), m_start_idx (start_idx) |
53 | {} |
54 | |
55 | label_text get_text (unsigned range_idx) const final override |
56 | { |
57 | unsigned event_idx = m_start_idx + range_idx; |
58 | const diagnostic_event &event = m_path->get_event (idx: event_idx); |
59 | |
60 | /* Get the description of the event, perhaps with colorization: |
61 | normally, we don't colorize within a range_label, but this |
62 | is special-cased for diagnostic paths. */ |
63 | bool colorize = pp_show_color (global_dc->printer); |
64 | label_text event_text (event.get_desc (can_colorize: colorize)); |
65 | gcc_assert (event_text.get ()); |
66 | pretty_printer pp; |
67 | pp_show_color (&pp) = pp_show_color (global_dc->printer); |
68 | diagnostic_event_id_t event_id (event_idx); |
69 | pp_printf (&pp, "%@ %s" , &event_id, event_text.get ()); |
70 | label_text result = label_text::take (buffer: xstrdup (pp_formatted_text (&pp))); |
71 | return result; |
72 | } |
73 | |
74 | private: |
75 | const diagnostic_path *m_path; |
76 | unsigned m_start_idx; |
77 | }; |
78 | |
79 | /* Return true if E1 and E2 can be consolidated into the same run of events |
80 | when printing a diagnostic_path. */ |
81 | |
82 | static bool |
83 | can_consolidate_events (const diagnostic_event &e1, |
84 | const diagnostic_event &e2, |
85 | bool check_locations) |
86 | { |
87 | if (e1.get_thread_id () != e2.get_thread_id ()) |
88 | return false; |
89 | |
90 | if (e1.get_fndecl () != e2.get_fndecl ()) |
91 | return false; |
92 | |
93 | if (e1.get_stack_depth () != e2.get_stack_depth ()) |
94 | return false; |
95 | |
96 | if (check_locations) |
97 | { |
98 | location_t loc1 = e1.get_location (); |
99 | location_t loc2 = e2.get_location (); |
100 | |
101 | if (loc1 < RESERVED_LOCATION_COUNT |
102 | || loc2 < RESERVED_LOCATION_COUNT) |
103 | return false; |
104 | |
105 | /* Neither can be macro-based. */ |
106 | if (linemap_location_from_macro_expansion_p (line_table, loc1)) |
107 | return false; |
108 | if (linemap_location_from_macro_expansion_p (line_table, loc2)) |
109 | return false; |
110 | } |
111 | |
112 | /* Passed all the tests. */ |
113 | return true; |
114 | } |
115 | |
116 | struct event_range; |
117 | struct path_summary; |
118 | class thread_event_printer; |
119 | |
120 | /* A bundle of information about all of the events in a diagnostic_path |
121 | relating to a specific path, for use by path_summary. */ |
122 | |
123 | class per_thread_summary |
124 | { |
125 | public: |
126 | per_thread_summary (label_text name, unsigned swimlane_idx) |
127 | : m_name (std::move (name)), |
128 | m_swimlane_idx (swimlane_idx), |
129 | m_min_depth (INT_MAX), |
130 | m_max_depth (INT_MIN) |
131 | {} |
132 | |
133 | void update_depth_limits (int stack_depth) |
134 | { |
135 | if (stack_depth < m_min_depth) |
136 | m_min_depth = stack_depth; |
137 | if (stack_depth > m_max_depth) |
138 | m_max_depth = stack_depth; |
139 | } |
140 | |
141 | const char *get_name () const { return m_name.get (); } |
142 | unsigned get_swimlane_index () const { return m_swimlane_idx; } |
143 | |
144 | private: |
145 | friend struct path_summary; |
146 | friend class thread_event_printer; |
147 | |
148 | const label_text m_name; |
149 | |
150 | /* The "swimlane index" is the order in which this per_thread_summary |
151 | was created, for use when printing the events. */ |
152 | const unsigned m_swimlane_idx; |
153 | |
154 | // The event ranges specific to this thread: |
155 | auto_vec<event_range *> m_event_ranges; |
156 | int m_min_depth; |
157 | int m_max_depth; |
158 | }; |
159 | |
160 | /* A range of consecutive events within a diagnostic_path, all within the |
161 | same thread, and with the same fndecl and stack_depth, and which are suitable |
162 | to print with a single call to diagnostic_show_locus. */ |
163 | struct event_range |
164 | { |
165 | event_range (const diagnostic_path *path, unsigned start_idx, |
166 | const diagnostic_event &initial_event, |
167 | const per_thread_summary &t) |
168 | : m_path (path), |
169 | m_initial_event (initial_event), |
170 | m_fndecl (initial_event.get_fndecl ()), |
171 | m_stack_depth (initial_event.get_stack_depth ()), |
172 | m_start_idx (start_idx), m_end_idx (start_idx), |
173 | m_path_label (path, start_idx), |
174 | m_richloc (initial_event.get_location (), &m_path_label), |
175 | m_thread_id (initial_event.get_thread_id ()), |
176 | m_per_thread_summary (t) |
177 | {} |
178 | |
179 | bool maybe_add_event (const diagnostic_event &new_ev, unsigned idx, |
180 | bool check_rich_locations) |
181 | { |
182 | if (!can_consolidate_events (e1: m_initial_event, e2: new_ev, |
183 | check_locations: check_rich_locations)) |
184 | return false; |
185 | if (check_rich_locations) |
186 | if (!m_richloc.add_location_if_nearby (loc: new_ev.get_location (), |
187 | restrict_to_current_line_spans: false, label: &m_path_label)) |
188 | return false; |
189 | m_end_idx = idx; |
190 | return true; |
191 | } |
192 | |
193 | /* Print the events in this range to DC, typically as a single |
194 | call to the printer's diagnostic_show_locus. */ |
195 | |
196 | void print (diagnostic_context *dc, pretty_printer *pp) |
197 | { |
198 | location_t initial_loc = m_initial_event.get_location (); |
199 | |
200 | /* Emit a span indicating the filename (and line/column) if the |
201 | line has changed relative to the last call to |
202 | diagnostic_show_locus. */ |
203 | if (dc->m_source_printing.enabled) |
204 | { |
205 | expanded_location exploc |
206 | = linemap_client_expand_location_to_spelling_point |
207 | (line_table, initial_loc, LOCATION_ASPECT_CARET); |
208 | if (exploc.file != LOCATION_FILE (dc->m_last_location)) |
209 | dc->m_text_callbacks.start_span (dc, exploc); |
210 | } |
211 | |
212 | /* If we have an UNKNOWN_LOCATION (or BUILTINS_LOCATION) as the |
213 | primary location for an event, diagnostic_show_locus won't print |
214 | anything. |
215 | |
216 | In particular the label for the event won't get printed. |
217 | Fail more gracefully in this case by showing the event |
218 | index and text, at no particular location. */ |
219 | if (get_pure_location (loc: initial_loc) <= BUILTINS_LOCATION) |
220 | { |
221 | for (unsigned i = m_start_idx; i <= m_end_idx; i++) |
222 | { |
223 | const diagnostic_event &iter_event = m_path->get_event (idx: i); |
224 | diagnostic_event_id_t event_id (i); |
225 | label_text event_text (iter_event.get_desc (can_colorize: true)); |
226 | pp_printf (pp, " %@: %s" , &event_id, event_text.get ()); |
227 | pp_newline (pp); |
228 | } |
229 | return; |
230 | } |
231 | |
232 | /* Call diagnostic_show_locus to show the events using labels. */ |
233 | diagnostic_show_locus (context: dc, richloc: &m_richloc, diagnostic_kind: DK_DIAGNOSTIC_PATH, pp); |
234 | |
235 | /* If we have a macro expansion, show the expansion to the user. */ |
236 | if (linemap_location_from_macro_expansion_p (line_table, initial_loc)) |
237 | { |
238 | gcc_assert (m_start_idx == m_end_idx); |
239 | maybe_unwind_expanded_macro_loc (context: dc, where: initial_loc); |
240 | } |
241 | } |
242 | |
243 | const diagnostic_path *m_path; |
244 | const diagnostic_event &m_initial_event; |
245 | tree m_fndecl; |
246 | int m_stack_depth; |
247 | unsigned m_start_idx; |
248 | unsigned m_end_idx; |
249 | path_label m_path_label; |
250 | gcc_rich_location m_richloc; |
251 | diagnostic_thread_id_t m_thread_id; |
252 | const per_thread_summary &m_per_thread_summary; |
253 | }; |
254 | |
255 | /* A struct for grouping together the events in a diagnostic_path into |
256 | ranges of events, partitioned by thread and by stack frame (i.e. by fndecl |
257 | and stack depth). */ |
258 | |
259 | struct path_summary |
260 | { |
261 | path_summary (const diagnostic_path &path, bool check_rich_locations); |
262 | |
263 | unsigned get_num_ranges () const { return m_ranges.length (); } |
264 | bool multithreaded_p () const { return m_per_thread_summary.length () > 1; } |
265 | |
266 | const per_thread_summary &get_events_for_thread_id (diagnostic_thread_id_t tid) |
267 | { |
268 | per_thread_summary **slot = m_thread_id_to_events.get (k: tid); |
269 | gcc_assert (slot); |
270 | gcc_assert (*slot); |
271 | return **slot; |
272 | } |
273 | |
274 | auto_delete_vec <event_range> m_ranges; |
275 | auto_delete_vec <per_thread_summary> m_per_thread_summary; |
276 | hash_map<int_hash<diagnostic_thread_id_t, -1, -2>, |
277 | per_thread_summary *> m_thread_id_to_events; |
278 | |
279 | private: |
280 | per_thread_summary & |
281 | get_or_create_events_for_thread_id (const diagnostic_path &path, |
282 | diagnostic_thread_id_t tid) |
283 | { |
284 | if (per_thread_summary **slot = m_thread_id_to_events.get (k: tid)) |
285 | return **slot; |
286 | |
287 | const diagnostic_thread &thread = path.get_thread (tid); |
288 | per_thread_summary *pts = new per_thread_summary (thread.get_name (can_colorize: false), |
289 | m_per_thread_summary.length ()); |
290 | m_thread_id_to_events.put (k: tid, v: pts); |
291 | m_per_thread_summary.safe_push (obj: pts); |
292 | return *pts; |
293 | } |
294 | }; |
295 | |
296 | /* path_summary's ctor. */ |
297 | |
298 | path_summary::path_summary (const diagnostic_path &path, |
299 | bool check_rich_locations) |
300 | { |
301 | const unsigned num_events = path.num_events (); |
302 | |
303 | event_range *cur_event_range = NULL; |
304 | for (unsigned idx = 0; idx < num_events; idx++) |
305 | { |
306 | const diagnostic_event &event = path.get_event (idx); |
307 | const diagnostic_thread_id_t thread_id = event.get_thread_id (); |
308 | per_thread_summary &pts |
309 | = get_or_create_events_for_thread_id (path, tid: thread_id); |
310 | |
311 | pts.update_depth_limits (stack_depth: event.get_stack_depth ()); |
312 | |
313 | if (cur_event_range) |
314 | if (cur_event_range->maybe_add_event (new_ev: event, idx, check_rich_locations)) |
315 | continue; |
316 | |
317 | cur_event_range = new event_range (&path, idx, event, pts); |
318 | m_ranges.safe_push (obj: cur_event_range); |
319 | pts.m_event_ranges.safe_push (obj: cur_event_range); |
320 | } |
321 | } |
322 | |
323 | /* Write SPACES to PP. */ |
324 | |
325 | static void |
326 | write_indent (pretty_printer *pp, int spaces) |
327 | { |
328 | for (int i = 0; i < spaces; i++) |
329 | pp_space (pp); |
330 | } |
331 | |
332 | /* Print FNDDECL to PP, quoting it if QUOTED is true. |
333 | |
334 | We can't use "%qE" here since we can't guarantee the capabilities |
335 | of PP. */ |
336 | |
337 | static void |
338 | print_fndecl (pretty_printer *pp, tree fndecl, bool quoted) |
339 | { |
340 | const char *n = DECL_NAME (fndecl) |
341 | ? identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)) |
342 | : _("<anonymous>" ); |
343 | if (quoted) |
344 | pp_printf (pp, "%qs" , n); |
345 | else |
346 | pp_string (pp, n); |
347 | } |
348 | |
349 | static const int base_indent = 2; |
350 | static const int per_frame_indent = 2; |
351 | |
352 | /* A bundle of state for printing event_range instances for a particular |
353 | thread. */ |
354 | |
355 | class thread_event_printer |
356 | { |
357 | public: |
358 | thread_event_printer (const per_thread_summary &t, bool show_depths) |
359 | : m_per_thread_summary (t), |
360 | m_show_depths (show_depths), |
361 | m_cur_indent (base_indent), |
362 | m_vbar_column_for_depth (), |
363 | m_num_printed (0) |
364 | { |
365 | } |
366 | |
367 | /* Get the previous event_range within this thread, if any. */ |
368 | const event_range *get_any_prev_range () const |
369 | { |
370 | if (m_num_printed > 0) |
371 | return m_per_thread_summary.m_event_ranges[m_num_printed - 1]; |
372 | else |
373 | return nullptr; |
374 | } |
375 | |
376 | /* Get the next event_range within this thread, if any. */ |
377 | const event_range *get_any_next_range () const |
378 | { |
379 | if (m_num_printed < m_per_thread_summary.m_event_ranges.length () - 1) |
380 | return m_per_thread_summary.m_event_ranges[m_num_printed + 1]; |
381 | else |
382 | return nullptr; |
383 | } |
384 | |
385 | void print_swimlane_for_event_range (diagnostic_context *dc, |
386 | pretty_printer *pp, |
387 | event_range *range) |
388 | { |
389 | const char *const line_color = "path" ; |
390 | const char *start_line_color |
391 | = colorize_start (pp_show_color (pp), name: line_color); |
392 | const char *end_line_color = colorize_stop (pp_show_color (pp)); |
393 | |
394 | write_indent (pp, spaces: m_cur_indent); |
395 | if (const event_range *prev_range = get_any_prev_range ()) |
396 | { |
397 | if (range->m_stack_depth > prev_range->m_stack_depth) |
398 | { |
399 | /* Show pushed stack frame(s). */ |
400 | const char *push_prefix = "+--> " ; |
401 | pp_string (pp, start_line_color); |
402 | pp_string (pp, push_prefix); |
403 | pp_string (pp, end_line_color); |
404 | m_cur_indent += strlen (s: push_prefix); |
405 | } |
406 | } |
407 | if (range->m_fndecl) |
408 | { |
409 | print_fndecl (pp, fndecl: range->m_fndecl, quoted: true); |
410 | pp_string (pp, ": " ); |
411 | } |
412 | if (range->m_start_idx == range->m_end_idx) |
413 | pp_printf (pp, "event %i" , |
414 | range->m_start_idx + 1); |
415 | else |
416 | pp_printf (pp, "events %i-%i" , |
417 | range->m_start_idx + 1, range->m_end_idx + 1); |
418 | if (m_show_depths) |
419 | pp_printf (pp, " (depth %i)" , range->m_stack_depth); |
420 | pp_newline (pp); |
421 | |
422 | /* Print a run of events. */ |
423 | { |
424 | write_indent (pp, spaces: m_cur_indent + per_frame_indent); |
425 | pp_string (pp, start_line_color); |
426 | pp_string (pp, "|" ); |
427 | pp_string (pp, end_line_color); |
428 | pp_newline (pp); |
429 | |
430 | char *saved_prefix = pp_take_prefix (pp); |
431 | char *prefix; |
432 | { |
433 | pretty_printer tmp_pp; |
434 | write_indent (pp: &tmp_pp, spaces: m_cur_indent + per_frame_indent); |
435 | pp_string (&tmp_pp, start_line_color); |
436 | pp_string (&tmp_pp, "|" ); |
437 | pp_string (&tmp_pp, end_line_color); |
438 | prefix = xstrdup (pp_formatted_text (&tmp_pp)); |
439 | } |
440 | pp_set_prefix (pp, prefix); |
441 | pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; |
442 | range->print (dc, pp); |
443 | pp_set_prefix (pp, saved_prefix); |
444 | |
445 | write_indent (pp, spaces: m_cur_indent + per_frame_indent); |
446 | pp_string (pp, start_line_color); |
447 | pp_string (pp, "|" ); |
448 | pp_string (pp, end_line_color); |
449 | pp_newline (pp); |
450 | } |
451 | |
452 | if (const event_range *next_range = get_any_next_range ()) |
453 | { |
454 | if (range->m_stack_depth > next_range->m_stack_depth) |
455 | { |
456 | if (m_vbar_column_for_depth.get (k: next_range->m_stack_depth)) |
457 | { |
458 | /* Show returning from stack frame(s), by printing |
459 | something like: |
460 | " |\n" |
461 | " <------------ +\n" |
462 | " |\n". */ |
463 | int vbar_for_next_frame |
464 | = *m_vbar_column_for_depth.get (k: next_range->m_stack_depth); |
465 | |
466 | int indent_for_next_frame |
467 | = vbar_for_next_frame - per_frame_indent; |
468 | write_indent (pp, spaces: vbar_for_next_frame); |
469 | pp_string (pp, start_line_color); |
470 | pp_character (pp, '<'); |
471 | for (int i = indent_for_next_frame + per_frame_indent; |
472 | i < m_cur_indent + per_frame_indent - 1; i++) |
473 | pp_character (pp, '-'); |
474 | pp_character (pp, '+'); |
475 | pp_string (pp, end_line_color); |
476 | pp_newline (pp); |
477 | m_cur_indent = indent_for_next_frame; |
478 | |
479 | write_indent (pp, spaces: vbar_for_next_frame); |
480 | pp_string (pp, start_line_color); |
481 | pp_character (pp, '|'); |
482 | pp_string (pp, end_line_color); |
483 | pp_newline (pp); |
484 | } |
485 | else |
486 | { |
487 | /* Handle disjoint paths (e.g. a callback at some later |
488 | time). */ |
489 | m_cur_indent = base_indent; |
490 | } |
491 | } |
492 | else if (range->m_stack_depth < next_range->m_stack_depth) |
493 | { |
494 | /* Prepare to show pushed stack frame. */ |
495 | gcc_assert (range->m_stack_depth != EMPTY); |
496 | gcc_assert (range->m_stack_depth != DELETED); |
497 | m_vbar_column_for_depth.put (k: range->m_stack_depth, |
498 | v: m_cur_indent + per_frame_indent); |
499 | m_cur_indent += per_frame_indent; |
500 | } |
501 | } |
502 | |
503 | m_num_printed++; |
504 | } |
505 | |
506 | int get_cur_indent () const { return m_cur_indent; } |
507 | |
508 | private: |
509 | const per_thread_summary &m_per_thread_summary; |
510 | bool m_show_depths; |
511 | |
512 | /* Print the ranges. */ |
513 | int m_cur_indent; |
514 | |
515 | /* Keep track of column numbers of existing '|' characters for |
516 | stack depths we've already printed. */ |
517 | static const int EMPTY = -1; |
518 | static const int DELETED = -2; |
519 | typedef int_hash <int, EMPTY, DELETED> vbar_hash; |
520 | hash_map <vbar_hash, int> m_vbar_column_for_depth; |
521 | |
522 | /* How many event ranges within this swimlane have we printed. |
523 | This is the index of the next event_range to print. */ |
524 | unsigned m_num_printed; |
525 | }; |
526 | |
527 | /* Print path_summary PS to DC, giving an overview of the interprocedural |
528 | calls and returns. |
529 | |
530 | Print the event descriptions in a nested form, printing the event |
531 | descriptions within calls to diagnostic_show_locus, using labels to |
532 | show the events: |
533 | |
534 | 'foo' (events 1-2) |
535 | | NN | |
536 | | | |
537 | +--> 'bar' (events 3-4) |
538 | | NN | |
539 | | | |
540 | +--> 'baz' (events 5-6) |
541 | | NN | |
542 | | | |
543 | <------------ + |
544 | | |
545 | 'foo' (events 7-8) |
546 | | NN | |
547 | | | |
548 | +--> 'bar' (events 9-10) |
549 | | NN | |
550 | | | |
551 | +--> 'baz' (events 11-12) |
552 | | NN | |
553 | | | |
554 | |
555 | If SHOW_DEPTHS is true, append " (depth N)" to the header of each run |
556 | of events. |
557 | |
558 | For events with UNKNOWN_LOCATION, print a summary of each the event. */ |
559 | |
560 | static void |
561 | print_path_summary_as_text (const path_summary *ps, diagnostic_context *dc, |
562 | bool show_depths) |
563 | { |
564 | pretty_printer *pp = dc->printer; |
565 | |
566 | std::vector<thread_event_printer> thread_event_printers; |
567 | for (auto t : ps->m_per_thread_summary) |
568 | thread_event_printers.push_back (x: thread_event_printer (*t, show_depths)); |
569 | |
570 | unsigned i; |
571 | event_range *range; |
572 | FOR_EACH_VEC_ELT (ps->m_ranges, i, range) |
573 | { |
574 | const int swimlane_idx |
575 | = range->m_per_thread_summary.get_swimlane_index (); |
576 | if (ps->multithreaded_p ()) |
577 | if (i == 0 || ps->m_ranges[i - 1]->m_thread_id != range->m_thread_id) |
578 | { |
579 | if (i > 0) |
580 | pp_newline (pp); |
581 | pp_printf (pp, "Thread: %qs" , |
582 | range->m_per_thread_summary.get_name ()); |
583 | pp_newline (pp); |
584 | } |
585 | thread_event_printer &tep = thread_event_printers[swimlane_idx]; |
586 | tep.print_swimlane_for_event_range (dc, pp, range); |
587 | } |
588 | } |
589 | |
590 | } /* end of anonymous namespace for path-printing code. */ |
591 | |
592 | /* Print PATH to CONTEXT, according to CONTEXT's path_format. */ |
593 | |
594 | void |
595 | default_tree_diagnostic_path_printer (diagnostic_context *context, |
596 | const diagnostic_path *path) |
597 | { |
598 | gcc_assert (path); |
599 | |
600 | const unsigned num_events = path->num_events (); |
601 | |
602 | switch (context->get_path_format ()) |
603 | { |
604 | case DPF_NONE: |
605 | /* Do nothing. */ |
606 | return; |
607 | |
608 | case DPF_SEPARATE_EVENTS: |
609 | { |
610 | /* A note per event. */ |
611 | for (unsigned i = 0; i < num_events; i++) |
612 | { |
613 | const diagnostic_event &event = path->get_event (idx: i); |
614 | label_text event_text (event.get_desc (can_colorize: false)); |
615 | gcc_assert (event_text.get ()); |
616 | diagnostic_event_id_t event_id (i); |
617 | if (context->show_path_depths_p ()) |
618 | { |
619 | int stack_depth = event.get_stack_depth (); |
620 | tree fndecl = event.get_fndecl (); |
621 | /* -fdiagnostics-path-format=separate-events doesn't print |
622 | fndecl information, so with -fdiagnostics-show-path-depths |
623 | print the fndecls too, if any. */ |
624 | if (fndecl) |
625 | inform (event.get_location (), |
626 | "%@ %s (fndecl %qD, depth %i)" , |
627 | &event_id, event_text.get (), |
628 | fndecl, stack_depth); |
629 | else |
630 | inform (event.get_location (), |
631 | "%@ %s (depth %i)" , |
632 | &event_id, event_text.get (), |
633 | stack_depth); |
634 | } |
635 | else |
636 | inform (event.get_location (), |
637 | "%@ %s" , &event_id, event_text.get ()); |
638 | } |
639 | } |
640 | break; |
641 | |
642 | case DPF_INLINE_EVENTS: |
643 | { |
644 | /* Consolidate related events. */ |
645 | path_summary summary (*path, true); |
646 | char *saved_prefix = pp_take_prefix (context->printer); |
647 | pp_set_prefix (context->printer, NULL); |
648 | print_path_summary_as_text (ps: &summary, dc: context, |
649 | show_depths: context->show_path_depths_p ()); |
650 | pp_flush (context->printer); |
651 | pp_set_prefix (context->printer, saved_prefix); |
652 | } |
653 | break; |
654 | } |
655 | } |
656 | |
657 | /* This has to be here, rather than diagnostic-format-json.cc, |
658 | since diagnostic-format-json.o is within OBJS-libcommon and thus |
659 | doesn't have access to trees (for m_fndecl). */ |
660 | |
661 | json::value * |
662 | default_tree_make_json_for_path (diagnostic_context *context, |
663 | const diagnostic_path *path) |
664 | { |
665 | json::array *path_array = new json::array (); |
666 | for (unsigned i = 0; i < path->num_events (); i++) |
667 | { |
668 | const diagnostic_event &event = path->get_event (idx: i); |
669 | |
670 | json::object *event_obj = new json::object (); |
671 | if (event.get_location ()) |
672 | event_obj->set (key: "location" , |
673 | v: json_from_expanded_location (context, |
674 | loc: event.get_location ())); |
675 | label_text event_text (event.get_desc (can_colorize: false)); |
676 | event_obj->set (key: "description" , v: new json::string (event_text.get ())); |
677 | if (tree fndecl = event.get_fndecl ()) |
678 | { |
679 | const char *function |
680 | = identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)); |
681 | event_obj->set (key: "function" , v: new json::string (function)); |
682 | } |
683 | event_obj->set (key: "depth" , |
684 | v: new json::integer_number (event.get_stack_depth ())); |
685 | path_array->append (v: event_obj); |
686 | } |
687 | return path_array; |
688 | } |
689 | |
690 | #if CHECKING_P |
691 | |
692 | /* Disable warnings about missing quoting in GCC diagnostics for the print |
693 | calls in the tests below. */ |
694 | #if __GNUC__ >= 10 |
695 | # pragma GCC diagnostic push |
696 | # pragma GCC diagnostic ignored "-Wformat-diag" |
697 | #endif |
698 | |
699 | namespace selftest { |
700 | |
701 | /* A subclass of simple_diagnostic_path that adds member functions |
702 | for adding test events. */ |
703 | |
704 | class test_diagnostic_path : public simple_diagnostic_path |
705 | { |
706 | public: |
707 | test_diagnostic_path (pretty_printer *event_pp) |
708 | : simple_diagnostic_path (event_pp) |
709 | { |
710 | } |
711 | |
712 | void add_entry (tree fndecl, int stack_depth) |
713 | { |
714 | add_event (UNKNOWN_LOCATION, fndecl, depth: stack_depth, |
715 | fmt: "entering %qE" , fndecl); |
716 | } |
717 | |
718 | void add_return (tree fndecl, int stack_depth) |
719 | { |
720 | add_event (UNKNOWN_LOCATION, fndecl, depth: stack_depth, |
721 | fmt: "returning to %qE" , fndecl); |
722 | } |
723 | |
724 | void add_call (tree caller, int caller_stack_depth, tree callee) |
725 | { |
726 | add_event (UNKNOWN_LOCATION, fndecl: caller, depth: caller_stack_depth, |
727 | fmt: "calling %qE" , callee); |
728 | add_entry (fndecl: callee, stack_depth: caller_stack_depth + 1); |
729 | } |
730 | }; |
731 | |
732 | /* Verify that empty paths are handled gracefully. */ |
733 | |
734 | static void |
735 | test_empty_path (pretty_printer *event_pp) |
736 | { |
737 | test_diagnostic_path path (event_pp); |
738 | ASSERT_FALSE (path.interprocedural_p ()); |
739 | |
740 | path_summary summary (path, false); |
741 | ASSERT_EQ (summary.get_num_ranges (), 0); |
742 | |
743 | test_diagnostic_context dc; |
744 | print_path_summary_as_text (ps: &summary, dc: &dc, show_depths: true); |
745 | ASSERT_STREQ ("" , |
746 | pp_formatted_text (dc.printer)); |
747 | } |
748 | |
749 | /* Verify that print_path_summary works on a purely intraprocedural path. */ |
750 | |
751 | static void |
752 | test_intraprocedural_path (pretty_printer *event_pp) |
753 | { |
754 | tree fntype_void_void |
755 | = build_function_type_array (void_type_node, 0, NULL); |
756 | tree fndecl_foo = build_fn_decl ("foo" , fntype_void_void); |
757 | |
758 | test_diagnostic_path path (event_pp); |
759 | path.add_event (UNKNOWN_LOCATION, fndecl: fndecl_foo, depth: 0, fmt: "first %qs" , "free" ); |
760 | path.add_event (UNKNOWN_LOCATION, fndecl: fndecl_foo, depth: 0, fmt: "double %qs" , "free" ); |
761 | |
762 | ASSERT_FALSE (path.interprocedural_p ()); |
763 | |
764 | path_summary summary (path, false); |
765 | ASSERT_EQ (summary.get_num_ranges (), 1); |
766 | |
767 | test_diagnostic_context dc; |
768 | print_path_summary_as_text (ps: &summary, dc: &dc, show_depths: true); |
769 | ASSERT_STREQ (" `foo': events 1-2 (depth 0)\n" |
770 | " |\n" |
771 | " | (1): first `free'\n" |
772 | " | (2): double `free'\n" |
773 | " |\n" , |
774 | pp_formatted_text (dc.printer)); |
775 | } |
776 | |
777 | /* Verify that print_path_summary works on an interprocedural path. */ |
778 | |
779 | static void |
780 | test_interprocedural_path_1 (pretty_printer *event_pp) |
781 | { |
782 | /* Build fndecls. The types aren't quite right, but that |
783 | doesn't matter for the purposes of this test. */ |
784 | tree fntype_void_void |
785 | = build_function_type_array (void_type_node, 0, NULL); |
786 | tree fndecl_test = build_fn_decl ("test" , fntype_void_void); |
787 | tree fndecl_make_boxed_int |
788 | = build_fn_decl ("make_boxed_int" , fntype_void_void); |
789 | tree fndecl_wrapped_malloc |
790 | = build_fn_decl ("wrapped_malloc" , fntype_void_void); |
791 | tree fndecl_free_boxed_int |
792 | = build_fn_decl ("free_boxed_int" , fntype_void_void); |
793 | tree fndecl_wrapped_free |
794 | = build_fn_decl ("wrapped_free" , fntype_void_void); |
795 | |
796 | test_diagnostic_path path (event_pp); |
797 | path.add_entry (fndecl: fndecl_test, stack_depth: 0); |
798 | path.add_call (caller: fndecl_test, caller_stack_depth: 0, callee: fndecl_make_boxed_int); |
799 | path.add_call (caller: fndecl_make_boxed_int, caller_stack_depth: 1, callee: fndecl_wrapped_malloc); |
800 | path.add_event (UNKNOWN_LOCATION, fndecl: fndecl_wrapped_malloc, depth: 2, fmt: "calling malloc" ); |
801 | path.add_return (fndecl: fndecl_test, stack_depth: 0); |
802 | path.add_call (caller: fndecl_test, caller_stack_depth: 0, callee: fndecl_free_boxed_int); |
803 | path.add_call (caller: fndecl_free_boxed_int, caller_stack_depth: 1, callee: fndecl_wrapped_free); |
804 | path.add_event (UNKNOWN_LOCATION, fndecl: fndecl_wrapped_free, depth: 2, fmt: "calling free" ); |
805 | path.add_return (fndecl: fndecl_test, stack_depth: 0); |
806 | path.add_call (caller: fndecl_test, caller_stack_depth: 0, callee: fndecl_free_boxed_int); |
807 | path.add_call (caller: fndecl_free_boxed_int, caller_stack_depth: 1, callee: fndecl_wrapped_free); |
808 | path.add_event (UNKNOWN_LOCATION, fndecl: fndecl_wrapped_free, depth: 2, fmt: "calling free" ); |
809 | ASSERT_EQ (path.num_events (), 18); |
810 | |
811 | ASSERT_TRUE (path.interprocedural_p ()); |
812 | |
813 | path_summary summary (path, false); |
814 | ASSERT_EQ (summary.get_num_ranges (), 9); |
815 | |
816 | test_diagnostic_context dc; |
817 | print_path_summary_as_text (ps: &summary, dc: &dc, show_depths: true); |
818 | ASSERT_STREQ |
819 | (" `test': events 1-2 (depth 0)\n" |
820 | " |\n" |
821 | " | (1): entering `test'\n" |
822 | " | (2): calling `make_boxed_int'\n" |
823 | " |\n" |
824 | " +--> `make_boxed_int': events 3-4 (depth 1)\n" |
825 | " |\n" |
826 | " | (3): entering `make_boxed_int'\n" |
827 | " | (4): calling `wrapped_malloc'\n" |
828 | " |\n" |
829 | " +--> `wrapped_malloc': events 5-6 (depth 2)\n" |
830 | " |\n" |
831 | " | (5): entering `wrapped_malloc'\n" |
832 | " | (6): calling malloc\n" |
833 | " |\n" |
834 | " <-------------+\n" |
835 | " |\n" |
836 | " `test': events 7-8 (depth 0)\n" |
837 | " |\n" |
838 | " | (7): returning to `test'\n" |
839 | " | (8): calling `free_boxed_int'\n" |
840 | " |\n" |
841 | " +--> `free_boxed_int': events 9-10 (depth 1)\n" |
842 | " |\n" |
843 | " | (9): entering `free_boxed_int'\n" |
844 | " | (10): calling `wrapped_free'\n" |
845 | " |\n" |
846 | " +--> `wrapped_free': events 11-12 (depth 2)\n" |
847 | " |\n" |
848 | " | (11): entering `wrapped_free'\n" |
849 | " | (12): calling free\n" |
850 | " |\n" |
851 | " <-------------+\n" |
852 | " |\n" |
853 | " `test': events 13-14 (depth 0)\n" |
854 | " |\n" |
855 | " | (13): returning to `test'\n" |
856 | " | (14): calling `free_boxed_int'\n" |
857 | " |\n" |
858 | " +--> `free_boxed_int': events 15-16 (depth 1)\n" |
859 | " |\n" |
860 | " | (15): entering `free_boxed_int'\n" |
861 | " | (16): calling `wrapped_free'\n" |
862 | " |\n" |
863 | " +--> `wrapped_free': events 17-18 (depth 2)\n" |
864 | " |\n" |
865 | " | (17): entering `wrapped_free'\n" |
866 | " | (18): calling free\n" |
867 | " |\n" , |
868 | pp_formatted_text (dc.printer)); |
869 | } |
870 | |
871 | /* Example where we pop the stack to an intermediate frame, rather than the |
872 | initial one. */ |
873 | |
874 | static void |
875 | test_interprocedural_path_2 (pretty_printer *event_pp) |
876 | { |
877 | /* Build fndecls. The types aren't quite right, but that |
878 | doesn't matter for the purposes of this test. */ |
879 | tree fntype_void_void |
880 | = build_function_type_array (void_type_node, 0, NULL); |
881 | tree fndecl_foo = build_fn_decl ("foo" , fntype_void_void); |
882 | tree fndecl_bar = build_fn_decl ("bar" , fntype_void_void); |
883 | tree fndecl_baz = build_fn_decl ("baz" , fntype_void_void); |
884 | |
885 | test_diagnostic_path path (event_pp); |
886 | path.add_entry (fndecl: fndecl_foo, stack_depth: 0); |
887 | path.add_call (caller: fndecl_foo, caller_stack_depth: 0, callee: fndecl_bar); |
888 | path.add_call (caller: fndecl_bar, caller_stack_depth: 1, callee: fndecl_baz); |
889 | path.add_return (fndecl: fndecl_bar, stack_depth: 1); |
890 | path.add_call (caller: fndecl_bar, caller_stack_depth: 1, callee: fndecl_baz); |
891 | ASSERT_EQ (path.num_events (), 8); |
892 | |
893 | ASSERT_TRUE (path.interprocedural_p ()); |
894 | |
895 | path_summary summary (path, false); |
896 | ASSERT_EQ (summary.get_num_ranges (), 5); |
897 | |
898 | test_diagnostic_context dc; |
899 | print_path_summary_as_text (ps: &summary, dc: &dc, show_depths: true); |
900 | ASSERT_STREQ |
901 | (" `foo': events 1-2 (depth 0)\n" |
902 | " |\n" |
903 | " | (1): entering `foo'\n" |
904 | " | (2): calling `bar'\n" |
905 | " |\n" |
906 | " +--> `bar': events 3-4 (depth 1)\n" |
907 | " |\n" |
908 | " | (3): entering `bar'\n" |
909 | " | (4): calling `baz'\n" |
910 | " |\n" |
911 | " +--> `baz': event 5 (depth 2)\n" |
912 | " |\n" |
913 | " | (5): entering `baz'\n" |
914 | " |\n" |
915 | " <------+\n" |
916 | " |\n" |
917 | " `bar': events 6-7 (depth 1)\n" |
918 | " |\n" |
919 | " | (6): returning to `bar'\n" |
920 | " | (7): calling `baz'\n" |
921 | " |\n" |
922 | " +--> `baz': event 8 (depth 2)\n" |
923 | " |\n" |
924 | " | (8): entering `baz'\n" |
925 | " |\n" , |
926 | pp_formatted_text (dc.printer)); |
927 | } |
928 | |
929 | /* Verify that print_path_summary is sane in the face of a recursive |
930 | diagnostic_path. */ |
931 | |
932 | static void |
933 | test_recursion (pretty_printer *event_pp) |
934 | { |
935 | tree fntype_void_void |
936 | = build_function_type_array (void_type_node, 0, NULL); |
937 | tree fndecl_factorial = build_fn_decl ("factorial" , fntype_void_void); |
938 | |
939 | test_diagnostic_path path (event_pp); |
940 | path.add_entry (fndecl: fndecl_factorial, stack_depth: 0); |
941 | for (int depth = 0; depth < 3; depth++) |
942 | path.add_call (caller: fndecl_factorial, caller_stack_depth: depth, callee: fndecl_factorial); |
943 | ASSERT_EQ (path.num_events (), 7); |
944 | |
945 | ASSERT_TRUE (path.interprocedural_p ()); |
946 | |
947 | path_summary summary (path, false); |
948 | ASSERT_EQ (summary.get_num_ranges (), 4); |
949 | |
950 | test_diagnostic_context dc; |
951 | print_path_summary_as_text (ps: &summary, dc: &dc, show_depths: true); |
952 | ASSERT_STREQ |
953 | (" `factorial': events 1-2 (depth 0)\n" |
954 | " |\n" |
955 | " | (1): entering `factorial'\n" |
956 | " | (2): calling `factorial'\n" |
957 | " |\n" |
958 | " +--> `factorial': events 3-4 (depth 1)\n" |
959 | " |\n" |
960 | " | (3): entering `factorial'\n" |
961 | " | (4): calling `factorial'\n" |
962 | " |\n" |
963 | " +--> `factorial': events 5-6 (depth 2)\n" |
964 | " |\n" |
965 | " | (5): entering `factorial'\n" |
966 | " | (6): calling `factorial'\n" |
967 | " |\n" |
968 | " +--> `factorial': event 7 (depth 3)\n" |
969 | " |\n" |
970 | " | (7): entering `factorial'\n" |
971 | " |\n" , |
972 | pp_formatted_text (dc.printer)); |
973 | } |
974 | |
975 | /* Run all of the selftests within this file. */ |
976 | |
977 | void |
978 | tree_diagnostic_path_cc_tests () |
979 | { |
980 | auto_fix_quotes fix_quotes; |
981 | pretty_printer *event_pp = global_dc->printer->clone (); |
982 | pp_show_color (event_pp) = 0; |
983 | test_empty_path (event_pp); |
984 | test_intraprocedural_path (event_pp); |
985 | test_interprocedural_path_1 (event_pp); |
986 | test_interprocedural_path_2 (event_pp); |
987 | test_recursion (event_pp); |
988 | delete event_pp; |
989 | } |
990 | |
991 | } // namespace selftest |
992 | |
993 | #if __GNUC__ >= 10 |
994 | # pragma GCC diagnostic pop |
995 | #endif |
996 | |
997 | #endif /* #if CHECKING_P */ |
998 | |