1 /* An experimental state machine, for tracking bad calls from within
4 Copyright (C) 2019-2025 Free Software Foundation, Inc.
5 Contributed by David Malcolm <dmalcolm@redhat.com>.
7 This file is part of GCC.
9 GCC is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3, or (at your option)
14 GCC is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with GCC; see the file COPYING3. If not see
21 <http://www.gnu.org/licenses/>. */
24 #define INCLUDE_VECTOR
26 #include "coretypes.h"
27 #include "make-unique.h"
30 #include "basic-block.h"
34 #include "diagnostic-core.h"
35 #include "diagnostic-path.h"
36 #include "analyzer/analyzer.h"
37 #include "diagnostic-event-id.h"
38 #include "analyzer/analyzer-logging.h"
39 #include "analyzer/sm.h"
40 #include "analyzer/pending-diagnostic.h"
42 #include "ordered-hash-map.h"
44 #include "analyzer/call-string.h"
45 #include "analyzer/program-point.h"
46 #include "analyzer/store.h"
47 #include "analyzer/region-model.h"
48 #include "analyzer/program-state.h"
49 #include "analyzer/checker-path.h"
51 #include "gimple-iterator.h"
53 #include "analyzer/supergraph.h"
54 #include "analyzer/diagnostic-manager.h"
55 #include "shortest-paths.h"
56 #include "analyzer/exploded-graph.h"
57 #include "analyzer/function-set.h"
58 #include "analyzer/analyzer-selftests.h"
66 /* An experimental state machine, for tracking calls to async-signal-unsafe
67 functions from within signal handlers. */
69 class signal_state_machine
: public state_machine
72 signal_state_machine (logger
*logger
);
74 bool inherited_state_p () const final override
{ return false; }
76 bool on_stmt (sm_context
&sm_ctxt
,
77 const supernode
*node
,
78 const gimple
*stmt
) const final override
;
80 bool can_purge_p (state_t s
) const final override
;
82 /* These states are "global", rather than per-expression. */
84 /* State for when we're in a signal handler. */
85 state_t m_in_signal_handler
;
91 /* Concrete subclass for describing call to an async-signal-unsafe function
92 from a signal handler. */
94 class signal_unsafe_call
95 : public pending_diagnostic_subclass
<signal_unsafe_call
>
98 signal_unsafe_call (const signal_state_machine
&sm
, const gcall
*unsafe_call
,
100 : m_sm (sm
), m_unsafe_call (unsafe_call
), m_unsafe_fndecl (unsafe_fndecl
)
102 gcc_assert (m_unsafe_fndecl
);
105 const char *get_kind () const final override
{ return "signal_unsafe_call"; }
107 bool operator== (const signal_unsafe_call
&other
) const
109 return m_unsafe_call
== other
.m_unsafe_call
;
112 int get_controlling_option () const final override
114 return OPT_Wanalyzer_unsafe_call_within_signal_handler
;
117 bool emit (diagnostic_emission_context
&ctxt
) final override
119 auto_diagnostic_group d
;
120 /* CWE-479: Signal Handler Use of a Non-reentrant Function. */
122 if (ctxt
.warn ("call to %qD from within signal handler",
125 /* If we know a possible alternative function, add a note
126 suggesting the replacement. */
127 if (const char *replacement
= get_replacement_fn ())
129 location_t note_loc
= gimple_location (m_unsafe_call
);
130 /* It would be nice to add a fixit, but the gimple call
131 location covers the whole call expression. It isn't
132 currently possible to cut this down to just the call
133 symbol. So the fixit would replace too much.
134 note_rich_loc.add_fixit_replace (replacement); */
136 "%qs is a possible signal-safe alternative for %qD",
137 replacement
, m_unsafe_fndecl
);
145 describe_state_change (pretty_printer
&pp
,
146 const evdesc::state_change
&change
) final override
148 if (change
.is_global_p ()
149 && change
.m_new_state
== m_sm
.m_in_signal_handler
)
151 const function
*handler
= change
.m_event
.get_dest_function ();
152 gcc_assert (handler
);
154 "registering %qD as signal handler",
162 describe_final_event (pretty_printer
&pp
,
163 const evdesc::final_event
&) final override
166 "call to %qD from within signal handler",
172 const signal_state_machine
&m_sm
;
173 const gcall
*m_unsafe_call
;
174 tree m_unsafe_fndecl
;
176 /* Returns a replacement function as text if it exists. Currently
177 only "exit" has a signal-safe replacement "_exit", which does
178 slightly less, but can be used in a signal handler. */
180 get_replacement_fn ()
182 gcc_assert (m_unsafe_fndecl
&& DECL_P (m_unsafe_fndecl
));
184 if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl
)))
191 /* signal_state_machine's ctor. */
193 signal_state_machine::signal_state_machine (logger
*logger
)
194 : state_machine ("signal", logger
),
195 m_in_signal_handler (add_state ("in_signal_handler")),
196 m_stop (add_state ("stop"))
200 /* Update MODEL for edges that simulate HANDLER_FUN being called as
201 an signal-handler in response to a signal. */
204 update_model_for_signal_handler (region_model
*model
,
205 const function
&handler_fun
)
208 /* Purge all state within MODEL. */
209 *model
= region_model (model
->get_manager ());
210 model
->push_frame (handler_fun
, NULL
, NULL
);
213 /* Custom exploded_edge info: entry into a signal-handler. */
215 class signal_delivery_edge_info_t
: public custom_edge_info
218 void print (pretty_printer
*pp
) const final override
220 pp_string (pp
, "signal delivered");
223 bool update_model (region_model
*model
,
224 const exploded_edge
*eedge
,
225 region_model_context
*) const final override
228 gcc_assert (eedge
->m_dest
->get_function ());
229 update_model_for_signal_handler (model
,
230 *eedge
->m_dest
->get_function ());
234 void add_events_to_path (checker_path
*emission_path
,
235 const exploded_edge
&eedge ATTRIBUTE_UNUSED
)
238 emission_path
->add_event
239 (make_unique
<precanned_custom_event
>
240 (event_loc_info (UNKNOWN_LOCATION
, NULL_TREE
, 0),
242 " when the signal is delivered to the process"));
246 /* Concrete subclass of custom_transition for modeling registration of a
247 signal handler and the signal handler later being called. */
249 class register_signal_handler
: public custom_transition
252 register_signal_handler (const signal_state_machine
&sm
,
254 : m_sm (sm
), m_fndecl (fndecl
) {}
256 /* Model a signal-handler FNDECL being called at some later point
257 by injecting an edge to a new function-entry node with an empty
258 callstring, setting the 'in-signal-handler' global state
260 void impl_transition (exploded_graph
*eg
,
261 exploded_node
*src_enode
,
262 int sm_idx
) final override
264 function
*handler_fun
= DECL_STRUCT_FUNCTION (m_fndecl
);
267 const extrinsic_state
&ext_state
= eg
->get_ext_state ();
268 program_point entering_handler
269 = program_point::from_function_entry (*ext_state
.get_model_manager (),
270 eg
->get_supergraph (),
273 program_state
state_entering_handler (ext_state
);
274 update_model_for_signal_handler (state_entering_handler
.m_region_model
,
276 state_entering_handler
.m_checker_states
[sm_idx
]->set_global_state
277 (m_sm
.m_in_signal_handler
);
279 exploded_node
*dst_enode
= eg
->get_or_create_node (entering_handler
,
280 state_entering_handler
,
283 eg
->add_edge (src_enode
, dst_enode
, NULL
, /*state_change (),*/
284 true, /* assume does work */
285 make_unique
<signal_delivery_edge_info_t
> ());
288 const signal_state_machine
&m_sm
;
292 /* Get a set of functions that are known to be unsafe to call from an
293 async signal handler. */
296 get_async_signal_unsafe_fns ()
298 // TODO: populate this list more fully
299 static const char * const async_signal_unsafe_fns
[] = {
300 /* This array must be kept sorted. */
313 const size_t count
= ARRAY_SIZE (async_signal_unsafe_fns
);
314 function_set
fs (async_signal_unsafe_fns
, count
);
318 /* Return true if FNDECL is known to be unsafe to call from a signal
322 signal_unsafe_p (tree fndecl
)
324 function_set fs
= get_async_signal_unsafe_fns ();
325 if (fs
.contains_decl_p (fndecl
))
327 if (is_std_function_p (fndecl
)
328 && fs
.contains_name_p (IDENTIFIER_POINTER (DECL_NAME (fndecl
))))
334 /* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
337 signal_state_machine::on_stmt (sm_context
&sm_ctxt
,
338 const supernode
*node
,
339 const gimple
*stmt
) const
341 const state_t global_state
= sm_ctxt
.get_global_state ();
342 if (global_state
== m_start
)
344 if (const gcall
*call
= dyn_cast
<const gcall
*> (stmt
))
345 if (tree callee_fndecl
= sm_ctxt
.get_fndecl_for_call (call
))
346 if (is_named_call_p (callee_fndecl
, "signal", call
, 2)
347 || is_std_named_call_p (callee_fndecl
, "signal", call
, 2))
349 tree handler
= gimple_call_arg (call
, 1);
350 if (TREE_CODE (handler
) == ADDR_EXPR
351 && TREE_CODE (TREE_OPERAND (handler
, 0)) == FUNCTION_DECL
)
353 tree fndecl
= TREE_OPERAND (handler
, 0);
354 register_signal_handler
rsh (*this, fndecl
);
355 sm_ctxt
.on_custom_transition (&rsh
);
359 else if (global_state
== m_in_signal_handler
)
361 if (const gcall
*call
= dyn_cast
<const gcall
*> (stmt
))
362 if (tree callee_fndecl
= sm_ctxt
.get_fndecl_for_call (call
))
363 if (signal_unsafe_p (callee_fndecl
))
364 if (sm_ctxt
.get_global_state () == m_in_signal_handler
)
365 sm_ctxt
.warn (node
, stmt
, NULL_TREE
,
366 make_unique
<signal_unsafe_call
>
367 (*this, call
, callee_fndecl
));
374 signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED
) const
379 } // anonymous namespace
381 /* Internal interface to this file. */
384 make_signal_state_machine (logger
*logger
)
386 return new signal_state_machine (logger
);
393 /* Run all of the selftests within this file. */
396 analyzer_sm_signal_cc_tests ()
398 function_set fs
= get_async_signal_unsafe_fns ();
403 } // namespace selftest
405 #endif /* CHECKING_P */
409 #endif /* #if ENABLE_ANALYZER */