Compiler projects using llvm
//===- unittest/Support/RemarksLinkingTest.cpp - Linking tests ------------===//
//
// 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 "llvm/Bitcode/BitcodeAnalyzer.h"
#include "llvm/Remarks/RemarkLinker.h"
#include "llvm/Remarks/RemarkSerializer.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
#include <string>

using namespace llvm;

static void serializeAndCheck(remarks::RemarkLinker &RL,
                              remarks::Format OutputFormat,
                              StringRef ExpectedOutput) {
  // 1. Create a serializer.
  // 2. Serialize all the remarks from the linker.
  // 3. Check that it matches the output.
  std::string Buf;
  raw_string_ostream OS(Buf);
  Error E = RL.serialize(OS, OutputFormat);
  EXPECT_FALSE(static_cast<bool>(E));

  // For bitstream, run it through the analyzer.
  if (OutputFormat == remarks::Format::Bitstream) {
    std::string AnalyzeBuf;
    raw_string_ostream AnalyzeOS(AnalyzeBuf);
    BCDumpOptions O(AnalyzeOS);
    O.ShowBinaryBlobs = true;
    BitcodeAnalyzer BA(OS.str());
    EXPECT_FALSE(BA.analyze(O)); // Expect no errors.
    EXPECT_EQ(AnalyzeOS.str(), ExpectedOutput);
  } else {
    EXPECT_EQ(OS.str(), ExpectedOutput);
  }
}

static void check(remarks::Format InputFormat, StringRef Input,
                  remarks::Format OutputFormat, StringRef ExpectedOutput) {
  remarks::RemarkLinker RL;
  EXPECT_FALSE(RL.link(Input, InputFormat));
  serializeAndCheck(RL, OutputFormat, ExpectedOutput);
}

static void check(remarks::Format InputFormat, StringRef Input,
                  remarks::Format InputFormat2, StringRef Input2,
                  remarks::Format OutputFormat, StringRef ExpectedOutput) {
  remarks::RemarkLinker RL;
  EXPECT_FALSE(RL.link(Input, InputFormat));
  EXPECT_FALSE(RL.link(Input2, InputFormat2));
  serializeAndCheck(RL, OutputFormat, ExpectedOutput);
}

TEST(Remarks, LinkingGoodYAML) {
  // One YAML remark.
  check(remarks::Format::YAML,
        "--- !Missed\n"
        "Pass:            inline\n"
        "Name:            NoDefinition\n"
        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
        "Function:        foo\n"
        "...\n",
        remarks::Format::YAML,
        "--- !Missed\n"
        "Pass:            inline\n"
        "Name:            NoDefinition\n"
        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
        "Function:        foo\n"
        "...\n");

  // Check that we don't keep remarks without debug locations.
  check(remarks::Format::YAML,
        "--- !Missed\n"
        "Pass:            inline\n"
        "Name:            NoDefinition\n"
        "Function:        foo\n"
        "...\n",
        remarks::Format::YAML, "");

  // Check that we deduplicate remarks.
  check(remarks::Format::YAML,
        "--- !Missed\n"
        "Pass:            inline\n"
        "Name:            NoDefinition\n"
        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
        "Function:        foo\n"
        "...\n"
        "--- !Missed\n"
        "Pass:            inline\n"
        "Name:            NoDefinition\n"
        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
        "Function:        foo\n"
        "...\n",
        remarks::Format::YAML,
        "--- !Missed\n"
        "Pass:            inline\n"
        "Name:            NoDefinition\n"
        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
        "Function:        foo\n"
        "...\n");
}

TEST(Remarks, LinkingGoodBitstream) {
  // One YAML remark.
  check(remarks::Format::YAML,
        "--- !Missed\n"
        "Pass:            inline\n"
        "Name:            NoDefinition\n"
        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
        "Function:        foo\n"
        "...\n",
        remarks::Format::Bitstream,
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=12 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n"
        "  <Remark version codeid=2 abbrevid=5 op0=0/>\n"
        "  <String table codeid=3 abbrevid=6/> blob data = "
        "'inline\\x00NoDefinition\\x00foo\\x00file.c\\x00'\n"
        "</Meta>\n"
        "<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n"
        "  <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n"
        "  <Remark debug location codeid=6 abbrevid=5 op0=3 op1=3 op2=12/>\n"
        "</Remark>\n");

  // Check that we deduplicate remarks.
  check(remarks::Format::YAML,
        "--- !Missed\n"
        "Pass:            inline\n"
        "Name:            NoDefinition\n"
        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
        "Function:        foo\n"
        "...\n"
        "--- !Missed\n"
        "Pass:            inline\n"
        "Name:            NoDefinition\n"
        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
        "Function:        foo\n"
        "...\n",
        remarks::Format::Bitstream,
        "<BLOCKINFO_BLOCK/>\n"
        "<Meta BlockID=8 NumWords=12 BlockCodeSize=3>\n"
        "  <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n"
        "  <Remark version codeid=2 abbrevid=5 op0=0/>\n"
        "  <String table codeid=3 abbrevid=6/> blob data = "
        "'inline\\x00NoDefinition\\x00foo\\x00file.c\\x00'\n"
        "</Meta>\n"
        "<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n"
        "  <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n"
        "  <Remark debug location codeid=6 abbrevid=5 op0=3 op1=3 op2=12/>\n"
        "</Remark>\n");
}

TEST(Remarks, LinkingGoodStrTab) {
  // Check that remarks from different entries use the same strtab.
  check(remarks::Format::YAML,
        "--- !Missed\n"
        "Pass:            inline\n"
        "Name:            NoDefinition\n"
        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
        "Function:        foo\n"
        "...\n",
        remarks::Format::YAML,
        "--- !Passed\n"
        "Pass:            inline\n"
        "Name:            Ok\n"
        "DebugLoc:        { File: file.c, Line: 3, Column: 12 }\n"
        "Function:        foo\n"
        "...\n",
        remarks::Format::YAMLStrTab,
        StringRef("REMARKS\0\0\0\0\0\0\0\0\0\x22\0\0\0\0\0\0\0"
                  "inline\0NoDefinition\0foo\0file.c\0Ok\0"
                  "--- !Passed\n"
                  "Pass:            0\n"
                  "Name:            4\n"
                  "DebugLoc:        { File: 3, Line: 3, Column: 12 }\n"
                  "Function:        2\n"
                  "...\n"
                  "--- !Missed\n"
                  "Pass:            0\n"
                  "Name:            1\n"
                  "DebugLoc:        { File: 3, Line: 3, Column: 12 }\n"
                  "Function:        2\n"
                  "...\n",
                  304));
}

// Check that we propagate parsing errors.
TEST(Remarks, LinkingError) {
  remarks::RemarkLinker RL;
  {
    Error E = RL.link("badyaml", remarks::Format::YAML);
    EXPECT_TRUE(static_cast<bool>(E));
    EXPECT_EQ(toString(std::move(E)),
              "YAML:1:1: error: document root is not of mapping type.\n"
              "\n"
              "badyaml\n"
              "^~~~~~~\n"
              "\n");
  }

  {
    // Check that the prepend path is propagated and fails with the full path.
    RL.setExternalFilePrependPath("/baddir/");
    Error E = RL.link(
        StringRef("REMARKS\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0badfile.opt.yaml",
                  40),
        remarks::Format::YAMLStrTab);
    EXPECT_TRUE(static_cast<bool>(E));
    std::string ErrorMessage = toString(std::move(E));
    EXPECT_EQ(StringRef(ErrorMessage).lower(),
              StringRef("'/baddir/badfile.opt.yaml': No such file or directory")
                  .lower());
  }
}