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