From d05166c26c9f9e26e06c42650f518ff5ee6bd957 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Fri, 15 Aug 2014 10:22:37 -0700 Subject: [PATCH] Add CheckedRange<> and its test. TEST=util_test CheckedRange R=rsesek@chromium.org Review URL: https://codereview.chromium.org/467113002 --- util/numeric/checked_range.h | 115 ++++++++++++++ util/numeric/checked_range_test.cc | 246 +++++++++++++++++++++++++++++ util/util.gyp | 2 + 3 files changed, 363 insertions(+) create mode 100644 util/numeric/checked_range.h create mode 100644 util/numeric/checked_range_test.cc diff --git a/util/numeric/checked_range.h b/util/numeric/checked_range.h new file mode 100644 index 00000000..b0f12227 --- /dev/null +++ b/util/numeric/checked_range.h @@ -0,0 +1,115 @@ +// 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_NUMERIC_CHECKED_RANGE_H_ +#define CRASHPAD_UTIL_NUMERIC_CHECKED_RANGE_H_ + +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/numerics/safe_math.h" + +namespace crashpad { + +//! \brief Ensures that a range, composed of a base and size, does not overflow +//! its data type. +template +class CheckedRange { + public: + CheckedRange(ValueType base, SizeType size) { + COMPILE_ASSERT(!std::numeric_limits::is_signed, + SizeType_must_be_unsigned); + SetRange(base, size); + } + + //! \brief Sets the range’s base and size to \a base and \a size, + //! respectively. + void SetRange(ValueType base, SizeType size) { + base_ = base; + size_ = size; + } + + //! \brief The range’s base. + ValueType base() const { return base_; } + + //! \brief The range’s size. + SizeType size() const { return size_; } + + //! \brief The range’s end (its base plus its size). + ValueType end() const { return base_ + size_; } + + //! \brief Returns the validity of the range. + //! + //! \return `true` if the range is valid, `false` otherwise. + //! + //! A range is valid if its size can be converted to the range’s data type + //! without data loss, and if its end (base plus size) can be computed without + //! overflowing its data type. + bool IsValid() const { + if (!base::IsValueInRangeForNumericType(size_)) { + return false; + } + base::CheckedNumeric checked_end(base_); + checked_end += static_cast(size_); + return checked_end.IsValid(); + } + + //! \brief Returns whether the range contains another value. + //! + //! \param[in] value The (possibly) contained value. + //! + //! \return `true` if the range contains \a value, `false` otherwise. + //! + //! A range contains a value if the value is greater than or equal to its + //! base, and less than its end (base plus size). + //! + //! This method must only be called if IsValid() would return `true`. + bool ContainsValue(ValueType value) const { + DCHECK(IsValid()); + + return value >= base() && value < end(); + } + + //! \brief Returns whether the range contains another range. + //! + //! \param[in] that The (possibly) contained range. + //! + //! \return `true` if `this` range, the containing range, contains \a that, + //! the contained range. `false` otherwise. + //! + //! A range contains another range when the contained range’s base is greater + //! than or equal to the containing range’s base, and the contained range’s + //! end is less than or equal to the containing range’s end. + //! + //! This method must only be called if IsValid() would return `true` for both + //! CheckedRange objects involved. + bool ContainsRange(const CheckedRange& that) const { + DCHECK(IsValid()); + DCHECK(that.IsValid()); + + return that.base() >= base() && that.end() <= end(); + } + + private: + ValueType base_; + SizeType size_; + + DISALLOW_COPY_AND_ASSIGN(CheckedRange); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_NUMERIC_CHECKED_RANGE_H_ diff --git a/util/numeric/checked_range_test.cc b/util/numeric/checked_range_test.cc new file mode 100644 index 00000000..baa843a5 --- /dev/null +++ b/util/numeric/checked_range_test.cc @@ -0,0 +1,246 @@ +// 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/numeric/checked_range.h" + +#include + +#include + +#include "base/basictypes.h" +#include "base/strings/stringprintf.h" +#include "gtest/gtest.h" + +namespace { + +using namespace crashpad; + +TEST(CheckedRange, IsValid) { + const struct UnsignedTestData { + uint32_t base; + uint32_t size; + bool valid; + } kUnsignedTestData[] = { + {0, 0, true}, + {0, 1, true}, + {0, 2, true}, + {0, 0x7fffffff, true}, + {0, 0x80000000, true}, + {0, 0xfffffffe, true}, + {0, 0xffffffff, true}, + {1, 0, true}, + {1, 1, true}, + {1, 2, true}, + {1, 0x7fffffff, true}, + {1, 0x80000000, true}, + {1, 0xfffffffe, true}, + {1, 0xffffffff, false}, + {0x7fffffff, 0, true}, + {0x7fffffff, 1, true}, + {0x7fffffff, 2, true}, + {0x7fffffff, 0x7fffffff, true}, + {0x7fffffff, 0x80000000, true}, + {0x7fffffff, 0xfffffffe, false}, + {0x7fffffff, 0xffffffff, false}, + {0x80000000, 0, true}, + {0x80000000, 1, true}, + {0x80000000, 2, true}, + {0x80000000, 0x7fffffff, true}, + {0x80000000, 0x80000000, false}, + {0x80000000, 0xfffffffe, false}, + {0x80000000, 0xffffffff, false}, + {0xfffffffe, 0, true}, + {0xfffffffe, 1, true}, + {0xfffffffe, 2, false}, + {0xfffffffe, 0x7fffffff, false}, + {0xfffffffe, 0x80000000, false}, + {0xfffffffe, 0xfffffffe, false}, + {0xfffffffe, 0xffffffff, false}, + {0xffffffff, 0, true}, + {0xffffffff, 1, false}, + {0xffffffff, 2, false}, + {0xffffffff, 0x7fffffff, false}, + {0xffffffff, 0x80000000, false}, + {0xffffffff, 0xfffffffe, false}, + {0xffffffff, 0xffffffff, false}, + }; + + for (size_t index = 0; index < arraysize(kUnsignedTestData); ++index) { + const UnsignedTestData& testcase = kUnsignedTestData[index]; + SCOPED_TRACE(base::StringPrintf("unsigned index %zu, base 0x%x, size 0x%x", + index, + testcase.base, + testcase.size)); + + CheckedRange range(testcase.base, testcase.size); + EXPECT_EQ(testcase.valid, range.IsValid()); + } + + const int32_t kMinInt32 = std::numeric_limits::min(); + const struct SignedTestData { + int32_t base; + uint32_t size; + bool valid; + } kSignedTestData[] = { + {0, 0, true}, + {0, 1, true}, + {0, 2, true}, + {0, 0x7fffffff, true}, + {0, 0x80000000, false}, + {0, 0xfffffffe, false}, + {0, 0xffffffff, false}, + {1, 0, true}, + {1, 1, true}, + {1, 2, true}, + {1, 0x7fffffff, false}, + {1, 0x80000000, false}, + {1, 0xfffffffe, false}, + {1, 0xffffffff, false}, + {0x7fffffff, 0, true}, + {0x7fffffff, 1, false}, + {0x7fffffff, 2, false}, + {0x7fffffff, 0x7fffffff, false}, + {0x7fffffff, 0x80000000, false}, + {0x7fffffff, 0xfffffffe, false}, + {0x7fffffff, 0xffffffff, false}, + {kMinInt32, 0, true}, + {kMinInt32, 1, true}, + {kMinInt32, 2, true}, + {kMinInt32, 0x7fffffff, true}, + {kMinInt32, 0x80000000, false}, + {kMinInt32, 0xfffffffe, false}, + {kMinInt32, 0xffffffff, false}, + {-2, 0, true}, + {-2, 1, true}, + {-2, 2, true}, + {-2, 0x7fffffff, true}, + {-2, 0x80000000, false}, + {-2, 0xfffffffe, false}, + {-2, 0xffffffff, false}, + {-1, 0, true}, + {-1, 1, true}, + {-1, 2, true}, + {-1, 0x7fffffff, true}, + {-1, 0x80000000, false}, + {-1, 0xfffffffe, false}, + {-1, 0xffffffff, false}, + }; + + for (size_t index = 0; index < arraysize(kSignedTestData); ++index) { + const SignedTestData& testcase = kSignedTestData[index]; + SCOPED_TRACE(base::StringPrintf("signed index %zu, base 0x%x, size 0x%x", + index, + testcase.base, + testcase.size)); + + CheckedRange range(testcase.base, testcase.size); + EXPECT_EQ(testcase.valid, range.IsValid()); + } +} + +TEST(CheckedRange, ContainsValue) { + const struct TestData { + uint32_t value; + bool valid; + } kTestData[] = { + {0, false}, + {1, false}, + {0x1fff, false}, + {0x2000, true}, + {0x2001, true}, + {0x2ffe, true}, + {0x2fff, true}, + {0x3000, false}, + {0x3001, false}, + {0x7fffffff, false}, + {0x80000000, false}, + {0x80000001, false}, + {0x80001fff, false}, + {0x80002000, false}, + {0x80002001, false}, + {0x80002ffe, false}, + {0x80002fff, false}, + {0x80003000, false}, + {0x80003001, false}, + {0xffffcfff, false}, + {0xffffdfff, false}, + {0xffffefff, false}, + {0xffffffff, false}, + }; + + CheckedRange parent_range(0x2000, 0x1000); + ASSERT_TRUE(parent_range.IsValid()); + + for (size_t index = 0; index < arraysize(kTestData); ++index) { + const TestData& testcase = kTestData[index]; + SCOPED_TRACE( + base::StringPrintf("index %zu, value 0x%x", index, testcase.value)); + + EXPECT_EQ(testcase.valid, parent_range.ContainsValue(testcase.value)); + } +} + +TEST(CheckedRange, ContainsRange) { + const struct TestData { + uint32_t base; + uint32_t size; + bool valid; + } kTestData[] = { + {0, 0, false}, + {0, 1, false}, + {0x2000, 0x1000, true}, + {0, 0x2000, false}, + {0x3000, 0x1000, false}, + {0x1800, 0x1000, false}, + {0x2800, 0x1000, false}, + {0x2000, 0x800, true}, + {0x2800, 0x800, true}, + {0x2400, 0x800, true}, + {0x2800, 0, true}, + {0x2000, 0xffffdfff, false}, + {0x2800, 0xffffd7ff, false}, + {0x3000, 0xffffcfff, false}, + {0xfffffffe, 1, false}, + {0xffffffff, 0, false}, + {0x1fff, 0, false}, + {0x2000, 0, true}, + {0x2001, 0, true}, + {0x2fff, 0, true}, + {0x3000, 0, true}, + {0x3001, 0, false}, + {0x1fff, 1, false}, + {0x2000, 1, true}, + {0x2001, 1, true}, + {0x2fff, 1, true}, + {0x3000, 1, false}, + {0x3001, 1, false}, + }; + + CheckedRange parent_range(0x2000, 0x1000); + ASSERT_TRUE(parent_range.IsValid()); + + for (size_t index = 0; index < arraysize(kTestData); ++index) { + const TestData& testcase = kTestData[index]; + SCOPED_TRACE(base::StringPrintf("index %zu, base 0x%x, size 0x%x", + index, + testcase.base, + testcase.size)); + + CheckedRange child_range(testcase.base, testcase.size); + ASSERT_TRUE(child_range.IsValid()); + EXPECT_EQ(testcase.valid, parent_range.ContainsRange(child_range)); + } +} + +} // namespace diff --git a/util/util.gyp b/util/util.gyp index b4f34716..9cb2eeb3 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -41,6 +41,7 @@ 'misc/initialization_state_dcheck.h', 'misc/uuid.cc', 'misc/uuid.h', + 'numeric/checked_range.h', 'numeric/in_range_cast.h', 'numeric/safe_assignment.h', 'posix/process_util.h', @@ -90,6 +91,7 @@ 'misc/initialization_state_dcheck_test.cc', 'misc/initialization_state_test.cc', 'misc/uuid_test.cc', + 'numeric/checked_range_test.cc', 'numeric/in_range_cast_test.cc', 'posix/process_util_test.cc', 'stdlib/strlcpy_test.cc',