Skip to content

Commit

Permalink
pythongh-85283: Add PySys_AuditTuple() function (python#108965)
Browse files Browse the repository at this point in the history
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().
  • Loading branch information
vstinner authored and Glyphack committed Jan 27, 2024
1 parent 39028e6 commit 76f9909
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 9 deletions.
21 changes: 17 additions & 4 deletions Doc/c-api/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
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
a tuple, it will be added into a single-element tuple. (The ``N`` format
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
leaks.)
a tuple, it will be added into a single-element tuple.
The ``N`` format option must not be used. It consumes a reference, but since
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
:c:type:`Py_ssize_t`, regardless of whether ``PY_SSIZE_T_CLEAN`` was defined.
:func:`sys.audit` performs the same function from Python code.
See also :c:func:`PySys_AuditTuple`.
.. versionadded:: 3.8
.. versionchanged:: 3.8.2
Expand All @@ -312,6 +317,14 @@ accessible to C code. They all work with the current interpreter thread's
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)
Append the callable *hook* to the list of active auditing hooks.
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,10 @@ New Features
``_PyThreadState_UncheckedGet()``.
(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
----------------------

Expand Down
6 changes: 5 additions & 1 deletion Include/cpython/sysmodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ typedef int(*Py_AuditHookFunction)(const char *, PyObject *, void *);

PyAPI_FUNC(int) PySys_Audit(
const char *event,
const char *argFormat,
const char *format,
...);
PyAPI_FUNC(int) PySys_AddAuditHook(Py_AuditHookFunction, void*);

PyAPI_FUNC(int) PySys_AuditTuple(
const char *event,
PyObject *args);
3 changes: 3 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -1716,6 +1716,9 @@ def test_open_code_hook(self):
def test_audit(self):
self.run_embedded_interpreter("test_audit")

def test_audit_tuple(self):
self.run_embedded_interpreter("test_audit_tuple")

def test_audit_subinterpreter(self):
self.run_embedded_interpreter("test_audit_subinterpreter")

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
59 changes: 58 additions & 1 deletion Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -1278,11 +1278,16 @@ static int _test_audit(Py_ssize_t setValue)
printf("Set event failed");
return 4;
}
if (PyErr_Occurred()) {
printf("Exception raised");
return 5;
}

if (sawSet != 42) {
printf("Failed to see *userData change\n");
return 5;
return 6;
}

return 0;
}

Expand All @@ -1296,6 +1301,57 @@ static int test_audit(void)
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 int _audit_subinterpreter_hook(const char *event, PyObject *args, void *userdata)
Expand Down Expand Up @@ -2140,6 +2196,7 @@ static struct TestCase TestCases[] = {
// Audit
{"test_open_code_hook", test_open_code_hook},
{"test_audit", test_audit},
{"test_audit_tuple", test_audit_tuple},
{"test_audit_subinterpreter", test_audit_subinterpreter},
{"test_audit_run_command", test_audit_run_command},
{"test_audit_run_file", test_audit_run_file},
Expand Down
22 changes: 19 additions & 3 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,7 @@ static int
sys_audit_tstate(PyThreadState *ts, const char *event,
const char *argFormat, va_list vargs)
{
/* N format is inappropriate, because you do not know
whether the reference is consumed by the call.
Assert rather than exception for perf reasons */
assert(event != NULL);
assert(!argFormat || !strchr(argFormat, 'N'));

if (!ts) {
Expand Down Expand Up @@ -338,6 +336,21 @@ PySys_Audit(const char *event, const char *argFormat, ...)
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
* finalization. In general, it should not need to be called,
* and as such the function is not exported.
Expand Down Expand Up @@ -509,6 +522,9 @@ sys_audit(PyObject *self, PyObject *const *args, Py_ssize_t argc)
return NULL;
}

assert(args[0] != NULL);
assert(PyUnicode_Check(args[0]));

if (!should_audit(tstate->interp)) {
Py_RETURN_NONE;
}
Expand Down

0 comments on commit 76f9909

Please sign in to comment.