/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <elf.h>
#include <string.h>

#include <memory>
#include <vector>

#include <gtest/gtest.h>

#include <unwindstack/DexFiles.h>
#include <unwindstack/Elf.h>
#include <unwindstack/MapInfo.h>
#include <unwindstack/Maps.h>
#include <unwindstack/Memory.h>

#include "DexFileData.h"
#include "ElfFake.h"
#include "MemoryFake.h"

namespace unwindstack {

class DexFilesTest : public ::testing::Test {
 protected:
  void CreateFakeElf(MapInfo* map_info) {
    MemoryFake* memory = new MemoryFake;
    ElfFake* elf = new ElfFake(memory);
    elf->FakeSetValid(true);
    ElfInterfaceFake* interface = new ElfInterfaceFake(memory);
    elf->FakeSetInterface(interface);

    interface->FakeSetGlobalVariable("__dex_debug_descriptor", 0x800);
    map_info->elf.reset(elf);
  }

  void Init(ArchEnum arch) {
    dex_files_.reset(new DexFiles(process_memory_));
    dex_files_->SetArch(arch);

    maps_.reset(
        new BufferMaps("1000-4000 ---s 00000000 00:00 0 /fake/elf\n"
                       "4000-6000 r--s 00000000 00:00 0 /fake/elf\n"
                       "6000-8000 -wxs 00000000 00:00 0 /fake/elf\n"
                       "a000-c000 r--p 00000000 00:00 0 /fake/elf2\n"
                       "c000-f000 rw-p 00001000 00:00 0 /fake/elf2\n"
                       "f000-11000 r--p 00000000 00:00 0 /fake/elf3\n"
                       "100000-110000 rw-p 0001000 00:00 0 /fake/elf3\n"
                       "200000-210000 rw-p 0002000 00:00 0 /fake/elf3\n"
                       "300000-400000 rw-p 0003000 00:00 0 /fake/elf3\n"));
    ASSERT_TRUE(maps_->Parse());

    // Global variable in a section that is not readable.
    MapInfo* map_info = maps_->Get(kMapGlobalNonReadable);
    ASSERT_TRUE(map_info != nullptr);
    CreateFakeElf(map_info);

    // Global variable not set by default.
    map_info = maps_->Get(kMapGlobalSetToZero);
    ASSERT_TRUE(map_info != nullptr);
    CreateFakeElf(map_info);

    // Global variable set in this map.
    map_info = maps_->Get(kMapGlobal);
    ASSERT_TRUE(map_info != nullptr);
    CreateFakeElf(map_info);
  }

  void SetUp() override {
    memory_ = new MemoryFake;
    process_memory_.reset(memory_);

    Init(ARCH_ARM);
  }

  void WriteDescriptor32(uint64_t addr, uint32_t head);
  void WriteDescriptor64(uint64_t addr, uint64_t head);
  void WriteEntry32(uint64_t entry_addr, uint32_t next, uint32_t prev, uint32_t dex_file);
  void WriteEntry64(uint64_t entry_addr, uint64_t next, uint64_t prev, uint64_t dex_file);
  void WriteDex(uint64_t dex_file);

  static constexpr size_t kMapGlobalNonReadable = 2;
  static constexpr size_t kMapGlobalSetToZero = 3;
  static constexpr size_t kMapGlobal = 5;
  static constexpr size_t kMapGlobalRw = 6;
  static constexpr size_t kMapDexFileEntries = 7;
  static constexpr size_t kMapDexFiles = 8;

