Skip to content

comprehensions iterating over locals() may break under some trace funcs in 3.12 #105256

Closed
@carljm

Description

@carljm

It is already the case in all recent Python versions that the lazy-update behavior of the dictionary returned from locals() causes weird frame-introspection-sensitive semantics around lazy iteration of locals(). E.g. this code will work fine:

def f(a, b):
    ret = {}
    for k, v in locals().items():
        ret[k] = v
    return ret

f(1, 2)

But run this under a tracing function that simply accesses frame.f_locals:

import sys

def tracefunc(frame, *a, **kw):
    frame.f_locals
    return tracefunc

sys.settrace(tracefunc)

f(1, 2)

And suddenly you instead get RuntimeError: dictionary changed size during iteration.

The reason is because accessing frame.f_locals triggers a lazy update of the cached locals dictionary on the frame from the fast locals array. And this is the same dictionary returned by locals() (it doesn't create a copy), and so the tracing function has the side effect of adding the keys k and v to the locals dictionary, while it is still being lazily iterated over. Without the access of frame.f_locals, nothing triggers this locals-dict lazy update after the initial call to locals(), so the iteration is fine.

This is a pre-existing problem with the semantics of locals() generally, and there are proposals to fix it, e.g. PEP 667.

What is new in Python 3.12 is that PEP 709 means comprehensions can newly be exposed to this same issue. Consider this version of the above function:

def f(a, b):
    return {k: v for k, v in locals.items()}

Under PEP 709, k and v are now part of the locals of f (albeit isolated and only existing during the execution of the comprehension), which means this version of f is now subject to the same issue as the previous for-loop version, if run under a tracing func that accesses f_locals.

Linked PRs

Metadata

Metadata

Assignees

Labels

3.12only security fixes3.13bugs and security fixes

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions