Skip to content

Commit a62b5da

Browse files
Merge pull request #4192 from hannes-steffenhagen-diffblue/string_to_optional
Add string2optional conversion functions
2 parents 937d557 + e9ccc27 commit a62b5da

File tree

5 files changed

+215
-45
lines changed

5 files changed

+215
-45
lines changed

src/util/narrow.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,17 @@ output_type narrow(input_type input)
3838
return output;
3939
}
4040

41+
/// Run-time checked narrow cast. Throws a std::out_of_range error
42+
/// if the input cannot be converted to the output_type without data lass
43+
template <typename output_type, typename input_type>
44+
output_type narrow_or_throw_out_of_range(input_type input)
45+
{
46+
auto const result = narrow_cast<input_type>(input);
47+
if(result != input)
48+
{
49+
throw std::out_of_range{"narrowing gave a different value than expected"};
50+
}
51+
return result;
52+
}
53+
4154
#endif // CPROVER_UTIL_NARROW_H

src/util/string2int.cpp

Lines changed: 28 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,82 +10,66 @@ Author: Michael Tautschnig, [email protected]
1010

1111
#include <cerrno>
1212
#include <cstdlib>
13+
#include <cstring>
1314
#include <limits>
15+
#include <stdexcept>
1416

1517
#include "invariant.h"
1618

17-
template <typename T>
18-
inline T str2number(const char *str, int base, bool safe)
19-
{
20-
int errno_bak=errno;
21-
errno=0;
22-
char *endptr;
23-
// _strtoi64 is available in Visual Studio, but not yet in MINGW
24-
#ifdef _MSC_VER
25-
const __int64 val=_strtoi64(str, &endptr, base);
26-
#else
27-
const long long val=strtoll(str, &endptr, base);
28-
#endif
29-
30-
if(safe)
31-
{
32-
CHECK_RETURN(0==errno);
33-
errno=errno_bak;
34-
CHECK_RETURN(endptr!=str);
35-
if(std::numeric_limits<T>::min()==0)
36-
{
37-
// unsigned
38-
CHECK_RETURN(val>=0);
39-
CHECK_RETURN(
40-
(unsigned long long)(T)val<=
41-
(unsigned long long)std::numeric_limits<T>::max());
42-
}
43-
else
44-
{
45-
// signed
46-
CHECK_RETURN(val<=(long long)std::numeric_limits<T>::max());
47-
CHECK_RETURN(val>=(long long)std::numeric_limits<T>::min());
48-
}
49-
}
50-
51-
return (T)val;
52-
}
53-
5419
unsigned safe_string2unsigned(const std::string &str, int base)
5520
{
56-
return str2number<unsigned>(str.c_str(), base, true);
21+
auto converted = string2optional<unsigned>(str, base);
22+
CHECK_RETURN(converted != nullopt);
23+
return *converted;
5724
}
5825

5926
std::size_t safe_string2size_t(const std::string &str, int base)
6027
{
61-
return str2number<std::size_t>(str.c_str(), base, true);
28+
auto converted = string2optional<std::size_t>(str, base);
29+
CHECK_RETURN(converted != nullopt);
30+
return *converted;
6231
}
6332

6433
int unsafe_string2int(const std::string &str, int base)
6534
{
66-
return str2number<int>(str.c_str(), base, false);
35+
return narrow_cast<int>(std::strtoll(str.c_str(), nullptr, base));
6736
}
6837

6938
unsigned unsafe_string2unsigned(const std::string &str, int base)
7039
{
71-
return str2number<unsigned>(str.c_str(), base, false);
40+
return narrow_cast<unsigned>(std::strtoul(str.c_str(), nullptr, base));
7241
}
7342

7443
std::size_t unsafe_string2size_t(const std::string &str, int base)
7544
{
76-
return str2number<std::size_t>(str.c_str(), base, false);
45+
return narrow_cast<std::size_t>(std::strtoull(str.c_str(), nullptr, base));
7746
}
7847

7948
signed long long int unsafe_string2signedlonglong(
8049
const std::string &str,
8150
int base)
8251
{
83-
return str2number<signed long long int>(str.c_str(), base, false);
52+
return std::strtoll(str.c_str(), nullptr, false);
8453
}
8554

