libcpp, c, middle-end: Optimize initializers using #embed in C
[official-gcc.git] / gcc / testsuite / gcc.dg / plugin / analyzer_gil_plugin.c
blob1d4d66e0fd2b850752904fc8842e4d468d93bacf
1 /* Proof-of-concept of a -fanalyzer plugin.
2 Detect (some) uses of CPython API outside of the Global Interpreter Lock.
3 https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock
4 */
5 /* { dg-options "-g" } */
7 #define INCLUDE_MEMORY
8 #define INCLUDE_VECTOR
9 #include "gcc-plugin.h"
10 #include "config.h"
11 #include "system.h"
12 #include "coretypes.h"
13 #include "make-unique.h"
14 #include "diagnostic.h"
15 #include "tree.h"
16 #include "gimple.h"
17 #include "gimple-iterator.h"
18 #include "gimple-walk.h"
19 #include "diagnostic-event-id.h"
20 #include "analyzer/analyzer.h"
21 #include "analyzer/analyzer-logging.h"
22 #include "json.h"
23 #include "analyzer/sm.h"
24 #include "analyzer/pending-diagnostic.h"
26 int plugin_is_GPL_compatible;
28 #if ENABLE_ANALYZER
30 namespace ana {
32 static bool
33 type_based_on_pyobject_p (tree type)
35 /* Ideally we'd also check for "subclasses" here by iterating up the
36 first field of each struct. */
37 if (TREE_CODE (type) != RECORD_TYPE)
38 return false;
39 tree name = TYPE_IDENTIFIER (type);
40 if (!name)
41 return false;
42 return id_equal (name, "PyObject");
45 /* An experimental state machine, for tracking whether the GIL is held,
46 as global state.. */
48 class gil_state_machine : public state_machine
50 public:
51 gil_state_machine (logger *logger);
53 bool inherited_state_p () const final override { return false; }
55 bool on_stmt (sm_context &sm_ctxt,
56 const supernode *node,
57 const gimple *stmt) const final override;
59 bool can_purge_p (state_t s) const final override;
61 void check_for_pyobject_usage_without_gil (sm_context &sm_ctxt,
62 const supernode *node,
63 const gimple *stmt,
64 tree op) const;
66 private:
67 void check_for_pyobject_in_call (sm_context &sm_ctxt,
68 const supernode *node,
69 const gcall *call,
70 tree callee_fndecl) const;
72 public:
73 /* These states are "global", rather than per-expression. */
75 /* State for when we've released the GIL. */
76 state_t m_released_gil;
78 /* Stop state. */
79 state_t m_stop;
82 /* Subclass for diagnostics involving the GIL. */
84 class gil_diagnostic : public pending_diagnostic
86 public:
87 /* There isn't a warning ID for us to use. */
88 int get_controlling_option () const final override
90 return 0;
93 location_t fixup_location (location_t loc,
94 bool) const final override
96 /* Ideally we'd check for specific macros here, and only
97 resolve certain macros. */
98 if (linemap_location_from_macro_expansion_p (line_table, loc))
99 loc = linemap_resolve_location (line_table, loc,
100 LRK_MACRO_EXPANSION_POINT, NULL);
101 return loc;
104 label_text describe_state_change (const evdesc::state_change &change)
105 final override
107 if (change.is_global_p ()
108 && change.m_new_state == m_sm.m_released_gil)
109 return change.formatted_print ("releasing the GIL here");
110 if (change.is_global_p ()
111 && change.m_new_state == m_sm.get_start_state ())
112 return change.formatted_print ("acquiring the GIL here");
113 return label_text ();
116 diagnostic_event::meaning
117 get_meaning_for_state_change (const evdesc::state_change &change)
118 const final override
120 if (change.is_global_p ())
122 if (change.m_new_state == m_sm.m_released_gil)
123 return diagnostic_event::meaning (diagnostic_event::VERB_release,
124 diagnostic_event::NOUN_lock);
125 else if (change.m_new_state == m_sm.get_start_state ())
126 return diagnostic_event::meaning (diagnostic_event::VERB_acquire,
127 diagnostic_event::NOUN_lock);
129 return diagnostic_event::meaning ();
131 protected:
132 gil_diagnostic (const gil_state_machine &sm) : m_sm (sm)
136 private:
137 const gil_state_machine &m_sm;
140 class double_save_thread : public gil_diagnostic
142 public:
143 double_save_thread (const gil_state_machine &sm, const gcall *call)
144 : gil_diagnostic (sm), m_call (call)
147 const char *get_kind () const final override
149 return "double_save_thread";
152 bool subclass_equal_p (const pending_diagnostic &base_other) const override
154 const double_save_thread &sub_other
155 = (const double_save_thread &)base_other;
156 return m_call == sub_other.m_call;
159 bool emit (diagnostic_emission_context &ctxt) final override
161 return ctxt.warn ("nested usage of %qs", "Py_BEGIN_ALLOW_THREADS");
164 label_text describe_final_event (const evdesc::final_event &ev) final override
166 return ev.formatted_print ("nested usage of %qs here",
167 "Py_BEGIN_ALLOW_THREADS");
170 private:
171 const gcall *m_call;
174 class fncall_without_gil : public gil_diagnostic
176 public:
177 fncall_without_gil (const gil_state_machine &sm, const gcall *call,
178 tree callee_fndecl, unsigned arg_idx)
179 : gil_diagnostic (sm), m_call (call), m_callee_fndecl (callee_fndecl),
180 m_arg_idx (arg_idx)
183 const char *get_kind () const final override
185 return "fncall_without_gil";
188 bool subclass_equal_p (const pending_diagnostic &base_other) const override
190 const fncall_without_gil &sub_other
191 = (const fncall_without_gil &)base_other;
192 return (m_call == sub_other.m_call
193 && m_callee_fndecl == sub_other.m_callee_fndecl
194 && m_arg_idx == sub_other.m_arg_idx);
197 bool emit (diagnostic_emission_context &ctxt) final override
199 if (m_callee_fndecl)
200 return ctxt.warn ("use of PyObject as argument %i of %qE"
201 " without the GIL",
202 m_arg_idx + 1, m_callee_fndecl);
203 else
204 return ctxt.warn ("use of PyObject as argument %i of call"
205 " without the GIL",
206 m_arg_idx + 1, m_callee_fndecl);
209 label_text describe_final_event (const evdesc::final_event &ev) final override
211 if (m_callee_fndecl)
212 return ev.formatted_print ("use of PyObject as argument %i of %qE here"
213 " without the GIL",
214 m_arg_idx + 1, m_callee_fndecl);
215 else
216 return ev.formatted_print ("use of PyObject as argument %i of call here"
217 " without the GIL",
218 m_arg_idx + 1, m_callee_fndecl);
221 private:
222 const gcall *m_call;
223 tree m_callee_fndecl;
224 unsigned m_arg_idx;
227 class pyobject_usage_without_gil : public gil_diagnostic
229 public:
230 pyobject_usage_without_gil (const gil_state_machine &sm, tree expr)
231 : gil_diagnostic (sm), m_expr (expr)
234 const char *get_kind () const final override
236 return "pyobject_usage_without_gil";
239 bool subclass_equal_p (const pending_diagnostic &base_other) const override
241 return same_tree_p (m_expr,
242 ((const pyobject_usage_without_gil&)base_other).m_expr);
245 bool emit (diagnostic_emission_context &ctxt) final override
247 return ctxt.warn ("use of PyObject %qE without the GIL", m_expr);
250 label_text describe_final_event (const evdesc::final_event &ev) final override
252 return ev.formatted_print ("PyObject %qE used here without the GIL",
253 m_expr);
256 private:
257 tree m_expr;
260 /* gil_state_machine's ctor. */
262 gil_state_machine::gil_state_machine (logger *logger)
263 : state_machine ("gil", logger)
265 m_released_gil = add_state ("released_gil");
266 m_stop = add_state ("stop");
269 struct cb_data
271 cb_data (const gil_state_machine &sm, sm_context &sm_ctxt,
272 const supernode *snode, const gimple *stmt)
273 : m_sm (sm), m_sm_ctxt (sm_ctxt), m_snode (snode), m_stmt (stmt)
277 const gil_state_machine &m_sm;
278 sm_context &m_sm_ctxt;
279 const supernode *m_snode;
280 const gimple *m_stmt;
283 static bool
284 check_for_pyobject (gimple *, tree op, tree, void *data)
286 cb_data *d = (cb_data *)data;
287 d->m_sm.check_for_pyobject_usage_without_gil (d->m_sm_ctxt, d->m_snode,
288 d->m_stmt, op);
289 return true;
292 /* Assuming that the GIL has been released, complain about any
293 PyObject * arguments passed to CALL. */
295 void
296 gil_state_machine::check_for_pyobject_in_call (sm_context &sm_ctxt,
297 const supernode *node,
298 const gcall *call,
299 tree callee_fndecl) const
301 for (unsigned i = 0; i < gimple_call_num_args (call); i++)
303 tree arg = gimple_call_arg (call, i);
304 if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
305 continue;
306 tree type = TREE_TYPE (TREE_TYPE (arg));
307 if (type_based_on_pyobject_p (type))
309 sm_ctxt.warn (node, call, NULL_TREE,
310 make_unique<fncall_without_gil> (*this, call,
311 callee_fndecl,
312 i));
313 sm_ctxt.set_global_state (m_stop);
318 /* Implementation of state_machine::on_stmt vfunc for gil_state_machine. */
320 bool
321 gil_state_machine::on_stmt (sm_context &sm_ctxt,
322 const supernode *node,
323 const gimple *stmt) const
325 const state_t global_state = sm_ctxt.get_global_state ();
326 if (const gcall *call = dyn_cast <const gcall *> (stmt))
328 if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (call))
330 if (is_named_call_p (callee_fndecl, "PyEval_SaveThread", call, 0))
332 if (0)
333 inform (input_location, "found call to %qs",
334 "PyEval_SaveThread");
335 if (global_state == m_released_gil)
337 sm_ctxt.warn (node, stmt, NULL_TREE,
338 make_unique<double_save_thread> (*this, call));
339 sm_ctxt.set_global_state (m_stop);
341 else
342 sm_ctxt.set_global_state (m_released_gil);
343 return true;
345 else if (is_named_call_p (callee_fndecl, "PyEval_RestoreThread",
346 call, 1))
348 if (0)
349 inform (input_location, "found call to %qs",
350 "PyEval_SaveThread");
351 if (global_state == m_released_gil)
352 sm_ctxt.set_global_state (m_start);
353 return true;
355 else if (global_state == m_released_gil)
357 /* Find PyObject * args of calls to fns with unknown bodies. */
358 if (!fndecl_has_gimple_body_p (callee_fndecl))
359 check_for_pyobject_in_call (sm_ctxt, node, call, callee_fndecl);
362 else if (global_state == m_released_gil)
363 check_for_pyobject_in_call (sm_ctxt, node, call, NULL);
365 else
366 if (global_state == m_released_gil)
368 /* Walk the stmt, finding uses of PyObject (or "subclasses"). */
369 cb_data d (*this, sm_ctxt, node, stmt);
370 walk_stmt_load_store_addr_ops (const_cast <gimple *> (stmt), &d,
371 check_for_pyobject,
372 check_for_pyobject,
373 check_for_pyobject);
375 return false;
378 bool
379 gil_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
381 return true;
384 void
385 gil_state_machine::check_for_pyobject_usage_without_gil (sm_context &sm_ctxt,
386 const supernode *node,
387 const gimple *stmt,
388 tree op) const
390 tree type = TREE_TYPE (op);
391 if (type_based_on_pyobject_p (type))
393 sm_ctxt.warn (node, stmt, NULL_TREE,
394 make_unique<pyobject_usage_without_gil> (*this, op));
395 sm_ctxt.set_global_state (m_stop);
399 /* Callback handler for the PLUGIN_ANALYZER_INIT event. */
401 static void
402 gil_analyzer_init_cb (void *gcc_data, void */*user_data*/)
404 ana::plugin_analyzer_init_iface *iface
405 = (ana::plugin_analyzer_init_iface *)gcc_data;
406 LOG_SCOPE (iface->get_logger ());
407 if (0)
408 inform (input_location, "got here: gil_analyzer_init_cb");
409 iface->register_state_machine
410 (make_unique<gil_state_machine> (iface->get_logger ()));
413 } // namespace ana
415 #endif /* #if ENABLE_ANALYZER */
418 plugin_init (struct plugin_name_args *plugin_info,
419 struct plugin_gcc_version *version)
421 #if ENABLE_ANALYZER
422 const char *plugin_name = plugin_info->base_name;
423 if (0)
424 inform (input_location, "got here; %qs", plugin_name);
425 register_callback (plugin_info->base_name,
426 PLUGIN_ANALYZER_INIT,
427 ana::gil_analyzer_init_cb,
428 NULL); /* void *user_data */
429 #else
430 sorry_no_analyzer ();
431 #endif
432 return 0;