1//===--------- SARIFDiagnostic.cpp - SARIF Diagnostic Formatting ----------===//
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#include "clang/Frontend/SARIFDiagnostic.h"
10#include "clang/Basic/CharInfo.h"
11#include "clang/Basic/DiagnosticOptions.h"
12#include "clang/Basic/FileManager.h"
13#include "clang/Basic/Sarif.h"
14#include "clang/Basic/SourceLocation.h"
15#include "clang/Basic/SourceManager.h"
16#include "clang/Lex/Lexer.h"
17#include "llvm/ADT/ArrayRef.h"
18#include "llvm/ADT/SmallString.h"
19#include "llvm/ADT/StringExtras.h"
20#include "llvm/ADT/StringRef.h"
21#include "llvm/Support/Casting.h"
22#include "llvm/Support/ConvertUTF.h"
23#include "llvm/Support/ErrorHandling.h"
24#include "llvm/Support/ErrorOr.h"
25#include "llvm/Support/Locale.h"
26#include "llvm/Support/Path.h"
27#include "llvm/Support/raw_ostream.h"
28#include <algorithm>
29#include <string>
30
31namespace clang {
32
33SARIFDiagnostic::SARIFDiagnostic(raw_ostream &OS, const LangOptions &LangOpts,
34 DiagnosticOptions *DiagOpts,
35 SarifDocumentWriter *Writer)
36 : DiagnosticRenderer(LangOpts, DiagOpts), Writer(Writer) {}
37
38// FIXME(llvm-project/issues/57323): Refactor Diagnostic classes.
39void SARIFDiagnostic::emitDiagnosticMessage(
40 FullSourceLoc Loc, PresumedLoc PLoc, DiagnosticsEngine::Level Level,
41 StringRef Message, ArrayRef<clang::CharSourceRange> Ranges,
42 DiagOrStoredDiag D) {
43
44 const auto *Diag = D.dyn_cast<const Diagnostic *>();
45
46 if (!Diag)
47 return;
48
49 SarifRule Rule = SarifRule::create().setRuleId(std::to_string(val: Diag->getID()));
50
51 Rule = addDiagnosticLevelToRule(Rule, Level);
52
53 unsigned RuleIdx = Writer->createRule(Rule);
54
55 SarifResult Result =
56 SarifResult::create(RuleIdx).setDiagnosticMessage(Message);
57
58 if (Loc.isValid())
59 Result = addLocationToResult(Result, Loc, PLoc, Ranges, Diag: *Diag);
60
61 Writer->appendResult(SarifResult: Result);
62}
63
64SarifResult SARIFDiagnostic::addLocationToResult(
65 SarifResult Result, FullSourceLoc Loc, PresumedLoc PLoc,
66 ArrayRef<CharSourceRange> Ranges, const Diagnostic &Diag) {
67 SmallVector<CharSourceRange> Locations = {};
68
69 if (PLoc.isInvalid()) {
70 // At least add the file name if available:
71 FileID FID = Loc.getFileID();
72 if (FID.isValid()) {
73 if (OptionalFileEntryRef FE = Loc.getFileEntryRef()) {
74 emitFilename(Filename: FE->getName(), SM: Loc.getManager());
75 // FIXME(llvm-project/issues/57366): File-only locations
76 }
77 }
78 return Result;
79 }
80
81 FileID CaretFileID = Loc.getExpansionLoc().getFileID();
82
83 for (const CharSourceRange Range : Ranges) {
84 // Ignore invalid ranges.
85 if (Range.isInvalid())
86 continue;
87
88 auto &SM = Loc.getManager();
89 SourceLocation B = SM.getExpansionLoc(Loc: Range.getBegin());
90 CharSourceRange ERange = SM.getExpansionRange(Loc: Range.getEnd());
91 SourceLocation E = ERange.getEnd();
92 bool IsTokenRange = ERange.isTokenRange();
93
94 std::pair<FileID, unsigned> BInfo = SM.getDecomposedLoc(Loc: B);
95 std::pair<FileID, unsigned> EInfo = SM.getDecomposedLoc(Loc: E);
96
97 // If the start or end of the range is in another file, just discard
98 // it.
99 if (BInfo.first != CaretFileID || EInfo.first != CaretFileID)
100 continue;
101
102 // Add in the length of the token, so that we cover multi-char
103 // tokens.
104 unsigned TokSize = 0;
105 if (IsTokenRange)
106 TokSize = Lexer::MeasureTokenLength(Loc: E, SM, LangOpts);
107
108 FullSourceLoc BF(B, SM), EF(E, SM);
109 SourceLocation BeginLoc = SM.translateLineCol(
110 FID: BF.getFileID(), Line: BF.getLineNumber(), Col: BF.getColumnNumber());
111 SourceLocation EndLoc = SM.translateLineCol(
112 FID: EF.getFileID(), Line: EF.getLineNumber(), Col: EF.getColumnNumber() + TokSize);
113
114 Locations.push_back(
115 Elt: CharSourceRange{SourceRange{BeginLoc, EndLoc}, /* ITR = */ false});
116 // FIXME: Additional ranges should use presumed location in both
117 // Text and SARIF diagnostics.
118 }
119
120 auto &SM = Loc.getManager();
121 auto FID = PLoc.getFileID();
122 // Visual Studio 2010 or earlier expects column number to be off by one.
123 unsigned int ColNo = (LangOpts.MSCompatibilityVersion &&
124 !LangOpts.isCompatibleWithMSVC(MajorVersion: LangOptions::MSVC2012))
125 ? PLoc.getColumn() - 1
126 : PLoc.getColumn();
127 SourceLocation DiagLoc = SM.translateLineCol(FID, Line: PLoc.getLine(), Col: ColNo);
128
129 // FIXME(llvm-project/issues/57366): Properly process #line directives.
130 Locations.push_back(
131 Elt: CharSourceRange{SourceRange{DiagLoc, DiagLoc}, /* ITR = */ false});
132
133 return Result.setLocations(Locations);
134}
135
136SarifRule
137SARIFDiagnostic::addDiagnosticLevelToRule(SarifRule Rule,
138 DiagnosticsEngine::Level Level) {
139 auto Config = SarifReportingConfiguration::create();
140
141 switch (Level) {
142 case DiagnosticsEngine::Note:
143 Config = Config.setLevel(SarifResultLevel::Note);
144 break;
145 case DiagnosticsEngine::Remark:
146 Config = Config.setLevel(SarifResultLevel::None);
147 break;
148 case DiagnosticsEngine::Warning:
149 Config = Config.setLevel(SarifResultLevel::Warning);
150 break;
151 case DiagnosticsEngine::Error:
152 Config = Config.setLevel(SarifResultLevel::Error).setRank(50);
153 break;
154 case DiagnosticsEngine::Fatal:
155 Config = Config.setLevel(SarifResultLevel::Error).setRank(100);
156 break;
157 case DiagnosticsEngine::Ignored:
158 assert(false && "Invalid diagnostic type");
159 }
160
161 return Rule.setDefaultConfiguration(Config);
162}
163
164llvm::StringRef SARIFDiagnostic::emitFilename(StringRef Filename,
165 const SourceManager &SM) {
166 if (DiagOpts->AbsolutePath) {
167 auto File = SM.getFileManager().getOptionalFileRef(Filename);
168 if (File) {
169 // We want to print a simplified absolute path, i. e. without "dots".
170 //
171 // The hardest part here are the paths like "<part1>/<link>/../<part2>".
172 // On Unix-like systems, we cannot just collapse "<link>/..", because
173 // paths are resolved sequentially, and, thereby, the path
174 // "<part1>/<part2>" may point to a different location. That is why
175 // we use FileManager::getCanonicalName(), which expands all indirections
176 // with llvm::sys::fs::real_path() and caches the result.
177 //
178 // On the other hand, it would be better to preserve as much of the
179 // original path as possible, because that helps a user to recognize it.
180 // real_path() expands all links, which is sometimes too much. Luckily,
181 // on Windows we can just use llvm::sys::path::remove_dots(), because,
182 // on that system, both aforementioned paths point to the same place.
183#ifdef _WIN32
184 SmallString<256> TmpFilename = File->getName();
185 llvm::sys::fs::make_absolute(TmpFilename);
186 llvm::sys::path::native(TmpFilename);
187 llvm::sys::path::remove_dots(TmpFilename, /* remove_dot_dot */ true);
188 Filename = StringRef(TmpFilename.data(), TmpFilename.size());
189#else
190 Filename = SM.getFileManager().getCanonicalName(File: *File);
191#endif
192 }
193 }
194
195 return Filename;
196}
197
198/// Print out the file/line/column information and include trace.
199///
200/// This method handlen the emission of the diagnostic location information.
201/// This includes extracting as much location information as is present for
202/// the diagnostic and printing it, as well as any include stack or source
203/// ranges necessary.
204void SARIFDiagnostic::emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
205 DiagnosticsEngine::Level Level,
206 ArrayRef<CharSourceRange> Ranges) {
207 assert(false && "Not implemented in SARIF mode");
208}
209
210void SARIFDiagnostic::emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) {
211 assert(false && "Not implemented in SARIF mode");
212}
213
214void SARIFDiagnostic::emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
215 StringRef ModuleName) {
216 assert(false && "Not implemented in SARIF mode");
217}
218
219void SARIFDiagnostic::emitBuildingModuleLocation(FullSourceLoc Loc,
220 PresumedLoc PLoc,
221 StringRef ModuleName) {
222 assert(false && "Not implemented in SARIF mode");
223}
224} // namespace clang
225