Skip to content

Commit afab871

Browse files
authored
Merge branch 'main' into struct-name-repeats
2 parents 26e095e + b72014c commit afab871

File tree

11 files changed

+94
-11
lines changed

11 files changed

+94
-11
lines changed

Doc/conf.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@
105105
# Short title used e.g. for <title> HTML tags.
106106
html_short_title = '%s Documentation' % release
107107

108+
# Deployment preview information, from Netlify
109+
# (See netlify.toml and https://docs.netlify.com/configure-builds/environment-variables/#git-metadata)
110+
html_context = {
111+
"is_deployment_preview": os.getenv("IS_DEPLOYMENT_PREVIEW"),
112+
"repository_url": os.getenv("REPOSITORY_URL"),
113+
"pr_id": os.getenv("REVIEW_ID")
114+
}
115+
108116
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
109117
# using the given strftime format.
110118
html_last_updated_fmt = '%b %d, %Y'

Doc/tools/templates/layout.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@
88
<a href="/3/{{ pagename }}{{ file_suffix }}">{% trans %} Python documentation for the current stable release{% endtrans %}</a>.
99
</div>
1010
{%- endif %}
11+
12+
{%- if is_deployment_preview %}
13+
<div id="deployment-preview-warning" style="padding: .5em; text-align: center; background-color: #fff2ba; color: #6a580e;">
14+
<div style="float: right; margin-top: -10px; margin-left: 10px;">
15+
<a href="https://www.netlify.com">
16+
<img src="https://www.netlify.com/img/global/badges/netlify-color-accent.svg" alt="Deploys by Netlify" />
17+
</a>
18+
</div>
19+
{% trans %}This is a deploy preview created from a <a href="{{ repository_url }}/pull/{{ pr_id }}">pull request</a>.
20+
For authoritative documentation, see the {% endtrans %}
21+
<a href="https://docs.python.org/3/{{ pagename }}{{ file_suffix }}">{% trans %} the current stable release{% endtrans %}</a>.
22+
</div>
23+
{%- endif %}
1124
{% endblock %}
1225

1326
{% block rootrellink %}

Doc/using/configure.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Features required to build CPython:
2424

2525
.. versionchanged:: 3.11
2626
C11 compiler, IEEE 754 and NaN support are now required.
27+
On Windows, Visual Studio 2017 or later is required.
2728

2829
.. versionchanged:: 3.10
2930
OpenSSL 1.1.1 is now required.

Lib/test/test_frame.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import re
33
import sys
44
import textwrap
5+
import threading
56
import types
67
import unittest
78
import weakref
@@ -11,6 +12,7 @@
1112
_testcapi = None
1213

1314
from test import support
15+
from test.support import threading_helper
1416
from test.support.script_helper import assert_python_ok
1517

1618

@@ -329,6 +331,46 @@ def f():
329331
if old_enabled:
330332
gc.enable()
331333

334+
@support.cpython_only
335+
@threading_helper.requires_working_threading()
336+
def test_sneaky_frame_object_teardown(self):
337+
338+
class SneakyDel:
339+
def __del__(self):
340+
"""
341+
Stash a reference to the entire stack for walking later.
342+
343+
It may look crazy, but you'd be surprised how common this is
344+
when using a test runner (like pytest). The typical recipe is:
345+
ResourceWarning + -Werror + a custom sys.unraisablehook.
346+
"""
347+
nonlocal sneaky_frame_object
348+
sneaky_frame_object = sys._getframe()
349+
350+
class SneakyThread(threading.Thread):
351+
"""
352+
A separate thread isn't needed to make this code crash, but it does
353+
make crashes more consistent, since it means sneaky_frame_object is
354+
backed by freed memory after the thread completes!
355+
"""
356+
357+
def run(self):
358+
"""Run SneakyDel.__del__ as this frame is popped."""
359+
ref = SneakyDel()
360+
361+
sneaky_frame_object = None
362+
t = SneakyThread()
363+
t.start()
364+
t.join()
365+
# sneaky_frame_object can be anything, really, but it's crucial that
366+
# SneakyThread.run's frame isn't anywhere on the stack while it's being
367+
# torn down:
368+
self.assertIsNotNone(sneaky_frame_object)
369+
while sneaky_frame_object is not None:
370+
self.assertIsNot(
371+
sneaky_frame_object.f_code, SneakyThread.run.__code__
372+
)
373+
sneaky_frame_object = sneaky_frame_object.f_back
332374

