xrpld
Loading...
Searching...
No Matches
LexicalCast.cpp
1#include <xrpl/beast/core/LexicalCast.h>
2
3#include <xrpl/beast/xor_shift_engine.h>
4
5#include <gtest/gtest.h>
6
7#include <array>
8#include <charconv>
9#include <cstddef>
10#include <cstdint>
11#include <limits>
12#include <string>
13#include <string_view>
14#include <type_traits>
15
16namespace beast {
17namespace {
18
19template <class T>
20[[nodiscard]] constexpr bool
21parses(std::string_view text)
22{
23 T out{};
24 return lexicalCastChecked(out, text);
25}
26
27template <class T>
28[[nodiscard]] constexpr T
29parsed(std::string_view text)
30{
31 T out{};
32 return lexicalCastChecked(out, text) ? out : T{};
33}
34
35template <class T>
36constexpr T kMax = std::numeric_limits<T>::max();
37
38template <class T>
39constexpr T kMin = std::numeric_limits<T>::min();
40
41template <class T>
42constexpr T kUnderMax = kMax<T> - 1;
43
44template <class T>
45constexpr T kOverMin = kMin<T> + 1;
46
47// Comfortably inside the range, not boundary values.
48constexpr auto kNearMax32 = kMax<uint32_t> - 5;
49constexpr auto kNearMin32 = kMin<int32_t> + 4;
50constexpr auto kUnderInt64Max = uint64_t{kMax<int64_t>} - 1;
51constexpr auto kInRangeInt16 = int16_t{-5711};
52
53// No wider integer type can hold these, so ToString cannot produce them.
54constexpr auto kAboveUint64Max = "18446744073709551616";
55constexpr auto kBelowInt64Min = "-9223372036854775809";
56
57// Out of range for every integer type we test.
58constexpr auto kTwentyNines = "99999999999999999999";
59constexpr auto kNegativeTwentyNines = "-99999999999999999999";
60
61// Arbitrary values chosen to sit well outside a type's range, not just over it.
62constexpr auto kAboveUint16Max = "75821";
63constexpr auto kBelowInt16Min = "-75821";
64constexpr auto kAboveInt32Max = "5294967295";
65constexpr auto kAboveInt16Max = "66666";
66
67constexpr auto kPositiveInt32 = int32_t{42};
68constexpr auto kNegativeInt32 = int32_t{-42};
69
70constexpr auto kPositiveInt32Text = "+42";
71constexpr auto kNegativeInt32Text = "-42";
72
73constexpr auto kNegativeOne = "-1";
74constexpr auto kNegativeZero = "-0";
75constexpr auto kBareZero = "0";
76constexpr auto kPositiveZero = "+0";
77
78// Full-width digits one and zero, not ASCII ones.
79constexpr std::string_view kFullWidthDigits = "\xef\xbc\x91\xef\xbc\x90";
80
81// The decimal text of a value, usable in a constant expression.
82template <class T>
83struct ToString
84{
85 std::array<char, 24> buffer{};
86 std::size_t length{};
87
88 constexpr explicit ToString(T value)
89 {
90 auto const result = std::to_chars(buffer.data(), buffer.data() + buffer.size(), value);
91 length = static_cast<std::size_t>(result.ptr - buffer.data());
92 }
93
94 constexpr
95 operator std::string_view() const
96 {
97 return {buffer.data(), length};
98 }
99};
100
101template <class T>
102constexpr auto kMaxText = ToString{kMax<T>};
103
104template <class T>
105constexpr auto kUnderMaxText = ToString{kUnderMax<T>};
106
107template <class T, class Wider>
108constexpr auto kOverMaxText = ToString{Wider{kMax<T>} + 1};
109
110template <class T>
111constexpr auto kMinText = ToString{kMin<T>};
112
113template <class T>
114constexpr auto kOverMinText = ToString{kOverMin<T>};
115
116template <class T, class Wider>
117constexpr auto kUnderMinText = ToString{Wider{kMin<T>} - 1};
118
119constexpr auto kOverUint32MaxText = ToString{uint64_t{kMax<uint32_t>} + 5};
120constexpr auto kNegatedOverUint32MaxText = ToString{-(int64_t{kMax<uint32_t>} + 5)};
121
122// lexicalCastThrow deduces its input type, so the text has to be an explicit
123// string_view rather than a ToString.
124template <class T, class Value>
125[[nodiscard]] constexpr T
126castThrow(Value value)
127{
128 return lexicalCastThrow<T>(std::string_view{ToString{value}});
129}
130
131template <class T>
132[[nodiscard]] bool
133roundTrips(std::string_view text)
134{
135 T out{};
136 return lexicalCastChecked(out, text) && std::to_string(out) == text;
137}
138
139template <class T>
140void
141expectRoundTrip(T value)
142{
143 SCOPED_TRACE(::testing::Message() << "value: " << value);
144
145 auto const text = lexicalCast<std::string>(value);
146 EXPECT_EQ(text, std::to_string(value));
147
148 auto decoded = static_cast<T>(~value); // ensure decoded != value
149 EXPECT_TRUE(lexicalCastChecked(decoded, text));
150 EXPECT_EQ(decoded, value);
151}
152
153} // namespace
154
155// int/unsigned/short/unsigned short are covered by the list below — they are
156// these exact types everywhere we build.
157static_assert(std::is_same_v<int, int32_t>);
159static_assert(std::is_same_v<short, int16_t>);
161
162using IntegerTypes = ::testing::Types< //
163 int16_t,
164 uint16_t,
165 int32_t,
166 uint32_t,
167 int64_t,
168 uint64_t>;
169
171{
172 template <class T>
173 static std::string
174 // NOLINTNEXTLINE(readability-identifier-naming) - required by gtest
176 {
177 return (std::is_signed_v<T> ? "int" : "uint") + std::to_string(sizeof(T) * 8) + "_t";
178 }
179};
180
181template <class T>
182class LexicalCastIntegers : public ::testing::Test
183{
184};
185
187
188TYPED_TEST(LexicalCastIntegers, round_trips_random_values)
189{
190 static constexpr auto kSampleCount = 1000uz;
191
192 xor_shift_engine r{50}; // seeded per test so a failure reproduces on its own
193
194 for (auto i = 0uz; i < kSampleCount; ++i)
195 expectRoundTrip(static_cast<TypeParam>(r()));
196}
197
198TYPED_TEST(LexicalCastIntegers, round_trips_numeric_limits)
199{
200 expectRoundTrip(std::numeric_limits<TypeParam>::min());
201 expectRoundTrip(std::numeric_limits<TypeParam>::max());
202}
203
204TEST(LexicalCast, round_trips_every_int16_value)
205{
206 for (int32_t i = kMin<int16_t>; i <= kMax<int16_t>; ++i)
207 {
208 auto const value = static_cast<int16_t>(i);
209
210 // ASSERT, or a broken cast reports all 65536 iterations.
211 auto const text = lexicalCast<std::string>(value);
212 ASSERT_EQ(text, std::to_string(value));
213 ASSERT_EQ(lexicalCast<int16_t>(text), value);
214 }
215}
216
217TEST(LexicalCast, rejects_overflow)
218{
219 static_assert(not parses<uint32_t>(kOverUint32MaxText));
220 static_assert(not parses<uint64_t>(kTwentyNines));
221 static_assert(not parses<uint16_t>(kAboveUint16Max));
222}
223
224TEST(LexicalCast, rejects_underflow)
225{
226 static_assert(not parses<uint32_t>(kNegativeOne));
227 static_assert(not parses<int32_t>(kNegatedOverUint32MaxText));
228 static_assert(not parses<int64_t>(kNegativeTwentyNines));
229 static_assert(not parses<int16_t>(kBelowInt16Min));
230}
231
232TEST(LexicalCast, accepts_up_to_the_maximum)
233{
234 static_assert(parsed<uint16_t>(kUnderMaxText<uint16_t>) == kUnderMax<uint16_t>);
235 static_assert(parsed<uint16_t>(kMaxText<uint16_t>) == kMax<uint16_t>);
236 static_assert(not parses<uint16_t>(kOverMaxText<uint16_t, uint32_t>));
237
238 static_assert(parsed<int16_t>(kUnderMaxText<int16_t>) == kUnderMax<int16_t>);
239 static_assert(parsed<int16_t>(kMaxText<int16_t>) == kMax<int16_t>);
240 static_assert(not parses<int16_t>(kOverMaxText<int16_t, int32_t>));
241
242 static_assert(parsed<uint32_t>(kUnderMaxText<uint32_t>) == kUnderMax<uint32_t>);
243 static_assert(parsed<uint32_t>(kMaxText<uint32_t>) == kMax<uint32_t>);
244 static_assert(not parses<uint32_t>(kOverMaxText<uint32_t, uint64_t>));
245
246 static_assert(parsed<int32_t>(kUnderMaxText<int32_t>) == kUnderMax<int32_t>);
247 static_assert(parsed<int32_t>(kMaxText<int32_t>) == kMax<int32_t>);
248 static_assert(not parses<int32_t>(kOverMaxText<int32_t, int64_t>));
249
250 static_assert(parsed<int64_t>(kUnderMaxText<int64_t>) == kUnderMax<int64_t>);
251 static_assert(parsed<int64_t>(kMaxText<int64_t>) == kMax<int64_t>);
252 static_assert(not parses<int64_t>(kOverMaxText<int64_t, uint64_t>));
253
254 static_assert(parsed<uint64_t>(kUnderMaxText<uint64_t>) == kUnderMax<uint64_t>);
255 static_assert(parsed<uint64_t>(kMaxText<uint64_t>) == kMax<uint64_t>);
256 static_assert(not parses<uint64_t>(kAboveUint64Max));
257}
258
259TEST(LexicalCast, accepts_down_to_the_minimum)
260{
261 static_assert(parsed<int16_t>(kOverMinText<int16_t>) == kOverMin<int16_t>);
262 static_assert(parsed<int16_t>(kMinText<int16_t>) == kMin<int16_t>);
263 static_assert(not parses<int16_t>(kUnderMinText<int16_t, int32_t>));
264
265 static_assert(parsed<int32_t>(kOverMinText<int32_t>) == kOverMin<int32_t>);
266 static_assert(parsed<int32_t>(kMinText<int32_t>) == kMin<int32_t>);
267 static_assert(not parses<int32_t>(kUnderMinText<int32_t, int64_t>));
268
269 static_assert(parsed<int64_t>(kOverMinText<int64_t>) == kOverMin<int64_t>);
270 static_assert(parsed<int64_t>(kMinText<int64_t>) == kMin<int64_t>);
271 static_assert(not parses<int64_t>(kBelowInt64Min));
272}
273
274TEST(LexicalCast, limits_round_trip_through_to_string)
275{
276 EXPECT_TRUE(roundTrips<uint64_t>(kMaxText<uint64_t>));
277 EXPECT_TRUE(roundTrips<int64_t>(kMaxText<int64_t>));
278 EXPECT_TRUE(roundTrips<int64_t>(kMinText<int64_t>));
279 EXPECT_TRUE(roundTrips<uint32_t>(kMaxText<uint32_t>));
280 EXPECT_TRUE(roundTrips<int32_t>(kMinText<int32_t>));
281 EXPECT_TRUE(roundTrips<uint16_t>(kMaxText<uint16_t>));
282 EXPECT_TRUE(roundTrips<int16_t>(kMinText<int16_t>));
283}
284
285TEST(LexicalCast, accepts_signed_zero_in_every_form)
286{
287 static_assert(parsed<int32_t>(kNegativeZero) == 0);
288 static_assert(parsed<int32_t>(kBareZero) == 0);
289 static_assert(parsed<int32_t>(kPositiveZero) == 0);
290}
291
292TEST(LexicalCast, rejects_negative_zero_when_unsigned)
293{
294 static_assert(not parses<uint32_t>(kNegativeZero));
295 static_assert(parsed<uint32_t>(kBareZero) == 0);
296 static_assert(parsed<uint32_t>(kPositiveZero) == 0);
297}
298
299TEST(LexicalCast, accepts_char_pointer_and_std_string_input)
300{
301 int32_t fromLiteral = 0;
302 EXPECT_TRUE(lexicalCastChecked(fromLiteral, kPositiveInt32Text));
303 EXPECT_EQ(fromLiteral, kPositiveInt32);
304
305 int32_t fromString = 0;
306 EXPECT_TRUE(lexicalCastChecked(fromString, std::string{kNegativeInt32Text}));
307 EXPECT_EQ(fromString, kNegativeInt32);
308}
309
310TEST(LexicalCast, throwing_cast_returns_in_range_values)
311{
312 static_assert(castThrow<uint64_t>(kUnderInt64Max) == kUnderInt64Max);
313 static_assert(castThrow<uint32_t>(kNearMax32) == kNearMax32);
314 static_assert(castThrow<int32_t>(kNearMin32) == kNearMin32);
315 static_assert(castThrow<int16_t>(kInRangeInt16) == kInRangeInt16);
316}
317
318TEST(LexicalCast, throwing_cast_throws_on_out_of_range)
319{
320 EXPECT_THROW(lexicalCastThrow<uint64_t>(kTwentyNines), BadLexicalCast);
321
322 // kNearMax32 with digits appended, so each is further past uint32_t's range.
323 for (auto const scale : {10, 100, 1000})
324 {
325 auto const tooBig = ToString{uint64_t{kNearMax32} * scale};
327 }
328
329 EXPECT_THROW(lexicalCastThrow<int32_t>(kAboveInt32Max), BadLexicalCast);
330 EXPECT_THROW(lexicalCastThrow<int16_t>(kAboveInt16Max), BadLexicalCast);
331}
332
333// Full-width digits, not ASCII ones.
334TEST(LexicalCast, throwing_cast_throws_on_utf8_digits)
335{
336 EXPECT_THROW(lexicalCastThrow<int>(kFullWidthDigits), BadLexicalCast);
337}
338
339} // namespace beast
T is_same_v
T is_signed_v
T max(T... args)
T min(T... args)
detail::XorShiftEngine<> xor_shift_engine
XOR-shift Generator.
constexpr Out lexicalCastThrow(In in)
Convert from one type to another, throw on error.
TEST(LexicalCast, round_trips_every_int16_value)
constexpr bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
TYPED_TEST_SUITE(LexicalCastIntegers, IntegerTypes, IntegerTypeNames)
constexpr Out lexicalCast(In in, Out defaultValue=Out())
Convert from one type to another.
::testing::Types< int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t > IntegerTypes
TYPED_TEST(LexicalCastIntegers, round_trips_random_values)
Thrown when a conversion is not possible with LexicalCast.
static std::string GetName(int)
T to_chars(T... args)
T to_string(T... args)