Skip to content

BUG: SWIG typechecks must reject foreign wrapped types (fixes IsInsideBuffer, #5310)#6190

Closed
hjmjohnson wants to merge 1 commit intoInsightSoftwareConsortium:mainfrom
hjmjohnson:fix-swig-typecheck-overload
Closed

BUG: SWIG typechecks must reject foreign wrapped types (fixes IsInsideBuffer, #5310)#6190
hjmjohnson wants to merge 1 commit intoInsightSoftwareConsortium:mainfrom
hjmjohnson:fix-swig-typecheck-overload

Conversation

@hjmjohnson
Copy link
Copy Markdown
Member

Fix for Discourse #7495 and supersedes #5310. SWIG's overload dispatcher was always selecting the integer-only Index overload of ImageFunction::IsInsideBuffer because the typecheck typemaps in pyBase.i claimed viability for any object that satisfied PySequence_Check + length == dim — including wrapped ContinuousIndex / Point instances (they expose __getitem__ / __len__).

Root cause walk-through

itk::ImageFunction::IsInsideBuffer has three overloads (IndexType, ContinuousIndexType, PointType). The SWIG-generated dispatcher tries them in declaration order:

// Index typecheck (DECL_PYTHON_SEQ_TYPEMAP):
if (SWIG_ConvertPtr(arg, &ptr, SWIGTYPE_p_itkIndex3, 0) == -1
    && (!PySequence_Check(arg) || PyObject_Length(arg) != 3)
    && !PyInt_Check(arg))   _v = 0;
else                        _v = 1;

For interp.IsInsideBuffer(itk.ContinuousIndex[itk.D, 3]()):

  • SWIG_ConvertPtr to itkIndex3 returns -1 (different SWIG type).
  • PySequence_Check returns True (wrapped fixed-length classes expose __getitem__ / __len__).
  • PyObject_Length returns 3.

_v = 1, the dispatcher commits to the Index overload, then the in typemap demands integer elements and emits

ValueError: Expecting a sequence of int (or long)

The ContinuousIndex and Point overloads were never reached. The same logic broke interp.IsInsideBuffer(itk.Point[itk.D, 3]()) and interp.IsInsideBuffer([1.5, 2.5, 3.5]).

Fix

Tighten the four affected typecheck typemaps in Wrapping/Generators/Python/PyBase/pyBase.i:

  1. If SWIG_ConvertPtr succeeds for our descriptor → accept (strong type-correct match).
  2. Otherwise, if SWIG_Python_GetSwigThis returns non-NULL, the input is a SWIG-wrapped instance of an unrelated type → reject, so the dispatcher keeps looking for the right overload.
  3. Otherwise, fall through to the cheap scalar / sequence-length checks as before. For the integer-only SEQ typemap (Index / Size / Offset), additionally peek at element 0 and reject sequences whose first element is not a Python int — so [1.5, 2.5, 3.5] no longer claims viability for the Index overload.

This applies to:

  • DECL_PYTHON_VEC_TYPEMAPVector, CovariantVector, Point, ContinuousIndex, FixedArray
  • DECL_PYTHON_SEQ_TYPEMAPIndex, Size, Offset
  • DECL_PYTHON_VARLEN_SEQ_TYPEMAPArray, RGBPixel, RGBAPixel
Verification (hand-traced before the fix builds)

After the fix, the dispatcher resolves each test case like this:

Input Index typecheck ContinuousIndex typecheck Point typecheck (fallback) Selected overload
[1, 2, 3] (list of ints) ✅ (elem 0 is int) Index
itk.ContinuousIndex instance ❌ (GetSwigThis non-NULL, foreign type) ✅ (ConvertPtr succeeds) ContinuousIndex
itk.Point instance ❌ (foreign type) ❌ (foreign type) ✅ (final fallback) Point
[1.5, 2.5, 3.5] (list of floats) ❌ (elem 0 is float) ✅ (sequence accepts float) ContinuousIndex

Test itkLinearImageFunctionWrappingTest.py asserts each of these calls returns True for in-buffer coordinates and False for out-of-buffer coordinates.

Closes #5310.

…reConsortium#5310, Discourse #7495)

The Python overload dispatcher generated by SWIG resolves overloaded
methods by running each overload's `typecheck` typemap in order and
selecting the first that returns `_v=1`.  The fixed-length and
variable-length sequence typecheck typemaps in `pyBase.i` claimed
viability whenever the input passed `PySequence_Check` and had the
right length:

    if (SWIG_ConvertPtr(...) == -1
        && (!PySequence_Check($input) || PyObject_Length($input) != dim)
        && !PyInt_Check($input) && !PyFloat_Check($input))
        _v = 0;
    else
        _v = 1;

But every wrapped ITK fixed-length type (`itk::Index`,
`itk::ContinuousIndex`, `itk::Point`, `itk::Vector`, ...) exposes
`__getitem__` and `__len__` for ergonomic use from Python — which
makes `PySequence_Check` succeed and `PyObject_Length` return the
fixed dimension.  As a result, every typecheck claimed viability for
every wrapped fixed-length input of the right length, regardless of
the actual C++ type, and overload dispatch effectively reduced to
declaration order.

Concretely, `itk::ImageFunction::IsInsideBuffer` has three overloads
(`IndexType`, `ContinuousIndexType`, `PointType`).  Calling

    interp.IsInsideBuffer(itk.ContinuousIndex[itk.D, 3]())

or

    interp.IsInsideBuffer(itk.Point[itk.D, 3]())

both reached the `Index` overload first and failed with

    ValueError: Expecting a sequence of int (or long)

because the wrapped `ContinuousIndex`/`Point` instance was passed
into the integer-only `Index` typemap (its `__getitem__` returned
floats, which the `in` typemap rejected).  The other two overloads
were never considered.

Fix the four affected typecheck typemaps in
`DECL_PYTHON_VEC_TYPEMAP` (`Vector`/`CovariantVector`/`Point`/
`ContinuousIndex`/`FixedArray`), `DECL_PYTHON_SEQ_TYPEMAP`
(`Index`/`Size`/`Offset`), and `DECL_PYTHON_VARLEN_SEQ_TYPEMAP`
(`Array`/`RGBPixel`/`RGBAPixel`):

  1. If `SWIG_ConvertPtr` succeeds for our descriptor, accept (this
     is the strong, type-correct match).
  2. Otherwise, if `SWIG_Python_GetSwigThis` returns non-NULL the
     input is a SWIG-wrapped instance of an *unrelated* type — reject,
     so the dispatcher can keep looking for the right overload.
  3. Otherwise, fall through to the cheap scalar / sequence-length
     checks as before.  For the integer-only `SEQ` typemap we
     additionally peek at element 0 and reject sequences whose first
     element is not a Python int / long, so a `[1.5, 2.5, 3.5]` list
     no longer claims viability for `Index`.

After this change all four documented `IsInsideBuffer` call patterns
resolve to the intended overload (verified with the new
`itkLinearImageFunctionWrappingTest` regression test).

Closes InsightSoftwareConsortium#5310.

Co-Authored-By: Hans Johnson <hans-johnson@uiowa.edu>
@github-actions github-actions Bot added type:Bug Inconsistencies or issues which will cause an incorrect result under some or all circumstances type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots area:Python wrapping Python bindings for a class type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct area:Core Issues affecting the Core module labels May 2, 2026
@hjmjohnson
Copy link
Copy Markdown
Member Author

Closing — content moved into #5310 (force-pushed bdbe9ae6c3 to dzenanz/isInsideBuffer). All discussion now happens on the original PR.

@hjmjohnson hjmjohnson closed this May 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Core Issues affecting the Core module area:Python wrapping Python bindings for a class type:Bug Inconsistencies or issues which will cause an incorrect result under some or all circumstances type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant