xref: /freebsd/contrib/llvm-project/clang/lib/Frontend/SARIFDiagnostic.cpp (revision a90b9d0159070121c221b966469c3e36d912bf82)
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(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 
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
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);
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