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 for (auto &[RelLoc, RelPLoc] : RelatedLocationsCache)
62 Result = addRelatedLocationToResult(Result, Loc: RelLoc, PLoc: RelPLoc);
63 RelatedLocationsCache.clear();
64
65 Writer->appendResult(SarifResult: Result);
66}
67
68void SARIFDiagnostic::emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) {
69 // We always emit include location before results, for example:
70 //
71 // In file included from ...
72 // In file included from ...
73 // error: ...
74 //
75 // At this time We cannot peek the SarifRule. But what we
76 // do is to push it into a cache and wait for next time
77 // \ref SARIFDiagnostic::emitDiagnosticMessage to pick it up.
78 RelatedLocationsCache.push_back(Elt: {Loc, PLoc});
79}
80
81void SARIFDiagnostic::emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
82 StringRef ModuleName) {
83 RelatedLocationsCache.push_back(Elt: {Loc, PLoc});
84}
85
86SarifResult SARIFDiagnostic::addLocationToResult(
87 SarifResult Result, FullSourceLoc Loc, PresumedLoc PLoc,
88 ArrayRef<CharSourceRange> Ranges, const Diagnostic &Diag) {
89 auto Locations = getSarifLocation(Loc, PLoc, Ranges);
90 return Result.addLocations(DiagLocs: Locations);
91}
92
93SarifResult SARIFDiagnostic::addRelatedLocationToResult(SarifResult Result,
94 FullSourceLoc Loc,
95 PresumedLoc PLoc) {
96 auto Locations = getSarifLocation(Loc, PLoc, Ranges: {});
97 return Result.addRelatedLocations(DiagLocs: Locations);
98}
99
100llvm::SmallVector<CharSourceRange>
101SARIFDiagnostic::getSarifLocation(FullSourceLoc Loc, PresumedLoc PLoc,
102 ArrayRef<CharSourceRange> Ranges) {
103 SmallVector<CharSourceRange> Locations = {};
104
105 if (PLoc.isInvalid()) {
106 // At least add the file name if available:
107 FileID FID = Loc.getFileID();
108 if (FID.isValid()) {
109 if (OptionalFileEntryRef FE = Loc.getFileEntryRef()) {
110 emitFilename(Filename: FE->getName(), SM: Loc.getManager());
111 // FIXME(llvm-project/issues/57366): File-only locations
112 }
113 }
114 return {};
115 }
116
117 FileID CaretFileID = Loc.getExpansionLoc().getFileID();
118
119 for (const CharSourceRange Range : Ranges) {
120 // Ignore invalid ranges.
121 if (Range.isInvalid())
122 continue;
123
124 auto &SM = Loc.getManager();
125 SourceLocation B = SM.getExpansionLoc(Loc: Range.getBegin());
126 CharSourceRange ERange = SM.getExpansionRange(Loc: Range.getEnd());
127 SourceLocation E = ERange.getEnd();
128 bool IsTokenRange = ERange.isTokenRange();
129
130 FileIDAndOffset BInfo = SM.getDecomposedLoc(Loc: B);
131 FileIDAndOffset EInfo = SM.getDecomposedLoc(Loc: E);
132
133 // If the start or end of the range is in another file, just discard
134 // it.
135 if (BInfo.first != CaretFileID || EInfo.first != CaretFileID)
136 continue;
137
138 // Add in the length of the token, so that we cover multi-char
139 // tokens.
140 unsigned TokSize = 0;
141 if (IsTokenRange)
142 TokSize = Lexer::MeasureTokenLength(Loc: E, SM, LangOpts);
143
144 FullSourceLoc BF(B, SM), EF(E, SM);
145 SourceLocation BeginLoc = SM.translateLineCol(
146 FID: BF.getFileID(), Line: BF.getLineNumber(), Col: BF.getColumnNumber());
147 SourceLocation EndLoc = SM.translateLineCol(
148 FID: EF.getFileID(), Line: EF.getLineNumber(), Col: EF.getColumnNumber() + TokSize);
149
150 Locations.push_back(
151 Elt: CharSourceRange{SourceRange{BeginLoc, EndLoc}, /* ITR = */ false});
152 // FIXME: Additional ranges should use presumed location in both
153 // Text and SARIF diagnostics.
154 }
155
156 auto &SM = Loc.getManager();
157 auto FID = PLoc.getFileID();
158 // Visual Studio 2010 or earlier expects column number to be off by one.
159 unsigned int ColNo = (LangOpts.MSCompatibilityVersion &&
160 !LangOpts.isCompatibleWithMSVC(MajorVersion: LangOptions::MSVC2012))
161 ? PLoc.getColumn() - 1
162 : PLoc.getColumn();
163 SourceLocation DiagLoc = SM.translateLineCol(FID, Line: PLoc.getLine(), Col: ColNo);
164
165 // FIXME(llvm-project/issues/57366): Properly process #line directives.
166 CharSourceRange Range = {SourceRange{DiagLoc, DiagLoc}, /* ITR = */ false};
167 if (Range.isValid())
168 Locations.push_back(Elt: std::move(Range));
169
170 return Locations;
171}
172
173SarifRule
174SARIFDiagnostic::addDiagnosticLevelToRule(SarifRule Rule,
175 DiagnosticsEngine::Level Level) {
176 auto Config = SarifReportingConfiguration::create();
177
178 switch (Level) {
179 case DiagnosticsEngine::Note:
180 Config = Config.setLevel(SarifResultLevel::Note);
181 break;
182 case DiagnosticsEngine::Remark:
183 Config = Config.setLevel(SarifResultLevel::None);
184 break;
185 case DiagnosticsEngine::Warning:
186 Config = Config.setLevel(SarifResultLevel::Warning);
187 break;
188 case DiagnosticsEngine::Error:
189 Config = Config.setLevel(SarifResultLevel::Error).setRank(50);
190 break;
191 case DiagnosticsEngine::Fatal:
192 Config = Config.setLevel(SarifResultLevel::Error).setRank(100);
193 break;
194 case DiagnosticsEngine::Ignored:
195 assert(false && "Invalid diagnostic type");
196 }
197
198 return Rule.setDefaultConfiguration(Config);
199}
200
201llvm::StringRef SARIFDiagnostic::emitFilename(StringRef Filename,
202 const SourceManager &SM) {
203 if (DiagOpts.AbsolutePath) {
204 auto File = SM.getFileManager().getOptionalFileRef(Filename);
205 if (File) {
206 // We want to print a simplified absolute path, i. e. without "dots".
207 //
208 // The hardest part here are the paths like "<part1>/<link>/../<part2>".
209 // On Unix-like systems, we cannot just collapse "<link>/..", because
210 // paths are resolved sequentially, and, thereby, the path
211 // "<part1>/<part2>" may point to a different location. That is why
212 // we use FileManager::getCanonicalName(), which expands all indirections
213 // with llvm::sys::fs::real_path() and caches the result.
214 //
215 // On the other hand, it would be better to preserve as much of the
216 // original path as possible, because that helps a user to recognize it.
217 // real_path() expands all links, which is sometimes too much. Luckily,
218 // on Windows we can just use llvm::sys::path::remove_dots(), because,
219 // on that system, both aforementioned paths point to the same place.
220#ifdef _WIN32
221 SmallString<256> TmpFilename = File->getName();
222 SM.getFileManager().makeAbsolutePath(TmpFilename);
223 llvm::sys::path::native(TmpFilename);
224 llvm::sys::path::remove_dots(TmpFilename, /* remove_dot_dot */ true);
225 Filename = StringRef(TmpFilename.data(), TmpFilename.size());
226#else
227 Filename = SM.getFileManager().getCanonicalName(File: *File);
228#endif
229 }
230 }
231
232 return Filename;
233}
234
235/// Print out the file/line/column information and include trace.
236///
237/// This method handlen the emission of the diagnostic location information.
238/// This includes extracting as much location information as is present for
239/// the diagnostic and printing it, as well as any include stack or source
240/// ranges necessary.
241void SARIFDiagnostic::emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
242 DiagnosticsEngine::Level Level,
243 ArrayRef<CharSourceRange> Ranges) {
244 assert(false && "Not implemented in SARIF mode");
245}
246
247void SARIFDiagnostic::emitBuildingModuleLocation(FullSourceLoc Loc,
248 PresumedLoc PLoc,
249 StringRef ModuleName) {
250 assert(false && "Not implemented in SARIF mode");
251}
252} // namespace clang
253