aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/clang16/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp
blob: fab520098f13e824cab3035670cef2fd63aa09b3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//===--- SarifDiagnostics.cpp - Sarif Diagnostics for Paths -----*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
//  This file defines the SarifDiagnostics object.
//
//===----------------------------------------------------------------------===//

#include "clang/Analysis/MacroExpansionContext.h"
#include "clang/Analysis/PathDiagnostic.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/Sarif.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/Version.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Path.h"

using namespace llvm;
using namespace clang;
using namespace ento;

namespace {
class SarifDiagnostics : public PathDiagnosticConsumer {
  std::string OutputFile;
  const LangOptions &LO;
  SarifDocumentWriter SarifWriter;

public:
  SarifDiagnostics(const std::string &Output, const LangOptions &LO,
                   const SourceManager &SM)
      : OutputFile(Output), LO(LO), SarifWriter(SM) {}
  ~SarifDiagnostics() override = default;

  void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
                            FilesMade *FM) override;

  StringRef getName() const override { return "SarifDiagnostics"; }
  PathGenerationScheme getGenerationScheme() const override { return Minimal; }
  bool supportsLogicalOpControlFlow() const override { return true; }
  bool supportsCrossFileDiagnostics() const override { return true; }
};
} // end anonymous namespace

void ento::createSarifDiagnosticConsumer(
    PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
    const std::string &Output, const Preprocessor &PP,
    const cross_tu::CrossTranslationUnitContext &CTU,
    const MacroExpansionContext &MacroExpansions) {

  // TODO: Emit an error here.
  if (Output.empty())
    return;

  C.push_back(
      new SarifDiagnostics(Output, PP.getLangOpts(), PP.getSourceManager()));
  createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, Output, PP,
                                          CTU, MacroExpansions);
}

static StringRef getRuleDescription(StringRef CheckName) {
  return llvm::StringSwitch<StringRef>(CheckName)
#define GET_CHECKERS
#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN)                 \
  .Case(FULLNAME, HELPTEXT)
#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
#undef CHECKER
#undef GET_CHECKERS
      ;
}

static StringRef getRuleHelpURIStr(StringRef CheckName) {
  return llvm::StringSwitch<StringRef>(CheckName)
#define GET_CHECKERS
#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN)                 \
  .Case(FULLNAME, DOC_URI)
#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
#undef CHECKER
#undef GET_CHECKERS
      ;
}

static ThreadFlowImportance
calculateImportance(const PathDiagnosticPiece &Piece) {
  switch (Piece.getKind()) {
  case PathDiagnosticPiece::Call:
  case PathDiagnosticPiece::Macro:
  case PathDiagnosticPiece::Note:
  case PathDiagnosticPiece::PopUp:
    // FIXME: What should be reported here?
    break;
  case PathDiagnosticPiece::Event:
    return Piece.getTagStr() == "ConditionBRVisitor"
               ? ThreadFlowImportance::Important
               : ThreadFlowImportance::Essential;
  case PathDiagnosticPiece::ControlFlow:
    return ThreadFlowImportance::Unimportant;
  }
  return ThreadFlowImportance::Unimportant;
}

/// Accepts a SourceRange corresponding to a pair of the first and last tokens
/// and converts to a Character granular CharSourceRange.
static CharSourceRange convertTokenRangeToCharRange(const SourceRange &R,
                                                    const SourceManager &SM,
                                                    const LangOptions &LO) {
  // Caret diagnostics have the first and last locations pointed at the same
  // location, return these as-is.
  if (R.getBegin() == R.getEnd())
    return CharSourceRange::getCharRange(R);

  SourceLocation BeginCharLoc = R.getBegin();
  // For token ranges, the raw end SLoc points at the first character of the
  // last token in the range. This must be moved to one past the end of the
  // last character using the lexer.
  SourceLocation EndCharLoc =
      Lexer::getLocForEndOfToken(R.getEnd(), /* Offset = */ 0, SM, LO);
  return CharSourceRange::getCharRange(BeginCharLoc, EndCharLoc);
}

static SmallVector<ThreadFlow, 8> createThreadFlows(const PathDiagnostic *Diag,
                                                    const LangOptions &LO) {
  SmallVector<ThreadFlow, 8> Flows;
  const PathPieces &Pieces = Diag->path.flatten(false);
  for (const auto &Piece : Pieces) {
    auto Range = convertTokenRangeToCharRange(
        Piece->getLocation().asRange(), Piece->getLocation().getManager(), LO);
    auto Flow = ThreadFlow::create()
                    .setImportance(calculateImportance(*Piece))
                    .setRange(Range)
                    .setMessage(Piece->getString());
    Flows.push_back(Flow);
  }
  return Flows;
}

static StringMap<uint32_t>
createRuleMapping(const std::vector<const PathDiagnostic *> &Diags,
                  SarifDocumentWriter &SarifWriter) {
  StringMap<uint32_t> RuleMapping;
  llvm::StringSet<> Seen;

  for (const PathDiagnostic *D : Diags) {
    StringRef CheckName = D->getCheckerName();
    std::pair<llvm::StringSet<>::iterator, bool> P = Seen.insert(CheckName);
    if (P.second) {
      auto Rule = SarifRule::create()
                      .setName(CheckName)
                      .setRuleId(CheckName)
                      .setDescription(getRuleDescription(CheckName))
                      .setHelpURI(getRuleHelpURIStr(CheckName));
      size_t RuleIdx = SarifWriter.createRule(Rule);
      RuleMapping[CheckName] = RuleIdx;
    }
  }
  return RuleMapping;
}

static SarifResult createResult(const PathDiagnostic *Diag,
                                const StringMap<uint32_t> &RuleMapping,
                                const LangOptions &LO) {

  StringRef CheckName = Diag->getCheckerName();
  uint32_t RuleIdx = RuleMapping.lookup(CheckName);
  auto Range = convertTokenRangeToCharRange(
      Diag->getLocation().asRange(), Diag->getLocation().getManager(), LO);

  SmallVector<ThreadFlow, 8> Flows = createThreadFlows(Diag, LO);
  auto Result = SarifResult::create(RuleIdx)
                    .setRuleId(CheckName)
                    .setDiagnosticMessage(Diag->getVerboseDescription())
                    .setDiagnosticLevel(SarifResultLevel::Warning)
                    .setLocations({Range})
                    .setThreadFlows(Flows);
  return Result;
}

void SarifDiagnostics::FlushDiagnosticsImpl(
    std::vector<const PathDiagnostic *> &Diags, FilesMade *) {
  // We currently overwrite the file if it already exists. However, it may be
  // useful to add a feature someday that allows the user to append a run to an
  // existing SARIF file. One danger from that approach is that the size of the
  // file can become large very quickly, so decoding into JSON to append a run
  // may be an expensive operation.
  std::error_code EC;
  llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF);
  if (EC) {
    llvm::errs() << "warning: could not create file: " << EC.message() << '\n';
    return;
  }

  std::string ToolVersion = getClangFullVersion();
  SarifWriter.createRun("clang", "clang static analyzer", ToolVersion);
  StringMap<uint32_t> RuleMapping = createRuleMapping(Diags, SarifWriter);
  for (const PathDiagnostic *D : Diags) {
    SarifResult Result = createResult(D, RuleMapping, LO);
    SarifWriter.appendResult(Result);
  }
  auto Document = SarifWriter.createDocument();
  OS << llvm::formatv("{0:2}\n", json::Value(std::move(Document)));
}