// 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 #include #include #include #include #include #include #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 StrToData(base::StringPiece s) { return std::vector(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; using ReferenceSets = std::map>; // 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 dis = Disassembler::Make(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(group.pool_tag().value()), static_cast(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 dis = Disassembler::Make(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(group.pool_tag().value()), static_cast(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 size_t CountDistinct(const std::vector& v) { return std::set(v.begin(), v.end()).size(); } } // namespace TEST(ZtfTranslatorTest, Translate) { ztf::dim_t kMaxVal = INT16_MAX; ztf::dim_t kMinVal = INT16_MIN; const std::vector 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 pool_list; std::vector type_list; DisassemblerZtf dis; for (ReferenceGroup group : dis.MakeReferenceGroups()) { pool_list.push_back(static_cast(group.pool_tag().value())); type_list.push_back(static_cast(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 text(StrToData("foobarbaz bazbarfoo")); ConstBufferView image(text.data(), text.size()); EXPECT_FALSE(DisassemblerZtf::QuickDetect(image)); EXPECT_FALSE(Disassembler::Make(image)); } // Test a case where there is no footer so a disassembler cannot be created. { const std::vector text(StrToData("ZTxtfoobarbaz bazbarfootxTZ")); ConstBufferView image(text.data(), text.size()); EXPECT_TRUE(DisassemblerZtf::QuickDetect(image)); EXPECT_FALSE(Disassembler::Make(image)); } // Test when the header is too short { const std::vector text(StrToData("ZTxtxTZ\n")); ConstBufferView image(text.data(), text.size()); EXPECT_FALSE(DisassemblerZtf::QuickDetect(image)); EXPECT_FALSE(Disassembler::Make(image)); } } TEST(DisassemblerZtfTest, ZtfSizeBound) { { std::vector 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(image)); } { std::vector 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(image)); } } // Try reading from a well formed source. TEST(DisassemblerZtfTest, NormalRead) { const std::vector 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 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 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* 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>* map) { auto it = map->find(key); EXPECT_NE(it, map->cend()); update_set(old_ref, new_ref, &(it->second)); }; std::vector 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( 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(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