Skip to content

Specialized STORE_SUBSCR_DICT allows silent mutation of frozendict after adaptive specialization #145122

@Abdoulrasheed

Description

@Abdoulrasheed

Bug report

Bug description:

Commit 3e2f5c1 broadened _GUARD_NOS_DICT from PyDict_CheckExact() to PyAnyDict_CheckExact() for read operations (BINARY_OP_SUBSCR_DICT, CONTAINS_OP_DICT). However, the same guard is also used by STORE_SUBSCR_DICT, which still calls _PyDict_SetItem_Take2() directly; bypassing frozendict's immutability checks entirely.

When a function containing d[key] = value is first specialized with dict, subsequent calls with frozendict pass the widened guard and mutate the frozendict at the C level. In debug builds this is an assertion crash; in release builds it's silent mutation with a stale cached hash.

def store(d, key, val):
    d[key] = val

# specialize
for i in range(100):
    store({}, 'k', i)

# now frozendict passes the widened guard and is mutated
fd = frozendict({'a': 1})
h_before = hash(fd)

store(fd, 'b', 2)  # should raise TypeError

print(fd)       # frozendict({'a': 1, 'b': 2}),    mutated
print(hash(fd) == h_before)  # True, stale hash

Before specialization, store(frozendict(...), 'b', 2) correctly raises TypeError: 'frozendict' object does not support item assignment.

After specialization, it silently mutates the frozendict and leaves the cached hash stale.

CPython versions tested on:

CPython main branch

Operating systems tested on:

macOS

Linked PRs

Metadata

Metadata

Assignees

Labels

3.15new features, bugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)release-blockertype-bugAn unexpected behavior, bug, or error

Projects

Status

Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions