]> git.ipfire.org Git - thirdparty/gcc.git/blame - gcc/analyzer/sm-signal.cc
Update copyright years.
[thirdparty/gcc.git] / gcc / analyzer / sm-signal.cc
CommitLineData
757bf1df
DM
1/* An experimental state machine, for tracking bad calls from within
2 signal handlers.
3
83ffe9cd 4 Copyright (C) 2019-2023 Free Software Foundation, Inc.
757bf1df
DM
5 Contributed by David Malcolm <dmalcolm@redhat.com>.
6
7This file is part of GCC.
8
9GCC is free software; you can redistribute it and/or modify it
10under the terms of the GNU General Public License as published by
11the Free Software Foundation; either version 3, or (at your option)
12any later version.
13
14GCC is distributed in the hope that it will be useful, but
15WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with GCC; see the file COPYING3. If not see
21<http://www.gnu.org/licenses/>. */
22
23#include "config.h"
6341f14e 24#define INCLUDE_MEMORY
757bf1df
DM
25#include "system.h"
26#include "coretypes.h"
6341f14e 27#include "make-unique.h"
757bf1df
DM
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-path.h"
35#include "diagnostic-metadata.h"
757bf1df
DM
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"
757bf1df
DM
42#include "ordered-hash-map.h"
43#include "selftest.h"
808f4dfe
DM
44#include "analyzer/call-string.h"
45#include "analyzer/program-point.h"
46#include "analyzer/store.h"
757bf1df
DM
47#include "analyzer/region-model.h"
48#include "analyzer/program-state.h"
49#include "analyzer/checker-path.h"
757bf1df
DM
50#include "cfg.h"
51#include "gimple-iterator.h"
52#include "cgraph.h"
53#include "analyzer/supergraph.h"
757bf1df
DM
54#include "analyzer/diagnostic-manager.h"
55#include "shortest-paths.h"
56#include "analyzer/exploded-graph.h"
4804c5fe
DM
57#include "analyzer/function-set.h"
58#include "analyzer/analyzer-selftests.h"
757bf1df
DM
59
60#if ENABLE_ANALYZER
61
75038aa6
DM
62namespace ana {
63
757bf1df
DM
64namespace {
65
66/* An experimental state machine, for tracking calls to async-signal-unsafe
67 functions from within signal handlers. */
68
69class signal_state_machine : public state_machine
70{
71public:
72 signal_state_machine (logger *logger);
73
ff171cb1 74 bool inherited_state_p () const final override { return false; }
757bf1df
DM
75
76 bool on_stmt (sm_context *sm_ctxt,
77 const supernode *node,
ff171cb1 78 const gimple *stmt) const final override;
757bf1df 79
ff171cb1 80 bool can_purge_p (state_t s) const final override;
757bf1df
DM
81
82 /* These states are "global", rather than per-expression. */
83
757bf1df
DM
84 /* State for when we're in a signal handler. */
85 state_t m_in_signal_handler;
86
87 /* Stop state. */
88 state_t m_stop;
89};
90
91/* Concrete subclass for describing call to an async-signal-unsafe function
92 from a signal handler. */
93
94class signal_unsafe_call
95 : public pending_diagnostic_subclass<signal_unsafe_call>
96{
97public:
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)
101 {
102 gcc_assert (m_unsafe_fndecl);
103 }
104
ff171cb1 105 const char *get_kind () const final override { return "signal_unsafe_call"; }
757bf1df
DM
106
107 bool operator== (const signal_unsafe_call &other) const
108 {
109 return m_unsafe_call == other.m_unsafe_call;
110 }
111
ff171cb1 112 int get_controlling_option () const final override
7fd6e36e
DM
113 {
114 return OPT_Wanalyzer_unsafe_call_within_signal_handler;
115 }
116
ff171cb1 117 bool emit (rich_location *rich_loc) final override
757bf1df 118 {
2221fb6f 119 auto_diagnostic_group d;
757bf1df
DM
120 diagnostic_metadata m;
121 /* CWE-479: Signal Handler Use of a Non-reentrant Function. */
122 m.add_cwe (479);
7fd6e36e 123 if (warning_meta (rich_loc, m, get_controlling_option (),
2221fb6f
MW
124 "call to %qD from within signal handler",
125 m_unsafe_fndecl))
126 {
127 /* If we know a possible alternative function, add a note
128 suggesting the replacement. */
129 if (const char *replacement = get_replacement_fn ())
130 {
131 location_t note_loc = gimple_location (m_unsafe_call);
132 /* It would be nice to add a fixit, but the gimple call
133 location covers the whole call expression. It isn't
134 currently possible to cut this down to just the call
135 symbol. So the fixit would replace too much.
136 note_rich_loc.add_fixit_replace (replacement); */
137 inform (note_loc,
138 "%qs is a possible signal-safe alternative for %qD",
139 replacement, m_unsafe_fndecl);
140 }
141 return true;
142 }
143 return false;
757bf1df
DM
144 }
145
146 label_text describe_state_change (const evdesc::state_change &change)
ff171cb1 147 final override
757bf1df
DM
148 {
149 if (change.is_global_p ()
150 && change.m_new_state == m_sm.m_in_signal_handler)
151 {
808f4dfe 152 function *handler = change.m_event.get_dest_function ();
757bf1df
DM
153 return change.formatted_print ("registering %qD as signal handler",
154 handler->decl);
155 }
156 return label_text ();
157 }
158
ff171cb1 159 label_text describe_final_event (const evdesc::final_event &ev) final override
757bf1df
DM
160 {
161 return ev.formatted_print ("call to %qD from within signal handler",
162 m_unsafe_fndecl);
163 }
164
165private:
166 const signal_state_machine &m_sm;
167 const gcall *m_unsafe_call;
168 tree m_unsafe_fndecl;
2221fb6f
MW
169
170 /* Returns a replacement function as text if it exists. Currently
171 only "exit" has a signal-safe replacement "_exit", which does
172 slightly less, but can be used in a signal handler. */
173 const char *
174 get_replacement_fn ()
175 {
176 gcc_assert (m_unsafe_fndecl && DECL_P (m_unsafe_fndecl));
177
178 if (id_equal ("exit", DECL_NAME (m_unsafe_fndecl)))
179 return "_exit";
180
181 return NULL;
182 }
757bf1df
DM
183};
184
185/* signal_state_machine's ctor. */
186
187signal_state_machine::signal_state_machine (logger *logger)
188: state_machine ("signal", logger)
189{
757bf1df
DM
190 m_in_signal_handler = add_state ("in_signal_handler");
191 m_stop = add_state ("stop");
192}
193
194/* Update MODEL for edges that simulate HANDLER_FUN being called as
195 an signal-handler in response to a signal. */
196
197static void
198update_model_for_signal_handler (region_model *model,
199 function *handler_fun)
200{
808f4dfe 201 gcc_assert (model);
757bf1df 202 /* Purge all state within MODEL. */
808f4dfe 203 *model = region_model (model->get_manager ());
757bf1df
DM
204 model->push_frame (handler_fun, NULL, NULL);
205}
206
207/* Custom exploded_edge info: entry into a signal-handler. */
208
eafa9d96 209class signal_delivery_edge_info_t : public custom_edge_info
757bf1df
DM
210{
211public:
ff171cb1 212 void print (pretty_printer *pp) const final override
757bf1df
DM
213 {
214 pp_string (pp, "signal delivered");
215 }
216
809192e7
DM
217 json::object *to_json () const
218 {
219 json::object *custom_obj = new json::object ();
220 return custom_obj;
221 }
222
eafa9d96
DM
223 bool update_model (region_model *model,
224 const exploded_edge *eedge,
ff171cb1 225 region_model_context *) const final override
757bf1df 226 {
eafa9d96
DM
227 gcc_assert (eedge);
228 update_model_for_signal_handler (model, eedge->m_dest->get_function ());
229 return true;
757bf1df
DM
230 }
231
232 void add_events_to_path (checker_path *emission_path,
233 const exploded_edge &eedge ATTRIBUTE_UNUSED)
ff171cb1 234 const final override
757bf1df
DM
235 {
236 emission_path->add_event
d60b40b8 237 (make_unique<precanned_custom_event>
e24fe128 238 (event_loc_info (UNKNOWN_LOCATION, NULL_TREE, 0),
86606d2a
DM
239 "later on,"
240 " when the signal is delivered to the process"));
757bf1df
DM
241 }
242};
243
244/* Concrete subclass of custom_transition for modeling registration of a
245 signal handler and the signal handler later being called. */
246
247class register_signal_handler : public custom_transition
248{
249public:
250 register_signal_handler (const signal_state_machine &sm,
251 tree fndecl)
252 : m_sm (sm), m_fndecl (fndecl) {}
253
254 /* Model a signal-handler FNDECL being called at some later point
255 by injecting an edge to a new function-entry node with an empty
256 callstring, setting the 'in-signal-handler' global state
257 on the node. */
258 void impl_transition (exploded_graph *eg,
259 exploded_node *src_enode,
ff171cb1 260 int sm_idx) final override
757bf1df
DM
261 {
262 function *handler_fun = DECL_STRUCT_FUNCTION (m_fndecl);
263 if (!handler_fun)
264 return;
bb8e93eb 265 const extrinsic_state &ext_state = eg->get_ext_state ();
757bf1df 266 program_point entering_handler
bb8e93eb
DM
267 = program_point::from_function_entry (*ext_state.get_model_manager (),
268 eg->get_supergraph (),
757bf1df
DM
269 handler_fun);
270
bb8e93eb 271 program_state state_entering_handler (ext_state);
757bf1df
DM
272 update_model_for_signal_handler (state_entering_handler.m_region_model,
273 handler_fun);
274 state_entering_handler.m_checker_states[sm_idx]->set_global_state
275 (m_sm.m_in_signal_handler);
276
277 exploded_node *dst_enode = eg->get_or_create_node (entering_handler,
278 state_entering_handler,
808f4dfe 279 src_enode);
757bf1df 280 if (dst_enode)
808f4dfe 281 eg->add_edge (src_enode, dst_enode, NULL, /*state_change (),*/
ca5ff105 282 make_unique<signal_delivery_edge_info_t> ());
757bf1df
DM
283 }
284
285 const signal_state_machine &m_sm;
286 tree m_fndecl;
287};
288
4804c5fe
DM
289/* Get a set of functions that are known to be unsafe to call from an
290 async signal handler. */
757bf1df 291
4804c5fe
DM
292static function_set
293get_async_signal_unsafe_fns ()
757bf1df 294{
4804c5fe
DM
295 // TODO: populate this list more fully
296 static const char * const async_signal_unsafe_fns[] = {
297 /* This array must be kept sorted. */
2221fb6f 298 "exit",
4804c5fe
DM
299 "fprintf",
300 "free",
301 "malloc",
302 "printf",
303 "snprintf",
304 "sprintf",
305 "vfprintf",
306 "vprintf",
307 "vsnprintf",
308 "vsprintf"
309 };
ca32b29e 310 const size_t count = ARRAY_SIZE (async_signal_unsafe_fns);
4804c5fe
DM
311 function_set fs (async_signal_unsafe_fns, count);
312 return fs;
16485ea9 313}
757bf1df 314
4804c5fe
DM
315/* Return true if FNDECL is known to be unsafe to call from a signal
316 handler. */
317
318static bool
319signal_unsafe_p (tree fndecl)
320{
321 function_set fs = get_async_signal_unsafe_fns ();
322 return fs.contains_decl_p (fndecl);
757bf1df
DM
323}
324
325/* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
326
327bool
328signal_state_machine::on_stmt (sm_context *sm_ctxt,
329 const supernode *node,
330 const gimple *stmt) const
331{
332 const state_t global_state = sm_ctxt->get_global_state ();
333 if (global_state == m_start)
334 {
335 if (const gcall *call = dyn_cast <const gcall *> (stmt))
336 if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
337 if (is_named_call_p (callee_fndecl, "signal", call, 2))
338 {
339 tree handler = gimple_call_arg (call, 1);
340 if (TREE_CODE (handler) == ADDR_EXPR
341 && TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL)
342 {
343 tree fndecl = TREE_OPERAND (handler, 0);
344 register_signal_handler rsh (*this, fndecl);
345 sm_ctxt->on_custom_transition (&rsh);
346 }
347 }
348 }
349 else if (global_state == m_in_signal_handler)
350 {
351 if (const gcall *call = dyn_cast <const gcall *> (stmt))
352 if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
353 if (signal_unsafe_p (callee_fndecl))
25ef215a
DM
354 if (sm_ctxt->get_global_state () == m_in_signal_handler)
355 sm_ctxt->warn (node, stmt, NULL_TREE,
6341f14e
DM
356 make_unique<signal_unsafe_call>
357 (*this, call, callee_fndecl));
757bf1df
DM
358 }
359
360 return false;
361}
362
757bf1df
DM
363bool
364signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
365{
366 return true;
367}
368
369} // anonymous namespace
370
371/* Internal interface to this file. */
372
373state_machine *
374make_signal_state_machine (logger *logger)
375{
376 return new signal_state_machine (logger);
377}
378
4804c5fe
DM
379#if CHECKING_P
380
381namespace selftest {
382
383/* Run all of the selftests within this file. */
384
385void
386analyzer_sm_signal_cc_tests ()
387{
388 function_set fs = get_async_signal_unsafe_fns ();
389 fs.assert_sorted ();
390 fs.assert_sane ();
391}
392
393} // namespace selftest
394
c9c8aef4
DM
395#endif /* CHECKING_P */
396
75038aa6
DM
397} // namespace ana
398
757bf1df 399#endif /* #if ENABLE_ANALYZER */