libcpp, c, middle-end: Optimize initializers using #embed in C
[official-gcc.git] / gcc / analyzer / sm-signal.cc
blobce214cba6cd3b52e267819817dd8ee089b684c02
1 /* An experimental state machine, for tracking bad calls from within
2 signal handlers.
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)
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_MEMORY
25 #define INCLUDE_VECTOR
26 #include "system.h"
27 #include "coretypes.h"
28 #include "make-unique.h"
29 #include "tree.h"
30 #include "function.h"
31 #include "basic-block.h"
32 #include "gimple.h"
33 #include "options.h"
34 #include "bitmap.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 label_text describe_state_change (const evdesc::state_change &change)
145 final override
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",
153 handler->decl);
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",
161 m_unsafe_fndecl);
164 private:
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. */
172 const char *
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)))
178 return "_exit";
180 return NULL;
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. */
196 static void
197 update_model_for_signal_handler (region_model *model,
198 const function &handler_fun)
200 gcc_assert (model);
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
210 public:
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 ();
219 return custom_obj;
222 bool update_model (region_model *model,
223 const exploded_edge *eedge,
224 region_model_context *) const final override
226 gcc_assert (eedge);
227 gcc_assert (eedge->m_dest->get_function ());
228 update_model_for_signal_handler (model,
229 *eedge->m_dest->get_function ());
230 return true;
233 void add_events_to_path (checker_path *emission_path,
234 const exploded_edge &eedge ATTRIBUTE_UNUSED)
235 const final override
237 emission_path->add_event
238 (make_unique<precanned_custom_event>
239 (event_loc_info (UNKNOWN_LOCATION, NULL_TREE, 0),
240 "later on,"
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
250 public:
251 register_signal_handler (const signal_state_machine &sm,
252 tree fndecl)
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
258 on the node. */
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);
264 if (!handler_fun)
265 return;
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 (),
270 *handler_fun);
272 program_state state_entering_handler (ext_state);
273 update_model_for_signal_handler (state_entering_handler.m_region_model,
274 *handler_fun);
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,
280 src_enode);
281 if (dst_enode)
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;
288 tree m_fndecl;
291 /* Get a set of functions that are known to be unsafe to call from an
292 async signal handler. */
294 static function_set
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. */
300 "exit",
301 "fprintf",
302 "free",
303 "malloc",
304 "printf",
305 "snprintf",
306 "sprintf",
307 "vfprintf",
308 "vprintf",
309 "vsnprintf",
310 "vsprintf"
312 const size_t count = ARRAY_SIZE (async_signal_unsafe_fns);
313 function_set fs (async_signal_unsafe_fns, count);
314 return fs;
317 /* Return true if FNDECL is known to be unsafe to call from a signal
318 handler. */
320 static bool
321 signal_unsafe_p (tree fndecl)
323 function_set fs = get_async_signal_unsafe_fns ();
324 if (fs.contains_decl_p (fndecl))
325 return true;
326 if (is_std_function_p (fndecl)
327 && fs.contains_name_p (IDENTIFIER_POINTER (DECL_NAME (fndecl))))
328 return true;
330 return false;
333 /* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
335 bool
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));
369 return false;
372 bool
373 signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
375 return true;
378 } // anonymous namespace
380 /* Internal interface to this file. */
382 state_machine *
383 make_signal_state_machine (logger *logger)
385 return new signal_state_machine (logger);
388 #if CHECKING_P
390 namespace selftest {
392 /* Run all of the selftests within this file. */
394 void
395 analyzer_sm_signal_cc_tests ()
397 function_set fs = get_async_signal_unsafe_fns ();
398 fs.assert_sorted ();
399 fs.assert_sane ();
402 } // namespace selftest
404 #endif /* CHECKING_P */
406 } // namespace ana
408 #endif /* #if ENABLE_ANALYZER */