This is an automated email from the ASF dual-hosted git repository.

mochen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 46838c88c2 Fix wrong checks for some integer config records (#12643)
46838c88c2 is described below

commit 46838c88c2e8b9583674002e15b843308de34d77
Author: Mo Chen <[email protected]>
AuthorDate: Wed Jan 14 10:34:42 2026 -0600

    Fix wrong checks for some integer config records (#12643)
    
    RECC_INT expects a numeric range, and not a regex.  Regex should be used 
with RECC_STR.
    
    1. Fix configs where RECC_INT is used with an invalid range.
    2. Add a static_assert so that RECC_INT is always used with a range.
    3. Make RECC_INT range check more robust.
    4. Add unit tests for range check.
---
 src/records/CMakeLists.txt              |   5 +-
 src/records/RecUtils.cc                 |  68 ++++++++---
 src/records/RecordsConfig.cc            | 146 ++++++++++++++++++++---
 src/records/unit_tests/test_RecUtils.cc | 201 ++++++++++++++++++++++++++++++++
 4 files changed, 389 insertions(+), 31 deletions(-)

diff --git a/src/records/CMakeLists.txt b/src/records/CMakeLists.txt
index 6a4bc9ff2f..907ec4f24c 100644
--- a/src/records/CMakeLists.txt
+++ b/src/records/CMakeLists.txt
@@ -40,7 +40,10 @@ target_link_libraries(
 )
 
 if(BUILD_TESTING)
-  add_executable(test_records unit_tests/unit_test_main.cc 
unit_tests/test_RecHttp.cc unit_tests/test_RecRegister.cc)
+  add_executable(
+    test_records unit_tests/unit_test_main.cc unit_tests/test_RecHttp.cc 
unit_tests/test_RecUtils.cc
+                 unit_tests/test_RecRegister.cc
+  )
   target_link_libraries(test_records PRIVATE records Catch2::Catch2 ts::tscore 
libswoc::libswoc ts::inkevent)
   add_catch2_test(NAME test_records COMMAND test_records)
 endif()
diff --git a/src/records/RecUtils.cc b/src/records/RecUtils.cc
index 5d1c63ba8e..5532f23370 100644
--- a/src/records/RecUtils.cc
+++ b/src/records/RecUtils.cc
@@ -29,6 +29,9 @@
 #include "records/RecordsConfig.h"
 #include "P_RecUtils.h"
 #include "P_RecCore.h"
+
+#include <charconv>
+#include <string_view>
 //-------------------------------------------------------------------------
 // RecRecord initializer / Free
 //-------------------------------------------------------------------------
@@ -390,23 +393,56 @@ recordRegexCheck(const char *pattern, const char *value)
 bool
 recordRangeCheck(const char *pattern, const char *value)
 {
-  char     *p = const_cast<char *>(pattern);
-  Tokenizer dashTok("-");
-
-  if (recordRegexCheck("^[0-9]+$", value)) {
-    while (*p != '[') {
-      p++;
-    } // skip to '['
-    if (dashTok.Initialize(++p, COPY_TOKS) == 2) {
-      int l_limit = atoi(dashTok[0]);
-      int u_limit = atoi(dashTok[1]);
-      int val     = atoi(value);
-      if (val >= l_limit && val <= u_limit) {
-        return true;
-      }
-    }
+  std::string_view sv_pattern(pattern);
+
+  // Find '[' and ']'
+  auto start = sv_pattern.find('[');
+  if (start != 0) {
+    Warning("recordRangeCheck: pattern '%s' does not start with '['", pattern);
+    return false; // No '[' found
   }
-  return false;
+
+  auto end = sv_pattern.find(']', start);
+  if (end != sv_pattern.size() - 1) {
+    return false; // No ']' found
+  }
+
+  // Extract range portion between brackets: "[0-10]" -> "0-10" or 
"[-123--100]" -> "-123--100"
+  sv_pattern = sv_pattern.substr(start + 1, end - start - 1);
+
+  RecInt lower_limit;
+  auto [lower_end, ec1] = std::from_chars(sv_pattern.data(), sv_pattern.data() 
+ sv_pattern.size(), lower_limit);
+  if (ec1 != std::errc{}) {
+    Warning("recordRangeCheck: failed to parse lower bound in pattern '%s'", 
pattern);
+    return false; // Failed to parse lower bound
+  }
+
+  if (lower_end >= sv_pattern.data() + sv_pattern.size() || *lower_end != '-') 
{
+    Warning("recordRangeCheck: no dash separator found in pattern '%s'", 
pattern);
+    return false; // No dash separator found
+  }
+
+  auto pos = lower_end + 1 - sv_pattern.data();
+  sv_pattern.remove_prefix(pos);
+  RecInt upper_limit;
+  auto [upper_end, ec2] = std::from_chars(sv_pattern.data(), sv_pattern.data() 
+ sv_pattern.size(), upper_limit);
+  if (ec2 != std::errc{} || upper_end != sv_pattern.data() + 
sv_pattern.size()) {
+    Warning("recordRangeCheck: failed to parse upper bound in pattern '%s'", 
pattern);
+    return false; // Failed to parse upper bound or extra characters
+  }
+
+  if (lower_limit > upper_limit) {
+    Warning("recordRangeCheck: invalid range in pattern '%s'", pattern);
+    return false;
+  }
+
+  RecInt val;
+  auto [value_end, ec3] = std::from_chars(value, value + strlen(value), val);
+  if (ec3 != std::errc{} || value_end != value + strlen(value)) {
+    return false; // Value parse error
+  }
+
+  return (val >= lower_limit && val <= upper_limit);
 }
 
 bool
diff --git a/src/records/RecordsConfig.cc b/src/records/RecordsConfig.cc
index 7ca85a7553..096f522ca9 100644
--- a/src/records/RecordsConfig.cc
+++ b/src/records/RecordsConfig.cc
@@ -26,6 +26,9 @@
 #include "tscore/Filenames.h"
 #include "records/RecordsConfig.h"
 
+#include <string_view>
+#include <cstddef>
+
 #if TS_USE_REMOTE_UNWINDING
 #define MGMT_CRASHLOG_HELPER "traffic_crashlog"
 #else
@@ -73,11 +76,11 @@ static constexpr RecordElement RecordsConfig[] =
   {RECT_CONFIG, "proxy.config.dump_mem_info_frequency", RECD_INT, "0", 
RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
   //# 0 == CLOCK_REALTIME, change carefully
-  {RECT_CONFIG, "proxy.config.system_clock", RECD_INT, "0", RECU_RESTART_TS, 
RR_NULL, RECC_INT, "[0-9]+", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.system_clock", RECD_INT, "0", RECU_RESTART_TS, 
RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.cache.max_disk_errors", RECD_INT, "5", 
RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.cache.persist_bad_disks", RECD_INT, "0", 
RECU_RESTART_TS, RR_NULL, RECC_INT, "[01]", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.cache.persist_bad_disks", RECD_INT, "0", 
RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.output.logfile.name", RECD_STRING, 
"traffic.out", RECU_RESTART_TS, RR_REQUIRED, RECC_NULL, nullptr,
    RECA_NULL}
@@ -207,7 +210,7 @@ static constexpr RecordElement RecordsConfig[] =
   ,
   {RECT_CONFIG, "proxy.config.diags.debug.tags", RECD_STRING, "http|dns", 
RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.diags.debug.throttling_interval_msec", RECD_INT, 
"0", RECU_DYNAMIC, RR_NULL, RECC_INT, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.diags.debug.throttling_interval_msec", RECD_INT, 
"0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.diags.debug.client_ip", RECD_STRING, nullptr, 
RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
@@ -414,7 +417,7 @@ static constexpr RecordElement RecordsConfig[] =
   ,
   {RECT_CONFIG, "proxy.config.http.auth_server_session_private", RECD_INT, 
"1", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-2]", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.http.max_post_size", RECD_INT, "0", 
RECU_DYNAMIC, RR_NULL, RECC_INT, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.http.max_post_size", RECD_INT, "0", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
   //        ##############################
   //        # parent proxy configuration #
@@ -1082,9 +1085,9 @@ static constexpr RecordElement RecordsConfig[] =
   {RECT_CONFIG, "proxy.config.log.max_line_size", RECD_INT, "9216", 
RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
   // How often periodic tasks get executed in the Log.cc infrastructure
-  {RECT_CONFIG, "proxy.config.log.periodic_tasks_interval", RECD_INT, "5", 
RECU_DYNAMIC, RR_NULL, RECC_INT, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.log.periodic_tasks_interval", RECD_INT, "5", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.log.throttling_interval_msec", RECD_INT, 
"60000", RECU_DYNAMIC, RR_NULL, RECC_INT, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.log.throttling_interval_msec", RECD_INT, 
"60000", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
 
   
//##############################################################################
@@ -1104,7 +1107,7 @@ static constexpr RecordElement RecordsConfig[] =
   ,
   {RECT_CONFIG, "proxy.config.plugin.dynamic_reload_mode", RECD_INT, "1", 
RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.url_remap.min_rules_required", RECD_INT, "0", 
RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-9]+", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.url_remap.min_rules_required", RECD_INT, "0", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.url_remap.acl_behavior_policy", RECD_INT, "0", 
RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
   ,
@@ -1251,16 +1254,16 @@ static constexpr RecordElement RecordsConfig[] =
   {RECT_CONFIG, "proxy.config.ssl.ocsp.enabled", RECD_INT, "0", 
RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
   ,
   //        # Number of seconds before an OCSP response expires in the 
stapling cache. 3600s (1 hour) by default.
-  {RECT_CONFIG, "proxy.config.ssl.ocsp.cache_timeout", RECD_INT, "3600", 
RECU_DYNAMIC, RR_NULL, RECC_INT, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.ssl.ocsp.cache_timeout", RECD_INT, "3600", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
   //        # Request method "mode" for queries to OCSP responders; 0 is POST, 
1 is "prefer GET."
   {RECT_CONFIG, "proxy.config.ssl.ocsp.request_mode", RECD_INT, "0", 
RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
   ,
   //        # Timeout for queries to OCSP responders. 10s by default.
-  {RECT_CONFIG, "proxy.config.ssl.ocsp.request_timeout", RECD_INT, "10", 
RECU_DYNAMIC, RR_NULL, RECC_INT, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.ssl.ocsp.request_timeout", RECD_INT, "10", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
   //        # Update period for stapling caches. 60s (1 min) by default.
-  {RECT_CONFIG, "proxy.config.ssl.ocsp.update_period", RECD_INT, "60", 
RECU_DYNAMIC, RR_NULL, RECC_INT, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.ssl.ocsp.update_period", RECD_INT, "60", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
   //        # Base path for OCSP prefetched responses
   {RECT_CONFIG, "proxy.config.ssl.ocsp.response.path", RECD_STRING, 
TS_BUILD_SYSCONFDIR, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
@@ -1472,13 +1475,13 @@ static constexpr RecordElement RecordsConfig[] =
   ,
   {RECT_CONFIG, "proxy.config.quic.disable_active_migration", RECD_INT, "0", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "[0-1]", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.quic.max_recv_udp_payload_size_in", RECD_INT, 
"65527", RECU_DYNAMIC, RR_NULL, RECC_INT, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.quic.max_recv_udp_payload_size_in", RECD_INT, 
"65527", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.quic.max_recv_udp_payload_size_out", RECD_INT, 
"65527", RECU_DYNAMIC, RR_NULL, RECC_INT, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.quic.max_recv_udp_payload_size_out", RECD_INT, 
"65527", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.quic.max_send_udp_payload_size_in", RECD_INT, 
"65527", RECU_DYNAMIC, RR_NULL, RECC_INT, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.quic.max_send_udp_payload_size_in", RECD_INT, 
"65527", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.quic.max_send_udp_payload_size_out", RECD_INT, 
"65527", RECU_DYNAMIC, RR_NULL, RECC_INT, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.quic.max_send_udp_payload_size_out", RECD_INT, 
"65527", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.quic.disable_http_0_9", RECD_INT, "1", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "[0-1]", RECA_NULL}
   ,
@@ -1577,6 +1580,116 @@ validate_check_type_has_regex()
   return true;
 }
 
+//-------------------------------------------------------------------------
+// Compile-time validation helpers for RECC_INT patterns
+//-------------------------------------------------------------------------
+
+namespace
+{
+constexpr bool
+is_digit(char c)
+{
+  return c >= '0' && c <= '9';
+}
+
+/**
+ * Parses an integer (possibly negative) from the given string view at compile 
time.
+ * Updates the index `i` to point past the parsed number.
+ * Returns true if a valid integer was parsed, false otherwise.
+ *
+ * This function is intended for compile-time validation of integer patterns.
+ * Note: C++23 introduces std::from_chars for constexpr integer parsing.
+ *
+ * @param[in] s The string view to parse.
+ * @param[in,out] i The index into `s` where parsing starts; updated to point 
past the parsed integer.
+ * @return true if parsing succeeded, false otherwise.
+ */
+constexpr bool
+parse_int(std::string_view s, std::size_t &i)
+{
+  std::size_t start = i;
+
+  // Optional negative sign
+  if (i < s.size() && s[i] == '-') {
+    ++i;
+  }
+
+  // Must have at least one digit
+  if (i >= s.size() || !is_digit(s[i])) {
+    i = start; // restore position on failure
+    return false;
+  }
+
+  // Parse remaining digits
+  while (i < s.size() && is_digit(s[i])) {
+    ++i;
+  }
+
+  return true; // Successfully parsed at least one digit
+}
+
+/**
+ * @brief Validates whether a string matches the [lower-upper] format with 
integer bounds.
+ *
+ * Supports negative numbers for both bounds.
+ * Examples of valid formats: "[0-255]", "[-100--50]".
+ *
+ * @param s The string to validate.
+ * @return true if the string matches the format, false otherwise.
+ */
+constexpr bool
+matches_bracketed_int_range(std::string_view s)
+{
+  std::size_t i = 0;
+  if (i >= s.size() || s[i++] != '[') {
+    return false;
+  }
+  // Parse first integer (may be negative)
+  if (!parse_int(s, i)) {
+    return false;
+  }
+  // Next character should be the dash separator
+  if (i >= s.size() || s[i++] != '-') {
+    return false;
+  }
+  // Parse second integer (may be negative)
+  if (!parse_int(s, i)) {
+    return false;
+  }
+  if (i >= s.size() || s[i++] != ']') {
+    return false;
+  }
+  return i == s.size(); // must end exactly here
+}
+
+// For string literals: deduces N and strips the trailing '\0'
+template <std::size_t N>
+consteval bool
+matches_bracketed_int_range(const char (&lit)[N])
+{
+  // N includes the null terminator
+  return matches_bracketed_int_range(std::string_view{lit, N - 1});
+}
+
+// Validate all RECC_INT entries in the RecordsConfig array at compile time
+template <std::size_t N>
+consteval bool
+validate_recc_int_patterns(const RecordElement (&config)[N])
+{
+  for (std::size_t i = 0; i < N; ++i) {
+    if (config[i].check == RECC_INT) {
+      if (config[i].regex == nullptr) {
+        return false; // RECC_INT must have a pattern
+      }
+      if (!matches_bracketed_int_range(config[i].regex)) {
+        return false; // Pattern must match [lower-upper] format
+      }
+    }
+  }
+  return true;
+}
+} // namespace
+
 static_assert(validate_regex_has_check_type(),
               "RecordsConfig validation failed: found RecordElement with regex 
pattern but no check type (RECC_NULL). "
               "Specify appropriate check type like RECC_INT, RECC_STRING, 
etc.");
@@ -1585,6 +1698,11 @@ static_assert(validate_check_type_has_regex(),
               "RecordsConfig validation failed: found RecordElement with check 
type but no regex pattern. "
               "Provide a regex pattern to validate the input or use RECC_NULL 
if no validation is needed.");
 
+static_assert(validate_recc_int_patterns(RecordsConfig),
+              "RecordsConfig validation failed: found RECC_INT with invalid 
pattern. "
+              "RECC_INT patterns must be in format [lower-upper] with integer 
bounds (negative numbers supported), "
+              "e.g., \"[0-2]\", \"[1-256]\", or \"[-100--50]\".");
+
 void
 RecordsConfigIterate(RecordElementCallback callback, void *data)
 {
diff --git a/src/records/unit_tests/test_RecUtils.cc 
b/src/records/unit_tests/test_RecUtils.cc
new file mode 100644
index 0000000000..30acc44a4b
--- /dev/null
+++ b/src/records/unit_tests/test_RecUtils.cc
@@ -0,0 +1,201 @@
+/** @file
+
+   Catch-based tests for RecUtils.cc
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include <catch2/catch_test_macros.hpp>
+
+#include "../P_RecUtils.h"
+
+TEST_CASE("recordRangeCheck via RecordValidityCheck", "[librecords][RecUtils]")
+{
+  SECTION("valid ranges")
+  {
+    // Basic range checks
+    REQUIRE(RecordValidityCheck("0", RECC_INT, "[0-1]"));
+    REQUIRE(RecordValidityCheck("1", RECC_INT, "[0-1]"));
+    REQUIRE(RecordValidityCheck("5", RECC_INT, "[0-10]"));
+    REQUIRE(RecordValidityCheck("10", RECC_INT, "[0-10]"));
+
+    // Larger ranges
+    REQUIRE(RecordValidityCheck("100", RECC_INT, "[0-255]"));
+    REQUIRE(RecordValidityCheck("255", RECC_INT, "[0-255]"));
+    REQUIRE(RecordValidityCheck("1024", RECC_INT, "[1-2048]"));
+  }
+
+  SECTION("boundary conditions")
+  {
+    // Lower boundary
+    REQUIRE(RecordValidityCheck("0", RECC_INT, "[0-100]"));
+    REQUIRE(RecordValidityCheck("1", RECC_INT, "[1-100]"));
+
+    // Upper boundary
+    REQUIRE(RecordValidityCheck("100", RECC_INT, "[0-100]"));
+    REQUIRE(RecordValidityCheck("99", RECC_INT, "[0-99]"));
+
+    // Single value range
+    REQUIRE(RecordValidityCheck("5", RECC_INT, "[5-5]"));
+  }
+
+  SECTION("out of range values")
+  {
+    // Below lower bound
+    REQUIRE_FALSE(RecordValidityCheck("-1", RECC_INT, "[0-10]"));
+    REQUIRE_FALSE(RecordValidityCheck("0", RECC_INT, "[1-10]"));
+
+    // Above upper bound
+    REQUIRE_FALSE(RecordValidityCheck("11", RECC_INT, "[0-10]"));
+    REQUIRE_FALSE(RecordValidityCheck("256", RECC_INT, "[0-255]"));
+
+    // Way out of bounds
+    REQUIRE_FALSE(RecordValidityCheck("1000", RECC_INT, "[0-10]"));
+    REQUIRE_FALSE(RecordValidityCheck("-1000", RECC_INT, "[0-10]"));
+  }
+
+  SECTION("invalid input formats")
+  {
+    // Non-numeric values (should fail parse_int validation)
+    REQUIRE_FALSE(RecordValidityCheck("abc", RECC_INT, "[0-10]"));
+    REQUIRE_FALSE(RecordValidityCheck("12abc", RECC_INT, "[0-100]"));
+    REQUIRE_FALSE(RecordValidityCheck("abc12", RECC_INT, "[0-100]"));
+    REQUIRE_FALSE(RecordValidityCheck("1.5", RECC_INT, "[0-10]"));
+
+    // Empty string
+    REQUIRE_FALSE(RecordValidityCheck("", RECC_INT, "[0-10]"));
+
+    // Whitespace
+    REQUIRE_FALSE(RecordValidityCheck(" 5", RECC_INT, "[0-10]"));
+    REQUIRE_FALSE(RecordValidityCheck("5 ", RECC_INT, "[0-10]"));
+    REQUIRE_FALSE(RecordValidityCheck(" 5 ", RECC_INT, "[0-10]"));
+  }
+
+  SECTION("negative ranges supported")
+  {
+    // Patterns with negative numbers are now supported
+    // Parsed left-to-right to handle dash as separator vs negative sign
+    REQUIRE(RecordValidityCheck("-5", RECC_INT, "[-10-0]"));
+    REQUIRE(RecordValidityCheck("0", RECC_INT, "[-10-10]"));
+    REQUIRE(RecordValidityCheck("-1", RECC_INT, "[-5--1]"));
+    REQUIRE(RecordValidityCheck("-100", RECC_INT, "[-123--100]"));
+    REQUIRE(RecordValidityCheck("-50", RECC_INT, "[-100-0]"));
+
+    // Positive value in negative range
+    REQUIRE(RecordValidityCheck("5", RECC_INT, "[-10-20]"));
+
+    // Out of range negative values
+    REQUIRE_FALSE(RecordValidityCheck("-11", RECC_INT, "[-10-0]"));
+    REQUIRE_FALSE(RecordValidityCheck("-6", RECC_INT, "[-5--1]"));
+    REQUIRE_FALSE(RecordValidityCheck("-124", RECC_INT, "[-123--100]"));
+
+    // Boundary conditions with negative numbers
+    REQUIRE(RecordValidityCheck("-123", RECC_INT, "[-123--100]"));      // 
lower bound
+    REQUIRE(RecordValidityCheck("-100", RECC_INT, "[-123--100]"));      // 
upper bound
+    REQUIRE_FALSE(RecordValidityCheck("-99", RECC_INT, "[-123--100]")); // 
just above upper bound
+  }
+
+  SECTION("invalid pattern formats")
+  {
+    // Missing brackets
+    REQUIRE_FALSE(RecordValidityCheck("5", RECC_INT, "0-10"));
+    REQUIRE_FALSE(RecordValidityCheck("5", RECC_INT, "[0-10"));
+    REQUIRE_FALSE(RecordValidityCheck("5", RECC_INT, "0-10]"));
+
+    // No bracket at all
+    REQUIRE_FALSE(RecordValidityCheck("5", RECC_INT, "invalid"));
+
+    // Invalid range format (no dash)
+    REQUIRE_FALSE(RecordValidityCheck("5", RECC_INT, "[010]"));
+
+    // Non-numeric range
+    REQUIRE_FALSE(RecordValidityCheck("5", RECC_INT, "[a-z]"));
+  }
+
+  SECTION("edge cases from actual ATS config")
+  {
+    // From RecordsConfig.cc examples
+    REQUIRE(RecordValidityCheck("0", RECC_INT, "[0-1]"));
+    REQUIRE(RecordValidityCheck("1", RECC_INT, "[0-1]"));
+    REQUIRE(RecordValidityCheck("2", RECC_INT, "[0-2]"));
+    REQUIRE(RecordValidityCheck("3", RECC_INT, "[0-3]"));
+    REQUIRE(RecordValidityCheck("256", RECC_INT, "[1-256]"));
+
+    // Common boolean patterns
+    REQUIRE(RecordValidityCheck("0", RECC_INT, "[0-1]"));       // false
+    REQUIRE(RecordValidityCheck("1", RECC_INT, "[0-1]"));       // true
+    REQUIRE_FALSE(RecordValidityCheck("2", RECC_INT, "[0-1]")); // invalid bool
+  }
+
+  SECTION("std::from_chars advantages - strict parsing")
+  {
+    // These would pass with atoi("123abc") -> 123
+    // But fail with from_chars (correct behavior)
+    REQUIRE_FALSE(RecordValidityCheck("5extra", RECC_INT, "[0-10]"));
+    REQUIRE_FALSE(RecordValidityCheck("10garbage", RECC_INT, "[0-100]"));
+    REQUIRE_FALSE(RecordValidityCheck("0x10", RECC_INT, "[0-100]")); // hex 
not supported in this context
+  }
+
+  SECTION("zero handling")
+  {
+    // Ensure "0" is properly distinguished from parse errors
+    // (atoi returns 0 for both "0" and parse errors)
+    REQUIRE(RecordValidityCheck("0", RECC_INT, "[0-10]"));
+    REQUIRE_FALSE(RecordValidityCheck("invalid", RECC_INT, "[0-10]")); // Also 
would be 0 with atoi
+  }
+
+  SECTION("large numbers")
+  {
+    // Test with realistic config values
+    REQUIRE(RecordValidityCheck("65535", RECC_INT, "[1-65535]")); // max uint16
+    REQUIRE(RecordValidityCheck("8080", RECC_INT, "[1-65535]"));  // typical 
port
+    REQUIRE(RecordValidityCheck("32768", RECC_INT, "[1024-65535]"));
+
+    // Out of typical ranges
+    REQUIRE_FALSE(RecordValidityCheck("65536", RECC_INT, "[1-65535]"));
+    REQUIRE_FALSE(RecordValidityCheck("100000", RECC_INT, "[1-65535]"));
+  }
+
+  SECTION("overflow and underflow handling")
+  {
+    // RecInt is int64_t:
+    // INT64_MAX = 9223372036854775807
+    // INT64_MIN = -9223372036854775808
+
+    // Valid boundary values for int64_t
+    REQUIRE(RecordValidityCheck("9223372036854775807", RECC_INT, 
"[0-9223372036854775807]"));   // INT64_MAX
+    REQUIRE(RecordValidityCheck("-9223372036854775808", RECC_INT, 
"[-9223372036854775808-0]")); // INT64_MIN
+
+    // Values that overflow INT64_MAX (should fail to parse)
+    REQUIRE_FALSE(RecordValidityCheck("9223372036854775808", RECC_INT, 
"[0-9999999999999999999]"));   // INT64_MAX + 1
+    REQUIRE_FALSE(RecordValidityCheck("99999999999999999999", RECC_INT, 
"[0-99999999999999999999]")); // Way over
+
+    // Values that underflow INT64_MIN (should fail to parse)
+    REQUIRE_FALSE(RecordValidityCheck("-9223372036854775809", RECC_INT, 
"[-9999999999999999999-0]"));   // INT64_MIN - 1
+    REQUIRE_FALSE(RecordValidityCheck("-99999999999999999999", RECC_INT, 
"[-99999999999999999999-0]")); // Way under
+
+    // Pattern bounds that overflow (should fail to parse the pattern itself)
+    REQUIRE_FALSE(RecordValidityCheck("5", RECC_INT, 
"[0-9223372036854775808]"));    // upper bound overflows
+    REQUIRE_FALSE(RecordValidityCheck("5", RECC_INT, 
"[-9223372036854775809-100]")); // lower bound underflows
+
+    // Valid values near the boundaries
+    REQUIRE(RecordValidityCheck("9223372036854775806", RECC_INT, 
"[0-9223372036854775807]"));   // INT64_MAX - 1
+    REQUIRE(RecordValidityCheck("-9223372036854775807", RECC_INT, 
"[-9223372036854775808-0]")); // INT64_MIN + 1
+  }
+}

Reply via email to