From a17a6495ac0da41a8612738ac5a303daad0e6eb8 Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Tue, 10 Nov 2026 14:09:00 +1300 Subject: [PATCH 1/2] [refactor] Replace std::stof with parse_float Replace the throwing std::stof calls with the non-throwing rta::helpers::parse_float helper (based on std::from_chars) across spectral_data.cpp, exiftool.cpp, and the UsageTimer test. The helper lives in src/cpp/misc/helpers.{h,cpp} and is wired into the relevant CMakeLists.txt files. A non-numeric input now returns false instead of throwing, letting callers handle malformed input gracefully. Assisted-by: nemotron-3.5-super-vl-free Signed-off-by: Anton Dukhovnikov --- src/cpp/misc/helpers.cpp | 21 ++++++++++++++++++++ src/cpp/misc/helpers.h | 24 +++++++++++++++++++++++ src/cpp/rawtoaces_core/CMakeLists.txt | 2 ++ src/cpp/rawtoaces_core/spectral_data.cpp | 13 +++++++++++- src/cpp/rawtoaces_util/CMakeLists.txt | 2 ++ src/cpp/rawtoaces_util/exiftool.cpp | 25 +++++++++++++++++------- tests/CMakeLists.txt | 2 ++ tests/testUsageTimer.cpp | 14 ++++--------- 8 files changed, 85 insertions(+), 18 deletions(-) create mode 100644 src/cpp/misc/helpers.cpp create mode 100644 src/cpp/misc/helpers.h diff --git a/src/cpp/misc/helpers.cpp b/src/cpp/misc/helpers.cpp new file mode 100644 index 00000000..c9235d38 --- /dev/null +++ b/src/cpp/misc/helpers.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the rawtoaces Project. + +#include "helpers.h" + +#include + +namespace rta +{ +namespace helpers +{ + +bool parse_float( const std::string &str, float &out ) +{ + auto [ptr, ec] = + std::from_chars( str.data(), str.data() + str.size(), out ); + return ec == std::errc(); +} + +} // namespace helpers +} // namespace rta diff --git a/src/cpp/misc/helpers.h b/src/cpp/misc/helpers.h new file mode 100644 index 00000000..77f48dbb --- /dev/null +++ b/src/cpp/misc/helpers.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the rawtoaces Project. + +#pragma once + +#include + +namespace rta +{ +namespace helpers +{ + +/// Parse a float from a string without throwing. +/// +/// Unlike `std::stof`, this never throws: a non-numeric input simply +/// returns `false`. +/// +/// @param str String to parse. +/// @param out Destination filled on success. +/// @return `true` if a float could be parsed from the start of `str`. +bool parse_float( const std::string &str, float &out ); + +} // namespace helpers +} // namespace rta diff --git a/src/cpp/rawtoaces_core/CMakeLists.txt b/src/cpp/rawtoaces_core/CMakeLists.txt index 83d95444..f26529c0 100644 --- a/src/cpp/rawtoaces_core/CMakeLists.txt +++ b/src/cpp/rawtoaces_core/CMakeLists.txt @@ -18,6 +18,7 @@ add_library( ${RAWTOACES_CORE_LIB} ${DO_SHARED} rawtoaces_core.cpp spectral_data.cpp core_math.cpp + ../misc/helpers.cpp # Make the headers visible in IDEs. This should not affect the builds. ${CORE_PUBLIC_HEADER} @@ -25,6 +26,7 @@ add_library( ${RAWTOACES_CORE_LIB} ${DO_SHARED} define.h core_math.h core_math_priv.h + ../misc/helpers.h ) target_link_libraries( diff --git a/src/cpp/rawtoaces_core/spectral_data.cpp b/src/cpp/rawtoaces_core/spectral_data.cpp index c17df26d..864f8994 100644 --- a/src/cpp/rawtoaces_core/spectral_data.cpp +++ b/src/cpp/rawtoaces_core/spectral_data.cpp @@ -2,6 +2,7 @@ // Copyright Contributors to the rawtoaces Project. #include +#include "../misc/helpers.h" #include #include @@ -278,7 +279,17 @@ bool SpectralData::load( for ( auto &[bin_wavelength, bin_values]: spectral_data[set_name].items() ) { - float this_wavelength = std::stof( bin_wavelength ); + float this_wavelength = 0.0f; + if ( !rta::helpers::parse_float( + bin_wavelength, this_wavelength ) ) + { + if ( error_message != nullptr ) + { + *error_message = "Error: Failed to parse wavelength '" + + bin_wavelength + "' in " + path + "."; + } + return false; + } if ( prev_wavelength != -1 ) { diff --git a/src/cpp/rawtoaces_util/CMakeLists.txt b/src/cpp/rawtoaces_util/CMakeLists.txt index 275685c9..e41c69a9 100644 --- a/src/cpp/rawtoaces_util/CMakeLists.txt +++ b/src/cpp/rawtoaces_util/CMakeLists.txt @@ -26,6 +26,8 @@ add_library ( ${RAWTOACES_UTIL_LIB} ${DO_SHARED} exiftool.h rawtoaces_util_priv.h attributes.h + ../misc/helpers.cpp + ../misc/helpers.h $<$: lens_correction.cpp diff --git a/src/cpp/rawtoaces_util/exiftool.cpp b/src/cpp/rawtoaces_util/exiftool.cpp index 70d66997..e18cada0 100644 --- a/src/cpp/rawtoaces_util/exiftool.cpp +++ b/src/cpp/rawtoaces_util/exiftool.cpp @@ -2,6 +2,7 @@ // Copyright Contributors to the rawtoaces Project. #include "exiftool.h" +#include "../misc/helpers.h" #include #include @@ -314,18 +315,24 @@ bool fetch_metadata( { if ( exiftool_key == "FocusDistance" ) { - do_focus_distance = true; - focus_distance = std::stof( value ); + if ( rta::helpers::parse_float( value, focus_distance ) ) + { + do_focus_distance = true; + } } else if ( exiftool_key == "FocusDistanceUpper" ) { - do_focus_distance = true; - focus_distance_upper = std::stof( value ); + if ( rta::helpers::parse_float( value, focus_distance_upper ) ) + { + do_focus_distance = true; + } } else if ( exiftool_key == "FocusDistanceLower" ) { - do_focus_distance = true; - focus_distance_lower = std::stof( value ); + if ( rta::helpers::parse_float( value, focus_distance_lower ) ) + { + do_focus_distance = true; + } } else if ( exiftool_key != "FileName" ) { @@ -337,7 +344,11 @@ bool fetch_metadata( if ( to_float ) { - spec[oiio_key] = std::stof( value ); + float parsed = 0.0f; + if ( rta::helpers::parse_float( value, parsed ) ) + { + spec[oiio_key] = parsed; + } } else { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 69d051a5..c0a1e968 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -210,6 +210,8 @@ add_executable ( test_utils.cpp test_data_helpers.cpp test_data_helpers.h + ../../src/cpp/misc/helpers.cpp + ../../src/cpp/misc/helpers.h ) target_link_libraries( diff --git a/tests/testUsageTimer.cpp b/tests/testUsageTimer.cpp index a52a9289..16711d66 100644 --- a/tests/testUsageTimer.cpp +++ b/tests/testUsageTimer.cpp @@ -6,6 +6,7 @@ #include #include +#include "../../../src/cpp/misc/helpers.h" #include #include @@ -35,16 +36,9 @@ float extractTimeFromOutput( const std::string &output ) if ( !timeStr.empty() ) { - try - { - return std::stof( timeStr ); - } - catch ( const std::exception &e ) - { - // always fails - OIIO_CHECK_EQUAL( - "Failed to parse time value: " + timeStr, e.what() ); - } + float parsed = 0.0f; + if ( rta::helpers::parse_float( timeStr, parsed ) ) + return parsed; } } } From 8db4e58fca0bc77c04882aa0fc9978189bccfc01 Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Tue, 10 Nov 2026 16:46:43 +1300 Subject: [PATCH 2/2] Add parse_float unittests to Test_Misc Add a test_parse_float unit test to Test_Misc covering valid decimal, integer, negative, and prefix-parsing inputs, plus non-numeric inputs that must return false without throwing. Wire src/cpp/misc/helpers.{h,cpp} into the Test_Misc target so rta::helpers::parse_float is available. Assisted-by: nemotron-3.5-super-vl-free Signed-off-by: Anton Dukhovnikov --- tests/CMakeLists.txt | 2 ++ tests/testMisc.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c0a1e968..4e708b5c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -190,6 +190,8 @@ add_test ( NAME Test_Logic COMMAND Test_Logic ) add_executable ( Test_Misc testMisc.cpp + ../../src/cpp/misc/helpers.cpp + ../../src/cpp/misc/helpers.h ) target_link_libraries( diff --git a/tests/testMisc.cpp b/tests/testMisc.cpp index 3fc8e8a6..4465b362 100644 --- a/tests/testMisc.cpp +++ b/tests/testMisc.cpp @@ -5,6 +5,7 @@ #include #include "../src/cpp/rawtoaces_core/define.h" +#include "../src/cpp/misc/helpers.h" #define DATA_PATH "../_deps/rawtoaces_data-src/data/" @@ -111,6 +112,31 @@ void test_IsValidCT() OIIO_CHECK_EQUAL( false, isValidCT( val14 ) ); } +void test_parse_float() +{ + float out = 0.0f; + + // Normal decimal value + OIIO_CHECK_EQUAL( true, rta::helpers::parse_float( "1.5", out ) ); + OIIO_CHECK_EQUAL( 1.5f, out ); + + // Integer string + OIIO_CHECK_EQUAL( true, rta::helpers::parse_float( "100", out ) ); + OIIO_CHECK_EQUAL( 100.0f, out ); + + // Negative value + OIIO_CHECK_EQUAL( true, rta::helpers::parse_float( "-2.25", out ) ); + OIIO_CHECK_EQUAL( -2.25f, out ); + + // Prefix parse accepted (matches std::stof behaviour): "100/1" yields 100 + OIIO_CHECK_EQUAL( true, rta::helpers::parse_float( "100/1", out ) ); + OIIO_CHECK_EQUAL( 100.0f, out ); + + // Non-numeric input returns false without throwing + OIIO_CHECK_EQUAL( false, rta::helpers::parse_float( "abc", out ) ); + OIIO_CHECK_EQUAL( false, rta::helpers::parse_float( "", out ) ); +} + int main( int, char ** ) { test_OpenDir(); @@ -118,6 +144,7 @@ int main( int, char ** ) test_IsNumeric(); test_IsCTLetterDigit(); test_IsValidCT(); + test_parse_float(); return unit_test_failures; }