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 | |
31 | namespace clang { |
32 | |
33 | SARIFDiagnostic::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. |
39 | void 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 | |
64 | SarifResult 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 | |
136 | SarifRule |
137 | SARIFDiagnostic::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 | |
164 | llvm::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. |
204 | void SARIFDiagnostic::emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc, |
205 | DiagnosticsEngine::Level Level, |
206 | ArrayRef<CharSourceRange> Ranges) { |
207 | assert(false && "Not implemented in SARIF mode" ); |
208 | } |
209 | |
210 | void SARIFDiagnostic::emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) { |
211 | assert(false && "Not implemented in SARIF mode" ); |
212 | } |
213 | |
214 | void SARIFDiagnostic::emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc, |
215 | StringRef ModuleName) { |
216 | assert(false && "Not implemented in SARIF mode" ); |
217 | } |
218 | |
219 | void SARIFDiagnostic::emitBuildingModuleLocation(FullSourceLoc Loc, |
220 | PresumedLoc PLoc, |
221 | StringRef ModuleName) { |
222 | assert(false && "Not implemented in SARIF mode" ); |
223 | } |
224 | } // namespace clang |
225 | |