Skip to content

Commit 82ba0cc

Browse files
authored
Merge pull request #9007 from diffblue/feature/from-chars-string2int
Use `std::from_chars` instead of `stoll`/`stoull` in string2int.h
2 parents 166a7d4 + 9e2a3a7 commit 82ba0cc

3 files changed

Lines changed: 110 additions & 55 deletions

File tree

‎src/memory-analyzer/analyze_symbol.cpp‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,20 @@ gdb_value_extractort::memory_scopet::memory_scopet(
3636
const memory_addresst &begin,
3737
const mp_integer &byte_size,
3838
const irep_idt &name)
39-
: begin_int(safe_string2size_t(begin.address_string, 0)),
39+
: // the address is given in hex, starting with 0x....
40+
begin_int(safe_string2size_t(begin.address_string.substr(2), 16)),
4041
byte_size(byte_size),
4142
name(name)
4243
{
44+
PRECONDITION(begin.address_string.substr(0, 2) == "0x");
4345
}
4446

4547
size_t gdb_value_extractort::memory_scopet::address2size_t(
4648
const memory_addresst &point) const
4749
{
48-
return safe_string2size_t(point.address_string, 0);
50+
// the address is given in hex, starting with 0x....
51+
PRECONDITION(point.address_string.substr(0, 2) == "0x");
52+
return safe_string2size_t(point.address_string.substr(2), 16);
4953
}
5054

5155
mp_integer gdb_value_extractort::memory_scopet::distance(

‎src/util/string2int.h‎

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Author: Michael Tautschnig, michael.tautschnig@cs.ox.ac.uk
1212

1313
#include "narrow.h"
1414

15+
#include <charconv>
1516
#include <optional>
1617
#include <string>
1718
#include <type_traits>
@@ -53,66 +54,38 @@ string2optional_unsigned(const std::string &, int base = 10);
5354
std::optional<std::size_t>
5455
string2optional_size_t(const std::string &, int base = 10);
5556

56-
/// convert string to signed long long if T is signed
57+
/// Convert a string to an integer, given the base of the representation,
58+
/// works with signed and unsigned integer types,
59+
/// rejects negative inputs when the result type is unsigned,
60+
/// rejects the empty string,
61+
/// rejects leading spaces,
62+
/// rejects any trailing non-numerical suffix.
63+
/// A prefix such as 0, 0x, 0X to change base is _not_ supported.
5764
template <typename T>
58-
auto string2optional_base(const std::string &str, int base) ->
59-
typename std::enable_if<std::is_signed<T>::value, long long>::type
65+
std::optional<T> string2optional(const std::string &str, int base = 10)
6066
{
61-
static_assert(
62-
sizeof(T) <= sizeof(long long),
63-
"this works under the assumption that long long is the largest type we try "
64-
"to convert");
65-
return std::stoll(str, nullptr, base);
66-
}
67+
PRECONDITION(base == 2 || base == 8 || base == 10 || base == 16);
6768

68-
/// convert string to unsigned long long if T is unsigned
69-
template <typename T>
70-
auto string2optional_base(const std::string &str, int base) ->
71-
typename std::enable_if<std::is_unsigned<T>::value, unsigned long long>::type
72-
{
7369
static_assert(
74-
sizeof(T) <= sizeof(unsigned long long),
75-
"this works under the assumption that long long is the largest type we try "
76-
"to convert");
77-
if(str.find('-') != std::string::npos)
78-
{
79-
throw std::out_of_range{
80-
"unsigned conversion behaves a bit strangely with negative values, "
81-
"therefore we disable it"};
82-
}
83-
return std::stoull(str, nullptr, base);
84-
}
70+
std::is_integral<T>::value, "string2optional requires an integral type");
8571

86-
/// attempt a given conversion, return nullopt if the conversion fails
87-
/// with out_of_range or invalid_argument
88-
template <typename do_conversiont>
89-
auto wrap_string_conversion(do_conversiont do_conversion)
90-
-> std::optional<decltype(do_conversion())>
91-
{
92-
try
93-
{
94-
return do_conversion();
95-
}
96-
catch(const std::invalid_argument &)
97-
{
72+
if(str.empty())
73+
return std::nullopt;
74+
75+
// reject negative inputs for unsigned types
76+
if(std::is_unsigned<T>::value && str.front() == '-')
9877
return std::nullopt;
99-
}
100-
catch(const std::out_of_range &)
101-
{
78+
79+
const char *first = str.data();
80+
const char *last = str.data() + str.size();
81+
82+
T value{};
83+
auto [ptr, ec] = std::from_chars(first, last, value, base);
84+
85+
if(ec != std::errc{} || ptr != last)
10286
return std::nullopt;
103-
}
104-
}
10587

