Undefined Behaviour in ImageToImageMetricv4

There is a note here ITK/Modules/Registration/Metricsv4/include/itkImageToImageMetricv4.hxx at master · InsightSoftwareConsortium/ITK · GitHub that ImageToImageMetricv4 does not allocate the virtual image to save memory. However, this causes undefined behaviour in ImageRegionConstIteratorWithIndex, because the buffer pointer is used in some of the iterator calculations and is set to nullptr. Here is a full stack trace from UBSan:

/Users/tobias/Code/riesling/build/vcpkg_installed/arm64-osx/include/ITK-5.4/itkImageRegionConstIteratorWithIndex.hxx:37:24: runtime error: applying non-zero offset 4 to null pointer
    #0 0x1052083d8 in itk::ImageRegionConstIteratorWithIndex<itk::Image<float, 3u>>::operator++() itkImageRegionConstIteratorWithIndex.hxx:37
    #1 0x1054a2848 in itk::ImageToImageMetricv4GetValueAndDerivativeThreader<itk::ThreadedImageRegionPartitioner<3u>, itk::ImageToImageMetricv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double, itk::DefaultImageToImageMetricTraitsv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double>>>::ThreadedExecution(itk::ImageRegion<3u> const&, unsigned int) itkImageToImageMetricv4GetValueAndDerivativeThreader.hxx:35
    #2 0x1055936f0 in itk::DomainThreader<itk::ThreadedImageRegionPartitioner<3u>, itk::ImageToImageMetricv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double, itk::DefaultImageToImageMetricTraitsv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double>>>::ThreaderCallback(void*) itkDomainThreader.hxx:123
    #3 0x105af7ad8 in itk::PoolMultiThreader::SingleMethodExecute()::$_0::operator()() const itkPoolMultiThreader.cxx:144
    #4 0x105aeef78 in void itk::(anonymous namespace)::ExceptionHandler::TryAndCatch<itk::PoolMultiThreader::SingleMethodExecute()::$_0>(itk::PoolMultiThreader::SingleMethodExecute()::$_0 const&) itkPoolMultiThreader.cxx:54
    #5 0x105aeec8c in itk::PoolMultiThreader::SingleMethodExecute() itkPoolMultiThreader.cxx:144
    #6 0x105ab6bb0 in itk::MultiThreaderBase::SetSingleMethodAndExecute(void* (*)(void*), void*) itkMultiThreaderBase.cxx:453
    #7 0x105592c0c in itk::DomainThreader<itk::ThreadedImageRegionPartitioner<3u>, itk::ImageToImageMetricv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double, itk::DefaultImageToImageMetricTraitsv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double>>>::StartThreadingSequence() itkDomainThreader.hxx:98
    #8 0x105591898 in itk::DomainThreader<itk::ThreadedImageRegionPartitioner<3u>, itk::ImageToImageMetricv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double, itk::DefaultImageToImageMetricTraitsv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double>>>::Execute(itk::ImageToImageMetricv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double, itk::DefaultImageToImageMetricTraitsv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double>>*, itk::ImageRegion<3u> const&) itkDomainThreader.hxx:65
    #9 0x1053492f8 in itk::ImageToImageMetricv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double, itk::DefaultImageToImageMetricTraitsv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double>>::GetValueAndDerivativeExecute() const itkImageToImageMetricv4.hxx:273
    #10 0x1053142d8 in itk::ImageToImageMetricv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double, itk::DefaultImageToImageMetricTraitsv4<itk::Image<float, 3u>, itk::Image<float, 3u>, itk::Image<float, 3u>, double>>::GetValueAndDerivative(double&, itk::Array<double>&) const itkImageToImageMetricv4.hxx:244
    #11 0x1056d0f04 in itk::GradientDescentOptimizerv4Template<double>::ResumeOptimization() itkGradientDescentOptimizerv4.hxx:99
    #12 0x1056e9908 in itk::GradientDescentOptimizerv4Template<double>::StartOptimization(bool) itkGradientDescentOptimizerv4.hxx:54
    #13 0x1056cf37c in itk::ConjugateGradientLineSearchOptimizerv4Template<double>::StartOptimization(bool) itkConjugateGradientLineSearchOptimizerv4.hxx:45
    #14 0x1058aca00 in itk::MultiStartOptimizerv4Template<double>::ResumeOptimization() itkMultiStartOptimizerv4.hxx:177
    #15 0x1058aa028 in itk::MultiStartOptimizerv4Template<double>::StartOptimization(bool) itkMultiStartOptimizerv4.hxx:154
    #16 0x105216804 in merlin::Register(itk::SmartPointer<itk::Image<float, 3u>>, itk::SmartPointer<itk::Image<float, 3u>>, itk::SmartPointer<itk::Image<float, 3u>>) merlin.cpp:346
    #17 0x104f7bdfc in main main_merlin.cpp:43
    #18 0x19bffc270  (<unknown module>)

While this is likely benign, it leads to a lot of line noise while trying to use UB San to find other problems. Is there any way to fix this apart from either:

  1. Allocating the virtual image
  2. Adding some kind of RegionIndexIterator?

PS The “missing” Allocate() call is actually here ITK/Modules/Numerics/Optimizersv4/include/itkObjectToObjectMetric.hxx at master · InsightSoftwareConsortium/ITK · GitHub in itkObjectToObjectMetric.hxx. If I add this->m_VirtualImage->Allocate(); then the UB San messages go away, in my case the images are small so this fix would work for me.