Skip to content

Commit 579731b

Browse files
committed
gh-85283: Add PySys_AuditTuple() function
PySys_Audit() now raises an exception if the event argument is NULL or if the "N" format is used in the *format* argument. Add tests on PySys_AuditTuple() and on new PySys_Audit() error code paths.
1 parent 0a31ff0 commit 579731b

File tree

7 files changed

+107
-5
lines changed

7 files changed

+107
-5
lines changed

Doc/c-api/sys.rst

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,19 +291,21 @@ accessible to C code. They all work with the current interpreter thread's
291291
Raise an auditing event with any active hooks. Return zero for success
292292
and non-zero with an exception set on failure.
293293
294+
The *event* string argument must not be *NULL*.
295+
294296
If any hooks have been added, *format* and other arguments will be used
295297
to construct a tuple to pass. Apart from ``N``, the same format characters
296298
as used in :c:func:`Py_BuildValue` are available. If the built value is not
297-
a tuple, it will be added into a single-element tuple. (The ``N`` format
298-
option consumes a reference, but since there is no way to know whether
299-
arguments to this function will be consumed, using it may cause reference
300-
leaks.)
299+
a tuple, it will be added into a single-element tuple. The ``N`` format
300+
must not be used in *format*.
301301
302302
Note that ``#`` format characters should always be treated as
303303
:c:type:`Py_ssize_t`, regardless of whether ``PY_SSIZE_T_CLEAN`` was defined.
304304
305305
:func:`sys.audit` performs the same function from Python code.
306306
307+
See also :c:func:`PySys_AuditTuple`.
308+
307309
.. versionadded:: 3.8
308310
309311
.. versionchanged:: 3.8.2
@@ -312,6 +314,14 @@ accessible to C code. They all work with the current interpreter thread's
312314
unavoidable deprecation warning was raised.
313315
314316
317+
.. c:function:: int PySys_AuditTuple(const char *event, PyObject *args)
318+
319+
Similar to :c:func:`PySys_Audit`, but pass arguments as a Python object. To
320+
pass no arguments, *args* can be *NULL*.
321+
322+
.. versionadded:: 3.13
323+
324+
315325
.. c:function:: int PySys_AddAuditHook(Py_AuditHookFunction hook, void *userData)
316326
317327
Append the callable *hook* to the list of active auditing hooks.

Doc/whatsnew/3.13.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,10 @@ New Features
975975
references) now supports the :ref:`Limited API <limited-c-api>`.
976976
(Contributed by Victor Stinner in :gh:`108634`.)
977977

978+
* Add :c:func:`PySys_AuditTuple` function: similar to :c:func:`PySys_Audit`,
979+
but pass event arguments as a Python :class:`tuple` object.
980+
(Contributed by Victor Stinner in :gh:`85283`.)
981+
978982
Porting to Python 3.13
979983
----------------------
980984

Include/cpython/sysmodule.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ typedef int(*Py_AuditHookFunction)(const char *, PyObject *, void *);
66

77
PyAPI_FUNC(int) PySys_Audit(
88
const char *event,
9-
const char *argFormat,
9+
const char *format,
1010
...);
1111
PyAPI_FUNC(int) PySys_AddAuditHook(Py_AuditHookFunction, void*);
12+
13+
PyAPI_FUNC(int) PySys_AuditTuple(
14+
const char *event,
15+
PyObject *args);

Lib/test/test_embed.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1712,6 +1712,9 @@ def test_open_code_hook(self):
17121712
def test_audit(self):
17131713
self.run_embedded_interpreter("test_audit")
17141714

1715+
def test_audit_tuple(self):
1716+
self.run_embedded_interpreter("test_audit_tuple")
1717+
17151718
def test_audit_subinterpreter(self):
17161719
self.run_embedded_interpreter("test_audit_subinterpreter")
17171720

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :c:func:`PySys_AuditTuple` function: similar to :c:func:`PySys_Audit`,
2+
but pass event arguments as a Python :class:`tuple` object. Patch by Victor
3+
Stinner.