8655
unsigned long long int unsafe_string2unsignedlonglong(
8756
const std::string &str,
8857
int base)
8958
{
90-
return str2number<unsigned long long int>(str.c_str(), base, false);
59+
return *string2optional<unsigned long long>(str, base);
60+
}
61+
62+
optionalt<int> string2optional_int(const std::string &str, int base)
63+
{
64+
return string2optional<int>(str, base);
65+
}
66+
67+
optionalt<unsigned> string2optional_unsigned(const std::string &str, int base)
68+
{
69+
return string2optional<unsigned>(str, base);
70+
}
71+
72+
optionalt<std::size_t> string2optional_size_t(const std::string &str, int base)
73+
{
74+
return string2optional<std::size_t>(str, base);
9175
}

src/util/string2int.h

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ Author: Michael Tautschnig, [email protected]
1010
#ifndef CPROVER_UTIL_STRING2INT_H
1111
#define CPROVER_UTIL_STRING2INT_H
1212

13+
#include "narrow.h"
14+
#include "optional.h"
1315
#include <string>
16+
#include <type_traits>
1417

1518
// These check that the string is indeed a valid number,
1619
// and fail an assertion otherwise.
@@ -30,4 +33,85 @@ long long int unsafe_string2signedlonglong(const std::string &str, int base=10);
3033
long long unsigned int unsafe_string2unsignedlonglong(
3134
const std::string &str, int base=10);
3235

36+
// if we had a `resultt` á la Boost.Outcome (https://ned14.github.io/outcome/)
37+
// we could also return the reason why the conversion failed
38+
39+
/// Convert string to integer as per stoi, but return nullopt when
40+
/// stoi would throw
41+
optionalt<int> string2optional_int(const std::string &, int base = 10);
42+
43+
/// Convert string to unsigned similar to the stoul or stoull functions,
44+
/// return nullopt when the conversion fails.
45+
/// Note: Unlike stoul or stoull negative inputs are disallowed
46+
optionalt<unsigned>
47+
string2optional_unsigned(const std::string &, int base = 10);
48+
49+
/// Convert string to size_t similar to the stoul or stoull functions,
50+
/// return nullopt when the conversion fails.
51+
/// Note: Unlike stoul or stoull negative inputs are disallowed
52+
optionalt<std::size_t>
53+
string2optional_size_t(const std::string &, int base = 10);
54+
55+
/// convert string to signed long long if T is signed
56+
template <typename T>
57+
auto string2optional_base(const std::string &str, int base) ->
58+
typename std::enable_if<std::is_signed<T>::value, long long>::type
59+
{
60+
static_assert(
61+
sizeof(T) <= sizeof(long long),
62+
"this works under the assumption that long long is the largest type we try "
63+
"to convert");
64+
return std::stoll(str, nullptr, base);
65+
}
66+
67+
/// convert string to unsigned long long if T is unsigned
68+
template <typename T>
69+
auto string2optional_base(const std::string &str, int base) ->
70+
typename std::enable_if<std::is_unsigned<T>::value, unsigned long long>::type
71+
{
72+
static_assert(
73+
sizeof(T) <= sizeof(unsigned long long),
74+
"this works under the assumption that long long is the largest type we try "
75+
"to convert");
76+
if(str.find('-') != std::string::npos)
77+
{
78+
throw std::out_of_range{
79+
"unsigned conversion behaves a bit strangely with negative values, "
80+
"therefore we disable it"};
81+
}
82+
return std::stoull(str, nullptr, base);
83+
}
84+
85+
/// attempt a given conversion, return nullopt if the conversion fails
86+
/// with out_of_range or invalid_argument
87+
template <typename do_conversiont>
88+
auto wrap_string_conversion(do_conversiont do_conversion)
89+
-> optionalt<decltype(do_conversion())>
90+
{
91+
try
92+
{
93+
return do_conversion();
94+
}
95+
catch(const std::invalid_argument &)
96+
{
97+
return nullopt;
98+
}
99+
catch(const std::out_of_range &)
100+
{
101+
return nullopt;
102+
}
103+
}
104+
105+
/// convert a string to an integer, given the base of the representation
106+
/// works with signed and unsigned integer types smaller than
107+
/// (unsigned) long long
108+
/// does not accept negative inputs when the result type is unsigned
109+
template <typename T>
110+
optionalt<T> string2optional(const std::string &str, int base)
111+
{
112+
return wrap_string_conversion([&]() {
113+
return narrow_or_throw_out_of_range<T>(string2optional_base<T>(str, base));
114+
});
115+
}
116+
33117
#endif // CPROVER_UTIL_STRING2INT_H

unit/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ SRC += analyses/ai/ai.cpp \
6969
util/simplify_expr.cpp \
7070
util/small_map.cpp \
7171
util/small_shared_two_way_ptr.cpp \
72-
util/std_expr.cpp \
72+
util/std_expr.cpp \
73+
util/string2int.cpp \
7374
util/string_utils/join_string.cpp \
7475
util/string_utils/split_string.cpp \
7576
util/string_utils/strip_string.cpp \

unit/util/string2int.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*******************************************************************\
2+
3+
Module: Unit tests for string2int.h
4+
5+
Author: Diffblue Ltd.
6+
7+
\*******************************************************************/
8+
9+
#include <testing-utils/use_catch.h>
10+
#include <util/string2int.h>
11+
12+
TEST_CASE(
13+
"converting optionally to a valid integer should succeed",
14+
"[core][util][string2int]")
15+
{
16+
REQUIRE(string2optional_int("13") == 13);
17+
REQUIRE(string2optional_int("-5") == -5);
18+
REQUIRE(string2optional_int("c0fefe", 16) == 0xc0fefe);
19+
}
20+
21+
TEST_CASE(
22+
"optionally converting invalid string to integer should return nullopt",
23+
"[core][util][string2int]")
24+
{
25+
REQUIRE(string2optional_int("thirteen") == nullopt);
26+
REQUIRE(string2optional_int("c0fefe") == nullopt);
27+
}
28+
29+
TEST_CASE(
30+
"optionally converting string out of range to integer should return nullopt",
31+
"[core][util][string2int]")
32+
{
33+
REQUIRE(
34+
string2optional_int("0xfffffffffffffffffffffffffffffffffffffffffff", 16) ==
35+
nullopt);
36+
}
37+
38+
TEST_CASE(
39+
"converting optionally to a valid unsigned should succeed",
40+
"[core][util][string2int]")
41+
{
42+
REQUIRE(string2optional_unsigned("13") == 13u);
43+
REQUIRE(string2optional_unsigned("c0fefe", 16) == 0xc0fefeu);
44+
}
45+
46+
TEST_CASE(
47+
"optionally converting invalid string to unsigned should return nullopt",
48+
"[core][util][string2int]")
49+
{
50+
REQUIRE(string2optional_unsigned("thirteen") == nullopt);
51+
REQUIRE(string2optional_unsigned("c0fefe") == nullopt);
52+
}
53+
54+
TEST_CASE(
55+
"optionally converting string out of range to unsigned should return nullopt",
56+
"[core][util][string2int]")
57+
{
58+
REQUIRE(
59+
string2optional_unsigned(
60+
"0xfffffffffffffffffffffffffffffffffffffffffff", 16) == nullopt);
61+
REQUIRE(string2optional_unsigned("-5") == nullopt);
62+
}
63+
64+
TEST_CASE(
65+
"converting optionally to a valid size_t should succeed",
66+
"[core][util][string2int]")
67+
{
68+
REQUIRE(string2optional_size_t("13") == std::size_t{13});
69+
REQUIRE(string2optional_size_t("c0fefe", 16) == std::size_t{0xc0fefe});
70+
}
71+
72+
TEST_CASE(
73+
"optionally converting invalid string to size_t should return nullopt",
74+
"[core][util][string2int]")
75+
{
76+
REQUIRE(string2optional_size_t("thirteen") == nullopt);
77+
REQUIRE(string2optional_size_t("c0fefe") == nullopt);
78+
}
79+
80+
TEST_CASE(
81+
"optionally converting string out of range to size_t should return nullopt",
82+
"[core][util][string2int]")
83+
{
84+
REQUIRE(
85+
string2optional_size_t(
86+
"0xfffffffffffffffffffffffffffffffffffffffffff", 16) == nullopt);
87+
REQUIRE(string2optional_size_t("-5") == nullopt);
88+
}

0 commit comments

Comments
 (0)