// 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
