1*12c85518Srobert //===-- clang/Basic/Sarif.cpp - SarifDocumentWriter class definition ------===//
2*12c85518Srobert //
3*12c85518Srobert // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*12c85518Srobert // See https://llvm.org/LICENSE.txt for license information.
5*12c85518Srobert // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*12c85518Srobert //
7*12c85518Srobert //===----------------------------------------------------------------------===//
8*12c85518Srobert ///
9*12c85518Srobert /// \file
10*12c85518Srobert /// This file contains the declaration of the SARIFDocumentWriter class, and
11*12c85518Srobert /// associated builders such as:
12*12c85518Srobert /// - \ref SarifArtifact
13*12c85518Srobert /// - \ref SarifArtifactLocation
14*12c85518Srobert /// - \ref SarifRule
15*12c85518Srobert /// - \ref SarifResult
16*12c85518Srobert //===----------------------------------------------------------------------===//
17*12c85518Srobert #include "clang/Basic/Sarif.h"
18*12c85518Srobert #include "clang/Basic/SourceLocation.h"
19*12c85518Srobert #include "clang/Basic/SourceManager.h"
20*12c85518Srobert #include "llvm/ADT/ArrayRef.h"
21*12c85518Srobert #include "llvm/ADT/STLExtras.h"
22*12c85518Srobert #include "llvm/ADT/StringMap.h"
23*12c85518Srobert #include "llvm/ADT/StringRef.h"
24*12c85518Srobert #include "llvm/Support/ConvertUTF.h"
25*12c85518Srobert #include "llvm/Support/JSON.h"
26*12c85518Srobert #include "llvm/Support/Path.h"
27*12c85518Srobert
28*12c85518Srobert #include <optional>
29*12c85518Srobert #include <string>
30*12c85518Srobert #include <utility>
31*12c85518Srobert
32*12c85518Srobert using namespace clang;
33*12c85518Srobert using namespace llvm;
34*12c85518Srobert
35*12c85518Srobert using clang::detail::SarifArtifact;
36*12c85518Srobert using clang::detail::SarifArtifactLocation;
37*12c85518Srobert
getFileName(const FileEntry & FE)38*12c85518Srobert static StringRef getFileName(const FileEntry &FE) {
39*12c85518Srobert StringRef Filename = FE.tryGetRealPathName();
40*12c85518Srobert if (Filename.empty())
41*12c85518Srobert Filename = FE.getName();
42*12c85518Srobert return Filename;
43*12c85518Srobert }
44*12c85518Srobert /// \name URI
45*12c85518Srobert /// @{
46*12c85518Srobert
47*12c85518Srobert /// \internal
48*12c85518Srobert /// \brief
49*12c85518Srobert /// Return the RFC3986 encoding of the input character.
50*12c85518Srobert ///
51*12c85518Srobert /// \param C Character to encode to RFC3986.
52*12c85518Srobert ///
53*12c85518Srobert /// \return The RFC3986 representation of \c C.
percentEncodeURICharacter(char C)54*12c85518Srobert static std::string percentEncodeURICharacter(char C) {
55*12c85518Srobert // RFC 3986 claims alpha, numeric, and this handful of
56*12c85518Srobert // characters are not reserved for the path component and
57*12c85518Srobert // should be written out directly. Otherwise, percent
58*12c85518Srobert // encode the character and write that out instead of the
59*12c85518Srobert // reserved character.
60*12c85518Srobert if (llvm::isAlnum(C) ||
61*12c85518Srobert StringRef::npos != StringRef("-._~:@!$&'()*+,;=").find(C))
62*12c85518Srobert return std::string(&C, 1);
63*12c85518Srobert return "%" + llvm::toHex(StringRef(&C, 1));
64*12c85518Srobert }
65*12c85518Srobert
66*12c85518Srobert /// \internal
67*12c85518Srobert /// \brief Return a URI representing the given file name.
68*12c85518Srobert ///
69*12c85518Srobert /// \param Filename The filename to be represented as URI.
70*12c85518Srobert ///
71*12c85518Srobert /// \return RFC3986 URI representing the input file name.
fileNameToURI(StringRef Filename)72*12c85518Srobert static std::string fileNameToURI(StringRef Filename) {
73*12c85518Srobert SmallString<32> Ret = StringRef("file://");
74*12c85518Srobert
75*12c85518Srobert // Get the root name to see if it has a URI authority.
76*12c85518Srobert StringRef Root = sys::path::root_name(Filename);
77*12c85518Srobert if (Root.startswith("//")) {
78*12c85518Srobert // There is an authority, so add it to the URI.
79*12c85518Srobert Ret += Root.drop_front(2).str();
80*12c85518Srobert } else if (!Root.empty()) {
81*12c85518Srobert // There is no authority, so end the component and add the root to the URI.
82*12c85518Srobert Ret += Twine("/" + Root).str();
83*12c85518Srobert }
84*12c85518Srobert
85*12c85518Srobert auto Iter = sys::path::begin(Filename), End = sys::path::end(Filename);
86*12c85518Srobert assert(Iter != End && "Expected there to be a non-root path component.");
87*12c85518Srobert // Add the rest of the path components, encoding any reserved characters;
88*12c85518Srobert // we skip past the first path component, as it was handled it above.
89*12c85518Srobert std::for_each(++Iter, End, [&Ret](StringRef Component) {
90*12c85518Srobert // For reasons unknown to me, we may get a backslash with Windows native
91*12c85518Srobert // paths for the initial backslash following the drive component, which
92*12c85518Srobert // we need to ignore as a URI path part.
93*12c85518Srobert if (Component == "\\")
94*12c85518Srobert return;
95*12c85518Srobert
96*12c85518Srobert // Add the separator between the previous path part and the one being
97*12c85518Srobert // currently processed.
98*12c85518Srobert Ret += "/";
99*12c85518Srobert
100*12c85518Srobert // URI encode the part.
101*12c85518Srobert for (char C : Component) {
102*12c85518Srobert Ret += percentEncodeURICharacter(C);
103*12c85518Srobert }
104*12c85518Srobert });
105*12c85518Srobert
106*12c85518Srobert return std::string(Ret);
107*12c85518Srobert }
108*12c85518Srobert /// @}
109*12c85518Srobert
110*12c85518Srobert /// \brief Calculate the column position expressed in the number of UTF-8 code
111*12c85518Srobert /// points from column start to the source location
112*12c85518Srobert ///
113*12c85518Srobert /// \param Loc The source location whose column needs to be calculated.
114*12c85518Srobert /// \param TokenLen Optional hint for when the token is multiple bytes long.
115*12c85518Srobert ///
116*12c85518Srobert /// \return The column number as a UTF-8 aware byte offset from column start to
117*12c85518Srobert /// the effective source location.
adjustColumnPos(FullSourceLoc Loc,unsigned int TokenLen=0)118*12c85518Srobert static unsigned int adjustColumnPos(FullSourceLoc Loc,
119*12c85518Srobert unsigned int TokenLen = 0) {
120*12c85518Srobert assert(!Loc.isInvalid() && "invalid Loc when adjusting column position");
121*12c85518Srobert
122*12c85518Srobert std::pair<FileID, unsigned> LocInfo = Loc.getDecomposedExpansionLoc();
123*12c85518Srobert std::optional<MemoryBufferRef> Buf =
124*12c85518Srobert Loc.getManager().getBufferOrNone(LocInfo.first);
125*12c85518Srobert assert(Buf && "got an invalid buffer for the location's file");
126*12c85518Srobert assert(Buf->getBufferSize() >= (LocInfo.second + TokenLen) &&
127*12c85518Srobert "token extends past end of buffer?");
128*12c85518Srobert
129*12c85518Srobert // Adjust the offset to be the start of the line, since we'll be counting
130*12c85518Srobert // Unicode characters from there until our column offset.
131*12c85518Srobert unsigned int Off = LocInfo.second - (Loc.getExpansionColumnNumber() - 1);
132*12c85518Srobert unsigned int Ret = 1;
133*12c85518Srobert while (Off < (LocInfo.second + TokenLen)) {
134*12c85518Srobert Off += getNumBytesForUTF8(Buf->getBuffer()[Off]);
135*12c85518Srobert Ret++;
136*12c85518Srobert }
137*12c85518Srobert
138*12c85518Srobert return Ret;
139*12c85518Srobert }
140*12c85518Srobert
141*12c85518Srobert /// \name SARIF Utilities
142*12c85518Srobert /// @{
143*12c85518Srobert
144*12c85518Srobert /// \internal
createMessage(StringRef Text)145*12c85518Srobert json::Object createMessage(StringRef Text) {
146*12c85518Srobert return json::Object{{"text", Text.str()}};
147*12c85518Srobert }
148*12c85518Srobert
149*12c85518Srobert /// \internal
150*12c85518Srobert /// \pre CharSourceRange must be a token range
createTextRegion(const SourceManager & SM,const CharSourceRange & R)151*12c85518Srobert static json::Object createTextRegion(const SourceManager &SM,
152*12c85518Srobert const CharSourceRange &R) {
153*12c85518Srobert FullSourceLoc BeginCharLoc{R.getBegin(), SM};
154*12c85518Srobert FullSourceLoc EndCharLoc{R.getEnd(), SM};
155*12c85518Srobert json::Object Region{{"startLine", BeginCharLoc.getExpansionLineNumber()},
156*12c85518Srobert {"startColumn", adjustColumnPos(BeginCharLoc)}};
157*12c85518Srobert
158*12c85518Srobert if (BeginCharLoc == EndCharLoc) {
159*12c85518Srobert Region["endColumn"] = adjustColumnPos(BeginCharLoc);
160*12c85518Srobert } else {
161*12c85518Srobert Region["endLine"] = EndCharLoc.getExpansionLineNumber();
162*12c85518Srobert Region["endColumn"] = adjustColumnPos(EndCharLoc);
163*12c85518Srobert }
164*12c85518Srobert return Region;
165*12c85518Srobert }
166*12c85518Srobert
createLocation(json::Object && PhysicalLocation,StringRef Message="")167*12c85518Srobert static json::Object createLocation(json::Object &&PhysicalLocation,
168*12c85518Srobert StringRef Message = "") {
169*12c85518Srobert json::Object Ret{{"physicalLocation", std::move(PhysicalLocation)}};
170*12c85518Srobert if (!Message.empty())
171*12c85518Srobert Ret.insert({"message", createMessage(Message)});
172*12c85518Srobert return Ret;
173*12c85518Srobert }
174*12c85518Srobert
importanceToStr(ThreadFlowImportance I)175*12c85518Srobert static StringRef importanceToStr(ThreadFlowImportance I) {
176*12c85518Srobert switch (I) {
177*12c85518Srobert case ThreadFlowImportance::Important:
178*12c85518Srobert return "important";
179*12c85518Srobert case ThreadFlowImportance::Essential:
180*12c85518Srobert return "essential";
181*12c85518Srobert case ThreadFlowImportance::Unimportant:
182*12c85518Srobert return "unimportant";
183*12c85518Srobert }
184*12c85518Srobert llvm_unreachable("Fully covered switch is not so fully covered");
185*12c85518Srobert }
186*12c85518Srobert
resultLevelToStr(SarifResultLevel R)187*12c85518Srobert static StringRef resultLevelToStr(SarifResultLevel R) {
188*12c85518Srobert switch (R) {
189*12c85518Srobert case SarifResultLevel::None:
190*12c85518Srobert return "none";
191*12c85518Srobert case SarifResultLevel::Note:
192*12c85518Srobert return "note";
193*12c85518Srobert case SarifResultLevel::Warning:
194*12c85518Srobert return "warning";
195*12c85518Srobert case SarifResultLevel::Error:
196*12c85518Srobert return "error";
197*12c85518Srobert }
198*12c85518Srobert llvm_unreachable("Potentially un-handled SarifResultLevel. "
199*12c85518Srobert "Is the switch not fully covered?");
200*12c85518Srobert }
201*12c85518Srobert
202*12c85518Srobert static json::Object
createThreadFlowLocation(json::Object && Location,const ThreadFlowImportance & Importance)203*12c85518Srobert createThreadFlowLocation(json::Object &&Location,
204*12c85518Srobert const ThreadFlowImportance &Importance) {
205*12c85518Srobert return json::Object{{"location", std::move(Location)},
206*12c85518Srobert {"importance", importanceToStr(Importance)}};
207*12c85518Srobert }
208*12c85518Srobert /// @}
209*12c85518Srobert
210*12c85518Srobert json::Object
createPhysicalLocation(const CharSourceRange & R)211*12c85518Srobert SarifDocumentWriter::createPhysicalLocation(const CharSourceRange &R) {
212*12c85518Srobert assert(R.isValid() &&
213*12c85518Srobert "Cannot create a physicalLocation from invalid SourceRange!");
214*12c85518Srobert assert(R.isCharRange() &&
215*12c85518Srobert "Cannot create a physicalLocation from a token range!");
216*12c85518Srobert FullSourceLoc Start{R.getBegin(), SourceMgr};
217*12c85518Srobert const FileEntry *FE = Start.getExpansionLoc().getFileEntry();
218*12c85518Srobert assert(FE != nullptr && "Diagnostic does not exist within a valid file!");
219*12c85518Srobert
220*12c85518Srobert const std::string &FileURI = fileNameToURI(getFileName(*FE));
221*12c85518Srobert auto I = CurrentArtifacts.find(FileURI);
222*12c85518Srobert
223*12c85518Srobert if (I == CurrentArtifacts.end()) {
224*12c85518Srobert uint32_t Idx = static_cast<uint32_t>(CurrentArtifacts.size());
225*12c85518Srobert const SarifArtifactLocation &Location =
226*12c85518Srobert SarifArtifactLocation::create(FileURI).setIndex(Idx);
227*12c85518Srobert const SarifArtifact &Artifact = SarifArtifact::create(Location)
228*12c85518Srobert .setRoles({"resultFile"})
229*12c85518Srobert .setLength(FE->getSize())
230*12c85518Srobert .setMimeType("text/plain");
231*12c85518Srobert auto StatusIter = CurrentArtifacts.insert({FileURI, Artifact});
232*12c85518Srobert // If inserted, ensure the original iterator points to the newly inserted
233*12c85518Srobert // element, so it can be used downstream.
234*12c85518Srobert if (StatusIter.second)
235*12c85518Srobert I = StatusIter.first;
236*12c85518Srobert }
237*12c85518Srobert assert(I != CurrentArtifacts.end() && "Failed to insert new artifact");
238*12c85518Srobert const SarifArtifactLocation &Location = I->second.Location;
239*12c85518Srobert json::Object ArtifactLocationObject{{"uri", Location.URI}};
240*12c85518Srobert if (Location.Index.has_value())
241*12c85518Srobert ArtifactLocationObject["index"] = *Location.Index;
242*12c85518Srobert return json::Object{{{"artifactLocation", std::move(ArtifactLocationObject)},
243*12c85518Srobert {"region", createTextRegion(SourceMgr, R)}}};
244*12c85518Srobert }
245*12c85518Srobert
getCurrentTool()246*12c85518Srobert json::Object &SarifDocumentWriter::getCurrentTool() {
247*12c85518Srobert assert(!Closed && "SARIF Document is closed. "
248*12c85518Srobert "Need to call createRun() before using getcurrentTool!");
249*12c85518Srobert
250*12c85518Srobert // Since Closed = false here, expect there to be at least 1 Run, anything
251*12c85518Srobert // else is an invalid state.
252*12c85518Srobert assert(!Runs.empty() && "There are no runs associated with the document!");
253*12c85518Srobert
254*12c85518Srobert return *Runs.back().getAsObject()->get("tool")->getAsObject();
255*12c85518Srobert }
256*12c85518Srobert
reset()257*12c85518Srobert void SarifDocumentWriter::reset() {
258*12c85518Srobert CurrentRules.clear();
259*12c85518Srobert CurrentArtifacts.clear();
260*12c85518Srobert }
261*12c85518Srobert
endRun()262*12c85518Srobert void SarifDocumentWriter::endRun() {
263*12c85518Srobert // Exit early if trying to close a closed Document.
264*12c85518Srobert if (Closed) {
265*12c85518Srobert reset();
266*12c85518Srobert return;
267*12c85518Srobert }
268*12c85518Srobert
269*12c85518Srobert // Since Closed = false here, expect there to be at least 1 Run, anything
270*12c85518Srobert // else is an invalid state.
271*12c85518Srobert assert(!Runs.empty() && "There are no runs associated with the document!");
272*12c85518Srobert
273*12c85518Srobert // Flush all the rules.
274*12c85518Srobert json::Object &Tool = getCurrentTool();
275*12c85518Srobert json::Array Rules;
276*12c85518Srobert for (const SarifRule &R : CurrentRules) {
277*12c85518Srobert json::Object Config{
278*12c85518Srobert {"enabled", R.DefaultConfiguration.Enabled},
279*12c85518Srobert {"level", resultLevelToStr(R.DefaultConfiguration.Level)},
280*12c85518Srobert {"rank", R.DefaultConfiguration.Rank}};
281*12c85518Srobert json::Object Rule{
282*12c85518Srobert {"name", R.Name},
283*12c85518Srobert {"id", R.Id},
284*12c85518Srobert {"fullDescription", json::Object{{"text", R.Description}}},
285*12c85518Srobert {"defaultConfiguration", std::move(Config)}};
286*12c85518Srobert if (!R.HelpURI.empty())
287*12c85518Srobert Rule["helpUri"] = R.HelpURI;
288*12c85518Srobert Rules.emplace_back(std::move(Rule));
289*12c85518Srobert }
290*12c85518Srobert json::Object &Driver = *Tool.getObject("driver");
291*12c85518Srobert Driver["rules"] = std::move(Rules);
292*12c85518Srobert
293*12c85518Srobert // Flush all the artifacts.
294*12c85518Srobert json::Object &Run = getCurrentRun();
295*12c85518Srobert json::Array *Artifacts = Run.getArray("artifacts");
296*12c85518Srobert for (const auto &Pair : CurrentArtifacts) {
297*12c85518Srobert const SarifArtifact &A = Pair.getValue();
298*12c85518Srobert json::Object Loc{{"uri", A.Location.URI}};
299*12c85518Srobert if (A.Location.Index.has_value()) {
300*12c85518Srobert Loc["index"] = static_cast<int64_t>(*A.Location.Index);
301*12c85518Srobert }
302*12c85518Srobert json::Object Artifact;
303*12c85518Srobert Artifact["location"] = std::move(Loc);
304*12c85518Srobert if (A.Length.has_value())
305*12c85518Srobert Artifact["length"] = static_cast<int64_t>(*A.Length);
306*12c85518Srobert if (!A.Roles.empty())
307*12c85518Srobert Artifact["roles"] = json::Array(A.Roles);
308*12c85518Srobert if (!A.MimeType.empty())
309*12c85518Srobert Artifact["mimeType"] = A.MimeType;
310*12c85518Srobert if (A.Offset.has_value())
311*12c85518Srobert Artifact["offset"] = *A.Offset;
312*12c85518Srobert Artifacts->push_back(json::Value(std::move(Artifact)));
313*12c85518Srobert }
314*12c85518Srobert
315*12c85518Srobert // Clear, reset temporaries before next run.
316*12c85518Srobert reset();
317*12c85518Srobert
318*12c85518Srobert // Mark the document as closed.
319*12c85518Srobert Closed = true;
320*12c85518Srobert }
321*12c85518Srobert
322*12c85518Srobert json::Array
createThreadFlows(ArrayRef<ThreadFlow> ThreadFlows)323*12c85518Srobert SarifDocumentWriter::createThreadFlows(ArrayRef<ThreadFlow> ThreadFlows) {
324*12c85518Srobert json::Object Ret{{"locations", json::Array{}}};
325*12c85518Srobert json::Array Locs;
326*12c85518Srobert for (const auto &ThreadFlow : ThreadFlows) {
327*12c85518Srobert json::Object PLoc = createPhysicalLocation(ThreadFlow.Range);
328*12c85518Srobert json::Object Loc = createLocation(std::move(PLoc), ThreadFlow.Message);
329*12c85518Srobert Locs.emplace_back(
330*12c85518Srobert createThreadFlowLocation(std::move(Loc), ThreadFlow.Importance));
331*12c85518Srobert }
332*12c85518Srobert Ret["locations"] = std::move(Locs);
333*12c85518Srobert return json::Array{std::move(Ret)};
334*12c85518Srobert }
335*12c85518Srobert
336*12c85518Srobert json::Object
createCodeFlow(ArrayRef<ThreadFlow> ThreadFlows)337*12c85518Srobert SarifDocumentWriter::createCodeFlow(ArrayRef<ThreadFlow> ThreadFlows) {
338*12c85518Srobert return json::Object{{"threadFlows", createThreadFlows(ThreadFlows)}};
339*12c85518Srobert }
340*12c85518Srobert
createRun(StringRef ShortToolName,StringRef LongToolName,StringRef ToolVersion)341*12c85518Srobert void SarifDocumentWriter::createRun(StringRef ShortToolName,
342*12c85518Srobert StringRef LongToolName,
343*12c85518Srobert StringRef ToolVersion) {
344*12c85518Srobert // Clear resources associated with a previous run.
345*12c85518Srobert endRun();
346*12c85518Srobert
347*12c85518Srobert // Signify a new run has begun.
348*12c85518Srobert Closed = false;
349*12c85518Srobert
350*12c85518Srobert json::Object Tool{
351*12c85518Srobert {"driver",
352*12c85518Srobert json::Object{{"name", ShortToolName},
353*12c85518Srobert {"fullName", LongToolName},
354*12c85518Srobert {"language", "en-US"},
355*12c85518Srobert {"version", ToolVersion},
356*12c85518Srobert {"informationUri",
357*12c85518Srobert "https://clang.llvm.org/docs/UsersManual.html"}}}};
358*12c85518Srobert json::Object TheRun{{"tool", std::move(Tool)},
359*12c85518Srobert {"results", {}},
360*12c85518Srobert {"artifacts", {}},
361*12c85518Srobert {"columnKind", "unicodeCodePoints"}};
362*12c85518Srobert Runs.emplace_back(std::move(TheRun));
363*12c85518Srobert }
364*12c85518Srobert
getCurrentRun()365*12c85518Srobert json::Object &SarifDocumentWriter::getCurrentRun() {
366*12c85518Srobert assert(!Closed &&
367*12c85518Srobert "SARIF Document is closed. "
368*12c85518Srobert "Can only getCurrentRun() if document is opened via createRun(), "
369*12c85518Srobert "create a run first");
370*12c85518Srobert
371*12c85518Srobert // Since Closed = false here, expect there to be at least 1 Run, anything
372*12c85518Srobert // else is an invalid state.
373*12c85518Srobert assert(!Runs.empty() && "There are no runs associated with the document!");
374*12c85518Srobert return *Runs.back().getAsObject();
375*12c85518Srobert }
376*12c85518Srobert
createRule(const SarifRule & Rule)377*12c85518Srobert size_t SarifDocumentWriter::createRule(const SarifRule &Rule) {
378*12c85518Srobert size_t Ret = CurrentRules.size();
379*12c85518Srobert CurrentRules.emplace_back(Rule);
380*12c85518Srobert return Ret;
381*12c85518Srobert }
382*12c85518Srobert
appendResult(const SarifResult & Result)383*12c85518Srobert void SarifDocumentWriter::appendResult(const SarifResult &Result) {
384*12c85518Srobert size_t RuleIdx = Result.RuleIdx;
385*12c85518Srobert assert(RuleIdx < CurrentRules.size() &&
386*12c85518Srobert "Trying to reference a rule that doesn't exist");
387*12c85518Srobert const SarifRule &Rule = CurrentRules[RuleIdx];
388*12c85518Srobert assert(Rule.DefaultConfiguration.Enabled &&
389*12c85518Srobert "Cannot add a result referencing a disabled Rule");
390*12c85518Srobert json::Object Ret{{"message", createMessage(Result.DiagnosticMessage)},
391*12c85518Srobert {"ruleIndex", static_cast<int64_t>(RuleIdx)},
392*12c85518Srobert {"ruleId", Rule.Id}};
393*12c85518Srobert if (!Result.Locations.empty()) {
394*12c85518Srobert json::Array Locs;
395*12c85518Srobert for (auto &Range : Result.Locations) {
396*12c85518Srobert Locs.emplace_back(createLocation(createPhysicalLocation(Range)));
397*12c85518Srobert }
398*12c85518Srobert Ret["locations"] = std::move(Locs);
399*12c85518Srobert }
400*12c85518Srobert if (!Result.ThreadFlows.empty())
401*12c85518Srobert Ret["codeFlows"] = json::Array{createCodeFlow(Result.ThreadFlows)};
402*12c85518Srobert
403*12c85518Srobert Ret["level"] = resultLevelToStr(
404*12c85518Srobert Result.LevelOverride.value_or(Rule.DefaultConfiguration.Level));
405*12c85518Srobert
406*12c85518Srobert json::Object &Run = getCurrentRun();
407*12c85518Srobert json::Array *Results = Run.getArray("results");
408*12c85518Srobert Results->emplace_back(std::move(Ret));
409*12c85518Srobert }
410*12c85518Srobert
createDocument()411*12c85518Srobert json::Object SarifDocumentWriter::createDocument() {
412*12c85518Srobert // Flush all temporaries to their destinations if needed.
413*12c85518Srobert endRun();
414*12c85518Srobert
415*12c85518Srobert json::Object Doc{
416*12c85518Srobert {"$schema", SchemaURI},
417*12c85518Srobert {"version", SchemaVersion},
418*12c85518Srobert };
419*12c85518Srobert if (!Runs.empty())
420*12c85518Srobert Doc["runs"] = json::Array(Runs);
421*12c85518Srobert return Doc;
422*12c85518Srobert }
423