8000 gh-85283: Add PySys_AuditTuple() function · python/cpython@528ac80 · GitHub
[go: up one dir, main page]

Skip to content

Commit 528ac80

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 2cd170d commit 528ac80

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
@@ -185,13 +185,9 @@ should_audit(PyInterpreterState *interp)
185185

186186

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

196192
if (!ts) {
197193
/* Audit hooks cannot be called with a NULL thread state */
@@ -202,14 +198,19 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
202198
the current Python thread state. */
203199
assert(ts == _PyThreadState_GET());
204200

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

211213
PyObject *eventName = NULL;
212-
PyObject *eventArgs = NULL;
213214
PyObject *hooks = NULL;
214215
PyObject *hook = NULL;
215216
int res = -1;
@@ -219,21 +220,6 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
219220

220221
PyObject *exc = _PyErr_GetRaisedException(ts);
221222

222-
/* Initialize event args now */
223-
if (argFormat && argFormat[0]) {
224-
eventArgs = Py_VaBuildValue(argFormat, vargs);
225-
if (eventArgs && !PyTuple_Check(eventArgs)) {
226-
PyObject *argTuple = PyTuple_Pack(1, eventArgs);
227-
Py_SETREF(eventArgs, argTuple);
228-
}
229-
}
230-
else {
231-
eventArgs = PyTuple_New(0);
232-
}
233-
if (!eventArgs) {
234-
goto exit;
235-
}
236-
237223
/* Call global hooks
238224
*
239225
* We don't worry about any races on hooks getting added,
@@ -300,7 +286,6 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
300286
Py_XDECREF(hook);
301287
Py_XDECREF(hooks);
302288
Py_XDECREF(eventName);
303-
Py_XDECREF(eventArgs);
304289

305290
if (!res) {
306291
_PyErr_SetRaisedException(ts, exc);
@@ -313,28 +298,85 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
313298
return res;
314299
}
315300

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

327342
int
328-
PySys_Audit(const char *event, const char *argFormat, ...)
343+
PySys_Audit(const char *event, const char *format, ...)
329344
{
330345
PyThreadState *tstate = _PyThreadState_GET();
331346
va_list vargs;
332-
va_start(vargs, argFormat);
333-
int res = sys_audit_tstate(tstate, event, argFormat, vargs);
347+
va_start(vargs, format);
348+
int res = sys_audit_vargs(tstate, event, format, vargs);
334349
va_end(vargs);
335350
return res;
336351
}
337352

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

0 commit comments

Comments
 (0)
0