Programs/_testembed.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,7 +1283,30 @@ static int _test_audit(Py_ssize_t setValue)
12831283
printf("Failed to see *userData change\n");
12841284
return 5;
12851285
}
1286+
1287+
// event argument must not be NULL (format can be NULL)
1288+
assert(!PyErr_Occurred());
1289+
assert(PySys_Audit(NULL, NULL) == -1);
1290+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
1291+
PyErr_Clear();
1292+
1293+
// 'N' must not be used in the format
1294+
PyObject *arg = PyUnicode_FromString("arg");
1295+
if (arg == NULL) {
1296+
goto error;
1297+
}
1298+
Py_ssize_t refcnt = Py_REFCNT(arg);
1299+
assert(PySys_Audit("_testembed.raise", "N", arg) == -1);
1300+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
1301+
PyErr_Clear();
1302+
assert(Py_REFCNT(arg) == refcnt);
1303+
Py_DECREF(arg);
1304+
12861305
return 0;
1306+
1307+
error:
1308+
PyErr_Print();
1309+
return 1;
12871310
}
12881311

12891312
static int test_audit(void)
@@ -1296,6 +1319,49 @@ static int test_audit(void)
12961319
return result;
12971320
}
12981321

1322+
static int test_audit_tuple(void)
1323+
{
1324+
Py_ssize_t sawSet = 0;
1325+
1326+
// we need at least one hook, otherwise code checking for
1327+
// PySys_AuditTuple() is skipped.
1328+
PySys_AddAuditHook(_audit_hook, &sawSet);
1329+
_testembed_Py_InitializeFromConfig();
1330+
1331+
assert(!PyErr_Occurred());
1332+
1333+
// pass Python tuple object
1334+
PyObject *tuple = Py_BuildValue("(i)", 444);
1335+
if (tuple == NULL) {
1336+
goto error;
1337+
}
1338+
assert(PySys_AuditTuple("_testembed.set", tuple) == 0);
1339+
assert(!PyErr_Occurred());
1340+
assert(sawSet == 444);
1341+
Py_DECREF(tuple);
1342+
1343+
// pass Python int object
1344+
PyObject *int_arg = PyLong_FromLong(555);
1345+
if (int_arg == NULL) {
1346+
goto error;
1347+
}
1348+
assert(PySys_AuditTuple("_testembed.set", int_arg) == 0);
1349+
assert(!PyErr_Occurred());
1350+
assert(sawSet == 555);
1351+
Py_DECREF(int_arg);
1352+
1353+
// NULL is accepted and means "no arguments"
1354+
assert(PySys_AuditTuple("_testembed.test_audit_tuple", NULL) == 0);
1355+
assert(!PyErr_Occurred());
1356+
1357+
Py_Finalize();
1358+
return 0;
1359+
1360+
error:
1361+
PyErr_Print();
1362+
return 1;
1363+
}
1364+
12991365
static volatile int _audit_subinterpreter_interpreter_count = 0;
13001366

13011367
static int _audit_subinterpreter_hook(const char *event, PyObject *args, void *userdata)
@@ -2140,6 +2206,7 @@ static struct TestCase TestCases[] = {
21402206
// Audit
21412207
{"test_open_code_hook", test_open_code_hook},
21422208
{"test_audit", test_audit},
2209+
{"test_audit_tuple", test_audit_tuple},
21432210
{"test_audit_subinterpreter", test_audit_subinterpreter},
21442211
{"test_audit_run_command", test_audit_run_command},
21452212
{"test_audit_run_file", test_audit_run_file},

Python/sysmodule.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,17 @@ PySys_Audit(const char *event, const char *argFormat, ...)
336336
return res;
337337
}
338338

339+
int
340+
PySys_AuditTuple(const char *event, PyObject *args)
341+
{
342+
if (args) {
343+
return PySys_Audit(event, "O", args);
344+
}
345+
else {
346+
return PySys_Audit(event, NULL);
347+
}
348+
}
349+
339350
/* We expose this function primarily for our own cleanup during
340351
* finalization. In general, it should not need to be called,
341352
* and as such the function is not exported.

0 commit comments

Comments
 (0)