xref: /llvm-project/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp (revision 7b6fe711b210a90cbb8facfe5343a0f999de5a0c)
19c4b2225SAlexander Belyaev //===--- SarifDiagnostics.cpp - Sarif Diagnostics for Paths -----*- C++ -*-===//
29c4b2225SAlexander Belyaev //
39c4b2225SAlexander Belyaev // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
49c4b2225SAlexander Belyaev // See https://llvm.org/LICENSE.txt for license information.
59c4b2225SAlexander Belyaev // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
69c4b2225SAlexander Belyaev //
79c4b2225SAlexander Belyaev //===----------------------------------------------------------------------===//
89c4b2225SAlexander Belyaev //
99c4b2225SAlexander Belyaev //  This file defines the SarifDiagnostics object.
109c4b2225SAlexander Belyaev //
119c4b2225SAlexander Belyaev //===----------------------------------------------------------------------===//
129c4b2225SAlexander Belyaev 
137c58fb6bSBalazs Benics #include "clang/Analysis/MacroExpansionContext.h"
149c4b2225SAlexander Belyaev #include "clang/Analysis/PathDiagnostic.h"
159c4b2225SAlexander Belyaev #include "clang/Basic/FileManager.h"
16*7b6fe711SVaibhav Yenamandra #include "clang/Basic/Sarif.h"
17*7b6fe711SVaibhav Yenamandra #include "clang/Basic/SourceManager.h"
189c4b2225SAlexander Belyaev #include "clang/Basic/Version.h"
199c4b2225SAlexander Belyaev #include "clang/Lex/Preprocessor.h"
209c4b2225SAlexander Belyaev #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"
219c4b2225SAlexander Belyaev #include "llvm/ADT/STLExtras.h"
229c4b2225SAlexander Belyaev #include "llvm/ADT/StringMap.h"
239c4b2225SAlexander Belyaev #include "llvm/Support/ConvertUTF.h"
249c4b2225SAlexander Belyaev #include "llvm/Support/JSON.h"
259c4b2225SAlexander Belyaev #include "llvm/Support/Path.h"
269c4b2225SAlexander Belyaev 
279c4b2225SAlexander Belyaev using namespace llvm;
289c4b2225SAlexander Belyaev using namespace clang;
299c4b2225SAlexander Belyaev using namespace ento;
309c4b2225SAlexander Belyaev 
319c4b2225SAlexander Belyaev namespace {
329c4b2225SAlexander Belyaev class SarifDiagnostics : public PathDiagnosticConsumer {
339c4b2225SAlexander Belyaev   std::string OutputFile;
349c4b2225SAlexander Belyaev   const LangOptions &LO;
35*7b6fe711SVaibhav Yenamandra   SarifDocumentWriter SarifWriter;
369c4b2225SAlexander Belyaev 
379c4b2225SAlexander Belyaev public:
SarifDiagnostics(const std::string & Output,const LangOptions & LO,const SourceManager & SM)38*7b6fe711SVaibhav Yenamandra   SarifDiagnostics(const std::string &Output, const LangOptions &LO,
39*7b6fe711SVaibhav Yenamandra                    const SourceManager &SM)
40*7b6fe711SVaibhav Yenamandra       : OutputFile(Output), LO(LO), SarifWriter(SM) {}
419c4b2225SAlexander Belyaev   ~SarifDiagnostics() override = default;
429c4b2225SAlexander Belyaev 
439c4b2225SAlexander Belyaev   void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
449c4b2225SAlexander Belyaev                             FilesMade *FM) override;
459c4b2225SAlexander Belyaev 
getName() const469c4b2225SAlexander Belyaev   StringRef getName() const override { return "SarifDiagnostics"; }
getGenerationScheme() const479c4b2225SAlexander Belyaev   PathGenerationScheme getGenerationScheme() const override { return Minimal; }
supportsLogicalOpControlFlow() const489c4b2225SAlexander Belyaev   bool supportsLogicalOpControlFlow() const override { return true; }
supportsCrossFileDiagnostics() const499c4b2225SAlexander Belyaev   bool supportsCrossFileDiagnostics() const override { return true; }
509c4b2225SAlexander Belyaev };
519c4b2225SAlexander Belyaev } // end anonymous namespace
529c4b2225SAlexander Belyaev 
createSarifDiagnosticConsumer(PathDiagnosticConsumerOptions DiagOpts,PathDiagnosticConsumers & C,const std::string & Output,const Preprocessor & PP,const cross_tu::CrossTranslationUnitContext & CTU,const MacroExpansionContext & MacroExpansions)539c4b2225SAlexander Belyaev void ento::createSarifDiagnosticConsumer(
549c4b2225SAlexander Belyaev     PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
559c4b2225SAlexander Belyaev     const std::string &Output, const Preprocessor &PP,
567c58fb6bSBalazs Benics     const cross_tu::CrossTranslationUnitContext &CTU,
577c58fb6bSBalazs Benics     const MacroExpansionContext &MacroExpansions) {
589c4b2225SAlexander Belyaev 
599c4b2225SAlexander Belyaev   // TODO: Emit an error here.
609c4b2225SAlexander Belyaev   if (Output.empty())
619c4b2225SAlexander Belyaev     return;
629c4b2225SAlexander Belyaev 
63*7b6fe711SVaibhav Yenamandra   C.push_back(
64*7b6fe711SVaibhav Yenamandra       new SarifDiagnostics(Output, PP.getLangOpts(), PP.getSourceManager()));
659c4b2225SAlexander Belyaev   createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, Output, PP,
667c58fb6bSBalazs Benics                                           CTU, MacroExpansions);
679c4b2225SAlexander Belyaev }
689c4b2225SAlexander Belyaev 
getRuleDescription(StringRef CheckName)699c4b2225SAlexander Belyaev static StringRef getRuleDescription(StringRef CheckName) {
709c4b2225SAlexander Belyaev   return llvm::StringSwitch<StringRef>(CheckName)
719c4b2225SAlexander Belyaev #define GET_CHECKERS
729c4b2225SAlexander Belyaev #define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN)                 \
739c4b2225SAlexander Belyaev   .Case(FULLNAME, HELPTEXT)
749c4b2225SAlexander Belyaev #include "clang/StaticAnalyzer/Checkers/Checkers.inc"
759c4b2225SAlexander Belyaev #undef CHECKER
769c4b2225SAlexander Belyaev #undef GET_CHECKERS
779c4b2225SAlexander Belyaev       ;
789c4b2225SAlexander Belyaev }
799c4b2225SAlexander Belyaev 
getRuleHelpURIStr(StringRef CheckName)809c4b2225SAlexander Belyaev static StringRef getRuleHelpURIStr(StringRef CheckName) {
819c4b2225SAlexander Belyaev   return llvm::StringSwitch<StringRef>(CheckName)
829c4b2225SAlexander Belyaev #define GET_CHECKERS
839c4b2225SAlexander Belyaev #define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN)                 \
849c4b2225SAlexander Belyaev   .Case(FULLNAME, DOC_URI)
859c4b2225SAlexander Belyaev #include "clang/StaticAnalyzer/Checkers/Checkers.inc"
869c4b2225SAlexander Belyaev #undef CHECKER
879c4b2225SAlexander Belyaev #undef GET_CHECKERS
889c4b2225SAlexander Belyaev       ;
899c4b2225SAlexander Belyaev }
909c4b2225SAlexander Belyaev 
91*7b6fe711SVaibhav Yenamandra static ThreadFlowImportance
calculateImportance(const PathDiagnosticPiece & Piece)92*7b6fe711SVaibhav Yenamandra calculateImportance(const PathDiagnosticPiece &Piece) {
93*7b6fe711SVaibhav Yenamandra   switch (Piece.getKind()) {
94*7b6fe711SVaibhav Yenamandra   case PathDiagnosticPiece::Call:
95*7b6fe711SVaibhav Yenamandra   case PathDiagnosticPiece::Macro:
96*7b6fe711SVaibhav Yenamandra   case PathDiagnosticPiece::Note:
97*7b6fe711SVaibhav Yenamandra   case PathDiagnosticPiece::PopUp:
98*7b6fe711SVaibhav Yenamandra     // FIXME: What should be reported here?
99*7b6fe711SVaibhav Yenamandra     break;
100*7b6fe711SVaibhav Yenamandra   case PathDiagnosticPiece::Event:
101*7b6fe711SVaibhav Yenamandra     return Piece.getTagStr() == "ConditionBRVisitor"
102*7b6fe711SVaibhav Yenamandra                ? ThreadFlowImportance::Important
103*7b6fe711SVaibhav Yenamandra                : ThreadFlowImportance::Essential;
104*7b6fe711SVaibhav Yenamandra   case PathDiagnosticPiece::ControlFlow:
105*7b6fe711SVaibhav Yenamandra     return ThreadFlowImportance::Unimportant;
106*7b6fe711SVaibhav Yenamandra   }
107*7b6fe711SVaibhav Yenamandra   return ThreadFlowImportance::Unimportant;
1089c4b2225SAlexander Belyaev }
1099c4b2225SAlexander Belyaev 
110*7b6fe711SVaibhav Yenamandra /// Accepts a SourceRange corresponding to a pair of the first and last tokens
111*7b6fe711SVaibhav Yenamandra /// and converts to a Character granular CharSourceRange.
convertTokenRangeToCharRange(const SourceRange & R,const SourceManager & SM,const LangOptions & LO)112*7b6fe711SVaibhav Yenamandra static CharSourceRange convertTokenRangeToCharRange(const SourceRange &R,
113*7b6fe711SVaibhav Yenamandra                                                     const SourceManager &SM,
114*7b6fe711SVaibhav Yenamandra                                                     const LangOptions &LO) {
115*7b6fe711SVaibhav Yenamandra   // Caret diagnostics have the first and last locations pointed at the same
116*7b6fe711SVaibhav Yenamandra   // location, return these as-is.
117*7b6fe711SVaibhav Yenamandra   if (R.getBegin() == R.getEnd())
118*7b6fe711SVaibhav Yenamandra     return CharSourceRange::getCharRange(R);
119*7b6fe711SVaibhav Yenamandra 
120*7b6fe711SVaibhav Yenamandra   SourceLocation BeginCharLoc = R.getBegin();
121*7b6fe711SVaibhav Yenamandra   // For token ranges, the raw end SLoc points at the first character of the
122*7b6fe711SVaibhav Yenamandra   // last token in the range. This must be moved to one past the end of the
123*7b6fe711SVaibhav Yenamandra   // last character using the lexer.
124*7b6fe711SVaibhav Yenamandra   SourceLocation EndCharLoc =
125*7b6fe711SVaibhav Yenamandra       Lexer::getLocForEndOfToken(R.getEnd(), /* Offset = */ 0, SM, LO);
126*7b6fe711SVaibhav Yenamandra   return CharSourceRange::getCharRange(BeginCharLoc, EndCharLoc);
127*7b6fe711SVaibhav Yenamandra }
128*7b6fe711SVaibhav Yenamandra 
createThreadFlows(const PathDiagnostic * Diag,const LangOptions & LO)129*7b6fe711SVaibhav Yenamandra static SmallVector<ThreadFlow, 8> createThreadFlows(const PathDiagnostic *Diag,
130*7b6fe711SVaibhav Yenamandra                                                     const LangOptions &LO) {
131*7b6fe711SVaibhav Yenamandra   SmallVector<ThreadFlow, 8> Flows;
132*7b6fe711SVaibhav Yenamandra   const PathPieces &Pieces = Diag->path.flatten(false);
133*7b6fe711SVaibhav Yenamandra   for (const auto &Piece : Pieces) {
134*7b6fe711SVaibhav Yenamandra     auto Range = convertTokenRangeToCharRange(
135*7b6fe711SVaibhav Yenamandra         Piece->getLocation().asRange(), Piece->getLocation().getManager(), LO);
136*7b6fe711SVaibhav Yenamandra     auto Flow = ThreadFlow::create()
137*7b6fe711SVaibhav Yenamandra                     .setImportance(calculateImportance(*Piece))
138*7b6fe711SVaibhav Yenamandra                     .setRange(Range)
139*7b6fe711SVaibhav Yenamandra                     .setMessage(Piece->getString());
140*7b6fe711SVaibhav Yenamandra     Flows.push_back(Flow);
141*7b6fe711SVaibhav Yenamandra   }
142*7b6fe711SVaibhav Yenamandra   return Flows;
143*7b6fe711SVaibhav Yenamandra }
144*7b6fe711SVaibhav Yenamandra 
145*7b6fe711SVaibhav Yenamandra static StringMap<uint32_t>
createRuleMapping(const std::vector<const PathDiagnostic * > & Diags,SarifDocumentWriter & SarifWriter)146*7b6fe711SVaibhav Yenamandra createRuleMapping(const std::vector<const PathDiagnostic *> &Diags,
147*7b6fe711SVaibhav Yenamandra                   SarifDocumentWriter &SarifWriter) {
148*7b6fe711SVaibhav Yenamandra   StringMap<uint32_t> RuleMapping;
1499c4b2225SAlexander Belyaev   llvm::StringSet<> Seen;
1509c4b2225SAlexander Belyaev 
151f5ef2c58SKazu Hirata   for (const PathDiagnostic *D : Diags) {
152*7b6fe711SVaibhav Yenamandra     StringRef CheckName = D->getCheckerName();
153*7b6fe711SVaibhav Yenamandra     std::pair<llvm::StringSet<>::iterator, bool> P = Seen.insert(CheckName);
1549c4b2225SAlexander Belyaev     if (P.second) {
155*7b6fe711SVaibhav Yenamandra       auto Rule = SarifRule::create()
156*7b6fe711SVaibhav Yenamandra                       .setName(CheckName)
157*7b6fe711SVaibhav Yenamandra                       .setRuleId(CheckName)
158*7b6fe711SVaibhav Yenamandra                       .setDescription(getRuleDescription(CheckName))
159*7b6fe711SVaibhav Yenamandra                       .setHelpURI(getRuleHelpURIStr(CheckName));
160*7b6fe711SVaibhav Yenamandra       size_t RuleIdx = SarifWriter.createRule(Rule);
161*7b6fe711SVaibhav Yenamandra       RuleMapping[CheckName] = RuleIdx;
1629c4b2225SAlexander Belyaev     }
163f5ef2c58SKazu Hirata   }
164*7b6fe711SVaibhav Yenamandra   return RuleMapping;
1659c4b2225SAlexander Belyaev }
1669c4b2225SAlexander Belyaev 
createResult(const PathDiagnostic * Diag,const StringMap<uint32_t> & RuleMapping,const LangOptions & LO)167*7b6fe711SVaibhav Yenamandra static SarifResult createResult(const PathDiagnostic *Diag,
168*7b6fe711SVaibhav Yenamandra                                 const StringMap<uint32_t> &RuleMapping,
169*7b6fe711SVaibhav Yenamandra                                 const LangOptions &LO) {
1709c4b2225SAlexander Belyaev 
171*7b6fe711SVaibhav Yenamandra   StringRef CheckName = Diag->getCheckerName();
172*7b6fe711SVaibhav Yenamandra   uint32_t RuleIdx = RuleMapping.lookup(CheckName);
173*7b6fe711SVaibhav Yenamandra   auto Range = convertTokenRangeToCharRange(
174*7b6fe711SVaibhav Yenamandra       Diag->getLocation().asRange(), Diag->getLocation().getManager(), LO);
1759c4b2225SAlexander Belyaev 
176*7b6fe711SVaibhav Yenamandra   SmallVector<ThreadFlow, 8> Flows = createThreadFlows(Diag, LO);
177*7b6fe711SVaibhav Yenamandra   auto Result = SarifResult::create(RuleIdx)
178*7b6fe711SVaibhav Yenamandra                     .setRuleId(CheckName)
179*7b6fe711SVaibhav Yenamandra                     .setDiagnosticMessage(Diag->getVerboseDescription())
180*7b6fe711SVaibhav Yenamandra                     .setDiagnosticLevel(SarifResultLevel::Warning)
181*7b6fe711SVaibhav Yenamandra                     .setLocations({Range})
182*7b6fe711SVaibhav Yenamandra                     .setThreadFlows(Flows);
183*7b6fe711SVaibhav Yenamandra   return Result;
1849c4b2225SAlexander Belyaev }
1859c4b2225SAlexander Belyaev 
FlushDiagnosticsImpl(std::vector<const PathDiagnostic * > & Diags,FilesMade *)1869c4b2225SAlexander Belyaev void SarifDiagnostics::FlushDiagnosticsImpl(
1879c4b2225SAlexander Belyaev     std::vector<const PathDiagnostic *> &Diags, FilesMade *) {
1889c4b2225SAlexander Belyaev   // We currently overwrite the file if it already exists. However, it may be
1899c4b2225SAlexander Belyaev   // useful to add a feature someday that allows the user to append a run to an
1909c4b2225SAlexander Belyaev   // existing SARIF file. One danger from that approach is that the size of the
1919c4b2225SAlexander Belyaev   // file can become large very quickly, so decoding into JSON to append a run
1929c4b2225SAlexander Belyaev   // may be an expensive operation.
1939c4b2225SAlexander Belyaev   std::error_code EC;
19482b3e28eSAbhina Sreeskantharajan   llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF);
1959c4b2225SAlexander Belyaev   if (EC) {
1969c4b2225SAlexander Belyaev     llvm::errs() << "warning: could not create file: " << EC.message() << '\n';
1979c4b2225SAlexander Belyaev     return;
1989c4b2225SAlexander Belyaev   }
199*7b6fe711SVaibhav Yenamandra 
200*7b6fe711SVaibhav Yenamandra   std::string ToolVersion = getClangFullVersion();
201*7b6fe711SVaibhav Yenamandra   SarifWriter.createRun("clang", "clang static analyzer", ToolVersion);
202*7b6fe711SVaibhav Yenamandra   StringMap<uint32_t> RuleMapping = createRuleMapping(Diags, SarifWriter);
203*7b6fe711SVaibhav Yenamandra   for (const PathDiagnostic *D : Diags) {
204*7b6fe711SVaibhav Yenamandra     SarifResult Result = createResult(D, RuleMapping, LO);
205*7b6fe711SVaibhav Yenamandra     SarifWriter.appendResult(Result);
206*7b6fe711SVaibhav Yenamandra   }
207*7b6fe711SVaibhav Yenamandra   auto Document = SarifWriter.createDocument();
208*7b6fe711SVaibhav Yenamandra   OS << llvm::formatv("{0:2}\n", json::Value(std::move(Document)));
2099c4b2225SAlexander Belyaev }
210