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