1 /* An experimental state machine, for tracking bad calls from within
4 Copyright (C) 2019-2024 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_MEMORY
25 #define INCLUDE_VECTOR
27 #include "coretypes.h"
28 #include "make-unique.h"
31 #include "basic-block.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
);
144 label_text
describe_state_change (const evdesc::state_change
&change
)
147 if (change
.is_global_p ()
148 && change
.m_new_state
== m_sm
.m_in_signal_handler
)
150 const function
*handler
= change
.m_event
.get_dest_function ();
151 gcc_assert (handler
);
152 return change
.formatted_print ("registering %qD as signal handler",
155 return label_text ();
158 label_text
describe_final_event (const evdesc::final_event
&ev
) final override
160 return ev
.formatted_print ("call to %qD from within signal handler",
165 const signal_state_machine
&m_sm
;
166 const gcall
*m_unsafe_call
;
167 tree m_unsafe_fndecl
;
169 /* Returns a replacement function as text if it exists. Currently
170 only "exit" has a signal-safe replacement "_exit", which does
171 slightly less, but can be used in a signal handler. */
173 get_replacement_fn ()
175 gcc_assert (m_unsafe_fndecl
&& DECL_P (m_unsafe_fndecl
));
177 if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl
)))
184 /* signal_state_machine's ctor. */
186 signal_state_machine::signal_state_machine (logger
*logger
)
187 : state_machine ("signal", logger
),
188 m_in_signal_handler (add_state ("in_signal_handler")),
189 m_stop (add_state ("stop"))
193 /* Update MODEL for edges that simulate HANDLER_FUN being called as
194 an signal-handler in response to a signal. */
197 update_model_for_signal_handler (region_model
*model
,
198 const function
&handler_fun
)
201 /* Purge all state within MODEL. */
202 *model
= region_model (model
->get_manager ());
203 model
->push_frame (handler_fun
, NULL
, NULL
);
206 /* Custom exploded_edge info: entry into a signal-handler. */
208 class signal_delivery_edge_info_t
: public custom_edge_info
211 void print (pretty_printer
*pp
) const final override
213 pp_string (pp
, "signal delivered");
216 json::object
*to_json () const
218 json::object
*custom_obj
= new json::object ();
222 bool update_model (region_model
*model
,
223 const exploded_edge
*eedge
,
224 region_model_context
*) const final override
227 gcc_assert (eedge
->m_dest
->get_function ());
228 update_model_for_signal_handler (model
,
229 *eedge
->m_dest
->get_function ());
233 void add_events_to_path (checker_path
*emission_path
,
234 const exploded_edge
&eedge ATTRIBUTE_UNUSED
)
237 emission_path
->add_event
238 (make_unique
<precanned_custom_event
>
239 (event_loc_info (UNKNOWN_LOCATION
, NULL_TREE
, 0),
241 " when the signal is delivered to the process"));
245 /* Concrete subclass of custom_transition for modeling registration of a
246 signal handler and the signal handler later being called. */
248 class register_signal_handler
: public custom_transition
251 register_signal_handler (const signal_state_machine
&sm
,
253 : m_sm (sm
), m_fndecl (fndecl
) {}
255 /* Model a signal-handler FNDECL being called at some later point
256 by injecting an edge to a new function-entry node with an empty
257 callstring, setting the 'in-signal-handler' global state
259 void impl_transition (exploded_graph
*eg
,
260 exploded_node
*src_enode
,
261 int sm_idx
) final override
263 function
*handler_fun
= DECL_STRUCT_FUNCTION (m_fndecl
);
266 const extrinsic_state
&ext_state
= eg
->get_ext_state ();
267 program_point entering_handler
268 = program_point::from_function_entry (*ext_state
.get_model_manager (),
269 eg
->get_supergraph (),
272 program_state
state_entering_handler (ext_state
);
273 update_model_for_signal_handler (state_entering_handler
.m_region_model
,
275 state_entering_handler
.m_checker_states
[sm_idx
]->set_global_state
276 (m_sm
.m_in_signal_handler
);
278 exploded_node
*dst_enode
= eg
->get_or_create_node (entering_handler
,
279 state_entering_handler
,
282 eg
->add_edge (src_enode
, dst_enode
, NULL
, /*state_change (),*/
283 true, /* assume does work */
284 make_unique
<signal_delivery_edge_info_t
> ());
287 const signal_state_machine
&m_sm
;
291 /* Get a set of functions that are known to be unsafe to call from an
292 async signal handler. */
295 get_async_signal_unsafe_fns ()
297 // TODO: populate this list more fully
298 static const char * const async_signal_unsafe_fns
[] = {
299 /* This array must be kept sorted. */
312 const size_t count
= ARRAY_SIZE (async_signal_unsafe_fns
);
313 function_set
fs (async_signal_unsafe_fns
, count
);
317 /* Return true if FNDECL is known to be unsafe to call from a signal
321 signal_unsafe_p (tree fndecl
)
323 function_set fs
= get_async_signal_unsafe_fns ();
324 if (fs
.contains_decl_p (fndecl
))
326 if (is_std_function_p (fndecl
)
327 && fs
.contains_name_p (IDENTIFIER_POINTER (DECL_NAME (fndecl
))))
333 /* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
336 signal_state_machine::on_stmt (sm_context
&sm_ctxt
,
337 const supernode
*node
,
338 const gimple
*stmt
) const
340 const state_t global_state
= sm_ctxt
.get_global_state ();
341 if (global_state
== m_start
)
343 if (const gcall
*call
= dyn_cast
<const gcall
*> (stmt
))
344 if (tree callee_fndecl
= sm_ctxt
.get_fndecl_for_call (call
))
345 if (is_named_call_p (callee_fndecl
, "signal", call
, 2)
346 || is_std_named_call_p (callee_fndecl
, "signal", call
, 2))
348 tree handler
= gimple_call_arg (call
, 1);
349 if (TREE_CODE (handler
) == ADDR_EXPR
350 && TREE_CODE (TREE_OPERAND (handler
, 0)) == FUNCTION_DECL
)
352 tree fndecl
= TREE_OPERAND (handler
, 0);
353 register_signal_handler
rsh (*this, fndecl
);
354 sm_ctxt
.on_custom_transition (&rsh
);
358 else if (global_state
== m_in_signal_handler
)
360 if (const gcall
*call
= dyn_cast
<const gcall
*> (stmt
))
361 if (tree callee_fndecl
= sm_ctxt
.get_fndecl_for_call (call
))
362 if (signal_unsafe_p (callee_fndecl
))
363 if (sm_ctxt
.get_global_state () == m_in_signal_handler
)
364 sm_ctxt
.warn (node
, stmt
, NULL_TREE
,
365 make_unique
<signal_unsafe_call
>
366 (*this, call
, callee_fndecl
));
373 signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED
) const
378 } // anonymous namespace
380 /* Internal interface to this file. */
383 make_signal_state_machine (logger
*logger
)
385 return new signal_state_machine (logger
);
392 /* Run all of the selftests within this file. */
395 analyzer_sm_signal_cc_tests ()
397 function_set fs
= get_async_signal_unsafe_fns ();
402 } // namespace selftest
404 #endif /* CHECKING_P */
408 #endif /* #if ENABLE_ANALYZER */