Extend ContainedRangeMap and StaticContainedRangeMap

This adds a new mode in ContainedRangeMap which allows existance of
equal ranges. Among those equal ranges, the most recently added range is
the innermost range.

This also adds a function to ContainedRangeMap and
StaticContainedRangeMap to allow users get a vector of entries that
contains given address from innermost to outermost ranges.

Change-Id: I84c1f2e49ffcaf8238df60e41498730103d1ead6
Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/3291137
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
This commit is contained in:
Zequan Wu 2021-11-30 13:53:53 -08:00 committed by Joshua Peraza
parent 4ee9854be5
commit 4458a5965a
6 changed files with 240 additions and 45 deletions

View file

@ -84,10 +84,12 @@ bool ContainedRangeMap<AddressType, EntryType>::StoreRange(
// range's, it violates the containment rules, and an attempt to store
// it must fail. iterator_base->first contains the key, which was the
// containing child's high address.
if (iterator_base->second->base_ == base && iterator_base->first == high) {
if (!allow_equal_range_ && iterator_base->second->base_ == base &&
iterator_base->first == high) {
// TODO(nealsid): See the TODO above on why this is commented out.
// BPLOG(INFO) << "StoreRange failed, identical range is already "
// "present: " << HexString(base) << "+" << HexString(size);
// BPLOG(INFO) << "StoreRange failed, identical range is already "
// "present: " << HexString(base) << "+" <<
// HexString(size);
return false;
}
@ -141,8 +143,10 @@ bool ContainedRangeMap<AddressType, EntryType>::StoreRange(
// the new child range contains were formerly children of this range but
// are now this range's grandchildren. Ownership of these is transferred
// to the new child range.
map_->insert(MapValue(high,
new ContainedRangeMap(base, entry, child_map)));
ContainedRangeMap* new_child =
new ContainedRangeMap(base, entry, child_map, allow_equal_range_);
map_->insert(MapValue(high, new_child));
return true;
}
@ -177,6 +181,20 @@ bool ContainedRangeMap<AddressType, EntryType>::RetrieveRange(
return true;
}
template <typename AddressType, typename EntryType>
bool ContainedRangeMap<AddressType, EntryType>::RetrieveRanges(
const AddressType& address,
std::vector<const EntryType*>& entries) const {
// If nothing was ever stored, then there's nothing to retrieve.
if (!map_)
return false;
MapIterator iterator = map_->lower_bound(address);
if (iterator == map_->end() || address < iterator->second->base_)
return false;
iterator->second->RetrieveRanges(address, entries);
entries.push_back(&iterator->second->entry_);
return true;
}
template<typename AddressType, typename EntryType>
void ContainedRangeMap<AddressType, EntryType>::Clear() {

View file

@ -62,6 +62,7 @@
#include <map>
#include <vector>
namespace google_breakpad {
@ -75,7 +76,8 @@ class ContainedRangeMap {
// The default constructor creates a ContainedRangeMap with no geometry
// and no entry, and as such is only suitable for the root node of a
// ContainedRangeMap tree.
ContainedRangeMap() : base_(), entry_(), map_(NULL) {}
explicit ContainedRangeMap(bool allow_equal_range = false)
: base_(), entry_(), map_(NULL), allow_equal_range_(allow_equal_range) {}
~ContainedRangeMap();
@ -95,7 +97,12 @@ class ContainedRangeMap {
// child ranges, and not the entry contained by |this|. This is necessary
// to support a sparsely-populated root range. If no descendant range
// encompasses the address, returns false.
bool RetrieveRange(const AddressType& address, EntryType* entry) const;
bool RetrieveRange(const AddressType& address, EntryType* entries) const;
// Retrieves the vector of entries encompassing the specified address from the
// innermost entry to the outermost entry.
bool RetrieveRanges(const AddressType& address,
std::vector<const EntryType*>& entries) const;
// Removes all children. Note that Clear only removes descendants,
// leaving the node on which it is called intact. Because the only
@ -118,9 +125,14 @@ class ContainedRangeMap {
// Creates a new ContainedRangeMap with the specified base address, entry,
// and initial child map, which may be NULL. This is only used internally
// by ContainedRangeMap when it creates a new child.
ContainedRangeMap(const AddressType& base, const EntryType& entry,
AddressToRangeMap* map)
: base_(base), entry_(entry), map_(map) {}
ContainedRangeMap(const AddressType& base,
const EntryType& entry,
AddressToRangeMap* map,
bool allow_equal_range)
: base_(base),
entry_(entry),
map_(map),
allow_equal_range_(allow_equal_range) {}
// The base address of this range. The high address does not need to
// be stored, because it is used as the key to an object in its parent's
@ -141,6 +153,12 @@ class ContainedRangeMap {
// address. This is a pointer to avoid allocating map structures for
// leaf nodes, where they are not needed.
AddressToRangeMap* map_;
// Whether or not we allow storing an entry into a range that equals to
// existing range in the map. Default is false.
// If this is true, the newly added range will become a child of existing
// innermost range which has same base and size.
bool allow_equal_range_;
};

View file

@ -51,9 +51,82 @@ namespace {
using google_breakpad::ContainedRangeMap;
// The first is the querying address, the second is the entries vector result.
using EntriesTestPair = std::pair<unsigned, std::vector<int>>;
using EntriesTestPairVec = std::vector<EntriesTestPair>;
static bool RunTestsWithRetrieveRange(
const ContainedRangeMap<unsigned int, int>& crm,
const int* test_data,
unsigned int test_length) {
// Now, do the RetrieveRange tests. This further validates that the
// objects were stored properly and that retrieval returns the correct
// object.
// If GENERATE_TEST_DATA is defined, instead of the retrieval tests, a
// new test_data array will be printed. Exercise caution when doing this.
// Be sure to verify the results manually!
#ifdef GENERATE_TEST_DATA
printf(" const int test_data[] = {\n");
#endif // GENERATE_TEST_DATA
static bool RunTests() {
for (unsigned int address = 0; address < test_length; ++address) {
int value;
if (!crm.RetrieveRange(address, &value))
value = 0;
#ifndef GENERATE_TEST_DATA
// Don't use ASSERT inside the loop because it won't show the failed
// |address|, and the line number will always be the same. That makes
// it difficult to figure out which test failed.
if (value != test_data[address]) {
fprintf(stderr, "FAIL: retrieve %d expected %d observed %d @ %s:%d\n",
address, test_data[address], value, __FILE__, __LINE__);
return false;
}
#else // !GENERATE_TEST_DATA
printf(" %d%c%s // %d\n", value, address == test_high - 1 ? ' ' : ',',
value < 10 ? " " : "", address);
#endif // !GENERATE_TEST_DATA
}
#ifdef GENERATE_TEST_DATA
printf(" };\n");
#endif // GENERATE_TEST_DATA
return true;
}
static bool RunTestsWithRetrieveRangeVector(
const ContainedRangeMap<unsigned int, int>& crm,
const EntriesTestPairVec& entries_tests) {
for (const EntriesTestPair& entries_test : entries_tests) {
std::vector<const int*> entries;
crm.RetrieveRanges(entries_test.first, entries);
if (entries.size() != entries_test.second.size()) {
fprintf(stderr,
"FAIL: retrieving entries at address %u has size %zu "
"expected to have size %zu "
"@ %s: %d\n",
entries_test.first, entries.size(), entries_test.second.size(),
__FILE__, __LINE__);
return false;
}
for (size_t i = 0; i < entries.size(); ++i) {
if (*entries[i] != entries_test.second[i]) {
fprintf(stderr,
"FAIL: retrieving entries at address %u entries[%zu] is %d "
"expected %d"
"@ %s: %d\n",
entries_test.first, i, *entries[i], entries_test.second[i],
__FILE__, __LINE__);
return false;
}
}
}
return true;
}
static bool RunTestsWithNoEqualRange() {
ContainedRangeMap<unsigned int, int> crm;
// First, do the StoreRange tests. This validates the containment
@ -211,45 +284,89 @@ static bool RunTests() {
0, // 98
0 // 99
};
unsigned int test_high = sizeof(test_data) / sizeof(int);
unsigned int test_length = sizeof(test_data) / sizeof(int);
return RunTestsWithRetrieveRange(crm, test_data, test_length);
}
// Now, do the RetrieveRange tests. This further validates that the
// objects were stored properly and that retrieval returns the correct
// object.
// If GENERATE_TEST_DATA is defined, instead of the retrieval tests, a
// new test_data array will be printed. Exercise caution when doing this.
// Be sure to verify the results manually!
#ifdef GENERATE_TEST_DATA
printf(" const int test_data[] = {\n");
#endif // GENERATE_TEST_DATA
static bool RunTestsWithEqualRange() {
ContainedRangeMap<unsigned int, int> crm(true);
for (unsigned int address = 0; address < test_high; ++address) {
int value;
if (!crm.RetrieveRange(address, &value))
value = 0;
// First, do the StoreRange tests. This validates the containment
// rules.
ASSERT_TRUE (crm.StoreRange(1, 3, 1));
ASSERT_TRUE (crm.StoreRange(1, 3, 2)); // exactly equal to 1
ASSERT_TRUE (crm.StoreRange(1, 3, 3)); // exactly equal to 1, 2
ASSERT_TRUE (crm.StoreRange(1, 3, 4)); // exactly equal to 1, 2, 3
ASSERT_FALSE(crm.StoreRange(0, 3, 5)); // partial overlap.
ASSERT_FALSE(crm.StoreRange(2, 3, 6)); // partial overlap.
#ifndef GENERATE_TEST_DATA
// Don't use ASSERT inside the loop because it won't show the failed
// |address|, and the line number will always be the same. That makes
// it difficult to figure out which test failed.
if (value != test_data[address]) {
fprintf(stderr, "FAIL: retrieve %d expected %d observed %d @ %s:%d\n",
address, test_data[address], value, __FILE__, __LINE__);
return false;
}
#else // !GENERATE_TEST_DATA
printf(" %d%c%s // %d\n", value,
address == test_high - 1 ? ' ' : ',',
value < 10 ? " " : "",
address);
#endif // !GENERATE_TEST_DATA
}
ASSERT_TRUE (crm.StoreRange(5, 3, 7));
ASSERT_TRUE (crm.StoreRange(5, 3, 8)); // exactly equal to 7
ASSERT_TRUE (crm.StoreRange(5, 3, 9)); // exactly equal to 7, 8
ASSERT_TRUE (crm.StoreRange(5, 4, 10)); // encompasses 7, 8, 9
ASSERT_TRUE (crm.StoreRange(5, 5, 11)); // encompasses 7, 8, 9, 10
#ifdef GENERATE_TEST_DATA
printf(" };\n");
#endif // GENERATE_TEST_DATA
ASSERT_TRUE (crm.StoreRange(10, 3, 12));
ASSERT_TRUE (crm.StoreRange(10, 3, 13)); // exactly equal to 12
ASSERT_TRUE (crm.StoreRange(11, 2, 14)); // encompasses by 12
ASSERT_TRUE (crm.StoreRange(11, 1, 15)); // encompasses by 12, 13
return true;
ASSERT_TRUE (crm.StoreRange(14, 3, 16));
ASSERT_TRUE (crm.StoreRange(14, 3, 17)); // exactly equal to 14
ASSERT_TRUE (crm.StoreRange(14, 1, 18)); // encompasses by 14, 15
ASSERT_TRUE (crm.StoreRange(14, 2, 19)); // encompasses by 14, 15 and encompasses 16
ASSERT_TRUE (crm.StoreRange(14, 1, 20)); // exactly equal to 18
ASSERT_TRUE (crm.StoreRange(14, 2, 21)); // exactly equal to 19
// Each element in test_data contains the expected result when calling
// RetrieveRange on an address.
const int test_data[] = {
0, // 0
4, // 1
4, // 2
4, // 3
0, // 4
9, // 5
9, // 6
9, // 7
10, // 8
11, // 9
13, // 10
15, // 11
14, // 12
0, // 13
20, // 14
21, // 15
17, // 16
0, // 17
};
unsigned int test_length = sizeof(test_data) / sizeof(int);
EntriesTestPairVec entries_tests = {
{0, {}},
{1, {4, 3, 2, 1}},
{2, {4, 3, 2, 1}},
{3, {4, 3, 2, 1}},
{4, {}},
{5, {9, 8, 7, 10, 11}},
{6, {9, 8, 7, 10, 11}},
{7, {9, 8, 7, 10, 11}},
{8, {10, 11}},
{9, {11}},
{10, {13, 12}},
{11, {15, 14, 13, 12}},
{12, {14, 13, 12}},
{13, {}},
{14, {20, 18, 21, 19, 17, 16}},
{15, {21, 19, 17, 16}},
{16, {17, 16}},
{17, {}},
};
return RunTestsWithRetrieveRange(crm, test_data, test_length) &&
RunTestsWithRetrieveRangeVector(crm, entries_tests);
}
static bool RunTests() {
return RunTestsWithNoEqualRange() && RunTestsWithEqualRange();
}

View file

@ -87,6 +87,23 @@ bool StaticContainedRangeMap<AddressType, EntryType>::RetrieveRange(
return true;
}
template <typename AddressType, typename EntryType>
bool StaticContainedRangeMap<AddressType, EntryType>::RetrieveRanges(
const AddressType& address,
std::vector<const EntryType*>& entries) const {
MapConstIterator iterator = map_.lower_bound(address);
if (iterator == map_.end())
return false;
const char* memory_child =
reinterpret_cast<const char*>(iterator.GetValuePtr());
StaticContainedRangeMap child_map(memory_child);
if (address < child_map.base_)
return false;
child_map.RetrieveRanges(address, entries);
entries.push_back(child_map.entry_ptr_);
return true;
}
} // namespace google_breakpad
#endif // PROCESSOR_STATIC_CONTAINED_RANGE_MAP_INL_H__

View file

@ -42,6 +42,7 @@
#ifndef PROCESSOR_STATIC_CONTAINED_RANGE_MAP_H__
#define PROCESSOR_STATIC_CONTAINED_RANGE_MAP_H__
#include <vector>
#include "processor/static_map-inl.h"
namespace google_breakpad {
@ -59,6 +60,11 @@ class StaticContainedRangeMap {
// encompasses the address, returns false.
bool RetrieveRange(const AddressType& address, const EntryType*& entry) const;
// Retrieves the vector of entries encompassing the specified address from the
// innermost entry to the outermost entry.
bool RetrieveRanges(const AddressType& address,
std::vector<const EntryType*>& entry) const;
private:
friend class ModuleComparer;
// AddressToRangeMap stores pointers. This makes reparenting simpler in

View file

@ -273,6 +273,25 @@ TEST_F(TestStaticCRMMap, TestSingleElementMap) {
ASSERT_EQ(*entry_test, entry);
}
TEST_F(TestStaticCRMMap, TestRetrieveRangeEntries) {
CRMMap crm_map;
crm_map.StoreRange(2, 5, 0);
crm_map.StoreRange(2, 6, 1);
crm_map.StoreRange(2, 7, 2);
unsigned int size;
scoped_array<char> serialized_data;
serialized_data.reset(serializer_.Serialize(&crm_map, &size));
scoped_ptr<TestMap> test_map(new TestMap(serialized_data.get()));
std::vector<const int*> entry_tests;
ASSERT_TRUE(test_map->RetrieveRanges(3, entry_tests));
ASSERT_EQ(*entry_tests[0], 0);
ASSERT_EQ(*entry_tests[1], 1);
ASSERT_EQ(*entry_tests[2], 2);
}
TEST_F(TestStaticCRMMap, RunTestData) {
unsigned int test_high = sizeof(test_data) / sizeof(test_data[0]);