106-
/// convert a string to an integer, given the base of the representation
107-
/// works with signed and unsigned integer types smaller than
108-
/// (unsigned) long long
109-
/// does not accept negative inputs when the result type is unsigned
110-
template <typename T>
111-
std::optional<T> string2optional(const std::string &str, int base = 10)
112-
{
113-
return wrap_string_conversion([&]() {
114-
return narrow_or_throw_out_of_range<T>(string2optional_base<T>(str, base));
115-
});
88+
return value;
11689
}
11790

11891
#endif // CPROVER_UTIL_STRING2INT_H

‎unit/util/string2int.cpp‎

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ Author: Diffblue Ltd.
66
77
\*******************************************************************/
88

9-
#include <testing-utils/use_catch.h>
109
#include <util/string2int.h>
1110

11+
#include <testing-utils/use_catch.h>
12+
13+
#include <cstdint>
14+
1215
TEST_CASE(
1316
"converting optionally to a valid integer should succeed",
1417
"[core][util][string2int]")
@@ -86,3 +89,78 @@ TEST_CASE(
8689
.has_value());
8790
REQUIRE(!string2optional_size_t("-5").has_value());
8891
}
92+
93+
// Tests for string2optional<T> template directly
94+
95+
TEST_CASE(
96+
"string2optional with various integral types",
97+
"[core][util][string2int]")
98+
{
99+
REQUIRE(
100+
string2optional<long long>("9223372036854775807") == 9223372036854775807LL);
101+
REQUIRE(
102+
string2optional<long long>("-9223372036854775808") ==
103+
(-9223372036854775807LL - 1));
104+
REQUIRE(
105+
string2optional<unsigned long long>("18446744073709551615") ==
106+
18446744073709551615ULL);
107+
REQUIRE(string2optional<std::int16_t>("32767") == 32767);
108+
REQUIRE(string2optional<std::int16_t>("-32768") == -32768);
109+
}
110+
111+
TEST_CASE(
112+
"string2optional overflow returns nullopt",
113+
"[core][util][string2int]")
114+
{
115+
// signed overflow (use types with guaranteed width)
116+
REQUIRE(!string2optional<std::int32_t>("2147483648").has_value());
117+
REQUIRE(!string2optional<std::int32_t>("-2147483649").has_value());
118+
REQUIRE(!string2optional<std::int16_t>("32768").has_value());
119+
// unsigned overflow
120+
REQUIRE(!string2optional<std::uint32_t>("4294967296").has_value());
121+
REQUIRE(
122+
!string2optional<unsigned long long>("18446744073709551616").has_value());
123+
}
124+
125+
TEST_CASE(
126+
"string2optional rejects negative input for unsigned types",
127+
"[core][util][string2int]")
128+
{
129+
REQUIRE(!string2optional<unsigned>("-1").has_value());
130+
REQUIRE(!string2optional<unsigned long long>("-42").has_value());
131+
REQUIRE(!string2optional<std::size_t>("-100").has_value());
132+
}
133+
134+
TEST_CASE("string2optional rejects partial parses", "[core][util][string2int]")
135+
{
136+
// trailing non-digit characters must cause rejection
137+
REQUIRE(!string2optional<int>("123abc").has_value());
138+
REQUIRE(!string2optional<int>("42 ").has_value());
139+
REQUIRE(!string2optional<unsigned>("99x").has_value());
140+
}
141+
142+
TEST_CASE(
143+
"string2optional rejects leading whitespace",
144+
"[core][util][string2int]")
145+
{
146+
// from_chars does not skip whitespace (unlike stoll/stoull)
147+
REQUIRE(!string2optional<int>(" 42").has_value());
148+
REQUIRE(!string2optional<int>("\t7").has_value());
149+
REQUIRE(!string2optional<unsigned>(" 1").has_value());
150+
}
151+
152+
TEST_CASE("string2optional rejects empty string", "[core][util][string2int]")
153+
{
154+
REQUIRE(!string2optional<int>("").has_value());
155+
REQUIRE(!string2optional<unsigned>("").has_value());
156+
REQUIRE(!string2optional<long long>("").has_value());
157+
}
158+
159+
TEST_CASE("string2optional with different bases", "[core][util][string2int]")
160+
{
161+
REQUIRE(string2optional<int>("FF", 16) == 255);
162+
REQUIRE(string2optional<int>("ff", 16) == 255);
163+
REQUIRE(string2optional<int>("77", 8) == 63);
164+
REQUIRE(string2optional<int>("101", 2) == 5);
165+
REQUIRE(string2optional<unsigned long long>("DEADBEEF", 16) == 0xDEADBEEFULL);
166+
}

0 commit comments

Comments
 (0)