Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/internal/pycore_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ int _PyCompile_EnterScope(struct _PyCompiler *c, identifier name, int scope_type
void *key, int lineno, PyObject *private,
_PyCompile_CodeUnitMetadata *umd);
void _PyCompile_ExitScope(struct _PyCompiler *c);
int _PyCompile_SetQualname(struct _PyCompiler *c);
Py_ssize_t _PyCompile_AddConst(struct _PyCompiler *c, PyObject *o);
_PyInstructionSequence *_PyCompile_InstrSequence(struct _PyCompiler *c);
int _PyCompile_StartAnnotationSetup(struct _PyCompiler *c);
Expand Down
3 changes: 2 additions & 1 deletion Lib/asyncio/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,5 @@ def print_call_graph(
limit: int | None = None,
) -> None:
"""Print the async call graph for the current task or the provided Future."""
print(format_call_graph(future, depth=depth, limit=limit), file=file)
# gh-156327: print_call_graph() must not report its own frame
print(format_call_graph(future, depth=depth + 1, limit=limit), file=file)
10 changes: 9 additions & 1 deletion Lib/test/test_asyncio/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def walk(s):
return ret

buf = io.StringIO()
asyncio.print_call_graph(fut, file=buf, depth=depth+1)
asyncio.print_call_graph(fut, file=buf, depth=depth)

stack = asyncio.capture_call_graph(fut, depth=depth)
return walk(stack), buf.getvalue()
Expand Down Expand Up @@ -484,6 +484,14 @@ def test_capture_call_graph_non_future(self):
with self.assertRaises(TypeError):
asyncio.capture_call_graph("not a future")

async def test_print_call_graph_innermost_frame(self):
# gh-156327: print_call_graph() must not report its own frame
buf = io.StringIO()
lineno = sys._getframe().f_lineno + 1
asyncio.print_call_graph(file=buf)
first_frame = buf.getvalue().splitlines()[2]
self.assertIn(f'File {__file__!r}, line {lineno},', first_frame)

async def test_call_graph_finished_task(self):
# gh-156408: the call graph must not record a finished coroutine's None frame
async def boom():
Expand Down
22 changes: 2 additions & 20 deletions Lib/test/test_importlib/test_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import re
import sys
import sysconfig
import unittest
from test import support
from test.support import import_helper
Expand All @@ -17,25 +18,6 @@
EnumKey, CloseKey, DeleteKey, OpenKey
)

def get_platform():
# Port of distutils.util.get_platform().
TARGET_TO_PLAT = {
'x86' : 'win32',
'x64' : 'win-amd64',
'arm' : 'win-arm32',
}
if ('VSCMD_ARG_TGT_ARCH' in os.environ and
os.environ['VSCMD_ARG_TGT_ARCH'] in TARGET_TO_PLAT):
return TARGET_TO_PLAT[os.environ['VSCMD_ARG_TGT_ARCH']]
elif 'amd64' in sys.version.lower():
return 'win-amd64'
elif '(arm)' in sys.version.lower():
return 'win-arm32'
elif '(arm64)' in sys.version.lower():
return 'win-arm64'
else:
return sys.platform

def delete_registry_tree(root, subkey):
try:
hkey = OpenKey(root, subkey, access=KEY_ALL_ACCESS)
Expand Down Expand Up @@ -143,7 +125,7 @@ def test_tagged_suffix(self):
suffixes = self.machinery.EXTENSION_SUFFIXES
abi_flags = "t" if support.Py_GIL_DISABLED else ""
ver = sys.version_info
platform = re.sub('[^a-zA-Z0-9]', '_', get_platform())
platform = re.sub('[^a-zA-Z0-9]', '_', sysconfig.get_platform())
expected_tag = f".cp{ver.major}{ver.minor}{abi_flags}-{platform}.pyd"
try:
untagged_i = suffixes.index(".pyd")
Expand Down
53 changes: 26 additions & 27 deletions Lib/test/test_marshal.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,14 +342,26 @@ def test_reference_loop_dict(self):
def test_reference_loop_tuple(self):
a = ([],)
a[0].append(a)
for v in range(3):
for v in range(marshal.version + 1):
self.assertRaises(ValueError, marshal.dumps, a, v)

a = ({},)
a[0][None] = a
for v in range(marshal.version + 1):
self.assertRaises(ValueError, marshal.dumps, a, v)

def test_shared_reference_tuple(self):
# A tuple referenced more than once still round-trips with the
# shared identity preserved.
a = (1, 2)
for v in range(3, marshal.version + 1):
d = marshal.dumps(a, v)
b = marshal.loads(d)
self.assertIsInstance(b, tuple)
self.assertIsInstance(b[0], list)
self.assertIs(b[0][0], b)
b = marshal.loads(marshal.dumps([a, a], v))
self.assertEqual(b[0], a)
self.assertIs(b[0], b[1])
big = tuple(range(300)) # too large for TYPE_SMALL_TUPLE
b = marshal.loads(marshal.dumps([big, big]))
self.assertEqual(b[0], big)
self.assertIs(b[0], b[1])

def test_reference_loop_code(self):
def f():
Expand Down Expand Up @@ -409,27 +421,6 @@ def test_loads_reference_loop_dict(self):
self.assertIs(a[None], a)

def test_loads_abnormal_reference_loops(self):
# Indirect self-references of tuples.
data = b'\xa8\x01\x00\x00\x00[\x01\x00\x00\x00r\x00\x00\x00\x00' # ([<R>],)
a = marshal.loads(data)
self.assertIsInstance(a, tuple)
self.assertIsInstance(a[0], list)
self.assertIs(a[0][0], a)

data = b'\xa8\x01\x00\x00\x00{Nr\x00\x00\x00\x000' # ({None: <R>},)
a = marshal.loads(data)
self.assertIsInstance(a, tuple)
self.assertIsInstance(a[0], dict)
self.assertIs(a[0][None], a)

# Direct self-reference which cannot be created in Python.
# This creates a reference loop which cannot be collected.
if False:
data = b'\xa8\x01\x00\x00\x00r\x00\x00\x00\x00' # (<R>,)
a = marshal.loads(data)
self.assertIsInstance(a, tuple)
self.assertIs(a[0], a)

# Direct self-references which cannot be created in Python
# because of unhashability.
data = b'\xfbr\x00\x00\x00\x00N0' # {<R>: None}
Expand All @@ -439,6 +430,8 @@ def test_loads_abnormal_reference_loops(self):

for data in [
# Indirect self-references of immutable objects.
b'\xa8\x01\x00\x00\x00[\x01\x00\x00\x00r\x00\x00\x00\x00', # ([<R>],)
b'\xa8\x01\x00\x00\x00{Nr\x00\x00\x00\x000', # ({None: <R>},)
b'\xba[\x01\x00\x00\x00r\x00\x00\x00\x00NN', # slice([<R>], None)
b'\xbaN[\x01\x00\x00\x00r\x00\x00\x00\x00N', # slice(None, [<R>])
b'\xbaNN[\x01\x00\x00\x00r\x00\x00\x00\x00', # slice(None, None, [<R>])
Expand All @@ -449,12 +442,18 @@ def test_loads_abnormal_reference_loops(self):
b'\xfdN{Nr\x00\x00\x00\x0000', # frozendict({None: {None: <R>})

# Direct self-references which cannot be created in Python.
b'\xa8\x01\x00\x00\x00r\x00\x00\x00\x00', # (<R>,)
b'\xbe\x01\x00\x00\x00r\x00\x00\x00\x00', # frozenset({<R>})
b'\xfdNr\x00\x00\x00\x000', # frozendict({None: <R>})
b'\xfdr\x00\x00\x00\x00N0', # frozendict({<R>: None})
b'\xbar\x00\x00\x00\x00NN', # slice(<R>, None)
b'\xbaNr\x00\x00\x00\x00N', # slice(None, <R>)
b'\xbaNNr\x00\x00\x00\x00', # slice(None, None, <R>)

# Indirect self-references which cannot be created in Python
# because of unhashability.
b'\xa8\x01\x00\x00\x00{r\x00\x00\x00\x00N0', # ({<R>: None},)
b'\xa8\x01\x00\x00\x00<\x01\x00\x00\x00r\x00\x00\x00\x00', # ({<R>},)
]:
with self.subTest(data=data):
self.assertRaises(ValueError, marshal.loads, data)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Forbid :mod:`marshalling <marshal>` recursive tuples, and fix a crash when
unmarshalling a self-referencing tuple.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix cleanup on error in ``compiler_set_qualname``. Previously it was called in
``_PyCompile_EnterScope``, after the scope had been entered, and this was not
reversed in case of an error.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :func:`asyncio.print_call_graph` including its own frame in the printed
call stack.
40 changes: 23 additions & 17 deletions Modules/_testcapi/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ call_pyobject_print(PyObject *self, PyObject * args)
}

fp = Py_fopen(filename, "w+");
if (fp == NULL) {
return NULL;
}

if (Py_IsTrue(print_raw)) {
flags = Py_PRINT_RAW;
Expand All @@ -32,17 +35,15 @@ call_pyobject_print(PyObject *self, PyObject * args)
}

