arm: fix typo in dg-require-effective-target [PR118089]
[official-gcc.git] / gcc / analyzer / sm-signal.cc
blobf8b378f904cd55b6461d1821b4861c47069e0f42
1 /* An experimental state machine, for tracking bad calls from within
2 signal handlers.
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)
12 any later version.
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/>. */
23 #include "config.h"
24 #define INCLUDE_VECTOR
25 #include "system.h"
26 #include "coretypes.h"
27 #include "make-unique.h"
28 #include "tree.h"
29 #include "function.h"
30 #include "basic-block.h"
31 #include "gimple.h"
32 #include "options.h"
33 #include "bitmap.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"
41 #include "sbitmap.h"
42 #include "ordered-hash-map.h"
43 #include "selftest.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"
50 #include "cfg.h"
51 #include "gimple-iterator.h"
52 #include "cgraph.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"
60 #if ENABLE_ANALYZER
62 namespace ana {
64 namespace {
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
71 public:
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;
87 /* Stop state. */
88 state_t m_stop;
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>
97 public:
98 signal_unsafe_call (const signal_state_machine &sm, const gcall *unsafe_call,
99 tree unsafe_fndecl)
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. */
121 ctxt.add_cwe (479);
122 if (ctxt.warn ("call to %qD from within signal handler",
123 m_unsafe_fndecl))
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); */
135 inform (note_loc,
136 "%qs is a possible signal-safe alternative for %qD",
137 replacement, m_unsafe_fndecl);
139 return true;
141 return false;
144 bool
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);
153 pp_printf (&pp,
154 "registering %qD as signal handler",
155 handler->decl);
156 return true;
158 return false;
161 bool
162 describe_final_event (pretty_printer &pp,
163 const evdesc::final_event &) final override
165 pp_printf (&pp,
166 "call to %qD from within signal handler",
167 m_unsafe_fndecl);
168 return true;
171 private:
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. */
179 const char *
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)))
185 return "_exit";
187 return NULL;
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. */
203 static void
204 update_model_for_signal_handler (region_model *model,
205 const function &handler_fun)
207 gcc_assert (model);
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
217 public:
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
227 gcc_assert (eedge);
228 gcc_assert (eedge->m_dest->get_function ());
229 update_model_for_signal_handler (model,
230 *eedge->m_dest->get_function ());
231 return true;
234 void add_events_to_path (checker_path *emission_path,
235 const exploded_edge &eedge ATTRIBUTE_UNUSED)
236 const final override
238 emission_path->add_event
239 (make_unique<precanned_custom_event>
240 (event_loc_info (UNKNOWN_LOCATION, NULL_TREE, 0),
241 "later on,"
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
251 public:
252 register_signal_handler (const signal_state_machine &sm,
253 tree fndecl)
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
259 on the node. */
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);
265 if (!handler_fun)
266 return;
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 (),
271 *handler_fun);
273 program_state state_entering_handler (ext_state);
274 update_model_for_signal_handler (state_entering_handler.m_region_model,
275 *handler_fun);
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,
281 src_enode);
282 if (dst_enode)
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;
289 tree m_fndecl;
292 /* Get a set of functions that are known to be unsafe to call from an
293 async signal handler. */
295 static function_set
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. */
301 "exit",
302 "fprintf",
303 "free",
304 "malloc",
305 "printf",
306 "snprintf",
307 "sprintf",
308 "vfprintf",
309 "vprintf",
310 "vsnprintf",
311 "vsprintf"
313 const size_t count = ARRAY_SIZE (async_signal_unsafe_fns);
314 function_set fs (async_signal_unsafe_fns, count);
315 return fs;
318 /* Return true if FNDECL is known to be unsafe to call from a signal
319 handler. */
321 static bool
322 signal_unsafe_p (tree fndecl)
324 function_set fs = get_async_signal_unsafe_fns ();
325 if (fs.contains_decl_p (fndecl))
326 return true;
327 if (is_std_function_p (fndecl)
328 && fs.contains_name_p (IDENTIFIER_POINTER (DECL_NAME (fndecl))))
329 return true;
331 return false;
334 /* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
336 bool
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));
370 return false;
373 bool
374 signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
376 return true;
379 } // anonymous namespace
381 /* Internal interface to this file. */
383 state_machine *
384 make_signal_state_machine (logger *logger)
386 return new signal_state_machine (logger);
389 #if CHECKING_P
391 namespace selftest {
393 /* Run all of the selftests within this file. */
395 void
396 analyzer_sm_signal_cc_tests ()
398 function_set fs = get_async_signal_unsafe_fns ();
399 fs.assert_sorted ();
400 fs.assert_sane ();
403 } // namespace selftest
405 #endif /* CHECKING_P */
407 } // namespace ana
409 #endif /* #if ENABLE_ANALYZER */