1 | /* Classes for analyzer diagnostics. |
2 | Copyright (C) 2019-2024 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 |
8 | under the terms of the GNU General Public License as published by |
9 | the Free Software Foundation; either version 3, or (at your option) |
10 | any later version. |
11 | |
12 | GCC is distributed in the hope that it will be useful, but |
13 | WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | General Public License 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 | #ifndef GCC_ANALYZER_PENDING_DIAGNOSTIC_H |
22 | #define GCC_ANALYZER_PENDING_DIAGNOSTIC_H |
23 | |
24 | #include "diagnostic-metadata.h" |
25 | #include "diagnostic-path.h" |
26 | #include "analyzer/sm.h" |
27 | |
28 | namespace ana { |
29 | |
30 | /* A bundle of information about things that are of interest to a |
31 | pending_diagnostic. |
32 | |
33 | For now, merely the set of regions that are pertinent to the |
34 | diagnostic, so that we can notify the user about when they |
35 | were created. */ |
36 | |
37 | struct interesting_t |
38 | { |
39 | void add_region_creation (const region *reg); |
40 | |
41 | void dump_to_pp (pretty_printer *pp, bool simple) const; |
42 | |
43 | auto_vec<const region *> m_region_creation; |
44 | }; |
45 | |
46 | /* Various bundles of information used for generating more precise |
47 | messages for events within a diagnostic_path, for passing to the |
48 | various "describe_*" vfuncs of pending_diagnostic. See those |
49 | for more information. */ |
50 | |
51 | namespace evdesc { |
52 | |
53 | struct event_desc |
54 | { |
55 | event_desc (bool colorize) : m_colorize (colorize) {} |
56 | |
57 | label_text formatted_print (const char *fmt, ...) const |
58 | ATTRIBUTE_GCC_DIAG(2,3); |
59 | |
60 | bool m_colorize; |
61 | }; |
62 | |
63 | /* For use by pending_diagnostic::describe_state_change. */ |
64 | |
65 | struct state_change : public event_desc |
66 | { |
67 | state_change (bool colorize, |
68 | tree expr, |
69 | tree origin, |
70 | state_machine::state_t old_state, |
71 | state_machine::state_t new_state, |
72 | diagnostic_event_id_t event_id, |
73 | const state_change_event &event) |
74 | : event_desc (colorize), |
75 | m_expr (expr), m_origin (origin), |
76 | m_old_state (old_state), m_new_state (new_state), |
77 | m_event_id (event_id), m_event (event) |
78 | {} |
79 | |
80 | bool is_global_p () const { return m_expr == NULL_TREE; } |
81 | |
82 | tree m_expr; |
83 | tree m_origin; |
84 | state_machine::state_t m_old_state; |
85 | state_machine::state_t m_new_state; |
86 | diagnostic_event_id_t m_event_id; |
87 | const state_change_event &m_event; |
88 | }; |
89 | |
90 | /* For use by pending_diagnostic::describe_call_with_state. */ |
91 | |
92 | struct call_with_state : public event_desc |
93 | { |
94 | call_with_state (bool colorize, |
95 | tree caller_fndecl, tree callee_fndecl, |
96 | tree expr, state_machine::state_t state) |
97 | : event_desc (colorize), |
98 | m_caller_fndecl (caller_fndecl), |
99 | m_callee_fndecl (callee_fndecl), |
100 | m_expr (expr), |
101 | m_state (state) |
102 | { |
103 | } |
104 | |
105 | tree m_caller_fndecl; |
106 | tree m_callee_fndecl; |
107 | tree m_expr; |
108 | state_machine::state_t m_state; |
109 | }; |
110 | |
111 | /* For use by pending_diagnostic::describe_return_of_state. */ |
112 | |
113 | struct return_of_state : public event_desc |
114 | { |
115 | return_of_state (bool colorize, |
116 | tree caller_fndecl, tree callee_fndecl, |
117 | state_machine::state_t state) |
118 | : event_desc (colorize), |
119 | m_caller_fndecl (caller_fndecl), |
120 | m_callee_fndecl (callee_fndecl), |
121 | m_state (state) |
122 | { |
123 | } |
124 | |
125 | tree m_caller_fndecl; |
126 | tree m_callee_fndecl; |
127 | state_machine::state_t m_state; |
128 | }; |
129 | |
130 | /* For use by pending_diagnostic::describe_final_event. */ |
131 | |
132 | struct final_event : public event_desc |
133 | { |
134 | final_event (bool colorize, |
135 | tree expr, state_machine::state_t state, |
136 | const warning_event &event) |
137 | : event_desc (colorize), |
138 | m_expr (expr), m_state (state), m_event (event) |
139 | {} |
140 | |
141 | tree m_expr; |
142 | state_machine::state_t m_state; |
143 | const warning_event &m_event; |
144 | }; |
145 | |
146 | } /* end of namespace evdesc */ |
147 | |
148 | /* A bundle of information for use by implementations of the |
149 | pending_diagnostic::emit vfunc. |
150 | |
151 | The rich_location will have already been populated with a |
152 | diagnostic_path. */ |
153 | |
154 | class diagnostic_emission_context |
155 | { |
156 | public: |
157 | diagnostic_emission_context (const saved_diagnostic &sd, |
158 | rich_location &rich_loc, |
159 | diagnostic_metadata &metadata, |
160 | logger *logger) |
161 | : m_sd (sd), |
162 | m_rich_loc (rich_loc), |
163 | m_metadata (metadata), |
164 | m_logger (logger) |
165 | { |
166 | } |
167 | |
168 | const pending_diagnostic &get_pending_diagnostic () const; |
169 | |
170 | bool warn (const char *, ...) ATTRIBUTE_GCC_DIAG (2,3); |
171 | void inform (const char *, ...) ATTRIBUTE_GCC_DIAG (2,3); |
172 | |
173 | location_t get_location () const { return m_rich_loc.get_loc (); } |
174 | logger *get_logger () const { return m_logger; } |
175 | |
176 | void add_cwe (int cwe) { m_metadata.add_cwe (cwe); } |
177 | void add_rule (const diagnostic_metadata::rule &r) |
178 | { |
179 | m_metadata.add_rule (r); |
180 | } |
181 | |
182 | private: |
183 | const saved_diagnostic &m_sd; |
184 | rich_location &m_rich_loc; |
185 | diagnostic_metadata &m_metadata; |
186 | logger *m_logger; |
187 | }; |
188 | |
189 | /* An abstract base class for capturing information about a diagnostic in |
190 | a form that is ready to emit at a later point (or be rejected). |
191 | Each kind of diagnostic will have a concrete subclass of |
192 | pending_diagnostic. |
193 | |
194 | Normally, gcc diagnostics are emitted using va_list, which can't be |
195 | portably stored for later use, so we have to use an "emit" virtual |
196 | function. |
197 | |
198 | This class also supports comparison, so that multiple pending_diagnostic |
199 | instances can be de-duplicated. |
200 | |
201 | As well as emitting a diagnostic, the class has various "precision of |
202 | wording" virtual functions, for generating descriptions for events |
203 | within a diagnostic_path. These are optional, but implementing these |
204 | allows for more precise wordings than the more generic |
205 | implementation. */ |
206 | |
207 | class pending_diagnostic |
208 | { |
209 | public: |
210 | virtual ~pending_diagnostic () {} |
211 | |
212 | /* Vfunc to get the command-line option used when emitting the diagnostic, |
213 | or zero if there is none. |
214 | Used by diagnostic_manager for early rejection of diagnostics (to avoid |
215 | having to generate feasible execution paths for them). */ |
216 | virtual int get_controlling_option () const = 0; |
217 | |
218 | /* Vfunc to give the diagnostic the chance to terminate the execution |
219 | path being explored. By default, don't terminate the path. */ |
220 | virtual bool terminate_path_p () const { return false; } |
221 | |
222 | /* Vfunc for emitting the diagnostic. |
223 | Return true if a diagnostic is actually emitted. */ |
224 | virtual bool emit (diagnostic_emission_context &) = 0; |
225 | |
226 | /* Hand-coded RTTI: get an ID for the subclass. */ |
227 | virtual const char *get_kind () const = 0; |
228 | |
229 | /* A vfunc for identifying "use of uninitialized value". */ |
230 | virtual bool use_of_uninit_p () const { return false; } |
231 | |
232 | /* Compare for equality with OTHER, which might be of a different |
233 | subclass. */ |
234 | |
235 | bool equal_p (const pending_diagnostic &other) const |
236 | { |
237 | /* Check for pointer equality on the IDs from get_kind. */ |
238 | if (get_kind () != other.get_kind ()) |
239 | return false; |
240 | /* Call vfunc now we know they have the same ID: */ |
241 | return subclass_equal_p (other); |
242 | } |
243 | |
244 | /* A vfunc for testing for equality, where we've already |
245 | checked they have the same ID. See pending_diagnostic_subclass |
246 | below for a convenience subclass for implementing this. */ |
247 | virtual bool subclass_equal_p (const pending_diagnostic &other) const = 0; |
248 | |
249 | /* Return true if T1 and T2 are "the same" for the purposes of |
250 | diagnostic deduplication. */ |
251 | static bool same_tree_p (tree t1, tree t2); |
252 | |
253 | /* Vfunc for fixing up locations, e.g. to avoid unwinding |
254 | inside specific macros. PRIMARY is true for the primary location |
255 | for the diagnostic, and FALSE for events in their paths. */ |
256 | virtual location_t fixup_location (location_t loc, bool primary) const; |
257 | |
258 | /* Precision-of-wording vfunc for describing a critical state change |
259 | within the diagnostic_path. |
260 | |
261 | For example, a double-free diagnostic might use the descriptions: |
262 | - "first 'free' happens here" |
263 | - "second 'free' happens here" |
264 | for the pertinent events, whereas a use-after-free might use the |
265 | descriptions: |
266 | - "freed here" |
267 | - "use after free here" |
268 | Note how in both cases the first event is a "free": the best |
269 | description to use depends on the diagnostic. */ |
270 | |
271 | virtual label_text describe_state_change (const evdesc::state_change &) |
272 | { |
273 | /* Default no-op implementation. */ |
274 | return label_text (); |
275 | } |
276 | |
277 | /* Vfunc for implementing diagnostic_event::get_meaning for |
278 | state_change_event. */ |
279 | virtual diagnostic_event::meaning |
280 | get_meaning_for_state_change (const evdesc::state_change &) const |
281 | { |
282 | /* Default no-op implementation. */ |
283 | return diagnostic_event::meaning (); |
284 | } |
285 | |
286 | /* Precision-of-wording vfunc for describing an interprocedural call |
287 | carrying critial state for the diagnostic, from caller to callee. |
288 | |
289 | For example a double-free diagnostic might use: |
290 | - "passing freed pointer 'ptr' in call to 'deallocator' from 'test'" |
291 | to make it clearer how the freed value moves from caller to |
292 | callee. */ |
293 | |
294 | virtual label_text describe_call_with_state (const evdesc::call_with_state &) |
295 | { |
296 | /* Default no-op implementation. */ |
297 | return label_text (); |
298 | } |
299 | |
300 | /* Precision-of-wording vfunc for describing an interprocedural return |
301 | within the diagnostic_path that carries critial state for the |
302 | diagnostic, from callee back to caller. |
303 | |
304 | For example, a deref-of-unchecked-malloc diagnostic might use: |
305 | - "returning possibly-NULL pointer to 'make_obj' from 'allocator'" |
306 | to make it clearer how the unchecked value moves from callee |
307 | back to caller. */ |
308 | |
309 | virtual label_text describe_return_of_state (const evdesc::return_of_state &) |
310 | { |
311 | /* Default no-op implementation. */ |
312 | return label_text (); |
313 | } |
314 | |
315 | /* Precision-of-wording vfunc for describing the final event within a |
316 | diagnostic_path. |
317 | |
318 | For example a double-free diagnostic might use: |
319 | - "second 'free' here; first 'free' was at (3)" |
320 | and a use-after-free might use |
321 | - "use after 'free' here; memory was freed at (2)". */ |
322 | |
323 | virtual label_text describe_final_event (const evdesc::final_event &) |
324 | { |
325 | /* Default no-op implementation. */ |
326 | return label_text (); |
327 | } |
328 | |
329 | /* End of precision-of-wording vfuncs. */ |
330 | |
331 | /* Vfunc for adding a function_entry_event to a checker_path, so that e.g. |
332 | the infinite recursion diagnostic can add a custom event subclass |
333 | that annotates recursively entering a function. */ |
334 | |
335 | virtual void |
336 | add_function_entry_event (const exploded_edge &eedge, |
337 | checker_path *emission_path); |
338 | |
339 | /* Vfunc for extending/overriding creation of the events for an |
340 | exploded_edge that corresponds to a superedge, allowing for custom |
341 | events to be created that are pertinent to a particular |
342 | pending_diagnostic subclass. |
343 | |
344 | For example, the -Wanalyzer-stale-setjmp-buffer diagnostic adds a |
345 | custom event showing when the pertinent stack frame is popped |
346 | (and thus the point at which the jmp_buf becomes invalid). */ |
347 | |
348 | virtual bool maybe_add_custom_events_for_superedge (const exploded_edge &, |
349 | checker_path *) |
350 | { |
351 | return false; |
352 | } |
353 | |
354 | /* Vfunc for adding a call_event to a checker_path, so that e.g. |
355 | the varargs diagnostics can add a custom event subclass that annotates |
356 | the variadic arguments. */ |
357 | virtual void add_call_event (const exploded_edge &, |
358 | checker_path *); |
359 | |
360 | /* Vfunc for adding any events for the creation of regions identified |
361 | by the mark_interesting_stuff vfunc. |
362 | See the comment for class region_creation_event. */ |
363 | virtual void add_region_creation_events (const region *reg, |
364 | tree capacity, |
365 | const event_loc_info &loc_info, |
366 | checker_path &emission_path); |
367 | |
368 | /* Vfunc for adding the final warning_event to a checker_path, so that e.g. |
369 | the infinite recursion diagnostic can have its diagnostic appear at |
370 | the callsite, but the final event in the path be at the entrypoint |
371 | of the called function. */ |
372 | virtual void add_final_event (const state_machine *sm, |
373 | const exploded_node *enode, |
374 | const gimple *stmt, |
375 | tree var, state_machine::state_t state, |
376 | checker_path *emission_path); |
377 | |
378 | /* Vfunc for determining that this pending_diagnostic supercedes OTHER, |
379 | and that OTHER should therefore not be emitted. |
380 | They have already been tested for being at the same stmt. */ |
381 | |
382 | virtual bool |
383 | supercedes_p (const pending_diagnostic &other ATTRIBUTE_UNUSED) const |
384 | { |
385 | return false; |
386 | } |
387 | |
388 | /* Vfunc for registering additional information of interest to this |
389 | diagnostic. */ |
390 | |
391 | virtual void mark_interesting_stuff (interesting_t *) |
392 | { |
393 | /* Default no-op implementation. */ |
394 | } |
395 | |
396 | /* Vfunc to give diagnostic subclasses the opportunity to reject diagnostics |
397 | by imposing their own additional feasibility checks on the path to a |
398 | given feasible_node. */ |
399 | virtual bool check_valid_fpath_p (const feasible_node &, |
400 | const gimple *) const |
401 | { |
402 | /* Default implementation: accept this path. */ |
403 | return true; |
404 | } |
405 | |
406 | /* Vfunc for use in SARIF output to give pending_diagnostic subclasses |
407 | the opportunity to add diagnostic-specific properties to the SARIF |
408 | "result" object for the diagnostic. |
409 | This is intended for use when debugging a diagnostic. */ |
410 | virtual void maybe_add_sarif_properties (sarif_object &/*result_obj*/) const |
411 | { |
412 | /* Default no-op implementation. */ |
413 | } |
414 | }; |
415 | |
416 | /* A template to make it easier to make subclasses of pending_diagnostic. |
417 | |
418 | This uses the curiously-recurring template pattern, to implement |
419 | pending_diagnostic::subclass_equal_p by casting and calling |
420 | the operator== |
421 | |
422 | This assumes that BASE_OTHER has already been checked to have |
423 | been of the same subclass (which pending_diagnostic::equal_p does). */ |
424 | |
425 | template <class Subclass> |
426 | class pending_diagnostic_subclass : public pending_diagnostic |
427 | { |
428 | public: |
429 | bool subclass_equal_p (const pending_diagnostic &base_other) const |
430 | final override |
431 | { |
432 | const Subclass &other = (const Subclass &)base_other; |
433 | return *(const Subclass*)this == other; |
434 | } |
435 | }; |
436 | |
437 | /* An abstract base class for capturing additional notes that are to be |
438 | emitted with a diagnostic. */ |
439 | |
440 | class pending_note |
441 | { |
442 | public: |
443 | virtual ~pending_note () {} |
444 | |
445 | /* Hand-coded RTTI: get an ID for the subclass. */ |
446 | virtual const char *get_kind () const = 0; |
447 | |
448 | /* Vfunc for emitting the note. */ |
449 | virtual void emit () const = 0; |
450 | |
451 | bool equal_p (const pending_note &other) const |
452 | { |
453 | /* Check for pointer equality on the IDs from get_kind. */ |
454 | if (get_kind () != other.get_kind ()) |
455 | return false; |
456 | /* Call vfunc now we know they have the same ID: */ |
457 | return subclass_equal_p (other); |
458 | } |
459 | |
460 | /* A vfunc for testing for equality, where we've already |
461 | checked they have the same ID. See pending_note_subclass |
462 | below for a convenience subclass for implementing this. */ |
463 | virtual bool subclass_equal_p (const pending_note &other) const = 0; |
464 | }; |
465 | |
466 | /* Analogous to pending_diagnostic_subclass, but for pending_note. */ |
467 | |
468 | template <class Subclass> |
469 | class pending_note_subclass : public pending_note |
470 | { |
471 | public: |
472 | bool subclass_equal_p (const pending_note &base_other) const |
473 | final override |
474 | { |
475 | const Subclass &other = (const Subclass &)base_other; |
476 | return *(const Subclass*)this == other; |
477 | } |
478 | }; |
479 | |
480 | } // namespace ana |
481 | |
482 | #endif /* GCC_ANALYZER_PENDING_DIAGNOSTIC_H */ |
483 | |