Skip to content

Fix Code.getargs() treating co_flags bitmask values as counts#14493

Open
EternalRights wants to merge 5 commits into
pytest-dev:mainfrom
EternalRights:fix-code-getargs-bit-flags
Open

Fix Code.getargs() treating co_flags bitmask values as counts#14493
EternalRights wants to merge 5 commits into
pytest-dev:mainfrom
EternalRights:fix-code-getargs-bit-flags

Conversation

@EternalRights
Copy link
Copy Markdown
Contributor

CO_VARARGS (4) and CO_VARKEYWORDS (8) are bitmask flags, but they were used directly as increment values for the argument count via &. This caused co_varnames[:argcount] to slice beyond the actual arguments when the function body contained enough local variables.

Fix by wrapping the bitmask results in bool(), so they contribute at most 1 to the argcount.

Closes #14492

CO_VARARGS (4) and CO_VARKEYWORDS (8) are bitmask flags, but they were
used directly as increment values for the argument count via `&`.
This caused co_varnames[:argcount] to slice beyond the actual arguments
when the function body contained enough local variables.

Fix by wrapping the bitmask results in bool(), so they contribute
at most 1 to the argcount.

Closes pytest-dev#14492
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label May 17, 2026
Comment thread src/_pytest/_code/code.py
if var:
argcount += raw.co_flags & CO_VARARGS
argcount += raw.co_flags & CO_VARKEYWORDS
argcount += bool(raw.co_flags & CO_VARARGS)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fear this may get confused by mixing in kwonly args

@EternalRights
Copy link
Copy Markdown
Contributor Author

You were right about kwonly args.

The layout of co_varnames in Python 3.8+ puts kwonly args between *args and **kwargs, so without accounting for co_kwonlyargcount the slice grabs the kwonly name before hitting z.

Pushed a fix that adds raw.co_kwonlyargcount to argcount when var=True, and added a test case with a kwonly arg (f6) to cover it.

ptal.

@RonnyPfannschmidt
Copy link
Copy Markdown
Member

man, this one is a messy piece with a history - i wonder if signature could be used in more modern code to just use the names from the signature

that being said - the remenants of py.code is one of the painful legacies that evolved back in 2005 when it was modern code growing out of the testing utilities of the pypy project - now its 20 years later and python changed quite a bit

@EternalRights
Copy link
Copy Markdown
Contributor Author

man, this one is a messy piece with a history - i wonder if signature could be used in more modern code to just use the names from the signature

that being said - the remenants of py.code is one of the painful legacies that evolved back in 2005 when it was modern code growing out of the testing utilities of the pypy project - now its 20 years later and python changed quite a bit

yeah this needs more thought. I’ll come back to it tomorrow.

co_varnames layout is: positional args, kwonly args, *args, **kwargs, locals.
The f6 test expected the wrong order. Also suppress ruff F841 false
positives on locals deliberately placed to test co_varnames slicing.
Codecov reports coverage for testing/ files since the project config
includes them. The function bodies of f5/f6 (local var assignments)
were never executed because the test only used Code.from_function(),
never called the functions. Replace raise NotImplementedError() with
actual calls to cover all diff lines.
@EternalRights
Copy link
Copy Markdown
Contributor Author

kwonly args are now accounted for and CI is all green. The inspect.signature idea makes sense for a future cleanup — the current co_varnames slicing works but it's fragile with every Python release adding new co_* fields.

@@ -0,0 +1 @@
Fixed ``Code.getargs()`` incorrectly including local variable names in the returned argument tuple for functions with ``*args`` and/or ``**kwargs``. The method was using ``co_flags`` bitmask values (``4`` and ``8``) directly as counts instead of converting them to ``1`` via ``bool()``.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the changelog entry also mention the kwonly fix?
account for co_kwonlyargcount in getargs when var=True

Right now it only talks about the bitmask, but someone affected by the kwonly case wouldn't find their bug in the release notes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Code.getargs: CO_VARARGS/CO_VARKEYWORDS bit flags treated as counts, local vars leak into argument list

3 participants