Coverage Report

Created: 2023-09-30 09:22

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