Skip to content

Commit e9ccc27

Browse files
Add string2optional conversion functions
These are intended as helpers for when you want to convert a string to and integer, but don't want to assert the result (e.g. when dealing with user input there's usually something better you can do than outright crashing if the conversion fails), but also don't want to deal with exceptions (which involve more code to write and read and it's easy to handle the wrong set of exceptions, whether it is too many or too few).
1 parent e654aa7 commit e9ccc27

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)