blob: 1870f834178cc4ab7158d040566c329127b9d9e5 [file]
//===- SARIFFormatTest.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 "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticIDs.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/FileSystemOptions.h"
#include "clang/Basic/Sarif.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/Version.h"
#include "clang/ScalableStaticAnalysis/SourceTransformation/SARIFTransformationReportFormat.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
using namespace llvm;
using namespace clang;
using namespace ssaf;
namespace {
struct TempPath {
SmallString<128> Path;
TempPath(StringRef Suffix) {
sys::fs::createUniquePath("ssaf-sarif-%%%%%%." + Suffix, Path,
/*MakeAbsolute=*/true);
}
~TempPath() { sys::fs::remove(Path); }
};
class SARIFFormatTest : public ::testing::Test {
protected:
SARIFFormatTest()
: Diags(DiagnosticIDs::create(), DiagOpts, new IgnoringDiagConsumer()),
VFS(makeIntrusiveRefCnt<vfs::InMemoryFileSystem>()),
FileMgr(FileSystemOptions(), VFS), SourceMgr(Diags, FileMgr) {}
json::Value writeAndParse(const ReportDocument &Doc) {
TempPath TP("sarif");
EXPECT_THAT_ERROR(writeSARIFTransformationReport(Doc, TP.Path),
Succeeded());
auto BufferOrErr = MemoryBuffer::getFile(TP.Path);
EXPECT_TRUE(static_cast<bool>(BufferOrErr));
auto ParsedOrErr = json::parse((*BufferOrErr)->getBuffer());
EXPECT_THAT_EXPECTED(ParsedOrErr, Succeeded());
return std::move(*ParsedOrErr);
}
DiagnosticOptions DiagOpts;
DiagnosticsEngine Diags;
IntrusiveRefCntPtr<vfs::InMemoryFileSystem> VFS;
FileManager FileMgr;
SourceManager SourceMgr;
};
TEST_F(SARIFFormatTest, ToolDriverNameAndVersion) {
ReportDocument Doc{"my-transformation", SourceMgr, {}};
json::Value V = writeAndParse(Doc);
const json::Object *Root = V.getAsObject();
ASSERT_NE(Root, nullptr);
const json::Array *Runs = Root->getArray("runs");
ASSERT_NE(Runs, nullptr);
ASSERT_EQ(Runs->size(), 1u);
const json::Object *Driver =
(*Runs)[0].getAsObject()->getObject("tool")->getObject("driver");
ASSERT_NE(Driver, nullptr);
EXPECT_EQ(*Driver->getString("name"), "clang-ssaf");
EXPECT_NE(Driver->getString("fullName")->find("my-transformation"),
StringRef::npos);
EXPECT_EQ(*Driver->getString("version"), CLANG_VERSION_STRING);
}
TEST_F(SARIFFormatTest, LevelMapping) {
ReportDocument Doc{"t",
SourceMgr,
{
{"r-note", clang::SarifResultLevel::Note, {}, "n"},
{"r-warn", clang::SarifResultLevel::Warning, {}, "w"},
{"r-error", clang::SarifResultLevel::Error, {}, "e"},
{"r-none", clang::SarifResultLevel::None, {}, "x"},
}};
json::Value V = writeAndParse(Doc);
const json::Array *Results =
V.getAsObject()->getArray("runs")->front().getAsObject()->getArray(
"results");
ASSERT_NE(Results, nullptr);
ASSERT_EQ(Results->size(), 4u);
EXPECT_EQ(*(*Results)[0].getAsObject()->getString("level"), "note");
EXPECT_EQ(*(*Results)[1].getAsObject()->getString("level"), "warning");
EXPECT_EQ(*(*Results)[2].getAsObject()->getString("level"), "error");
EXPECT_EQ(*(*Results)[3].getAsObject()->getString("level"), "none");
}
TEST_F(SARIFFormatTest, InvalidRangeOmitsLocations) {
ReportDocument Doc{
"t",
SourceMgr,
{{"r", clang::SarifResultLevel::Note, clang::CharSourceRange{}, "msg"}}};
json::Value V = writeAndParse(Doc);
const json::Object *Result = V.getAsObject()
->getArray("runs")
->front()
.getAsObject()
->getArray("results")
->front()
.getAsObject();
EXPECT_EQ(Result->get("locations"), nullptr);
}
TEST_F(SARIFFormatTest, EmptyResultsValidDocument) {
ReportDocument Doc{"t", SourceMgr, {}};
json::Value V = writeAndParse(Doc);
const json::Object *Run =
V.getAsObject()->getArray("runs")->front().getAsObject();
// SARIF allows the results array to be absent or empty for an empty run.
if (const json::Array *Results = Run->getArray("results"))
EXPECT_TRUE(Results->empty());
}
TEST_F(SARIFFormatTest, NoFixKey) {
ReportDocument Doc{
"t", SourceMgr, {{"r", clang::SarifResultLevel::Warning, {}, "msg"}}};
json::Value V = writeAndParse(Doc);
const json::Object *Result = V.getAsObject()
->getArray("runs")
->front()
.getAsObject()
->getArray("results")
->front()
.getAsObject();
EXPECT_EQ(Result->get("fix"), nullptr);
EXPECT_EQ(Result->get("fixes"), nullptr);
}
TEST_F(SARIFFormatTest, EmptyRuleIdAccepted) {
ReportDocument Doc{
"t", SourceMgr, {{"", clang::SarifResultLevel::Note, {}, "m"}}};
json::Value V = writeAndParse(Doc);
const json::Object *Result = V.getAsObject()
->getArray("runs")
->front()
.getAsObject()
->getArray("results")
->front()
.getAsObject();
ASSERT_NE(Result->getString("ruleId"), std::nullopt);
EXPECT_EQ(*Result->getString("ruleId"), "");
}
TEST_F(SARIFFormatTest, DistinctRuleIdsDeduplicated) {
ReportDocument Doc{"t",
SourceMgr,
{
{"a", clang::SarifResultLevel::Note, {}, "m1"},
{"b", clang::SarifResultLevel::Note, {}, "m2"},
{"a", clang::SarifResultLevel::Note, {}, "m3"},
}};
json::Value V = writeAndParse(Doc);
const json::Array *Rules = V.getAsObject()
->getArray("runs")
->front()
.getAsObject()
->getObject("tool")
->getObject("driver")
->getArray("rules");
ASSERT_NE(Rules, nullptr);
EXPECT_EQ(Rules->size(), 2u);
}
} // namespace