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 (const FileEntry *FE = Loc.getFileEntry()) {
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     llvm::ErrorOr<const FileEntry *> File =
168         SM.getFileManager().getFile(Filename);
169     if (File) {
170       // We want to print a simplified absolute path, i. e. without "dots".
171       //
172       // The hardest part here are the paths like "<part1>/<link>/../<part2>".
173       // On Unix-like systems, we cannot just collapse "<link>/..", because
174       // paths are resolved sequentially, and, thereby, the path
175       // "<part1>/<part2>" may point to a different location. That is why
176       // we use FileManager::getCanonicalName(), which expands all indirections
177       // with llvm::sys::fs::real_path() and caches the result.
178       //
179       // On the other hand, it would be better to preserve as much of the
180       // original path as possible, because that helps a user to recognize it.
181       // real_path() expands all links, which is sometimes too much. Luckily,
182       // on Windows we can just use llvm::sys::path::remove_dots(), because,
183       // on that system, both aforementioned paths point to the same place.
184 #ifdef _WIN32
185       SmallString<256> TmpFilename = (*File)->getName();
186       llvm::sys::fs::make_absolute(TmpFilename);
187       llvm::sys::path::native(TmpFilename);
188       llvm::sys::path::remove_dots(TmpFilename, /* remove_dot_dot */ true);
189       Filename = StringRef(TmpFilename.data(), TmpFilename.size());
190 #else
191       Filename = SM.getFileManager().getCanonicalName(*File);
192 #endif
193     }
194   }
195 
196   return Filename;
197 }
198 
199 /// Print out the file/line/column information and include trace.
200 ///
201 /// This method handlen the emission of the diagnostic location information.
202 /// This includes extracting as much location information as is present for
203 /// the diagnostic and printing it, as well as any include stack or source
204 /// ranges necessary.
205 void SARIFDiagnostic::emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
206                                         DiagnosticsEngine::Level Level,
207                                         ArrayRef<CharSourceRange> Ranges) {
208   assert(false && "Not implemented in SARIF mode");
209 }
210 
211 void SARIFDiagnostic::emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) {
212   assert(false && "Not implemented in SARIF mode");
213 }
214 
215 void SARIFDiagnostic::emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
216                                          StringRef ModuleName) {
217   assert(false && "Not implemented in SARIF mode");
218 }
219 
220 void SARIFDiagnostic::emitBuildingModuleLocation(FullSourceLoc Loc,
221                                                  PresumedLoc PLoc,
222                                                  StringRef ModuleName) {
223   assert(false && "Not implemented in SARIF mode");
224 }
225 } // namespace clang
226