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:
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:
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():
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.
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:
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:
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():
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.