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
SARIFDiagnostic(raw_ostream & OS,const LangOptions & LangOpts,DiagnosticOptions * DiagOpts,SarifDocumentWriter * Writer)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.
emitDiagnosticMessage(FullSourceLoc Loc,PresumedLoc PLoc,DiagnosticsEngine::Level Level,StringRef Message,ArrayRef<clang::CharSourceRange> Ranges,DiagOrStoredDiag D)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(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);
60
61 Writer->appendResult(Result);
62 }
63
addLocationToResult(SarifResult Result,FullSourceLoc Loc,PresumedLoc PLoc,ArrayRef<CharSourceRange> Ranges,const Diagnostic & Diag)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(FE->getName(), 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(Range.getBegin());
90 CharSourceRange ERange = SM.getExpansionRange(Range.getEnd());
91 SourceLocation E = ERange.getEnd();
92 bool IsTokenRange = ERange.isTokenRange();
93
94 std::pair<FileID, unsigned> BInfo = SM.getDecomposedLoc(B);
95 std::pair<FileID, unsigned> EInfo = SM.getDecomposedLoc(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(E, SM, LangOpts);
107
108 FullSourceLoc BF(B, SM), EF(E, SM);
109 SourceLocation BeginLoc = SM.translateLineCol(
110 BF.getFileID(), BF.getLineNumber(), BF.getColumnNumber());
111 SourceLocation EndLoc = SM.translateLineCol(
112 EF.getFileID(), EF.getLineNumber(), EF.getColumnNumber() + TokSize);
113
114 Locations.push_back(
115 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(LangOptions::MSVC2012))
125 ? PLoc.getColumn() - 1
126 : PLoc.getColumn();
127 SourceLocation DiagLoc = SM.translateLineCol(FID, PLoc.getLine(), ColNo);
128
129 // FIXME(llvm-project/issues/57366): Properly process #line directives.
130 Locations.push_back(
131 CharSourceRange{SourceRange{DiagLoc, DiagLoc}, /* ITR = */ false});
132
133 return Result.setLocations(Locations);
134 }
135
136 SarifRule
addDiagnosticLevelToRule(SarifRule Rule,DiagnosticsEngine::Level Level)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
emitFilename(StringRef Filename,const SourceManager & SM)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);
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.
emitDiagnosticLoc(FullSourceLoc Loc,PresumedLoc PLoc,DiagnosticsEngine::Level Level,ArrayRef<CharSourceRange> Ranges)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
emitIncludeLocation(FullSourceLoc Loc,PresumedLoc PLoc)210 void SARIFDiagnostic::emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) {
211 assert(false && "Not implemented in SARIF mode");
212 }
213
emitImportLocation(FullSourceLoc Loc,PresumedLoc PLoc,StringRef ModuleName)214 void SARIFDiagnostic::emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
215 StringRef ModuleName) {
216 assert(false && "Not implemented in SARIF mode");
217 }
218
emitBuildingModuleLocation(FullSourceLoc Loc,PresumedLoc PLoc,StringRef ModuleName)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