Skip to content

gh-159098: Keep the type alive during generic attribute lookup - #159100

Open
lazerg wants to merge 2 commits into
python:mainfrom
lazerg:fix-issue-159098
Open

lazerg wants to merge 2 commits into
python:mainfrom
lazerg:fix-issue-159098

Conversation

@lazerg

@lazerg lazerg commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

_PyObject_GenericGetAttrWithDict() keeps a borrowed tp = Py_TYPE(obj), but hashing or comparing a str subclass name can run Python code that reassigns obj.__class__ and lets the GC free the old type. The lookup then keeps using tp. This takes a reference to the type for the duration of the lookup, the same way _PyObject_GenericSetAttrWithDict() already does with _Py_INCREF_TYPE().

_PyObject_GetMethod() and _PyObject_GetMethodStackRef() had the same problem when a str subclass key in the instance dict runs __eq__ during the dict lookup, so they take the same reference.

Fixes #159098

@picnixz

picnixz commented Oct 10, 2026

Copy link
Copy Markdown
Member

Thie adds nontrivial refcounting contention on the FT build. What is the effect on micro and macro benchmarks for regular types?

@lazerg

lazerg commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

I measured it on free-threaded release builds (--disable-gil, -O3, no PGO/LTO, macOS arm64), main vs this PR, 11 interleaved runs per case.

_Py_INCREF_TYPE uses the per-thread refcount for heap types, so it does not write the shared refcount. With 4 and 8 threads hitting the same class, the difference was within noise in both directions.

Single-threaded, only the paths that miss specialization pay for it:

  • getattr(o, name) for an instance attr, class attr, or method: +2% to +3.5% (sd < 1%)
  • getattr(o, "missing", None): +5%
  • megamorphic p.x, p.cattr, p.meth() over 16 classes: +5% to +6%
  • specialized o.x and o.meth(): no change

On the default build (plain Py_INCREF) the same cases are +1% to +3.7%.

pyperformance, 18 benchmarks on the FT build: geometric mean 1.01x slower, with 1-3% on richards, json_dumps, and nbody. A second run of main against itself moved by up to 3%, so the macro difference is inside the noise on this machine.

_PyObject_GenericSetAttrWithDict() already pays the same cost.

@picnixz

picnixz commented Oct 10, 2026

Copy link
Copy Markdown
Member

Setting things is less common than getting. I am concerned by that and I know that we already had a similar issue about that and @kumaraditya303 was also concerned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

getattr() can access a freed heap type after re-entrant attribute-name hashing or comparison

2 participants