static PyObject *
pyobject_print_null(PyObject *self, PyObject *args)
pyobject_print_null(PyObject *self, PyObject *filename)
{
PyObject *filename;
FILE *fp;

if (!PyArg_UnpackTuple(args, "call_pyobject_print", 1, 1, &filename)) {
fp = Py_fopen(filename, "w+");
if (fp == NULL) {
return NULL;
}

fp = Py_fopen(filename, "w+");

if (PyObject_Print(NULL, fp, 0) < 0) {
fclose(fp);
return NULL;
Expand All @@ -54,26 +55,29 @@ pyobject_print_null(PyObject *self, PyObject *args)
}

static PyObject *
pyobject_print_noref_object(PyObject *self, PyObject *args)
pyobject_print_noref_object(PyObject *self, PyObject *filename)
{
PyObject *test_string;
PyObject *filename;
FILE *fp;
char correct_string[100];

test_string = PyUnicode_FromString("Spam spam spam");
if (test_string == NULL) {
return NULL;
}

Py_SET_REFCNT(test_string, 0);

PyOS_snprintf(correct_string, 100, "<refcnt %zd at %p>",
Py_REFCNT(test_string), (void *)test_string);

if (!PyArg_UnpackTuple(args, "call_pyobject_print", 1, 1, &filename)) {
fp = Py_fopen(filename, "w+");
if (fp == NULL) {
Py_SET_REFCNT(test_string, 1);
Py_DECREF(test_string);
return NULL;
}

fp = Py_fopen(filename, "w+");

if (PyObject_Print(test_string, fp, 0) < 0){
fclose(fp);
Py_SET_REFCNT(test_string, 1);
Expand All @@ -90,20 +94,22 @@ pyobject_print_noref_object(PyObject *self, PyObject *args)
}

static PyObject *
pyobject_print_os_error(PyObject *self, PyObject *args)
pyobject_print_os_error(PyObject *self, PyObject *filename)
{
PyObject *test_string;
PyObject *filename;
FILE *fp;

test_string = PyUnicode_FromString("Spam spam spam");

if (!PyArg_UnpackTuple(args, "call_pyobject_print", 1, 1, &filename)) {
if (test_string == NULL) {
return NULL;
}

// open file in read mode to induce OSError
fp = Py_fopen(filename, "r");
if (fp == NULL) {
Py_DECREF(test_string);
return NULL;
}

if (PyObject_Print(test_string, fp, 0) < 0) {
fclose(fp);
Expand Down Expand Up @@ -582,9 +588,9 @@ pysentinel_checkexact(PyObject *self, PyObject *obj)

static PyMethodDef test_methods[] = {
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
{"pyobject_print_noref_object", pyobject_print_noref_object, METH_VARARGS},
{"pyobject_print_os_error", pyobject_print_os_error, METH_VARARGS},
{"pyobject_print_null", pyobject_print_null, METH_O},
{"pyobject_print_noref_object", pyobject_print_noref_object, METH_O},
{"pyobject_print_os_error", pyobject_print_os_error, METH_O},
{"pyobject_clear_weakrefs_no_callbacks", pyobject_clear_weakrefs_no_callbacks, METH_O},
{"pyobject_enable_deferred_refcount", pyobject_enable_deferred_refcount, METH_O},
{"pyobject_is_unique_temporary", pyobject_is_unique_temporary, METH_O},
Expand Down
6 changes: 0 additions & 6 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1431,7 +1431,6 @@ pymarshal_write_long_to_file(PyObject* self, PyObject *args)

fp = Py_fopen(filename, "wb");
if (fp == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}

Expand All @@ -1456,7 +1455,6 @@ pymarshal_write_object_to_file(PyObject* self, PyObject *args)

fp = Py_fopen(filename, "wb");
if (fp == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}

Expand All @@ -1480,7 +1478,6 @@ pymarshal_read_short_from_file(PyObject* self, PyObject *args)

fp = Py_fopen(filename, "rb");
if (fp == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}

Expand All @@ -1505,7 +1502,6 @@ pymarshal_read_long_from_file(PyObject* self, PyObject *args)

fp = Py_fopen(filename, "rb");
if (fp == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}

Expand All @@ -1527,7 +1523,6 @@ pymarshal_read_last_object_from_file(PyObject* self, PyObject *args)

FILE *fp = Py_fopen(filename, "rb");
if (fp == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}

Expand All @@ -1550,7 +1545,6 @@ pymarshal_read_object_from_file(PyObject* self, PyObject *args)

FILE *fp = Py_fopen(filename, "rb");
if (fp == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}

Expand Down
Loading
Loading