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)));
}
|