403 lines
16 KiB
C++
403 lines
16 KiB
C++
// Copyright 2018 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "components/zucchini/disassembler_ztf.h"
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "base/cxx17_backports.h"
|
|
#include "base/strings/string_piece.h"
|
|
#include "components/zucchini/buffer_view.h"
|
|
#include "components/zucchini/element_detection.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace zucchini {
|
|
|
|
namespace {
|
|
|
|
constexpr char kNormalText[] = R"(ZTxt
|
|
Hello World!
|
|
This is an example of an absolute reference <<1,1>>
|
|
And {-01,+05} is an example of a relative ref
|
|
txTZ
|
|
TRAILING DATA)";
|
|
// -1 to exclude null byte.
|
|
constexpr size_t kNormalTextExtraBytes = base::size("TRAILING DATA") - 1;
|
|
|
|
constexpr char kOutOfBoundsText[] = R"(ZTxt<1,1>
|
|
Hello World!
|
|
This is an example of an OOB absolute reference <890,605>
|
|
And {-050,+100} is an example of an OOB relative ref.
|
|
but [+00,+10] is valid at least. As is (1,5).
|
|
<1, 6> and { ,1} aren't nor is {4,5]
|
|
{7,6}<1,1><2,3>{+00,+00}{004,100}[+00,+60][+000,-100]<-000,-035>(-00,-00)txTZ
|
|
)";
|
|
|
|
// Converts a raw string into data.
|
|
std::vector<uint8_t> StrToData(base::StringPiece s) {
|
|
return std::vector<uint8_t>(s.begin(), s.end());
|
|
}
|
|
|
|
// Compare if |a.location < b.location| as references have unique locations.
|
|
struct ReferenceCompare {
|
|
bool operator()(const Reference& a, const Reference& b) const {
|
|
return a.location < b.location;
|
|
}
|
|
};
|
|
|
|
using ReferenceKey =
|
|
std::pair<DisassemblerZtf::ReferencePool, DisassemblerZtf::ReferenceType>;
|
|
using ReferenceSets =
|
|
std::map<ReferenceKey, std::set<Reference, ReferenceCompare>>;
|
|
|
|
// Write references in |refs_to_write| to |image|. Also validate the
|
|
// disassembler parses |image| such that it is of |expected_size|.
|
|
void WriteReferences(MutableBufferView image,
|
|
size_t expected_size,
|
|
const ReferenceSets& refs_to_write) {
|
|
EXPECT_TRUE(DisassemblerZtf::QuickDetect(image));
|
|
std::unique_ptr<DisassemblerZtf> dis =
|
|
Disassembler::Make<DisassemblerZtf>(image);
|
|
EXPECT_TRUE(dis);
|
|
EXPECT_EQ(expected_size, dis->size());
|
|
image.shrink(dis->size());
|
|
auto reference_groups = dis->MakeReferenceGroups();
|
|
for (const auto& group : reference_groups) {
|
|
auto writer = group.GetWriter(image, dis.get());
|
|
ReferenceKey key = {
|
|
static_cast<DisassemblerZtf::ReferencePool>(group.pool_tag().value()),
|
|
static_cast<DisassemblerZtf::ReferenceType>(group.type_tag().value())};
|
|
if (!refs_to_write.count(key))
|
|
continue;
|
|
for (const auto& ref : refs_to_write.at(key))
|
|
writer->PutNext(ref);
|
|
}
|
|
}
|
|
|
|
// Read references in |refs_to_read| from |image|. Once found
|
|
// the elements are removed from |refs_to_read|. Also validate the
|
|
// disassembler parses |image| such that it is of |expected_size|.
|
|
void ReadReferences(ConstBufferView image,
|
|
size_t expected_size,
|
|
ReferenceSets* refs_to_read) {
|
|
EXPECT_TRUE(DisassemblerZtf::QuickDetect(image));
|
|
std::unique_ptr<DisassemblerZtf> dis =
|
|
Disassembler::Make<DisassemblerZtf>(image);
|
|
EXPECT_TRUE(dis);
|
|
EXPECT_EQ(expected_size, dis->size());
|
|
auto reference_groups = dis->MakeReferenceGroups();
|
|
for (const auto& group : reference_groups) {
|
|
auto reader = group.GetReader(dis.get());
|
|
ReferenceKey key = {
|
|
static_cast<DisassemblerZtf::ReferencePool>(group.pool_tag().value()),
|
|
static_cast<DisassemblerZtf::ReferenceType>(group.type_tag().value())};
|
|
if (!refs_to_read->count(key)) {
|
|
// No elements of this pool/type pair are expected so assert that none are
|
|
// found.
|
|
auto ref = reader->GetNext();
|
|
EXPECT_FALSE(ref.has_value());
|
|
continue;
|
|
}
|
|
// For each reference remove it from the set if it exists, error if
|
|
// unexpected references are found.
|
|
for (auto ref = reader->GetNext(); ref.has_value();
|
|
ref = reader->GetNext()) {
|
|
EXPECT_EQ(1UL, refs_to_read->at(key).erase(ref.value()));
|
|
}
|
|
EXPECT_EQ(0U, refs_to_read->at(key).size());
|
|
}
|
|
}
|
|
|
|
void TestTranslation(const ZtfTranslator& translator,
|
|
offset_t expected_location,
|
|
ztf::LineCol lc) {
|
|
// Check the lc is translated to the expected location.
|
|
EXPECT_EQ(expected_location, translator.LineColToOffset(lc));
|
|
auto new_lc = translator.OffsetToLineCol(expected_location);
|
|
if (expected_location == kInvalidOffset) {
|
|
EXPECT_FALSE(translator.IsValid(lc));
|
|
EXPECT_FALSE(new_lc.has_value());
|
|
} else {
|
|
EXPECT_TRUE(translator.IsValid(lc));
|
|
// Check that the reverse is true. |ztf::LineCol{0, 0}| is a sentinel and
|
|
// should never be valid.
|
|
EXPECT_EQ(lc.line, new_lc->line);
|
|
EXPECT_EQ(lc.col, new_lc->col);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
size_t CountDistinct(const std::vector<T>& v) {
|
|
return std::set<T>(v.begin(), v.end()).size();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(ZtfTranslatorTest, Translate) {
|
|
ztf::dim_t kMaxVal = INT16_MAX;
|
|
ztf::dim_t kMinVal = INT16_MIN;
|
|
|
|
const std::vector<uint8_t> text(StrToData(kOutOfBoundsText));
|
|
ConstBufferView image(text.data(), text.size());
|
|
ZtfTranslator translator;
|
|
EXPECT_TRUE(translator.Init(image));
|
|
|
|
// Absolute Translations:
|
|
|
|
// Check a bunch of invalid locations.
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{50, 60});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{0, 0});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{1, 0});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{0, 1});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{0, 1});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{1, -1});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{-1, 1});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{-1, -1});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{1, kMaxVal});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{kMaxVal, 1});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{1, kMinVal});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{kMinVal, 1});
|
|
|
|
// Check the start of the file.
|
|
TestTranslation(translator, 0, ztf::LineCol{1, 1});
|
|
TestTranslation(translator, 1, ztf::LineCol{1, 2});
|
|
|
|
// Check the boundary around a newline.
|
|
TestTranslation(translator, 9, ztf::LineCol{1, 10});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{1, 11});
|
|
TestTranslation(translator, 10, ztf::LineCol{2, 1});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{2, 0});
|
|
|
|
// Check the end of the file.
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{8, 1});
|
|
TestTranslation(translator, kInvalidOffset, ztf::LineCol{7, 79});
|
|
// Need to subtract to account for the newline.
|
|
TestTranslation(translator, text.size() - 1, ztf::LineCol{7, 78});
|
|
TestTranslation(translator, text.size() - 2, ztf::LineCol{7, 77});
|
|
|
|
// Delta Validity
|
|
// - Reminder! 0 -> 1:1
|
|
|
|
// Common possible edge cases.
|
|
EXPECT_TRUE(translator.IsValid(0, ztf::DeltaLineCol{0, 0}));
|
|
EXPECT_TRUE(translator.IsValid(0, ztf::DeltaLineCol{0, 1}));
|
|
EXPECT_TRUE(translator.IsValid(0, ztf::DeltaLineCol{1, 0}));
|
|
EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{-1, -1}));
|
|
EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{-1, 0}));
|
|
EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{0, -1}));
|
|
EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{0, -1}));
|
|
EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{0, kMaxVal}));
|
|
EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{kMaxVal, 0}));
|
|
EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{0, kMinVal}));
|
|
EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{kMinVal, 0}));
|
|
EXPECT_FALSE(translator.IsValid(233, ztf::DeltaLineCol{0, kMaxVal}));
|
|
EXPECT_FALSE(translator.IsValid(233, ztf::DeltaLineCol{kMaxVal, 0}));
|
|
EXPECT_FALSE(translator.IsValid(233, ztf::DeltaLineCol{kMaxVal, kMaxVal}));
|
|
|
|
// Newline area.
|
|
EXPECT_TRUE(translator.IsValid(0, ztf::DeltaLineCol{0, 9}));
|
|
EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{0, 10}));
|
|
EXPECT_FALSE(translator.IsValid(9, ztf::DeltaLineCol{0, 1}));
|
|
EXPECT_FALSE(translator.IsValid(9, ztf::DeltaLineCol{-1, 0}));
|
|
EXPECT_FALSE(translator.IsValid(9, ztf::DeltaLineCol{1, -10}));
|
|
EXPECT_TRUE(translator.IsValid(9, ztf::DeltaLineCol{1, -9}));
|
|
|
|
// End of file.
|
|
EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{7, 78}));
|
|
EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{7, 77}));
|
|
EXPECT_FALSE(translator.IsValid(0, ztf::DeltaLineCol{6, 78}));
|
|
EXPECT_TRUE(translator.IsValid(0, ztf::DeltaLineCol{6, 77}));
|
|
EXPECT_FALSE(translator.IsValid(text.size() - 1, ztf::DeltaLineCol{0, 1}));
|
|
EXPECT_FALSE(translator.IsValid(text.size() - 1, ztf::DeltaLineCol{1, 0}));
|
|
EXPECT_TRUE(translator.IsValid(text.size() - 2, ztf::DeltaLineCol{0, 1}));
|
|
EXPECT_FALSE(translator.IsValid(text.size() - 2, ztf::DeltaLineCol{1, 0}));
|
|
}
|
|
|
|
// Ensures that ReferenceGroups from DisassemblerZtf::MakeReferenceGroups()
|
|
// cover each non-sentinel element in ReferenceType in order, exactly once. Also
|
|
// ensures that the ReferenceType elements are grouped by ReferencePool, and
|
|
// listed in increasing order.
|
|
TEST(DisassemblerZtfTest, ReferenceGroups) {
|
|
std::vector<uint32_t> pool_list;
|
|
std::vector<uint32_t> type_list;
|
|
DisassemblerZtf dis;
|
|
for (ReferenceGroup group : dis.MakeReferenceGroups()) {
|
|
pool_list.push_back(static_cast<uint32_t>(group.pool_tag().value()));
|
|
type_list.push_back(static_cast<uint32_t>(group.type_tag().value()));
|
|
}
|
|
|
|
// Check ReferenceByte coverage.
|
|
constexpr size_t kNumTypes = DisassemblerZtf::kNumTypes;
|
|
EXPECT_EQ(kNumTypes, type_list.size());
|
|
EXPECT_EQ(kNumTypes, CountDistinct(type_list));
|
|
EXPECT_TRUE(std::is_sorted(type_list.begin(), type_list.end()));
|
|
|
|
// Check that ReferenceType elements are grouped by ReferencePool. Note that
|
|
// repeats can occur, and pools can be skipped.
|
|
EXPECT_TRUE(std::is_sorted(pool_list.begin(), pool_list.end()));
|
|
}
|
|
|
|
TEST(DisassemblerZtfTest, BadMagic) {
|
|
// Test a case where there is no header so a disassembler cannot be created.
|
|
{
|
|
const std::vector<uint8_t> text(StrToData("foobarbaz bazbarfoo"));
|
|
ConstBufferView image(text.data(), text.size());
|
|
EXPECT_FALSE(DisassemblerZtf::QuickDetect(image));
|
|
EXPECT_FALSE(Disassembler::Make<DisassemblerZtf>(image));
|
|
}
|
|
// Test a case where there is no footer so a disassembler cannot be created.
|
|
{
|
|
const std::vector<uint8_t> text(StrToData("ZTxtfoobarbaz bazbarfootxTZ"));
|
|
ConstBufferView image(text.data(), text.size());
|
|
EXPECT_TRUE(DisassemblerZtf::QuickDetect(image));
|
|
EXPECT_FALSE(Disassembler::Make<DisassemblerZtf>(image));
|
|
}
|
|
// Test when the header is too short
|
|
{
|
|
const std::vector<uint8_t> text(StrToData("ZTxtxTZ\n"));
|
|
ConstBufferView image(text.data(), text.size());
|
|
EXPECT_FALSE(DisassemblerZtf::QuickDetect(image));
|
|
EXPECT_FALSE(Disassembler::Make<DisassemblerZtf>(image));
|
|
}
|
|
}
|
|
|
|
TEST(DisassemblerZtfTest, ZtfSizeBound) {
|
|
{
|
|
std::vector<uint8_t> text(StrToData("ZTxt"));
|
|
std::fill_n(std::back_inserter(text), ztf::kMaxDimValue - 2, '\n');
|
|
text.insert(text.end(), {'t', 'x', 'T', 'Z', '\n'});
|
|
ConstBufferView image(text.data(), text.size());
|
|
EXPECT_TRUE(DisassemblerZtf::QuickDetect(image));
|
|
EXPECT_TRUE(Disassembler::Make<DisassemblerZtf>(image));
|
|
}
|
|
{
|
|
std::vector<uint8_t> text(StrToData("ZTxt"));
|
|
std::fill_n(std::back_inserter(text), ztf::kMaxDimValue - 1, '\n');
|
|
text.insert(text.end(), {'t', 'x', 'T', 'Z', '\n'});
|
|
ConstBufferView image(text.data(), text.size());
|
|
EXPECT_TRUE(DisassemblerZtf::QuickDetect(image));
|
|
EXPECT_FALSE(Disassembler::Make<DisassemblerZtf>(image));
|
|
}
|
|
}
|
|
|
|
// Try reading from a well formed source.
|
|
TEST(DisassemblerZtfTest, NormalRead) {
|
|
const std::vector<uint8_t> text(StrToData(kNormalText));
|
|
ConstBufferView image(text.data(), text.size());
|
|
ReferenceSets expected_map = {
|
|
{{DisassemblerZtf::kAngles, DisassemblerZtf::kAnglesAbs1},
|
|
{Reference({63, 0})}},
|
|
{{DisassemblerZtf::kBraces, DisassemblerZtf::kBracesRel2},
|
|
{Reference({74, 27})}},
|
|
};
|
|
ReadReferences(image, text.size() - kNormalTextExtraBytes, &expected_map);
|
|
}
|
|
|
|
// Try writing to a well formed source and ensure that what is read back
|
|
// reflects what was written.
|
|
TEST(DisassemblerZtfTest, NormalWrite) {
|
|
std::vector<uint8_t> mutable_text(StrToData(kNormalText));
|
|
MutableBufferView image(mutable_text.data(), mutable_text.size());
|
|
ReferenceSets change_map = {
|
|
{{DisassemblerZtf::kParentheses, DisassemblerZtf::kParenthesesAbs1},
|
|
{Reference({63, 71})}},
|
|
{{DisassemblerZtf::kBrackets, DisassemblerZtf::kBracketsRel3},
|
|
{Reference({74, 4})}},
|
|
};
|
|
WriteReferences(image, mutable_text.size() - kNormalTextExtraBytes,
|
|
change_map);
|
|
|
|
// As a sanity check see if a disassembler can identify the same references.
|
|
ConstBufferView const_image(image);
|
|
ReadReferences(const_image, mutable_text.size() - kNormalTextExtraBytes,
|
|
&change_map);
|
|
}
|
|
|
|
// Try reading from a source rife with errors.
|
|
TEST(DisassemblerZtfTest, ReadOutOfBoundsRefs) {
|
|
const std::vector<uint8_t> text(StrToData(kOutOfBoundsText));
|
|
ConstBufferView image(text.data(), text.size());
|
|
ReferenceSets expected_map = {
|
|
{{DisassemblerZtf::kAngles, DisassemblerZtf::kAnglesAbs1},
|
|
{Reference({4, 0}), Reference({223, 0}), Reference({228, 12})}},
|
|
{{DisassemblerZtf::kBrackets, DisassemblerZtf::kBracketsRel2},
|
|
{Reference({139, 149})}},
|
|
{{DisassemblerZtf::kBraces, DisassemblerZtf::kBracesAbs1},
|
|
{Reference({218, 223})}},
|
|
{{DisassemblerZtf::kBraces, DisassemblerZtf::kBracesRel2},
|
|
{Reference({233, 233})}},
|
|
{{DisassemblerZtf::kParentheses, DisassemblerZtf::kParenthesesAbs1},
|
|
{Reference({174, 4})}},
|
|
};
|
|
ReadReferences(image, text.size(), &expected_map);
|
|
}
|
|
|
|
// Try writing to a source rife with errors (malformed references or ones that
|
|
// reference non-existent locations. Some of the values written are also bad. To
|
|
// validate check if the expected set of references are read back.
|
|
TEST(DisassemblerZtfTest, WriteOutOfBoundsRefs) {
|
|
// Replace |old_val| (provided for checking) with |new_val| in |set|.
|
|
auto update_set = [](Reference old_ref, Reference new_ref,
|
|
std::set<Reference, ReferenceCompare>* set) {
|
|
auto it = set->find(old_ref);
|
|
EXPECT_NE(it, set->cend());
|
|
EXPECT_EQ(*it, old_ref);
|
|
set->erase(it);
|
|
set->insert(new_ref);
|
|
};
|
|
|
|
// Replace |old_val| (provided for checking) with |new_val| in the set which
|
|
// is the value corresponding to |key| in |map|.
|
|
auto update_map =
|
|
[update_set](
|
|
ReferenceKey key, Reference old_ref, Reference new_ref,
|
|
std::map<ReferenceKey, std::set<Reference, ReferenceCompare>>* map) {
|
|
auto it = map->find(key);
|
|
EXPECT_NE(it, map->cend());
|
|
update_set(old_ref, new_ref, &(it->second));
|
|
};
|
|
|
|
std::vector<uint8_t> mutable_text(StrToData(kOutOfBoundsText));
|
|
MutableBufferView image(mutable_text.data(), mutable_text.size());
|
|
ReferenceSets change_map = {
|
|
{{DisassemblerZtf::kAngles, DisassemblerZtf::kAnglesAbs1},
|
|
{Reference({223, 15}), Reference({228, 13})}},
|
|
{{DisassemblerZtf::kAngles, DisassemblerZtf::kAnglesAbs3},
|
|
{Reference({4, 50})}}, // This should fail to write.
|
|
{{DisassemblerZtf::kBrackets, DisassemblerZtf::kBracketsRel2},
|
|
{Reference({139, static_cast<offset_t>(
|
|
mutable_text.size())})}}, // This should fail.
|
|
{{DisassemblerZtf::kParentheses, DisassemblerZtf::kParenthesesAbs1},
|
|
{Reference({174, 21})}}, // This should fail.
|
|
{{DisassemblerZtf::kBraces, DisassemblerZtf::kBracesAbs1},
|
|
{Reference({218, 219})}},
|
|
{{DisassemblerZtf::kBraces, DisassemblerZtf::kBracesRel2},
|
|
{Reference({233, 174})}},
|
|
};
|
|
WriteReferences(image, mutable_text.size(), change_map);
|
|
|
|
// As a sanity check see if a disassembler can identify the same references
|
|
// (excluding the invalid ones).
|
|
change_map.erase(change_map.find(
|
|
{DisassemblerZtf::kAngles, DisassemblerZtf::kAnglesAbs3}));
|
|
change_map.at({DisassemblerZtf::kAngles, DisassemblerZtf::kAnglesAbs1})
|
|
.emplace(Reference{4, 0});
|
|
update_map({DisassemblerZtf::kBrackets, DisassemblerZtf::kBracketsRel2},
|
|
Reference({139, static_cast<offset_t>(mutable_text.size())}),
|
|
Reference({139, 149}), &change_map);
|
|
update_map({DisassemblerZtf::kParentheses, DisassemblerZtf::kParenthesesAbs1},
|
|
Reference({174, 21}), Reference({174, 4}), &change_map);
|
|
ConstBufferView const_image(image);
|
|
ReadReferences(const_image, mutable_text.size(), &change_map);
|
|
}
|
|
|
|
} // namespace zucchini
|