  std::shared_ptr<Memory> process_memory_;
  MemoryFake* memory_;
  std::unique_ptr<DexFiles> dex_files_;
  std::unique_ptr<BufferMaps> maps_;
};

void DexFilesTest::WriteDescriptor32(uint64_t addr, uint32_t head) {
  //   void* first_entry_
  memory_->SetData32(addr + 12, head);
}

void DexFilesTest::WriteDescriptor64(uint64_t addr, uint64_t head) {
  //   void* first_entry_
  memory_->SetData64(addr + 16, head);
}

void DexFilesTest::WriteEntry32(uint64_t entry_addr, uint32_t next, uint32_t prev,
                                uint32_t dex_file) {
  // Format of the 32 bit DEXFileEntry structure:
  //   uint32_t next
  memory_->SetData32(entry_addr, next);
  //   uint32_t prev
  memory_->SetData32(entry_addr + 4, prev);
  //   uint32_t dex_file
  memory_->SetData32(entry_addr + 8, dex_file);
}

void DexFilesTest::WriteEntry64(uint64_t entry_addr, uint64_t next, uint64_t prev,
                                uint64_t dex_file) {
  // Format of the 64 bit DEXFileEntry structure:
  //   uint64_t next
  memory_->SetData64(entry_addr, next);
  //   uint64_t prev
  memory_->SetData64(entry_addr + 8, prev);
  //   uint64_t dex_file
  memory_->SetData64(entry_addr + 16, dex_file);
}

void DexFilesTest::WriteDex(uint64_t dex_file) {
  memory_->SetMemory(dex_file, kDexData, sizeof(kDexData) * sizeof(uint32_t));
}

TEST_F(DexFilesTest, get_method_information_invalid) {
  std::string method_name = "nothing";
  uint64_t method_offset = 0x124;
  MapInfo* info = maps_->Get(kMapDexFileEntries);

  dex_files_->GetMethodInformation(maps_.get(), info, 0, &method_name, &method_offset);
  EXPECT_EQ("nothing", method_name);
  EXPECT_EQ(0x124U, method_offset);
}

TEST_F(DexFilesTest, get_method_information_32) {
  std::string method_name = "nothing";
  uint64_t method_offset = 0x124;
  MapInfo* info = maps_->Get(kMapDexFiles);

  WriteDescriptor32(0xf800, 0x200000);
  WriteEntry32(0x200000, 0, 0, 0x300000);
  WriteDex(0x300000);

  dex_files_->GetMethodInformation(maps_.get(), info, 0x300100, &method_name, &method_offset);
  EXPECT_EQ("Main.<init>", method_name);
  EXPECT_EQ(0U, method_offset);
}

TEST_F(DexFilesTest, get_method_information_64) {
  Init(ARCH_ARM64);

  std::string method_name = "nothing";
  uint64_t method_offset = 0x124;
  MapInfo* info = maps_->Get(kMapDexFiles);

  WriteDescriptor64(0xf800, 0x200000);
  WriteEntry64(0x200000, 0, 0, 0x301000);
  WriteDex(0x301000);

  dex_files_->GetMethodInformation(maps_.get(), info, 0x301102, &method_name, &method_offset);
  EXPECT_EQ("Main.<init>", method_name);
  EXPECT_EQ(2U, method_offset);
}

TEST_F(DexFilesTest, get_method_information_not_first_entry_32) {
  std::string method_name = "nothing";
  uint64_t method_offset = 0x124;
  MapInfo* info = maps_->Get(kMapDexFiles);

  WriteDescriptor32(0xf800, 0x200000);
  WriteEntry32(0x200000, 0x200100, 0, 0x100000);
  WriteEntry32(0x200100, 0, 0x200000, 0x300000);
  WriteDex(0x300000);

  dex_files_->GetMethodInformation(maps_.get(), info, 0x300104, &method_name, &method_offset);
  EXPECT_EQ("Main.<init>", method_name);
  EXPECT_EQ(4U, method_offset);
}

TEST_F(DexFilesTest, get_method_information_not_first_entry_64) {
  Init(ARCH_ARM64);

  std::string method_name = "nothing";
  uint64_t method_offset = 0x124;
  MapInfo* info = maps_->Get(kMapDexFiles);

  WriteDescriptor64(0xf800, 0x200000);
  WriteEntry64(0x200000, 0x200100, 0, 0x100000);
  WriteEntry64(0x200100, 0, 0x200000, 0x300000);
  WriteDex(0x300000);

  dex_files_->GetMethodInformation(maps_.get(), info, 0x300106, &method_name, &method_offset);
  EXPECT_EQ("Main.<init>", method_name);
  EXPECT_EQ(6U, method_offset);
}

TEST_F(DexFilesTest, get_method_information_cached) {
  std::string method_name = "nothing";
  uint64_t method_offset = 0x124;
  MapInfo* info = maps_->Get(kMapDexFiles);

  WriteDescriptor32(0xf800, 0x200000);
  WriteEntry32(0x200000, 0, 0, 0x300000);
  WriteDex(0x300000);

  dex_files_->GetMethodInformation(maps_.get(), info, 0x300100, &method_name, &method_offset);
  EXPECT_EQ("Main.<init>", method_name);
  EXPECT_EQ(0U, method_offset);

  // Clear all memory and make sure that data is acquired from the cache.
  memory_->Clear();
  dex_files_->GetMethodInformation(maps_.get(), info, 0x300100, &method_name, &method_offset);
  EXPECT_EQ("Main.<init>", method_name);
  EXPECT_EQ(0U, method_offset);
}

TEST_F(DexFilesTest, get_method_information_search_libs) {
  std::string method_name = "nothing";
  uint64_t method_offset = 0x124;
  MapInfo* info = maps_->Get(kMapDexFiles);

  WriteDescriptor32(0xf800, 0x200000);
  WriteEntry32(0x200000, 0x200100, 0, 0x100000);
  WriteEntry32(0x200100, 0, 0x200000, 0x300000);
  WriteDex(0x300000);

  // Only search a given named list of libs.
  std::vector<std::string> libs{"libart.so"};
  dex_files_.reset(new DexFiles(process_memory_, libs));
  dex_files_->SetArch(ARCH_ARM);

  dex_files_->GetMethodInformation(maps_.get(), info, 0x300104, &method_name, &method_offset);
  EXPECT_EQ("nothing", method_name);
  EXPECT_EQ(0x124U, method_offset);

  MapInfo* map_info = maps_->Get(kMapGlobal);
  map_info->name = "/system/lib/libart.so";
  dex_files_.reset(new DexFiles(process_memory_, libs));
  dex_files_->SetArch(ARCH_ARM);
  // Set the rw map to the same name or this will not scan this entry.
  map_info = maps_->Get(kMapGlobalRw);
  map_info->name = "/system/lib/libart.so";
  // Make sure that clearing out copy of the libs doesn't affect the
  // DexFiles object.
  libs.clear();

  dex_files_->GetMethodInformation(maps_.get(), info, 0x300104, &method_name, &method_offset);
  EXPECT_EQ("Main.<init>", method_name);
  EXPECT_EQ(4U, method_offset);
}

TEST_F(DexFilesTest, get_method_information_global_skip_zero_32) {
  std::string method_name = "nothing";
  uint64_t method_offset = 0x124;
  MapInfo* info = maps_->Get(kMapDexFiles);

  // First global variable found, but value is zero.
  WriteDescriptor32(0xa800, 0);

  WriteDescriptor32(0xf800, 0x200000);
  WriteEntry32(0x200000, 0, 0, 0x300000);
  WriteDex(0x300000);

  dex_files_->GetMethodInformation(maps_.get(), info, 0x300100, &method_name, &method_offset);
  EXPECT_EQ("Main.<init>", method_name);
  EXPECT_EQ(0U, method_offset);

  // Verify that second is ignored when first is set to non-zero
  dex_files_.reset(new DexFiles(process_memory_));
  dex_files_->SetArch(ARCH_ARM);
  method_name = "fail";
  method_offset = 0x123;
  WriteDescriptor32(0xa800, 0x100000);
  dex_files_->GetMethodInformation(maps_.get(), info, 0x300100, &method_name, &method_offset);
  EXPECT_EQ("fail", method_name);
  EXPECT_EQ(0x123U, method_offset);
}

TEST_F(DexFilesTest, get_method_information_global_skip_zero_64) {
  Init(ARCH_ARM64);

  std::string method_name = "nothing";
  uint64_t method_offset = 0x124;
  MapInfo* info = maps_->Get(kMapDexFiles);

  // First global variable found, but value is zero.
  WriteDescriptor64(0xa800, 0);

  WriteDescriptor64(0xf800, 0x200000);
  WriteEntry64(0x200000, 0, 0, 0x300000);
  WriteDex(0x300000);

  dex_files_->GetMethodInformation(maps_.get(), info, 0x300100, &method_name, &method_offset);
  EXPECT_EQ("Main.<init>", method_name);
  EXPECT_EQ(0U, method_offset);

  // Verify that second is ignored when first is set to non-zero
  dex_files_.reset(new DexFiles(process_memory_));
  dex_files_->SetArch(ARCH_ARM64);
  method_name = "fail";
  method_offset = 0x123;
  WriteDescriptor64(0xa800, 0x100000);
  dex_files_->GetMethodInformation(maps_.get(), info, 0x300100, &method_name, &method_offset);
  EXPECT_EQ("fail", method_name);
  EXPECT_EQ(0x123U, method_offset);
}

}  // namespace unwindstack
