//===-- PDBFPOProgramToDWARFExpressionTests.cpp -----------------*- C++ -*-===//
//
// 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 "Plugins/SymbolFile/NativePDB/PdbFPOProgramToDWARFExpression.h"

#include "lldb/Core/StreamBuffer.h"
#include "lldb/Expression/DWARFExpression.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/DataExtractor.h"
#include "lldb/Utility/StreamString.h"

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::npdb;

/// Valid programs tests

static void
CheckValidProgramTranslation(llvm::StringRef fpo_program,
                             llvm::StringRef target_register_name,
                             llvm::StringRef expected_dwarf_expression) {
  // initial setup
  ArchSpec arch_spec("i686-pc-windows");
  llvm::Triple::ArchType arch_type = arch_spec.GetMachine();
  ByteOrder byte_order = arch_spec.GetByteOrder();
  uint32_t address_size = arch_spec.GetAddressByteSize();
  uint32_t byte_size = arch_spec.GetDataByteSize();

  // program translation
  StreamBuffer<32> stream(Stream::eBinary, address_size, byte_order);
  ASSERT_TRUE(TranslateFPOProgramToDWARFExpression(
      fpo_program, target_register_name, arch_type, stream));

  // print dwarf expression to comparable textual representation
  DataBufferSP buffer =
      std::make_shared<DataBufferHeap>(stream.GetData(), stream.GetSize());
  DataExtractor extractor(buffer, byte_order, address_size, byte_size);

  StreamString result_dwarf_expression;
  ASSERT_TRUE(DWARFExpression::PrintDWARFExpression(
      result_dwarf_expression, extractor, address_size, 4, false));

  // actual check
  ASSERT_STREQ(expected_dwarf_expression.data(),
               result_dwarf_expression.GetString().data());
}

TEST(PDBFPOProgramToDWARFExpressionTests, SingleAssignmentRegisterRef) {
  CheckValidProgramTranslation("$T0 $ebp = ", "$T0", "DW_OP_breg6 +0");
}

TEST(PDBFPOProgramToDWARFExpressionTests, MultipleIndependentAssignments) {
  CheckValidProgramTranslation("$T1 1 = $T0 0 =", "$T0", "DW_OP_consts +0");
}

TEST(PDBFPOProgramToDWARFExpressionTests, MultipleDependentAssignments) {
  CheckValidProgramTranslation(
      "$T1 $ebp 4 + = $T0 $T1 8 - 128 @ = ", "$T0",
      "DW_OP_breg6 +0, DW_OP_consts +4, DW_OP_plus , DW_OP_consts +8, "
      "DW_OP_minus , DW_OP_consts +128, DW_OP_lit1 , DW_OP_minus , DW_OP_not , "
      "DW_OP_and ");
}

TEST(PDBFPOProgramToDWARFExpressionTests, DependencyChain) {
  CheckValidProgramTranslation("$T1 0 = $T0 $T1 = $ebp $T0 =", "$ebp",
                               "DW_OP_consts +0");
}

/// Invalid programs tests
static void
CheckInvalidProgramTranslation(llvm::StringRef fpo_program,
                               llvm::StringRef target_register_name) {
  // initial setup
  ArchSpec arch_spec("i686-pc-windows");
  llvm::Triple::ArchType arch_type = arch_spec.GetMachine();
  ByteOrder byte_order = arch_spec.GetByteOrder();
  uint32_t address_size = arch_spec.GetAddressByteSize();

  // program translation
  StreamBuffer<32> stream(Stream::eBinary, address_size, byte_order);
  EXPECT_FALSE(TranslateFPOProgramToDWARFExpression(
      fpo_program, target_register_name, arch_type, stream));
  EXPECT_EQ((size_t)0, stream.GetSize());
}

TEST(PDBFPOProgramToDWARFExpressionTests, InvalidAssignmentSingle) {
  CheckInvalidProgramTranslation("$T0 0", "$T0");
}

TEST(PDBFPOProgramToDWARFExpressionTests, InvalidAssignmentMultiple) {
  CheckInvalidProgramTranslation("$T1 0 = $T0 0", "$T0");
}

TEST(PDBFPOProgramToDWARFExpressionTests, UnknownOp) {
  CheckInvalidProgramTranslation("$T0 $ebp 0 & = ", "$T0");
}

TEST(PDBFPOProgramToDWARFExpressionTests, InvalidOpBinary) {
  CheckInvalidProgramTranslation("$T0 0 + = ", "$T0");
}

TEST(PDBFPOProgramToDWARFExpressionTests, InvalidOpUnary) {
  CheckInvalidProgramTranslation("$T0 ^ = ", "$T0");
}

TEST(PDBFPOProgramToDWARFExpressionTests, MissingTargetRegister) {
  CheckInvalidProgramTranslation("$T1 0 = ", "$T0");
}

TEST(PDBFPOProgramToDWARFExpressionTests, UnresolvedRegisterReference) {
  CheckInvalidProgramTranslation("$T0 $abc = ", "$T0");
}

TEST(PDBFPOProgramToDWARFExpressionTests,
     UnresolvedRegisterAssignmentReference) {
  CheckInvalidProgramTranslation("$T2 0 = $T0 $T1 = ", "$T0");
}

TEST(PDBFPOProgramToDWARFExpressionTests,
     UnresolvedCyclicRegisterAssignmentReference) {
  CheckInvalidProgramTranslation("$T1 $T0 = $T0 $T1 = ", "$T0");
}

TEST(PDBFPOProgramToDWARFExpressionTests,
     UnresolvedDependentCyclicRegisterAssignmentReference) {
  CheckInvalidProgramTranslation("$T1 $T0 = $T0 $T1 = $T2 $T1 =", "$T2");
}

TEST(PDBFPOProgramToDWARFExpressionTests, UnsupportedRASearch) {
  CheckInvalidProgramTranslation("$T0 .raSearch = ", "$T0");
}
