LCOV - code coverage report
Current view: top level - gcc/analyzer - setjmp-longjmp.cc (source / functions) Coverage Total Hit
Test: gcc.info Lines: 90.2 % 164 148
Test Date: 2026-05-11 19:44:49 Functions: 73.7 % 19 14
Legend: Lines:     hit not hit

            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 */
        

Generated by: LCOV version 2.4-beta

LCOV profile is generated on x86_64 machine using following configure options: configure --disable-bootstrap --enable-coverage=opt --enable-languages=c,c++,fortran,go,jit,lto,rust,m2 --enable-host-shared. GCC test suite is run with the built compiler.