We’re hitting a UBSan pointer-overflow report in ImageConvolutionKernel::applyToImage() whenever the destination area
includes the left edge of the image (which it always does for a full-image blur):
runtime error: pointer index expression with base 0x… overflowed to 0x…
#4 juce::Image::BitmapData::getPixelPointer(x=-2, y=0) juce_Image.h:334
#5 juce::ImageConvolutionKernel::applyToImage(…) juce_ImageConvolutionKernel.cpp
The cause is in the inner loop (current master, still present after the 2024 refactor in fae3e2c010):
int sx = x - (size >> 1);
const uint8* src = srcData.getPixelPointer (sx, sy);
For output pixels near the left edge, sx is negative (e.g. -2 for a 5-wide kernel at x = 0). getPixelPointer() casts its
arguments to size_t, so the negative index wraps and the computed pointer overflows. The loop below never dereferences
out of bounds — it skips sx < 0 and does src += pixelStride to catch up — but merely forming the out-of-range pointer is
already undefined behaviour, and it trips -fsanitize=pointer-overflow.
The fix is minimal: clamp the start pointer to column 0 and drop the catch-up branch. While sx < 0 the pointer then
simply stays at pixel 0, which is exactly where it needs to be once sx reaches 0, so the output is bit-identical:
int sx = x - (size >> 1);
const uint8* src = srcData.getPixelPointer (jmax (0, sx), sy);
for (int xx = 0; xx < size; ++xx)
{
if (sx >= srcData.width)
break;
if (sx >= 0)
{
const auto kernelMult = values[xx + yy * size];
for (auto& s : sum)
s += kernelMult * *src++;
}
++sx;
}
Repro is any convolution whose destination area touches the left edge, e.g.:
juce::Image image (juce::Image::ARGB, 16, 16, true);
juce::ImageConvolutionKernel kernel (5);
kernel.createGaussianBlur (4.0f);
kernel.applyToImage (image, image, image.getBounds()); // pointer overflow at x < 2
built with -fsanitize=undefined (specifically pointer-overflow).
