BUG: Wrap std::sto* exceptions in IO with itk::ExceptionObject (#3213) - #6089
Conversation
|
| Filename | Overview |
|---|---|
| Modules/Core/Common/include/itkStringTools.h | New header declaring five ITKCommon-exported string-to-numeric helpers with well-documented context-aware exception wrapping. |
| Modules/Core/Common/src/itkStringTools.cxx | Solid implementation; [[noreturn]] throwParseFailure correctly prevents "falls off end" UB; StringToULong leading-minus loop is correct; input truncation at 64 chars prevents large-message attacks. |
| Modules/Core/Common/test/itkStringToolsGTest.cxx | Good coverage of valid, invalid, overflow, truncation, null-context, and minus-rejection cases; missing an explicit StringToULong overflow test to match StringToInt parity. |
| Modules/IO/ImageBase/src/itkRegularExpressionSeriesFileNames.cxx | StringToDouble migrated correctly; however, the comparator throws inside std::sort (pre-existing UB, not worsened by this PR). |
| Modules/IO/SpatialObjects/src/itkPolygonGroupSpatialObjectXMLFile.cxx | Migration from std::stoi/stod to StringToInt/Float is correct; subtle behavior change: stod→stof for RESOLUTION fields now throws instead of silently producing infinity for out-of-float-range doubles. |
| Modules/IO/SpatialObjects/test/itkPolygonGroupXMLMalformedTest.cxx | New end-to-end regression test correctly verifies that malformed numeric XML surfaces as itk::ExceptionObject, not std::exception. |
| Modules/Video/IO/src/itkVideoIOFactory.cxx | StringToInt replaces std::stoi for camera index; minor: conversion is redundantly called per-loop-iteration (pre-existing pattern). |
| Modules/IO/GDCM/src/itkGDCMImageIO.cxx | DICOM pixel-depth fields migrated from std::stoi to StringToInt with informative contexts; straightforward mechanical migration. |
| Modules/IO/Siemens/src/itkSiemensVisionImageIO.cxx | TEXT_ANGLE empty-string guard preserved; TEXT_SLICE_POS and TEXT_ECHO_NUM now throw itk::ExceptionObject on malformed data instead of std::invalid_argument. |
| Modules/IO/NIFTI/src/itkNiftiImageIO.cxx | qform_code and sform_code metadata parsing migrated to StringToInt; clean migration with meaningful context strings. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["IO Reader calls std::sto*"] --> B{Conversion succeeds?}
B -- Yes --> C[Return parsed value]
B -- "No (invalid_argument or out_of_range)" --> D["itk::StringTo* helper catches std::exception"]
D --> E["throwParseFailure() [[noreturn]]"]
E --> F["itkGenericExceptionMacro throws itk::ExceptionObject with context + offending input"]
F --> G["Downstream catch(itk::ExceptionObject) in Slicer / application"]
G --> H[Graceful error handling]
style A fill:#f9f,stroke:#333
style F fill:#f96,stroke:#333
style H fill:#6f6,stroke:#333
Reviews (1): Last reviewed commit: "BUG: Wrap std::sto* call sites in IO wit..." | Re-trigger Greptile
8a97f38 to
cfd78de
Compare
9563196 to
8667009
Compare
dzenanz
left a comment
There was a problem hiding this comment.
The fix looks good on a glance. I did not take a good look at the tests.
8667009 to
3ad9e63
Compare
|
Renamed Why: Internally: Tests added: explicit No callers outside this PR's own GTest, so the rename is zero-blast-radius. Pre-commit passes locally; CI re-running on the new HEAD. |
3ad9e63 to
bc3ee65
Compare
|
Refactored to consistent fixed-width naming as suggested. Force-pushed as API now:
Same width-portability concern as Implementation:
Tests: all six functions get parity coverage — valid round-trip including boundary values ( IO consumers updated: Pre-commit clean locally; CI re-running on the new HEAD. |
bc3ee65 to
f597f52
Compare
|
@N-Dekker Thank you for catching this. It is an improvement over the historical code! |
Provide context-aware string-to-number helpers that catch std::invalid_argument and std::out_of_range from std::stoi/std::stol/ std::stoul/std::stod/std::stof and rethrow them as itk::ExceptionObject whose message includes a caller-supplied context string and the offending input. This is the prerequisite for fixing ITK issue InsightSoftwareConsortium#3213: low-level C++ conversion exceptions thrown by std::sto* propagate past code that catches only itk::ExceptionObject (notably Slicer, see Slicer/Slicer#6200), causing application crashes on corrupt input where the previous atoi/atof code silently returned 0. Tests added in this commit: itkStringToolsGTest covers valid round-trip, empty input, non-numeric input, out-of-range overflow, message content, the long-input truncation path, the null-context path, and the explicit-minus-sign rejection in StringToULong (which std::stoul silently wraps). itkPolygonGroupXMLMalformedTest is an end-to-end corruption fixture that feeds malformed XML to the PolygonGroup XML reader and asserts that the failure surfaces as itk::ExceptionObject (not std::exception), locking in the wrapping contract end-to-end. The IO migrations that let it pass arrive in the following commit.
Address ITK issue InsightSoftwareConsortium#3213. The earlier sweep that replaced atoi/atof with std::stoi/std::stod in production IO code introduced a behavior change that callers did not opt into: a corrupt file now throws a std::invalid_argument or std::out_of_range, which is not derived from itk::ExceptionObject. Applications that catch only itk::ExceptionObject (e.g. Slicer, see Slicer/Slicer#6200) crash on input that previously read silently as zero. Replace each non-test std::sto* call with the corresponding itk::StringTo* helper added in the previous commit. Each call site passes a context string identifying the field being parsed, so the resulting itk::ExceptionObject names the offending header field and quotes the offending input. Affected modules: Core/Common itkSmapsFileParser IO/Bruker Bruker 2dseq VisuFGOrderDesc parsing IO/GDCM DICOM (0028,0100/0101/0102/0103) attributes IO/GE SIGNA series/image numbers, images-per-slice IO/ImageBase RegularExpressionSeriesFileNames numeric sort IO/MeshVTK VTK PolyData header version IO/NIFTI NIfTI qform_code/sform_code metadata IO/Siemens Siemens Vision header text fields IO/SpatialObjects PolygonGroup XML reader Video/IO VideoIOFactory camera index Test-side calls (parsing argv in CTest drivers) are intentionally left unchanged: a stack trace from a developer-supplied bad argument is more useful there than a wrapped exception. Closes InsightSoftwareConsortium#3213
`lt_pair_numeric_string_string::operator()` calls `itk::StringToDouble`, which throws `itk::ExceptionObject` on malformed input. A comparator that throws while inside `std::sort` is undefined behavior in C++. Walk every (filename, sub-match) pair before invoking `std::sort` and let `itk::StringToDouble` validate each numeric key. A malformed key now surfaces as a clean `itk::ExceptionObject` with the offending input *before* `std::sort` is entered, eliminating the UB without changing the success-path behavior. Follow-up to InsightSoftwareConsortium#3213, addressing a concern flagged on PR review.
f597f52 to
8052dd8
Compare
|
dzenanz |
…ue-3213-string-processing-exception-types BUG: Wrap std::sto* exceptions in IO with itk::ExceptionObject (InsightSoftwareConsortium#3213)
…ue-3213-string-processing-exception-types BUG: Wrap std::sto* exceptions in IO with itk::ExceptionObject (InsightSoftwareConsortium#3213)
Fixes #3213 by introducing context-aware
itk::StringTo*helpers and migrating non-teststd::sto*call sites in the IO modules to use them. Corrupt input now raises anitk::ExceptionObject(with the offending field name and value) rather than a barestd::invalid_argument/std::out_of_rangethat crashes downstream applications such as Slicer.Background and rationale
ITK switched from atoi/atof to
std::stoi/std::stodin commit b9a384a (2018) for better error checking. As discussed in Slicer/Slicer#6200, that change introduced a behavior regression for callers that catch onlyitk::ExceptionObject: a corrupt header that previously parsed silently as zero now throws an exception unrelated to ITK's exception hierarchy and crashes the application. The Slicer reviewers asked that ITK catch the low-level conversion exceptions internally and rethrow them asitk::ExceptionObjectwith a meaningful message — which is what this PR does.Behavior tiers as framed in that thread:
Wrapping moves the IO read paths into the BEST tier without changing success-path behavior.
What changed
Commit 1 — ENH: Add itk::StringTo{Int,Long,ULong,Double,Float} helpers
Modules/Core/Common/include/itkStringTools.hand implementation insrc/itkStringTools.cxx.const char * context. On failure it throwsitk::ExceptionObjectwhose message includes the target type, the failure kind (invalid_argumentorout_of_range), the context, the offending input (truncated past 64 chars), and the underlyingwhat().StringToULongadditionally rejects an explicit leading minus sign;std::stoulsilently wraps it.itkStringToolsGTest.cxxcovers valid round-trip, empty input, non-numeric input, out-of-range overflow, message content, the long-input truncation path, the null-context path, and the explicit-minus rejection.Commit 2 — BUG: Wrap std::sto call sites in IO with itk::ExceptionObject*
Per-module migration:
Plus a new corruption test
itkPolygonGroupXMLMalformedTestthat feeds malformed XML and asserts the failure surfaces asitk::ExceptionObject(notstd::exception), locking in the wrapping contract end-to-end for at least one IO module.Scope notes (what is intentionally NOT touched)
std::sto*calls (parsing argv in CTest drivers; roughly 60 sites) are intentionally left as-is. A stack trace from a developer-supplied bad command-line argument is more diagnostic than a wrapped exception; the Slicer reviewers explicitly noted that test-side crashes are desirable.text_angle_len; this PR preserves that pattern.Test plan
ITKCommonGTestDriver --gtest_filter=StringTools.*passesitkPolygonGroupXMLMalformedTestpasses (asserts wrapped exception)