1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
|
//===- 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);
return Output;
}
class SarifDocumentWriterTest : public ::testing::Test {
protected:
SarifDocumentWriterTest()
: InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem),
FileMgr(FileSystemOptions(), InMemoryFileSystem),
DiagID(new DiagnosticIDs()),
Diags(DiagID, DiagOpts, new IgnoringDiagConsumer()),
SourceMgr(Diags, FileMgr) {}
IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem;
FileManager FileMgr;
IntrusiveRefCntPtr<DiagnosticIDs> DiagID;
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);
FileEntryRef SourceFile =
FileMgr.getVirtualFileRef(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":41,"location":{"index":0,"uri":"file:///main.cpp"},"mimeType":"text/plain","roles":["resultFile"]},{"length":27,"location":{"index":1,"uri":"file:///test-header-1.h"},"mimeType":"text/plain","roles":["resultFile"]},{"length":30,"location":{"index":2,"uri":"file:///test-header-2.h"},"mimeType":"text/plain","roles":["resultFile"]},{"length":28,"location":{"index":3,"uri":"file:///test-header-3.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
|