xref: /freebsd-src/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp (revision bdd1243df58e60e85101c09001d9812a789b6bc4)
10b57cec5SDimitry Andric //===--- SarifDiagnostics.cpp - Sarif Diagnostics for Paths -----*- C++ -*-===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric //  This file defines the SarifDiagnostics object.
100b57cec5SDimitry Andric //
110b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
120b57cec5SDimitry Andric 
13fe6060f1SDimitry Andric #include "clang/Analysis/MacroExpansionContext.h"
14a7dea167SDimitry Andric #include "clang/Analysis/PathDiagnostic.h"
155ffd83dbSDimitry Andric #include "clang/Basic/FileManager.h"
16*bdd1243dSDimitry Andric #include "clang/Basic/Sarif.h"
17*bdd1243dSDimitry Andric #include "clang/Basic/SourceManager.h"
180b57cec5SDimitry Andric #include "clang/Basic/Version.h"
190b57cec5SDimitry Andric #include "clang/Lex/Preprocessor.h"
200b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"
210b57cec5SDimitry Andric #include "llvm/ADT/STLExtras.h"
220b57cec5SDimitry Andric #include "llvm/ADT/StringMap.h"
23480093f4SDimitry Andric #include "llvm/Support/ConvertUTF.h"
240b57cec5SDimitry Andric #include "llvm/Support/JSON.h"
250b57cec5SDimitry Andric #include "llvm/Support/Path.h"
260b57cec5SDimitry Andric 
270b57cec5SDimitry Andric using namespace llvm;
280b57cec5SDimitry Andric using namespace clang;
290b57cec5SDimitry Andric using namespace ento;
300b57cec5SDimitry Andric 
310b57cec5SDimitry Andric namespace {
320b57cec5SDimitry Andric class SarifDiagnostics : public PathDiagnosticConsumer {
330b57cec5SDimitry Andric   std::string OutputFile;
34480093f4SDimitry Andric   const LangOptions &LO;
35*bdd1243dSDimitry Andric   SarifDocumentWriter SarifWriter;
360b57cec5SDimitry Andric 
370b57cec5SDimitry Andric public:
SarifDiagnostics(const std::string & Output,const LangOptions & LO,const SourceManager & SM)38*bdd1243dSDimitry Andric   SarifDiagnostics(const std::string &Output, const LangOptions &LO,
39*bdd1243dSDimitry Andric                    const SourceManager &SM)
40*bdd1243dSDimitry Andric       : OutputFile(Output), LO(LO), SarifWriter(SM) {}
410b57cec5SDimitry Andric   ~SarifDiagnostics() override = default;
420b57cec5SDimitry Andric 
430b57cec5SDimitry Andric   void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
440b57cec5SDimitry Andric                             FilesMade *FM) override;
450b57cec5SDimitry Andric 
getName() const460b57cec5SDimitry Andric   StringRef getName() const override { return "SarifDiagnostics"; }
getGenerationScheme() const470b57cec5SDimitry Andric   PathGenerationScheme getGenerationScheme() const override { return Minimal; }
supportsLogicalOpControlFlow() const480b57cec5SDimitry Andric   bool supportsLogicalOpControlFlow() const override { return true; }
supportsCrossFileDiagnostics() const490b57cec5SDimitry Andric   bool supportsCrossFileDiagnostics() const override { return true; }
500b57cec5SDimitry Andric };
510b57cec5SDimitry Andric } // end anonymous namespace
520b57cec5SDimitry Andric 
createSarifDiagnosticConsumer(PathDiagnosticConsumerOptions DiagOpts,PathDiagnosticConsumers & C,const std::string & Output,const Preprocessor & PP,const cross_tu::CrossTranslationUnitContext & CTU,const MacroExpansionContext & MacroExpansions)53a7dea167SDimitry Andric void ento::createSarifDiagnosticConsumer(
54e8d8bef9SDimitry Andric     PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
55480093f4SDimitry Andric     const std::string &Output, const Preprocessor &PP,
56fe6060f1SDimitry Andric     const cross_tu::CrossTranslationUnitContext &CTU,
57fe6060f1SDimitry Andric     const MacroExpansionContext &MacroExpansions) {
585ffd83dbSDimitry Andric 
595ffd83dbSDimitry Andric   // TODO: Emit an error here.
605ffd83dbSDimitry Andric   if (Output.empty())
615ffd83dbSDimitry Andric     return;
625ffd83dbSDimitry Andric 
63*bdd1243dSDimitry Andric   C.push_back(
64*bdd1243dSDimitry Andric       new SarifDiagnostics(Output, PP.getLangOpts(), PP.getSourceManager()));
65e8d8bef9SDimitry Andric   createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, Output, PP,
66fe6060f1SDimitry Andric                                           CTU, MacroExpansions);
670b57cec5SDimitry Andric }
680b57cec5SDimitry Andric 
getRuleDescription(StringRef CheckName)690b57cec5SDimitry Andric static StringRef getRuleDescription(StringRef CheckName) {
700b57cec5SDimitry Andric   return llvm::StringSwitch<StringRef>(CheckName)
710b57cec5SDimitry Andric #define GET_CHECKERS
720b57cec5SDimitry Andric #define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN)                 \
730b57cec5SDimitry Andric   .Case(FULLNAME, HELPTEXT)
740b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/Checkers.inc"
750b57cec5SDimitry Andric #undef CHECKER
760b57cec5SDimitry Andric #undef GET_CHECKERS
770b57cec5SDimitry Andric       ;
780b57cec5SDimitry Andric }
790b57cec5SDimitry Andric 
getRuleHelpURIStr(StringRef CheckName)800b57cec5SDimitry Andric static StringRef getRuleHelpURIStr(StringRef CheckName) {
810b57cec5SDimitry Andric   return llvm::StringSwitch<StringRef>(CheckName)
820b57cec5SDimitry Andric #define GET_CHECKERS
830b57cec5SDimitry Andric #define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN)                 \
840b57cec5SDimitry Andric   .Case(FULLNAME, DOC_URI)
850b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/Checkers.inc"
860b57cec5SDimitry Andric #undef CHECKER
870b57cec5SDimitry Andric #undef GET_CHECKERS
880b57cec5SDimitry Andric       ;
890b57cec5SDimitry Andric }
900b57cec5SDimitry Andric 
91*bdd1243dSDimitry Andric static ThreadFlowImportance
calculateImportance(const PathDiagnosticPiece & Piece)92*bdd1243dSDimitry Andric calculateImportance(const PathDiagnosticPiece &Piece) {
93*bdd1243dSDimitry Andric   switch (Piece.getKind()) {
94*bdd1243dSDimitry Andric   case PathDiagnosticPiece::Call:
95*bdd1243dSDimitry Andric   case PathDiagnosticPiece::Macro:
96*bdd1243dSDimitry Andric   case PathDiagnosticPiece::Note:
97*bdd1243dSDimitry Andric   case PathDiagnosticPiece::PopUp:
98*bdd1243dSDimitry Andric     // FIXME: What should be reported here?
99*bdd1243dSDimitry Andric     break;
100*bdd1243dSDimitry Andric   case PathDiagnosticPiece::Event:
101*bdd1243dSDimitry Andric     return Piece.getTagStr() == "ConditionBRVisitor"
102*bdd1243dSDimitry Andric                ? ThreadFlowImportance::Important
103*bdd1243dSDimitry Andric                : ThreadFlowImportance::Essential;
104*bdd1243dSDimitry Andric   case PathDiagnosticPiece::ControlFlow:
105*bdd1243dSDimitry Andric     return ThreadFlowImportance::Unimportant;
106*bdd1243dSDimitry Andric   }
107*bdd1243dSDimitry Andric   return ThreadFlowImportance::Unimportant;
1080b57cec5SDimitry Andric }
1090b57cec5SDimitry Andric 
110*bdd1243dSDimitry Andric /// Accepts a SourceRange corresponding to a pair of the first and last tokens
111*bdd1243dSDimitry Andric /// and converts to a Character granular CharSourceRange.
convertTokenRangeToCharRange(const SourceRange & R,const SourceManager & SM,const LangOptions & LO)112*bdd1243dSDimitry Andric static CharSourceRange convertTokenRangeToCharRange(const SourceRange &R,
113*bdd1243dSDimitry Andric                                                     const SourceManager &SM,
114*bdd1243dSDimitry Andric                                                     const LangOptions &LO) {
115*bdd1243dSDimitry Andric   // Caret diagnostics have the first and last locations pointed at the same
116*bdd1243dSDimitry Andric   // location, return these as-is.
117*bdd1243dSDimitry Andric   if (R.getBegin() == R.getEnd())
118*bdd1243dSDimitry Andric     return CharSourceRange::getCharRange(R);
119*bdd1243dSDimitry Andric 
120*bdd1243dSDimitry Andric   SourceLocation BeginCharLoc = R.getBegin();
121*bdd1243dSDimitry Andric   // For token ranges, the raw end SLoc points at the first character of the
122*bdd1243dSDimitry Andric   // last token in the range. This must be moved to one past the end of the
123*bdd1243dSDimitry Andric   // last character using the lexer.
124*bdd1243dSDimitry Andric   SourceLocation EndCharLoc =
125*bdd1243dSDimitry Andric       Lexer::getLocForEndOfToken(R.getEnd(), /* Offset = */ 0, SM, LO);
126*bdd1243dSDimitry Andric   return CharSourceRange::getCharRange(BeginCharLoc, EndCharLoc);
127*bdd1243dSDimitry Andric }
128*bdd1243dSDimitry Andric 
createThreadFlows(const PathDiagnostic * Diag,const LangOptions & LO)129*bdd1243dSDimitry Andric static SmallVector<ThreadFlow, 8> createThreadFlows(const PathDiagnostic *Diag,
130*bdd1243dSDimitry Andric                                                     const LangOptions &LO) {
131*bdd1243dSDimitry Andric   SmallVector<ThreadFlow, 8> Flows;
132*bdd1243dSDimitry Andric   const PathPieces &Pieces = Diag->path.flatten(false);
133*bdd1243dSDimitry Andric   for (const auto &Piece : Pieces) {
134*bdd1243dSDimitry Andric     auto Range = convertTokenRangeToCharRange(
135*bdd1243dSDimitry Andric         Piece->getLocation().asRange(), Piece->getLocation().getManager(), LO);
136*bdd1243dSDimitry Andric     auto Flow = ThreadFlow::create()
137*bdd1243dSDimitry Andric                     .setImportance(calculateImportance(*Piece))
138*bdd1243dSDimitry Andric                     .setRange(Range)
139*bdd1243dSDimitry Andric                     .setMessage(Piece->getString());
140*bdd1243dSDimitry Andric     Flows.push_back(Flow);
141*bdd1243dSDimitry Andric   }
142*bdd1243dSDimitry Andric   return Flows;
143*bdd1243dSDimitry Andric }
144*bdd1243dSDimitry Andric 
145*bdd1243dSDimitry Andric static StringMap<uint32_t>
createRuleMapping(const std::vector<const PathDiagnostic * > & Diags,SarifDocumentWriter & SarifWriter)146*bdd1243dSDimitry Andric createRuleMapping(const std::vector<const PathDiagnostic *> &Diags,
147*bdd1243dSDimitry Andric                   SarifDocumentWriter &SarifWriter) {
148*bdd1243dSDimitry Andric   StringMap<uint32_t> RuleMapping;
1490b57cec5SDimitry Andric   llvm::StringSet<> Seen;
1500b57cec5SDimitry Andric 
15181ad6265SDimitry Andric   for (const PathDiagnostic *D : Diags) {
152*bdd1243dSDimitry Andric     StringRef CheckName = D->getCheckerName();
153*bdd1243dSDimitry Andric     std::pair<llvm::StringSet<>::iterator, bool> P = Seen.insert(CheckName);
1540b57cec5SDimitry Andric     if (P.second) {
155*bdd1243dSDimitry Andric       auto Rule = SarifRule::create()
156*bdd1243dSDimitry Andric                       .setName(CheckName)
157*bdd1243dSDimitry Andric                       .setRuleId(CheckName)
158*bdd1243dSDimitry Andric                       .setDescription(getRuleDescription(CheckName))
159*bdd1243dSDimitry Andric                       .setHelpURI(getRuleHelpURIStr(CheckName));
160*bdd1243dSDimitry Andric       size_t RuleIdx = SarifWriter.createRule(Rule);
161*bdd1243dSDimitry Andric       RuleMapping[CheckName] = RuleIdx;
1620b57cec5SDimitry Andric     }
16381ad6265SDimitry Andric   }
164*bdd1243dSDimitry Andric   return RuleMapping;
1650b57cec5SDimitry Andric }
1660b57cec5SDimitry Andric 
createResult(const PathDiagnostic * Diag,const StringMap<uint32_t> & RuleMapping,const LangOptions & LO)167*bdd1243dSDimitry Andric static SarifResult createResult(const PathDiagnostic *Diag,
168*bdd1243dSDimitry Andric                                 const StringMap<uint32_t> &RuleMapping,
169*bdd1243dSDimitry Andric                                 const LangOptions &LO) {
1700b57cec5SDimitry Andric 
171*bdd1243dSDimitry Andric   StringRef CheckName = Diag->getCheckerName();
172*bdd1243dSDimitry Andric   uint32_t RuleIdx = RuleMapping.lookup(CheckName);
173*bdd1243dSDimitry Andric   auto Range = convertTokenRangeToCharRange(
174*bdd1243dSDimitry Andric       Diag->getLocation().asRange(), Diag->getLocation().getManager(), LO);
1750b57cec5SDimitry Andric 
176*bdd1243dSDimitry Andric   SmallVector<ThreadFlow, 8> Flows = createThreadFlows(Diag, LO);
177*bdd1243dSDimitry Andric   auto Result = SarifResult::create(RuleIdx)
178*bdd1243dSDimitry Andric                     .setRuleId(CheckName)
179*bdd1243dSDimitry Andric                     .setDiagnosticMessage(Diag->getVerboseDescription())
180*bdd1243dSDimitry Andric                     .setDiagnosticLevel(SarifResultLevel::Warning)
181*bdd1243dSDimitry Andric                     .setLocations({Range})
182*bdd1243dSDimitry Andric                     .setThreadFlows(Flows);
183*bdd1243dSDimitry Andric   return Result;
1840b57cec5SDimitry Andric }
1850b57cec5SDimitry Andric 
FlushDiagnosticsImpl(std::vector<const PathDiagnostic * > & Diags,FilesMade *)1860b57cec5SDimitry Andric void SarifDiagnostics::FlushDiagnosticsImpl(
1870b57cec5SDimitry Andric     std::vector<const PathDiagnostic *> &Diags, FilesMade *) {
1880b57cec5SDimitry Andric   // We currently overwrite the file if it already exists. However, it may be
1890b57cec5SDimitry Andric   // useful to add a feature someday that allows the user to append a run to an
1900b57cec5SDimitry Andric   // existing SARIF file. One danger from that approach is that the size of the
1910b57cec5SDimitry Andric   // file can become large very quickly, so decoding into JSON to append a run
1920b57cec5SDimitry Andric   // may be an expensive operation.
1930b57cec5SDimitry Andric   std::error_code EC;
194fe6060f1SDimitry Andric   llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF);
1950b57cec5SDimitry Andric   if (EC) {
1960b57cec5SDimitry Andric     llvm::errs() << "warning: could not create file: " << EC.message() << '\n';
1970b57cec5SDimitry Andric     return;
1980b57cec5SDimitry Andric   }
199*bdd1243dSDimitry Andric 
200*bdd1243dSDimitry Andric   std::string ToolVersion = getClangFullVersion();
201*bdd1243dSDimitry Andric   SarifWriter.createRun("clang", "clang static analyzer", ToolVersion);
202*bdd1243dSDimitry Andric   StringMap<uint32_t> RuleMapping = createRuleMapping(Diags, SarifWriter);
203*bdd1243dSDimitry Andric   for (const PathDiagnostic *D : Diags) {
204*bdd1243dSDimitry Andric     SarifResult Result = createResult(D, RuleMapping, LO);
205*bdd1243dSDimitry Andric     SarifWriter.appendResult(Result);
206*bdd1243dSDimitry Andric   }
207*bdd1243dSDimitry Andric   auto Document = SarifWriter.createDocument();
208*bdd1243dSDimitry Andric   OS << llvm::formatv("{0:2}\n", json::Value(std::move(Document)));
2090b57cec5SDimitry Andric }
210