COMP: Adopt VXL private vnl_vector ivars via protected_set_data#6233
COMP: Adopt VXL private vnl_vector ivars via protected_set_data#6233hjmjohnson wants to merge 1 commit intoInsightSoftwareConsortium:mainfrom
Conversation
VXL's `vnl_vector::data` and `num_elmts` data members were privatized when
`VXL_USE_HISTORICAL_PROTECTED_IVARS` is `OFF`, which is the default for
modern VXL trees and is required to build against VXL 7.0.0. Switch
`itk::Array<T>` to use the `protected_set_data(T*, size_t, bool)` helper
that VXL has provided since v3.3.0 as the supported transition path for
derived classes that previously poked the raw members.
`protected_set_data` was added in upstream VXL commit ccdcf37b27 ("ENH:
Add options to manage backward compatibility") on 2019-10-06 and first
shipped in tag v3.3.0 (project VERSION 3.3.0.0, released 2021-05-24).
Also flip the bundled-VXL configuration so that
`VXL_USE_HISTORICAL_PROTECTED_IVARS=OFF` and
`VXL_USE_HISTORICAL_IMPLICIT_CONVERSIONS=OFF` unconditionally, and bump
the system-VXL minimum from 3.0 to 4.0 so consumers using an external
VXL pick up a release that contains the helper.
|
| Filename | Overview |
|---|---|
| Modules/Core/Common/include/itkArray.hxx | Rewrites 5 direct vnl_vector::data/num_elmts assignments to protected_set_data calls, but always passes false as the ownership flag — causing a memory leak when LetArrayManageMemory=true (constructor, SetData, SetDataSameSize) and an assertion failure / silent memory leak in SetSize because set_size asserts m_LetArrayManageMemory before allocating. |
| Modules/ThirdParty/VNL/CMakeLists.txt | Bumps minimum system-VXL version from 3.0.0 to 4.0.0, makes VXL_USE_HISTORICAL_IMPLICIT_CONVERSIONS unconditionally OFF (removing the legacy gate), and flips VXL_USE_HISTORICAL_PROTECTED_IVARS from ON to OFF; two stale inline comments in mark_as_advanced still say these track the legacy-remove setting. |
Sequence Diagram
sequenceDiagram
participant Caller
participant Array as itkArray
participant VNL as vnl_vector
Note over Array,VNL: Array(datain, sz, true) - ownership case
Caller->>Array: "Array(datain, sz, true)"
Array->>VNL: "protected_set_data(datain, sz, false)"
Note right of VNL: "m_LetArrayManageMemory = false (WRONG)"
Note over Array,VNL: At destruction
Array->>Array: "!m_LetArrayManageMemory is false, skip null-out"
VNL->>VNL: "~vnl_vector: flag=false, NO deallocation"
Note over VNL: "datain leaked"
Note over Array,VNL: SetSize on non-owning Array
Caller->>Array: "Array(ext, sz, false) then SetSize(newSz)"
Array->>VNL: "protected_set_data(nullptr, 0, false)"
Note right of VNL: "m_LetArrayManageMemory = false"
Array->>VNL: "set_size(newSz)"
VNL->>VNL: "vnl_vector_alloc_blah: assert(m_LetArrayManageMemory) FAILS"
Comments Outside Diff (1)
-
Modules/ThirdParty/VNL/CMakeLists.txt, line 194-195 (link)These two
mark_as_advancedcomments still say "must track ITK LegacyRemove setting", but both variables are now set unconditionally (not gated onITK_LEGACY_REMOVE/ITK_FUTURE_LEGACY_REMOVE). The comments are misleading for future readers.
Reviews (1): Last reviewed commit: "COMP: Adopt VXL private vnl_vector ivars..." | Re-trigger Greptile
| vnl_vector<TValue>::protected_set_data(datain, sz, false); | ||
| } | ||
|
|
||
| #if defined(ITK_LEGACY_REMOVE) |
There was a problem hiding this comment.
Memory leak when
LetArrayManageMemory=true. protected_set_data is called with a hardcoded false, which sets vnl's internal m_LetArrayManageMemory to false. When the caller passes true (meaning the Array should own and eventually free datain), neither the Array destructor nor vnl's destructor will actually deallocate the buffer: ~Array() skips the null-out because m_LetArrayManageMemory=true, and vnl_vector::~vnl_vector() skips deallocation because its own flag is false. The third argument should mirror LetArrayManageMemory.
| vnl_vector<TValue>::protected_set_data(datain, sz, false); | |
| } | |
| #if defined(ITK_LEGACY_REMOVE) | |
| vnl_vector<TValue>::protected_set_data(datain, sz, LetArrayManageMemory); | |
| } | |
| #if defined(ITK_LEGACY_REMOVE) |
| vnl_vector<TValue>::protected_set_data(datain, this->size(), false); | ||
| m_LetArrayManageMemory = LetArrayManageMemory; |
There was a problem hiding this comment.
Same ownership mismatch as the constructor and
SetData: when LetArrayManageMemory=true, SetDataSameSize should tell vnl to own the new buffer so that the destructor chain actually frees it.
| vnl_vector<TValue>::protected_set_data(datain, this->size(), false); | |
| m_LetArrayManageMemory = LetArrayManageMemory; | |
| vnl_vector<TValue>::protected_set_data(datain, this->size(), LetArrayManageMemory); | |
| m_LetArrayManageMemory = LetArrayManageMemory; |
| } | ||
| vnl_vector<TValue>::data = datain; | ||
| vnl_vector<TValue>::num_elmts = sz; | ||
| vnl_vector<TValue>::protected_set_data(datain, sz, false); |
There was a problem hiding this comment.
Same ownership-flag mismatch as in the constructor: the third argument is hardcoded to
false, so vnl's destructor will never free datain even when LetArrayManageMemory=true. The fix is protected_set_data(datain, sz, LetArrayManageMemory) so that vnl's ownership flag mirrors the caller's intent, exactly as done in VXL's own vnl_vector::set_data helper.
| if (!m_LetArrayManageMemory) | ||
| { | ||
| vnl_vector<TValue>::data = nullptr; | ||
| vnl_vector<TValue>::protected_set_data(nullptr, 0, false); | ||
| } | ||
|
|
||
| // Call the superclass's set_size |
There was a problem hiding this comment.
Assertion failure (debug) or silent memory leak (release) when resizing an externally-managed Array. After
protected_set_data(nullptr, 0, false), vnl's m_LetArrayManageMemory is false. The subsequent set_size(sz) call takes the else branch (data is null) and invokes vnl_vector_alloc_blah, which immediately asserts this->m_LetArrayManageMemory — that assertion fires because the flag is false. In release builds the assert is a no-op, but m_LetArrayManageMemory stays false, so the newly allocated block is never freed. Passing true correctly signals that ownership of the forthcoming allocation will be held by vnl (and thus by Array).
| if (!m_LetArrayManageMemory) | |
| { | |
| vnl_vector<TValue>::data = nullptr; | |
| vnl_vector<TValue>::protected_set_data(nullptr, 0, false); | |
| } | |
| // Call the superclass's set_size | |
| if (!m_LetArrayManageMemory) | |
| { | |
| vnl_vector<TValue>::protected_set_data(nullptr, 0, true); | |
| } | |
| // Call the superclass's set_size |
Switch
itk::Array<T>to usevnl_vector::protected_set_data(...)and turn offVXL_USE_HISTORICAL_PROTECTED_IVARSso ITK builds against modern VXL trees, including VXL 7.0.0 wherevnl_vector::dataandnum_elmtsare private.Why
protected_set_dataVXL added
vnl_vector::protected_set_data(T*, size_t, bool)specifically as the supported transition path for derived classes (such asitk::Array<T>) that previously assigneddataandnum_elmtsdirectly. The header comments it as:itk::Array<T>is the only ITK class that reaches into those base members directly, so the source-side change is localized toModules/Core/Common/include/itkArray.hxx. Each direct assignment is rewritten as aprotected_set_datacall that preserves the existing semantics (the third argument tracksvnl_vector's ownm_LetArrayManageMemoryflag; ownership is still managed byArray<T>::m_LetArrayManageMemory).Provenance: when did
protected_set_datafirst ship?vnl_vector::protected_set_datawas added in upstream VXL commitccdcf37b27("ENH: Add options to manage backward compatibility") on 2019-10-06.The commit landed on master long before any tag in the present tag chain pointed at it, so the first released VXL version that carries it is v3.3.0 (project VERSION 3.3.0.0, released 2021-05-24).
protected_set_data?The
Modules/ThirdParty/VNL/CMakeLists.txtminimum forITK_USE_SYSTEM_VXLis bumped from 3.0.0 to 4.0.0 to ensure any external VXL is recent enough to contain the helper.CMake configuration changes
find_package(VXL 3.0.0 REQUIRED)→find_package(VXL 4.0.0 REQUIRED)VXL_USE_HISTORICAL_IMPLICIT_CONVERSIONSis now unconditionallyOFF(previously gated onITK_LEGACY_REMOVE OR ITK_FUTURE_LEGACY_REMOVE).VXL_USE_HISTORICAL_PROTECTED_IVARSflips fromONtoOFF, which is what makesvnl_vector::data/num_elmtsprivate and is the trigger for the source-side change above.Test plan
pre-commit run --all-filespasses against the rewritten tip.ninja -C build-vxl700 -j10) succeeds against a VXL 7.0.0 tree configured withVXL_USE_HISTORICAL_PROTECTED_IVARS=OFF.