fix: add life support to handles cast to string_view#6092
Conversation
|
Looks fine to me. Ran a review with Claude Opus 4.8, below. 🤖 AI review 🤖
I've reviewed PR #6092. Here's my assessment. OverviewThe PR fixes a genuine use-after-free bug when casting Python objects to
The fix adds Correctness — looks right
Test coverage — goodThe test exercises all three newly-fixed paths (str → UTF-8, Minor suggestions (non-blocking)
RisksLow. The change is narrowly scoped, touches no ABI/internals, adds no overhead to the common Verdict: Solid, correct bug fix with appropriate test coverage. Approve, modulo confirming the changelog expectation. 🤖 Why does like 510 exist? 🤖
Good catch — line 510 ( What it does now
Why it existsIt's a leftover from Python 2 support. Before the Python 2 removal (commit // Python 2 era:
temp = reinterpret_steal<object>(PyUnicode_FromObject(load_src.ptr()));
...
load_src = temp; // now load_src != srcWhen a Python 2 Relevance to this PRIt's harmless but slightly inconsistent with the PR: the new UTF-8 path reads the buffer from Cleanest fix (independent of this PR, or folded into it): drop line 510 and replace the four |
`load_src` was a Python 2-era leftover. A removed branch used to reassign it to a temporary unicode object; since that was deleted with Python 2 support, it is now an exact, never-reassigned alias of `src`. Use `src` directly. Spotted in review of #6092. Assisted-by: ClaudeCode:claude-opus-4.8
PR pybind#6092 added loader_life_support::add_patient(src) whenever a string view is loaded, fixing a real use-after-free: when a container of views is built from a non-sequence iterable (e.g. a generator), the container caster materializes a temporary (a tuple/dict, or the generator's per-iteration items) that owns the strings and releases it before the bound function body runs, leaving the views dangling. But add_patient throws when there is no life support frame, so casting a view outside a bound function (e.g. a manual py::cast<std::string_view>) started throwing even though the caller owns the source -- a regression. The view caster cannot tell a durable, caller-owned source from a pybind11-managed transient one; that provenance lives in the container caster. Introduce an ambient transient_source_guard that the list, set, map, and array casters set around their generator/materialized paths, and have the string caster keep the source alive only when loading from a transient source. This means: - views into durable sources (direct arguments, sequences, manual casts) add no life support and no longer throw outside a bound function, and - a generator used outside a frame still throws, since such a view cannot be made safe rather than silently dangling. The guard restores (rather than clears) the previous value, so a durable container nested in a transient one is correctly treated as transient. Verified with AddressSanitizer: the in-frame generator case is clean, the out-of-frame durable cases succeed, and the out-of-frame generator case throws. Assisted-by: ClaudeCode:claude-opus-4.8
PR pybind#6092 added loader_life_support::add_patient(src) to keep the source object alive when loading a string view, fixing a real use-after-free when a container of views is built from a non-sequence iterable (e.g. a generator): list_caster materializes a temporary tuple that owns the strings and destroys it when load() returns, before the bound function body runs. add_patient throws when there is no life support frame, so casting to a view outside a bound function (e.g. a manual py::cast<std::string_view>) now raises instead of relying on the caller-owned source, a regression from pybind#6092. For these view-into-src cases registration is best effort: inside a bound function it keeps src alive (fixing the UAF), and outside one the caller owns src's lifetime as before. Add try_add_patient(), which returns false instead of throwing when there is no frame, and use it at the three view load sites. add_patient() keeps its strict contract for value-creating conversions. Assisted-by: ClaudeCode:claude-opus-4.8
PR pybind#6092 added loader_life_support::add_patient(src) to keep the source object alive when loading a string view, fixing a real use-after-free when a container of views is built from a non-sequence iterable (e.g. a generator): list_caster materializes a temporary tuple that owns the strings and destroys it when load() returns, before the bound function body runs. add_patient throws when there is no life support frame, so casting to a view outside a bound function (e.g. a manual py::cast<std::string_view>) now raises instead of relying on the caller-owned source, a regression from pybind#6092. For these view-into-src cases registration is best effort: inside a bound function it keeps src alive (fixing the UAF), and outside one the caller owns src's lifetime as before. Add try_add_patient(), which returns false instead of throwing when there is no frame, and use it at the three view load sites. add_patient() keeps its strict contract for value-creating conversions. Assisted-by: ClaudeCode:claude-opus-4.8
…ind#6092)" This re-applies pybind#6092 (reverting pybind#6097) so the follow-up fixes in this PR can build on it. Assisted-by: ClaudeCode:claude-opus-4.8
PR pybind#6092 added loader_life_support::add_patient(src) to keep the source object alive when loading a string view, fixing a real use-after-free when a container of views is built from a non-sequence iterable (e.g. a generator): list_caster materializes a temporary tuple that owns the strings and destroys it when load() returns, before the bound function body runs. add_patient throws when there is no life support frame, so casting to a view outside a bound function (e.g. a manual py::cast<std::string_view>) now raises instead of relying on the caller-owned source, a regression from pybind#6092. For these view-into-src cases registration is best effort: inside a bound function it keeps src alive (fixing the UAF), and outside one the caller owns src's lifetime as before. Add try_add_patient(), which returns false instead of throwing when there is no frame, and use it at the three view load sites. add_patient() keeps its strict contract for value-creating conversions. Assisted-by: ClaudeCode:claude-opus-4.8
Description
std::string_view is a reference type and therefore the handles are required to be put on life-support.
Suggested changelog entry:
Add life support to handles cast to string_view.