From 10d1b76b90b23463c22bf8d06a3f93781e48d2e2 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Wed, 10 Sep 2014 15:30:11 -0400 Subject: [PATCH] Add string_number_conversion and its test. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This includes the StringToNumber() function, both int and unsigned int variants. Similar functionality is available in base, but it is unsuitable for applications where a number’s base may be determined based on an "0x" or "0X" prefix (hexadecimal) or an "0" prefix (octal). TEST=util_test StringNumberConversion.* R=rsesek@chromium.org Review URL: https://codereview.chromium.org/557033002 --- util/stdlib/string_number_conversion.cc | 157 +++++++++++++ util/stdlib/string_number_conversion.h | 63 ++++++ util/stdlib/string_number_conversion_test.cc | 218 +++++++++++++++++++ util/util.gyp | 3 + 4 files changed, 441 insertions(+) create mode 100644 util/stdlib/string_number_conversion.cc create mode 100644 util/stdlib/string_number_conversion.h create mode 100644 util/stdlib/string_number_conversion_test.cc diff --git a/util/stdlib/string_number_conversion.cc b/util/stdlib/string_number_conversion.cc new file mode 100644 index 00000000..76c80fdd --- /dev/null +++ b/util/stdlib/string_number_conversion.cc @@ -0,0 +1,157 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/stdlib/string_number_conversion.h" + +#include +#include +#include +#include + +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "util/stdlib/cxx.h" + +// CONSTEXPR_COMPILE_ASSERT will be a normal COMPILE_ASSERT if the C++ library +// is the C++11 library. If using an older C++ library when compiling C++11 +// code, the std::numeric_limits<>::min() and max() functions will not be +// marked as constexpr, and thus won’t be usable with C++11’s static_assert(). +// In that case, a run-time CHECK() will have to do. +#if CXX_LIBRARY_VERSION >= 2011 +#define CONSTEXPR_COMPILE_ASSERT(condition, message) \ + COMPILE_ASSERT(condition, message) +#else +#define CONSTEXPR_COMPILE_ASSERT(condition, message) CHECK(condition) +#endif + +namespace { + +template +struct StringToIntegerTraits { + typedef TIntType IntType; + typedef TLongType LongType; + static void TypeCheck() { + COMPILE_ASSERT(std::numeric_limits::is_integer && + std::numeric_limits::is_integer, + IntType_and_LongType_must_be_integer); + COMPILE_ASSERT(std::numeric_limits::is_signed == + std::numeric_limits::is_signed, + IntType_and_LongType_signedness_must_agree); + CONSTEXPR_COMPILE_ASSERT(std::numeric_limits::min() >= + std::numeric_limits::min() && + std::numeric_limits::min() < + std::numeric_limits::max(), + IntType_min_must_be_in_LongType_range); + CONSTEXPR_COMPILE_ASSERT(std::numeric_limits::max() > + std::numeric_limits::min() && + std::numeric_limits::max() <= + std::numeric_limits::max(), + IntType_max_must_be_in_LongType_range); + } +}; + +template +struct StringToSignedIntegerTraits + : public StringToIntegerTraits { + static void TypeCheck() { + COMPILE_ASSERT(std::numeric_limits::is_signed, + StringToSignedTraits_IntType_must_be_signed); + return super::TypeCheck(); + } + static bool IsNegativeOverflow(TLongType value) { + return value < std::numeric_limits::min(); + } + + private: + typedef StringToIntegerTraits super; +}; + +template +struct StringToUnsignedIntegerTraits + : public StringToIntegerTraits { + static void TypeCheck() { + COMPILE_ASSERT(!std::numeric_limits::is_signed, + StringToUnsignedTraits_IntType_must_be_unsigned); + return super::TypeCheck(); + } + static bool IsNegativeOverflow(TLongType value) { return false; } + + private: + typedef StringToIntegerTraits super; +}; + +struct StringToIntTraits : public StringToSignedIntegerTraits { + static LongType Convert(const char* str, char** end, int base) { + return strtol(str, end, base); + } +}; + +struct StringToUnsignedIntTraits + : public StringToUnsignedIntegerTraits { + static LongType Convert(const char* str, char** end, int base) { + if (str[0] == '-') { + return 0; + } + return strtoul(str, end, base); + } +}; + +template +bool StringToIntegerInternal(const base::StringPiece& string, + typename Traits::IntType* number) { + typedef typename Traits::IntType IntType; + typedef typename Traits::LongType LongType; + + Traits::TypeCheck(); + + if (string.empty() || isspace(string[0])) { + return false; + } + + if (string[string.length()] != '\0') { + // The implementations use the C standard library’s conversion routines, + // which rely on the strings having a trailing NUL character. std::string + // will NUL-terminate. + std::string terminated_string(string.data(), string.length()); + return StringToIntegerInternal(terminated_string, number); + } + + errno = 0; + char* end; + LongType result = Traits::Convert(string.data(), &end, 0); + if (Traits::IsNegativeOverflow(result) || + result > std::numeric_limits::max() || + errno == ERANGE || + end != string.data() + string.length()) { + return false; + } + *number = result; + return true; +} + +} // namespace + +namespace crashpad { + +bool StringToNumber(const base::StringPiece& string, int* number) { + return StringToIntegerInternal(string, number); +} + +bool StringToNumber(const base::StringPiece& string, unsigned int* number) { + return StringToIntegerInternal(string, number); +} + +} // namespace crashpad diff --git a/util/stdlib/string_number_conversion.h b/util/stdlib/string_number_conversion.h new file mode 100644 index 00000000..dbc4611e --- /dev/null +++ b/util/stdlib/string_number_conversion.h @@ -0,0 +1,63 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_STDLIB_STRING_NUMBER_CONVERSION_H_ +#define CRASHPAD_UTIL_STDLIB_STRING_NUMBER_CONVERSION_H_ + +#include "base/strings/string_piece.h" + +namespace crashpad { + +// Convert between strings and numbers. +// +// These functions will only set *number if a perfect conversion can be +// performed. A perfect conversion contains no leading or trailing characters +// (including whitespace) other than the number to convert, and does not +// overflow the targeted data type. If a perfect conversion is possible, *number +// is set and these functions return true. Otherwise, they return false. +// +// The interface in base/strings/string_number_conversions.h doesn’t allow +// arbitrary bases based on whether the string begins with prefixes such as "0x" +// as strtol does with base = 0. The functions here are implemented on the +// strtol family with base = 0, and thus do accept such input. + +//! \{ +//! \brief Convert a string to a number. +//! +//! A conversion will only be performed if it can be done perfectly: if \a +//! string contains no leading or trailing characters (including whitespace) +//! other than the number to convert, and does not overflow the targeted data +//! type. +//! +//! \param[in] string The string to convert to a number. As in `strtol()` with a +//! `base` parameter of `0`, the string is treated as decimal unless it +//! begins with a `"0x"` or `"0X"` prefix, in which case it is treated as +//! hexadecimal, or a `"0"` prefix, in which case it is treated as octal. +//! \param[out] number The converted number. This will only be set if a perfect +//! conversion can be performed. +//! +//! \return `true` if a perfect conversion could be performed, with \a number +//! set appropriately. `false` if a perfect conversion was not possible. +//! +//! \note The interface in `base/strings/string_number_conversions.h` doesn’t +//! allow arbitrary bases based on whether the string begins with a prefix +//! indicating its base. The functions here are provided for situations +//! where such prefix recognition is desirable. +bool StringToNumber(const base::StringPiece& string, int* number); +bool StringToNumber(const base::StringPiece& string, unsigned int* number); +//! \} + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_STDLIB_STRING_NUMBER_CONVERSION_H_ diff --git a/util/stdlib/string_number_conversion_test.cc b/util/stdlib/string_number_conversion_test.cc new file mode 100644 index 00000000..ef1376c7 --- /dev/null +++ b/util/stdlib/string_number_conversion_test.cc @@ -0,0 +1,218 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/stdlib/string_number_conversion.h" + +#include "base/basictypes.h" +#include "gtest/gtest.h" + +#include + +namespace { + +using namespace crashpad; + +TEST(StringNumberConversion, StringToInt) { + const struct { + const char* string; + bool valid; + int value; + } kTestData[] = { + {"", false, 0}, + {"0", true, 0}, + {"1", true, 1}, + {"2147483647", true, std::numeric_limits::max()}, + {"2147483648", false, 0}, + {"4294967295", false, 0}, + {"4294967296", false, 0}, + {"-1", true, -1}, + {"-2147483648", true, std::numeric_limits::min()}, + {"-2147483649", false, 0}, + {"00", true, 0}, + {"01", true, 1}, + {"-01", true, -1}, + {"+2", true, 2}, + {"0x10", true, 16}, + {"-0x10", true, -16}, + {"+0x20", true, 32}, + {"0xf", true, 15}, + {"0xg", false, 0}, + {"0x7fffffff", true, std::numeric_limits::max()}, + {"0x7FfFfFfF", true, std::numeric_limits::max()}, + {"0x80000000", false, 0}, + {"0xFFFFFFFF", false, 0}, + {"-0x7fffffff", true, -2147483647}, + {"-0x80000000", true, -2147483648}, + {"-0x80000001", false, 0}, + {"-0xffffffff", false, 0}, + {"0x100000000", false, 0}, + {"0xabcdef", true, 11259375}, + {"010", true, 8}, + {"-010", true, -8}, + {"+020", true, 16}, + {"07", true, 7}, + {"08", false, 0}, + {" 0", false, 0}, + {"0 ", false, 0}, + {" 0 ", false, 0}, + {" 1", false, 0}, + {"1 ", false, 0}, + {" 1 ", false, 0}, + {"a2", false, 0}, + {"2a", false, 0}, + {"2a2", false, 0}, + {".0", false, 0}, + {".1", false, 0}, + {"-.2", false, 0}, + {"+.3", false, 0}, + {"1.23", false, 0}, + {"-273.15", false, 0}, + {"+98.6", false, 0}, + {"1e1", false, 0}, + {"1E1", false, 0}, + {"0x123p4", false, 0}, + {"infinity", false, 0}, + {"NaN", false, 0}, + {"-9223372036854775810", false, 0}, + {"-9223372036854775809", false, 0}, + {"9223372036854775808", false, 0}, + {"9223372036854775809", false, 0}, + {"18446744073709551615", false, 0}, + {"18446744073709551616", false, 0}, + }; + + for (size_t index = 0; index < arraysize(kTestData); ++index) { + int value; + bool valid = StringToNumber(kTestData[index].string, &value); + if (kTestData[index].valid) { + EXPECT_TRUE(valid) << "index " << index << ", string " + << kTestData[index].string; + if (valid) { + EXPECT_EQ(kTestData[index].value, value) + << "index " << index << ", string " << kTestData[index].string; + } + } else { + EXPECT_FALSE(valid) << "index " << index << ", string " + << kTestData[index].string << ", value " << value; + } + } + + // Ensure that embedded NUL characters are treated as bad input. + const char input[] = "6\0006"; + base::StringPiece input_string(input, arraysize(input) - 1); + int output; + EXPECT_FALSE(StringToNumber(input_string, &output)); + + // Ensure that a NUL is not required at the end of the string. + EXPECT_TRUE(StringToNumber(base::StringPiece("66", 1), &output)); + EXPECT_EQ(6, output); +} + +TEST(StringNumberConversion, StringToUnsignedInt) { + const struct { + const char* string; + bool valid; + unsigned int value; + } kTestData[] = { + {"", false, 0}, + {"0", true, 0}, + {"1", true, 1}, + {"2147483647", true, 2147483647}, + {"2147483648", true, 2147483648}, + {"4294967295", true, std::numeric_limits::max()}, + {"4294967296", false, 0}, + {"-1", false, 0}, + {"-2147483648", false, 0}, + {"-2147483649", false, 0}, + {"00", true, 0}, + {"01", true, 1}, + {"-01", false, 0}, + {"+2", true, 2}, + {"0x10", true, 16}, + {"-0x10", false, 0}, + {"+0x20", true, 32}, + {"0xf", true, 15}, + {"0xg", false, 0}, + {"0x7fffffff", true, 0x7fffffff}, + {"0x7FfFfFfF", true, 0x7fffffff}, + {"0x80000000", true, 0x80000000}, + {"0xFFFFFFFF", true, 0xffffffff}, + {"-0x7fffffff", false, 0}, + {"-0x80000000", false, 0}, + {"-0x80000001", false, 0}, + {"-0xffffffff", false, 0}, + {"0x100000000", false, 0}, + {"0xabcdef", true, 11259375}, + {"010", true, 8}, + {"-010", false, 0}, + {"+020", true, 16}, + {"07", true, 7}, + {"08", false, 0}, + {" 0", false, 0}, + {"0 ", false, 0}, + {" 0 ", false, 0}, + {" 1", false, 0}, + {"1 ", false, 0}, + {" 1 ", false, 0}, + {"a2", false, 0}, + {"2a", false, 0}, + {"2a2", false, 0}, + {".0", false, 0}, + {".1", false, 0}, + {"-.2", false, 0}, + {"+.3", false, 0}, + {"1.23", false, 0}, + {"-273.15", false, 0}, + {"+98.6", false, 0}, + {"1e1", false, 0}, + {"1E1", false, 0}, + {"0x123p4", false, 0}, + {"infinity", false, 0}, + {"NaN", false, 0}, + {"-9223372036854775810", false, 0}, + {"-9223372036854775809", false, 0}, + {"9223372036854775808", false, 0}, + {"9223372036854775809", false, 0}, + {"18446744073709551615", false, 0}, + {"18446744073709551616", false, 0}, + }; + + for (size_t index = 0; index < arraysize(kTestData); ++index) { + unsigned int value; + bool valid = StringToNumber(kTestData[index].string, &value); + if (kTestData[index].valid) { + EXPECT_TRUE(valid) << "index " << index << ", string " + << kTestData[index].string; + if (valid) { + EXPECT_EQ(kTestData[index].value, value) + << "index " << index << ", string " << kTestData[index].string; + } + } else { + EXPECT_FALSE(valid) << "index " << index << ", string " + << kTestData[index].string << ", value " << value; + } + } + + // Ensure that embedded NUL characters are treated as bad input. + const char input[] = "6\0006"; + base::StringPiece input_string(input, arraysize(input) - 1); + unsigned int output; + EXPECT_FALSE(StringToNumber(input_string, &output)); + + // Ensure that a NUL is not required at the end of the string. + EXPECT_TRUE(StringToNumber(base::StringPiece("66", 1), &output)); + EXPECT_EQ(6u, output); +} + +} // namespace diff --git a/util/util.gyp b/util/util.gyp index b38e1fb7..0176647d 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -80,6 +80,8 @@ 'stdlib/cxx.h', 'stdlib/objc.h', 'stdlib/pointer_container.h', + 'stdlib/string_number_conversion.cc', + 'stdlib/string_number_conversion.h', 'stdlib/strlcpy.cc', 'stdlib/strlcpy.h', 'stdlib/strnlen.cc', @@ -192,6 +194,7 @@ 'numeric/checked_range_test.cc', 'numeric/in_range_cast_test.cc', 'posix/process_util_test.cc', + 'stdlib/string_number_conversion_test.cc', 'stdlib/strlcpy_test.cc', 'stdlib/strnlen_test.cc', 'test/executable_path_test.cc',