Add analyzer plugin support and CPython GIL example

This patch adds a new GCC plugin event: PLUGIN_ANALYZER_INIT, called
when -fanalyzer is starting, allowing for GCC plugins to register
additional state-machine-based checks within -fanalyzer.  The idea
is that 3rd-party code might want to add domain-specific checks for
its own APIs - with the caveat that the analyzer is itself still
rather experimental.

As an example, the patch adds a proof-of-concept plugin to the testsuite
for checking CPython code: verifying that code that relinquishes
CPython's global interpreter lock doesn't attempt to do anything with
PyObjects in the sections where the lock isn't held.  It also adds a
warning about nested releases of the lock, which is forbidden.
For example:

demo.c: In function 'foo':
demo.c:11:3: warning: use of PyObject '*(obj)' without the GIL
   11 |   Py_INCREF (obj);
      |   ^~~~~~~~~
  'test': events 1-3
    |
    |   15 | void test (PyObject *obj)
    |      |      ^~~~
    |      |      |
    |      |      (1) entry to 'test'
    |   16 | {
    |   17 |   Py_BEGIN_ALLOW_THREADS
    |      |   ~~~~~~~~~~~~~~~~~~~~~~
    |      |   |
    |      |   (2) releasing the GIL here
    |   18 |   foo (obj);
    |      |   ~~~~~~~~~
    |      |   |
    |      |   (3) calling 'foo' from 'test'
    |
    +--> 'foo': events 4-5
           |
           |    9 | foo (PyObject *obj)
           |      | ^~~
           |      | |
           |      | (4) entry to 'foo'
           |   10 | {
           |   11 |   Py_INCREF (obj);
           |      |   ~~~~~~~~~
           |      |   |
           |      |   (5) PyObject '*(obj)' used here without the GIL
           |

Doing so requires adding some logic for ignoring macro expansions in
analyzer diagnostics, since the insides of Py_INCREF and
Py_BEGIN_ALLOW_THREADS are not of interest to the user for these cases.

gcc/analyzer/ChangeLog:
	* analyzer-pass.cc (pass_analyzer::execute): Move sorry call to...
	(sorry_no_analyzer): New.
	* analyzer.h (class state_machine): New forward decl.
	(class logger): New forward decl.
	(class plugin_analyzer_init_iface): New.
	(sorry_no_analyzer): New decl.
	* checker-path.cc (checker_path::fixup_locations): New.
	* checker-path.h (checker_event::set_location): New.
	(checker_path::fixup_locations): New decl.
	* diagnostic-manager.cc
	(diagnostic_manager::emit_saved_diagnostic): Call
	checker_path::fixup_locations, and call fixup_location
	on the primary location.
	* engine.cc: Include "plugin.h".
	(class plugin_analyzer_init_impl): New.
	(impl_run_checkers): Invoke PLUGIN_ANALYZER_INIT callbacks.
	* pending-diagnostic.h (pending_diagnostic::fixup_location): New
	vfunc.

gcc/ChangeLog:
	* doc/plugins.texi (Plugin callbacks): Add PLUGIN_ANALYZER_INIT.
	* plugin.c (register_callback): Likewise.
	(invoke_plugin_callbacks_full): Likewise.
	* plugin.def (PLUGIN_ANALYZER_INIT): New event.

gcc/testsuite/ChangeLog:
	* gcc.dg/plugin/analyzer_gil_plugin.c: New test.
	* gcc.dg/plugin/gil-1.c: New test.
	* gcc.dg/plugin/gil.h: New header.
	* gcc.dg/plugin/plugin.exp (plugin_test_list): Add the new plugin
	and test.
This commit is contained in:
David Malcolm 2020-11-30 14:32:28 -05:00
parent 5ddb6eca28
commit 66dde7bc64
14 changed files with 658 additions and 4 deletions

View file

@ -83,9 +83,7 @@ pass_analyzer::execute (function *)
#if ENABLE_ANALYZER
ana::run_checkers ();
#else
sorry ("%qs was not enabled in this build of GCC"
" (missing configure-time option %qs)",
"-fanalyzer", "--enable-analyzer");
sorry_no_analyzer ();
#endif
return 0;
@ -100,3 +98,17 @@ make_pass_analyzer (gcc::context *ctxt)
{
return new pass_analyzer (ctxt);
}
#if !ENABLE_ANALYZER
/* Issue a "sorry" diagnostic that the analyzer was not enabled. */
void
sorry_no_analyzer ()
{
sorry ("%qs was not enabled in this build of GCC"
" (missing configure-time option %qs)",
"-fanalyzer", "--enable-analyzer");
}
#endif /* #if !ENABLE_ANALYZER */

View file

@ -189,6 +189,15 @@ private:
extern location_t get_stmt_location (const gimple *stmt, function *fun);
/* Passed by pointer to PLUGIN_ANALYZER_INIT callbacks. */
class plugin_analyzer_init_iface
{
public:
virtual void register_state_machine (state_machine *) = 0;
virtual logger *get_logger () const = 0;
};
} // namespace ana
extern bool is_special_named_call_p (const gcall *call, const char *funcname,
@ -309,4 +318,8 @@ private:
#pragma GCC diagnostic ignored "-Wformat-diag"
#endif
#if !ENABLE_ANALYZER
extern void sorry_no_analyzer ();
#endif /* #if !ENABLE_ANALYZER */
#endif /* GCC_ANALYZER_ANALYZER_H */

View file

@ -982,6 +982,15 @@ checker_path::add_final_event (const state_machine *sm,
add_event (end_of_path);
}
void
checker_path::fixup_locations (pending_diagnostic *pd)
{
checker_event *e;
int i;
FOR_EACH_VEC_ELT (m_events, i, e)
e->set_location (pd->fixup_location (e->get_location ()));
}
} // namespace ana
#endif /* #if ENABLE_ANALYZER */

View file

@ -105,6 +105,8 @@ public:
void dump (pretty_printer *pp) const;
void set_location (location_t loc) { m_loc = loc; }
public:
const enum event_kind m_kind;
protected:
@ -504,6 +506,8 @@ public:
e->prepare_for_emission (this, pd, diagnostic_event_id_t (i));
}
void fixup_locations (pending_diagnostic *pd);
void record_setjmp_event (const exploded_node *enode,
diagnostic_event_id_t setjmp_emission_id)
{

View file

@ -665,7 +665,14 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
emission_path.prepare_for_emission (sd.m_d);
gcc_rich_location rich_loc (get_stmt_location (stmt, sd.m_snode->m_fun));
location_t loc = get_stmt_location (stmt, sd.m_snode->m_fun);
/* Allow the pending_diagnostic to fix up the primary location
and any locations for events. */
loc = sd.m_d->fixup_location (loc);
emission_path.fixup_locations (sd.m_d);
gcc_rich_location rich_loc (loc);
rich_loc.set_path (&emission_path);
auto_diagnostic_group d;

View file

@ -63,6 +63,7 @@ along with GCC; see the file COPYING3. If not see
#include "analyzer/state-purge.h"
#include "analyzer/bar-chart.h"
#include <zlib.h>
#include "plugin.h"
/* For an overview, see gcc/doc/analyzer.texi. */
@ -4638,6 +4639,33 @@ dump_analyzer_json (const supergraph &sg,
free (filename);
}
/* Concrete subclass of plugin_analyzer_init_iface, allowing plugins
to register new state machines. */
class plugin_analyzer_init_impl : public plugin_analyzer_init_iface
{
public:
plugin_analyzer_init_impl (auto_delete_vec <state_machine> *checkers,
logger *logger)
: m_checkers (checkers),
m_logger (logger)
{}
void register_state_machine (state_machine *sm) FINAL OVERRIDE
{
m_checkers->safe_push (sm);
}
logger *get_logger () const FINAL OVERRIDE
{
return m_logger;
}
private:
auto_delete_vec <state_machine> *m_checkers;
logger *m_logger;
};
/* Run the analysis "engine". */
void
@ -4683,6 +4711,9 @@ impl_run_checkers (logger *logger)
auto_delete_vec <state_machine> checkers;
make_checkers (checkers, logger);
plugin_analyzer_init_impl data (&checkers, logger);
invoke_plugin_callbacks (PLUGIN_ANALYZER_INIT, &data);
if (logger)
{
int i;

View file

@ -175,6 +175,14 @@ class pending_diagnostic
diagnostic deduplication. */
static bool same_tree_p (tree t1, tree t2);
/* A vfunc for fixing up locations (both the primary location for the
diagnostic, and for events in their paths), e.g. to avoid unwinding
inside specific macros. */
virtual location_t fixup_location (location_t loc) const
{
return loc;
}
/* For greatest precision-of-wording, the various following "describe_*"
virtual functions give the pending diagnostic a way to describe events
in a diagnostic_path in terms that make sense for that diagnostic.

View file

@ -218,6 +218,10 @@ enum plugin_event
as a const char* pointer. */
PLUGIN_INCLUDE_FILE,
/* Called when -fanalyzer starts. The event data is an
ana::plugin_analyzer_init_iface *. */
PLUGIN_ANALYZER_INIT,
PLUGIN_EVENT_FIRST_DYNAMIC /* Dummy event used for indexing callback
array. */
@};

View file

@ -497,6 +497,7 @@ register_callback (const char *plugin_name,
case PLUGIN_EARLY_GIMPLE_PASSES_END:
case PLUGIN_NEW_PASS:
case PLUGIN_INCLUDE_FILE:
case PLUGIN_ANALYZER_INIT:
{
struct callback_info *new_callback;
if (!callback)
@ -577,6 +578,7 @@ invoke_plugin_callbacks_full (int event, void *gcc_data)
case PLUGIN_EARLY_GIMPLE_PASSES_END:
case PLUGIN_NEW_PASS:
case PLUGIN_INCLUDE_FILE:
case PLUGIN_ANALYZER_INIT:
{
/* Iterate over every callback registered with this event and
call it. */

View file

@ -99,6 +99,10 @@ DEFEVENT (PLUGIN_NEW_PASS)
as a const char* pointer. */
DEFEVENT (PLUGIN_INCLUDE_FILE)
/* Called when -fanalyzer starts. The event data is an
ana::plugin_analyzer_init_iface *. */
DEFEVENT (PLUGIN_ANALYZER_INIT)
/* When adding a new hard-coded plugin event, don't forget to edit in
file plugin.c the functions register_callback and
invoke_plugin_callbacks_full accordingly! */

View file

@ -0,0 +1,436 @@
/* Proof-of-concept of a -fanalyzer plugin.
Detect (some) uses of CPython API outside of the Global Interpreter Lock.
https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock
*/
/* { dg-options "-g" } */
#include "gcc-plugin.h"
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "diagnostic.h"
#include "tree.h"
#include "gimple.h"
#include "gimple-iterator.h"
#include "gimple-walk.h"
#include "diagnostic-event-id.h"
#include "analyzer/analyzer.h"
#include "analyzer/analyzer-logging.h"
#include "json.h"
#include "analyzer/sm.h"
#include "analyzer/pending-diagnostic.h"
int plugin_is_GPL_compatible;
#if ENABLE_ANALYZER
namespace ana {
static bool
type_based_on_pyobject_p (tree type)
{
/* Ideally we'd also check for "subclasses" here by iterating up the
first field of each struct. */
if (TREE_CODE (type) != RECORD_TYPE)
return false;
tree name = TYPE_IDENTIFIER (type);
if (!name)
return false;
return id_equal (name, "PyObject");
}
/* An experimental state machine, for tracking whether the GIL is held,
as global state.. */
class gil_state_machine : public state_machine
{
public:
gil_state_machine (logger *logger);
bool inherited_state_p () const FINAL OVERRIDE { return false; }
bool on_stmt (sm_context *sm_ctxt,
const supernode *node,
const gimple *stmt) const FINAL OVERRIDE;
void on_condition (sm_context *sm_ctxt,
const supernode *node,
const gimple *stmt,
tree lhs,
enum tree_code op,
tree rhs) const FINAL OVERRIDE;
bool can_purge_p (state_t s) const FINAL OVERRIDE;
void check_for_pyobject_usage_without_gil (sm_context *sm_ctxt,
const supernode *node,
const gimple *stmt,
tree op) const;
private:
void check_for_pyobject_in_call (sm_context *sm_ctxt,
const supernode *node,
const gcall *call,
tree callee_fndecl) const;
public:
/* These states are "global", rather than per-expression. */
/* State for when we've released the GIL. */
state_t m_released_gil;
/* Stop state. */
state_t m_stop;
};
/* Subclass for diagnostics involving the GIL. */
class gil_diagnostic : public pending_diagnostic
{
public:
location_t fixup_location (location_t loc) const FINAL OVERRIDE
{
/* Ideally we'd check for specific macros here, and only
resolve certain macros. */
if (linemap_location_from_macro_expansion_p (line_table, loc))
loc = linemap_resolve_location (line_table, loc,
LRK_MACRO_EXPANSION_POINT, NULL);
return loc;
}
label_text describe_state_change (const evdesc::state_change &change)
FINAL OVERRIDE
{
if (change.is_global_p ()
&& change.m_new_state == m_sm.m_released_gil)
return change.formatted_print ("releasing the GIL here");
if (change.is_global_p ()
&& change.m_new_state == m_sm.get_start_state ())
return change.formatted_print ("acquiring the GIL here");
return label_text ();
}
protected:
gil_diagnostic (const gil_state_machine &sm) : m_sm (sm)
{
}
private:
const gil_state_machine &m_sm;
};
class double_save_thread : public gil_diagnostic
{
public:
double_save_thread (const gil_state_machine &sm, const gcall *call)
: gil_diagnostic (sm), m_call (call)
{}
const char *get_kind () const FINAL OVERRIDE
{
return "double_save_thread";
}
bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
{
const double_save_thread &sub_other
= (const double_save_thread &)base_other;
return m_call == sub_other.m_call;
}
bool emit (rich_location *rich_loc) FINAL OVERRIDE
{
return warning_at (rich_loc, 0,
"nested usage of %qs", "Py_BEGIN_ALLOW_THREADS");
}
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
{
return ev.formatted_print ("nested usage of %qs here",
"Py_BEGIN_ALLOW_THREADS");
}
private:
const gcall *m_call;
};
class fncall_without_gil : public gil_diagnostic
{
public:
fncall_without_gil (const gil_state_machine &sm, const gcall *call,
tree callee_fndecl, unsigned arg_idx)
: gil_diagnostic (sm), m_call (call), m_callee_fndecl (callee_fndecl),
m_arg_idx (arg_idx)
{}
const char *get_kind () const FINAL OVERRIDE
{
return "fncall_without_gil";
}
bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
{
const fncall_without_gil &sub_other
= (const fncall_without_gil &)base_other;
return (m_call == sub_other.m_call
&& m_callee_fndecl == sub_other.m_callee_fndecl
&& m_arg_idx == sub_other.m_arg_idx);
}
bool emit (rich_location *rich_loc) FINAL OVERRIDE
{
auto_diagnostic_group d;
/* There isn't a warning ID for use to use. */
if (m_callee_fndecl)
return warning_at (rich_loc, 0,
"use of PyObject as argument %i of %qE"
" without the GIL",
m_arg_idx + 1, m_callee_fndecl);
else
return warning_at (rich_loc, 0,
"use of PyObject as argument %i of call"
" without the GIL",
m_arg_idx + 1, m_callee_fndecl);
}
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
{
if (m_callee_fndecl)
return ev.formatted_print ("use of PyObject as argument %i of %qE here"
" without the GIL",
m_arg_idx + 1, m_callee_fndecl);
else
return ev.formatted_print ("use of PyObject as argument %i of call here"
" without the GIL",
m_arg_idx + 1, m_callee_fndecl);
}
private:
const gcall *m_call;
tree m_callee_fndecl;
unsigned m_arg_idx;
};
class pyobject_usage_without_gil : public gil_diagnostic
{
public:
pyobject_usage_without_gil (const gil_state_machine &sm, tree expr)
: gil_diagnostic (sm), m_expr (expr)
{}
const char *get_kind () const FINAL OVERRIDE
{
return "pyobject_usage_without_gil";
}
bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
{
return same_tree_p (m_expr,
((const pyobject_usage_without_gil&)base_other).m_expr);
}
bool emit (rich_location *rich_loc) FINAL OVERRIDE
{
auto_diagnostic_group d;
/* There isn't a warning ID for use to use. */
return warning_at (rich_loc, 0,
"use of PyObject %qE without the GIL", m_expr);
}
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
{
return ev.formatted_print ("PyObject %qE used here without the GIL",
m_expr);
}
private:
tree m_expr;
};
/* gil_state_machine's ctor. */
gil_state_machine::gil_state_machine (logger *logger)
: state_machine ("gil", logger)
{
m_released_gil = add_state ("released_gil");
m_stop = add_state ("stop");
}
struct cb_data
{
cb_data (const gil_state_machine &sm, sm_context *sm_ctxt,
const supernode *snode, const gimple *stmt)
: m_sm (sm), m_sm_ctxt (sm_ctxt), m_snode (snode), m_stmt (stmt)
{
}
const gil_state_machine &m_sm;
sm_context *m_sm_ctxt;
const supernode *m_snode;
const gimple *m_stmt;
};
static bool
check_for_pyobject (gimple *, tree op, tree, void *data)
{
cb_data *d = (cb_data *)data;
d->m_sm.check_for_pyobject_usage_without_gil (d->m_sm_ctxt, d->m_snode,
d->m_stmt, op);
return true;
}
/* Assuming that the GIL has been released, complain about any
PyObject * arguments passed to CALL. */
void
gil_state_machine::check_for_pyobject_in_call (sm_context *sm_ctxt,
const supernode *node,
const gcall *call,
tree callee_fndecl) const
{
for (unsigned i = 0; i < gimple_call_num_args (call); i++)
{
tree arg = gimple_call_arg (call, i);
if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
continue;
tree type = TREE_TYPE (TREE_TYPE (arg));
if (type_based_on_pyobject_p (type))
{
sm_ctxt->warn (node, call, NULL_TREE,
new fncall_without_gil (*this, call,
callee_fndecl,
i));
sm_ctxt->set_global_state (m_stop);
}
}
}
/* Implementation of state_machine::on_stmt vfunc for gil_state_machine. */
bool
gil_state_machine::on_stmt (sm_context *sm_ctxt,
const supernode *node,
const gimple *stmt) const
{
const state_t global_state = sm_ctxt->get_global_state ();
if (const gcall *call = dyn_cast <const gcall *> (stmt))
{
if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
{
if (is_named_call_p (callee_fndecl, "PyEval_SaveThread", call, 0))
{
if (0)
inform (input_location, "found call to %qs",
"PyEval_SaveThread");
if (global_state == m_released_gil)
{
sm_ctxt->warn (node, stmt, NULL_TREE,
new double_save_thread (*this, call));
sm_ctxt->set_global_state (m_stop);
}
else
sm_ctxt->set_global_state (m_released_gil);
return true;
}
else if (is_named_call_p (callee_fndecl, "PyEval_RestoreThread",
call, 1))
{
if (0)
inform (input_location, "found call to %qs",
"PyEval_SaveThread");
if (global_state == m_released_gil)
sm_ctxt->set_global_state (m_start);
return true;
}
else if (global_state == m_released_gil)
{
/* Find PyObject * args of calls to fns with unknown bodies. */
if (!fndecl_has_gimple_body_p (callee_fndecl))
check_for_pyobject_in_call (sm_ctxt, node, call, callee_fndecl);
}
}
else if (global_state == m_released_gil)
check_for_pyobject_in_call (sm_ctxt, node, call, NULL);
}
else
if (global_state == m_released_gil)
{
/* Walk the stmt, finding uses of PyObject (or "subclasses"). */
cb_data d (*this, sm_ctxt, node, stmt);
walk_stmt_load_store_addr_ops (const_cast <gimple *> (stmt), &d,
check_for_pyobject,
check_for_pyobject,
check_for_pyobject);
}
return false;
}
/* Implementation of state_machine::on_condition vfunc for
gil_state_machine. */
void
gil_state_machine::on_condition (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
const supernode *node ATTRIBUTE_UNUSED,
const gimple *stmt ATTRIBUTE_UNUSED,
tree lhs ATTRIBUTE_UNUSED,
enum tree_code op ATTRIBUTE_UNUSED,
tree rhs ATTRIBUTE_UNUSED) const
{
// Empty
}
bool
gil_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
{
return true;
}
void
gil_state_machine::check_for_pyobject_usage_without_gil (sm_context *sm_ctxt,
const supernode *node,
const gimple *stmt,
tree op) const
{
tree type = TREE_TYPE (op);
if (type_based_on_pyobject_p (type))
{
sm_ctxt->warn (node, stmt, NULL_TREE,
new pyobject_usage_without_gil (*this, op));
sm_ctxt->set_global_state (m_stop);
}
}
/* Callback handler for the PLUGIN_ANALYZER_INIT event. */
static void
gil_analyzer_init_cb (void *gcc_data, void */*user_data*/)
{
ana::plugin_analyzer_init_iface *iface
= (ana::plugin_analyzer_init_iface *)gcc_data;
LOG_SCOPE (iface->get_logger ());
if (0)
inform (input_location, "got here: gil_analyzer_init_cb");
iface->register_state_machine (new gil_state_machine (iface->get_logger ()));
}
} // namespace ana
#endif /* #if ENABLE_ANALYZER */
int
plugin_init (struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version)
{
#if ENABLE_ANALYZER
const char *plugin_name = plugin_info->base_name;
if (0)
inform (input_location, "got here; %qs", plugin_name);
register_callback (plugin_info->base_name,
PLUGIN_ANALYZER_INIT,
ana::gil_analyzer_init_cb,
NULL); /* void *user_data */
#else
sorry_no_analyzer ();
#endif
return 0;
}

View file

@ -0,0 +1,90 @@
/* { dg-do compile } */
/* { dg-options "-fanalyzer" } */
#include "gil.h"
void test_1 (void)
{
Py_BEGIN_ALLOW_THREADS
Py_END_ALLOW_THREADS
}
void test_2 (PyObject *obj)
{
Py_BEGIN_ALLOW_THREADS /* { dg-message "releasing the GIL here" } */
Py_INCREF (obj); /* { dg-warning "use of PyObject '\\*\\(obj\\)' without the GIL" } */
Py_DECREF (obj);
Py_END_ALLOW_THREADS
}
void test_3 (PyObject *obj)
{
Py_BEGIN_ALLOW_THREADS /* { dg-message "releasing the GIL here" } */
Py_BEGIN_ALLOW_THREADS /* { dg-warning "nested usage of 'Py_BEGIN_ALLOW_THREADS'" } */
Py_END_ALLOW_THREADS
Py_END_ALLOW_THREADS
}
void test_4 (PyObject *obj)
{
/* These aren't nested, so should be OK. */
Py_BEGIN_ALLOW_THREADS
Py_END_ALLOW_THREADS
Py_BEGIN_ALLOW_THREADS
Py_END_ALLOW_THREADS
}
/* Interprocedural example of erroneously nested usage. */
static void __attribute__((noinline))
called_by_test_5 (void)
{
Py_BEGIN_ALLOW_THREADS /* { dg-warning "nested usage of 'Py_BEGIN_ALLOW_THREADS'" } */
Py_END_ALLOW_THREADS
}
void test_5 (PyObject *obj)
{
Py_BEGIN_ALLOW_THREADS /* { dg-message "releasing the GIL here" } */
called_by_test_5 ();
Py_END_ALLOW_THREADS
}
/* Interprocedural example of bogusly using a PyObject outside of GIL. */
static void __attribute__((noinline))
called_by_test_6 (PyObject *obj)
{
Py_INCREF (obj); /* { dg-warning "use of PyObject '\\*\\(obj\\)' without the GIL" } */
Py_DECREF (obj);
}
void test_6 (PyObject *obj)
{
Py_BEGIN_ALLOW_THREADS /* { dg-message "releasing the GIL here" } */
called_by_test_6 (obj);
Py_END_ALLOW_THREADS
}
extern void called_by_test_7 (PyObject *obj);
void test_7 (PyObject *obj)
{
Py_BEGIN_ALLOW_THREADS /* { dg-message "releasing the GIL here" } */
called_by_test_7 (obj); /* { dg-warning "use of PyObject as argument 1 of 'called_by_test_7' without the GIL" } */
Py_END_ALLOW_THREADS
}
typedef void (*callback_t) (PyObject *);
void test_8 (PyObject *obj, callback_t cb)
{
Py_BEGIN_ALLOW_THREADS /* { dg-message "releasing the GIL here" } */
cb (obj); /* { dg-warning "use of PyObject as argument 1 of call without the GIL" } */
Py_END_ALLOW_THREADS
}

View file

@ -0,0 +1,32 @@
/* Adapted from CPython 3.8's ceval.h. */
typedef struct PyThreadState PyThreadState;
extern PyThreadState * PyEval_SaveThread(void);
extern void PyEval_RestoreThread(PyThreadState *);
#define Py_BEGIN_ALLOW_THREADS { \
PyThreadState *_save; \
_save = PyEval_SaveThread();
#define Py_BLOCK_THREADS PyEval_RestoreThread(_save);
#define Py_UNBLOCK_THREADS _save = PyEval_SaveThread();
#define Py_END_ALLOW_THREADS PyEval_RestoreThread(_save); \
}
/* Adapted/hacked up from CPython 3.8's object.h. */
typedef struct _object {
int ob_refcnt;
} PyObject;
#define _PyObject_CAST(op) ((PyObject*)(op))
extern void _Py_Dealloc(PyObject *);
#define _Py_INCREF(OP) do { (OP)->ob_refcnt++; } while (0);
#define _Py_DECREF(OP) do { \
if (--(OP)->ob_refcnt == 0) { \
_Py_Dealloc(OP); \
} \
} while (0)
#define Py_INCREF(op) _Py_INCREF(_PyObject_CAST(op))
#define Py_DECREF(op) _Py_DECREF(_PyObject_CAST(op))

View file

@ -118,6 +118,8 @@ set plugin_test_list [list \
{ dump_plugin.c \
dump-1.c \
dump-2.c } \
{ analyzer_gil_plugin.c \
gil-1.c } \
]
foreach plugin_test $plugin_test_list {