Line data Source code
1 : /* setjmp/longjmp handling
2 : Copyright (C) 2019-2026 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 : #include "analyzer/common.h"
22 :
23 : #include "analyzer/region-model.h"
24 : #include "analyzer/checker-path.h"
25 : #include "analyzer/checker-event.h"
26 : #include "analyzer/exploded-graph.h"
27 : #include "analyzer/constraint-manager.h"
28 :
29 : #if ENABLE_ANALYZER
30 :
31 : /* Return true if stmt is a setjmp or sigsetjmp call. */
32 :
33 : bool
34 41827 : is_setjmp_call_p (const gcall &call)
35 : {
36 41827 : if (is_special_named_call_p (call, "setjmp", 1)
37 41827 : || is_special_named_call_p (call, "sigsetjmp", 2))
38 : /* region_model::on_setjmp requires a pointer. */
39 33 : if (POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (&call, 0))))
40 : return true;
41 :
42 : return false;
43 : }
44 :
45 : /* Return true if stmt is a longjmp or siglongjmp call. */
46 :
47 : bool
48 41798 : is_longjmp_call_p (const gcall &call)
49 : {
50 41798 : if (is_special_named_call_p (call, "longjmp", 2)
51 41798 : || is_special_named_call_p (call, "siglongjmp", 2))
52 : /* exploded_node::on_longjmp requires a pointer for the initial
53 : argument. */
54 45 : if (POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (&call, 0))))
55 : return true;
56 :
57 : return false;
58 : }
59 :
60 : namespace ana {
61 :
62 : /* struct setjmp_record. */
63 :
64 : int
65 0 : setjmp_record::cmp (const setjmp_record &rec1, const setjmp_record &rec2)
66 : {
67 0 : if (int cmp_enode = rec1.m_enode->m_index - rec2.m_enode->m_index)
68 : return cmp_enode;
69 0 : gcc_assert (&rec1 == &rec2);
70 : return 0;
71 : }
72 :
73 : /* class setjmp_svalue : public svalue. */
74 :
75 : /* Implementation of svalue::accept vfunc for setjmp_svalue. */
76 :
77 : void
78 942 : setjmp_svalue::accept (visitor *v) const
79 : {
80 942 : v->visit_setjmp_svalue (this);
81 942 : }
82 :
83 : /* Implementation of svalue::dump_to_pp vfunc for setjmp_svalue. */
84 :
85 : void
86 0 : setjmp_svalue::dump_to_pp (pretty_printer *pp, bool simple) const
87 : {
88 0 : if (simple)
89 0 : pp_printf (pp, "SETJMP(EN: %i)", get_enode_index ());
90 : else
91 0 : pp_printf (pp, "setjmp_svalue(EN%i)", get_enode_index ());
92 0 : }
93 :
94 : /* Implementation of svalue::print_dump_widget_label vfunc for
95 : setjmp_svalue. */
96 :
97 : void
98 0 : setjmp_svalue::print_dump_widget_label (pretty_printer *pp) const
99 : {
100 0 : pp_printf (pp, "setjmp_svalue(EN: %i)", get_enode_index ());
101 0 : }
102 :
103 : /* Implementation of svalue::add_dump_widget_children vfunc for
104 : setjmp_svalue. */
105 :
106 : void
107 0 : setjmp_svalue::
108 : add_dump_widget_children (text_art::tree_widget &,
109 : const text_art::dump_widget_info &) const
110 : {
111 : /* No children. */
112 0 : }
113 :
114 : /* Get the index of the stored exploded_node. */
115 :
116 : int
117 0 : setjmp_svalue::get_enode_index () const
118 : {
119 0 : return m_setjmp_record.m_enode->m_index;
120 : }
121 :
122 : /* Verify that the stack at LONGJMP_POINT is still valid, given a call
123 : to "setjmp" at SETJMP_POINT - the stack frame that "setjmp" was
124 : called in must still be valid.
125 :
126 : Caveat: this merely checks the call_strings in the points; it doesn't
127 : detect the case where a frame returns and is then called again. */
128 :
129 : static bool
130 111 : valid_longjmp_stack_p (const program_point &longjmp_point,
131 : const program_point &setjmp_point)
132 : {
133 111 : const call_string &cs_at_longjmp = longjmp_point.get_call_string ();
134 111 : const call_string &cs_at_setjmp = setjmp_point.get_call_string ();
135 :
136 282 : if (cs_at_longjmp.length () < cs_at_setjmp.length ())
137 : return false;
138 :
139 : /* Check that the call strings match, up to the depth of the
140 : setjmp point. */
141 150 : for (unsigned depth = 0; depth < cs_at_setjmp.length (); depth++)
142 65 : if (cs_at_longjmp[depth] != cs_at_setjmp[depth])
143 : return false;
144 :
145 : return true;
146 : }
147 :
148 : /* A pending_diagnostic subclass for complaining about bad longjmps,
149 : where the enclosing function of the "setjmp" has returned (and thus
150 : the stack frame no longer exists). */
151 :
152 : class stale_jmp_buf : public pending_diagnostic_subclass<stale_jmp_buf>
153 : {
154 : public:
155 5 : stale_jmp_buf (const gcall &setjmp_call, const gcall &longjmp_call,
156 : const program_point &setjmp_point)
157 5 : : m_setjmp_call (setjmp_call), m_longjmp_call (longjmp_call),
158 5 : m_setjmp_point (setjmp_point), m_stack_pop_event (nullptr)
159 : {}
160 :
161 10 : int get_controlling_option () const final override
162 : {
163 10 : return OPT_Wanalyzer_stale_setjmp_buffer;
164 : }
165 :
166 5 : bool emit (diagnostic_emission_context &ctxt) final override
167 : {
168 5 : return ctxt.warn ("%qs called after enclosing function of %qs has returned",
169 : get_user_facing_name (m_longjmp_call),
170 5 : get_user_facing_name (m_setjmp_call));
171 : }
172 :
173 25 : const char *get_kind () const final override
174 25 : { return "stale_jmp_buf"; }
175 :
176 5 : bool operator== (const stale_jmp_buf &other) const
177 : {
178 5 : return (&m_setjmp_call == &other.m_setjmp_call
179 5 : && &m_longjmp_call == &other.m_longjmp_call);
180 : }
181 :
182 : bool
183 51 : maybe_add_custom_events_for_eedge (const exploded_edge &eedge,
184 : checker_path *emission_path)
185 : final override
186 : {
187 : /* Detect exactly when the stack first becomes invalid,
188 : and issue an event then. */
189 51 : if (m_stack_pop_event)
190 : return false;
191 51 : const exploded_node *src_node = eedge.m_src;
192 51 : const program_point &src_point = src_node->get_point ();
193 51 : const exploded_node *dst_node = eedge.m_dest;
194 51 : const program_point &dst_point = dst_node->get_point ();
195 51 : if (valid_longjmp_stack_p (src_point, m_setjmp_point)
196 51 : && !valid_longjmp_stack_p (dst_point, m_setjmp_point))
197 : {
198 : /* Compare with diagnostic_manager::add_events_for_superedge. */
199 5 : const int src_stack_depth = src_point.get_stack_depth ();
200 10 : m_stack_pop_event = new precanned_custom_event
201 5 : (event_loc_info (src_point.get_location (),
202 : src_point.get_fndecl (),
203 10 : src_stack_depth),
204 10 : "stack frame is popped here, invalidating saved environment");
205 5 : emission_path->add_event
206 5 : (std::unique_ptr<custom_event> (m_stack_pop_event));
207 5 : return false;
208 : }
209 : return false;
210 : }
211 :
212 : bool
213 10 : describe_final_event (pretty_printer &pp,
214 : const evdesc::final_event &) final override
215 : {
216 10 : if (m_stack_pop_event)
217 10 : pp_printf (&pp,
218 : "%qs called after enclosing function of %qs returned at %@",
219 : get_user_facing_name (m_longjmp_call),
220 : get_user_facing_name (m_setjmp_call),
221 : m_stack_pop_event->get_id_ptr ());
222 : else
223 0 : pp_printf (&pp,
224 : "%qs called after enclosing function of %qs has returned",
225 : get_user_facing_name (m_longjmp_call),
226 : get_user_facing_name (m_setjmp_call));
227 10 : return true;
228 : }
229 :
230 :
231 : private:
232 : const gcall &m_setjmp_call;
233 : const gcall &m_longjmp_call;
234 : program_point m_setjmp_point;
235 : custom_event *m_stack_pop_event;
236 : };
237 :
238 : /* Update this model for a call and return of setjmp/sigsetjmp at CALL within
239 : ENODE, using CTXT to report any diagnostics.
240 :
241 : This is for the initial direct invocation of setjmp/sigsetjmp (which returns
242 : 0), as opposed to any second return due to longjmp/sigsetjmp. */
243 :
244 : void
245 34 : region_model::on_setjmp (const gcall &call,
246 : const exploded_node &enode,
247 : const superedge &sedge,
248 : region_model_context *ctxt)
249 : {
250 34 : const svalue *buf_ptr = get_rvalue (gimple_call_arg (&call, 0), ctxt);
251 34 : const region *buf_reg = deref_rvalue (buf_ptr, gimple_call_arg (&call, 0),
252 : ctxt);
253 :
254 : /* Create a setjmp_svalue for this call and store it in BUF_REG's
255 : region. */
256 34 : if (buf_reg)
257 : {
258 34 : setjmp_record r (&enode, &sedge, call);
259 34 : const svalue *sval
260 34 : = m_mgr->get_or_create_setjmp_svalue (r, buf_reg->get_type ());
261 34 : set_value (buf_reg, sval, ctxt);
262 : }
263 :
264 : /* Direct calls to setjmp return 0. */
265 34 : if (tree lhs = gimple_call_lhs (&call))
266 : {
267 16 : const svalue *new_sval
268 16 : = m_mgr->get_or_create_int_cst (TREE_TYPE (lhs), 0);
269 16 : const region *lhs_reg = get_lvalue (lhs, ctxt);
270 16 : set_value (lhs_reg, new_sval, ctxt);
271 : }
272 34 : }
273 :
274 : /* Update this region_model for rewinding from a "longjmp" at LONGJMP_CALL
275 : to a "setjmp" at SETJMP_CALL where the final stack depth should be
276 : SETJMP_STACK_DEPTH. Pop any stack frames. Leak detection is *not*
277 : done, and should be done by the caller. */
278 :
279 : void
280 42 : region_model::on_longjmp (const gcall &longjmp_call, const gcall &setjmp_call,
281 : int setjmp_stack_depth, region_model_context *ctxt)
282 : {
283 : /* Evaluate the val, using the frame of the "longjmp". */
284 42 : tree fake_retval = gimple_call_arg (&longjmp_call, 1);
285 42 : const svalue *fake_retval_sval = get_rvalue (fake_retval, ctxt);
286 :
287 : /* Pop any frames until we reach the stack depth of the function where
288 : setjmp was called. */
289 42 : gcc_assert (get_stack_depth () >= setjmp_stack_depth);
290 80 : while (get_stack_depth () > setjmp_stack_depth)
291 38 : pop_frame (nullptr, nullptr, ctxt, nullptr, false);
292 :
293 42 : gcc_assert (get_stack_depth () == setjmp_stack_depth);
294 :
295 : /* Assign to LHS of "setjmp" in new_state. */
296 42 : if (tree lhs = gimple_call_lhs (&setjmp_call))
297 : {
298 : /* Passing 0 as the val to longjmp leads to setjmp returning 1. */
299 38 : const svalue *zero_sval
300 38 : = m_mgr->get_or_create_int_cst (TREE_TYPE (fake_retval), 0);
301 38 : tristate eq_zero = eval_condition (fake_retval_sval, EQ_EXPR, zero_sval);
302 : /* If we have 0, use 1. */
303 38 : if (eq_zero.is_true ())
304 : {
305 3 : const svalue *one_sval
306 3 : = m_mgr->get_or_create_int_cst (TREE_TYPE (fake_retval), 1);
307 3 : fake_retval_sval = one_sval;
308 : }
309 : else
310 : {
311 : /* Otherwise note that the value is nonzero. */
312 35 : m_constraints->add_constraint (fake_retval_sval, NE_EXPR, zero_sval);
313 : }
314 :
315 : /* Decorate the return value from setjmp as being unmergeable,
316 : so that we don't attempt to merge states with it as zero
317 : with states in which it's nonzero, leading to a clean distinction
318 : in the exploded_graph betweeen the first return and the second
319 : return. */
320 38 : fake_retval_sval = m_mgr->get_or_create_unmergeable (fake_retval_sval);
321 :
322 38 : const region *lhs_reg = get_lvalue (lhs, ctxt);
323 38 : set_value (lhs_reg, fake_retval_sval, ctxt);
324 : }
325 42 : }
326 :
327 : /* Handle LONGJMP_CALL, a call to longjmp or siglongjmp.
328 :
329 : Attempt to locate where setjmp/sigsetjmp was called on the jmp_buf and build
330 : an exploded_node and exploded_edge to it representing a rewind to that frame,
331 : handling the various kinds of failure that can occur. */
332 :
333 : void
334 63 : exploded_node::on_longjmp (exploded_graph &eg,
335 : const gcall &longjmp_call,
336 : program_state *new_state,
337 : region_model_context *ctxt)
338 : {
339 63 : tree buf_ptr = gimple_call_arg (&longjmp_call, 0);
340 63 : gcc_assert (POINTER_TYPE_P (TREE_TYPE (buf_ptr)));
341 :
342 63 : region_model *new_region_model = new_state->m_region_model;
343 63 : const svalue *buf_ptr_sval = new_region_model->get_rvalue (buf_ptr, ctxt);
344 63 : const region *buf = new_region_model->deref_rvalue (buf_ptr_sval, buf_ptr,
345 : ctxt);
346 :
347 63 : const svalue *buf_content_sval
348 63 : = new_region_model->get_store_value (buf, ctxt);
349 63 : const setjmp_svalue *setjmp_sval
350 63 : = buf_content_sval->dyn_cast_setjmp_svalue ();
351 63 : if (!setjmp_sval)
352 43 : return;
353 :
354 25 : const setjmp_record tmp_setjmp_record = setjmp_sval->get_setjmp_record ();
355 :
356 : /* Build a custom enode and eedge for rewinding from the longjmp/siglongjmp
357 : call back to the setjmp/sigsetjmp. */
358 25 : rewind_info_t rewind_info (tmp_setjmp_record, longjmp_call);
359 :
360 25 : const gcall &setjmp_call = rewind_info.get_setjmp_call ();
361 25 : const program_point point_before_setjmp = rewind_info.get_point_before_setjmp ();
362 25 : const program_point point_after_setjmp = rewind_info.get_point_after_setjmp ();
363 :
364 25 : const program_point &longjmp_point = get_point ();
365 :
366 : /* Verify that the setjmp's call_stack hasn't been popped. */
367 25 : if (!valid_longjmp_stack_p (longjmp_point, point_after_setjmp))
368 : {
369 5 : ctxt->warn (std::make_unique<stale_jmp_buf> (setjmp_call,
370 : longjmp_call,
371 : point_before_setjmp));
372 5 : return;
373 : }
374 :
375 60 : gcc_assert (longjmp_point.get_stack_depth ()
376 : >= point_after_setjmp.get_stack_depth ());
377 :
378 : /* Update the state for use by the destination node. */
379 :
380 : /* Stash the current number of diagnostics so that we can update
381 : any that this adds to show where the longjmp is rewinding to. */
382 :
383 20 : diagnostic_manager *dm = &eg.get_diagnostic_manager ();
384 20 : unsigned prev_num_diagnostics = dm->get_num_diagnostics ();
385 :
386 40 : new_region_model->on_longjmp (longjmp_call, setjmp_call,
387 : point_after_setjmp.get_stack_depth (), ctxt);
388 :
389 : /* Detect leaks in the new state relative to the old state. */
390 20 : program_state::detect_leaks (get_state (), *new_state, nullptr,
391 : eg.get_ext_state (), ctxt);
392 20 : exploded_node *next
393 20 : = eg.get_or_create_node (point_after_setjmp, *new_state, this);
394 :
395 : /* Create custom exploded_edge for a longjmp. */
396 20 : if (next)
397 : {
398 20 : exploded_edge *eedge
399 20 : = eg.add_edge (const_cast<exploded_node *> (this), next, nullptr, true,
400 20 : std::make_unique<rewind_info_t> (tmp_setjmp_record,
401 : longjmp_call));
402 :
403 : /* For any diagnostics that were queued here (such as leaks) we want
404 : the checker_path to show the rewinding events after the "final event"
405 : so that the user sees where the longjmp is rewinding to (otherwise the
406 : path is meaningless).
407 :
408 : For example, we want to emit something like:
409 : | NN | {
410 : | NN | longjmp (env, 1);
411 : | | ~~~~~~~~~~~~~~~~
412 : | | |
413 : | | (10) 'ptr' leaks here; was allocated at (7)
414 : | | (11) rewinding from 'longjmp' in 'inner'...
415 : |
416 : <-------------+
417 : |
418 : 'outer': event 12
419 : |
420 : | NN | i = setjmp(env);
421 : | | ^~~~~~
422 : | | |
423 : | | (12) ...to 'setjmp' in 'outer' (saved at (2))
424 :
425 : where the "final" event above is event (10), but we want to append
426 : events (11) and (12) afterwards.
427 :
428 : Do this by setting m_trailing_eedge on any diagnostics that were
429 : just saved. */
430 20 : unsigned num_diagnostics = dm->get_num_diagnostics ();
431 28 : for (unsigned i = prev_num_diagnostics; i < num_diagnostics; i++)
432 : {
433 8 : saved_diagnostic *sd = dm->get_saved_diagnostic (i);
434 8 : sd->m_trailing_eedge = eedge;
435 : }
436 : }
437 25 : }
438 :
439 : /* class rewind_info_t : public custom_edge_info. */
440 :
441 : /* Implementation of custom_edge_info::update_model vfunc
442 : for rewind_info_t.
443 :
444 : Update state for the special-case of a rewind of a longjmp
445 : to a setjmp (which doesn't have a superedge, but does affect
446 : state). */
447 :
448 : bool
449 22 : rewind_info_t::update_model (region_model *model,
450 : const exploded_edge *eedge,
451 : region_model_context *) const
452 : {
453 22 : gcc_assert (eedge);
454 22 : const program_point &longjmp_point = eedge->m_src->get_point ();
455 22 : const program_point &setjmp_point = eedge->m_dest->get_point ();
456 :
457 66 : gcc_assert (longjmp_point.get_stack_depth ()
458 : >= setjmp_point.get_stack_depth ());
459 :
460 44 : model->on_longjmp (get_longjmp_call (),
461 : get_setjmp_call (),
462 : setjmp_point.get_stack_depth (), nullptr);
463 22 : return true;
464 : }
465 :
466 : /* Implementation of custom_edge_info::add_events_to_path vfunc
467 : for rewind_info_t. */
468 :
469 : void
470 15 : rewind_info_t::add_events_to_path (checker_path *emission_path,
471 : const exploded_edge &eedge,
472 : pending_diagnostic &,
473 : const state_transition *) const
474 : {
475 15 : const exploded_node *src_node = eedge.m_src;
476 15 : const program_point &src_point = src_node->get_point ();
477 15 : const int src_stack_depth = src_point.get_stack_depth ();
478 15 : const exploded_node *dst_node = eedge.m_dest;
479 15 : const program_point &dst_point = dst_node->get_point ();
480 15 : const int dst_stack_depth = dst_point.get_stack_depth ();
481 :
482 15 : emission_path->add_event
483 15 : (std::make_unique<rewind_from_longjmp_event>
484 15 : (&eedge,
485 15 : event_loc_info (get_longjmp_call ().location,
486 : src_point.get_fndecl (),
487 15 : src_stack_depth),
488 15 : this));
489 15 : emission_path->add_event
490 15 : (std::make_unique<rewind_to_setjmp_event>
491 15 : (&eedge,
492 15 : event_loc_info (get_setjmp_call ().location,
493 : dst_point.get_fndecl (),
494 15 : dst_stack_depth),
495 15 : this));
496 15 : }
497 :
498 : } // namespace ana
499 :
500 : #endif /* #if ENABLE_ANALYZER */
|