Code.getargs: CO_VARARGS/CO_VARKEYWORDS bit flags treated as counts, local vars leak into argument list
Was debugging a traceback and noticed the argument list had extra entries that shouldn't be there. Traced it to `Code.getargs()` in `src/_pytest/_code/code.py` lines 123-125:
```python
if var:
argcount += raw.co_flags & CO_VARARGS
argcount += raw.co_flags & CO_VARKEYWORDS
```
`CO_VARARGS` is 4 and `CO_VARKEYWORDS` is 8. The bitwise AND against `co_flags` gives 4 or 0, 8 or 0 -- but the intent is to add 1 for each flag, since `co_varnames` has one slot each for `*args` and `**kwargs`.
So when a function has both `*args` and `**kwargs`, `argcount` gets bumped by 12 instead of 2. The `co_varnames[:argcount]` slice overshoots into local variables:
```python
def f(a, b, *args, **kwargs):
x = 1
y = 2
raise ValueError('test')
# Code(f).getargs(var=True) returns:
# ('a', 'b', 'args', 'kwargs', 'x', 'y') <- x, y are NOT arguments
# should be:
# ('a', 'b', 'args', 'kwargs')
```
This feeds into `repr_args()` which shows function arguments in tracebacks, so local variables end up displayed as if they were parameters. Bit misleading.
The fix is just wrapping the flags in `bool()`:
```python
if var:
argcount += bool(raw.co_flags & CO_VARARGS)
argcount += bool(raw.co_flags & CO_VARKEYWORDS)
```
Been there since the initial commit in 2015. The existing tests don't catch it because none of the test functions used there have both `*args`/`**kwargs` and local variables at the same time.
关闭于 2026-05-30 1 条评论