aboutsummaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
authorpracticalswift <practicalswift@users.noreply.github.com>2021-09-18 04:30:30 +0000
committerpracticalswift <practicalswift@users.noreply.github.com>2021-09-18 04:31:24 +0000
commit4747db876154ddd828c03d9eda10ecf8b25d8dc8 (patch)
tree7b9b6ebad2043653c550fb1adc2be76742804711 /src/util
parente69cbac628bfdca4a8e4ead821190eaf5b6b3d07 (diff)
util: Introduce ToIntegral<T>(const std::string&) for locale independent parsing using std::from_chars(…) (C++17)
util: Avoid locale dependent functions strtol/strtoll/strtoul/strtoull in ParseInt32/ParseInt64/ParseUInt32/ParseUInt64 fuzz: Assert equivalence between new and old Parse{Int,Uint}{8,32,64} functions test: Add unit tests for ToIntegral<T>(const std::string&)
Diffstat (limited to 'src/util')
-rw-r--r--src/util/strencodings.cpp110
-rw-r--r--src/util/strencodings.h20
2 files changed, 58 insertions, 72 deletions
diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp
index f514613f0d..0aa80ea0ae 100644
--- a/src/util/strencodings.cpp
+++ b/src/util/strencodings.cpp
@@ -11,8 +11,7 @@
#include <algorithm>
#include <cstdlib>
#include <cstring>
-#include <errno.h>
-#include <limits>
+#include <optional>
static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@@ -282,6 +281,32 @@ std::string DecodeBase32(const std::string& str, bool* pf_invalid)
return std::string((const char*)vchRet.data(), vchRet.size());
}
+[[nodiscard]] static bool ParsePrechecks(const std::string&);
+
+namespace {
+template <typename T>
+bool ParseIntegral(const std::string& str, T* out)
+{
+ static_assert(std::is_integral<T>::value);
+ if (!ParsePrechecks(str)) {
+ return false;
+ }
+ // Replicate the exact behavior of strtol/strtoll/strtoul/strtoull when
+ // handling leading +/- for backwards compatibility.
+ if (str.length() >= 2 && str[0] == '+' && str[1] == '-') {
+ return false;
+ }
+ const std::optional<T> opt_int = ToIntegral<T>((!str.empty() && str[0] == '+') ? str.substr(1) : str);
+ if (!opt_int) {
+ return false;
+ }
+ if (out != nullptr) {
+ *out = *opt_int;
+ }
+ return true;
+}
+}; // namespace
+
[[nodiscard]] static bool ParsePrechecks(const std::string& str)
{
if (str.empty()) // No empty string allowed
@@ -293,95 +318,36 @@ std::string DecodeBase32(const std::string& str, bool* pf_invalid)
return true;
}
-bool ParseInt32(const std::string& str, int32_t *out)
+bool ParseInt32(const std::string& str, int32_t* out)
{
- if (!ParsePrechecks(str))
- return false;
- char *endp = nullptr;
- errno = 0; // strtol will not set errno if valid
- long int n = strtol(str.c_str(), &endp, 10);
- if(out) *out = (int32_t)n;
- // Note that strtol returns a *long int*, so even if strtol doesn't report an over/underflow
- // we still have to check that the returned value is within the range of an *int32_t*. On 64-bit
- // platforms the size of these types may be different.
- return endp && *endp == 0 && !errno &&
- n >= std::numeric_limits<int32_t>::min() &&
- n <= std::numeric_limits<int32_t>::max();
+ return ParseIntegral<int32_t>(str, out);
}
-bool ParseInt64(const std::string& str, int64_t *out)
+bool ParseInt64(const std::string& str, int64_t* out)
{
- if (!ParsePrechecks(str))
- return false;
- char *endp = nullptr;
- errno = 0; // strtoll will not set errno if valid
- long long int n = strtoll(str.c_str(), &endp, 10);
- if(out) *out = (int64_t)n;
- // Note that strtoll returns a *long long int*, so even if strtol doesn't report an over/underflow
- // we still have to check that the returned value is within the range of an *int64_t*.
- return endp && *endp == 0 && !errno &&
- n >= std::numeric_limits<int64_t>::min() &&
- n <= std::numeric_limits<int64_t>::max();
+ return ParseIntegral<int64_t>(str, out);
}
-bool ParseUInt8(const std::string& str, uint8_t *out)
+bool ParseUInt8(const std::string& str, uint8_t* out)
{
- uint32_t u32;
- if (!ParseUInt32(str, &u32) || u32 > std::numeric_limits<uint8_t>::max()) {
- return false;
- }
- if (out != nullptr) {
- *out = static_cast<uint8_t>(u32);
- }
- return true;
+ return ParseIntegral<uint8_t>(str, out);
}
bool ParseUInt16(const std::string& str, uint16_t* out)
{
- uint32_t u32;
- if (!ParseUInt32(str, &u32) || u32 > std::numeric_limits<uint16_t>::max()) {
- return false;
- }
- if (out != nullptr) {
- *out = static_cast<uint16_t>(u32);
- }
- return true;
+ return ParseIntegral<uint16_t>(str, out);
}
-bool ParseUInt32(const std::string& str, uint32_t *out)
+bool ParseUInt32(const std::string& str, uint32_t* out)
{
- if (!ParsePrechecks(str))
- return false;
- if (str.size() >= 1 && str[0] == '-') // Reject negative values, unfortunately strtoul accepts these by default if they fit in the range
- return false;
- char *endp = nullptr;
- errno = 0; // strtoul will not set errno if valid
- unsigned long int n = strtoul(str.c_str(), &endp, 10);
- if(out) *out = (uint32_t)n;
- // Note that strtoul returns a *unsigned long int*, so even if it doesn't report an over/underflow
- // we still have to check that the returned value is within the range of an *uint32_t*. On 64-bit
- // platforms the size of these types may be different.
- return endp && *endp == 0 && !errno &&
- n <= std::numeric_limits<uint32_t>::max();
+ return ParseIntegral<uint32_t>(str, out);
}
-bool ParseUInt64(const std::string& str, uint64_t *out)
+bool ParseUInt64(const std::string& str, uint64_t* out)
{
- if (!ParsePrechecks(str))
- return false;
- if (str.size() >= 1 && str[0] == '-') // Reject negative values, unfortunately strtoull accepts these by default if they fit in the range
- return false;
- char *endp = nullptr;
- errno = 0; // strtoull will not set errno if valid
- unsigned long long int n = strtoull(str.c_str(), &endp, 10);
- if(out) *out = (uint64_t)n;
- // Note that strtoull returns a *unsigned long long int*, so even if it doesn't report an over/underflow
- // we still have to check that the returned value is within the range of an *uint64_t*.
- return endp && *endp == 0 && !errno &&
- n <= std::numeric_limits<uint64_t>::max();
+ return ParseIntegral<uint64_t>(str, out);
}
-
bool ParseDouble(const std::string& str, double *out)
{
if (!ParsePrechecks(str))
diff --git a/src/util/strencodings.h b/src/util/strencodings.h
index 26dc0a0ce3..1217572c45 100644
--- a/src/util/strencodings.h
+++ b/src/util/strencodings.h
@@ -12,8 +12,10 @@
#include <attributes.h>
#include <span.h>
+#include <charconv>
#include <cstdint>
#include <iterator>
+#include <optional>
#include <string>
#include <vector>
@@ -95,6 +97,24 @@ constexpr inline bool IsSpace(char c) noexcept {
}
/**
+ * Convert string to integral type T.
+ *
+ * @returns std::nullopt if the entire string could not be parsed, or if the
+ * parsed value is not in the range representable by the type T.
+ */
+template <typename T>
+std::optional<T> ToIntegral(const std::string& str)
+{
+ static_assert(std::is_integral<T>::value);
+ T result;
+ const auto [first_nonmatching, error_condition] = std::from_chars(str.data(), str.data() + str.size(), result);
+ if (first_nonmatching != str.data() + str.size() || error_condition != std::errc{}) {
+ return std::nullopt;
+ }
+ return {result};
+}
+
+/**
* Convert string to signed 32-bit integer with strict parse error feedback.
* @returns true if the entire string could be parsed as valid integer,
* false if not the entire string could be parsed or when overflow or underflow occurred.