]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-85283: Add PySys_AuditTuple() function (#108965)
authorVictor Stinner <vstinner@python.org>
Thu, 5 Oct 2023 21:59:35 +0000 (23:59 +0200)
committerGitHub <noreply@github.com>
Thu, 5 Oct 2023 21:59:35 +0000 (21:59 +0000)
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().

Doc/c-api/sys.rst
Doc/whatsnew/3.13.rst
Include/cpython/sysmodule.h
Lib/test/test_embed.py
Misc/NEWS.d/next/C API/2023-09-06-00-14-49.gh-issue-85283.GKY0Cc.rst [new file with mode: 0644]
Programs/_testembed.c
Python/sysmodule.c

index aed625c5f6cdaefb701ab7a7f3deb27987035063..e3c54b075114ff7b53de3553d20d6404c28ab641 100644 (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
    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
@@ -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.
index 7a62963203e16488dae4ee970b28be75f6ec79e0..d5987ae31ce68df4c827dc9bc37f17062fd291d1 100644 (file)
@@ -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
 ----------------------
 
index e028fb7ecd52307d06160ae547d0295962b40914..36c4f89432067bf35171833c96cefd4f19da14a1 100644 (file)
@@ -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);
index dc476ef83c2519f001ea4bd06afa30779b18a704..46c9b03c4178e9ce56032d46692da5012d3acfce 100644 (file)
@@ -1716,6 +1716,9 @@ class AuditingTests(EmbeddingTestsMixin, unittest.TestCase):
     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")
 
diff --git a/Misc/NEWS.d/next/C API/2023-09-06-00-14-49.gh-issue-85283.GKY0Cc.rst b/Misc/NEWS.d/next/C API/2023-09-06-00-14-49.gh-issue-85283.GKY0Cc.rst
new file mode 100644 (file)
index 0000000..811551c
--- /dev/null
@@ -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.
index bc991020d0fa77e566edfcd9afc01bf19656c6c1..e66c51818227c46b70d5f8ac4f48b62769f25dfb 100644 (file)
@@ -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;
 }
 
@@ -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)
@@ -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},
index b00301765e1890808e579b132156d487aadbd70e..a7ce07d28ae7df5dd3642d830ab95ce24adeb4ab 100644 (file)
@@ -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) {
@@ -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.
@@ -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;
     }