1 /* An experimental state machine, for tracking bad calls from within
4 Copyright (C) 2019-2020 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/>. */
25 #include "coretypes.h"
28 #include "basic-block.h"
32 #include "diagnostic-path.h"
33 #include "diagnostic-metadata.h"
35 #include "analyzer/analyzer.h"
36 #include "diagnostic-event-id.h"
37 #include "analyzer/analyzer-logging.h"
38 #include "analyzer/sm.h"
39 #include "analyzer/pending-diagnostic.h"
42 #include "ordered-hash-map.h"
44 #include "analyzer/region-model.h"
45 #include "analyzer/program-state.h"
46 #include "analyzer/checker-path.h"
49 #include "gimple-iterator.h"
51 #include "analyzer/supergraph.h"
52 #include "analyzer/call-string.h"
53 #include "analyzer/program-point.h"
54 #include "alloc-pool.h"
55 #include "fibonacci_heap.h"
56 #include "analyzer/diagnostic-manager.h"
57 #include "shortest-paths.h"
58 #include "analyzer/exploded-graph.h"
59 #include "analyzer/function-set.h"
60 #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 void on_condition (sm_context
*sm_ctxt
,
81 const supernode
*node
,
85 tree rhs
) const FINAL OVERRIDE
;
87 bool can_purge_p (state_t s
) const FINAL OVERRIDE
;
89 /* These states are "global", rather than per-expression. */
94 /* State for when we're in a signal handler. */
95 state_t m_in_signal_handler
;
101 /* Concrete subclass for describing call to an async-signal-unsafe function
102 from a signal handler. */
104 class signal_unsafe_call
105 : public pending_diagnostic_subclass
<signal_unsafe_call
>
108 signal_unsafe_call (const signal_state_machine
&sm
, const gcall
*unsafe_call
,
110 : m_sm (sm
), m_unsafe_call (unsafe_call
), m_unsafe_fndecl (unsafe_fndecl
)
112 gcc_assert (m_unsafe_fndecl
);
115 const char *get_kind () const FINAL OVERRIDE
{ return "signal_unsafe_call"; }
117 bool operator== (const signal_unsafe_call
&other
) const
119 return m_unsafe_call
== other
.m_unsafe_call
;
122 bool emit (rich_location
*rich_loc
) FINAL OVERRIDE
124 diagnostic_metadata m
;
125 /* CWE-479: Signal Handler Use of a Non-reentrant Function. */
127 return warning_at (rich_loc
, m
,
128 OPT_Wanalyzer_unsafe_call_within_signal_handler
,
129 "call to %qD from within signal handler",
133 label_text
describe_state_change (const evdesc::state_change
&change
)
136 if (change
.is_global_p ()
137 && change
.m_new_state
== m_sm
.m_in_signal_handler
)
140 = change
.m_event
.m_dst_state
.m_region_model
->get_current_function ();
141 return change
.formatted_print ("registering %qD as signal handler",
144 return label_text ();
147 label_text
describe_final_event (const evdesc::final_event
&ev
) FINAL OVERRIDE
149 return ev
.formatted_print ("call to %qD from within signal handler",
154 const signal_state_machine
&m_sm
;
155 const gcall
*m_unsafe_call
;
156 tree m_unsafe_fndecl
;
159 /* signal_state_machine's ctor. */
161 signal_state_machine::signal_state_machine (logger
*logger
)
162 : state_machine ("signal", logger
)
164 m_start
= add_state ("start");
165 m_in_signal_handler
= add_state ("in_signal_handler");
166 m_stop
= add_state ("stop");
169 /* Update MODEL for edges that simulate HANDLER_FUN being called as
170 an signal-handler in response to a signal. */
173 update_model_for_signal_handler (region_model
*model
,
174 function
*handler_fun
)
176 /* Purge all state within MODEL. */
177 *model
= region_model ();
178 model
->push_frame (handler_fun
, NULL
, NULL
);
181 /* Custom exploded_edge info: entry into a signal-handler. */
183 class signal_delivery_edge_info_t
: public exploded_edge::custom_info_t
186 void print (pretty_printer
*pp
) FINAL OVERRIDE
188 pp_string (pp
, "signal delivered");
191 void update_model (region_model
*model
,
192 const exploded_edge
&eedge
) FINAL OVERRIDE
194 update_model_for_signal_handler (model
, eedge
.m_dest
->get_function ());
197 void add_events_to_path (checker_path
*emission_path
,
198 const exploded_edge
&eedge ATTRIBUTE_UNUSED
)
201 emission_path
->add_event
202 (new custom_event (UNKNOWN_LOCATION
, NULL_TREE
, 0,
204 " when the signal is delivered to the process"));
208 /* Concrete subclass of custom_transition for modeling registration of a
209 signal handler and the signal handler later being called. */
211 class register_signal_handler
: public custom_transition
214 register_signal_handler (const signal_state_machine
&sm
,
216 : m_sm (sm
), m_fndecl (fndecl
) {}
218 /* Model a signal-handler FNDECL being called at some later point
219 by injecting an edge to a new function-entry node with an empty
220 callstring, setting the 'in-signal-handler' global state
222 void impl_transition (exploded_graph
*eg
,
223 exploded_node
*src_enode
,
224 int sm_idx
) FINAL OVERRIDE
226 function
*handler_fun
= DECL_STRUCT_FUNCTION (m_fndecl
);
229 program_point entering_handler
230 = program_point::from_function_entry (eg
->get_supergraph (),
233 program_state
state_entering_handler (eg
->get_ext_state ());
234 update_model_for_signal_handler (state_entering_handler
.m_region_model
,
236 state_entering_handler
.m_checker_states
[sm_idx
]->set_global_state
237 (m_sm
.m_in_signal_handler
);
239 exploded_node
*dst_enode
= eg
->get_or_create_node (entering_handler
,
240 state_entering_handler
,
243 eg
->add_edge (src_enode
, dst_enode
, NULL
, state_change (),
244 new signal_delivery_edge_info_t ());
247 const signal_state_machine
&m_sm
;
251 /* Get a set of functions that are known to be unsafe to call from an
252 async signal handler. */
255 get_async_signal_unsafe_fns ()
257 // TODO: populate this list more fully
258 static const char * const async_signal_unsafe_fns
[] = {
259 /* This array must be kept sorted. */
272 = sizeof(async_signal_unsafe_fns
) / sizeof (async_signal_unsafe_fns
[0]);
273 function_set
fs (async_signal_unsafe_fns
, count
);
277 /* Return true if FNDECL is known to be unsafe to call from a signal
281 signal_unsafe_p (tree fndecl
)
283 function_set fs
= get_async_signal_unsafe_fns ();
284 return fs
.contains_decl_p (fndecl
);
287 /* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
290 signal_state_machine::on_stmt (sm_context
*sm_ctxt
,
291 const supernode
*node
,
292 const gimple
*stmt
) const
294 const state_t global_state
= sm_ctxt
->get_global_state ();
295 if (global_state
== m_start
)
297 if (const gcall
*call
= dyn_cast
<const gcall
*> (stmt
))
298 if (tree callee_fndecl
= sm_ctxt
->get_fndecl_for_call (call
))
299 if (is_named_call_p (callee_fndecl
, "signal", call
, 2))
301 tree handler
= gimple_call_arg (call
, 1);
302 if (TREE_CODE (handler
) == ADDR_EXPR
303 && TREE_CODE (TREE_OPERAND (handler
, 0)) == FUNCTION_DECL
)
305 tree fndecl
= TREE_OPERAND (handler
, 0);
306 register_signal_handler
rsh (*this, fndecl
);
307 sm_ctxt
->on_custom_transition (&rsh
);
311 else if (global_state
== m_in_signal_handler
)
313 if (const gcall
*call
= dyn_cast
<const gcall
*> (stmt
))
314 if (tree callee_fndecl
= sm_ctxt
->get_fndecl_for_call (call
))
315 if (signal_unsafe_p (callee_fndecl
))
316 sm_ctxt
->warn_for_state (node
, stmt
, NULL_TREE
, m_in_signal_handler
,
317 new signal_unsafe_call (*this, call
,
324 /* Implementation of state_machine::on_condition vfunc for
325 signal_state_machine. */
328 signal_state_machine::on_condition (sm_context
*sm_ctxt ATTRIBUTE_UNUSED
,
329 const supernode
*node ATTRIBUTE_UNUSED
,
330 const gimple
*stmt ATTRIBUTE_UNUSED
,
331 tree lhs ATTRIBUTE_UNUSED
,
332 enum tree_code op ATTRIBUTE_UNUSED
,
333 tree rhs ATTRIBUTE_UNUSED
) const
339 signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED
) const
344 } // anonymous namespace
346 /* Internal interface to this file. */
349 make_signal_state_machine (logger
*logger
)
351 return new signal_state_machine (logger
);
358 /* Run all of the selftests within this file. */
361 analyzer_sm_signal_cc_tests ()
363 function_set fs
= get_async_signal_unsafe_fns ();
368 } // namespace selftest
370 #endif /* CHECKING_P */
371 #endif /* #if ENABLE_ANALYZER */