Description
Crash report
What happened?
import typing
typing.TypeVar(name="X", bound=type)
Triggers a segfault in python 3.12.3 on all platforms.
Does not reproduce on 3.11 or below.
This bug is similar to #110787, but that fix was never backported to 3.12, and additionally has a flaw that causes some arguments to be shifted in the previously-crashing cases.
When _PyArg_UnpackKeywordsWithVararg
gets an input with insufficient positional parameters (which have been provided as keyword arguments) the varargs
slot is positioned at the end of the mandatory positional parameters slots. But in the test case, the varargs
slot is being overwritten by the bound
slot because the keyword argument copy loop that begins here isn't aware of varargs
.
If the minimal positionals are provided in the positionals tuple, https://github.com/sobolevn/cpython/blob/c4ca210f12a18edbe30b91aeb6e1915d74936caf/Python/getargs.c#L2525 line in 3.12 (missing the !=) is always true, and the keyword arguments are offset by 1, pushing them to the end of the array and leaving the varargs
slot alone. But if there aren't and they need to be backfilled from the keyword arguments, nargs
doesn't change in the loop, causing it to overwrite the varargs
slot and additionally fail to completely fill the array (causing a segfault when the parent function tries to use that last garbage slot).
This can be fixed by changing the nargs
to i
so the line reads if (i < vararg) {
, then keyword arguments that look up before the varargs
entry are not offset, and those that look up after are offset, leaving that slot untouched and ensuring the array is properly filled.
Because i
always begins at the end of where the provided positional arguments start, this will hopefully never accidentally overwrite positional arguments, and should solve the problem entirely.
The current fix with != is insufficient because if you provide a third parameter, the != becomes true again, and it reuses a slot. Thanks to a null check it doesn't segfault, but it does result in unexpected behavior:
import typing
T = typing.TypeVar(name="T", bound=type, covariant=True)
assert T.__covariant__
fails with an AssertionError in the 3.13 tag and main.
(TypeVar("T", bound=type, covariant=True).__covariant__
is true, however)
CPython versions tested on:
3.11, 3.12, CPython main branch
Operating systems tested on:
macOS, Windows
Output from running 'python -VV' on the command line:
Python 3.14.0a0 (heads/main-dirty:cb6f75a32ca, May 8 2024, 20:35:11) [Clang 15.0.0 (clang-1500.3.9.4)]
Linked PRs
- gh-118814: Fix crash in
_PyArg_UnpackKeywordsWithVararg
#122558 - gh-118814: Fix the TypeVar constructor when name is passed by keyword #122664
- [3.13] gh-118814: Fix the TypeVar constructor when name is passed by keyword (GH-122664) #122806
- [3.12] gh-118814: Fix the TypeVar constructor when name is passed by keyword (GH-122664) #122807