Skip to content

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

Description

@EternalRights

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions