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