333375
@unittest.skipIf(_testcapi is None, 'need _testcapi')
334376
class TestCAPI(unittest.TestCase):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix an issue that could cause frames to be visible to Python code as they
2+
are being torn down, possibly leading to memory corruption or hard crashes
3+
of the interpreter.

Python/bytecodes.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,10 @@ dummy_func(
619619
DTRACE_FUNCTION_EXIT();
620620
_Py_LeaveRecursiveCallPy(tstate);
621621
assert(frame != &entry_frame);
622-
frame = cframe.current_frame = pop_frame(tstate, frame);
622+
// GH-99729: We need to unlink the frame *before* clearing it:
623+
_PyInterpreterFrame *dying = frame;
624+
frame = cframe.current_frame = dying->previous;
625+
_PyEvalFrameClearAndPop(tstate, dying);
623626
_PyFrame_StackPush(frame, retval);
624627
goto resume_frame;
625628
}

Python/ceval.c

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,14 +1009,6 @@ trace_function_exit(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObject
10091009
return 0;
10101010
}
10111011

1012-
static _PyInterpreterFrame *
1013-
pop_frame(PyThreadState *tstate, _PyInterpreterFrame *frame)
1014-
{
1015-
_PyInterpreterFrame *prev_frame = frame->previous;
1016-
_PyEvalFrameClearAndPop(tstate, frame);
1017-
return prev_frame;
1018-
}
1019-
10201012

10211013
int _Py_CheckRecursiveCallPy(
10221014
PyThreadState *tstate)
@@ -1432,7 +1424,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
14321424
assert(_PyErr_Occurred(tstate));
14331425
_Py_LeaveRecursiveCallPy(tstate);
14341426
assert(frame != &entry_frame);
1435-
frame = cframe.current_frame = pop_frame(tstate, frame);
1427+
// GH-99729: We need to unlink the frame *before* clearing it:
1428+
_PyInterpreterFrame *dying = frame;
1429+
frame = cframe.current_frame = dying->previous;
1430+
_PyEvalFrameClearAndPop(tstate, dying);
14361431
if (frame == &entry_frame) {
14371432
/* Restore previous cframe and exit */
14381433
tstate->cframe = cframe.previous;

Python/frame.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ _PyFrame_Clear(_PyInterpreterFrame *frame)
127127
* to have cleared the enclosing generator, if any. */
128128
assert(frame->owner != FRAME_OWNED_BY_GENERATOR ||
129129
_PyFrame_GetGenerator(frame)->gi_frame_state == FRAME_CLEARED);
130+
// GH-99729: Clearing this frame can expose the stack (via finalizers). It's
131+
// crucial that this frame has been unlinked, and is no longer visible:
132+
assert(_PyThreadState_GET()->cframe->current_frame != frame);
130133
if (frame->frame_obj) {
131134
PyFrameObject *f = frame->frame_obj;
132135
frame->frame_obj = NULL;

Python/generated_cases.c.h

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tools/scripts/summarize_stats.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ def gather_stats(input):
184184
key = key.strip()
185185
value = int(value)
186186
stats[key] += value
187+
stats['__nfiles__'] += 1
187188
return stats
188189
else:
189190
raise ValueError(f"{input:r} is not a file or directory path")
@@ -561,6 +562,9 @@ def output_single_stats(stats):
561562
emit_specialization_overview(opcode_stats, total)
562563
emit_call_stats(stats)
563564
emit_object_stats(stats)
565+
with Section("Meta stats", summary="Meta statistics"):
566+
emit_table(("", "Count:"), [('Number of data files', stats['__nfiles__'])])
567+
564568

565569
def output_comparative_stats(base_stats, head_stats):
566570
base_opcode_stats = extract_opcode_stats(base_stats)

netlify.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[build]
2+
base = "Doc/"
3+
command = "make html"
4+
publish = "build/html"
5+
6+
[build.environment]
7+
PYTHON_VERSION = "3.8"
8+
IS_DEPLOYMENT_PREVIEW = "true"

0 commit comments

Comments
 (0)