Skip to content

Commit 2386731

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 2c4c26c commit 2386731

File tree

7 files changed

+162
-34
lines changed

7 files changed

+162
-34
lines changed

Doc/c-api/sys.rst

Lines changed: 15 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,15 @@ 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 event pass arguments as a Python
320+
:class:`tuple` object. *args* can be *NULL* to pass no arguments: it is
321+
treated the same as passing an empty tuple.
322+
323+
.. versionadded:: 3.13
324+
325+
315326
.. c:function:: int PySys_AddAuditHook(Py_AuditHookFunction hook, void *userData)
316327
317328
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
@@ -924,6 +924,10 @@ New Features
924924
references) now supports the :ref:`Limited API <limited-c-api>`.
925925
(Contributed by Victor Stinner in :gh:`108634`.)
926926

927+
* Add :c:func:`PySys_AuditTuple` function: similar to :c:func:`PySys_Audit`,
928+
but pass event arguments as a Python :class:`tuple` object.
929+
(Contributed by Victor Stinner in :gh:`85283`.)
930+
927931
Porting to Python 3.13
928932
----------------------
929933

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
@@ -1703,6 +1703,9 @@ def test_open_code_hook(self):
17031703
def test_audit(self):
17041704
self.run_embedded_interpreter("test_audit")
17051705

1706+
def test_audit_tuple(self):
1707+
self.run_embedded_interpreter("test_audit_tuple")
1708+
17061709
def test_audit_subinterpreter(self):
17071710
self.run_embedded_interpreter("test_audit_subinterpreter")
17081711

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: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,7 +1276,30 @@ static int _test_audit(Py_ssize_t setValue)
12761276
printf("Failed to see *userData change\n");
12771277
return 5;
12781278
}
1279+
1280+
// event argument must not be NULL (format can be NULL)
1281+
assert(!PyErr_Occurred());
1282+
assert(PySys_Audit(NULL, NULL) == -1);
1283+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
1284+
PyErr_Clear();
1285+
1286+
// 'N' must not be used in the format
1287+
PyObject *arg = PyUnicode_FromString("arg");
1288+
if (arg == NULL) {
1289+
goto error;
1290+
}
1291+
Py_ssize_t refcnt = Py_REFCNT(arg);
1292+
assert(PySys_Audit("_testembed.raise", "N", arg) == -1);
1293+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
1294+
PyErr_Clear();
1295+
assert(Py_REFCNT(arg) == refcnt);
1296+
Py_DECREF(arg);
1297+
12791298
return 0;
1299+
1300+
error:
1301+
PyErr_Print();
1302+
return 1;
12801303
}
12811304

12821305
static int test_audit(void)
@@ -1289,6 +1312,43 @@ static int test_audit(void)
12891312
return result;
12901313
}
12911314

1315+
static int test_audit_tuple(void)
1316+
{
1317+
_testembed_Py_InitializeFromConfig();
1318+
1319+
assert(!PyErr_Occurred());
1320+
1321+
// pass tuple
1322+
PyObject *tuple = Py_BuildValue("ii", 3, 5);
1323+
if (tuple == NULL) {
1324+
goto error;
1325+
}
1326+
assert(PySys_AuditTuple("_testembed.test_audit_tuple", tuple) == 0);
1327+
assert(!PyErr_Occurred());
1328+
Py_DECREF(tuple);
1329+
1330+
// NULL is accepted and means "no arguments"
1331+
assert(PySys_AuditTuple("_testembed.test_audit_tuple", NULL) == 0);
1332+
assert(!PyErr_Occurred());
1333+
1334+
// wrong argument type
1335+
PyObject *not_tuple = PyLong_FromLong(123);
1336+
if (not_tuple == NULL) {
1337+
goto error;
1338+
}
1339+
assert(PySys_AuditTuple("_testembed.test_audit_tuple", not_tuple) == -1);
1340+
assert(PyErr_ExceptionMatches(PyExc_TypeError));
1341+
PyErr_Clear();
1342+
Py_DECREF(not_tuple);
1343+
1344+
Py_Finalize();
1345+
return 0;
1346+
1347+
error:
1348+
PyErr_Print();
1349+
return 1;
1350+
}
1351+
12921352
static volatile int _audit_subinterpreter_interpreter_count = 0;
12931353

12941354
static int _audit_subinterpreter_hook(const char *event, PyObject *args, void *userdata)
@@ -2133,6 +2193,7 @@ static struct TestCase TestCases[] = {
21332193
// Audit
21342194
{"test_open_code_hook", test_open_code_hook},
21352195
{"test_audit", test_audit},
2196+
{"test_audit_tuple", test_audit_tuple},
21362197
{"test_audit_subinterpreter", test_audit_subinterpreter},
21372198
{"test_audit_run_command", test_audit_run_command},
21382199
{"test_audit_run_file", test_audit_run_file},

Python/sysmodule.c

Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,9 @@ should_audit(PyInterpreterState *interp)
183183

184184

185185
static int
186-
sys_audit_tstate(PyThreadState *ts, const char *event,
187-
const char *argFormat, va_list vargs)
186+
sys_audit_tuple(PyThreadState *ts, const char *event, PyObject *eventArgs)
188187
{
189-
/* N format is inappropriate, because you do not know
190-
whether the reference is consumed by the call.
191-
Assert rather than exception for perf reasons */
192-
assert(!argFormat || !strchr(argFormat, 'N'));
188+
assert(PyTuple_Check(eventArgs));
193189

194190
if (!ts) {
195191
/* Audit hooks cannot be called with a NULL thread state */
@@ -200,14 +196,19 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
200196
the current Python thread state. */
201197
assert(ts == _PyThreadState_GET());
202198

199+
if (event == NULL) {
200+
_PyErr_SetString(ts, PyExc_ValueError,
201+
"event argument must not be NULL");
202+
return -1;
203+
}
204+
203205
/* Early exit when no hooks are registered */
204206
PyInterpreterState *is = ts->interp;
205207
if (!should_audit(is)) {
206208
return 0;
207209
}
208210

209211
PyObject *eventName = NULL;
210-
PyObject *eventArgs = NULL;
211212
PyObject *hooks = NULL;
212213
PyObject *hook = NULL;
213214
int res = -1;
@@ -217,21 +218,6 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
217218

218219
PyObject *exc = _PyErr_GetRaisedException(ts);
219220

220-
/* Initialize event args now */
221-
if (argFormat && argFormat[0]) {
222-
eventArgs = Py_VaBuildValue(argFormat, vargs);
223-
if (eventArgs && !PyTuple_Check(eventArgs)) {
224-
PyObject *argTuple = PyTuple_Pack(1, eventArgs);
225-
Py_SETREF(eventArgs, argTuple);
226-
}
227-
}
228-
else {
229-
eventArgs = PyTuple_New(0);
230-
}
231-
if (!eventArgs) {
232-
goto exit;
233-
}
234-
235221
/* Call global hooks
236222
*
237223
* We don't worry about any races on hooks getting added,
@@ -298,7 +284,6 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
298284
Py_XDECREF(hook);
299285
Py_XDECREF(hooks);
300286
Py_XDECREF(eventName);
301-
Py_XDECREF(eventArgs);
302287

303288
if (!res) {
304289
_PyErr_SetRaisedException(ts, exc);
@@ -311,28 +296,85 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
311296
return res;
312297
}
313298

299+
static int
300+
sys_audit_vargs(PyThreadState *tstate, const char *event,
301+
const char *format, va_list vargs)
302+
{
303+
if (format && strchr(format, 'N')) {
304+
_PyErr_SetString(tstate, PyExc_ValueError,
305+
"format argument must not use the 'N' format");
306+
return -1;
307+
}
308+
309+
PyObject *args;
310+
if (format && format[0]) {
311+
args = Py_VaBuildValue(format, vargs);
312+
if (args && !PyTuple_Check(args)) {
313+
PyObject *argTuple = PyTuple_Pack(1, args);
314+
Py_SETREF(args, argTuple);
315+
}
316+
}
317+
else {
318+
args = PyTuple_New(0);
319+
}
320+
if (!args) {
321+
return -1;
322+
}
323+
324+
int res = sys_audit_tuple(tstate, event, args);
325+
Py_DECREF(args);
326+
return res;
327+
}
328+
314329
int
315330
_PySys_Audit(PyThreadState *tstate, const char *event,
316-
const char *argFormat, ...)
331+
const char *format, ...)
317332
{
318333
va_list vargs;
319-
va_start(vargs, argFormat);
320-
int res = sys_audit_tstate(tstate, event, argFormat, vargs);
334+
va_start(vargs, format);
335+
int res = sys_audit_vargs(tstate, event, format, vargs);
321336
va_end(vargs);
322337
return res;
323338
}
324339

325340
int
326-
PySys_Audit(const char *event, const char *argFormat, ...)
341+
PySys_Audit(const char *event, const char *format, ...)
327342
{
328343
PyThreadState *tstate = _PyThreadState_GET();
329344
va_list vargs;
330-
va_start(vargs, argFormat);
331-
int res = sys_audit_tstate(tstate, event, argFormat, vargs);
345+
va_start(vargs, format);
346+
int res = sys_audit_vargs(tstate, event, format, vargs);
332347
va_end(vargs);
333348
return res;
334349
}
335350

351+
int
352+
PySys_AuditTuple(const char *event, PyObject *args)
353+
{
354+
PyThreadState *tstate = _PyThreadState_GET();
355+
int delete_args = 0;
356+
357+
if (args == NULL) {
358+
delete_args = 1;
359+
args = PyTuple_New(0);
360+
if (args == NULL) {
361+
return -1;
362+
}
363+
}
364+
else if (!PyTuple_Check(args)) {
365+
_PyErr_Format(tstate, PyExc_TypeError,
366+
"expected tuple, got %s", Py_TYPE(args)->tp_name);
367+
return -1;
368+
}
369+
370+
int res = sys_audit_tuple(tstate, event, args);
371+
372+
if (delete_args) {
373+
Py_DECREF(args);
374+
}
375+
return res;
376+
}
377+
336378
/* We expose this function primarily for our own cleanup during
337379
* finalization. In general, it should not need to be called,
338380
* and as such the function is not exported.

0 commit comments

Comments
 (0)