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