gh-85283: Add PySys_AuditTuple() function (#108965)

sys.audit() now has assertions to check that the event argument is
not NULL and that the format argument does not use the "N" format.

Add tests on PySys_AuditTuple().
This commit is contained in:
Victor Stinner 2023-10-05 23:59:35 +02:00 committed by GitHub
parent aaf297c048
commit bb057b3370
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 109 additions and 9 deletions

View File

@ -291,19 +291,24 @@ accessible to C code. They all work with the current interpreter thread's
Raise an auditing event with any active hooks. Return zero for success Raise an auditing event with any active hooks. Return zero for success
and non-zero with an exception set on failure. and non-zero with an exception set on failure.
The *event* string argument must not be *NULL*.
If any hooks have been added, *format* and other arguments will be used If any hooks have been added, *format* and other arguments will be used
to construct a tuple to pass. Apart from ``N``, the same format characters to construct a tuple to pass. Apart from ``N``, the same format characters
as used in :c:func:`Py_BuildValue` are available. If the built value is not as used in :c:func:`Py_BuildValue` are available. If the built value is not
a tuple, it will be added into a single-element tuple. (The ``N`` format a tuple, it will be added into a single-element tuple.
option consumes a reference, but since there is no way to know whether
arguments to this function will be consumed, using it may cause reference The ``N`` format option must not be used. It consumes a reference, but since
leaks.) there is no way to know whether arguments to this function will be consumed,
using it may cause reference leaks.
Note that ``#`` format characters should always be treated as Note that ``#`` format characters should always be treated as
:c:type:`Py_ssize_t`, regardless of whether ``PY_SSIZE_T_CLEAN`` was defined. :c:type:`Py_ssize_t`, regardless of whether ``PY_SSIZE_T_CLEAN`` was defined.
:func:`sys.audit` performs the same function from Python code. :func:`sys.audit` performs the same function from Python code.
See also :c:func:`PySys_AuditTuple`.
.. versionadded:: 3.8 .. versionadded:: 3.8
.. versionchanged:: 3.8.2 .. versionchanged:: 3.8.2
@ -312,6 +317,14 @@ accessible to C code. They all work with the current interpreter thread's
unavoidable deprecation warning was raised. unavoidable deprecation warning was raised.
.. c:function:: int PySys_AuditTuple(const char *event, PyObject *args)
Similar to :c:func:`PySys_Audit`, but pass arguments as a Python object.
*args* must be a :class:`tuple`. To pass no arguments, *args* can be *NULL*.
.. versionadded:: 3.13
.. c:function:: int PySys_AddAuditHook(Py_AuditHookFunction hook, void *userData) .. c:function:: int PySys_AddAuditHook(Py_AuditHookFunction hook, void *userData)
Append the callable *hook* to the list of active auditing hooks. Append the callable *hook* to the list of active auditing hooks.

View File

@ -1013,6 +1013,10 @@ New Features
``_PyThreadState_UncheckedGet()``. ``_PyThreadState_UncheckedGet()``.
(Contributed by Victor Stinner in :gh:`108867`.) (Contributed by Victor Stinner in :gh:`108867`.)
* Add :c:func:`PySys_AuditTuple` function: similar to :c:func:`PySys_Audit`,
but pass event arguments as a Python :class:`tuple` object.
(Contributed by Victor Stinner in :gh:`85283`.)
Porting to Python 3.13 Porting to Python 3.13
---------------------- ----------------------

View File

@ -6,6 +6,10 @@ typedef int(*Py_AuditHookFunction)(const char *, PyObject *, void *);
PyAPI_FUNC(int) PySys_Audit( PyAPI_FUNC(int) PySys_Audit(
const char *event, const char *event,
const char *argFormat, const char *format,
...); ...);
PyAPI_FUNC(int) PySys_AddAuditHook(Py_AuditHookFunction, void*); PyAPI_FUNC(int) PySys_AddAuditHook(Py_AuditHookFunction, void*);
PyAPI_FUNC(int) PySys_AuditTuple(
const char *event,
PyObject *args);

View File

@ -1716,6 +1716,9 @@ class AuditingTests(EmbeddingTestsMixin, unittest.TestCase):
def test_audit(self): def test_audit(self):
self.run_embedded_interpreter("test_audit") self.run_embedded_interpreter("test_audit")
def test_audit_tuple(self):
self.run_embedded_interpreter("test_audit_tuple")
def test_audit_subinterpreter(self): def test_audit_subinterpreter(self):
self.run_embedded_interpreter("test_audit_subinterpreter") self.run_embedded_interpreter("test_audit_subinterpreter")

View File

@ -0,0 +1,3 @@
Add :c:func:`PySys_AuditTuple` function: similar to :c:func:`PySys_Audit`,
but pass event arguments as a Python :class:`tuple` object. Patch by Victor
Stinner.

View File

@ -1278,11 +1278,16 @@ static int _test_audit(Py_ssize_t setValue)
printf("Set event failed"); printf("Set event failed");
return 4; return 4;
} }
if (PyErr_Occurred()) {
printf("Exception raised");
return 5;
}
if (sawSet != 42) { if (sawSet != 42) {
printf("Failed to see *userData change\n"); printf("Failed to see *userData change\n");
return 5; return 6;
} }
return 0; return 0;
} }
@ -1296,6 +1301,57 @@ static int test_audit(void)
return result; return result;
} }
static int test_audit_tuple(void)
{
#define ASSERT(TEST, EXITCODE) \
if (!(TEST)) { \
printf("ERROR test failed at %s:%i\n", __FILE__, __LINE__); \
return (EXITCODE); \
}
Py_ssize_t sawSet = 0;
// we need at least one hook, otherwise code checking for
// PySys_AuditTuple() is skipped.
PySys_AddAuditHook(_audit_hook, &sawSet);
_testembed_Py_InitializeFromConfig();
ASSERT(!PyErr_Occurred(), 0);
// pass Python tuple object
PyObject *tuple = Py_BuildValue("(i)", 444);
if (tuple == NULL) {
goto error;
}
ASSERT(PySys_AuditTuple("_testembed.set", tuple) == 0, 10);
ASSERT(!PyErr_Occurred(), 11);
ASSERT(sawSet == 444, 12);
Py_DECREF(tuple);
// pass Python int object
PyObject *int_arg = PyLong_FromLong(555);
if (int_arg == NULL) {
goto error;
}
ASSERT(PySys_AuditTuple("_testembed.set", int_arg) == -1, 20);
ASSERT(PyErr_ExceptionMatches(PyExc_TypeError), 21);
PyErr_Clear();
Py_DECREF(int_arg);
// NULL is accepted and means "no arguments"
ASSERT(PySys_AuditTuple("_testembed.test_audit_tuple", NULL) == 0, 30);
ASSERT(!PyErr_Occurred(), 31);
Py_Finalize();
return 0;
error:
PyErr_Print();
return 1;
#undef ASSERT
}
static volatile int _audit_subinterpreter_interpreter_count = 0; static volatile int _audit_subinterpreter_interpreter_count = 0;
static int _audit_subinterpreter_hook(const char *event, PyObject *args, void *userdata) static int _audit_subinterpreter_hook(const char *event, PyObject *args, void *userdata)
@ -2140,6 +2196,7 @@ static struct TestCase TestCases[] = {
// Audit // Audit
{"test_open_code_hook", test_open_code_hook}, {"test_open_code_hook", test_open_code_hook},
{"test_audit", test_audit}, {"test_audit", test_audit},
{"test_audit_tuple", test_audit_tuple},
{"test_audit_subinterpreter", test_audit_subinterpreter}, {"test_audit_subinterpreter", test_audit_subinterpreter},
{"test_audit_run_command", test_audit_run_command}, {"test_audit_run_command", test_audit_run_command},
{"test_audit_run_file", test_audit_run_file}, {"test_audit_run_file", test_audit_run_file},

View File

@ -191,9 +191,7 @@ static int
sys_audit_tstate(PyThreadState *ts, const char *event, sys_audit_tstate(PyThreadState *ts, const char *event,
const char *argFormat, va_list vargs) const char *argFormat, va_list vargs)
{ {
/* N format is inappropriate, because you do not know assert(event != NULL);
whether the reference is consumed by the call.
Assert rather than exception for perf reasons */
assert(!argFormat || !strchr(argFormat, 'N')); assert(!argFormat || !strchr(argFormat, 'N'));
if (!ts) { if (!ts) {
@ -338,6 +336,21 @@ PySys_Audit(const char *event, const char *argFormat, ...)
return res; return res;
} }
int
PySys_AuditTuple(const char *event, PyObject *args)
{
if (args == NULL) {
return PySys_Audit(event, NULL);
}
if (!PyTuple_Check(args)) {
PyErr_Format(PyExc_TypeError, "args must be tuple, got %s",
Py_TYPE(args)->tp_name);
return -1;
}
return PySys_Audit(event, "O", args);
}
/* We expose this function primarily for our own cleanup during /* We expose this function primarily for our own cleanup during
* finalization. In general, it should not need to be called, * finalization. In general, it should not need to be called,
* and as such the function is not exported. * and as such the function is not exported.
@ -509,6 +522,9 @@ sys_audit(PyObject *self, PyObject *const *args, Py_ssize_t argc)
return NULL; return NULL;
} }
assert(args[0] != NULL);
assert(PyUnicode_Check(args[0]));
if (!should_audit(tstate->interp)) { if (!should_audit(tstate->interp)) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }