8000 bpo-41180: Replace marshal code.__new__ audit event with marshal.load… · python/cpython@a5764d3 · GitHub
[go: up one dir, main page]

Skip to content

Commit a5764d3

Browse files
authored
bpo-41180: Replace marshal code.__new__ audit event with marshal.load[s] and marshal.dumps (GH-26970)
1 parent 2df13e1 commit a5764d3

File tree

5 files changed

+75
-10
lines changed

5 files changed

+75
-10
lines changed

Doc/library/marshal.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ The module defines these functions:
6666
The *version* argument indicates the data format that ``dump`` should use
6767
(see below).
6868

69+
.. audit-event:: marshal.dumps value,version marshal.dump
70+
6971

7072
.. function:: load(file)
7173

@@ -74,11 +76,18 @@ The module defines these functions:
7476
format), raise :exc:`EOFError`, :exc:`ValueError` or :exc:`TypeError`. The
7577
file must be a readable :term:`binary file`.
7678

79+
.. audit-event:: marshal.load "" marshal.load
80+
7781
.. note::
7882

7983
If an object containing an unsupported type was marshalled with :func:`dump`,
8084
:func:`load` will substitute ``None`` for the unmarshallable type.
8185

86+
.. versionchanged:: 3.10
87+
88+
This call used to raise a ``code.__new__`` audit event for each code object. Now
89+
it raises a single ``marshal.load`` event for the entire load operation.
90+
8291

8392
.. function:: dumps(value[, version])
8493

@@ -89,13 +98,22 @@ The module defines these functions:
8998
The *version* argument indicates the data format that ``dumps`` should use
9099
(see below).
91100

101+
.. audit-event:: marshal.dumps value,version marshal.dump
102+
92103

93104
.. function:: loads(bytes)
94105

95106
Convert the :term:`bytes-like object` to a value. If no valid value is found, raise
96107
:exc:`EOFError`, :exc:`ValueError` or :exc:`TypeError`. Extra bytes in the
97108
input are ignored.
98109

110+
.. audit-event:: marshal.loads bytes marshal.load
111+
112+
.. versionchanged:: 3.10
113+
114+
This call used to raise a ``code.__new__`` audit event for each code object. Now
115+
it raises a single ``marshal.loads`` event for the entire load operation.
116+
99117

100118
In addition, the following constants are defined:
101119

Lib/test/audit-tests.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77

88
import contextlib
9+
import os
910
import sys
1011

1112

@@ -106,6 +107,32 @@ def test_block_add_hook_baseexception():
106107
pass
107108

108109

110+
def test_marshal():
111+
import marshal
112+
o = ("a", "b", "c", 1, 2, 3)
113+
payload = marshal.dumps(o)
114+
115+
with TestHook() as hook:
116+
assertEqual(o, marshal.loads(marshal.dumps(o)))
117+
118+
try:
119+
with open("test-marshal.bin", "wb") as f:
120+
marshal.dump(o, f)
121+
with open("test-marshal.bin", "rb") as f:
122+
assertEqual(o, marshal.load(f))
123+
finally:
124+
os.unlink("test-marshal.bin")
125+
126+
actual = [(a[0], a[1]) for e, a in hook.seen if e == "marshal.dumps"]
127+
assertSequenceEqual(actual, [(o, marshal.version)] * 2)
128+
129+
actual = [a[0] for e, a in hook.seen if e == "marshal.loads"]
130+
assertSequenceEqual(actual, [payload])
131+
132+
actual = [e for e, a in hook.seen if e == "marshal.load"]
133+
assertSequenceEqual(actual, ["marshal.load"])
134+
135+
109136
def test_pickle():
110137
import pickle
111138

Lib/test/test_audit.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ def test_block_add_hook(self):
5454
def test_block_add_hook_baseexception(self):
5555
self.do_test("test_block_add_hook_baseexception")
5656

57+
def test_marshal(self):
58+
import_helper.import_module("marshal")
59+
60+
self.do_test("test_marshal")
61+
5762
def test_pickle(self):
5863
import_helper.import_module("pickle")
5964

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Add auditing events to the :mod:`marshal` module, and stop raising
2+
``code.__init__`` events for every unmarshalled code object. Directly
3+
instantiated code objects will continue to raise an event, and audit event
4+
handlers should inspect or collect the raw marshal data. This reduces a
5+
significant performance overhead when loading from ``.pyc`` files.

Python/marshal.c

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -596,14 +596,18 @@ PyMarshal_WriteObjectToFile(PyObject *x, FILE *fp, int version)
596596
{
597597
char buf[BUFSIZ];
598598
WFILE wf;
599+
if (PySys_Audit("marshal.dumps", "Oi", x, version) < 0) {
600+
return; /* caller must check PyErr_Occurred() */
601+
}
599602
memset(&wf, 0, sizeof(wf));
600603
wf.fp = fp;
601604
wf.ptr = wf.buf = buf;
602605
wf.end = wf.ptr + sizeof(buf);
603606
wf.error = WFERR_OK;
604607
wf.version = version;
605-
if (w_init_refs(&wf, version))
606-
return; /* caller mush check PyErr_Occurred() */
608+
if (w_init_refs(&wf, version)) {
609+
return; /* caller must check PyErr_Occurred() */
610+
}
607611
w_object(x, &wf);
608612
w_clear_refs(&wf);
609613
w_flush(&wf);
@@ -1371,12 +1375,6 @@ r_object(RFILE *p)
13711375
if (linetable == NULL)
13721376
goto code_error;
13731377

1374-
if (PySys_Audit("code.__new__", "OOOiiiiii",
1375-
code, filename, name, argcount, posonlyargcount,
1376-
kwonlyargcount, nlocals, stacksize, flags) < 0) {
1377-
goto code_error;
1378-
}
1379-
13801378
v = (PyObject *) PyCode_NewWithPosOnlyArgs(
13811379
argcount, posonlyargcount, kwonlyargcount,
13821380
nlocals, stacksize, flags,
@@ -1435,6 +1433,15 @@ read_object(RFILE *p)
14351433
fprintf(stderr, "XXX readobject called with exception set\n");
14361434
return NULL;
14371435
}
1436+
if (p->ptr && p->end) {
1437+
if (PySys_Audit("marshal.loads", "y#", p->ptr, (Py_ssize_t)(p->end - p->ptr)) < 0) {
1438+
return NULL;
1439+
}
1440+
} else if (p->fp || p->readable) {
1441+
if (PySys_Audit("marshal.load", NULL) < 0) {
1442+
return NULL;
1443+
}
1444+
}
14381445
v = r_object(p);
14391446
if (v == NULL && !PyErr_Occurred())
14401447
PyErr_SetString(PyExc_TypeError, "NULL object in marshal data for object");
@@ -1531,7 +1538,7 @@ PyMarshal_ReadObjectFromFile(FILE *fp)
15311538
rf.refs = PyList_New(0);
15321539
if (rf.refs == NULL)
15331540
return NULL;
1534-
result = r_object(&rf);
1541+
result = read_object(&rf);
15351542
Py_DECREF(rf.refs);
15361543
if (rf.buf != NULL)
15371544
PyMem_Free(rf.buf);
@@ -1552,7 +1559,7 @@ PyMarshal_ReadObjectFromString(const char *str, Py_ssize_t len)
15521559
rf.refs = PyList_New(0);
15531560
if (rf.refs == NULL)
15541561
return NULL;
1555-
result = r_object(&rf);
1562+
result = read_object(&rf);
15561563
Py_DECREF(rf.refs);
15571564
if (rf.buf != NULL)
15581565
PyMem_Free(rf.buf);
@@ -1564,6 +1571,9 @@ PyMarshal_WriteObjectToString(PyObject *x, int version)
15641571
{
15651572
WFILE wf;
15661573

1574+
if (PySys_Audit("marshal.dumps", "Oi", x, version) < 0) {
1575+
return NULL;
1576+
}
15671577
memset(&wf, 0, sizeof(wf));
15681578
wf.str = PyBytes_FromStringAndSize((char *)NULL, 50);
15691579
if (wf.str == NULL)

0 commit comments

Comments
 (0)
0