From 7c647c6511ce49e1f47dcbada9a5f17c8efcf620 Mon Sep 17 00:00:00 2001 From: Bertrand Coconnier Date: Sun, 9 Feb 2025 12:59:43 +0100 Subject: [PATCH] Validate numeric input using regex and simplify code. --- src/input_output/string_utilities.cpp | 21 +++++----- tests/unit_tests/StringUtilitiesTest.h | 54 ++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/input_output/string_utilities.cpp b/src/input_output/string_utilities.cpp index 13bd159d0f..9ba5c24f9b 100644 --- a/src/input_output/string_utilities.cpp +++ b/src/input_output/string_utilities.cpp @@ -41,6 +41,7 @@ INCLUDES #include #include #include +#include #ifdef __APPLE__ #include #else @@ -84,26 +85,26 @@ struct CNumericLocale */ double atof_locale_c(const string& input) { - string v = input; - trim(v); + static const std::regex number_format(R"(^\s*[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?\s*$)"); + const char* first = input.c_str(); - if (v.empty()) { - InvalidNumber e("Expecting a numeric attribute value, but got an empty string"); + while (*first) { + if (!isspace(*first)) break; + first++; + } + + if (!*first) { + InvalidNumber e("Expecting a numeric attribute value, but only got spaces"); cerr << e.what() << endl; throw e; } - if (v.find_first_not_of("+-.0123456789Ee") != std::string::npos) { + if (!std::regex_match(input, number_format)) { InvalidNumber e("Expecting a numeric attribute value, but got: " + input); cerr << e.what() << endl; throw e; } - const char* first = v.c_str(); - - //Ignoring the leading '+' sign - if (*first == '+') ++first; - CNumericLocale numeric_c; errno = 0; // Reset the error code double value = strtod_l(first, nullptr, numeric_c.Locale); diff --git a/tests/unit_tests/StringUtilitiesTest.h b/tests/unit_tests/StringUtilitiesTest.h index 287cd167b4..3124c50371 100644 --- a/tests/unit_tests/StringUtilitiesTest.h +++ b/tests/unit_tests/StringUtilitiesTest.h @@ -89,14 +89,60 @@ class StringUtilitiesTest : public CxxTest::TestSuite } void testAtofLocaleC() { - TS_ASSERT_EQUALS(atof_locale_c("+1 "), 1.0); + // Test legal numbers + TS_ASSERT_EQUALS(atof_locale_c("0.0"), 0.0); + TS_ASSERT_EQUALS(atof_locale_c("+1"), 1.0); + TS_ASSERT_EQUALS(atof_locale_c("1"), 1.0); + TS_ASSERT_EQUALS(atof_locale_c("-1"), -1.0); TS_ASSERT_EQUALS(atof_locale_c(" 123.4"), 123.4); + TS_ASSERT_EQUALS(atof_locale_c("123.4 "), 123.4); + TS_ASSERT_EQUALS(atof_locale_c(" 123.4 "), 123.4); + TS_ASSERT_EQUALS(atof_locale_c(".25"), 0.25); + TS_ASSERT_EQUALS(atof_locale_c("1.e1"), 10.); + TS_ASSERT_EQUALS(atof_locale_c("1e1"), 10.); + TS_ASSERT_EQUALS(atof_locale_c(".1e1"), 1.); + TS_ASSERT_EQUALS(atof_locale_c("+.1e1"), 1.); + TS_ASSERT_EQUALS(atof_locale_c("-.1e1"), -1.); + TS_ASSERT_EQUALS(atof_locale_c("31.4e1"), 314); + TS_ASSERT_EQUALS(atof_locale_c("+3.14e+2"), 314); + TS_ASSERT_EQUALS(atof_locale_c("+3.14e2"), 314); + TS_ASSERT_EQUALS(atof_locale_c("+3.14e-2"), 0.0314); + TS_ASSERT_EQUALS(atof_locale_c("3.14e+2"), 314); + TS_ASSERT_EQUALS(atof_locale_c("3.14e2"), 314); + TS_ASSERT_EQUALS(atof_locale_c("3.14e-2"), 0.0314); + TS_ASSERT_EQUALS(atof_locale_c("-3.14e+2"), -314); + TS_ASSERT_EQUALS(atof_locale_c("-3.14e2"), -314); TS_ASSERT_EQUALS(atof_locale_c("-3.14e-2"), -0.0314); + TS_ASSERT_EQUALS(atof_locale_c("+3.14E+2"), 314); + TS_ASSERT_EQUALS(atof_locale_c("+3.14E2"), 314); + TS_ASSERT_EQUALS(atof_locale_c("+3.14E-2"), 0.0314); + TS_ASSERT_EQUALS(atof_locale_c("3.14E+2"), 314); + TS_ASSERT_EQUALS(atof_locale_c("3.14E2"), 314); + TS_ASSERT_EQUALS(atof_locale_c("3.14E-2"), 0.0314); + TS_ASSERT_EQUALS(atof_locale_c("-3.14E+2"), -314); + TS_ASSERT_EQUALS(atof_locale_c("-3.14E2"), -314); + TS_ASSERT_EQUALS(atof_locale_c("-3.14E-2"), -0.0314); + // Test rounded down numbers TS_ASSERT_EQUALS(atof_locale_c("1E-999"), 0.0); TS_ASSERT_EQUALS(atof_locale_c("-1E-999"), 0.0); - TS_ASSERT_EQUALS(atof_locale_c("0.0"), 0.0); - TS_ASSERT_THROWS(atof_locale_c("1E+999"), BaseException&); - TS_ASSERT_THROWS(atof_locale_c("-1E+999"), BaseException&); + // Test invalid numbers + TS_ASSERT_THROWS(atof_locale_c("1E+999"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c("-1E+999"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c("invalid"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c("1.0.0"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c("1E-"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c("E-2"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c("."), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c(".E"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c(".E2"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c(".E-2"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c("1.2E"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c("1.2E+"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c("1.2E1.0"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c("--1"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c("++1"), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c(""), InvalidNumber&); + TS_ASSERT_THROWS(atof_locale_c(" "), InvalidNumber&); } private: