Skip to content

BUG: Fix -Wstringop-truncation and replace fixed-width magic numbers#109

Merged
hjmjohnson merged 3 commits intoInsightSoftwareConsortium:mainfrom
hjmjohnson:update-cid-tags
Apr 30, 2026
Merged

BUG: Fix -Wstringop-truncation and replace fixed-width magic numbers#109
hjmjohnson merged 3 commits intoInsightSoftwareConsortium:mainfrom
hjmjohnson:update-cid-tags

Conversation

@hjmjohnson
Copy link
Copy Markdown
Member

Fixes 6 GCC -Wstringop-truncation warnings observed in Slicer CDash build 4206682 by switching the affected strncpy calls to std::snprintf, then refactors the bare 16/18/40/42/64/66/8/32 literals into a ScancoHeaderField constexpr namespace.

Root cause and reproduction

GCC 13/15 -Wstringop-truncation fires whenever strncpy(dst, src, N) is called with N == sizeof(dst): if src is at least N bytes the resulting buffer has no NUL terminator, and downstream Get*() accessors that return the buffer as a const char * C-string then read past the end. The in-memory m_HeaderData.m_* buffers are sized disk_width + 2 (e.g. m_Version[18] for the 16-byte ISQ field) specifically so a NUL terminator can fit; itkISQHeaderIO.cxx:137 already documents this with an explicit m_Version[16] = '\0' after memcpy. The strncpy(dst, src.c_str(), sizeof(dst)) pattern violated that invariant.

Reproduced locally with g++-15 against ~/src/ITK/build; all 6 warnings disappear with the snprintf replacement.

Commits
  1. BUG: Replace strncpy with snprintf to ensure NUL termination — 6 sites in src/itkScancoImageIO.cxx plus 6 setter inlines in include/itkScancoImageIO.h (same bug, didn't trigger in the Slicer build only because those setters weren't instantiated). std::snprintf(dst, sizeof(dst), "%s", src) always NUL-terminates and silently truncates oversized input.
  2. STYLE: Replace fixed-width Scanco field magic numbers with constexpr — introduces a ScancoHeaderField namespace in itkScancoDataManipulation.h with inline constexpr std::size_t for VersionDiskWidth/BufferSize (16/18), PatientNameDiskWidth/BufferSize (40/42), RescaleUnitsDiskWidth/BufferSize (16/18), CalibrationDataDiskWidth/BufferSize (64/66), EncodedDateDiskWidth (8), and DateStringBufferSize (32). Updates 33 call sites across itkScancoDataManipulation.{h,cxx}, itkISQHeaderIO.cxx, and itkAIMHeaderIO.cxx. No semantic change.
Out of scope
  • 5 strncpy(dst, src, src.length()+1) calls in itkAIMHeaderIO.cxx use a different pattern that doesn't trip -Wstringop-truncation but has its own buffer-overrun risk if input exceeds the destination width. Worth a follow-up.
  • 4 strncmp/memcpy literal-tag comparisons against "MultiHeader " / "Calibration " where the 16 is just the strlen of the literal — replacing those with the constant adds no clarity.

GCC's -Wstringop-truncation flagged 6 calls in itkScancoImageIO.cxx
where strncpy was invoked with a bound equal to the destination size.
When the source string is at least as long as the destination, strncpy
leaves the buffer without a NUL terminator, causing undefined behavior
in subsequent C-string accessors (GetVersion, GetPatientName, etc.).

The in-memory header buffers (m_Version[18], m_PatientName[42],
m_CreationDate[32], m_ModificationDate[32], m_RescaleUnits[18],
m_CalibrationData[66]) are explicitly sized two bytes wider than the
on-disk fixed-width fields (16, 40, 8/decoded-32, 32, 16, 64) so that a
NUL terminator always fits; itkISQHeaderIO.cxx:137 documents this with
an explicit '\\0' write.

Switch all six metadata-import sites and the five (six counting
ModificationDate) public Set*() inline accessors to
std::snprintf(dst, sizeof(dst), "%s", src), which always
NUL-terminates and silently truncates oversized input. Add <cstdio> to
both translation units.
The on-disk Scanco header has fixed-width text fields (16, 40, 64
bytes for Version/PatientName/CalibrationData and 16 for RescaleUnits;
8 bytes for the encoded VMS date) and the in-memory buffers are sized
two bytes wider so a NUL terminator always fits. Until now those
widths appeared as bare numeric literals throughout the
read/write/manipulate paths, with the disk-vs-buffer distinction only
implicit in pairings like 16/18, 40/42, 64/66.

Introduce a ScancoHeaderField namespace in itkScancoDataManipulation.h
holding constexpr widths for each field (DiskWidth + BufferSize pairs
plus EncodedDateDiskWidth and DateStringBufferSize), and use them at
all the read/write call sites in itkScancoImageIO, itkISQHeaderIO,
itkAIMHeaderIO, and itkScancoDataManipulation. No semantic change.
Copy link
Copy Markdown
Member

@dzenanz dzenanz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good on a glance.

@hjmjohnson hjmjohnson marked this pull request as ready for review April 30, 2026 19:46
@hjmjohnson hjmjohnson merged commit ce10a8e into InsightSoftwareConsortium:main Apr 30, 2026
15 of 16 checks passed
hjmjohnson added a commit that referenced this pull request Apr 30, 2026
Run clang-format 19.1.7 (matching the version pinned by ITK's
.pre-commit-config.yaml and used by the ITKClangFormatLinterAction)
over the files modified in PRs #109 (BUG/STYLE for Wstringop-truncation
and the constexpr field-width refactor).

The lint job on main (commit ce10a8e) failed because the snprintf
replacements and the ScancoHeaderField constants pushed several
multi-arg call sites past the column limit, which clang-format
re-wraps differently than the manual formatting in the merged commit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants