//===-- TestLineEntry.cpp -------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "gtest/gtest.h"
#include <iostream>
#include <optional>

#include "Plugins/ObjectFile/Mach-O/ObjectFileMachO.h"
#include "Plugins/SymbolFile/DWARF/DWARFASTParserClang.h"
#include "Plugins/SymbolFile/DWARF/SymbolFileDWARF.h"
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
#include "TestingSupport/SubsystemRAII.h"
#include "TestingSupport/TestUtilities.h"

#include "lldb/Core/Module.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/SymbolContext.h"

#include "llvm/Support/FileUtilities.h"
#include "llvm/Support/Program.h"
#include "llvm/Testing/Support/Error.h"

using namespace lldb_private;
using namespace lldb;

class LineEntryTest : public testing::Test {
  SubsystemRAII<FileSystem, HostInfo, ObjectFileMachO, SymbolFileDWARF,
                TypeSystemClang>
      subsystem;

public:
  void SetUp() override;

protected:
  llvm::Expected<SymbolContextList>
  GetLineEntriesForLine(uint32_t line, std::optional<uint16_t> column);
  std::optional<TestFile> m_file;
  ModuleSP m_module_sp;
};

void LineEntryTest::SetUp() {
  auto ExpectedFile = TestFile::fromYamlFile("inlined-functions.yaml");
  ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded());
  m_file.emplace(std::move(*ExpectedFile));
  m_module_sp = std::make_shared<Module>(m_file->moduleSpec());
}

  // TODO: Handle SourceLocationSpec column information
llvm::Expected<SymbolContextList> LineEntryTest::GetLineEntriesForLine(
    uint32_t line, std::optional<uint16_t> column = std::nullopt) {
  SymbolContextList sc_comp_units;
  SymbolContextList sc_line_entries;
  FileSpec file_spec("inlined-functions.cpp");
  m_module_sp->ResolveSymbolContextsForFileSpec(
      file_spec, line, /*check_inlines=*/true, lldb::eSymbolContextCompUnit,
      sc_comp_units);
  if (sc_comp_units.GetSize() == 0)
    return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                   "No comp unit found on the test object.");

  SourceLocationSpec location_spec(file_spec, line, column,
                                   /*check_inlines=*/true,
                                   /*exact_match=*/true);

  sc_comp_units[0].comp_unit->ResolveSymbolContext(
      location_spec, eSymbolContextLineEntry, sc_line_entries);
  if (sc_line_entries.GetSize() == 0)
    return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                   "No line entry found on the test object.");
  return sc_line_entries;
}

// This tests if we can get all line entries that match the passed line, if
// no column is specified.
TEST_F(LineEntryTest, GetAllExactLineMatchesWithoutColumn) {
  auto sc_line_entries = GetLineEntriesForLine(12);
  ASSERT_THAT_EXPECTED(sc_line_entries, llvm::Succeeded());
  ASSERT_EQ(sc_line_entries->NumLineEntriesWithLine(12), 6u);
}

// This tests if we can get exact line and column matches.
TEST_F(LineEntryTest, GetAllExactLineColumnMatches) {
  auto sc_line_entries = GetLineEntriesForLine(12, 39);
  ASSERT_THAT_EXPECTED(sc_line_entries, llvm::Succeeded());
  ASSERT_EQ(sc_line_entries->NumLineEntriesWithLine(12), 1u);
  auto line_entry = sc_line_entries.get()[0].line_entry;
  ASSERT_EQ(line_entry.column, 39);
}

TEST_F(LineEntryTest, GetSameLineContiguousAddressRangeNoInlines) {
  auto sc_line_entries = GetLineEntriesForLine(18);
  ASSERT_THAT_EXPECTED(sc_line_entries, llvm::Succeeded());
  auto line_entry = sc_line_entries.get()[0].line_entry;
  bool include_inlined_functions = false;
  auto range =
      line_entry.GetSameLineContiguousAddressRange(include_inlined_functions);
  ASSERT_EQ(range.GetByteSize(), (uint64_t)0x24);
}

TEST_F(LineEntryTest, GetSameLineContiguousAddressRangeOneInline) {
  auto sc_line_entries = GetLineEntriesForLine(18);
  ASSERT_THAT_EXPECTED(sc_line_entries, llvm::Succeeded());
  auto line_entry = sc_line_entries.get()[0].line_entry;
  bool include_inlined_functions = true;
  auto range =
      line_entry.GetSameLineContiguousAddressRange(include_inlined_functions);
  ASSERT_EQ(range.GetByteSize(), (uint64_t)0x49);
}

TEST_F(LineEntryTest, GetSameLineContiguousAddressRangeNestedInline) {
  auto sc_line_entries = GetLineEntriesForLine(12);
  ASSERT_THAT_EXPECTED(sc_line_entries, llvm::Succeeded());
  auto line_entry = sc_line_entries.get()[0].line_entry;
  bool include_inlined_functions = true;
  auto range =
      line_entry.GetSameLineContiguousAddressRange(include_inlined_functions);
  ASSERT_EQ(range.GetByteSize(), (uint64_t)0x33);
}

