From 96cbfad00a2030e8e20f95b618a1095ca1a950b9 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 1 Jul 2022 11:08:20 +0100 Subject: [PATCH] GH-94262: Don't create frame objects for frames that aren't yet complete. (GH-94371) (cherry picked from commit 544531de23d69f5b23883794fc7bb23a958a0fcb) Co-authored-by: Mark Shannon --- Include/internal/pycore_frame.h | 17 +++++++++ Lib/test/test_generators.py | 37 +++++++++++++++++++ ...2-06-28-10-08-06.gh-issue-94262.m-HWUZ.rst | 3 ++ Objects/codeobject.c | 11 +++--- Objects/frameobject.c | 10 ++++- Python/frame.c | 8 +++- Python/sysmodule.c | 14 +++++-- 7 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-06-28-10-08-06.gh-issue-94262.m-HWUZ.rst diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h index 405afd67d2274a..efdcdbcf8df805 100644 --- a/Include/internal/pycore_frame.h +++ b/Include/internal/pycore_frame.h @@ -133,6 +133,21 @@ _PyFrame_SetStackPointer(_PyInterpreterFrame *frame, PyObject **stack_pointer) frame->stacktop = (int)(stack_pointer - frame->localsplus); } +/* Determine whether a frame is incomplete. + * A frame is incomplete if it is part way through + * creating cell objects or a generator or coroutine. + * + * Frames on the frame stack are incomplete until the + * first RESUME instruction. + * Frames owned by a generator are always complete. + */ +static inline bool +_PyFrame_IsIncomplete(_PyInterpreterFrame *frame) +{ + return frame->owner != FRAME_OWNED_BY_GENERATOR && + frame->prev_instr < _PyCode_CODE(frame->f_code) + frame->f_code->_co_firsttraceable; +} + /* For use by _PyFrame_GetFrameObject Do not call directly. */ PyFrameObject * @@ -144,6 +159,8 @@ _PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame); static inline PyFrameObject * _PyFrame_GetFrameObject(_PyInterpreterFrame *frame) { + + assert(!_PyFrame_IsIncomplete(frame)); PyFrameObject *res = frame->frame_obj; if (res != NULL) { return res; diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index 87a7dd69d106c4..e5aa7da1e0df13 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -170,6 +170,43 @@ def f(): g.send(0) self.assertEqual(next(g), 1) + def test_handle_frame_object_in_creation(self): + + #Attempt to expose partially constructed frames + #See https://github.com/python/cpython/issues/94262 + + def cb(*args): + inspect.stack() + + def gen(): + yield 1 + + thresholds = gc.get_threshold() + + gc.callbacks.append(cb) + gc.set_threshold(1, 0, 0) + try: + gen() + finally: + gc.set_threshold(*thresholds) + gc.callbacks.pop() + + class Sneaky: + def __del__(self): + inspect.stack() + + sneaky = Sneaky() + sneaky._s = Sneaky() + sneaky._s._s = sneaky + + gc.set_threshold(1, 0, 0) + try: + del sneaky + gen() + finally: + gc.set_threshold(*thresholds) + + class ExceptionTest(unittest.TestCase): # Tests for the issue #23353: check that the currently handled exception diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-06-28-10-08-06.gh-issue-94262.m-HWUZ.rst b/Misc/NEWS.d/next/Core and Builtins/2022-06-28-10-08-06.gh-issue-94262.m-HWUZ.rst new file mode 100644 index 00000000000000..7ba39bbcfe21da --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-06-28-10-08-06.gh-issue-94262.m-HWUZ.rst @@ -0,0 +1,3 @@ +Don't create frame objects for incomplete frames. Prevents the creation of +generators and closures from being observable to Python and C extensions, +restoring the behavior of 3.10 and earlier. diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 2471006cabb037..4859b1ecebe50f 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -636,11 +636,10 @@ PyCode_New(int argcount, int kwonlyargcount, exceptiontable); } -static const char assert0[4] = { - LOAD_ASSERTION_ERROR, - 0, - RAISE_VARARGS, - 1 +static const char assert0[6] = { + RESUME, 0, + LOAD_ASSERTION_ERROR, 0, + RAISE_VARARGS, 1 }; PyCodeObject * @@ -664,7 +663,7 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno) if (filename_ob == NULL) { goto failed; } - code_ob = PyBytes_FromStringAndSize(assert0, 4); + code_ob = PyBytes_FromStringAndSize(assert0, 6); if (code_ob == NULL) { goto failed; } diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 8be41d4ae06c30..aead23987aa9c0 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -1133,8 +1133,14 @@ PyFrame_GetBack(PyFrameObject *frame) { assert(frame != NULL); PyFrameObject *back = frame->f_back; - if (back == NULL && frame->f_frame->previous != NULL) { - back = _PyFrame_GetFrameObject(frame->f_frame->previous); + if (back == NULL) { + _PyInterpreterFrame *prev = frame->f_frame->previous; + while (prev && _PyFrame_IsIncomplete(prev)) { + prev = prev->previous; + } + if (prev) { + back = _PyFrame_GetFrameObject(prev); + } } Py_XINCREF(back); return back; diff --git a/Python/frame.c b/Python/frame.c index b6674edfd1e081..c4e93492e081af 100644 --- a/Python/frame.c +++ b/Python/frame.c @@ -66,9 +66,13 @@ take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame) f->f_frame = frame; frame->owner = FRAME_OWNED_BY_FRAME_OBJECT; assert(f->f_back == NULL); - if (frame->previous != NULL) { + _PyInterpreterFrame *prev = frame->previous; + while (prev && _PyFrame_IsIncomplete(prev)) { + prev = prev->previous; + } + if (prev) { /* Link PyFrameObjects.f_back and remove link through _PyInterpreterFrame.previous */ - PyFrameObject *back = _PyFrame_GetFrameObject(frame->previous); + PyFrameObject *back = _PyFrame_GetFrameObject(prev); if (back == NULL) { /* Memory error here. */ assert(PyErr_ExceptionMatches(PyExc_MemoryError)); diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 769864196036e8..261ebb96ba285a 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1784,9 +1784,17 @@ sys__getframe_impl(PyObject *module, int depth) return NULL; } - while (depth > 0 && frame != NULL) { - frame = frame->previous; - --depth; + if (frame != NULL) { + while (depth > 0) { + frame = frame->previous; + if (frame == NULL) { + break; + } + if (_PyFrame_IsIncomplete(frame)) { + continue; + } + --depth; + } } if (frame == NULL) { _PyErr_SetString(tstate, PyExc_ValueError, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy