BUG: Disown Python objects adopted by unique_ptr members - #6707
Open
hjmjohnson wants to merge 1 commit into
Open
BUG: Disown Python objects adopted by unique_ptr members#6707hjmjohnson wants to merge 1 commit into
hjmjohnson wants to merge 1 commit into
Conversation
GradientImageFilter::OverrideBoundaryCondition and OptimizerParameters::SetHelper store their raw-pointer argument in a unique_ptr member, but the Python wrapping left ownership with the proxy. Both sides freed the object and the interpreter aborted at shutdown. Apply SWIGTYPE *DISOWN to both parameters and add Python regression tests.
hjmjohnson
marked this pull request as ready for review
July 26, 2026 16:17
Contributor
|
| Filename | Overview |
|---|---|
| Modules/Core/Common/wrapping/itkOptimizerParameters.wrap | Applies a parameter-specific DISOWN typemap matching the helper type and SetHelper parameter name. |
| Modules/Core/Common/wrapping/test/itkOptimizerParametersOwnershipTest.py | Verifies ownership transfer for SetHelper, with process termination covering the previous shutdown double-free. |
| Modules/Filtering/ImageGradient/wrapping/itkGradientImageFilter.wrap | Applies DISOWN to boundary-condition arguments for the wrapped image specializations. |
| Modules/Filtering/ImageGradient/wrapping/test/itkGradientImageFilterOwnershipTest.py | Verifies ownership transfer and confirms the filter remains operational with the adopted boundary condition. |
| Modules/Core/Common/wrapping/test/CMakeLists.txt | Registers the optimizer ownership regression test when Python wrapping is enabled. |
| Modules/Filtering/ImageGradient/wrapping/test/CMakeLists.txt | Registers the gradient-filter ownership test under its required Python, float, and 2D wrapping configuration. |
Sequence Diagram
sequenceDiagram
participant Python as Python proxy
participant SWIG as SWIG wrapper
participant Cpp as C++ object
participant Member as unique_ptr member
Python->>SWIG: SetHelper / OverrideBoundaryCondition(argument)
SWIG->>Python: Clear thisown via DISOWN
SWIG->>Cpp: Pass raw pointer
Cpp->>Member: reset(pointer)
Note over Python,Member: C++ member becomes the sole owner
Member-->>Member: Delete adopted object during replacement or destruction
Reviews (1): Last reviewed commit: "BUG: Disown Python objects adopted by un..." | Re-trigger Greptile
This was referenced Jul 26, 2026
dzenanz
approved these changes
Jul 27, 2026
dzenanz
left a comment
Member
There was a problem hiding this comment.
Looks good on a glance. It would be good if someone else reviewed this too.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two wrapped ITK methods adopt their raw-pointer argument into a
std::unique_ptrmember, but the Python wrapping left ownership with the proxy object. Both sides free it, and the interpreter aborts at shutdown. Fixed by applyingSWIGTYPE *DISOWNto both parameters; no C++ change.Affects
itk.GradientImageFilter.OverrideBoundaryConditionanditk.OptimizerParameters.SetHelper.Reproducer (current main, before this PR)
After this PR,
thisownisFalsein both cases and the interpreter exits cleanly.Root cause
Both methods take a raw pointer and immediately adopt it:
GradientImageFilter::OverrideBoundaryConditionm_BoundaryCondition.reset(boundaryCondition)(itkGradientImageFilter.hxx:45)OptimizerParameters::SetHelperm_Helper.reset(helper)(itkOptimizerParameters.h:140)OptimizerParameters::SetHelperdocuments the intent explicitly: "OptimizerParameters manages the helper once its been assigned."SWIG's default typemap for a raw pointer parameter is non-owning, so the Python proxy keeps
thisown == True. The C++unique_ptrand the Python proxy then both destroy the object.These two are the complete set of public methods with this pattern. A scan for
m_*.reset(<parameter>)across headers and sources finds three further adopters —SetCostFunctionAdaptoronSingleValuedNonLinearVnlOptimizer,MultipleValuedNonLinearVnlOptimizer, andSingleValuedNonLinearVnlOptimizerv4— but all three areprotected. They appear in the generated.ifiles insideprotected:blocks, SWIG does not wrap protected members, and the generated Python modules contain no reference to them, so they are not reachable from Python and are out of scope here.Why DISOWN rather than a C++ signature change
Changing the signature to
std::unique_ptrwould express ownership in the type system, which is the better long-term API. That is deliberately not this PR — it is an API change requiring deprecation of the existing overload, and it is not backportable. This PR is the minimal, backportable crash fix against the API as it exists.The follow-up API work is planned separately and supersedes the approach in #4533.
Test plan
Two new Python tests, one per affected module:
Modules/Filtering/ImageGradient/wrapping/test/itkGradientImageFilterOwnershipTest.pyModules/Core/Common/wrapping/test/itkOptimizerParametersOwnershipTest.pyEach asserts the
thisowntransition and then exercises the object. Because the double-free manifests at interpreter shutdown, a non-zero exit code is itself part of the regression check — these tests fail on unpatched main even if the assertions are removed.Both built and run locally in a
ITK_WRAP_PYTHON=ONtree before pushing.pre-commit run --all-filespasses.