Clang Project

clang_source_code/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp
1//===--- SarifDiagnostics.cpp - Sarif Diagnostics for Paths -----*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9//  This file defines the SarifDiagnostics object.
10//
11//===----------------------------------------------------------------------===//
12
13#include "clang/Basic/Version.h"
14#include "clang/Lex/Preprocessor.h"
15#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
16#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
17#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"
18#include "llvm/ADT/STLExtras.h"
19#include "llvm/ADT/StringMap.h"
20#include "llvm/Support/JSON.h"
21#include "llvm/Support/Path.h"
22
23using namespace llvm;
24using namespace clang;
25using namespace ento;
26
27namespace {
28class SarifDiagnostics : public PathDiagnosticConsumer {
29  std::string OutputFile;
30
31public:
32  SarifDiagnostics(AnalyzerOptions &, const std::string &Output)
33      : OutputFile(Output) {}
34  ~SarifDiagnostics() override = default;
35
36  void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
37                            FilesMade *FM) override;
38
39  StringRef getName() const override { return "SarifDiagnostics"; }
40  PathGenerationScheme getGenerationScheme() const override { return Minimal; }
41  bool supportsLogicalOpControlFlow() const override { return true; }
42  bool supportsCrossFileDiagnostics() const override { return true; }
43};
44// end anonymous namespace
45
46void ento::createSarifDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
47                                         PathDiagnosticConsumers &C,
48                                         const std::string &Output,
49                                         const Preprocessor &) {
50  C.push_back(new SarifDiagnostics(AnalyzerOptsOutput));
51}
52
53static StringRef getFileName(const FileEntry &FE) {
54  StringRef Filename = FE.tryGetRealPathName();
55  if (Filename.empty())
56    Filename = FE.getName();
57  return Filename;
58}
59
60static std::string percentEncodeURICharacter(char C) {
61  // RFC 3986 claims alpha, numeric, and this handful of
62  // characters are not reserved for the path component and
63  // should be written out directly. Otherwise, percent
64  // encode the character and write that out instead of the
65  // reserved character.
66  if (llvm::isAlnum(C) ||
67      StringRef::npos != StringRef("-._~:@!$&'()*+,;=").find(C))
68    return std::string(&C1);
69  return "%" + llvm::toHex(StringRef(&C, 1));
70}
71
72static std::string fileNameToURI(StringRef Filename) {
73  llvm::SmallString<32Ret = StringRef("file://");
74
75  // Get the root name to see if it has a URI authority.
76  StringRef Root = sys::path::root_name(Filename);
77  if (Root.startswith("//")) {
78    // There is an authority, so add it to the URI.
79    Ret += Root.drop_front(2).str();
80  } else if (!Root.empty()) {
81    // There is no authority, so end the component and add the root to the URI.
82    Ret += Twine("/" + Root).str();
83  }
84
85  auto Iter = sys::path::begin(Filename), End = sys::path::end(Filename);
86   (0) . __assert_fail ("Iter != End && \"Expected there to be a non-root path component.\"", "/home/seafit/code_projects/clang_source/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp", 86, __PRETTY_FUNCTION__))" file_link="../../../../include/assert.h.html#88" macro="true">assert(Iter != End && "Expected there to be a non-root path component.");
87  // Add the rest of the path components, encoding any reserved characters;
88  // we skip past the first path component, as it was handled it above.
89  std::for_each(++Iter, End, [&Ret](StringRef Component) {
90    // For reasons unknown to me, we may get a backslash with Windows native
91    // paths for the initial backslash following the drive component, which
92    // we need to ignore as a URI path part.
93    if (Component == "\\")
94      return;
95
96    // Add the separator between the previous path part and the one being
97    // currently processed.
98    Ret += "/";
99
100    // URI encode the part.
101    for (char C : Component) {
102      Ret += percentEncodeURICharacter(C);
103    }
104  });
105
106  return Ret.str().str();
107}
108
109static json::Object createFileLocation(const FileEntry &FE) {
110  return json::Object{{"uri", fileNameToURI(getFileName(FE))}};
111}
112
113static json::Object createFile(const FileEntry &FE) {
114  return json::Object{{"fileLocation", createFileLocation(FE)},
115                      {"roles", json::Array{"resultFile"}},
116                      {"length", FE.getSize()},
117                      {"mimeType""text/plain"}};
118}
119
120static json::Object createFileLocation(const FileEntry &FE,
121                                       json::Array &Files) {
122  std::string FileURI = fileNameToURI(getFileName(FE));
123
124  // See if the Files array contains this URI already. If it does not, create
125  // a new file object to add to the array.
126  auto I = llvm::find_if(Files, [&](const json::Value &File) {
127    if (const json::Object *Obj = File.getAsObject()) {
128      if (const json::Object *FileLoc = Obj->getObject("fileLocation")) {
129        Optional<StringRef> URI = FileLoc->getString("uri");
130        return URI && URI->equals(FileURI);
131      }
132    }
133    return false;
134  });
135
136  // Calculate the index within the file location array so it can be stored in
137  // the JSON object.
138  auto Index = static_cast<unsigned>(std::distance(Files.begin(), I));
139  if (I == Files.end())
140    Files.push_back(createFile(FE));
141
142  return json::Object{{"uri", FileURI}, {"fileIndex", Index}};
143}
144
145static json::Object createTextRegion(SourceRange R, const SourceManager &SM) {
146  return json::Object{
147      {"startLine", SM.getExpansionLineNumber(R.getBegin())},
148      {"endLine", SM.getExpansionLineNumber(R.getEnd())},
149      {"startColumn", SM.getExpansionColumnNumber(R.getBegin())},
150      {"endColumn", SM.getExpansionColumnNumber(R.getEnd())}};
151}
152
153static json::Object createPhysicalLocation(SourceRange R, const FileEntry &FE,
154                                           const SourceManager &SMgr,
155                                           json::Array &Files) {
156  return json::Object{{{"fileLocation", createFileLocation(FE, Files)},
157                       {"region", createTextRegion(R, SMgr)}}};
158}
159
160enum class Importance { ImportantEssentialUnimportant };
161
162static StringRef importanceToStr(Importance I) {
163  switch (I) {
164  case Importance::Important:
165    return "important";
166  case Importance::Essential:
167    return "essential";
168  case Importance::Unimportant:
169    return "unimportant";
170  }
171  llvm_unreachable("Fully covered switch is not so fully covered");
172}
173
174static json::Object createThreadFlowLocation(json::Object &&Location,
175                                             Importance I) {
176  return json::Object{{"location", std::move(Location)},
177                      {"importance", importanceToStr(I)}};
178}
179
180static json::Object createMessage(StringRef Text) {
181  return json::Object{{"text", Text.str()}};
182}
183
184static json::Object createLocation(json::Object &&PhysicalLocation,
185                                   StringRef Message = "") {
186  json::Object Ret{{"physicalLocation", std::move(PhysicalLocation)}};
187  if (!Message.empty())
188    Ret.insert({"message", createMessage(Message)});
189  return Ret;
190}
191
192static Importance calculateImportance(const PathDiagnosticPiece &Piece) {
193  switch (Piece.getKind()) {
194  case PathDiagnosticPiece::Kind::Call:
195  case PathDiagnosticPiece::Kind::Macro:
196  case PathDiagnosticPiece::Kind::Note:
197    // FIXME: What should be reported here?
198    break;
199  case PathDiagnosticPiece::Kind::Event:
200    return Piece.getTagStr() == "ConditionBRVisitor" ? Importance::Important
201                                                     : Importance::Essential;
202  case PathDiagnosticPiece::Kind::ControlFlow:
203    return Importance::Unimportant;
204  }
205  return Importance::Unimportant;
206}
207
208static json::Object createThreadFlow(const PathPieces &Pieces,
209                                     json::Array &Files) {
210  const SourceManager &SMgr = Pieces.front()->getLocation().getManager();
211  json::Array Locations;
212  for (const auto &Piece : Pieces) {
213    const PathDiagnosticLocation &P = Piece->getLocation();
214    Locations.push_back(createThreadFlowLocation(
215        createLocation(createPhysicalLocation(P.asRange(),
216                                              *P.asLocation().getFileEntry(),
217                                              SMgr, Files),
218                       Piece->getString()),
219        calculateImportance(*Piece)));
220  }
221  return json::Object{{"locations", std::move(Locations)}};
222}
223
224static json::Object createCodeFlow(const PathPieces &Pieces,
225                                   json::Array &Files) {
226  return json::Object{
227      {"threadFlows", json::Array{createThreadFlow(Pieces, Files)}}};
228}
229
230static json::Object createTool() {
231  return json::Object{{"name""clang"},
232                      {"fullName""clang static analyzer"},
233                      {"language""en-US"},
234                      {"version", getClangFullVersion()}};
235}
236
237static json::Object createResult(const PathDiagnostic &Diag, json::Array &Files,
238                                 const StringMap<unsigned> &RuleMapping) {
239  const PathPieces &Path = Diag.path.flatten(false);
240  const SourceManager &SMgr = Path.front()->getLocation().getManager();
241
242  auto Iter = RuleMapping.find(Diag.getCheckName());
243   (0) . __assert_fail ("Iter != RuleMapping.end() && \"Rule ID is not in the array index map?\"", "/home/seafit/code_projects/clang_source/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp", 243, __PRETTY_FUNCTION__))" file_link="../../../../include/assert.h.html#88" macro="true">assert(Iter != RuleMapping.end() && "Rule ID is not in the array index map?");
244
245  return json::Object{
246      {"message", createMessage(Diag.getVerboseDescription())},
247      {"codeFlows", json::Array{createCodeFlow(Path, Files)}},
248      {"locations",
249       json::Array{createLocation(createPhysicalLocation(
250           Diag.getLocation().asRange(),
251           *Diag.getLocation().asLocation().getFileEntry(), SMgr, Files))}},
252      {"ruleIndex", Iter->getValue()},
253      {"ruleId", Diag.getCheckName()}};
254}
255
256static StringRef getRuleDescription(StringRef CheckName) {
257  return llvm::StringSwitch<StringRef>(CheckName)
258#define GET_CHECKERS
259#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI)                            \
260  .Case(FULLNAME, HELPTEXT)
261#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
262#undef CHECKER
263#undef GET_CHECKERS
264      ;
265}
266
267static StringRef getRuleHelpURIStr(StringRef CheckName) {
268  return llvm::StringSwitch<StringRef>(CheckName)
269#define GET_CHECKERS
270#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI)                            \
271  .Case(FULLNAME, DOC_URI)
272#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
273#undef CHECKER
274#undef GET_CHECKERS
275      ;
276}
277
278static json::Object createRule(const PathDiagnostic &Diag) {
279  StringRef CheckName = Diag.getCheckName();
280  json::Object Ret{
281      {"fullDescription", createMessage(getRuleDescription(CheckName))},
282      {"name", createMessage(CheckName)},
283      {"id", CheckName}};
284
285  std::string RuleURI = getRuleHelpURIStr(CheckName);
286  if (!RuleURI.empty())
287    Ret["helpUri"] = RuleURI;
288
289  return Ret;
290}
291
292static json::Array createRules(std::vector<const PathDiagnostic *> &Diags,
293                               StringMap<unsigned> &RuleMapping) {
294  json::Array Rules;
295  llvm::StringSet<> Seen;
296
297  llvm::for_each(Diags, [&](const PathDiagnostic *D) {
298    StringRef RuleID = D->getCheckName();
299    std::pair<llvm::StringSet<>::iterator, bool> P = Seen.insert(RuleID);
300    if (P.second) {
301      RuleMapping[RuleID] = Rules.size(); // Maps RuleID to an Array Index.
302      Rules.push_back(createRule(*D));
303    }
304  });
305
306  return Rules;
307}
308
309static json::Object createResources(std::vector<const PathDiagnostic *> &Diags,
310                                    StringMap<unsigned> &RuleMapping) {
311  return json::Object{{"rules", createRules(Diags, RuleMapping)}};
312}
313
314static json::Object createRun(std::vector<const PathDiagnostic *> &Diags) {
315  json::Array Results, Files;
316  StringMap<unsignedRuleMapping;
317  json::Object Resources = createResources(Diags, RuleMapping);
318  
319  llvm::for_each(Diags, [&](const PathDiagnostic *D) {
320    Results.push_back(createResult(*D, Files, RuleMapping));
321  });
322
323  return json::Object{{"tool", createTool()},
324                      {"resources", std::move(Resources)},
325                      {"results", std::move(Results)},
326                      {"files", std::move(Files)}};
327}
328
329void SarifDiagnostics::FlushDiagnosticsImpl(
330    std::vector<const PathDiagnostic *> &DiagsFilesMade *) {
331  // We currently overwrite the file if it already exists. However, it may be
332  // useful to add a feature someday that allows the user to append a run to an
333  // existing SARIF file. One danger from that approach is that the size of the
334  // file can become large very quickly, so decoding into JSON to append a run
335  // may be an expensive operation.
336  std::error_code EC;
337  llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::F_Text);
338  if (EC) {
339    llvm::errs() << "warning: could not create file: " << EC.message() << '\n';
340    return;
341  }
342  json::Object Sarif{
343      {"$schema",
344       "http://json.schemastore.org/sarif-2.0.0-csd.2.beta.2018-11-28"},
345      {"version""2.0.0-csd.2.beta.2018-11-28"},
346      {"runs", json::Array{createRun(Diags)}}};
347  OS << llvm::formatv("{0:2}", json::Value(std::move(Sarif)));
348}
349