xref: /openbsd-src/gnu/llvm/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp (revision 12c855180aad702bbcca06e0398d774beeafb155)
1e5dd7070Spatrick //===--- SarifDiagnostics.cpp - Sarif Diagnostics for Paths -----*- C++ -*-===//
2e5dd7070Spatrick //
3e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e5dd7070Spatrick //
7e5dd7070Spatrick //===----------------------------------------------------------------------===//
8e5dd7070Spatrick //
9e5dd7070Spatrick //  This file defines the SarifDiagnostics object.
10e5dd7070Spatrick //
11e5dd7070Spatrick //===----------------------------------------------------------------------===//
12e5dd7070Spatrick 
13a9ac8606Spatrick #include "clang/Analysis/MacroExpansionContext.h"
14e5dd7070Spatrick #include "clang/Analysis/PathDiagnostic.h"
15ec727ea7Spatrick #include "clang/Basic/FileManager.h"
16*12c85518Srobert #include "clang/Basic/Sarif.h"
17*12c85518Srobert #include "clang/Basic/SourceManager.h"
18e5dd7070Spatrick #include "clang/Basic/Version.h"
19e5dd7070Spatrick #include "clang/Lex/Preprocessor.h"
20e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"
21e5dd7070Spatrick #include "llvm/ADT/STLExtras.h"
22e5dd7070Spatrick #include "llvm/ADT/StringMap.h"
23e5dd7070Spatrick #include "llvm/Support/ConvertUTF.h"
24e5dd7070Spatrick #include "llvm/Support/JSON.h"
25e5dd7070Spatrick #include "llvm/Support/Path.h"
26e5dd7070Spatrick 
27e5dd7070Spatrick using namespace llvm;
28e5dd7070Spatrick using namespace clang;
29e5dd7070Spatrick using namespace ento;
30e5dd7070Spatrick 
31e5dd7070Spatrick namespace {
32e5dd7070Spatrick class SarifDiagnostics : public PathDiagnosticConsumer {
33e5dd7070Spatrick   std::string OutputFile;
34e5dd7070Spatrick   const LangOptions &LO;
35*12c85518Srobert   SarifDocumentWriter SarifWriter;
36e5dd7070Spatrick 
37e5dd7070Spatrick public:
SarifDiagnostics(const std::string & Output,const LangOptions & LO,const SourceManager & SM)38*12c85518Srobert   SarifDiagnostics(const std::string &Output, const LangOptions &LO,
39*12c85518Srobert                    const SourceManager &SM)
40*12c85518Srobert       : OutputFile(Output), LO(LO), SarifWriter(SM) {}
41e5dd7070Spatrick   ~SarifDiagnostics() override = default;
42e5dd7070Spatrick 
43e5dd7070Spatrick   void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
44e5dd7070Spatrick                             FilesMade *FM) override;
45e5dd7070Spatrick 
getName() const46e5dd7070Spatrick   StringRef getName() const override { return "SarifDiagnostics"; }
getGenerationScheme() const47e5dd7070Spatrick   PathGenerationScheme getGenerationScheme() const override { return Minimal; }
supportsLogicalOpControlFlow() const48e5dd7070Spatrick   bool supportsLogicalOpControlFlow() const override { return true; }
supportsCrossFileDiagnostics() const49e5dd7070Spatrick   bool supportsCrossFileDiagnostics() const override { return true; }
50e5dd7070Spatrick };
51e5dd7070Spatrick } // end anonymous namespace
52e5dd7070Spatrick 
createSarifDiagnosticConsumer(PathDiagnosticConsumerOptions DiagOpts,PathDiagnosticConsumers & C,const std::string & Output,const Preprocessor & PP,const cross_tu::CrossTranslationUnitContext & CTU,const MacroExpansionContext & MacroExpansions)53e5dd7070Spatrick void ento::createSarifDiagnosticConsumer(
54a9ac8606Spatrick     PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
55e5dd7070Spatrick     const std::string &Output, const Preprocessor &PP,
56a9ac8606Spatrick     const cross_tu::CrossTranslationUnitContext &CTU,
57a9ac8606Spatrick     const MacroExpansionContext &MacroExpansions) {
58ec727ea7Spatrick 
59ec727ea7Spatrick   // TODO: Emit an error here.
60ec727ea7Spatrick   if (Output.empty())
61ec727ea7Spatrick     return;
62ec727ea7Spatrick 
63*12c85518Srobert   C.push_back(
64*12c85518Srobert       new SarifDiagnostics(Output, PP.getLangOpts(), PP.getSourceManager()));
65a9ac8606Spatrick   createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, Output, PP,
66a9ac8606Spatrick                                           CTU, MacroExpansions);
67e5dd7070Spatrick }
68e5dd7070Spatrick 
getRuleDescription(StringRef CheckName)69e5dd7070Spatrick static StringRef getRuleDescription(StringRef CheckName) {
70e5dd7070Spatrick   return llvm::StringSwitch<StringRef>(CheckName)
71e5dd7070Spatrick #define GET_CHECKERS
72e5dd7070Spatrick #define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN)                 \
73e5dd7070Spatrick   .Case(FULLNAME, HELPTEXT)
74e5dd7070Spatrick #include "clang/StaticAnalyzer/Checkers/Checkers.inc"
75e5dd7070Spatrick #undef CHECKER
76e5dd7070Spatrick #undef GET_CHECKERS
77e5dd7070Spatrick       ;
78e5dd7070Spatrick }
79e5dd7070Spatrick 
getRuleHelpURIStr(StringRef CheckName)80e5dd7070Spatrick static StringRef getRuleHelpURIStr(StringRef CheckName) {
81e5dd7070Spatrick   return llvm::StringSwitch<StringRef>(CheckName)
82e5dd7070Spatrick #define GET_CHECKERS
83e5dd7070Spatrick #define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN)                 \
84e5dd7070Spatrick   .Case(FULLNAME, DOC_URI)
85e5dd7070Spatrick #include "clang/StaticAnalyzer/Checkers/Checkers.inc"
86e5dd7070Spatrick #undef CHECKER
87e5dd7070Spatrick #undef GET_CHECKERS
88e5dd7070Spatrick       ;
89e5dd7070Spatrick }
90e5dd7070Spatrick 
91*12c85518Srobert static ThreadFlowImportance
calculateImportance(const PathDiagnosticPiece & Piece)92*12c85518Srobert calculateImportance(const PathDiagnosticPiece &Piece) {
93*12c85518Srobert   switch (Piece.getKind()) {
94*12c85518Srobert   case PathDiagnosticPiece::Call:
95*12c85518Srobert   case PathDiagnosticPiece::Macro:
96*12c85518Srobert   case PathDiagnosticPiece::Note:
97*12c85518Srobert   case PathDiagnosticPiece::PopUp:
98*12c85518Srobert     // FIXME: What should be reported here?
99*12c85518Srobert     break;
100*12c85518Srobert   case PathDiagnosticPiece::Event:
101*12c85518Srobert     return Piece.getTagStr() == "ConditionBRVisitor"
102*12c85518Srobert                ? ThreadFlowImportance::Important
103*12c85518Srobert                : ThreadFlowImportance::Essential;
104*12c85518Srobert   case PathDiagnosticPiece::ControlFlow:
105*12c85518Srobert     return ThreadFlowImportance::Unimportant;
106*12c85518Srobert   }
107*12c85518Srobert   return ThreadFlowImportance::Unimportant;
108e5dd7070Spatrick }
109e5dd7070Spatrick 
110*12c85518Srobert /// Accepts a SourceRange corresponding to a pair of the first and last tokens
111*12c85518Srobert /// and converts to a Character granular CharSourceRange.
convertTokenRangeToCharRange(const SourceRange & R,const SourceManager & SM,const LangOptions & LO)112*12c85518Srobert static CharSourceRange convertTokenRangeToCharRange(const SourceRange &R,
113*12c85518Srobert                                                     const SourceManager &SM,
114*12c85518Srobert                                                     const LangOptions &LO) {
115*12c85518Srobert   // Caret diagnostics have the first and last locations pointed at the same
116*12c85518Srobert   // location, return these as-is.
117*12c85518Srobert   if (R.getBegin() == R.getEnd())
118*12c85518Srobert     return CharSourceRange::getCharRange(R);
119*12c85518Srobert 
120*12c85518Srobert   SourceLocation BeginCharLoc = R.getBegin();
121*12c85518Srobert   // For token ranges, the raw end SLoc points at the first character of the
122*12c85518Srobert   // last token in the range. This must be moved to one past the end of the
123*12c85518Srobert   // last character using the lexer.
124*12c85518Srobert   SourceLocation EndCharLoc =
125*12c85518Srobert       Lexer::getLocForEndOfToken(R.getEnd(), /* Offset = */ 0, SM, LO);
126*12c85518Srobert   return CharSourceRange::getCharRange(BeginCharLoc, EndCharLoc);
127*12c85518Srobert }
128*12c85518Srobert 
createThreadFlows(const PathDiagnostic * Diag,const LangOptions & LO)129*12c85518Srobert static SmallVector<ThreadFlow, 8> createThreadFlows(const PathDiagnostic *Diag,
130*12c85518Srobert                                                     const LangOptions &LO) {
131*12c85518Srobert   SmallVector<ThreadFlow, 8> Flows;
132*12c85518Srobert   const PathPieces &Pieces = Diag->path.flatten(false);
133*12c85518Srobert   for (const auto &Piece : Pieces) {
134*12c85518Srobert     auto Range = convertTokenRangeToCharRange(
135*12c85518Srobert         Piece->getLocation().asRange(), Piece->getLocation().getManager(), LO);
136*12c85518Srobert     auto Flow = ThreadFlow::create()
137*12c85518Srobert                     .setImportance(calculateImportance(*Piece))
138*12c85518Srobert                     .setRange(Range)
139*12c85518Srobert                     .setMessage(Piece->getString());
140*12c85518Srobert     Flows.push_back(Flow);
141*12c85518Srobert   }
142*12c85518Srobert   return Flows;
143*12c85518Srobert }
144*12c85518Srobert 
145*12c85518Srobert static StringMap<uint32_t>
createRuleMapping(const std::vector<const PathDiagnostic * > & Diags,SarifDocumentWriter & SarifWriter)146*12c85518Srobert createRuleMapping(const std::vector<const PathDiagnostic *> &Diags,
147*12c85518Srobert                   SarifDocumentWriter &SarifWriter) {
148*12c85518Srobert   StringMap<uint32_t> RuleMapping;
149e5dd7070Spatrick   llvm::StringSet<> Seen;
150e5dd7070Spatrick 
151*12c85518Srobert   for (const PathDiagnostic *D : Diags) {
152*12c85518Srobert     StringRef CheckName = D->getCheckerName();
153*12c85518Srobert     std::pair<llvm::StringSet<>::iterator, bool> P = Seen.insert(CheckName);
154e5dd7070Spatrick     if (P.second) {
155*12c85518Srobert       auto Rule = SarifRule::create()
156*12c85518Srobert                       .setName(CheckName)
157*12c85518Srobert                       .setRuleId(CheckName)
158*12c85518Srobert                       .setDescription(getRuleDescription(CheckName))
159*12c85518Srobert                       .setHelpURI(getRuleHelpURIStr(CheckName));
160*12c85518Srobert       size_t RuleIdx = SarifWriter.createRule(Rule);
161*12c85518Srobert       RuleMapping[CheckName] = RuleIdx;
162e5dd7070Spatrick     }
163*12c85518Srobert   }
164*12c85518Srobert   return RuleMapping;
165e5dd7070Spatrick }
166e5dd7070Spatrick 
createResult(const PathDiagnostic * Diag,const StringMap<uint32_t> & RuleMapping,const LangOptions & LO)167*12c85518Srobert static SarifResult createResult(const PathDiagnostic *Diag,
168*12c85518Srobert                                 const StringMap<uint32_t> &RuleMapping,
169*12c85518Srobert                                 const LangOptions &LO) {
170e5dd7070Spatrick 
171*12c85518Srobert   StringRef CheckName = Diag->getCheckerName();
172*12c85518Srobert   uint32_t RuleIdx = RuleMapping.lookup(CheckName);
173*12c85518Srobert   auto Range = convertTokenRangeToCharRange(
174*12c85518Srobert       Diag->getLocation().asRange(), Diag->getLocation().getManager(), LO);
175e5dd7070Spatrick 
176*12c85518Srobert   SmallVector<ThreadFlow, 8> Flows = createThreadFlows(Diag, LO);
177*12c85518Srobert   auto Result = SarifResult::create(RuleIdx)
178*12c85518Srobert                     .setRuleId(CheckName)
179*12c85518Srobert                     .setDiagnosticMessage(Diag->getVerboseDescription())
180*12c85518Srobert                     .setDiagnosticLevel(SarifResultLevel::Warning)
181*12c85518Srobert                     .setLocations({Range})
182*12c85518Srobert                     .setThreadFlows(Flows);
183*12c85518Srobert   return Result;
184e5dd7070Spatrick }
185e5dd7070Spatrick 
FlushDiagnosticsImpl(std::vector<const PathDiagnostic * > & Diags,FilesMade *)186e5dd7070Spatrick void SarifDiagnostics::FlushDiagnosticsImpl(
187e5dd7070Spatrick     std::vector<const PathDiagnostic *> &Diags, FilesMade *) {
188e5dd7070Spatrick   // We currently overwrite the file if it already exists. However, it may be
189e5dd7070Spatrick   // useful to add a feature someday that allows the user to append a run to an
190e5dd7070Spatrick   // existing SARIF file. One danger from that approach is that the size of the
191e5dd7070Spatrick   // file can become large very quickly, so decoding into JSON to append a run
192e5dd7070Spatrick   // may be an expensive operation.
193e5dd7070Spatrick   std::error_code EC;
194a9ac8606Spatrick   llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF);
195e5dd7070Spatrick   if (EC) {
196e5dd7070Spatrick     llvm::errs() << "warning: could not create file: " << EC.message() << '\n';
197e5dd7070Spatrick     return;
198e5dd7070Spatrick   }
199*12c85518Srobert 
200*12c85518Srobert   std::string ToolVersion = getClangFullVersion();
201*12c85518Srobert   SarifWriter.createRun("clang", "clang static analyzer", ToolVersion);
202*12c85518Srobert   StringMap<uint32_t> RuleMapping = createRuleMapping(Diags, SarifWriter);
203*12c85518Srobert   for (const PathDiagnostic *D : Diags) {
204*12c85518Srobert     SarifResult Result = createResult(D, RuleMapping, LO);
205*12c85518Srobert     SarifWriter.appendResult(Result);
206*12c85518Srobert   }
207*12c85518Srobert   auto Document = SarifWriter.createDocument();
208*12c85518Srobert   OS << llvm::formatv("{0:2}\n", json::Value(std::move(Document)));
209e5dd7070Spatrick }
210