mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-25 00:16:05 +00:00

Depends on https://reviews.llvm.org/D142861. Alternative to https://reviews.llvm.org/D137601. xxHash is much faster than djbHash. This makes a simple Rust test case with a large constant string 10% faster to compile. Previous attempts at changing this hash function (e.g. https://reviews.llvm.org/D97396) had to be reverted due to breaking tests that depended on iteration order. No additional tests fail with this patch compared to `main` when running `check-all` with `-DLLVM_ENABLE_PROJECTS="all"` (on a Linux host), so I hope I found everything that needs to be changed. Differential Revision: https://reviews.llvm.org/D142862
394 lines
18 KiB
C++
394 lines
18 KiB
C++
//===- unittests/Basic/SarifTest.cpp - Test writing SARIF documents -------===//
|
|
//
|
|
// 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 "clang/Basic/Sarif.h"
|
|
#include "clang/Basic/DiagnosticOptions.h"
|
|
#include "clang/Basic/FileManager.h"
|
|
#include "clang/Basic/FileSystemOptions.h"
|
|
#include "clang/Basic/LangOptions.h"
|
|
#include "clang/Basic/SourceLocation.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include "llvm/Support/JSON.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/Support/VirtualFileSystem.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
#include <algorithm>
|
|
|
|
using namespace clang;
|
|
|
|
namespace {
|
|
|
|
using LineCol = std::pair<unsigned int, unsigned int>;
|
|
|
|
static std::string serializeSarifDocument(llvm::json::Object &&Doc) {
|
|
std::string Output;
|
|
llvm::json::Value Value(std::move(Doc));
|
|
llvm::raw_string_ostream OS{Output};
|
|
OS << llvm::formatv("{0}", Value);
|
|
OS.flush();
|
|
return Output;
|
|
}
|
|
|
|
class SarifDocumentWriterTest : public ::testing::Test {
|
|
protected:
|
|
SarifDocumentWriterTest()
|
|
: InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem),
|
|
FileMgr(FileSystemOptions(), InMemoryFileSystem),
|
|
DiagID(new DiagnosticIDs()), DiagOpts(new DiagnosticOptions()),
|
|
Diags(DiagID, DiagOpts.get(), new IgnoringDiagConsumer()),
|
|
SourceMgr(Diags, FileMgr) {}
|
|
|
|
IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem;
|
|
FileManager FileMgr;
|
|
IntrusiveRefCntPtr<DiagnosticIDs> DiagID;
|
|
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts;
|
|
DiagnosticsEngine Diags;
|
|
SourceManager SourceMgr;
|
|
LangOptions LangOpts;
|
|
|
|
FileID registerSource(llvm::StringRef Name, const char *SourceText,
|
|
bool IsMainFile = false) {
|
|
std::unique_ptr<llvm::MemoryBuffer> SourceBuf =
|
|
llvm::MemoryBuffer::getMemBuffer(SourceText);
|
|
const FileEntry *SourceFile =
|
|
FileMgr.getVirtualFile(Name, SourceBuf->getBufferSize(), 0);
|
|
SourceMgr.overrideFileContents(SourceFile, std::move(SourceBuf));
|
|
FileID FID = SourceMgr.getOrCreateFileID(SourceFile, SrcMgr::C_User);
|
|
if (IsMainFile)
|
|
SourceMgr.setMainFileID(FID);
|
|
return FID;
|
|
}
|
|
|
|
CharSourceRange getFakeCharSourceRange(FileID FID, LineCol Begin,
|
|
LineCol End) {
|
|
auto BeginLoc = SourceMgr.translateLineCol(FID, Begin.first, Begin.second);
|
|
auto EndLoc = SourceMgr.translateLineCol(FID, End.first, End.second);
|
|
return CharSourceRange{SourceRange{BeginLoc, EndLoc}, /* ITR = */ false};
|
|
}
|
|
};
|
|
|
|
TEST_F(SarifDocumentWriterTest, canCreateEmptyDocument) {
|
|
// GIVEN:
|
|
SarifDocumentWriter Writer{SourceMgr};
|
|
|
|
// WHEN:
|
|
const llvm::json::Object &EmptyDoc = Writer.createDocument();
|
|
std::vector<StringRef> Keys(EmptyDoc.size());
|
|
std::transform(EmptyDoc.begin(), EmptyDoc.end(), Keys.begin(),
|
|
[](auto Item) { return Item.getFirst(); });
|
|
|
|
// THEN:
|
|
ASSERT_THAT(Keys, testing::UnorderedElementsAre("$schema", "version"));
|
|
}
|
|
|
|
// Test that a newly inserted run will associate correct tool names
|
|
TEST_F(SarifDocumentWriterTest, canCreateDocumentWithOneRun) {
|
|
// GIVEN:
|
|
SarifDocumentWriter Writer{SourceMgr};
|
|
const char *ShortName = "sariftest";
|
|
const char *LongName = "sarif writer test";
|
|
|
|
// WHEN:
|
|
Writer.createRun(ShortName, LongName);
|
|
Writer.endRun();
|
|
const llvm::json::Object &Doc = Writer.createDocument();
|
|
const llvm::json::Array *Runs = Doc.getArray("runs");
|
|
|
|
// THEN:
|
|
// A run was created
|
|
ASSERT_THAT(Runs, testing::NotNull());
|
|
|
|
// It is the only run
|
|
ASSERT_EQ(Runs->size(), 1UL);
|
|
|
|
// The tool associated with the run was the tool
|
|
const llvm::json::Object *Driver =
|
|
Runs->begin()->getAsObject()->getObject("tool")->getObject("driver");
|
|
ASSERT_THAT(Driver, testing::NotNull());
|
|
|
|
ASSERT_TRUE(Driver->getString("name").has_value());
|
|
ASSERT_TRUE(Driver->getString("fullName").has_value());
|
|
ASSERT_TRUE(Driver->getString("language").has_value());
|
|
|
|
EXPECT_EQ(*Driver->getString("name"), ShortName);
|
|
EXPECT_EQ(*Driver->getString("fullName"), LongName);
|
|
EXPECT_EQ(*Driver->getString("language"), "en-US");
|
|
}
|
|
|
|
TEST_F(SarifDocumentWriterTest, addingResultsWillCrashIfThereIsNoRun) {
|
|
#if defined(NDEBUG) || !GTEST_HAS_DEATH_TEST
|
|
GTEST_SKIP() << "This death test is only available for debug builds.";
|
|
#endif
|
|
// GIVEN:
|
|
SarifDocumentWriter Writer{SourceMgr};
|
|
|
|
// WHEN:
|
|
// A SarifDocumentWriter::createRun(...) was not called prior to
|
|
// SarifDocumentWriter::appendResult(...)
|
|
// But a rule exists
|
|
auto RuleIdx = Writer.createRule(SarifRule::create());
|
|
const SarifResult &EmptyResult = SarifResult::create(RuleIdx);
|
|
|
|
// THEN:
|
|
auto Matcher = ::testing::AnyOf(
|
|
::testing::HasSubstr("create a run first"),
|
|
::testing::HasSubstr("no runs associated with the document"));
|
|
ASSERT_DEATH(Writer.appendResult(EmptyResult), Matcher);
|
|
}
|
|
|
|
TEST_F(SarifDocumentWriterTest, settingInvalidRankWillCrash) {
|
|
#if defined(NDEBUG) || !GTEST_HAS_DEATH_TEST
|
|
GTEST_SKIP() << "This death test is only available for debug builds.";
|
|
#endif
|
|
// GIVEN:
|
|
SarifDocumentWriter Writer{SourceMgr};
|
|
|
|
// WHEN:
|
|
// A SarifReportingConfiguration is created with an invalid "rank"
|
|
// * Ranks below 0.0 are invalid
|
|
// * Ranks above 100.0 are invalid
|
|
|
|
// THEN: The builder will crash in either case
|
|
EXPECT_DEATH(SarifReportingConfiguration::create().setRank(-1.0),
|
|
::testing::HasSubstr("Rule rank cannot be smaller than 0.0"));
|
|
EXPECT_DEATH(SarifReportingConfiguration::create().setRank(101.0),
|
|
::testing::HasSubstr("Rule rank cannot be larger than 100.0"));
|
|
}
|
|
|
|
TEST_F(SarifDocumentWriterTest, creatingResultWithDisabledRuleWillCrash) {
|
|
#if defined(NDEBUG) || !GTEST_HAS_DEATH_TEST
|
|
GTEST_SKIP() << "This death test is only available for debug builds.";
|
|
#endif
|
|
|
|
// GIVEN:
|
|
SarifDocumentWriter Writer{SourceMgr};
|
|
|
|
// WHEN:
|
|
// A disabled Rule is created, and a result is create referencing this rule
|
|
const auto &Config = SarifReportingConfiguration::create().disable();
|
|
auto RuleIdx =
|
|
Writer.createRule(SarifRule::create().setDefaultConfiguration(Config));
|
|
const SarifResult &Result = SarifResult::create(RuleIdx);
|
|
|
|
// THEN:
|
|
// SarifResult::create(...) will produce a crash
|
|
ASSERT_DEATH(
|
|
Writer.appendResult(Result),
|
|
::testing::HasSubstr("Cannot add a result referencing a disabled Rule"));
|
|
}
|
|
|
|
// Test adding rule and result shows up in the final document
|
|
TEST_F(SarifDocumentWriterTest, addingResultWithValidRuleAndRunIsOk) {
|
|
// GIVEN:
|
|
SarifDocumentWriter Writer{SourceMgr};
|
|
const SarifRule &Rule =
|
|
SarifRule::create()
|
|
.setRuleId("clang.unittest")
|
|
.setDescription("Example rule created during unit tests")
|
|
.setName("clang unit test");
|
|
|
|
// WHEN:
|
|
Writer.createRun("sarif test", "sarif test runner");
|
|
unsigned RuleIdx = Writer.createRule(Rule);
|
|
const SarifResult &Result = SarifResult::create(RuleIdx);
|
|
|
|
Writer.appendResult(Result);
|
|
const llvm::json::Object &Doc = Writer.createDocument();
|
|
|
|
// THEN:
|
|
// A document with a valid schema and version exists
|
|
ASSERT_THAT(Doc.get("$schema"), ::testing::NotNull());
|
|
ASSERT_THAT(Doc.get("version"), ::testing::NotNull());
|
|
const llvm::json::Array *Runs = Doc.getArray("runs");
|
|
|
|
// A run exists on this document
|
|
ASSERT_THAT(Runs, ::testing::NotNull());
|
|
ASSERT_EQ(Runs->size(), 1UL);
|
|
const llvm::json::Object *TheRun = Runs->back().getAsObject();
|
|
|
|
// The run has slots for tools, results, rules and artifacts
|
|
ASSERT_THAT(TheRun->get("tool"), ::testing::NotNull());
|
|
ASSERT_THAT(TheRun->get("results"), ::testing::NotNull());
|
|
ASSERT_THAT(TheRun->get("artifacts"), ::testing::NotNull());
|
|
const llvm::json::Object *Driver =
|
|
TheRun->getObject("tool")->getObject("driver");
|
|
const llvm::json::Array *Results = TheRun->getArray("results");
|
|
const llvm::json::Array *Artifacts = TheRun->getArray("artifacts");
|
|
|
|
// The tool is as expected
|
|
ASSERT_TRUE(Driver->getString("name").has_value());
|
|
ASSERT_TRUE(Driver->getString("fullName").has_value());
|
|
|
|
EXPECT_EQ(*Driver->getString("name"), "sarif test");
|
|
EXPECT_EQ(*Driver->getString("fullName"), "sarif test runner");
|
|
|
|
// The results are as expected
|
|
EXPECT_EQ(Results->size(), 1UL);
|
|
|
|
// The artifacts are as expected
|
|
EXPECT_TRUE(Artifacts->empty());
|
|
}
|
|
|
|
TEST_F(SarifDocumentWriterTest, checkSerializingResultsWithDefaultRuleConfig) {
|
|
// GIVEN:
|
|
const std::string ExpectedOutput =
|
|
R"({"$schema":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json","runs":[{"artifacts":[],"columnKind":"unicodeCodePoints","results":[{"level":"warning","message":{"text":""},"ruleId":"clang.unittest","ruleIndex":0}],"tool":{"driver":{"fullName":"sarif test runner","informationUri":"https://clang.llvm.org/docs/UsersManual.html","language":"en-US","name":"sarif test","rules":[{"defaultConfiguration":{"enabled":true,"level":"warning","rank":-1},"fullDescription":{"text":"Example rule created during unit tests"},"id":"clang.unittest","name":"clang unit test"}],"version":"1.0.0"}}}],"version":"2.1.0"})";
|
|
|
|
SarifDocumentWriter Writer{SourceMgr};
|
|
const SarifRule &Rule =
|
|
SarifRule::create()
|
|
.setRuleId("clang.unittest")
|
|
.setDescription("Example rule created during unit tests")
|
|
.setName("clang unit test");
|
|
|
|
// WHEN: A run contains a result
|
|
Writer.createRun("sarif test", "sarif test runner", "1.0.0");
|
|
unsigned RuleIdx = Writer.createRule(Rule);
|
|
const SarifResult &Result = SarifResult::create(RuleIdx);
|
|
Writer.appendResult(Result);
|
|
std::string Output = serializeSarifDocument(Writer.createDocument());
|
|
|
|
// THEN:
|
|
ASSERT_THAT(Output, ::testing::StrEq(ExpectedOutput));
|
|
}
|
|
|
|
TEST_F(SarifDocumentWriterTest, checkSerializingResultsWithCustomRuleConfig) {
|
|
// GIVEN:
|
|
const std::string ExpectedOutput =
|
|
R"({"$schema":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json","runs":[{"artifacts":[],"columnKind":"unicodeCodePoints","results":[{"level":"error","message":{"text":""},"ruleId":"clang.unittest","ruleIndex":0}],"tool":{"driver":{"fullName":"sarif test runner","informationUri":"https://clang.llvm.org/docs/UsersManual.html","language":"en-US","name":"sarif test","rules":[{"defaultConfiguration":{"enabled":true,"level":"error","rank":35.5},"fullDescription":{"text":"Example rule created during unit tests"},"id":"clang.unittest","name":"clang unit test"}],"version":"1.0.0"}}}],"version":"2.1.0"})";
|
|
|
|
SarifDocumentWriter Writer{SourceMgr};
|
|
const SarifRule &Rule =
|
|
SarifRule::create()
|
|
.setRuleId("clang.unittest")
|
|
.setDescription("Example rule created during unit tests")
|
|
.setName("clang unit test")
|
|
.setDefaultConfiguration(SarifReportingConfiguration::create()
|
|
.setLevel(SarifResultLevel::Error)
|
|
.setRank(35.5));
|
|
|
|
// WHEN: A run contains a result
|
|
Writer.createRun("sarif test", "sarif test runner", "1.0.0");
|
|
unsigned RuleIdx = Writer.createRule(Rule);
|
|
const SarifResult &Result = SarifResult::create(RuleIdx);
|
|
Writer.appendResult(Result);
|
|
std::string Output = serializeSarifDocument(Writer.createDocument());
|
|
|
|
// THEN:
|
|
ASSERT_THAT(Output, ::testing::StrEq(ExpectedOutput));
|
|
}
|
|
|
|
// Check that serializing artifacts from results produces valid SARIF
|
|
TEST_F(SarifDocumentWriterTest, checkSerializingArtifacts) {
|
|
// GIVEN:
|
|
const std::string ExpectedOutput =
|
|
R"({"$schema":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json","runs":[{"artifacts":[{"length":40,"location":{"index":0,"uri":"file:///main.cpp"},"mimeType":"text/plain","roles":["resultFile"]}],"columnKind":"unicodeCodePoints","results":[{"level":"error","locations":[{"physicalLocation":{"artifactLocation":{"index":0,"uri":"file:///main.cpp"},"region":{"endColumn":14,"startColumn":14,"startLine":3}}}],"message":{"text":"expected ';' after top level declarator"},"ruleId":"clang.unittest","ruleIndex":0}],"tool":{"driver":{"fullName":"sarif test runner","informationUri":"https://clang.llvm.org/docs/UsersManual.html","language":"en-US","name":"sarif test","rules":[{"defaultConfiguration":{"enabled":true,"level":"warning","rank":-1},"fullDescription":{"text":"Example rule created during unit tests"},"id":"clang.unittest","name":"clang unit test"}],"version":"1.0.0"}}}],"version":"2.1.0"})";
|
|
|
|
SarifDocumentWriter Writer{SourceMgr};
|
|
const SarifRule &Rule =
|
|
SarifRule::create()
|
|
.setRuleId("clang.unittest")
|
|
.setDescription("Example rule created during unit tests")
|
|
.setName("clang unit test");
|
|
|
|
// WHEN: A result is added with valid source locations for its diagnostics
|
|
Writer.createRun("sarif test", "sarif test runner", "1.0.0");
|
|
unsigned RuleIdx = Writer.createRule(Rule);
|
|
|
|
llvm::SmallVector<CharSourceRange, 1> DiagLocs;
|
|
const char *SourceText = "int foo = 0;\n"
|
|
"int bar = 1;\n"
|
|
"float x = 0.0\n";
|
|
|
|
FileID MainFileID =
|
|
registerSource("/main.cpp", SourceText, /* IsMainFile = */ true);
|
|
CharSourceRange SourceCSR =
|
|
getFakeCharSourceRange(MainFileID, {3, 14}, {3, 14});
|
|
|
|
DiagLocs.push_back(SourceCSR);
|
|
|
|
const SarifResult &Result =
|
|
SarifResult::create(RuleIdx)
|
|
.setLocations(DiagLocs)
|
|
.setDiagnosticMessage("expected ';' after top level declarator")
|
|
.setDiagnosticLevel(SarifResultLevel::Error);
|
|
Writer.appendResult(Result);
|
|
std::string Output = serializeSarifDocument(Writer.createDocument());
|
|
|
|
// THEN: Assert that the serialized SARIF is as expected
|
|
ASSERT_THAT(Output, ::testing::StrEq(ExpectedOutput));
|
|
}
|
|
|
|
TEST_F(SarifDocumentWriterTest, checkSerializingCodeflows) {
|
|
// GIVEN:
|
|
const std::string ExpectedOutput =
|
|
R"({"$schema":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json","runs":[{"artifacts":[{"length":28,"location":{"index":3,"uri":"file:///test-header-3.h"},"mimeType":"text/plain","roles":["resultFile"]},{"length":41,"location":{"index":0,"uri":"file:///main.cpp"},"mimeType":"text/plain","roles":["resultFile"]},{"length":30,"location":{"index":2,"uri":"file:///test-header-2.h"},"mimeType":"text/plain","roles":["resultFile"]},{"length":27,"location":{"index":1,"uri":"file:///test-header-1.h"},"mimeType":"text/plain","roles":["resultFile"]}],"columnKind":"unicodeCodePoints","results":[{"codeFlows":[{"threadFlows":[{"locations":[{"importance":"essential","location":{"message":{"text":"Message #1"},"physicalLocation":{"artifactLocation":{"index":1,"uri":"file:///test-header-1.h"},"region":{"endColumn":8,"endLine":2,"startColumn":1,"startLine":1}}}},{"importance":"important","location":{"message":{"text":"Message #2"},"physicalLocation":{"artifactLocation":{"index":2,"uri":"file:///test-header-2.h"},"region":{"endColumn":8,"endLine":2,"startColumn":1,"startLine":1}}}},{"importance":"unimportant","location":{"message":{"text":"Message #3"},"physicalLocation":{"artifactLocation":{"index":3,"uri":"file:///test-header-3.h"},"region":{"endColumn":8,"endLine":2,"startColumn":1,"startLine":1}}}}]}]}],"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"index":0,"uri":"file:///main.cpp"},"region":{"endColumn":8,"endLine":2,"startColumn":5,"startLine":2}}}],"message":{"text":"Redefinition of 'foo'"},"ruleId":"clang.unittest","ruleIndex":0}],"tool":{"driver":{"fullName":"sarif test runner","informationUri":"https://clang.llvm.org/docs/UsersManual.html","language":"en-US","name":"sarif test","rules":[{"defaultConfiguration":{"enabled":true,"level":"warning","rank":-1},"fullDescription":{"text":"Example rule created during unit tests"},"id":"clang.unittest","name":"clang unit test"}],"version":"1.0.0"}}}],"version":"2.1.0"})";
|
|
|
|
const char *SourceText = "int foo = 0;\n"
|
|
"int foo = 1;\n"
|
|
"float x = 0.0;\n";
|
|
FileID MainFileID =
|
|
registerSource("/main.cpp", SourceText, /* IsMainFile = */ true);
|
|
CharSourceRange DiagLoc{getFakeCharSourceRange(MainFileID, {2, 5}, {2, 8})};
|
|
|
|
SarifDocumentWriter Writer{SourceMgr};
|
|
const SarifRule &Rule =
|
|
SarifRule::create()
|
|
.setRuleId("clang.unittest")
|
|
.setDescription("Example rule created during unit tests")
|
|
.setName("clang unit test");
|
|
|
|
constexpr unsigned int NumCases = 3;
|
|
llvm::SmallVector<ThreadFlow, NumCases> Threadflows;
|
|
const char *HeaderTexts[NumCases]{("#pragma once\n"
|
|
"#include <foo>"),
|
|
("#ifndef FOO\n"
|
|
"#define FOO\n"
|
|
"#endif"),
|
|
("#ifdef FOO\n"
|
|
"#undef FOO\n"
|
|
"#endif")};
|
|
const char *HeaderNames[NumCases]{"/test-header-1.h", "/test-header-2.h",
|
|
"/test-header-3.h"};
|
|
ThreadFlowImportance Importances[NumCases]{ThreadFlowImportance::Essential,
|
|
ThreadFlowImportance::Important,
|
|
ThreadFlowImportance::Unimportant};
|
|
for (size_t Idx = 0; Idx != NumCases; ++Idx) {
|
|
FileID FID = registerSource(HeaderNames[Idx], HeaderTexts[Idx]);
|
|
CharSourceRange &&CSR = getFakeCharSourceRange(FID, {1, 1}, {2, 8});
|
|
std::string Message = llvm::formatv("Message #{0}", Idx + 1);
|
|
ThreadFlow Item = ThreadFlow::create()
|
|
.setRange(CSR)
|
|
.setImportance(Importances[Idx])
|
|
.setMessage(Message);
|
|
Threadflows.push_back(Item);
|
|
}
|
|
|
|
// WHEN: A result containing code flows and diagnostic locations is added
|
|
Writer.createRun("sarif test", "sarif test runner", "1.0.0");
|
|
unsigned RuleIdx = Writer.createRule(Rule);
|
|
const SarifResult &Result =
|
|
SarifResult::create(RuleIdx)
|
|
.setLocations({DiagLoc})
|
|
.setDiagnosticMessage("Redefinition of 'foo'")
|
|
.setThreadFlows(Threadflows)
|
|
.setDiagnosticLevel(SarifResultLevel::Warning);
|
|
Writer.appendResult(Result);
|
|
std::string Output = serializeSarifDocument(Writer.createDocument());
|
|
|
|
// THEN: Assert that the serialized SARIF is as expected
|
|
ASSERT_THAT(Output, ::testing::StrEq(ExpectedOutput));
|
|
}
|
|
|
|
} // namespace
|