/*
# inlined-functions.cpp
inline __attribute__((always_inline)) int sum2(int a, int b) {
    int result = a + b;
    return result;
}

int sum3(int a, int b, int c) {
    int result = a + b + c;
    return result;
}

inline __attribute__((always_inline)) int sum4(int a, int b, int c, int d) {
    int result = sum2(a, b) + sum2(c, d);
    result += 0;
    return result;
}

int main(int argc, char** argv) {
    sum3(3, 4, 5) + sum2(1, 2);
    int sum = sum4(1, 2, 3, 4);
    sum2(5, 6);
    return 0;
}

// g++ -c inlined-functions.cpp -o inlined-functions.o -g -Wno-unused-value
// obj2yaml inlined-functions.o > inlined-functions.yaml

# Dump of source line per address:
# inlined-functions.cpp is src.cpp for space considerations.
0x20: src.cpp:17
0x21: src.cpp:17
0x26: src.cpp:17
0x27: src.cpp:17
0x29: src.cpp:17
0x2e: src.cpp:17
0x2f: src.cpp:17
0x31: src.cpp:17
0x36: src.cpp:18
0x37: src.cpp:18
0x39: src.cpp:18
0x3e: src.cpp:18
0x3f: src.cpp:18
0x41: src.cpp:18
0x46: src.cpp:18
0x47: src.cpp:18
0x49: src.cpp:18
0x4e: src.cpp:18
0x4f: src.cpp:18
0x51: src.cpp:18
0x56: src.cpp:18
0x57: src.cpp:18
0x59: src.cpp:18
0x5e: src.cpp:18 -> sum2@src.cpp:2
0x5f: src.cpp:18 -> sum2@src.cpp:2
0x61: src.cpp:18 -> sum2@src.cpp:2
0x66: src.cpp:18 -> sum2@src.cpp:2
0x67: src.cpp:18 -> sum2@src.cpp:2
0x69: src.cpp:18 -> sum2@src.cpp:2
0x6e: src.cpp:18 -> sum2@src.cpp:2
0x6f: src.cpp:18 -> sum2@src.cpp:2
0x71: src.cpp:18 -> sum2@src.cpp:2
0x76: src.cpp:18 -> sum2@src.cpp:2
0x77: src.cpp:18 -> sum2@src.cpp:2
0x79: src.cpp:18 -> sum2@src.cpp:2
0x7e: src.cpp:18 -> sum2@src.cpp:2
0x7f: src.cpp:19 -> sum4@src.cpp:12
0x81: src.cpp:19 -> sum4@src.cpp:12
0x86: src.cpp:19 -> sum4@src.cpp:12
0x87: src.cpp:19 -> sum4@src.cpp:12
0x89: src.cpp:19 -> sum4@src.cpp:12
0x8e: src.cpp:19 -> sum4@src.cpp:12 -> sum2@src.cpp:2
0x8f: src.cpp:19 -> sum4@src.cpp:12 -> sum2@src.cpp:2
0x91: src.cpp:19 -> sum4@src.cpp:12 -> sum2@src.cpp:2
0x96: src.cpp:19 -> sum4@src.cpp:12 -> sum2@src.cpp:3
0x97: src.cpp:19 -> sum4@src.cpp:12
0x99: src.cpp:19 -> sum4@src.cpp:12
0x9e: src.cpp:19 -> sum4@src.cpp:12
0x9f: src.cpp:19 -> sum4@src.cpp:12
0xa1: src.cpp:19 -> sum4@src.cpp:12
0xa6: src.cpp:19 -> sum4@src.cpp:12 -> sum2@src.cpp:2
0xa7: src.cpp:19 -> sum4@src.cpp:12 -> sum2@src.cpp:2
0xa9: src.cpp:19 -> sum4@src.cpp:12 -> sum2@src.cpp:2
0xae: src.cpp:19 -> sum4@src.cpp:12
0xaf: src.cpp:19 -> sum4@src.cpp:12
0xb1: src.cpp:19 -> sum4@src.cpp:12
0xb6: src.cpp:19 -> sum4@src.cpp:13
0xb7: src.cpp:19 -> sum4@src.cpp:13
0xb9: src.cpp:19 -> sum4@src.cpp:14
0xbe: src.cpp:19
0xbf: src.cpp:19
0xc1: src.cpp:19
0xc6: src.cpp:19
0xc7: src.cpp:19
0xc9: src.cpp:19
0xce: src.cpp:20 -> sum2@src.cpp:2
0xcf: src.cpp:20 -> sum2@src.cpp:2
0xd1: src.cpp:20 -> sum2@src.cpp:2
0xd6: src.cpp:21
0xd7: src.cpp:21
0xd9: src.cpp:21
0xde: src.cpp:21
*/
