Python-checkins
Threads by month
- ----- 2025 -----
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
January 2025
- 1 participants
- 705 discussions
gh-105704: Disallow square brackets (`[` and `]`) in domain names for parsed URLs (#129418)
by orsenthil Jan. 31, 2025
by orsenthil Jan. 31, 2025
Jan. 31, 2025
https://github.com/python/cpython/commit/d89a5f6a6e65511a5f6e0618c4c30a7aa5…
commit: d89a5f6a6e65511a5f6e0618c4c30a7aa5aba56a
branch: main
author: Seth Michael Larson <seth(a)python.org>
committer: orsenthil <senthilx(a)amazon.com>
date: 2025-01-31T09:41:34-08:00
summary:
gh-105704: Disallow square brackets (`[` and `]`) in domain names for parsed URLs (#129418)
* gh-105704: Disallow square brackets ( and ) in domain names for parsed URLs
* Use Sphinx references
Co-authored-by: Peter Bierma <zintensitydev(a)gmail.com>
* Add mismatched bracket test cases, fix news format
* Add more test coverage for ports
---------
Co-authored-by: Peter Bierma <zintensitydev(a)gmail.com>
files:
A Misc/NEWS.d/next/Security/2025-01-28-14-08-03.gh-issue-105704.EnhHxu.rst
M Lib/test/test_urlparse.py
M Lib/urllib/parse.py
diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
index 4516bdea6adb19..b51cc006b73280 100644
--- a/Lib/test/test_urlparse.py
+++ b/Lib/test/test_urlparse.py
@@ -1412,16 +1412,51 @@ def test_invalid_bracketed_hosts(self):
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af::2309::fae7:1234]/Path?Query')
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af:2309::fae7:1234:2342:438e:192.0.2.146]/Path?Query')
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@]v6a.ip[/Path')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[v6a.ip]')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[v6a.ip].suffix')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[v6a.ip]/')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[v6a.ip].suffix/')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[v6a.ip]?')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[v6a.ip].suffix?')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]/')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix/')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]?')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix?')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:a')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix:a')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:a1')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix:a1')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:1a')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix:1a')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix:/')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:?')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://user@prefix.[v6a.ip]')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://user@[v6a.ip].suffix')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[v6a.ip')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://v6a.ip]')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://]v6a.ip[')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://]v6a.ip')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://v6a.ip[')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[v6a.ip')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://v6a.ip].suffix')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix]v6a.ip[suffix')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix]v6a.ip')
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://v6a.ip[suffix')
def test_splitting_bracketed_hosts(self):
- p1 = urllib.parse.urlsplit('scheme://user@[v6a.ip]/path?query')
+ p1 = urllib.parse.urlsplit('scheme://user@[v6a.ip]:1234/path?query')
self.assertEqual(p1.hostname, 'v6a.ip')
self.assertEqual(p1.username, 'user')
self.assertEqual(p1.path, '/path')
+ self.assertEqual(p1.port, 1234)
p2 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7%test]/path?query')
self.assertEqual(p2.hostname, '0439:23af:2309::fae7%test')
self.assertEqual(p2.username, 'user')
self.assertEqual(p2.path, '/path')
+ self.assertIs(p2.port, None)
p3 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7:1234:192.0.2.146%test]/path?query')
self.assertEqual(p3.hostname, '0439:23af:2309::fae7:1234:192.0.2.146%test')
self.assertEqual(p3.username, 'user')
diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py
index c412c729852272..9d51f4c6812b57 100644
--- a/Lib/urllib/parse.py
+++ b/Lib/urllib/parse.py
@@ -439,6 +439,23 @@ def _checknetloc(netloc):
raise ValueError("netloc '" + netloc + "' contains invalid " +
"characters under NFKC normalization")
+def _check_bracketed_netloc(netloc):
+ # Note that this function must mirror the splitting
+ # done in NetlocResultMixins._hostinfo().
+ hostname_and_port = netloc.rpartition('@')[2]
+ before_bracket, have_open_br, bracketed = hostname_and_port.partition('[')
+ if have_open_br:
+ # No data is allowed before a bracket.
+ if before_bracket:
+ raise ValueError("Invalid IPv6 URL")
+ hostname, _, port = bracketed.partition(']')
+ # No data is allowed after the bracket but before the port delimiter.
+ if port and not port.startswith(":"):
+ raise ValueError("Invalid IPv6 URL")
+ else:
+ hostname, _, port = hostname_and_port.partition(':')
+ _check_bracketed_host(hostname)
+
# Valid bracketed hosts are defined in
# https://www.rfc-editor.org/rfc/rfc3986#page-49 and https://url.spec.whatwg.org/
def _check_bracketed_host(hostname):
@@ -505,8 +522,7 @@ def _urlsplit(url, scheme=None, allow_fragments=True):
(']' in netloc and '[' not in netloc)):
raise ValueError("Invalid IPv6 URL")
if '[' in netloc and ']' in netloc:
- bracketed_host = netloc.partition('[')[2].partition(']')[0]
- _check_bracketed_host(bracketed_host)
+ _check_bracketed_netloc(netloc)
if allow_fragments and '#' in url:
url, fragment = url.split('#', 1)
if '?' in url:
diff --git a/Misc/NEWS.d/next/Security/2025-01-28-14-08-03.gh-issue-105704.EnhHxu.rst b/Misc/NEWS.d/next/Security/2025-01-28-14-08-03.gh-issue-105704.EnhHxu.rst
new file mode 100644
index 00000000000000..bff1bc6b0d609c
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2025-01-28-14-08-03.gh-issue-105704.EnhHxu.rst
@@ -0,0 +1,4 @@
+When using :func:`urllib.parse.urlsplit` and :func:`urllib.parse.urlparse` host
+parsing would not reject domain names containing square brackets (``[`` and
+``]``). Square brackets are only valid for IPv6 and IPvFuture hosts according to
+`RFC 3986 Section 3.2.2 <https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2>`__.
1
0
GH-128563: Move some labels, to simplify implementing tailcalling interpreter. (GH-129525)
by markshannon Jan. 31, 2025
by markshannon Jan. 31, 2025
Jan. 31, 2025
https://github.com/python/cpython/commit/54f74b80aef8b581f2b124d150903cec83…
commit: 54f74b80aef8b581f2b124d150903cec83aff005
branch: main
author: Mark Shannon <mark(a)hotpy.org>
committer: markshannon <mark(a)hotpy.org>
date: 2025-01-31T17:13:20Z
summary:
GH-128563: Move some labels, to simplify implementing tailcalling interpreter. (GH-129525)
files:
M Python/bytecodes.c
M Python/ceval.c
M Python/ceval_macros.h
M Python/generated_cases.c.h
M Tools/cases_generator/analyzer.py
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index f659a5e5c920a7..effc8e0b6f6578 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -5303,14 +5303,40 @@ dummy_func(
tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
return NULL;
}
- goto resume_with_error;
+ next_instr = frame->instr_ptr;
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ goto error;
}
- label(resume_with_error) {
+ label(start_frame) {
+ if (_Py_EnterRecursivePy(tstate)) {
+ goto exit_unwind;
+ }
next_instr = frame->instr_ptr;
stack_pointer = _PyFrame_GetStackPointer(frame);
- goto error;
+
+ #ifdef LLTRACE
+ {
+ int lltrace = maybe_lltrace_resume_frame(frame, GLOBALS());
+ frame->lltrace = lltrace;
+ if (lltrace < 0) {
+ goto exit_unwind;
+ }
+ }
+ #endif
+
+ #ifdef Py_DEBUG
+ /* _PyEval_EvalFrameDefault() must not be called with an exception set,
+ because it can clear it (directly or indirectly) and so the
+ caller loses its exception */
+ assert(!_PyErr_Occurred(tstate));
+ #endif
+
+ DISPATCH();
}
+
+
+
// END BYTECODES //
}
@@ -5320,7 +5346,6 @@ dummy_func(
exit_unwind:
handle_eval_breaker:
resume_frame:
- resume_with_error:
start_frame:
unbound_local_error:
;
diff --git a/Python/ceval.c b/Python/ceval.c
index e3b87441f8088d..11518684c136bd 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -792,6 +792,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
return NULL;
}
+ /* Local "register" variables.
+ * These are cached values from the frame and code object. */
+ _Py_CODEUNIT *next_instr;
+ _PyStackRef *stack_pointer;
+
#if defined(Py_DEBUG) && !defined(Py_STACKREF_DEBUG)
/* Set these to invalid but identifiable values for debugging. */
entry_frame.f_funcobj = (_PyStackRef){.bits = 0xaaa0};
@@ -819,67 +824,36 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
/* support for generator.throw() */
if (throwflag) {
if (_Py_EnterRecursivePy(tstate)) {
- goto exit_unwind;
+ goto early_exit;
}
- /* Because this avoids the RESUME,
- * we need to update instrumentation */
#ifdef Py_GIL_DISABLED
/* Load thread-local bytecode */
if (frame->tlbc_index != ((_PyThreadStateImpl *)tstate)->tlbc_index) {
_Py_CODEUNIT *bytecode =
_PyEval_GetExecutableCode(tstate, _PyFrame_GetCode(frame));
if (bytecode == NULL) {
- goto exit_unwind;
+ goto early_exit;
}
ptrdiff_t off = frame->instr_ptr - _PyFrame_GetBytecode(frame);
frame->tlbc_index = ((_PyThreadStateImpl *)tstate)->tlbc_index;
frame->instr_ptr = bytecode + off;
}
#endif
+ /* Because this avoids the RESUME, we need to update instrumentation */
_Py_Instrument(_PyFrame_GetCode(frame), tstate->interp);
- monitor_throw(tstate, frame, frame->instr_ptr);
- /* TO DO -- Monitor throw entry. */
- goto resume_with_error;
+ next_instr = frame->instr_ptr;
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ monitor_throw(tstate, frame, next_instr);
+ goto error;
}
- /* Local "register" variables.
- * These are cached values from the frame and code object. */
- _Py_CODEUNIT *next_instr;
- _PyStackRef *stack_pointer;
-
#if defined(_Py_TIER2) && !defined(_Py_JIT)
/* Tier 2 interpreter state */
_PyExecutorObject *current_executor = NULL;
const _PyUOpInstruction *next_uop = NULL;
#endif
-start_frame:
- if (_Py_EnterRecursivePy(tstate)) {
- goto exit_unwind;
- }
-
- next_instr = frame->instr_ptr;
-resume_frame:
- stack_pointer = _PyFrame_GetStackPointer(frame);
-
-#ifdef LLTRACE
- {
- int lltrace = maybe_lltrace_resume_frame(frame, GLOBALS());
- frame->lltrace = lltrace;
- if (lltrace < 0) {
- goto exit_unwind;
- }
- }
-#endif
-
-#ifdef Py_DEBUG
- /* _PyEval_EvalFrameDefault() must not be called with an exception set,
- because it can clear it (directly or indirectly) and so the
- caller loses its exception */
- assert(!_PyErr_Occurred(tstate));
-#endif
-
- DISPATCH();
+ goto start_frame;
#include "generated_cases.c.h"
@@ -983,10 +957,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
OPT_HIST(trace_uop_execution_counter, trace_run_length_hist);
assert(next_uop[-1].format == UOP_FORMAT_TARGET);
frame->return_offset = 0; // Don't leave this random
- _PyFrame_SetStackPointer(frame, stack_pointer);
Py_DECREF(current_executor);
tstate->previous_executor = NULL;
- goto resume_with_error;
+ next_instr = frame->instr_ptr;
+ goto error;
jump_to_jump_target:
assert(next_uop[-1].format == UOP_FORMAT_JUMP);
@@ -1018,6 +992,20 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
#endif // _Py_TIER2
+early_exit:
+ assert(_PyErr_Occurred(tstate));
+ _Py_LeaveRecursiveCallPy(tstate);
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
+ // GH-99729: We need to unlink the frame *before* clearing it:
+ _PyInterpreterFrame *dying = frame;
+ frame = tstate->current_frame = dying->previous;
+ _PyEval_FrameClearAndPop(tstate, dying);
+ frame->return_offset = 0;
+ assert(frame->owner == FRAME_OWNED_BY_INTERPRETER);
+ /* Restore previous frame and exit */
+ tstate->current_frame = frame->previous;
+ tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
+ return NULL;
}
#if defined(__GNUC__)
diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h
index 62c80c96e422fd..c2fc38f3c18e53 100644
--- a/Python/ceval_macros.h
+++ b/Python/ceval_macros.h
@@ -381,7 +381,9 @@ do { \
tstate->previous_executor = NULL; \
frame = tstate->current_frame; \
if (next_instr == NULL) { \
- goto resume_with_error; \
+ next_instr = frame->instr_ptr; \
+ stack_pointer = _PyFrame_GetStackPointer(frame); \
+ goto error; \
} \
stack_pointer = _PyFrame_GetStackPointer(frame); \
DISPATCH(); \
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index ffdad70815caef..38ea63d71ab044 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -8703,14 +8703,36 @@
tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
return NULL;
}
- goto resume_with_error;
+ next_instr = frame->instr_ptr;
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ goto error;
}
- resume_with_error:
+ start_frame:
{
+ if (_Py_EnterRecursivePy(tstate)) {
+ goto exit_unwind;
+ }
next_instr = frame->instr_ptr;
stack_pointer = _PyFrame_GetStackPointer(frame);
- goto error;
+ #ifdef LLTRACE
+ {
+ int lltrace = maybe_lltrace_resume_frame(frame, GLOBALS());
+ frame->lltrace = lltrace;
+ if (lltrace < 0) {
+ goto exit_unwind;
+ }
+ }
+ #endif
+
+ #ifdef Py_DEBUG
+ /* _PyEval_EvalFrameDefault() must not be called with an exception set,
+ because it can clear it (directly or indirectly) and so the
+ caller loses its exception */
+ assert(!_PyErr_Occurred(tstate));
+ #endif
+
+ DISPATCH();
}
/* END LABELS */
diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py
index b9293ff4b19951..acf9458019fb4b 100644
--- a/Tools/cases_generator/analyzer.py
+++ b/Tools/cases_generator/analyzer.py
@@ -511,7 +511,6 @@ def has_error_with_pop(op: parser.InstDef) -> bool:
variable_used(op, "ERROR_IF")
or variable_used(op, "pop_1_error")
or variable_used(op, "exception_unwind")
- or variable_used(op, "resume_with_error")
)
@@ -520,7 +519,6 @@ def has_error_without_pop(op: parser.InstDef) -> bool:
variable_used(op, "ERROR_NO_POP")
or variable_used(op, "pop_1_error")
or variable_used(op, "exception_unwind")
- or variable_used(op, "resume_with_error")
)
1
0
gh-128509: Add `sys._is_immortal` for identifying immortal objects (#128510)
by kumaraditya303 Jan. 31, 2025
by kumaraditya303 Jan. 31, 2025
Jan. 31, 2025
https://github.com/python/cpython/commit/9ba281d871c4df3a3ac4cb7896d24ba0d4…
commit: 9ba281d871c4df3a3ac4cb7896d24ba0d42751a3
branch: main
author: Peter Bierma <zintensitydev(a)gmail.com>
committer: kumaraditya303 <kumaraditya(a)python.org>
date: 2025-01-31T15:27:08Z
summary:
gh-128509: Add `sys._is_immortal` for identifying immortal objects (#128510)
Co-authored-by: Kumar Aditya <kumaraditya(a)python.org>
files:
A Misc/NEWS.d/next/Library/2025-01-04-20-51-48.gh-issue-128509.3gr_-O.rst
M Doc/glossary.rst
M Doc/library/sys.rst
M Doc/whatsnew/3.14.rst
M Python/clinic/sysmodule.c.h
M Python/sysmodule.c
diff --git a/Doc/glossary.rst b/Doc/glossary.rst
index e3a14601398e89..d933ca6b467cf3 100644
--- a/Doc/glossary.rst
+++ b/Doc/glossary.rst
@@ -658,6 +658,9 @@ Glossary
and therefore it is never deallocated while the interpreter is running.
For example, :const:`True` and :const:`None` are immortal in CPython.
+ Immortal objects can be identified via :func:`sys._is_immortal`, or
+ via :c:func:`PyUnstable_IsImmortal` in the C API.
+
immutable
An object with a fixed value. Immutable objects include numbers, strings and
tuples. Such an object cannot be altered. A new object has to
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index 5a096235713319..855237e0984972 100644
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -855,6 +855,11 @@ always available. Unless explicitly noted otherwise, all variables are read-only
reflect the actual number of references. Consequently, do not rely
on the returned value to be accurate, other than a value of 0 or 1.
+ .. impl-detail::
+
+ :term:`Immortal <immortal>` objects with a large reference count can be
+ identified via :func:`_is_immortal`.
+
.. versionchanged:: 3.12
Immortal objects have very large refcounts that do not match
the actual number of references to the object.
@@ -1264,6 +1269,24 @@ always available. Unless explicitly noted otherwise, all variables are read-only
.. versionadded:: 3.12
+.. function:: _is_immortal(op)
+
+ Return :const:`True` if the given object is :term:`immortal`, :const:`False`
+ otherwise.
+
+ .. note::
+
+ Objects that are immortal (and thus return ``True`` upon being passed
+ to this function) are not guaranteed to be immortal in future versions,
+ and vice versa for mortal objects.
+
+ .. versionadded:: next
+
+ .. impl-detail::
+
+ This function should be used for specialized purposes only.
+ It is not guaranteed to exist in all implementations of Python.
+
.. function:: _is_interned(string)
Return :const:`True` if the given string is "interned", :const:`False`
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 484e306335829a..daed0e8aa509a1 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -649,9 +649,13 @@ sys
which only exists in specialized builds of Python, may now return objects
from other interpreters than the one it's called in.
+* Add :func:`sys._is_immortal` for determining if an object is :term:`immortal`.
+ (Contributed by Peter Bierma in :gh:`128509`.)
+
* On FreeBSD, :data:`sys.platform` doesn't contain the major version anymore.
It is always ``'freebsd'``, instead of ``'freebsd13'`` or ``'freebsd14'``.
+
sys.monitoring
--------------
diff --git a/Misc/NEWS.d/next/Library/2025-01-04-20-51-48.gh-issue-128509.3gr_-O.rst b/Misc/NEWS.d/next/Library/2025-01-04-20-51-48.gh-issue-128509.3gr_-O.rst
new file mode 100644
index 00000000000000..ba45884304f662
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-01-04-20-51-48.gh-issue-128509.3gr_-O.rst
@@ -0,0 +1,2 @@
+Add :func:`sys._is_immortal` for identifying :term:`immortal` objects at
+runtime.
diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h
index cfcbd55388efa0..1e53624d4d45d7 100644
--- a/Python/clinic/sysmodule.c.h
+++ b/Python/clinic/sysmodule.c.h
@@ -373,6 +373,36 @@ sys__is_interned(PyObject *module, PyObject *arg)
return return_value;
}
+PyDoc_STRVAR(sys__is_immortal__doc__,
+"_is_immortal($module, op, /)\n"
+"--\n"
+"\n"
+"Return True if the given object is \"immortal\" per PEP 683.\n"
+"\n"
+"This function should be used for specialized purposes only.");
+
+#define SYS__IS_IMMORTAL_METHODDEF \
+ {"_is_immortal", (PyCFunction)sys__is_immortal, METH_O, sys__is_immortal__doc__},
+
+static int
+sys__is_immortal_impl(PyObject *module, PyObject *op);
+
+static PyObject *
+sys__is_immortal(PyObject *module, PyObject *op)
+{
+ PyObject *return_value = NULL;
+ int _return_value;
+
+ _return_value = sys__is_immortal_impl(module, op);
+ if ((_return_value == -1) && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = PyBool_FromLong((long)_return_value);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(sys_settrace__doc__,
"settrace($module, function, /)\n"
"--\n"
@@ -1724,4 +1754,4 @@ sys__is_gil_enabled(PyObject *module, PyObject *Py_UNUSED(ignored))
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
#define SYS_GETANDROIDAPILEVEL_METHODDEF
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
-/*[clinic end generated code: output=568b0a0069dc43e8 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=1e5f608092c12636 input=a9049054013a1b77]*/
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index 11b96c8455de14..d5cb448eb618e8 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -972,6 +972,23 @@ sys__is_interned_impl(PyObject *module, PyObject *string)
return PyUnicode_CHECK_INTERNED(string);
}
+/*[clinic input]
+sys._is_immortal -> bool
+
+ op: object
+ /
+
+Return True if the given object is "immortal" per PEP 683.
+
+This function should be used for specialized purposes only.
+[clinic start generated code]*/
+
+static int
+sys__is_immortal_impl(PyObject *module, PyObject *op)
+/*[clinic end generated code: output=c2f5d6a80efb8d1a input=4609c9bf5481db76]*/
+{
+ return PyUnstable_IsImmortal(op);
+}
/*
* Cached interned string objects used for calling the profile and
@@ -2588,6 +2605,7 @@ static PyMethodDef sys_methods[] = {
SYS__GETFRAMEMODULENAME_METHODDEF
SYS_GETWINDOWSVERSION_METHODDEF
SYS__ENABLELEGACYWINDOWSFSENCODING_METHODDEF
+ SYS__IS_IMMORTAL_METHODDEF
SYS_INTERN_METHODDEF
SYS__IS_INTERNED_METHODDEF
SYS_IS_FINALIZING_METHODDEF
1
0
https://github.com/python/cpython/commit/60a85415aeb5a8be54b3c412d19a7444bf…
commit: 60a85415aeb5a8be54b3c412d19a7444bf5ac757
branch: main
author: Victor Stinner <vstinner(a)python.org>
committer: vstinner <vstinner(a)python.org>
date: 2025-01-31T16:02:50+01:00
summary:
gh-93649: Add Modules/_testcapi/function.c file (#129521)
* Move PyFunction C API tests to a new file.
* Add Lib/test/test_capi/test_function.py.
* Move tests from test_capi.test_misc to test_capi.test_function.
files:
A Lib/test/test_capi/test_function.py
A Modules/_testcapi/function.c
M Lib/test/test_capi/test_misc.py
M Modules/Setup.stdlib.in
M Modules/_testcapi/parts.h
M Modules/_testcapimodule.c
M PCbuild/_testcapi.vcxproj
M PCbuild/_testcapi.vcxproj.filters
diff --git a/Lib/test/test_capi/test_function.py b/Lib/test/test_capi/test_function.py
new file mode 100644
index 00000000000000..9dca377e28ba42
--- /dev/null
+++ b/Lib/test/test_capi/test_function.py
@@ -0,0 +1,323 @@
+import unittest
+from test.support import import_helper
+
+
+_testcapi = import_helper.import_module('_testcapi')
+
+
+class FunctionTest(unittest.TestCase):
+ def test_function_get_code(self):
+ # Test PyFunction_GetCode()
+ import types
+
+ def some():
+ pass
+
+ code = _testcapi.function_get_code(some)
+ self.assertIsInstance(code, types.CodeType)
+ self.assertEqual(code, some.__code__)
+
+ with self.assertRaises(SystemError):
+ _testcapi.function_get_code(None) # not a function
+
+ def test_function_get_globals(self):
+ # Test PyFunction_GetGlobals()
+ def some():
+ pass
+
+ globals_ = _testcapi.function_get_globals(some)
+ self.assertIsInstance(globals_, dict)
+ self.assertEqual(globals_, some.__globals__)
+
+ with self.assertRaises(SystemError):
+ _testcapi.function_get_globals(None) # not a function
+
+ def test_function_get_module(self):
+ # Test PyFunction_GetModule()
+ def some():
+ pass
+
+ module = _testcapi.function_get_module(some)
+ self.assertIsInstance(module, str)
+ self.assertEqual(module, some.__module__)
+
+ with self.assertRaises(SystemError):
+ _testcapi.function_get_module(None) # not a function
+
+ def test_function_get_defaults(self):
+ # Test PyFunction_GetDefaults()
+ def some(
+ pos_only1, pos_only2='p',
+ /,
+ zero=0, optional=None,
+ *,
+ kw1,
+ kw2=True,
+ ):
+ pass
+
+ defaults = _testcapi.function_get_defaults(some)
+ self.assertEqual(defaults, ('p', 0, None))
+ self.assertEqual(defaults, some.__defaults__)
+
+ with self.assertRaises(SystemError):
+ _testcapi.function_get_defaults(None) # not a function
+
+ def test_function_set_defaults(self):
+ # Test PyFunction_SetDefaults()
+ def some(
+ pos_only1, pos_only2='p',
+ /,
+ zero=0, optional=None,
+ *,
+ kw1,
+ kw2=True,
+ ):
+ pass
+
+ old_defaults = ('p', 0, None)
+ self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
+ self.assertEqual(some.__defaults__, old_defaults)
+
+ with self.assertRaises(SystemError):
+ _testcapi.function_set_defaults(some, 1) # not tuple or None
+ self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
+ self.assertEqual(some.__defaults__, old_defaults)
+
+ with self.assertRaises(SystemError):
+ _testcapi.function_set_defaults(1, ()) # not a function
+ self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
+ self.assertEqual(some.__defaults__, old_defaults)
+
+ new_defaults = ('q', 1, None)
+ _testcapi.function_set_defaults(some, new_defaults)
+ self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
+ self.assertEqual(some.__defaults__, new_defaults)
+
+ # Empty tuple is fine:
+ new_defaults = ()
+ _testcapi.function_set_defaults(some, new_defaults)
+ self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
+ self.assertEqual(some.__defaults__, new_defaults)
+
+ class tuplesub(tuple): ... # tuple subclasses must work
+
+ new_defaults = tuplesub(((1, 2), ['a', 'b'], None))
+ _testcapi.function_set_defaults(some, new_defaults)
+ self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
+ self.assertEqual(some.__defaults__, new_defaults)
+
+ # `None` is special, it sets `defaults` to `NULL`,
+ # it needs special handling in `_testcapi`:
+ _testcapi.function_set_defaults(some, None)
+ self.assertEqual(_testcapi.function_get_defaults(some), None)
+ self.assertEqual(some.__defaults__, None)
+
+ def test_function_get_kw_defaults(self):
+ # Test PyFunction_GetKwDefaults()
+ def some(
+ pos_only1, pos_only2='p',
+ /,
+ zero=0, optional=None,
+ *,
+ kw1,
+ kw2=True,
+ ):
+ pass
+
+ defaults = _testcapi.function_get_kw_defaults(some)
+ self.assertEqual(defaults, {'kw2': True})
+ self.assertEqual(defaults, some.__kwdefaults__)
+
+ with self.assertRaises(SystemError):
+ _testcapi.function_get_kw_defaults(None) # not a function
+
+ def test_function_set_kw_defaults(self):
+ # Test PyFunction_SetKwDefaults()
+ def some(
+ pos_only1, pos_only2='p',
+ /,
+ zero=0, optional=None,
+ *,
+ kw1,
+ kw2=True,
+ ):
+ pass
+
+ old_defaults = {'kw2': True}
+ self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
+ self.assertEqual(some.__kwdefaults__, old_defaults)
+
+ with self.assertRaises(SystemError):
+ _testcapi.function_set_kw_defaults(some, 1) # not dict or None
+ self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
+ self.assertEqual(some.__kwdefaults__, old_defaults)
+
+ with self.assertRaises(SystemError):
+ _testcapi.function_set_kw_defaults(1, {}) # not a function
+ self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
+ self.assertEqual(some.__kwdefaults__, old_defaults)
+
+ new_defaults = {'kw2': (1, 2, 3)}
+ _testcapi.function_set_kw_defaults(some, new_defaults)
+ self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
+ self.assertEqual(some.__kwdefaults__, new_defaults)
+
+ # Empty dict is fine:
+ new_defaults = {}
+ _testcapi.function_set_kw_defaults(some, new_defaults)
+ self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
+ self.assertEqual(some.__kwdefaults__, new_defaults)
+
+ class dictsub(dict): ... # dict subclasses must work
+
+ new_defaults = dictsub({'kw2': None})
+ _testcapi.function_set_kw_defaults(some, new_defaults)
+ self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
+ self.assertEqual(some.__kwdefaults__, new_defaults)
+
+ # `None` is special, it sets `kwdefaults` to `NULL`,
+ # it needs special handling in `_testcapi`:
+ _testcapi.function_set_kw_defaults(some, None)
+ self.assertEqual(_testcapi.function_get_kw_defaults(some), None)
+ self.assertEqual(some.__kwdefaults__, None)
+
+ def test_function_get_closure(self):
+ # Test PyFunction_GetClosure()
+ from types import CellType
+
+ def regular_function(): ...
+ def unused_one_level(arg1):
+ def inner(arg2, arg3): ...
+ return inner
+ def unused_two_levels(arg1, arg2):
+ def decorator(arg3, arg4):
+ def inner(arg5, arg6): ...
+ return inner
+ return decorator
+ def with_one_level(arg1):
+ def inner(arg2, arg3):
+ return arg1 + arg2 + arg3
+ return inner
+ def with_two_levels(arg1, arg2):
+ def decorator(arg3, arg4):
+ def inner(arg5, arg6):
+ return arg1 + arg2 + arg3 + arg4 + arg5 + arg6
+ return inner
+ return decorator
+
+ # Functions without closures:
+ self.assertIsNone(_testcapi.function_get_closure(regular_function))
+ self.assertIsNone(regular_function.__closure__)
+
+ func = unused_one_level(1)
+ closure = _testcapi.function_get_closure(func)
+ self.assertIsNone(closure)
+ self.assertIsNone(func.__closure__)
+
+ func = unused_two_levels(1, 2)(3, 4)
+ closure = _testcapi.function_get_closure(func)
+ self.assertIsNone(closure)
+ self.assertIsNone(func.__closure__)
+
+ # Functions with closures:
+ func = with_one_level(5)
+ closure = _testcapi.function_get_closure(func)
+ self.assertEqual(closure, func.__closure__)
+ self.assertIsInstance(closure, tuple)
+ self.assertEqual(len(closure), 1)
+ self.assertEqual(len(closure), len(func.__code__.co_freevars))
+ for cell in closure:
+ self.assertIsInstance(cell, CellType)
+ self.assertTrue(closure[0].cell_contents, 5)
+
+ func = with_two_levels(1, 2)(3, 4)
+ closure = _testcapi.function_get_closure(func)
+ self.assertEqual(closure, func.__closure__)
+ self.assertIsInstance(closure, tuple)
+ self.assertEqual(len(closure), 4)
+ self.assertEqual(len(closure), len(func.__code__.co_freevars))
+ for cell in closure:
+ self.assertIsInstance(cell, CellType)
+ self.assertEqual([cell.cell_contents for cell in closure],
+ [1, 2, 3, 4])
+
+ def test_function_get_closure_error(self):
+ # Test PyFunction_GetClosure()
+ with self.assertRaises(SystemError):
+ _testcapi.function_get_closure(1)
+ with self.assertRaises(SystemError):
+ _testcapi.function_get_closure(None)
+
+ def test_function_set_closure(self):
+ # Test PyFunction_SetClosure()
+ from types import CellType
+
+ def function_without_closure(): ...
+ def function_with_closure(arg):
+ def inner():
+ return arg
+ return inner
+
+ func = function_without_closure
+ _testcapi.function_set_closure(func, (CellType(1), CellType(1)))
+ closure = _testcapi.function_get_closure(func)
+ self.assertEqual([c.cell_contents for c in closure], [1, 1])
+ self.assertEqual([c.cell_contents for c in func.__closure__], [1, 1])
+
+ func = function_with_closure(1)
+ _testcapi.function_set_closure(func,
+ (CellType(1), CellType(2), CellType(3)))
+ closure = _testcapi.function_get_closure(func)
+ self.assertEqual([c.cell_contents for c in closure], [1, 2, 3])
+ self.assertEqual([c.cell_contents for c in func.__closure__], [1, 2, 3])
+
+ def test_function_set_closure_none(self):
+ # Test PyFunction_SetClosure()
+ def function_without_closure(): ...
+ def function_with_closure(arg):
+ def inner():
+ return arg
+ return inner
+
+ _testcapi.function_set_closure(function_without_closure, None)
+ self.assertIsNone(
+ _testcapi.function_get_closure(function_without_closure))
+ self.assertIsNone(function_without_closure.__closure__)
+
+ _testcapi.function_set_closure(function_with_closure, None)
+ self.assertIsNone(
+ _testcapi.function_get_closure(function_with_closure))
+ self.assertIsNone(function_with_closure.__closure__)
+
+ def test_function_set_closure_errors(self):
+ # Test PyFunction_SetClosure()
+ def function_without_closure(): ...
+
+ with self.assertRaises(SystemError):
+ _testcapi.function_set_closure(None, ()) # not a function
+
+ with self.assertRaises(SystemError):
+ _testcapi.function_set_closure(function_without_closure, 1)
+ self.assertIsNone(function_without_closure.__closure__) # no change
+
+ # NOTE: this works, but goes against the docs:
+ _testcapi.function_set_closure(function_without_closure, (1, 2))
+ self.assertEqual(
+ _testcapi.function_get_closure(function_without_closure), (1, 2))
+ self.assertEqual(function_without_closure.__closure__, (1, 2))
+
+ # TODO: test PyFunction_New()
+ # TODO: test PyFunction_NewWithQualName()
+ # TODO: test PyFunction_SetVectorcall()
+ # TODO: test PyFunction_GetAnnotations()
+ # TODO: test PyFunction_SetAnnotations()
+ # TODO: test PyClassMethod_New()
+ # TODO: test PyStaticMethod_New()
+ #
+ # PyFunction_AddWatcher() and PyFunction_ClearWatcher() are tested by
+ # test_capi.test_watchers.
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
index efa01a84167002..acc4803d785366 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -928,175 +928,6 @@ def __init__(self):
_testcapi.clear_managed_dict(c)
self.assertEqual(c.__dict__, {})
- def test_function_get_code(self):
- import types
-
- def some():
- pass
-
- code = _testcapi.function_get_code(some)
- self.assertIsInstance(code, types.CodeType)
- self.assertEqual(code, some.__code__)
-
- with self.assertRaises(SystemError):
- _testcapi.function_get_code(None) # not a function
-
- def test_function_get_globals(self):
- def some():
- pass
-
- globals_ = _testcapi.function_get_globals(some)
- self.assertIsInstance(globals_, dict)
- self.assertEqual(globals_, some.__globals__)
-
- with self.assertRaises(SystemError):
- _testcapi.function_get_globals(None) # not a function
-
- def test_function_get_module(self):
- def some():
- pass
-
- module = _testcapi.function_get_module(some)
- self.assertIsInstance(module, str)
- self.assertEqual(module, some.__module__)
-
- with self.assertRaises(SystemError):
- _testcapi.function_get_module(None) # not a function
-
- def test_function_get_defaults(self):
- def some(
- pos_only1, pos_only2='p',
- /,
- zero=0, optional=None,
- *,
- kw1,
- kw2=True,
- ):
- pass
-
- defaults = _testcapi.function_get_defaults(some)
- self.assertEqual(defaults, ('p', 0, None))
- self.assertEqual(defaults, some.__defaults__)
-
- with self.assertRaises(SystemError):
- _testcapi.function_get_defaults(None) # not a function
-
- def test_function_set_defaults(self):
- def some(
- pos_only1, pos_only2='p',
- /,
- zero=0, optional=None,
- *,
- kw1,
- kw2=True,
- ):
- pass
-
- old_defaults = ('p', 0, None)
- self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
- self.assertEqual(some.__defaults__, old_defaults)
-
- with self.assertRaises(SystemError):
- _testcapi.function_set_defaults(some, 1) # not tuple or None
- self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
- self.assertEqual(some.__defaults__, old_defaults)
-
- with self.assertRaises(SystemError):
- _testcapi.function_set_defaults(1, ()) # not a function
- self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
- self.assertEqual(some.__defaults__, old_defaults)
-
- new_defaults = ('q', 1, None)
- _testcapi.function_set_defaults(some, new_defaults)
- self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
- self.assertEqual(some.__defaults__, new_defaults)
-
- # Empty tuple is fine:
- new_defaults = ()
- _testcapi.function_set_defaults(some, new_defaults)
- self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
- self.assertEqual(some.__defaults__, new_defaults)
-
- class tuplesub(tuple): ... # tuple subclasses must work
-
- new_defaults = tuplesub(((1, 2), ['a', 'b'], None))
- _testcapi.function_set_defaults(some, new_defaults)
- self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
- self.assertEqual(some.__defaults__, new_defaults)
-
- # `None` is special, it sets `defaults` to `NULL`,
- # it needs special handling in `_testcapi`:
- _testcapi.function_set_defaults(some, None)
- self.assertEqual(_testcapi.function_get_defaults(some), None)
- self.assertEqual(some.__defaults__, None)
-
- def test_function_get_kw_defaults(self):
- def some(
- pos_only1, pos_only2='p',
- /,
- zero=0, optional=None,
- *,
- kw1,
- kw2=True,
- ):
- pass
-
- defaults = _testcapi.function_get_kw_defaults(some)
- self.assertEqual(defaults, {'kw2': True})
- self.assertEqual(defaults, some.__kwdefaults__)
-
- with self.assertRaises(SystemError):
- _testcapi.function_get_kw_defaults(None) # not a function
-
- def test_function_set_kw_defaults(self):
- def some(
- pos_only1, pos_only2='p',
- /,
- zero=0, optional=None,
- *,
- kw1,
- kw2=True,
- ):
- pass
-
- old_defaults = {'kw2': True}
- self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
- self.assertEqual(some.__kwdefaults__, old_defaults)
-
- with self.assertRaises(SystemError):
- _testcapi.function_set_kw_defaults(some, 1) # not dict or None
- self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
- self.assertEqual(some.__kwdefaults__, old_defaults)
-
- with self.assertRaises(SystemError):
- _testcapi.function_set_kw_defaults(1, {}) # not a function
- self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
- self.assertEqual(some.__kwdefaults__, old_defaults)
-
- new_defaults = {'kw2': (1, 2, 3)}
- _testcapi.function_set_kw_defaults(some, new_defaults)
- self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
- self.assertEqual(some.__kwdefaults__, new_defaults)
-
- # Empty dict is fine:
- new_defaults = {}
- _testcapi.function_set_kw_defaults(some, new_defaults)
- self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
- self.assertEqual(some.__kwdefaults__, new_defaults)
-
- class dictsub(dict): ... # dict subclasses must work
-
- new_defaults = dictsub({'kw2': None})
- _testcapi.function_set_kw_defaults(some, new_defaults)
- self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
- self.assertEqual(some.__kwdefaults__, new_defaults)
-
- # `None` is special, it sets `kwdefaults` to `NULL`,
- # it needs special handling in `_testcapi`:
- _testcapi.function_set_kw_defaults(some, None)
- self.assertEqual(_testcapi.function_get_kw_defaults(some), None)
- self.assertEqual(some.__kwdefaults__, None)
-
def test_unstable_gc_new_with_extra_data(self):
class Data(_testcapi.ObjExtraData):
__slots__ = ('x', 'y')
@@ -1319,127 +1150,6 @@ def test_pyobject_getitemdata_error(self):
_testcapi.pyobject_getitemdata(0)
- def test_function_get_closure(self):
- from types import CellType
-
- def regular_function(): ...
- def unused_one_level(arg1):
- def inner(arg2, arg3): ...
- return inner
- def unused_two_levels(arg1, arg2):
- def decorator(arg3, arg4):
- def inner(arg5, arg6): ...
- return inner
- return decorator
- def with_one_level(arg1):
- def inner(arg2, arg3):
- return arg1 + arg2 + arg3
- return inner
- def with_two_levels(arg1, arg2):
- def decorator(arg3, arg4):
- def inner(arg5, arg6):
- return arg1 + arg2 + arg3 + arg4 + arg5 + arg6
- return inner
- return decorator
-
- # Functions without closures:
- self.assertIsNone(_testcapi.function_get_closure(regular_function))
- self.assertIsNone(regular_function.__closure__)
-
- func = unused_one_level(1)
- closure = _testcapi.function_get_closure(func)
- self.assertIsNone(closure)
- self.assertIsNone(func.__closure__)
-
- func = unused_two_levels(1, 2)(3, 4)
- closure = _testcapi.function_get_closure(func)
- self.assertIsNone(closure)
- self.assertIsNone(func.__closure__)
-
- # Functions with closures:
- func = with_one_level(5)
- closure = _testcapi.function_get_closure(func)
- self.assertEqual(closure, func.__closure__)
- self.assertIsInstance(closure, tuple)
- self.assertEqual(len(closure), 1)
- self.assertEqual(len(closure), len(func.__code__.co_freevars))
- for cell in closure:
- self.assertIsInstance(cell, CellType)
- self.assertTrue(closure[0].cell_contents, 5)
-
- func = with_two_levels(1, 2)(3, 4)
- closure = _testcapi.function_get_closure(func)
- self.assertEqual(closure, func.__closure__)
- self.assertIsInstance(closure, tuple)
- self.assertEqual(len(closure), 4)
- self.assertEqual(len(closure), len(func.__code__.co_freevars))
- for cell in closure:
- self.assertIsInstance(cell, CellType)
- self.assertEqual([cell.cell_contents for cell in closure],
- [1, 2, 3, 4])
-
- def test_function_get_closure_error(self):
- with self.assertRaises(SystemError):
- _testcapi.function_get_closure(1)
- with self.assertRaises(SystemError):
- _testcapi.function_get_closure(None)
-
- def test_function_set_closure(self):
- from types import CellType
-
- def function_without_closure(): ...
- def function_with_closure(arg):
- def inner():
- return arg
- return inner
-
- func = function_without_closure
- _testcapi.function_set_closure(func, (CellType(1), CellType(1)))
- closure = _testcapi.function_get_closure(func)
- self.assertEqual([c.cell_contents for c in closure], [1, 1])
- self.assertEqual([c.cell_contents for c in func.__closure__], [1, 1])
-
- func = function_with_closure(1)
- _testcapi.function_set_closure(func,
- (CellType(1), CellType(2), CellType(3)))
- closure = _testcapi.function_get_closure(func)
- self.assertEqual([c.cell_contents for c in closure], [1, 2, 3])
- self.assertEqual([c.cell_contents for c in func.__closure__], [1, 2, 3])
-
- def test_function_set_closure_none(self):
- def function_without_closure(): ...
- def function_with_closure(arg):
- def inner():
- return arg
- return inner
-
- _testcapi.function_set_closure(function_without_closure, None)
- self.assertIsNone(
- _testcapi.function_get_closure(function_without_closure))
- self.assertIsNone(function_without_closure.__closure__)
-
- _testcapi.function_set_closure(function_with_closure, None)
- self.assertIsNone(
- _testcapi.function_get_closure(function_with_closure))
- self.assertIsNone(function_with_closure.__closure__)
-
- def test_function_set_closure_errors(self):
- def function_without_closure(): ...
-
- with self.assertRaises(SystemError):
- _testcapi.function_set_closure(None, ()) # not a function
-
- with self.assertRaises(SystemError):
- _testcapi.function_set_closure(function_without_closure, 1)
- self.assertIsNone(function_without_closure.__closure__) # no change
-
- # NOTE: this works, but goes against the docs:
- _testcapi.function_set_closure(function_without_closure, (1, 2))
- self.assertEqual(
- _testcapi.function_get_closure(function_without_closure), (1, 2))
- self.assertEqual(function_without_closure.__closure__, (1, 2))
-
-
class TestPendingCalls(unittest.TestCase):
# See the comment in ceval.c (at the "handle_eval_breaker" label)
@@ -3209,5 +2919,6 @@ def test_pack_version_ctypes(self):
result = ctypes_func(*args)
self.assertEqual(result, expected)
+
if __name__ == "__main__":
unittest.main()
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index a276fa642de42e..6bb05a06a3465d 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -162,7 +162,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_testcapi/function.c b/Modules/_testcapi/function.c
new file mode 100644
index 00000000000000..ec1ba508df2ce9
--- /dev/null
+++ b/Modules/_testcapi/function.c
@@ -0,0 +1,143 @@
+#include "parts.h"
+#include "util.h"
+
+
+static PyObject *
+function_get_code(PyObject *self, PyObject *func)
+{
+ PyObject *code = PyFunction_GetCode(func);
+ if (code != NULL) {
+ return Py_NewRef(code);
+ } else {
+ return NULL;
+ }
+}
+
+
+static PyObject *
+function_get_globals(PyObject *self, PyObject *func)
+{
+ PyObject *globals = PyFunction_GetGlobals(func);
+ if (globals != NULL) {
+ return Py_NewRef(globals);
+ } else {
+ return NULL;
+ }
+}
+
+
+static PyObject *
+function_get_module(PyObject *self, PyObject *func)
+{
+ PyObject *module = PyFunction_GetModule(func);
+ if (module != NULL) {
+ return Py_NewRef(module);
+ } else {
+ return NULL;
+ }
+}
+
+
+static PyObject *
+function_get_defaults(PyObject *self, PyObject *func)
+{
+ PyObject *defaults = PyFunction_GetDefaults(func);
+ if (defaults != NULL) {
+ return Py_NewRef(defaults);
+ } else if (PyErr_Occurred()) {
+ return NULL;
+ } else {
+ Py_RETURN_NONE; // This can happen when `defaults` are set to `None`
+ }
+}
+
+
+static PyObject *
+function_set_defaults(PyObject *self, PyObject *args)
+{
+ PyObject *func = NULL, *defaults = NULL;
+ if (!PyArg_ParseTuple(args, "OO", &func, &defaults)) {
+ return NULL;
+ }
+ int result = PyFunction_SetDefaults(func, defaults);
+ if (result == -1)
+ return NULL;
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *
+function_get_kw_defaults(PyObject *self, PyObject *func)
+{
+ PyObject *defaults = PyFunction_GetKwDefaults(func);
+ if (defaults != NULL) {
+ return Py_NewRef(defaults);
+ } else if (PyErr_Occurred()) {
+ return NULL;
+ } else {
+ Py_RETURN_NONE; // This can happen when `kwdefaults` are set to `None`
+ }
+}
+
+
+static PyObject *
+function_set_kw_defaults(PyObject *self, PyObject *args)
+{
+ PyObject *func = NULL, *defaults = NULL;
+ if (!PyArg_ParseTuple(args, "OO", &func, &defaults)) {
+ return NULL;
+ }
+ int result = PyFunction_SetKwDefaults(func, defaults);
+ if (result == -1)
+ return NULL;
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *
+function_get_closure(PyObject *self, PyObject *func)
+{
+ PyObject *closure = PyFunction_GetClosure(func);
+ if (closure != NULL) {
+ return Py_NewRef(closure);
+ } else if (PyErr_Occurred()) {
+ return NULL;
+ } else {
+ Py_RETURN_NONE; // This can happen when `closure` is set to `None`
+ }
+}
+
+
+static PyObject *
+function_set_closure(PyObject *self, PyObject *args)
+{
+ PyObject *func = NULL, *closure = NULL;
+ if (!PyArg_ParseTuple(args, "OO", &func, &closure)) {
+ return NULL;
+ }
+ int result = PyFunction_SetClosure(func, closure);
+ if (result == -1) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+
+static PyMethodDef test_methods[] = {
+ {"function_get_code", function_get_code, METH_O, NULL},
+ {"function_get_globals", function_get_globals, METH_O, NULL},
+ {"function_get_module", function_get_module, METH_O, NULL},
+ {"function_get_defaults", function_get_defaults, METH_O, NULL},
+ {"function_set_defaults", function_set_defaults, METH_VARARGS, NULL},
+ {"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
+ {"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
+ {"function_get_closure", function_get_closure, METH_O, NULL},
+ {"function_set_closure", function_set_closure, METH_VARARGS, NULL},
+ {NULL},
+};
+
+int
+_PyTestCapi_Init_Function(PyObject *m)
+{
+ return PyModule_AddFunctions(m, test_methods);
+}
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index c4b73459b3712b..af6400162daf2b 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -64,5 +64,6 @@ int _PyTestCapi_Init_Config(PyObject *mod);
int _PyTestCapi_Init_Import(PyObject *mod);
int _PyTestCapi_Init_Frame(PyObject *mod);
int _PyTestCapi_Init_Type(PyObject *mod);
+int _PyTestCapi_Init_Function(PyObject *mod);
#endif // Py_TESTCAPI_PARTS_H
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index ad9d836120b506..5b5b630e7e7581 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2652,119 +2652,6 @@ test_macros(PyObject *self, PyObject *Py_UNUSED(args))
Py_RETURN_NONE;
}
-static PyObject *
-function_get_code(PyObject *self, PyObject *func)
-{
- PyObject *code = PyFunction_GetCode(func);
- if (code != NULL) {
- return Py_NewRef(code);
- } else {
- return NULL;
- }
-}
-
-static PyObject *
-function_get_globals(PyObject *self, PyObject *func)
-{
- PyObject *globals = PyFunction_GetGlobals(func);
- if (globals != NULL) {
- return Py_NewRef(globals);
- } else {
- return NULL;
- }
-}
-
-static PyObject *
-function_get_module(PyObject *self, PyObject *func)
-{
- PyObject *module = PyFunction_GetModule(func);
- if (module != NULL) {
- return Py_NewRef(module);
- } else {
- return NULL;
- }
-}
-
-static PyObject *
-function_get_defaults(PyObject *self, PyObject *func)
-{
- PyObject *defaults = PyFunction_GetDefaults(func);
- if (defaults != NULL) {
- return Py_NewRef(defaults);
- } else if (PyErr_Occurred()) {
- return NULL;
- } else {
- Py_RETURN_NONE; // This can happen when `defaults` are set to `None`
- }
-}
-
-static PyObject *
-function_set_defaults(PyObject *self, PyObject *args)
-{
- PyObject *func = NULL, *defaults = NULL;
- if (!PyArg_ParseTuple(args, "OO", &func, &defaults)) {
- return NULL;
- }
- int result = PyFunction_SetDefaults(func, defaults);
- if (result == -1)
- return NULL;
- Py_RETURN_NONE;
-}
-
-static PyObject *
-function_get_kw_defaults(PyObject *self, PyObject *func)
-{
- PyObject *defaults = PyFunction_GetKwDefaults(func);
- if (defaults != NULL) {
- return Py_NewRef(defaults);
- } else if (PyErr_Occurred()) {
- return NULL;
- } else {
- Py_RETURN_NONE; // This can happen when `kwdefaults` are set to `None`
- }
-}
-
-static PyObject *
-function_set_kw_defaults(PyObject *self, PyObject *args)
-{
- PyObject *func = NULL, *defaults = NULL;
- if (!PyArg_ParseTuple(args, "OO", &func, &defaults)) {
- return NULL;
- }
- int result = PyFunction_SetKwDefaults(func, defaults);
- if (result == -1)
- return NULL;
- Py_RETURN_NONE;
-}
-
-static PyObject *
-function_get_closure(PyObject *self, PyObject *func)
-{
- PyObject *closure = PyFunction_GetClosure(func);
- if (closure != NULL) {
- return Py_NewRef(closure);
- } else if (PyErr_Occurred()) {
- return NULL;
- } else {
- Py_RETURN_NONE; // This can happen when `closure` is set to `None`
- }
-}
-
-static PyObject *
-function_set_closure(PyObject *self, PyObject *args)
-{
- PyObject *func = NULL, *closure = NULL;
- if (!PyArg_ParseTuple(args, "OO", &func, &closure)) {
- return NULL;
- }
- int result = PyFunction_SetClosure(func, closure);
- if (result == -1) {
- return NULL;
- }
- Py_RETURN_NONE;
-}
-
-
static PyObject *
test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
@@ -3286,15 +3173,6 @@ static PyMethodDef TestMethods[] = {
{"settrace_to_record", settrace_to_record, METH_O, NULL},
{"test_macros", test_macros, METH_NOARGS, NULL},
{"clear_managed_dict", clear_managed_dict, METH_O, NULL},
- {"function_get_code", function_get_code, METH_O, NULL},
- {"function_get_globals", function_get_globals, METH_O, NULL},
- {"function_get_module", function_get_module, METH_O, NULL},
- {"function_get_defaults", function_get_defaults, METH_O, NULL},
- {"function_set_defaults", function_set_defaults, METH_VARARGS, NULL},
- {"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
- {"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
- {"function_get_closure", function_get_closure, METH_O, NULL},
- {"function_set_closure", function_set_closure, METH_VARARGS, NULL},
{"test_weakref_capi", test_weakref_capi, METH_NOARGS},
{"function_set_warning", function_set_warning, METH_NOARGS},
{"test_critical_sections", test_critical_sections, METH_NOARGS},
@@ -4081,6 +3959,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_Type(m) < 0) {
return NULL;
}
+ if (_PyTestCapi_Init_Function(m) < 0) {
+ return NULL;
+ }
PyState_AddModule(m, &_testcapimodule);
return m;
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index 09969331c6edd4..a68f15d25aabb7 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -130,6 +130,7 @@
<ClCompile Include="..\Modules\_testcapi\import.c" />
<ClCompile Include="..\Modules\_testcapi\frame.c" />
<ClCompile Include="..\Modules\_testcapi\type.c" />
+ <ClCompile Include="..\Modules\_testcapi\function.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index 52491643ad842f..21091e9dc1aa16 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -123,6 +123,9 @@
<ClCompile Include="..\Modules\_testcapi\type.c">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\Modules\_testcapi\function.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc">
1
0
[3.13] gh-126108: Fix potential null pointer dereference in `PySys_AddWarnOptionUnicode` (GH-126118) (#129520)
by kumaraditya303 Jan. 31, 2025
by kumaraditya303 Jan. 31, 2025
Jan. 31, 2025
https://github.com/python/cpython/commit/0468ea12305ef5a0a3d1dc4af8a82fb94d…
commit: 0468ea12305ef5a0a3d1dc4af8a82fb94d202cd6
branch: 3.13
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: kumaraditya303 <kumaraditya(a)python.org>
date: 2025-01-31T20:32:44+05:30
summary:
[3.13] gh-126108: Fix potential null pointer dereference in `PySys_AddWarnOptionUnicode` (GH-126118) (#129520)
gh-126108: Fix potential null pointer dereference in `PySys_AddWarnOptionUnicode` (GH-126118)
(cherry picked from commit fad36bf38248130bc48b81a5e7c31a7649a6456e)
Co-authored-by: Valery Fedorenko <federicovalenso(a)gmail.com>
files:
A Misc/NEWS.d/next/Security/2024-10-29-09-15-10.gh-issue-126108.eTIjHY.rst
M Python/sysmodule.c
diff --git a/Misc/NEWS.d/next/Security/2024-10-29-09-15-10.gh-issue-126108.eTIjHY.rst b/Misc/NEWS.d/next/Security/2024-10-29-09-15-10.gh-issue-126108.eTIjHY.rst
new file mode 100644
index 00000000000000..9f2c7e84d4dff0
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2024-10-29-09-15-10.gh-issue-126108.eTIjHY.rst
@@ -0,0 +1 @@
+Fix a possible ``NULL`` pointer dereference in :c:func:`!PySys_AddWarnOptionUnicode`.
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index 3f170fff156fcd..9cf4a580d4408f 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -2841,6 +2841,7 @@ PySys_ResetWarnOptions(void)
static int
_PySys_AddWarnOptionWithError(PyThreadState *tstate, PyObject *option)
{
+ assert(tstate != NULL);
PyObject *warnoptions = get_warnoptions(tstate);
if (warnoptions == NULL) {
return -1;
@@ -2856,11 +2857,11 @@ PyAPI_FUNC(void)
PySys_AddWarnOptionUnicode(PyObject *option)
{
PyThreadState *tstate = _PyThreadState_GET();
+ _Py_EnsureTstateNotNULL(tstate);
+ assert(!_PyErr_Occurred(tstate));
if (_PySys_AddWarnOptionWithError(tstate, option) < 0) {
/* No return value, therefore clear error state if possible */
- if (tstate) {
- _PyErr_Clear(tstate);
- }
+ _PyErr_Clear(tstate);
}
}
1
0
[3.12] gh-126108: Fix potential null pointer dereference in `PySys_AddWarnOptionUnicode` (GH-126118) (#129522)
by kumaraditya303 Jan. 31, 2025
by kumaraditya303 Jan. 31, 2025
Jan. 31, 2025
https://github.com/python/cpython/commit/40d7f745e95883c169b1e1d8e2fa60fd89…
commit: 40d7f745e95883c169b1e1d8e2fa60fd89298771
branch: 3.12
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: kumaraditya303 <kumaraditya(a)python.org>
date: 2025-01-31T20:32:30+05:30
summary:
[3.12] gh-126108: Fix potential null pointer dereference in `PySys_AddWarnOptionUnicode` (GH-126118) (#129522)
gh-126108: Fix potential null pointer dereference in `PySys_AddWarnOptionUnicode` (GH-126118)
(cherry picked from commit fad36bf38248130bc48b81a5e7c31a7649a6456e)
Co-authored-by: Valery Fedorenko <federicovalenso(a)gmail.com>
files:
A Misc/NEWS.d/next/Security/2024-10-29-09-15-10.gh-issue-126108.eTIjHY.rst
M Python/sysmodule.c
diff --git a/Misc/NEWS.d/next/Security/2024-10-29-09-15-10.gh-issue-126108.eTIjHY.rst b/Misc/NEWS.d/next/Security/2024-10-29-09-15-10.gh-issue-126108.eTIjHY.rst
new file mode 100644
index 00000000000000..9f2c7e84d4dff0
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2024-10-29-09-15-10.gh-issue-126108.eTIjHY.rst
@@ -0,0 +1 @@
+Fix a possible ``NULL`` pointer dereference in :c:func:`!PySys_AddWarnOptionUnicode`.
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index d41fe8223deb3f..1f7cc655c45ed0 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -2653,6 +2653,7 @@ PySys_ResetWarnOptions(void)
static int
_PySys_AddWarnOptionWithError(PyThreadState *tstate, PyObject *option)
{
+ assert(tstate != NULL);
PyObject *warnoptions = get_warnoptions(tstate);
if (warnoptions == NULL) {
return -1;
@@ -2667,11 +2668,11 @@ void
PySys_AddWarnOptionUnicode(PyObject *option)
{
PyThreadState *tstate = _PyThreadState_GET();
+ _Py_EnsureTstateNotNULL(tstate);
+ assert(!_PyErr_Occurred(tstate));
if (_PySys_AddWarnOptionWithError(tstate, option) < 0) {
/* No return value, therefore clear error state if possible */
- if (tstate) {
- _PyErr_Clear(tstate);
- }
+ _PyErr_Clear(tstate);
}
}
1
0
gh-126108: Fix potential null pointer dereference in `PySys_AddWarnOptionUnicode` (#126118)
by kumaraditya303 Jan. 31, 2025
by kumaraditya303 Jan. 31, 2025
Jan. 31, 2025
https://github.com/python/cpython/commit/fad36bf38248130bc48b81a5e7c31a7649…
commit: fad36bf38248130bc48b81a5e7c31a7649a6456e
branch: main
author: Valery Fedorenko <federicovalenso(a)gmail.com>
committer: kumaraditya303 <kumaraditya(a)python.org>
date: 2025-01-31T20:06:30+05:30
summary:
gh-126108: Fix potential null pointer dereference in `PySys_AddWarnOptionUnicode` (#126118)
files:
A Misc/NEWS.d/next/Security/2024-10-29-09-15-10.gh-issue-126108.eTIjHY.rst
M Python/sysmodule.c
diff --git a/Misc/NEWS.d/next/Security/2024-10-29-09-15-10.gh-issue-126108.eTIjHY.rst b/Misc/NEWS.d/next/Security/2024-10-29-09-15-10.gh-issue-126108.eTIjHY.rst
new file mode 100644
index 00000000000000..9f2c7e84d4dff0
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2024-10-29-09-15-10.gh-issue-126108.eTIjHY.rst
@@ -0,0 +1 @@
+Fix a possible ``NULL`` pointer dereference in :c:func:`!PySys_AddWarnOptionUnicode`.
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index 7e4cb45af05672..11b96c8455de14 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -2847,6 +2847,7 @@ PySys_ResetWarnOptions(void)
static int
_PySys_AddWarnOptionWithError(PyThreadState *tstate, PyObject *option)
{
+ assert(tstate != NULL);
PyObject *warnoptions = get_warnoptions(tstate);
if (warnoptions == NULL) {
return -1;
@@ -2862,11 +2863,11 @@ PyAPI_FUNC(void)
PySys_AddWarnOptionUnicode(PyObject *option)
{
PyThreadState *tstate = _PyThreadState_GET();
+ _Py_EnsureTstateNotNULL(tstate);
+ assert(!_PyErr_Occurred(tstate));
if (_PySys_AddWarnOptionWithError(tstate, option) < 0) {
/* No return value, therefore clear error state if possible */
- if (tstate) {
- _PyErr_Clear(tstate);
- }
+ _PyErr_Clear(tstate);
}
}
1
0
Jan. 31, 2025
https://github.com/python/cpython/commit/7eaef74561c27865496505913d19eec7bb…
commit: 7eaef74561c27865496505913d19eec7bb3fbcf5
branch: main
author: Pablo Galindo Salgado <Pablogsal(a)gmail.com>
committer: pablogsal <Pablogsal(a)gmail.com>
date: 2025-01-31T14:08:48Z
summary:
gh-129430: Make walking vm regions more efficient in MacOS (#129494)
files:
M Modules/_testexternalinspection.c
diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c
index 5a28c0e266226b..22074c81b7405f 100644
--- a/Modules/_testexternalinspection.c
+++ b/Modules/_testexternalinspection.c
@@ -232,15 +232,15 @@ search_map_for_section(pid_t pid, const char* secname, const char* substr) {
&count,
&object_name) == KERN_SUCCESS)
{
- int path_len = proc_regionfilename(
- pid, address, map_filename, MAXPATHLEN);
- if (path_len == 0) {
+ if ((region_info.protection & VM_PROT_READ) == 0
+ || (region_info.protection & VM_PROT_EXECUTE) == 0) {
address += size;
continue;
}
- if ((region_info.protection & VM_PROT_READ) == 0
- || (region_info.protection & VM_PROT_EXECUTE) == 0) {
+ int path_len = proc_regionfilename(
+ pid, address, map_filename, MAXPATHLEN);
+ if (path_len == 0) {
address += size;
continue;
}
1
0
https://github.com/python/cpython/commit/79f85a0bc1c3b3c30b2f979033eef9840e…
commit: 79f85a0bc1c3b3c30b2f979033eef9840e21db31
branch: main
author: Victor Stinner <vstinner(a)python.org>
committer: vstinner <vstinner(a)python.org>
date: 2025-01-31T15:06:14+01:00
summary:
gh-129354: Use PyErr_FormatUnraisable() function (#129518)
Replace PyErr_WriteUnraisable() with PyErr_FormatUnraisable().
files:
M Modules/_ssl.c
M Modules/_threadmodule.c
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index c15a582a92aa4a..85e917fbbb7093 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -4666,7 +4666,8 @@ _servername_callback(SSL *s, int *al, void *args)
servername_bytes = PyBytes_FromString(servername);
if (servername_bytes == NULL) {
- PyErr_WriteUnraisable((PyObject *) sslctx);
+ PyErr_FormatUnraisable("Exception ignored "
+ "in ssl servername callback");
goto error;
}
/* server_hostname was encoded to an A-label by our caller; put it
@@ -4674,7 +4675,10 @@ _servername_callback(SSL *s, int *al, void *args)
*/
servername_str = PyUnicode_FromEncodedObject(servername_bytes, "ascii", NULL);
if (servername_str == NULL) {
- PyErr_WriteUnraisable(servername_bytes);
+ PyErr_FormatUnraisable("Exception ignored "
+ "in ssl servername callback "
+ "while decoding name %R",
+ servername_bytes);
Py_DECREF(servername_bytes);
goto error;
}
@@ -4687,7 +4691,10 @@ _servername_callback(SSL *s, int *al, void *args)
Py_DECREF(ssl_socket);
if (result == NULL) {
- PyErr_WriteUnraisable(sslctx->set_sni_cb);
+ PyErr_FormatUnraisable("Exception ignored "
+ "in ssl servername callback "
+ "while calling set SNI callback %R",
+ sslctx->set_sni_cb);
*al = SSL_AD_HANDSHAKE_FAILURE;
ret = SSL_TLSEXT_ERR_ALERT_FATAL;
}
@@ -4700,7 +4707,11 @@ _servername_callback(SSL *s, int *al, void *args)
} else {
*al = (int) PyLong_AsLong(result);
if (PyErr_Occurred()) {
- PyErr_WriteUnraisable(result);
+ PyErr_FormatUnraisable("Exception ignored "
+ "in ssl servername callback "
+ "while calling set SNI callback "
+ "(result=%R)",
+ result);
*al = SSL_AD_INTERNAL_ERROR;
}
ret = SSL_TLSEXT_ERR_ALERT_FATAL;
@@ -5007,7 +5018,8 @@ static unsigned int psk_client_callback(SSL *s,
error:
if (PyErr_Occurred()) {
- PyErr_WriteUnraisable(callback);
+ PyErr_FormatUnraisable("Exception ignored in ssl PSK client callback "
+ "while calling callback %R", callback);
}
PyGILState_Release(gstate);
return 0;
@@ -5116,7 +5128,8 @@ static unsigned int psk_server_callback(SSL *s,
error:
if (PyErr_Occurred()) {
- PyErr_WriteUnraisable(callback);
+ PyErr_FormatUnraisable("Exception ignored in ssl PSK server callback "
+ "while calling callback %R", callback);
}
PyGILState_Release(gstate);
return 0;
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index dbc574f7816b85..e251736fb36aa9 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -1539,17 +1539,20 @@ create_localsdict(localobject *self, thread_module_state *state,
goto err;
}
- if (PyDict_SetItem(self->localdicts, tstate->threading_local_key, ldict) <
- 0) {
+ if (PyDict_SetItem(self->localdicts, tstate->threading_local_key,
+ ldict) < 0)
+ {
goto err;
}
wr = create_sentinel_wr(self);
if (wr == NULL) {
PyObject *exc = PyErr_GetRaisedException();
- if (PyDict_DelItem(self->localdicts, tstate->threading_local_key) <
- 0) {
- PyErr_WriteUnraisable((PyObject *)self);
+ if (PyDict_DelItem(self->localdicts,
+ tstate->threading_local_key) < 0)
+ {
+ PyErr_FormatUnraisable("Exception ignored while deleting "
+ "thread local of %R", self);
}
PyErr_SetRaisedException(exc);
goto err;
@@ -1557,9 +1560,11 @@ create_localsdict(localobject *self, thread_module_state *state,
if (PySet_Add(self->thread_watchdogs, wr) < 0) {
PyObject *exc = PyErr_GetRaisedException();
- if (PyDict_DelItem(self->localdicts, tstate->threading_local_key) <
- 0) {
- PyErr_WriteUnraisable((PyObject *)self);
+ if (PyDict_DelItem(self->localdicts,
+ tstate->threading_local_key) < 0)
+ {
+ PyErr_FormatUnraisable("Exception ignored while deleting "
+ "thread local of %R", self);
}
PyErr_SetRaisedException(exc);
goto err;
@@ -1609,13 +1614,16 @@ _ldict(localobject *self, thread_module_state *state)
we create a new one the next time we do an attr
access */
PyObject *exc = PyErr_GetRaisedException();
- if (PyDict_DelItem(self->localdicts, tstate->threading_local_key) <
- 0) {
- PyErr_WriteUnraisable((PyObject *)self);
- PyErr_Clear();
+ if (PyDict_DelItem(self->localdicts,
+ tstate->threading_local_key) < 0)
+ {
+ PyErr_FormatUnraisable("Exception ignored while deleting "
+ "thread local of %R", self);
+ assert(!PyErr_Occurred());
}
if (PySet_Discard(self->thread_watchdogs, wr) < 0) {
- PyErr_WriteUnraisable((PyObject *)self);
+ PyErr_FormatUnraisable("Exception ignored while discarding "
+ "thread watchdog of %R", self);
}
PyErr_SetRaisedException(exc);
Py_DECREF(ldict);
@@ -1746,12 +1754,14 @@ clear_locals(PyObject *locals_and_key, PyObject *dummyweakref)
if (self->localdicts != NULL) {
PyObject *key = PyTuple_GetItem(locals_and_key, 1);
if (PyDict_Pop(self->localdicts, key, NULL) < 0) {
- PyErr_WriteUnraisable((PyObject*)self);
+ PyErr_FormatUnraisable("Exception ignored while clearing "
+ "thread local %R", (PyObject *)self);
}
}
if (self->thread_watchdogs != NULL) {
if (PySet_Discard(self->thread_watchdogs, dummyweakref) < 0) {
- PyErr_WriteUnraisable((PyObject *)self);
+ PyErr_FormatUnraisable("Exception ignored while clearing "
+ "thread local %R", (PyObject *)self);
}
}
@@ -2314,7 +2324,8 @@ thread_shutdown(PyObject *self, PyObject *args)
// Wait for the thread to finish. If we're interrupted, such
// as by a ctrl-c we print the error and exit early.
if (ThreadHandle_join(handle, -1) < 0) {
- PyErr_WriteUnraisable(NULL);
+ PyErr_FormatUnraisable("Exception ignored while joining a thread "
+ "in _thread._shutdown()");
ThreadHandle_decref(handle);
Py_RETURN_NONE;
}
1
0
https://github.com/python/cpython/commit/5424e3b0348c6029e96d4cb106cbcd5cca…
commit: 5424e3b0348c6029e96d4cb106cbcd5cca86d980
branch: main
author: Victor Stinner <vstinner(a)python.org>
committer: vstinner <vstinner(a)python.org>
date: 2025-01-31T15:03:54+01:00
summary:
gh-93649: Add Modules/_testcapi/type.c file (#129516)
Move PyType C API tests to a new file.
Move following tests from test_capi.test_misc to test_capi.test_type:
* BuiltinStaticTypesTests
* test_get_type_name()
* test_get_base_by_token()
files:
A Modules/_testcapi/type.c
M Lib/test/test_capi/test_misc.py
M Lib/test/test_capi/test_type.py
M Modules/Setup.stdlib.in
M Modules/_testcapi/parts.h
M Modules/_testcapimodule.c
M PCbuild/_testcapi.vcxproj
M PCbuild/_testcapi.vcxproj.filters
diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
index 1087b38c225085..efa01a84167002 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -1111,147 +1111,6 @@ class Data(_testcapi.ObjExtraData):
del d.extra
self.assertIsNone(d.extra)
- def test_get_type_name(self):
- class MyType:
- pass
-
- from _testcapi import (
- get_type_name, get_type_qualname,
- get_type_fullyqualname, get_type_module_name)
-
- from collections import OrderedDict
- ht = _testcapi.get_heaptype_for_name()
- for cls, fullname, modname, qualname, name in (
- (int,
- 'int',
- 'builtins',
- 'int',
- 'int'),
- (OrderedDict,
- 'collections.OrderedDict',
- 'collections',
- 'OrderedDict',
- 'OrderedDict'),
- (ht,
- '_testcapi.HeapTypeNameType',
- '_testcapi',
- 'HeapTypeNameType',
- 'HeapTypeNameType'),
- (MyType,
- f'{__name__}.CAPITest.test_get_type_name.<locals>.MyType',
- __name__,
- 'CAPITest.test_get_type_name.<locals>.MyType',
- 'MyType'),
- ):
- with self.subTest(cls=repr(cls)):
- self.assertEqual(get_type_fullyqualname(cls), fullname)
- self.assertEqual(get_type_module_name(cls), modname)
- self.assertEqual(get_type_qualname(cls), qualname)
- self.assertEqual(get_type_name(cls), name)
-
- # override __module__
- ht.__module__ = 'test_module'
- self.assertEqual(get_type_fullyqualname(ht), 'test_module.HeapTypeNameType')
- self.assertEqual(get_type_module_name(ht), 'test_module')
- self.assertEqual(get_type_qualname(ht), 'HeapTypeNameType')
- self.assertEqual(get_type_name(ht), 'HeapTypeNameType')
-
- # override __name__ and __qualname__
- MyType.__name__ = 'my_name'
- MyType.__qualname__ = 'my_qualname'
- self.assertEqual(get_type_fullyqualname(MyType), f'{__name__}.my_qualname')
- self.assertEqual(get_type_module_name(MyType), __name__)
- self.assertEqual(get_type_qualname(MyType), 'my_qualname')
- self.assertEqual(get_type_name(MyType), 'my_name')
-
- # override also __module__
- MyType.__module__ = 'my_module'
- self.assertEqual(get_type_fullyqualname(MyType), 'my_module.my_qualname')
- self.assertEqual(get_type_module_name(MyType), 'my_module')
- self.assertEqual(get_type_qualname(MyType), 'my_qualname')
- self.assertEqual(get_type_name(MyType), 'my_name')
-
- # PyType_GetFullyQualifiedName() ignores the module if it's "builtins"
- # or "__main__" of it is not a string
- MyType.__module__ = 'builtins'
- self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
- MyType.__module__ = '__main__'
- self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
- MyType.__module__ = 123
- self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
-
- def test_get_base_by_token(self):
- def get_base_by_token(src, key, comparable=True):
- def run(use_mro):
- find_first = _testcapi.pytype_getbasebytoken
- ret1, result = find_first(src, key, use_mro, True)
- ret2, no_result = find_first(src, key, use_mro, False)
- self.assertIn(ret1, (0, 1))
- self.assertEqual(ret1, result is not None)
- self.assertEqual(ret1, ret2)
- self.assertIsNone(no_result)
- return result
-
- found_in_mro = run(True)
- found_in_bases = run(False)
- if comparable:
- self.assertIs(found_in_mro, found_in_bases)
- return found_in_mro
- return found_in_mro, found_in_bases
-
- create_type = _testcapi.create_type_with_token
- get_token = _testcapi.get_tp_token
-
- Py_TP_USE_SPEC = _testcapi.Py_TP_USE_SPEC
- self.assertEqual(Py_TP_USE_SPEC, 0)
-
- A1 = create_type('_testcapi.A1', Py_TP_USE_SPEC)
- self.assertTrue(get_token(A1) != Py_TP_USE_SPEC)
-
- B1 = create_type('_testcapi.B1', id(self))
- self.assertTrue(get_token(B1) == id(self))
-
- tokenA1 = get_token(A1)
- # find A1 from A1
- found = get_base_by_token(A1, tokenA1)
- self.assertIs(found, A1)
-
- # no token in static types
- STATIC = type(1)
- self.assertEqual(get_token(STATIC), 0)
- found = get_base_by_token(STATIC, tokenA1)
- self.assertIs(found, None)
-
- # no token in pure subtypes
- class A2(A1): pass
- self.assertEqual(get_token(A2), 0)
- # find A1
- class Z(STATIC, B1, A2): pass
- found = get_base_by_token(Z, tokenA1)
- self.assertIs(found, A1)
-
- # searching for NULL token is an error
- with self.assertRaises(SystemError):
- get_base_by_token(Z, 0)
- with self.assertRaises(SystemError):
- get_base_by_token(STATIC, 0)
-
- # share the token with A1
- C1 = create_type('_testcapi.C1', tokenA1)
- self.assertTrue(get_token(C1) == tokenA1)
-
- # find C1 first by shared token
- class Z(C1, A2): pass
- found = get_base_by_token(Z, tokenA1)
- self.assertIs(found, C1)
- # B1 not found
- found = get_base_by_token(Z, get_token(B1))
- self.assertIs(found, None)
-
- with self.assertRaises(TypeError):
- _testcapi.pytype_getbasebytoken(
- 'not a type', id(self), True, False)
-
def test_gen_get_code(self):
def genf(): yield
gen = genf()
@@ -2922,39 +2781,6 @@ def test_linked_lifecycle_link_incref_unlink_decref(self):
0, get_refcount(interpid))
-class BuiltinStaticTypesTests(unittest.TestCase):
-
- TYPES = [
- object,
- type,
- int,
- str,
- dict,
- type(None),
- bool,
- BaseException,
- Exception,
- Warning,
- DeprecationWarning, # Warning subclass
- ]
-
- def test_tp_bases_is_set(self):
- # PyTypeObject.tp_bases is documented as public API.
- # See https://github.com/python/cpython/issues/105020.
- for typeobj in self.TYPES:
- with self.subTest(typeobj):
- bases = _testcapi.type_get_tp_bases(typeobj)
- self.assertIsNot(bases, None)
-
- def test_tp_mro_is_set(self):
- # PyTypeObject.tp_bases is documented as public API.
- # See https://github.com/python/cpython/issues/105020.
- for typeobj in self.TYPES:
- with self.subTest(typeobj):
- mro = _testcapi.type_get_tp_mro(typeobj)
- self.assertIsNot(mro, None)
-
-
class TestStaticTypes(unittest.TestCase):
_has_run = False
diff --git a/Lib/test/test_capi/test_type.py b/Lib/test/test_capi/test_type.py
index ffcaae73bca236..7e5d013d737ab0 100644
--- a/Lib/test/test_capi/test_type.py
+++ b/Lib/test/test_capi/test_type.py
@@ -4,7 +4,181 @@
_testcapi = import_helper.import_module('_testcapi')
+class BuiltinStaticTypesTests(unittest.TestCase):
+
+ TYPES = [
+ object,
+ type,
+ int,
+ str,
+ dict,
+ type(None),
+ bool,
+ BaseException,
+ Exception,
+ Warning,
+ DeprecationWarning, # Warning subclass
+ ]
+
+ def test_tp_bases_is_set(self):
+ # PyTypeObject.tp_bases is documented as public API.
+ # See https://github.com/python/cpython/issues/105020.
+ for typeobj in self.TYPES:
+ with self.subTest(typeobj):
+ bases = _testcapi.type_get_tp_bases(typeobj)
+ self.assertIsNot(bases, None)
+
+ def test_tp_mro_is_set(self):
+ # PyTypeObject.tp_bases is documented as public API.
+ # See https://github.com/python/cpython/issues/105020.
+ for typeobj in self.TYPES:
+ with self.subTest(typeobj):
+ mro = _testcapi.type_get_tp_mro(typeobj)
+ self.assertIsNot(mro, None)
+
+
class TypeTests(unittest.TestCase):
+ def test_get_type_name(self):
+ class MyType:
+ pass
+
+ from _testcapi import (
+ get_type_name, get_type_qualname,
+ get_type_fullyqualname, get_type_module_name)
+
+ from collections import OrderedDict
+ ht = _testcapi.get_heaptype_for_name()
+ for cls, fullname, modname, qualname, name in (
+ (int,
+ 'int',
+ 'builtins',
+ 'int',
+ 'int'),
+ (OrderedDict,
+ 'collections.OrderedDict',
+ 'collections',
+ 'OrderedDict',
+ 'OrderedDict'),
+ (ht,
+ '_testcapi.HeapTypeNameType',
+ '_testcapi',
+ 'HeapTypeNameType',
+ 'HeapTypeNameType'),
+ (MyType,
+ f'{__name__}.TypeTests.test_get_type_name.<locals>.MyType',
+ __name__,
+ 'TypeTests.test_get_type_name.<locals>.MyType',
+ 'MyType'),
+ ):
+ with self.subTest(cls=repr(cls)):
+ self.assertEqual(get_type_fullyqualname(cls), fullname)
+ self.assertEqual(get_type_module_name(cls), modname)
+ self.assertEqual(get_type_qualname(cls), qualname)
+ self.assertEqual(get_type_name(cls), name)
+
+ # override __module__
+ ht.__module__ = 'test_module'
+ self.assertEqual(get_type_fullyqualname(ht), 'test_module.HeapTypeNameType')
+ self.assertEqual(get_type_module_name(ht), 'test_module')
+ self.assertEqual(get_type_qualname(ht), 'HeapTypeNameType')
+ self.assertEqual(get_type_name(ht), 'HeapTypeNameType')
+
+ # override __name__ and __qualname__
+ MyType.__name__ = 'my_name'
+ MyType.__qualname__ = 'my_qualname'
+ self.assertEqual(get_type_fullyqualname(MyType), f'{__name__}.my_qualname')
+ self.assertEqual(get_type_module_name(MyType), __name__)
+ self.assertEqual(get_type_qualname(MyType), 'my_qualname')
+ self.assertEqual(get_type_name(MyType), 'my_name')
+
+ # override also __module__
+ MyType.__module__ = 'my_module'
+ self.assertEqual(get_type_fullyqualname(MyType), 'my_module.my_qualname')
+ self.assertEqual(get_type_module_name(MyType), 'my_module')
+ self.assertEqual(get_type_qualname(MyType), 'my_qualname')
+ self.assertEqual(get_type_name(MyType), 'my_name')
+
+ # PyType_GetFullyQualifiedName() ignores the module if it's "builtins"
+ # or "__main__" of it is not a string
+ MyType.__module__ = 'builtins'
+ self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
+ MyType.__module__ = '__main__'
+ self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
+ MyType.__module__ = 123
+ self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname')
+
+ def test_get_base_by_token(self):
+ def get_base_by_token(src, key, comparable=True):
+ def run(use_mro):
+ find_first = _testcapi.pytype_getbasebytoken
+ ret1, result = find_first(src, key, use_mro, True)
+ ret2, no_result = find_first(src, key, use_mro, False)
+ self.assertIn(ret1, (0, 1))
+ self.assertEqual(ret1, result is not None)
+ self.assertEqual(ret1, ret2)
+ self.assertIsNone(no_result)
+ return result
+
+ found_in_mro = run(True)
+ found_in_bases = run(False)
+ if comparable:
+ self.assertIs(found_in_mro, found_in_bases)
+ return found_in_mro
+ return found_in_mro, found_in_bases
+
+ create_type = _testcapi.create_type_with_token
+ get_token = _testcapi.get_tp_token
+
+ Py_TP_USE_SPEC = _testcapi.Py_TP_USE_SPEC
+ self.assertEqual(Py_TP_USE_SPEC, 0)
+
+ A1 = create_type('_testcapi.A1', Py_TP_USE_SPEC)
+ self.assertTrue(get_token(A1) != Py_TP_USE_SPEC)
+
+ B1 = create_type('_testcapi.B1', id(self))
+ self.assertTrue(get_token(B1) == id(self))
+
+ tokenA1 = get_token(A1)
+ # find A1 from A1
+ found = get_base_by_token(A1, tokenA1)
+ self.assertIs(found, A1)
+
+ # no token in static types
+ STATIC = type(1)
+ self.assertEqual(get_token(STATIC), 0)
+ found = get_base_by_token(STATIC, tokenA1)
+ self.assertIs(found, None)
+
+ # no token in pure subtypes
+ class A2(A1): pass
+ self.assertEqual(get_token(A2), 0)
+ # find A1
+ class Z(STATIC, B1, A2): pass
+ found = get_base_by_token(Z, tokenA1)
+ self.assertIs(found, A1)
+
+ # searching for NULL token is an error
+ with self.assertRaises(SystemError):
+ get_base_by_token(Z, 0)
+ with self.assertRaises(SystemError):
+ get_base_by_token(STATIC, 0)
+
+ # share the token with A1
+ C1 = create_type('_testcapi.C1', tokenA1)
+ self.assertTrue(get_token(C1) == tokenA1)
+
+ # find C1 first by shared token
+ class Z(C1, A2): pass
+ found = get_base_by_token(Z, tokenA1)
+ self.assertIs(found, C1)
+ # B1 not found
+ found = get_base_by_token(Z, get_token(B1))
+ self.assertIs(found, None)
+
+ with self.assertRaises(TypeError):
+ _testcapi.pytype_getbasebytoken(
+ 'not a type', id(self), True, False)
+
def test_freeze(self):
# test PyType_Freeze()
type_freeze = _testcapi.type_freeze
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 1fd7c8bd2e2d73..a276fa642de42e 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -162,7 +162,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index e5d2502edf5362..c4b73459b3712b 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -63,5 +63,6 @@ int _PyTestCapi_Init_Object(PyObject *module);
int _PyTestCapi_Init_Config(PyObject *mod);
int _PyTestCapi_Init_Import(PyObject *mod);
int _PyTestCapi_Init_Frame(PyObject *mod);
+int _PyTestCapi_Init_Type(PyObject *mod);
#endif // Py_TESTCAPI_PARTS_H
diff --git a/Modules/_testcapi/type.c b/Modules/_testcapi/type.c
new file mode 100644
index 00000000000000..9bef58d1f83668
--- /dev/null
+++ b/Modules/_testcapi/type.c
@@ -0,0 +1,251 @@
+#include "parts.h"
+#include "util.h"
+
+
+static PyType_Slot HeapTypeNameType_slots[] = {
+ {0},
+};
+
+static PyType_Spec HeapTypeNameType_Spec = {
+ .name = "_testcapi.HeapTypeNameType",
+ .basicsize = sizeof(PyObject),
+ .flags = Py_TPFLAGS_DEFAULT,
+ .slots = HeapTypeNameType_slots,
+};
+
+static PyObject *
+get_heaptype_for_name(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ return PyType_FromSpec(&HeapTypeNameType_Spec);
+}
+
+
+static PyObject *
+get_type_name(PyObject *self, PyObject *type)
+{
+ assert(PyType_Check(type));
+ return PyType_GetName((PyTypeObject *)type);
+}
+
+
+static PyObject *
+get_type_qualname(PyObject *self, PyObject *type)
+{
+ assert(PyType_Check(type));
+ return PyType_GetQualName((PyTypeObject *)type);
+}
+
+
+static PyObject *
+get_type_fullyqualname(PyObject *self, PyObject *type)
+{
+ assert(PyType_Check(type));
+ return PyType_GetFullyQualifiedName((PyTypeObject *)type);
+}
+
+
+static PyObject *
+get_type_module_name(PyObject *self, PyObject *type)
+{
+ assert(PyType_Check(type));
+ return PyType_GetModuleName((PyTypeObject *)type);
+}
+
+
+static PyObject *
+test_get_type_dict(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ /* Test for PyType_GetDict */
+
+ // Assert ints have a `to_bytes` method
+ PyObject *long_dict = PyType_GetDict(&PyLong_Type);
+ assert(long_dict);
+ assert(PyDict_GetItemString(long_dict, "to_bytes")); // borrowed ref
+ Py_DECREF(long_dict);
+
+ // Make a new type, add an attribute to it and assert it's there
+ PyObject *HeapTypeNameType = PyType_FromSpec(&HeapTypeNameType_Spec);
+ assert(HeapTypeNameType);
+ assert(PyObject_SetAttrString(
+ HeapTypeNameType, "new_attr", Py_NewRef(Py_None)) >= 0);
+ PyObject *type_dict = PyType_GetDict((PyTypeObject*)HeapTypeNameType);
+ assert(type_dict);
+ assert(PyDict_GetItemString(type_dict, "new_attr")); // borrowed ref
+ Py_DECREF(HeapTypeNameType);
+ Py_DECREF(type_dict);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *
+test_get_statictype_slots(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ newfunc tp_new = PyType_GetSlot(&PyLong_Type, Py_tp_new);
+ if (PyLong_Type.tp_new != tp_new) {
+ PyErr_SetString(PyExc_AssertionError, "mismatch: tp_new of long");
+ return NULL;
+ }
+
+ reprfunc tp_repr = PyType_GetSlot(&PyLong_Type, Py_tp_repr);
+ if (PyLong_Type.tp_repr != tp_repr) {
+ PyErr_SetString(PyExc_AssertionError, "mismatch: tp_repr of long");
+ return NULL;
+ }
+
+ ternaryfunc tp_call = PyType_GetSlot(&PyLong_Type, Py_tp_call);
+ if (tp_call != NULL) {
+ PyErr_SetString(PyExc_AssertionError, "mismatch: tp_call of long");
+ return NULL;
+ }
+
+ binaryfunc nb_add = PyType_GetSlot(&PyLong_Type, Py_nb_add);
+ if (PyLong_Type.tp_as_number->nb_add != nb_add) {
+ PyErr_SetString(PyExc_AssertionError, "mismatch: nb_add of long");
+ return NULL;
+ }
+
+ lenfunc mp_length = PyType_GetSlot(&PyLong_Type, Py_mp_length);
+ if (mp_length != NULL) {
+ PyErr_SetString(PyExc_AssertionError, "mismatch: mp_length of long");
+ return NULL;
+ }
+
+ void *over_value = PyType_GetSlot(&PyLong_Type, Py_bf_releasebuffer + 1);
+ if (over_value != NULL) {
+ PyErr_SetString(PyExc_AssertionError, "mismatch: max+1 of long");
+ return NULL;
+ }
+
+ tp_new = PyType_GetSlot(&PyLong_Type, 0);
+ if (tp_new != NULL) {
+ PyErr_SetString(PyExc_AssertionError, "mismatch: slot 0 of long");
+ return NULL;
+ }
+ if (PyErr_ExceptionMatches(PyExc_SystemError)) {
+ // This is the right exception
+ PyErr_Clear();
+ }
+ else {
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+
+// Get type->tp_version_tag
+static PyObject *
+type_get_version(PyObject *self, PyObject *type)
+{
+ if (!PyType_Check(type)) {
+ PyErr_SetString(PyExc_TypeError, "argument must be a type");
+ return NULL;
+ }
+ PyObject *res = PyLong_FromUnsignedLong(
+ ((PyTypeObject *)type)->tp_version_tag);
+ if (res == NULL) {
+ assert(PyErr_Occurred());
+ return NULL;
+ }
+ return res;
+}
+
+static PyObject *
+type_modified(PyObject *self, PyObject *arg)
+{
+ if (!PyType_Check(arg)) {
+ PyErr_SetString(PyExc_TypeError, "argument must be a type");
+ return NULL;
+ }
+ PyTypeObject *type = (PyTypeObject*)arg;
+
+ PyType_Modified(type);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *
+type_assign_version(PyObject *self, PyObject *arg)
+{
+ if (!PyType_Check(arg)) {
+ PyErr_SetString(PyExc_TypeError, "argument must be a type");
+ return NULL;
+ }
+ PyTypeObject *type = (PyTypeObject*)arg;
+
+ int res = PyUnstable_Type_AssignVersionTag(type);
+ return PyLong_FromLong(res);
+}
+
+
+static PyObject *
+type_get_tp_bases(PyObject *self, PyObject *arg)
+{
+ if (!PyType_Check(arg)) {
+ PyErr_SetString(PyExc_TypeError, "argument must be a type");
+ return NULL;
+ }
+ PyTypeObject *type = (PyTypeObject*)arg;
+
+ PyObject *bases = type->tp_bases;
+ if (bases == NULL) {
+ Py_RETURN_NONE;
+ }
+ return Py_NewRef(bases);
+}
+
+static PyObject *
+type_get_tp_mro(PyObject *self, PyObject *arg)
+{
+ if (!PyType_Check(arg)) {
+ PyErr_SetString(PyExc_TypeError, "argument must be a type");
+ return NULL;
+ }
+ PyTypeObject *type = (PyTypeObject*)arg;
+
+ PyObject *mro = ((PyTypeObject *)type)->tp_mro;
+ if (mro == NULL) {
+ Py_RETURN_NONE;
+ }
+ return Py_NewRef(mro);
+}
+
+
+static PyObject *
+type_freeze(PyObject *module, PyObject *arg)
+{
+ if (!PyType_Check(arg)) {
+ PyErr_SetString(PyExc_TypeError, "argument must be a type");
+ return NULL;
+ }
+ PyTypeObject *type = (PyTypeObject*)arg;
+
+ if (PyType_Freeze(type) < 0) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+
+static PyMethodDef test_methods[] = {
+ {"get_heaptype_for_name", get_heaptype_for_name, METH_NOARGS},
+ {"get_type_name", get_type_name, METH_O},
+ {"get_type_qualname", get_type_qualname, METH_O},
+ {"get_type_fullyqualname", get_type_fullyqualname, METH_O},
+ {"get_type_module_name", get_type_module_name, METH_O},
+ {"test_get_type_dict", test_get_type_dict, METH_NOARGS},
+ {"test_get_statictype_slots", test_get_statictype_slots, METH_NOARGS},
+ {"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")},
+ {"type_modified", type_modified, METH_O, PyDoc_STR("PyType_Modified")},
+ {"type_assign_version", type_assign_version, METH_O, PyDoc_STR("PyUnstable_Type_AssignVersionTag")},
+ {"type_get_tp_bases", type_get_tp_bases, METH_O},
+ {"type_get_tp_mro", type_get_tp_mro, METH_O},
+ {"type_freeze", type_freeze, METH_O},
+ {NULL},
+};
+
+int
+_PyTestCapi_Init_Type(PyObject *m)
+{
+ return PyModule_AddFunctions(m, test_methods);
+}
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index d25e61dbc3d588..ad9d836120b506 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -530,136 +530,6 @@ test_buildvalue_N(PyObject *self, PyObject *Py_UNUSED(ignored))
}
-static PyObject *
-test_get_statictype_slots(PyObject *self, PyObject *Py_UNUSED(ignored))
-{
- newfunc tp_new = PyType_GetSlot(&PyLong_Type, Py_tp_new);
- if (PyLong_Type.tp_new != tp_new) {
- PyErr_SetString(PyExc_AssertionError, "mismatch: tp_new of long");
- return NULL;
- }
-
- reprfunc tp_repr = PyType_GetSlot(&PyLong_Type, Py_tp_repr);
- if (PyLong_Type.tp_repr != tp_repr) {
- PyErr_SetString(PyExc_AssertionError, "mismatch: tp_repr of long");
- return NULL;
- }
-
- ternaryfunc tp_call = PyType_GetSlot(&PyLong_Type, Py_tp_call);
- if (tp_call != NULL) {
- PyErr_SetString(PyExc_AssertionError, "mismatch: tp_call of long");
- return NULL;
- }
-
- binaryfunc nb_add = PyType_GetSlot(&PyLong_Type, Py_nb_add);
- if (PyLong_Type.tp_as_number->nb_add != nb_add) {
- PyErr_SetString(PyExc_AssertionError, "mismatch: nb_add of long");
- return NULL;
- }
-
- lenfunc mp_length = PyType_GetSlot(&PyLong_Type, Py_mp_length);
- if (mp_length != NULL) {
- PyErr_SetString(PyExc_AssertionError, "mismatch: mp_length of long");
- return NULL;
- }
-
- void *over_value = PyType_GetSlot(&PyLong_Type, Py_bf_releasebuffer + 1);
- if (over_value != NULL) {
- PyErr_SetString(PyExc_AssertionError, "mismatch: max+1 of long");
- return NULL;
- }
-
- tp_new = PyType_GetSlot(&PyLong_Type, 0);
- if (tp_new != NULL) {
- PyErr_SetString(PyExc_AssertionError, "mismatch: slot 0 of long");
- return NULL;
- }
- if (PyErr_ExceptionMatches(PyExc_SystemError)) {
- // This is the right exception
- PyErr_Clear();
- }
- else {
- return NULL;
- }
-
- Py_RETURN_NONE;
-}
-
-
-static PyType_Slot HeapTypeNameType_slots[] = {
- {0},
-};
-
-static PyType_Spec HeapTypeNameType_Spec = {
- .name = "_testcapi.HeapTypeNameType",
- .basicsize = sizeof(PyObject),
- .flags = Py_TPFLAGS_DEFAULT,
- .slots = HeapTypeNameType_slots,
-};
-
-static PyObject *
-get_heaptype_for_name(PyObject *self, PyObject *Py_UNUSED(ignored))
-{
- return PyType_FromSpec(&HeapTypeNameType_Spec);
-}
-
-
-static PyObject *
-get_type_name(PyObject *self, PyObject *type)
-{
- assert(PyType_Check(type));
- return PyType_GetName((PyTypeObject *)type);
-}
-
-
-static PyObject *
-get_type_qualname(PyObject *self, PyObject *type)
-{
- assert(PyType_Check(type));
- return PyType_GetQualName((PyTypeObject *)type);
-}
-
-
-static PyObject *
-get_type_fullyqualname(PyObject *self, PyObject *type)
-{
- assert(PyType_Check(type));
- return PyType_GetFullyQualifiedName((PyTypeObject *)type);
-}
-
-
-static PyObject *
-get_type_module_name(PyObject *self, PyObject *type)
-{
- assert(PyType_Check(type));
- return PyType_GetModuleName((PyTypeObject *)type);
-}
-
-
-static PyObject *
-test_get_type_dict(PyObject *self, PyObject *Py_UNUSED(ignored))
-{
- /* Test for PyType_GetDict */
-
- // Assert ints have a `to_bytes` method
- PyObject *long_dict = PyType_GetDict(&PyLong_Type);
- assert(long_dict);
- assert(PyDict_GetItemString(long_dict, "to_bytes")); // borrowed ref
- Py_DECREF(long_dict);
-
- // Make a new type, add an attribute to it and assert it's there
- PyObject *HeapTypeNameType = PyType_FromSpec(&HeapTypeNameType_Spec);
- assert(HeapTypeNameType);
- assert(PyObject_SetAttrString(
- HeapTypeNameType, "new_attr", Py_NewRef(Py_None)) >= 0);
- PyObject *type_dict = PyType_GetDict((PyTypeObject*)HeapTypeNameType);
- assert(type_dict);
- assert(PyDict_GetItemString(type_dict, "new_attr")); // borrowed ref
- Py_DECREF(HeapTypeNameType);
- Py_DECREF(type_dict);
- Py_RETURN_NONE;
-}
-
static PyObject *
pyobject_repr_from_null(PyObject *self, PyObject *Py_UNUSED(ignored))
{
@@ -2380,68 +2250,6 @@ test_py_is_funcs(PyObject *self, PyObject *Py_UNUSED(ignored))
}
-// type->tp_version_tag
-static PyObject *
-type_get_version(PyObject *self, PyObject *type)
-{
- if (!PyType_Check(type)) {
- PyErr_SetString(PyExc_TypeError, "argument must be a type");
- return NULL;
- }
- PyObject *res = PyLong_FromUnsignedLong(
- ((PyTypeObject *)type)->tp_version_tag);
- if (res == NULL) {
- assert(PyErr_Occurred());
- return NULL;
- }
- return res;
-}
-
-static PyObject *
-type_modified(PyObject *self, PyObject *type)
-{
- if (!PyType_Check(type)) {
- PyErr_SetString(PyExc_TypeError, "argument must be a type");
- return NULL;
- }
- PyType_Modified((PyTypeObject *)type);
- Py_RETURN_NONE;
-}
-
-
-static PyObject *
-type_assign_version(PyObject *self, PyObject *type)
-{
- if (!PyType_Check(type)) {
- PyErr_SetString(PyExc_TypeError, "argument must be a type");
- return NULL;
- }
- int res = PyUnstable_Type_AssignVersionTag((PyTypeObject *)type);
- return PyLong_FromLong(res);
-}
-
-
-static PyObject *
-type_get_tp_bases(PyObject *self, PyObject *type)
-{
- PyObject *bases = ((PyTypeObject *)type)->tp_bases;
- if (bases == NULL) {
- Py_RETURN_NONE;
- }
- return Py_NewRef(bases);
-}
-
-static PyObject *
-type_get_tp_mro(PyObject *self, PyObject *type)
-{
- PyObject *mro = ((PyTypeObject *)type)->tp_mro;
- if (mro == NULL) {
- Py_RETURN_NONE;
- }
- return Py_NewRef(mro);
-}
-
-
/* We only use 2 in test_capi/test_misc.py. */
#define NUM_BASIC_STATIC_TYPES 2
static PyTypeObject BasicStaticTypes[NUM_BASIC_STATIC_TYPES] = {
@@ -3205,19 +3013,6 @@ finalize_thread_hang(PyObject *self, PyObject *callback)
}
-static PyObject *
-type_freeze(PyObject *module, PyObject *args)
-{
- PyTypeObject *type;
- if (!PyArg_ParseTuple(args, "O!", &PyType_Type, &type)) {
- return NULL;
- }
- if (PyType_Freeze(type) < 0) {
- return NULL;
- }
- Py_RETURN_NONE;
-}
-
struct atexit_data {
int called;
PyThreadState *tstate;
@@ -3415,13 +3210,6 @@ static PyMethodDef TestMethods[] = {
{"py_buildvalue", py_buildvalue, METH_VARARGS},
{"py_buildvalue_ints", py_buildvalue_ints, METH_VARARGS},
{"test_buildvalue_N", test_buildvalue_N, METH_NOARGS},
- {"test_get_statictype_slots", test_get_statictype_slots, METH_NOARGS},
- {"get_heaptype_for_name", get_heaptype_for_name, METH_NOARGS},
- {"get_type_name", get_type_name, METH_O},
- {"get_type_qualname", get_type_qualname, METH_O},
- {"get_type_fullyqualname", get_type_fullyqualname, METH_O},
- {"get_type_module_name", get_type_module_name, METH_O},
- {"test_get_type_dict", test_get_type_dict, METH_NOARGS},
{"test_reftracer", test_reftracer, METH_NOARGS},
{"_test_thread_state", test_thread_state, METH_VARARGS},
{"gilstate_ensure_release", gilstate_ensure_release, METH_NOARGS},
@@ -3489,11 +3277,6 @@ static PyMethodDef TestMethods[] = {
{"test_refcount_funcs", test_refcount_funcs, METH_NOARGS},
{"test_py_is_macros", test_py_is_macros, METH_NOARGS},
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
- {"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")},
- {"type_modified", type_modified, METH_O, PyDoc_STR("PyType_Modified")},
- {"type_assign_version", type_assign_version, METH_O, PyDoc_STR("PyUnstable_Type_AssignVersionTag")},
- {"type_get_tp_bases", type_get_tp_bases, METH_O},
- {"type_get_tp_mro", type_get_tp_mro, METH_O},
{"get_basic_static_type", get_basic_static_type, METH_VARARGS, NULL},
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
{"gen_get_code", gen_get_code, METH_O, NULL},
@@ -3516,7 +3299,6 @@ static PyMethodDef TestMethods[] = {
{"function_set_warning", function_set_warning, METH_NOARGS},
{"test_critical_sections", test_critical_sections, METH_NOARGS},
{"finalize_thread_hang", finalize_thread_hang, METH_O, NULL},
- {"type_freeze", type_freeze, METH_VARARGS},
{"test_atexit", test_atexit, METH_NOARGS},
{"code_offset_to_line", _PyCFunction_CAST(code_offset_to_line), METH_FASTCALL},
{"tracemalloc_track_race", tracemalloc_track_race, METH_NOARGS},
@@ -4296,6 +4078,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_Frame(m) < 0) {
return NULL;
}
+ if (_PyTestCapi_Init_Type(m) < 0) {
+ return NULL;
+ }
PyState_AddModule(m, &_testcapimodule);
return m;
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index e94b4c46e6dbb8..09969331c6edd4 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -129,6 +129,7 @@
<ClCompile Include="..\Modules\_testcapi\config.c" />
<ClCompile Include="..\Modules\_testcapi\import.c" />
<ClCompile Include="..\Modules\_testcapi\frame.c" />
+ <ClCompile Include="..\Modules\_testcapi\type.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index 782f91afb1996f..52491643ad842f 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -120,6 +120,9 @@
<ClCompile Include="..\Modules\_testcapi\frame.c">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\Modules\_testcapi\type.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc">
1
0