blob: 7fe0033095e0ebe80e808597c392f01fae58c3d3 [file]
/* setjmp/longjmp handling
Copyright (C) 2019-2026 Free Software Foundation, Inc.
Contributed by David Malcolm <dmalcolm@redhat.com>.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include "analyzer/common.h"
#include "analyzer/region-model.h"
#include "analyzer/checker-path.h"
#include "analyzer/checker-event.h"
#include "analyzer/exploded-graph.h"
#include "analyzer/constraint-manager.h"
#if ENABLE_ANALYZER
/* Return true if stmt is a setjmp or sigsetjmp call. */
bool
is_setjmp_call_p (const gcall &call)
{
if (is_special_named_call_p (call, "setjmp", 1)
|| is_special_named_call_p (call, "sigsetjmp", 2))
/* region_model::on_setjmp requires a pointer. */
if (POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (&call, 0))))
return true;
return false;
}
/* Return true if stmt is a longjmp or siglongjmp call. */
bool
is_longjmp_call_p (const gcall &call)
{
if (is_special_named_call_p (call, "longjmp", 2)
|| is_special_named_call_p (call, "siglongjmp", 2))
/* exploded_node::on_longjmp requires a pointer for the initial
argument. */
if (POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (&call, 0))))
return true;
return false;
}
namespace ana {
/* struct setjmp_record. */
int
setjmp_record::cmp (const setjmp_record &rec1, const setjmp_record &rec2)
{
if (int cmp_enode = rec1.m_enode->m_index - rec2.m_enode->m_index)
return cmp_enode;
gcc_assert (&rec1 == &rec2);
return 0;
}
/* class setjmp_svalue : public svalue. */
/* Implementation of svalue::accept vfunc for setjmp_svalue. */
void
setjmp_svalue::accept (visitor *v) const
{
v->visit_setjmp_svalue (this);
}
/* Implementation of svalue::dump_to_pp vfunc for setjmp_svalue. */
void
setjmp_svalue::dump_to_pp (pretty_printer *pp, bool simple) const
{
if (simple)
pp_printf (pp, "SETJMP(EN: %i)", get_enode_index ());
else
pp_printf (pp, "setjmp_svalue(EN%i)", get_enode_index ());
}
/* Implementation of svalue::print_dump_widget_label vfunc for
setjmp_svalue. */
void
setjmp_svalue::print_dump_widget_label (pretty_printer *pp) const
{
pp_printf (pp, "setjmp_svalue(EN: %i)", get_enode_index ());
}
/* Implementation of svalue::add_dump_widget_children vfunc for
setjmp_svalue. */
void
setjmp_svalue::
add_dump_widget_children (text_art::tree_widget &,
const text_art::dump_widget_info &) const
{
/* No children. */
}
/* Get the index of the stored exploded_node. */
int
setjmp_svalue::get_enode_index () const
{
return m_setjmp_record.m_enode->m_index;
}
/* Verify that the stack at LONGJMP_POINT is still valid, given a call
to "setjmp" at SETJMP_POINT - the stack frame that "setjmp" was
called in must still be valid.
Caveat: this merely checks the call_strings in the points; it doesn't
detect the case where a frame returns and is then called again. */
static bool
valid_longjmp_stack_p (const program_point &longjmp_point,
const program_point &setjmp_point)
{
const call_string &cs_at_longjmp = longjmp_point.get_call_string ();
const call_string &cs_at_setjmp = setjmp_point.get_call_string ();
if (cs_at_longjmp.length () < cs_at_setjmp.length ())
return false;
/* Check that the call strings match, up to the depth of the
setjmp point. */
for (unsigned depth = 0; depth < cs_at_setjmp.length (); depth++)
if (cs_at_longjmp[depth] != cs_at_setjmp[depth])
return false;
return true;
}
/* A pending_diagnostic subclass for complaining about bad longjmps,
where the enclosing function of the "setjmp" has returned (and thus
the stack frame no longer exists). */
class stale_jmp_buf : public pending_diagnostic_subclass<stale_jmp_buf>
{
public:
stale_jmp_buf (const gcall &setjmp_call, const gcall &longjmp_call,
const program_point &setjmp_point)
: m_setjmp_call (setjmp_call), m_longjmp_call (longjmp_call),
m_setjmp_point (setjmp_point), m_stack_pop_event (nullptr)
{}
int get_controlling_option () const final override
{
return OPT_Wanalyzer_stale_setjmp_buffer;
}
bool emit (diagnostic_emission_context &ctxt) final override
{
return ctxt.warn ("%qs called after enclosing function of %qs has returned",
get_user_facing_name (m_longjmp_call),
get_user_facing_name (m_setjmp_call));
}
const char *get_kind () const final override
{ return "stale_jmp_buf"; }
bool operator== (const stale_jmp_buf &other) const
{
return (&m_setjmp_call == &other.m_setjmp_call
&& &m_longjmp_call == &other.m_longjmp_call);
}
bool
maybe_add_custom_events_for_eedge (const exploded_edge &eedge,
checker_path *emission_path)
final override
{
/* Detect exactly when the stack first becomes invalid,
and issue an event then. */
if (m_stack_pop_event)
return false;
const exploded_node *src_node = eedge.m_src;
const program_point &src_point = src_node->get_point ();
const exploded_node *dst_node = eedge.m_dest;
const program_point &dst_point = dst_node->get_point ();
if (valid_longjmp_stack_p (src_point, m_setjmp_point)
&& !valid_longjmp_stack_p (dst_point, m_setjmp_point))
{
/* Compare with diagnostic_manager::add_events_for_superedge. */
const int src_stack_depth = src_point.get_stack_depth ();
m_stack_pop_event = new precanned_custom_event
(event_loc_info (src_point.get_location (),
src_point.get_fndecl (),
src_stack_depth),
"stack frame is popped here, invalidating saved environment");
emission_path->add_event
(std::unique_ptr<custom_event> (m_stack_pop_event));
return false;
}
return false;
}
bool
describe_final_event (pretty_printer &pp,
const evdesc::final_event &) final override
{
if (m_stack_pop_event)
pp_printf (&pp,
"%qs called after enclosing function of %qs returned at %@",
get_user_facing_name (m_longjmp_call),
get_user_facing_name (m_setjmp_call),
m_stack_pop_event->get_id_ptr ());
else
pp_printf (&pp,
"%qs called after enclosing function of %qs has returned",
get_user_facing_name (m_longjmp_call),
get_user_facing_name (m_setjmp_call));
return true;
}
private:
const gcall &m_setjmp_call;
const gcall &m_longjmp_call;
program_point m_setjmp_point;
custom_event *m_stack_pop_event;
};
/* Update this model for a call and return of setjmp/sigsetjmp at CALL within
ENODE, using CTXT to report any diagnostics.
This is for the initial direct invocation of setjmp/sigsetjmp (which returns
0), as opposed to any second return due to longjmp/sigsetjmp. */
void
region_model::on_setjmp (const gcall &call,
const exploded_node &enode,
const superedge &sedge,
region_model_context *ctxt)
{
const svalue *buf_ptr = get_rvalue (gimple_call_arg (&call, 0), ctxt);
const region *buf_reg = deref_rvalue (buf_ptr, gimple_call_arg (&call, 0),
ctxt);
/* Create a setjmp_svalue for this call and store it in BUF_REG's
region. */
if (buf_reg)
{
setjmp_record r (&enode, &sedge, call);
const svalue *sval
= m_mgr->get_or_create_setjmp_svalue (r, buf_reg->get_type ());
set_value (buf_reg, sval, ctxt);
}
/* Direct calls to setjmp return 0. */
if (tree lhs = gimple_call_lhs (&call))
{
const svalue *new_sval
= m_mgr->get_or_create_int_cst (TREE_TYPE (lhs), 0);
const region *lhs_reg = get_lvalue (lhs, ctxt);
set_value (lhs_reg, new_sval, ctxt);
}
}
/* Update this region_model for rewinding from a "longjmp" at LONGJMP_CALL
to a "setjmp" at SETJMP_CALL where the final stack depth should be
SETJMP_STACK_DEPTH. Pop any stack frames. Leak detection is *not*
done, and should be done by the caller. */
void
region_model::on_longjmp (const gcall &longjmp_call, const gcall &setjmp_call,
int setjmp_stack_depth, region_model_context *ctxt)
{
/* Evaluate the val, using the frame of the "longjmp". */
tree fake_retval = gimple_call_arg (&longjmp_call, 1);
const svalue *fake_retval_sval = get_rvalue (fake_retval, ctxt);
/* Pop any frames until we reach the stack depth of the function where
setjmp was called. */
gcc_assert (get_stack_depth () >= setjmp_stack_depth);
while (get_stack_depth () > setjmp_stack_depth)
pop_frame (nullptr, nullptr, ctxt, nullptr, false);
gcc_assert (get_stack_depth () == setjmp_stack_depth);
/* Assign to LHS of "setjmp" in new_state. */
if (tree lhs = gimple_call_lhs (&setjmp_call))
{
/* Passing 0 as the val to longjmp leads to setjmp returning 1. */
const svalue *zero_sval
= m_mgr->get_or_create_int_cst (TREE_TYPE (fake_retval), 0);
tristate eq_zero = eval_condition (fake_retval_sval, EQ_EXPR, zero_sval);
/* If we have 0, use 1. */
if (eq_zero.is_true ())
{
const svalue *one_sval
= m_mgr->get_or_create_int_cst (TREE_TYPE (fake_retval), 1);
fake_retval_sval = one_sval;
}
else
{
/* Otherwise note that the value is nonzero. */
m_constraints->add_constraint (fake_retval_sval, NE_EXPR, zero_sval);
}
/* Decorate the return value from setjmp as being unmergeable,
so that we don't attempt to merge states with it as zero
with states in which it's nonzero, leading to a clean distinction
in the exploded_graph betweeen the first return and the second
return. */
fake_retval_sval = m_mgr->get_or_create_unmergeable (fake_retval_sval);
const region *lhs_reg = get_lvalue (lhs, ctxt);
set_value (lhs_reg, fake_retval_sval, ctxt);
}
}
/* Handle LONGJMP_CALL, a call to longjmp or siglongjmp.
Attempt to locate where setjmp/sigsetjmp was called on the jmp_buf and build
an exploded_node and exploded_edge to it representing a rewind to that frame,
handling the various kinds of failure that can occur. */
void
exploded_node::on_longjmp (exploded_graph &eg,
const gcall &longjmp_call,
program_state *new_state,
region_model_context *ctxt)
{
tree buf_ptr = gimple_call_arg (&longjmp_call, 0);
gcc_assert (POINTER_TYPE_P (TREE_TYPE (buf_ptr)));
region_model *new_region_model = new_state->m_region_model;
const svalue *buf_ptr_sval = new_region_model->get_rvalue (buf_ptr, ctxt);
const region *buf = new_region_model->deref_rvalue (buf_ptr_sval, buf_ptr,
ctxt);
const svalue *buf_content_sval
= new_region_model->get_store_value (buf, ctxt);
const setjmp_svalue *setjmp_sval
= buf_content_sval->dyn_cast_setjmp_svalue ();
if (!setjmp_sval)
return;
const setjmp_record tmp_setjmp_record = setjmp_sval->get_setjmp_record ();
/* Build a custom enode and eedge for rewinding from the longjmp/siglongjmp
call back to the setjmp/sigsetjmp. */
rewind_info_t rewind_info (tmp_setjmp_record, longjmp_call);
const gcall &setjmp_call = rewind_info.get_setjmp_call ();
const program_point point_before_setjmp = rewind_info.get_point_before_setjmp ();
const program_point point_after_setjmp = rewind_info.get_point_after_setjmp ();
const program_point &longjmp_point = get_point ();
/* Verify that the setjmp's call_stack hasn't been popped. */
if (!valid_longjmp_stack_p (longjmp_point, point_after_setjmp))
{
ctxt->warn (std::make_unique<stale_jmp_buf> (setjmp_call,
longjmp_call,
point_before_setjmp));
return;
}
gcc_assert (longjmp_point.get_stack_depth ()
>= point_after_setjmp.get_stack_depth ());
/* Update the state for use by the destination node. */
/* Stash the current number of diagnostics so that we can update
any that this adds to show where the longjmp is rewinding to. */
diagnostic_manager *dm = &eg.get_diagnostic_manager ();
unsigned prev_num_diagnostics = dm->get_num_diagnostics ();
new_region_model->on_longjmp (longjmp_call, setjmp_call,
point_after_setjmp.get_stack_depth (), ctxt);
/* Detect leaks in the new state relative to the old state. */
program_state::detect_leaks (get_state (), *new_state, nullptr,
eg.get_ext_state (), ctxt);
exploded_node *next
= eg.get_or_create_node (point_after_setjmp, *new_state, this);
/* Create custom exploded_edge for a longjmp. */
if (next)
{
exploded_edge *eedge
= eg.add_edge (const_cast<exploded_node *> (this), next, nullptr, true,
std::make_unique<rewind_info_t> (tmp_setjmp_record,
longjmp_call));
/* For any diagnostics that were queued here (such as leaks) we want
the checker_path to show the rewinding events after the "final event"
so that the user sees where the longjmp is rewinding to (otherwise the
path is meaningless).
For example, we want to emit something like:
| NN | {
| NN | longjmp (env, 1);
| | ~~~~~~~~~~~~~~~~
| | |
| | (10) 'ptr' leaks here; was allocated at (7)
| | (11) rewinding from 'longjmp' in 'inner'...
|
<-------------+
|
'outer': event 12
|
| NN | i = setjmp(env);
| | ^~~~~~
| | |
| | (12) ...to 'setjmp' in 'outer' (saved at (2))
where the "final" event above is event (10), but we want to append
events (11) and (12) afterwards.
Do this by setting m_trailing_eedge on any diagnostics that were
just saved. */
unsigned num_diagnostics = dm->get_num_diagnostics ();
for (unsigned i = prev_num_diagnostics; i < num_diagnostics; i++)
{
saved_diagnostic *sd = dm->get_saved_diagnostic (i);
sd->m_trailing_eedge = eedge;
}
}
}
/* class rewind_info_t : public custom_edge_info. */
/* Implementation of custom_edge_info::update_model vfunc
for rewind_info_t.
Update state for the special-case of a rewind of a longjmp
to a setjmp (which doesn't have a superedge, but does affect
state). */
bool
rewind_info_t::update_model (region_model *model,
const exploded_edge *eedge,
region_model_context *) const
{
gcc_assert (eedge);
const program_point &longjmp_point = eedge->m_src->get_point ();
const program_point &setjmp_point = eedge->m_dest->get_point ();
gcc_assert (longjmp_point.get_stack_depth ()
>= setjmp_point.get_stack_depth ());
model->on_longjmp (get_longjmp_call (),
get_setjmp_call (),
setjmp_point.get_stack_depth (), nullptr);
return true;
}
/* Implementation of custom_edge_info::add_events_to_path vfunc
for rewind_info_t. */
void
rewind_info_t::add_events_to_path (checker_path *emission_path,
const exploded_edge &eedge,
pending_diagnostic &,
const state_transition *) const
{
const exploded_node *src_node = eedge.m_src;
const program_point &src_point = src_node->get_point ();
const int src_stack_depth = src_point.get_stack_depth ();
const exploded_node *dst_node = eedge.m_dest;
const program_point &dst_point = dst_node->get_point ();
const int dst_stack_depth = dst_point.get_stack_depth ();
emission_path->add_event
(std::make_unique<rewind_from_longjmp_event>
(&eedge,
event_loc_info (get_longjmp_call ().location,
src_point.get_fndecl (),
src_stack_depth),
this));
emission_path->add_event
(std::make_unique<rewind_to_setjmp_event>
(&eedge,
event_loc_info (get_setjmp_call ().location,
dst_point.get_fndecl (),
dst_stack_depth),
this));
}
} // namespace ana
#endif /* #if ENABLE_ANALYZER */