Commit 78754aa2 authored by Etienne Pierre-Doray's avatar Etienne Pierre-Doray Committed by Commit Bot

[Zucchini] Change in target delta

This CL changes the way target delta is computed.
Introduces OffsetMapper used to map offsets (targets) from old to new image
in both gen and apply.
Previous:
 - old label associated with old target is retrieved
 - delta is applied to label
 - new target is retrieved from label
New:
 - old target is projected to estimated new target
 - estimated new target is indirected into a key
 - delta is applied to key
 - new target is retrieved from key

This removes the need for UnorderedLabelManager.

Bug: 729154
Change-Id: If32f7db8f424fd59275f583860b38ba9740ee594
Reviewed-on: https://chromium-review.googlesource.com/627188
Commit-Queue: Etienne Pierre-Doray <etiennep@chromium.org>
Reviewed-by: default avatarSamuel Huang <huangs@chromium.org>
Cr-Commit-Position: refs/heads/master@{#541456}
parent 7fc07db4
......@@ -9,7 +9,6 @@
#include <vector>
#include "chrome/installer/zucchini/image_index.h"
#include "chrome/installer/zucchini/label_manager.h"
#include "chrome/installer/zucchini/test_disassembler.h"
#include "testing/gtest/include/gtest/gtest.h"
......
......@@ -8,6 +8,7 @@
#include "base/logging.h"
#include "chrome/installer/zucchini/encoded_view.h"
#include "chrome/installer/zucchini/patch_reader.h"
#include "chrome/installer/zucchini/suffix_array.h"
namespace zucchini {
......@@ -187,13 +188,136 @@ EquivalenceCandidate VisitEquivalenceSeed(
min_similarity);
}
/******** OffsetMapper ********/
OffsetMapper::OffsetMapper(std::vector<Equivalence>&& equivalences)
: equivalences_(std::move(equivalences)) {
DCHECK(std::is_sorted(equivalences_.begin(), equivalences_.end(),
[](const Equivalence& a, const Equivalence& b) {
return a.src_offset < b.src_offset;
}));
}
OffsetMapper::OffsetMapper(EquivalenceSource&& equivalence_source) {
for (auto e = equivalence_source.GetNext(); e.has_value();
e = equivalence_source.GetNext()) {
equivalences_.push_back(*e);
}
PruneEquivalencesAndSortBySource(&equivalences_);
}
OffsetMapper::OffsetMapper(const EquivalenceMap& equivalence_map)
: equivalences_(equivalence_map.size()) {
std::transform(equivalence_map.begin(), equivalence_map.end(),
equivalences_.begin(),
[](const EquivalenceCandidate& c) { return c.eq; });
PruneEquivalencesAndSortBySource(&equivalences_);
}
OffsetMapper::~OffsetMapper() = default;
offset_t OffsetMapper::ForwardProject(offset_t offset) const {
auto pos = std::upper_bound(
equivalences_.begin(), equivalences_.end(), offset,
[](offset_t a, const Equivalence& b) { return a < b.src_offset; });
if (pos != equivalences_.begin()) {
if (pos == equivalences_.end() || offset < pos[-1].src_end() ||
offset - pos[-1].src_end() < pos->src_offset - offset) {
--pos;
}
}
return offset - pos->src_offset + pos->dst_offset;
}
void OffsetMapper::ForwardProjectAll(std::vector<offset_t>* offsets) const {
DCHECK(std::is_sorted(offsets->begin(), offsets->end()));
auto current = equivalences_.begin();
for (auto& src : *offsets) {
while (current != end() && current->src_end() <= src) {
++current;
}
if (current != end() && current->src_offset <= src) {
src = src - current->src_offset + current->dst_offset;
} else {
src = kInvalidOffset;
}
}
offsets->erase(std::remove(offsets->begin(), offsets->end(), kInvalidOffset),
offsets->end());
offsets->shrink_to_fit();
}
void OffsetMapper::PruneEquivalencesAndSortBySource(
std::vector<Equivalence>* equivalences) {
std::sort(equivalences->begin(), equivalences->end(),
[](const Equivalence& a, const Equivalence& b) {
return a.src_offset < b.src_offset;
});
for (auto current = equivalences->begin(); current != equivalences->end();
++current) {
// A "reaper" is an equivalence after |current| that overlaps with it, but
// is longer, and so truncates |current|. For example:
// ****** <= |current|
// **
// ****
// ****
// ********** <= |next| as reaper.
// If a reaper is found (as |next|), every equivalence strictly between
// |current| and |next| would be truncated to 0 and discarded. Handling this
// case is important to avoid O(n^2) behavior.
bool next_is_reaper = false;
// Look ahead to resolve overlaps, until a better candidate is found.
auto next = current + 1;
for (; next != equivalences->end(); ++next) {
DCHECK_GE(next->src_offset, current->src_offset);
if (next->src_offset >= current->src_end())
break; // No more overlap.
if (current->length < next->length) {
// |next| is better: So it is a reaper that shrinks |current|.
offset_t delta = current->src_end() - next->src_offset;
current->length -= delta;
next_is_reaper = true;
break;
}
}
if (next_is_reaper) {
// Discard all equivalences strictly between |cur| and |next|.
for (auto reduced = current + 1; reduced != next; ++reduced)
reduced->length = 0;
current = next - 1;
} else {
// Shrink all equivalences that overlap with |current|. These are all
// worse than |current| since no reaper is found.
for (auto reduced = current + 1; reduced != next; ++reduced) {
offset_t delta =
std::min(reduced->length, current->src_end() - reduced->src_offset);
reduced->length -= delta;
reduced->src_offset += delta;
reduced->dst_offset += delta;
DCHECK_EQ(reduced->src_offset, current->src_end());
}
}
}
// Discard all equivalences with length == 0.
equivalences->erase(std::remove_if(equivalences->begin(), equivalences->end(),
[](const Equivalence& equivalence) {
return equivalence.length == 0;
}),
equivalences->end());
}
/******** EquivalenceMap ********/
EquivalenceMap::EquivalenceMap() = default;
EquivalenceMap::EquivalenceMap(
const std::vector<EquivalenceCandidate>& equivalences)
: candidates_(equivalences) {
EquivalenceMap::EquivalenceMap(std::vector<EquivalenceCandidate>&& equivalences)
: candidates_(std::move(equivalences)) {
SortByDestination();
}
......@@ -226,17 +350,6 @@ void EquivalenceMap::Build(
<< new_view.size() - coverage << " / " << new_view.size();
}
std::vector<Equivalence> EquivalenceMap::MakeForwardEquivalences() const {
std::vector<Equivalence> equivalences(size());
std::transform(begin(), end(), equivalences.begin(),
[](const EquivalenceCandidate& c) { return c.eq; });
std::sort(equivalences.begin(), equivalences.end(),
[](const Equivalence& a, const Equivalence& b) {
return a.src_offset < b.src_offset;
});
return equivalences;
}
void EquivalenceMap::CreateCandidates(
const std::vector<offset_t>& old_sa,
const EncodedView& old_view,
......@@ -306,42 +419,55 @@ void EquivalenceMap::Prune(
const EncodedView& new_view,
const std::vector<TargetsAffinity>& target_affinities,
double min_similarity) {
// TODO(etiennep): unify with
// OffsetMapper::PruneEquivalencesAndSortBySource().
for (auto current = candidates_.begin(); current != candidates_.end();
++current) {
if (current->similarity < min_similarity)
continue; // This candidate will be discarded anyways.
bool next_is_reaper = false;
// Look ahead to resolve overlaps, until a better candidate is found.
for (auto next = current + 1; next != candidates_.end(); ++next) {
auto next = current + 1;
for (; next != candidates_.end(); ++next) {
DCHECK_GE(next->eq.dst_offset, current->eq.dst_offset);
if (next->eq.dst_offset >= current->eq.dst_offset + current->eq.length)
break; // No more overlap.
offset_t delta = current->eq.dst_end() - next->eq.dst_offset;
// |next| is better, so |current| shrinks.
if (current->similarity < next->similarity) {
// |next| is better: So it is a reaper that shrinks |current|.
offset_t delta = current->eq.dst_end() - next->eq.dst_offset;
current->eq.length -= delta;
current->similarity = GetEquivalenceSimilarity(
old_view.image_index(), new_view.image_index(), target_affinities,
current->eq);
next_is_reaper = true;
break;
}
}
// Shrinks all overlapping candidates following and worse than |current|.
for (auto next = current + 1; next != candidates_.end(); ++next) {
if (next->eq.dst_offset >= current->eq.dst_offset + current->eq.length)
break; // No more overlap.
offset_t delta = current->eq.dst_end() - next->eq.dst_offset;
next->eq.length = next->eq.length > delta ? next->eq.length - delta : 0;
next->eq.src_offset += delta;
next->eq.dst_offset += delta;
next->similarity = GetEquivalenceSimilarity(old_view.image_index(),
new_view.image_index(),
target_affinities, next->eq);
DCHECK_EQ(next->eq.dst_offset, current->eq.dst_end());
if (next_is_reaper) {
// Discard all equivalences strictly between |cur| and |next|.
for (auto reduced = current + 1; reduced != next; ++reduced) {
reduced->eq.length = 0;
reduced->similarity = 0;
}
current = next - 1;
} else {
// Shrinks all overlapping candidates following and worse than |current|.
for (auto reduced = current + 1; reduced != next; ++reduced) {
offset_t delta = std::min(
reduced->eq.length, current->eq.dst_end() - reduced->eq.dst_offset);
reduced->eq.length -= delta;
reduced->eq.src_offset += delta;
reduced->eq.dst_offset += delta;
reduced->similarity = GetEquivalenceSimilarity(
old_view.image_index(), new_view.image_index(), target_affinities,
reduced->eq);
DCHECK_EQ(reduced->eq.dst_offset, current->eq.dst_end());
}
}
}
......
......@@ -19,6 +19,7 @@ namespace zucchini {
constexpr double kMismatchFatal = -std::numeric_limits<double>::infinity();
class EncodedView;
class EquivalenceSource;
// Returns similarity score between a token (raw byte or first byte of a
// reference) in |old_image_index| at |src| and a token in |new_image_index|
......@@ -75,6 +76,56 @@ EquivalenceCandidate VisitEquivalenceSeed(
offset_t dst,
double min_similarity);
// Container of pruned equivalences used to map offsets from |old_image| to
// offsets in |new_image|. Equivalences are pruned by cropping smaller
// equivalences to avoid overlaps, to make the equivalence map (for covered
// bytes in |old_image| and |new_image|) one-to-one.
class OffsetMapper {
public:
using const_iterator = std::vector<Equivalence>::const_iterator;
// Constructors for various data sources.
// - From a list of |equivalences|, already sorted (by |src_offset|) and
// pruned, useful for tests.
explicit OffsetMapper(std::vector<Equivalence>&& equivalences);
// - From a generator, useful for Zucchini-apply.
explicit OffsetMapper(EquivalenceSource&& equivalence_source);
// - From an EquivalenceMap that needs to be processed, useful for
// Zucchini-gen.
explicit OffsetMapper(const EquivalenceMap& equivalence_map);
~OffsetMapper();
size_t size() const { return equivalences_.size(); }
const_iterator begin() const { return equivalences_.begin(); }
const_iterator end() const { return equivalences_.end(); }
// Returns an offset in |new_image| corresponding to |offset| in |old_image|.
// If |offset| is not part of an equivalence, the equivalence nearest to
// |offset| is used as if it contained |offset|. This assumes |equivalences_|
// is not empty.
offset_t ForwardProject(offset_t offset) const;
// Given sorted |offsets|, applies a projection in-place of all offsets that
// are part of a pruned equivalence from |old_image| to |new_image|. Other
// offsets are removed from |offsets|.
void ForwardProjectAll(std::vector<offset_t>* offsets) const;
// Accessor for testing.
const std::vector<Equivalence> equivalences() const { return equivalences_; }
// Sorts |equivalences| by |src_offset| and removes all source overlaps; so a
// source location that was covered by some Equivalence would become covered
// by exactly one Equivalence. Moreover, for the offset, the equivalence
// corresponds to the largest (pre-pruning) covering Equivalence, and in case
// of a tie, the Equivalence with minimal |src_offset|. |equivalences| may
// change in size since empty Equivalences are removed.
static void PruneEquivalencesAndSortBySource(
std::vector<Equivalence>* equivalences);
private:
std::vector<Equivalence> equivalences_;
};
// Container of equivalences between |old_image_index| and |new_image_index|,
// sorted by |Equivalence::dst_offset|, only used during patch generation.
class EquivalenceMap {
......@@ -83,8 +134,7 @@ class EquivalenceMap {
EquivalenceMap();
// Initializes the object with |equivalences|.
explicit EquivalenceMap(
const std::vector<EquivalenceCandidate>& equivalences);
explicit EquivalenceMap(std::vector<EquivalenceCandidate>&& candidates);
EquivalenceMap(EquivalenceMap&&);
EquivalenceMap(const EquivalenceMap&) = delete;
~EquivalenceMap();
......@@ -106,10 +156,6 @@ class EquivalenceMap {
const_iterator begin() const { return candidates_.begin(); }
const_iterator end() const { return candidates_.end(); }
// Returns a vector containing equivalences sorted by
// |Equivalence::src_offset|.
std::vector<Equivalence> MakeForwardEquivalences() const;
private:
// Discovers equivalence candidates between |old_view| and |new_view| and
// stores them in the object. Note that resulting candidates are not sorted
......
......@@ -19,6 +19,8 @@ namespace zucchini {
namespace {
using OffsetVector = std::vector<offset_t>;
// Make all references 2 bytes long.
constexpr offset_t kReferenceSize = 2;
......@@ -245,6 +247,108 @@ TEST(EquivalenceMapTest, ExtendEquivalenceBackward) {
{{22, 19, 0}, 0.0}, 8.0));
}
TEST(EquivalenceMapTest, PruneEquivalencesAndSortBySource) {
auto PruneEquivalencesAndSortBySourceTest =
[](std::vector<Equivalence>&& equivalences) {
OffsetMapper::PruneEquivalencesAndSortBySource(&equivalences);
return equivalences;
};
EXPECT_EQ(std::vector<Equivalence>(),
PruneEquivalencesAndSortBySourceTest({}));
EXPECT_EQ(std::vector<Equivalence>({{0, 10, 1}}),
PruneEquivalencesAndSortBySourceTest({{0, 10, 1}}));
EXPECT_EQ(std::vector<Equivalence>(),
PruneEquivalencesAndSortBySourceTest({{0, 10, 0}}));
EXPECT_EQ(std::vector<Equivalence>({{0, 10, 1}, {1, 11, 1}}),
PruneEquivalencesAndSortBySourceTest({{0, 10, 1}, {1, 11, 1}}));
EXPECT_EQ(std::vector<Equivalence>({{0, 10, 2}, {2, 13, 1}}),
PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 2}}));
EXPECT_EQ(std::vector<Equivalence>({{0, 10, 2}}),
PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 1}}));
EXPECT_EQ(std::vector<Equivalence>({{0, 10, 2}, {2, 14, 1}}),
PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 13, 2}}));
EXPECT_EQ(std::vector<Equivalence>({{0, 10, 1}, {1, 12, 3}}),
PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 3}}));
EXPECT_EQ(std::vector<Equivalence>({{0, 10, 3}, {3, 16, 2}}),
PruneEquivalencesAndSortBySourceTest(
{{0, 10, 3}, {1, 13, 3}, {3, 16, 2}})); // Pruning is greedy
// Consider following pattern that may cause O(n^2) behavior if not handled
// properly.
// ***************
// **********
// ********
// ******
// ****
// **
// ***************
// This test case makes sure the function does not stall on a large instance
// of this pattern.
EXPECT_EQ(std::vector<Equivalence>({{0, 10, +300000}, {300000, 30, +300000}}),
PruneEquivalencesAndSortBySourceTest([] {
std::vector<Equivalence> equivalenses;
equivalenses.push_back({0, 10, +300000});
for (offset_t i = 0; i < 100000; ++i)
equivalenses.push_back({200000 + i, 20, +200000 - 2 * i});
equivalenses.push_back({300000, 30, +300000});
return equivalenses;
}()));
}
TEST(EquivalenceMapTest, ForwardProject) {
auto ForwardProjectAllTest = [](const OffsetMapper& offset_mapper,
std::initializer_list<offset_t> offsets) {
OffsetVector offsets_vec(offsets);
offset_mapper.ForwardProjectAll(&offsets_vec);
return offsets_vec;
};
OffsetMapper offset_mapper1({{0, 10, 2}, {2, 13, 1}, {4, 16, 2}});
EXPECT_EQ(OffsetVector({10}), ForwardProjectAllTest(offset_mapper1, {0}));
EXPECT_EQ(OffsetVector({13}), ForwardProjectAllTest(offset_mapper1, {2}));
EXPECT_EQ(OffsetVector({}), ForwardProjectAllTest(offset_mapper1, {3}));
EXPECT_EQ(OffsetVector({10, 13}),
ForwardProjectAllTest(offset_mapper1, {0, 2}));
EXPECT_EQ(OffsetVector({11, 13, 17}),
ForwardProjectAllTest(offset_mapper1, {1, 2, 5}));
EXPECT_EQ(OffsetVector({11, 17}),
ForwardProjectAllTest(offset_mapper1, {1, 3, 5}));
EXPECT_EQ(OffsetVector({10, 11, 13, 16, 17}),
ForwardProjectAllTest(offset_mapper1, {0, 1, 2, 3, 4, 5, 6}));
OffsetMapper offset_mapper2({{0, 10, 2}, {13, 2, 1}, {16, 4, 2}});
EXPECT_EQ(OffsetVector({2}), ForwardProjectAllTest(offset_mapper2, {13}));
EXPECT_EQ(OffsetVector({10, 2}),
ForwardProjectAllTest(offset_mapper2, {0, 13}));
EXPECT_EQ(OffsetVector({11, 2, 5}),
ForwardProjectAllTest(offset_mapper2, {1, 13, 17}));
EXPECT_EQ(OffsetVector({11, 5}),
ForwardProjectAllTest(offset_mapper2, {1, 14, 17}));
EXPECT_EQ(OffsetVector({10, 11, 2, 4, 5}),
ForwardProjectAllTest(offset_mapper2, {0, 1, 13, 14, 16, 17, 18}));
}
TEST(EquivalenceMapTest, ProjectOffset) {
OffsetMapper offset_mapper1({{0, 10, 2}, {2, 13, 1}, {4, 16, 2}});
EXPECT_EQ(10U, offset_mapper1.ForwardProject(0));
EXPECT_EQ(11U, offset_mapper1.ForwardProject(1));
EXPECT_EQ(13U, offset_mapper1.ForwardProject(2));
EXPECT_EQ(14U, offset_mapper1.ForwardProject(3)); // Previous equivalence.
EXPECT_EQ(16U, offset_mapper1.ForwardProject(4));
EXPECT_EQ(17U, offset_mapper1.ForwardProject(5));
EXPECT_EQ(18U, offset_mapper1.ForwardProject(6)); // Previous equivalence.
OffsetMapper offset_mapper2({{0, 10, 2}, {13, 2, 1}, {16, 4, 2}});
EXPECT_EQ(10U, offset_mapper2.ForwardProject(0));
EXPECT_EQ(11U, offset_mapper2.ForwardProject(1));
EXPECT_EQ(2U, offset_mapper2.ForwardProject(13));
EXPECT_EQ(3U, offset_mapper2.ForwardProject(14)); // Previous equivalence.
EXPECT_EQ(4U, offset_mapper2.ForwardProject(16));
EXPECT_EQ(5U, offset_mapper2.ForwardProject(17));
EXPECT_EQ(6U, offset_mapper2.ForwardProject(18)); // Previous equivalence.
}
TEST(EquivalenceMapTest, Build) {
auto test_build_equivalence = [](const ImageIndex old_index,
const ImageIndex new_index,
......@@ -339,21 +443,4 @@ TEST(EquivalenceMapTest, Build) {
4.0));
}
TEST(EquivalenceMapTest, MakeForwardEquivalences) {
EXPECT_EQ(std::vector<Equivalence>(),
EquivalenceMap().MakeForwardEquivalences());
EXPECT_EQ(std::vector<Equivalence>({{0, 0, 1}}),
EquivalenceMap({{{0, 0, 1}, 0.0}}).MakeForwardEquivalences());
EXPECT_EQ(std::vector<Equivalence>({{0, 0, 1}, {1, 1, 1}}),
EquivalenceMap({{{0, 0, 1}, 0.0}, {{1, 1, 1}, 0.0}})
.MakeForwardEquivalences());
EXPECT_EQ(std::vector<Equivalence>({{0, 1, 1}, {1, 0, 1}}),
EquivalenceMap({{{1, 0, 1}, 0.0}, {{0, 1, 1}, 0.0}})
.MakeForwardEquivalences());
EXPECT_EQ(
std::vector<Equivalence>({{0, 2, 1}, {1, 0, 1}, {4, 1, 1}}),
EquivalenceMap({{{1, 0, 1}, 0.0}, {{4, 1, 1}, 0.0}, {{0, 2, 1}, 0.0}})
.MakeForwardEquivalences());
}
} // namespace zucchini
......@@ -10,6 +10,7 @@
#include "base/logging.h"
#include "chrome/installer/zucchini/algorithm.h"
#include "chrome/installer/zucchini/equivalence_map.h"
namespace zucchini {
......@@ -22,8 +23,26 @@ TargetPool::TargetPool(std::vector<offset_t>&& targets) {
}
TargetPool::TargetPool(TargetPool&&) = default;
TargetPool::TargetPool(const TargetPool&) = default;
TargetPool::~TargetPool() = default;
void TargetPool::InsertTargets(const std::vector<offset_t>& targets) {
std::copy(targets.begin(), targets.end(), std::back_inserter(targets_));
SortAndUniquify(&targets_);
}
void TargetPool::InsertTargets(TargetSource* targets) {
for (auto target = targets->GetNext(); target.has_value();
target = targets->GetNext()) {
targets_.push_back(*target);
}
// InsertTargets() can be called many times (number of reference types for the
// pool) in succession. Calling SortAndUniquify() every time enables deduping
// to occur more often. This prioritizes peak memory reduction over running
// time.
SortAndUniquify(&targets_);
}
void TargetPool::InsertTargets(const std::vector<Reference>& references) {
// This can be called many times, so it's better to let std::back_inserter()
// manage |targets_| resize, instead of manually reserving space.
......@@ -41,10 +60,25 @@ void TargetPool::InsertTargets(ReferenceReader&& references) {
SortAndUniquify(&targets_);
}
offset_t TargetPool::KeyForOffset(offset_t offset) const {
key_t TargetPool::KeyForOffset(offset_t offset) const {
auto pos = std::lower_bound(targets_.begin(), targets_.end(), offset);
DCHECK(pos != targets_.end() && *pos == offset);
return static_cast<offset_t>(pos - targets_.begin());
}
key_t TargetPool::KeyForNearestOffset(offset_t offset) const {
auto pos = std::lower_bound(targets_.begin(), targets_.end(), offset);
if (pos != targets_.begin()) {
// If distances are equal, prefer lower key.
if (pos == targets_.end() || *pos - offset >= offset - pos[-1])
--pos;
}
return static_cast<offset_t>(pos - targets_.begin());
}
void TargetPool::FilterAndProject(const OffsetMapper& offset_mapper) {
offset_mapper.ForwardProjectAll(&targets_);
std::sort(targets_.begin(), targets_.end());
}
} // namespace zucchini
......@@ -10,9 +10,13 @@
#include <vector>
#include "chrome/installer/zucchini/image_utils.h"
#include "chrome/installer/zucchini/patch_reader.h"
namespace zucchini {
class OffsetMapper;
class TargetSource;
// Ordered container of distinct targets that have the same semantics, along
// with a list of associated reference types, only used during patch generation.
class TargetPool {
......@@ -22,33 +26,46 @@ class TargetPool {
TargetPool();
// Initializes the object with given sorted and unique |targets|.
explicit TargetPool(std::vector<offset_t>&& targets);
TargetPool(const TargetPool&) = delete;
TargetPool(TargetPool&&);
TargetPool(const TargetPool&);
~TargetPool();
// The following functions insert each new target from |references|. This
// invalidates all previous key lookups.
// Insert new targets from various sources. These invalidate all previous key
// lookups.
// - From a list of targets, useful for adding extra targets in Zucchini-gen:
void InsertTargets(const std::vector<offset_t>& targets);
// - From TargetSource, useful for adding extra targets in Zucchini-apply:
void InsertTargets(TargetSource* targets);
// - From list of References, useful for listing targets in Zucchini-gen:
void InsertTargets(const std::vector<Reference>& references);
// - From ReferenceReader, useful for listing targets in Zucchini-apply:
void InsertTargets(ReferenceReader&& references);
// Adds |type| as a reference type associated with the pool of targets.
void AddType(TypeTag type) { types_.push_back(type); }
// Returns a canonical key associated with |offset|.
// Returns a canonical key associated with a valid target at |offset|.
key_t KeyForOffset(offset_t offset) const;
// Returns a canonical key associated with the target nearest to |offset|.
key_t KeyForNearestOffset(offset_t offset) const;
// Returns the target for a |key|, which is assumed to be valid and held by
// this class.
offset_t OffsetForKey(key_t key) const { return targets_[key]; }
// Uses |offset_mapper| to transform "old" |targets_| to "new" |targets_|,
// resulting in sorted and unique targets.
void FilterAndProject(const OffsetMapper& offset_mapper);
// Accessors for testing.
const std::vector<offset_t>& targets() const { return targets_; }
const std::vector<TypeTag>& types() const { return types_; }
// Returns the number of targets.
size_t size() const { return targets_.size(); }
const_iterator begin() const;
const_iterator end() const;
const_iterator begin() const { return targets_.cbegin(); }
const_iterator end() const { return targets_.cend(); }
private:
std::vector<TypeTag> types_; // Enumerates type_tag for this pool.
......
......@@ -4,6 +4,7 @@
#include "chrome/installer/zucchini/target_pool.h"
#include <cmath>
#include <utility>
#include <vector>
......@@ -35,21 +36,28 @@ TEST(TargetPoolTest, InsertTargetsFromReferences) {
}
TEST(TargetPoolTest, KeyOffset) {
auto test_key_offset = [](OffsetVector&& targets) {
auto test_key_offset = [](const std::string& nearest_offsets_key,
OffsetVector&& targets) {
TargetPool target_pool(std::move(targets));
for (offset_t offset : target_pool.targets()) {
offset_t key = target_pool.KeyForOffset(offset);
EXPECT_LT(key, target_pool.size());
EXPECT_EQ(offset, target_pool.OffsetForKey(key));
}
for (offset_t offset = 0; offset < nearest_offsets_key.size(); ++offset) {
key_t key = target_pool.KeyForNearestOffset(offset);
EXPECT_EQ(key, static_cast<key_t>(nearest_offsets_key[offset] - '0'));
}
};
test_key_offset({0});
test_key_offset({1});
test_key_offset({0, 1});
test_key_offset({0, 2});
test_key_offset({1, 2});
test_key_offset({1, 3});
test_key_offset({1, 3, 7, 9, 13});
test_key_offset("0000000000000000", {});
test_key_offset("0000000000000000", {0});
test_key_offset("0000000000000000", {1});
test_key_offset("0111111111111111", {0, 1});
test_key_offset("0011111111111111", {0, 2});
test_key_offset("0011111111111111", {1, 2});
test_key_offset("0001111111111111", {1, 3});
test_key_offset("0001112223334444", {1, 3, 7, 9, 13});
test_key_offset("0000011112223333", {1, 7, 9, 13});
}
} // namespace zucchini
......@@ -12,62 +12,11 @@
#include "base/logging.h"
#include "chrome/installer/zucchini/disassembler.h"
#include "chrome/installer/zucchini/element_detection.h"
#include "chrome/installer/zucchini/label_manager.h"
#include "chrome/installer/zucchini/equivalence_map.h"
#include "chrome/installer/zucchini/image_index.h"
namespace zucchini {
std::vector<offset_t> MakeNewTargetsFromPatch(
const std::vector<offset_t>& old_targets,
const EquivalenceSource& equivalence_source) {
// |old_targets| is sorted. This enables binary search usage below.
std::vector<offset_t> new_targets(old_targets.size(), kUnusedIndex);
// First pass: For each equivalence, attempt to claim target in |new_targets|
// associated with each target in the "old" interval of the equivalence. "Old"
// interval collisions may occur.
{
EquivalenceSource tmp_equiv_source(equivalence_source);
for (auto equivalence = tmp_equiv_source.GetNext(); equivalence.has_value();
equivalence = tmp_equiv_source.GetNext()) {
auto it = std::lower_bound(old_targets.begin(), old_targets.end(),
equivalence->src_offset);
offset_t idx = it - old_targets.begin();
for (; it != old_targets.end() && *it < equivalence->src_end();
++it, ++idx) {
// To resolve collisions, |new_targets[idx]| is temporarily assigned the
// marked maximal length of competing equivalences that contain
// |new_tagets[idx]|. As a result, longer equivalences are favored.
if (new_targets[idx] == kUnusedIndex ||
UnmarkIndex(new_targets[idx]) < equivalence->length) {
new_targets[idx] = MarkIndex(equivalence->length);
}
}
}
}
// Second pass: Assign each claimed target in |new_targets|.
{
EquivalenceSource tmp_equiv_source(equivalence_source);
for (auto equivalence = tmp_equiv_source.GetNext(); equivalence.has_value();
equivalence = tmp_equiv_source.GetNext()) {
auto it = std::lower_bound(old_targets.begin(), old_targets.end(),
equivalence->src_offset);
offset_t idx = it - old_targets.begin();
for (; it != old_targets.end() && *it < equivalence->src_end();
++it, ++idx) {
// First |equivalence| (ordered by |Equivalence::dst_offset|) with
// designed length claims the target. This is how ties are resolved.
if (IsMarked(new_targets[idx]) &&
UnmarkIndex(new_targets[idx]) == equivalence->length) {
new_targets[idx] =
*it - equivalence->src_offset + equivalence->dst_offset;
}
}
}
}
return new_targets;
}
bool ApplyEquivalenceAndExtraData(ConstBufferView old_image,
const PatchElementReader& patch_reader,
MutableBufferView new_image) {
......@@ -157,28 +106,22 @@ bool ApplyReferencesCorrection(ExecutableType exe_type,
for (const auto& ref_group : old_disasm->MakeReferenceGroups())
pool_groups[ref_group.pool_tag()].push_back(ref_group);
OffsetMapper offset_mapper(patch.GetEquivalenceSource());
std::vector<ReferenceGroup> new_groups = new_disasm->MakeReferenceGroups();
for (const auto& pool_and_sub_groups : pool_groups) {
PoolTag pool_tag = pool_and_sub_groups.first;
const std::vector<ReferenceGroup>& sub_groups = pool_and_sub_groups.second;
// Load all old targets for the pool.
OrderedLabelManager old_label_manager;
TargetPool targets;
// Load "old" targets, then filter and map them to "new" targets.
for (ReferenceGroup group : sub_groups)
old_label_manager.InsertTargets(
std::move(*group.GetReader(old_disasm.get())));
// Generate estimated new targets for the pool.
std::vector<offset_t> new_targets = MakeNewTargetsFromPatch(
old_label_manager.Labels(), patch.GetEquivalenceSource());
UnorderedLabelManager new_label_manager;
new_label_manager.Init(std::move(new_targets));
targets.InsertTargets(std::move(*group.GetReader(old_disasm.get())));
targets.FilterAndProject(offset_mapper);
// Load extra targets from patch.
TargetSource target_source = patch.GetExtraTargetSource(pool_tag);
for (auto offset = target_source.GetNext(); offset.has_value();
offset = target_source.GetNext())
new_label_manager.InsertNewOffset(offset.value());
targets.InsertTargets(&target_source);
if (!target_source.Done()) {
LOG(ERROR) << "Found trailing extra_targets";
return false;
......@@ -199,13 +142,15 @@ bool ApplyReferencesCorrection(ExecutableType exe_type,
ref = ref_gen->GetNext()) {
DCHECK_GE(ref->location, equivalence->src_offset);
DCHECK_LT(ref->location, equivalence->src_end());
offset_t index = old_label_manager.IndexOfOffset(ref->target);
offset_t projected_target = offset_mapper.ForwardProject(ref->target);
offset_t expected_key = targets.KeyForNearestOffset(projected_target);
auto delta = ref_delta_source.GetNext();
if (!delta.has_value()) {
LOG(ERROR) << "Error reading reference_delta";
return false;
}
ref->target = new_label_manager.OffsetOfIndex(index + delta.value());
ref->target = targets.OffsetForKey(expected_key + delta.value());
ref->location =
ref->location - equivalence->src_offset + equivalence->dst_offset;
ref_writer->PutNext(*ref);
......
......@@ -13,14 +13,6 @@
namespace zucchini {
// Projects targets in |old_targets| to a list of new targets using equivalences
// in |equivalence_source|. Targets that cannot be projected have offset
// assigned as |kUnusedIndex|. Returns the list of new targets in a new vector.
// |old_targets| must be sorted in ascending order
std::vector<offset_t> MakeNewTargetsFromPatch(
const std::vector<offset_t>& old_targets,
const EquivalenceSource& equivalence_source);
// Reads equivalences from |patch_reader| to form preliminary |new_image|,
// copying regions from |old_image| and writing extra data from |patch_reader|.
bool ApplyEquivalenceAndExtraData(ConstBufferView old_image,
......
......@@ -13,68 +13,10 @@ namespace zucchini {
namespace {
constexpr auto BAD = kUnusedIndex;
using OffsetVector = std::vector<offset_t>;
} // namespace
// Helper function wrapping MakeNewTargetsFromPatch(). |old_targets| must be
// sorted in ascending order and |equivalence| must be sorted in ascending order
// of |Equivalence::dst_offset|.
std::vector<offset_t> MakeNewTargetsFromPatchTest(
const OffsetVector& old_targets,
const std::vector<Equivalence>& equivalences) {
// Serialize |equivalences| to patch format, and read it back as
// EquivalenceSource.
EquivalenceSink equivalence_sink;
for (const Equivalence& equivalence : equivalences)
equivalence_sink.PutNext(equivalence);
std::vector<uint8_t> buffer(equivalence_sink.SerializedSize());
BufferSink sink(buffer.data(), buffer.size());
equivalence_sink.SerializeInto(&sink);
BufferSource source(buffer.data(), buffer.size());
EquivalenceSource equivalence_source;
equivalence_source.Initialize(&source);
return MakeNewTargetsFromPatch(old_targets, equivalence_source);
}
TEST(ZucchiniApplyTest, MakeNewTargetsFromPatch) {
// Note that |old_offsets| provided are sorted, and |equivalences| provided
// are sorted by |dst_offset|.
EXPECT_EQ(OffsetVector(), MakeNewTargetsFromPatchTest({}, {}));
EXPECT_EQ(OffsetVector({BAD, BAD}), MakeNewTargetsFromPatchTest({0, 1}, {}));
EXPECT_EQ(OffsetVector({0, 1, BAD}),
MakeNewTargetsFromPatchTest({0, 1, 2}, {{0, 0, 2}}));
EXPECT_EQ(OffsetVector({1, 2, BAD}),
MakeNewTargetsFromPatchTest({0, 1, 2}, {{0, 1, 2}}));
EXPECT_EQ(
OffsetVector({1, BAD, 4, 5, 6, BAD}),
MakeNewTargetsFromPatchTest({0, 1, 2, 3, 4, 5}, {{0, 1, 1}, {2, 4, 3}}));
EXPECT_EQ(
OffsetVector({3, BAD, 0, 1, 2, BAD}),
MakeNewTargetsFromPatchTest({0, 1, 2, 3, 4, 5}, {{2, 0, 3}, {0, 3, 1}}));
// Overlap.
EXPECT_EQ(
OffsetVector({1, 2, 3, BAD, BAD}),
MakeNewTargetsFromPatchTest({0, 1, 2, 3, 4}, {{0, 1, 3}, {1, 4, 2}}));
EXPECT_EQ(
OffsetVector({1, 4, 5, 6, BAD}),
MakeNewTargetsFromPatchTest({0, 1, 2, 3, 4}, {{0, 1, 2}, {1, 4, 3}}));
EXPECT_EQ(
OffsetVector({1, 2, 5, BAD, BAD}),
MakeNewTargetsFromPatchTest({0, 1, 2, 3, 4}, {{0, 1, 2}, {1, 4, 2}}));
// Jump.
EXPECT_EQ(OffsetVector({5, BAD, 6}),
MakeNewTargetsFromPatchTest({10, 13, 15},
{{0, 1, 2}, {9, 4, 2}, {15, 6, 2}}));
}
// TODO(huangs): Add more tests.
} // namespace zucchini
......@@ -37,66 +37,13 @@ constexpr size_t kNumIterations = 2;
} // namespace
std::vector<offset_t> MakeNewTargetsFromEquivalenceMap(
const std::vector<offset_t>& old_targets,
const std::vector<Equivalence>& equivalences) {
auto current_equivalence = equivalences.begin();
std::vector<offset_t> new_targets;
new_targets.reserve(old_targets.size());
for (offset_t src : old_targets) {
while (current_equivalence != equivalences.end() &&
current_equivalence->src_end() <= src)
++current_equivalence;
if (current_equivalence != equivalences.end() &&
current_equivalence->src_offset <= src) {
// Select the longest equivalence that contains |src|. In case of a tie,
// prefer equivalence with minimal |dst_offset|.
auto best_equivalence = current_equivalence;
for (auto next_equivalence = current_equivalence;
next_equivalence != equivalences.end() &&
src >= next_equivalence->src_offset;
++next_equivalence) {
if (next_equivalence->length > best_equivalence->length ||
(next_equivalence->length == best_equivalence->length &&
next_equivalence->dst_offset < best_equivalence->dst_offset)) {
// If an |next_equivalence| is longer or equal to |best_equivalence|,
// it can be show that |src < next_equivalence->src_end()| i.e., |src|
// is inside |next_equivalence|.
DCHECK_LT(src, next_equivalence->src_end());
best_equivalence = next_equivalence;
}
}
new_targets.push_back(src - best_equivalence->src_offset +
best_equivalence->dst_offset);
} else {
new_targets.push_back(kUnusedIndex);
}
}
return new_targets;
}
std::vector<offset_t> FindExtraTargets(
const ReferenceSet& new_references,
const UnorderedLabelManager& new_label_manager,
const EquivalenceMap& equivalence_map) {
auto equivalence = equivalence_map.begin();
std::vector<offset_t> targets;
for (const IndirectReference& ref : new_references) {
while (equivalence != equivalence_map.end() &&
equivalence->eq.dst_end() <= ref.location) {
++equivalence;
}
if (equivalence == equivalence_map.end())
break;
if (ref.location >= equivalence->eq.dst_offset) {
offset_t target_offset =
new_references.target_pool().OffsetForKey(ref.target_key);
if (!new_label_manager.ContainsOffset(target_offset))
targets.push_back(target_offset);
}
}
return targets;
std::vector<offset_t> FindExtraTargets(const TargetPool& projected_old_targets,
const TargetPool& new_targets) {
std::vector<offset_t> extra_targets;
std::set_difference(
new_targets.begin(), new_targets.end(), projected_old_targets.begin(),
projected_old_targets.end(), std::back_inserter(extra_targets));
return extra_targets;
}
EquivalenceMap CreateEquivalenceMap(const ImageIndex& old_image_index,
......@@ -202,10 +149,11 @@ bool GenerateRawDelta(ConstBufferView old_image,
bool GenerateReferencesDelta(const ReferenceSet& src_refs,
const ReferenceSet& dst_refs,
const UnorderedLabelManager& new_label_manager,
const TargetPool& projected_target_pool,
const OffsetMapper& offset_mapper,
const EquivalenceMap& equivalence_map,
ReferenceDeltaSink* reference_delta_sink) {
offset_t ref_width = src_refs.width();
size_t ref_width = src_refs.width();
auto dst_ref = dst_refs.begin();
// For each equivalence, for each covered |dst_ref| and the matching
......@@ -236,11 +184,17 @@ bool GenerateReferencesDelta(const ReferenceSet& src_refs,
// Local offset of |src_ref| should match that of |dst_ref|.
DCHECK_EQ(src_ref->location - equiv.src_offset,
dst_ref->location - equiv.dst_offset);
offset_t dst_index = new_label_manager.IndexOfOffset(
dst_refs.target_pool().OffsetForKey(dst_ref->target_key));
offset_t old_offset =
src_refs.target_pool().OffsetForKey(src_ref->target_key);
offset_t new_estimated_offset = offset_mapper.ForwardProject(old_offset);
offset_t new_estimated_key =
projected_target_pool.KeyForNearestOffset(new_estimated_offset);
offset_t new_offset =
dst_refs.target_pool().OffsetForKey(dst_ref->target_key);
offset_t new_key = projected_target_pool.KeyForOffset(new_offset);
reference_delta_sink->PutNext(
static_cast<int32_t>(dst_index - src_ref->target_key));
static_cast<int32_t>(new_key - new_estimated_key));
}
if (dst_ref == dst_refs.end())
break; // Done.
......@@ -303,48 +257,33 @@ bool GenerateExecutableElement(ExecutableType exe_type,
return false;
}
DCHECK_EQ(old_image_index.PoolCount(), new_image_index.PoolCount());
size_t pool_count = old_image_index.PoolCount();
EquivalenceMap equivalences =
CreateEquivalenceMap(old_image_index, new_image_index);
std::vector<Equivalence> forward_equivalences =
equivalences.MakeForwardEquivalences();
std::vector<UnorderedLabelManager> new_label_managers(pool_count);
for (const auto& old_pool_tag_and_targets : old_image_index.target_pools()) {
PoolTag pool_tag = old_pool_tag_and_targets.first;
const auto& new_target_pool = new_image_index.pool(pool_tag);
// Label Projection to initialize |new_label_manager|.
std::vector<offset_t> new_labels = MakeNewTargetsFromEquivalenceMap(
old_pool_tag_and_targets.second.targets(), forward_equivalences);
new_label_managers[pool_tag.value()].Init(std::move(new_labels));
// Find extra targets in |new_image_index|, emit into patch, merge them to
// |new_labelsl_manager|, and update new references.
OrderedLabelManager extra_label_manager;
for (TypeTag type : new_target_pool.types()) {
extra_label_manager.InsertOffsets(
FindExtraTargets(new_image_index.refs(type),
new_label_managers[pool_tag.value()], equivalences));
}
if (!GenerateExtraTargets(extra_label_manager.Labels(), pool_tag,
patch_writer)) {
return false;
}
for (offset_t offset : extra_label_manager.Labels())
new_label_managers[pool_tag.value()].InsertNewOffset(offset);
}
OffsetMapper offset_mapper(equivalences);
ReferenceDeltaSink reference_delta_sink;
for (const auto& old_refs : old_image_index.reference_sets()) {
const auto& new_refs = new_image_index.refs(old_refs.first);
if (!GenerateReferencesDelta(
old_refs.second, new_refs,
new_label_managers[new_refs.pool_tag().value()], equivalences,
&reference_delta_sink)) {
for (const auto& old_targets : old_image_index.target_pools()) {
PoolTag pool_tag = old_targets.first;
TargetPool projected_old_targets = old_targets.second;
projected_old_targets.FilterAndProject(offset_mapper);
std::vector<offset_t> extra_target =
FindExtraTargets(projected_old_targets, new_image_index.pool(pool_tag));
projected_old_targets.InsertTargets(extra_target);
if (!GenerateExtraTargets(extra_target, pool_tag, patch_writer))
return false;
for (TypeTag type_tag : old_targets.second.types()) {
if (!GenerateReferencesDelta(old_image_index.refs(type_tag),
new_image_index.refs(type_tag),
projected_old_targets, offset_mapper,
equivalences, &reference_delta_sink)) {
return false;
}
}
}
patch_writer->SetReferenceDeltaSink(std::move(reference_delta_sink));
return GenerateEquivalencesAndExtraData(new_image, equivalences,
patch_writer) &&
GenerateRawDelta(old_image, new_image, equivalences, new_image_index,
......
......@@ -15,29 +15,17 @@
namespace zucchini {
class EquivalenceMap;
class OffsetMapper;
class ImageIndex;
class PatchElementWriter;
class ReferenceDeltaSink;
class ReferenceSet;
class UnorderedLabelManager;
class TargetPool;
// Projects targets in |old_targets| to a list of new targets using
// |equivalences|. Targets that cannot be projected have offset assigned as
// |kUnusedIndex|. Returns the list of new targets in a new vector.
// |old_targets| must be sorted in ascending order and |equivalence| must be
// sorted in ascending order of |Equivalence::src_offset|.
std::vector<offset_t> MakeNewTargetsFromEquivalenceMap(
const std::vector<offset_t>& old_targets,
const std::vector<Equivalence>& equivalences);
// Extract references in |new_references| that have the following properties:
// - The location is found in |equivalences| (dst).
// - The target (key) is absent in |new_label_manager|.
// The targets of the extracted references are returned in a new vector.
std::vector<offset_t> FindExtraTargets(
const ReferenceSet& new_references,
const UnorderedLabelManager& new_label_manager,
const EquivalenceMap& equivalence_map);
// Extract all targets in |new_targets| with no associated target in
// |projected_old_targets| and returns these targets in a new vector.
std::vector<offset_t> FindExtraTargets(const TargetPool& projected_old_targets,
const TargetPool& new_targets);
// Creates an EquivalenceMap from "old" image to "new" image and returns the
// result. The params |*_image_index|:
......@@ -63,11 +51,12 @@ bool GenerateRawDelta(ConstBufferView old_image,
PatchElementWriter* patch_writer);
// Writes reference delta between references from |old_refs| and from
// |new_refs| to |patch_writer|. |new_label_manager| contains projected
// labels from old to new image for references pool associated with |new_refs|
bool GenerateReferencesDelta(const ReferenceSet& old_refs,
const ReferenceSet& new_refs,
const UnorderedLabelManager& new_label_manager,
// |new_refs| to |patch_writer|. |projected_target_pool| contains projected
// targets from old to new image for references pool associated with |new_refs|.
bool GenerateReferencesDelta(const ReferenceSet& src_refs,
const ReferenceSet& dst_refs,
const TargetPool& projected_target_pool,
const OffsetMapper& offset_mapper,
const EquivalenceMap& equivalence_map,
ReferenceDeltaSink* reference_delta_sink);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment