//===--------- SARIFDiagnostic.cpp - SARIF Diagnostic Formatting ----------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/Frontend/SARIFDiagnostic.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/Sarif.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/ErrorOr.h" #include "llvm/Support/Locale.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include #include namespace clang { SARIFDiagnostic::SARIFDiagnostic(raw_ostream &OS, const LangOptions &LangOpts, DiagnosticOptions *DiagOpts, SarifDocumentWriter *Writer) : DiagnosticRenderer(LangOpts, DiagOpts), Writer(Writer) {} // FIXME(llvm-project/issues/57323): Refactor Diagnostic classes. void SARIFDiagnostic::emitDiagnosticMessage( FullSourceLoc Loc, PresumedLoc PLoc, DiagnosticsEngine::Level Level, StringRef Message, ArrayRef Ranges, DiagOrStoredDiag D) { const auto *Diag = D.dyn_cast(); if (!Diag) return; SarifRule Rule = SarifRule::create().setRuleId(std::to_string(Diag->getID())); Rule = addDiagnosticLevelToRule(Rule, Level); unsigned RuleIdx = Writer->createRule(Rule); SarifResult Result = SarifResult::create(RuleIdx).setDiagnosticMessage(Message); if (Loc.isValid()) Result = addLocationToResult(Result, Loc, PLoc, Ranges, *Diag); Writer->appendResult(Result); } SarifResult SARIFDiagnostic::addLocationToResult( SarifResult Result, FullSourceLoc Loc, PresumedLoc PLoc, ArrayRef Ranges, const Diagnostic &Diag) { SmallVector Locations = {}; if (PLoc.isInvalid()) { // At least add the file name if available: FileID FID = Loc.getFileID(); if (FID.isValid()) { if (const FileEntry *FE = Loc.getFileEntry()) { emitFilename(FE->getName(), Loc.getManager()); // FIXME(llvm-project/issues/57366): File-only locations } } return Result; } FileID CaretFileID = Loc.getExpansionLoc().getFileID(); for (const CharSourceRange Range : Ranges) { // Ignore invalid ranges. if (Range.isInvalid()) continue; auto &SM = Loc.getManager(); SourceLocation B = SM.getExpansionLoc(Range.getBegin()); CharSourceRange ERange = SM.getExpansionRange(Range.getEnd()); SourceLocation E = ERange.getEnd(); bool IsTokenRange = ERange.isTokenRange(); std::pair BInfo = SM.getDecomposedLoc(B); std::pair EInfo = SM.getDecomposedLoc(E); // If the start or end of the range is in another file, just discard // it. if (BInfo.first != CaretFileID || EInfo.first != CaretFileID) continue; // Add in the length of the token, so that we cover multi-char // tokens. unsigned TokSize = 0; if (IsTokenRange) TokSize = Lexer::MeasureTokenLength(E, SM, LangOpts); FullSourceLoc BF(B, SM), EF(E, SM); SourceLocation BeginLoc = SM.translateLineCol( BF.getFileID(), BF.getLineNumber(), BF.getColumnNumber()); SourceLocation EndLoc = SM.translateLineCol( EF.getFileID(), EF.getLineNumber(), EF.getColumnNumber() + TokSize); Locations.push_back( CharSourceRange{SourceRange{BeginLoc, EndLoc}, /* ITR = */ false}); // FIXME: Additional ranges should use presumed location in both // Text and SARIF diagnostics. } auto &SM = Loc.getManager(); auto FID = PLoc.getFileID(); // Visual Studio 2010 or earlier expects column number to be off by one. unsigned int ColNo = (LangOpts.MSCompatibilityVersion && !LangOpts.isCompatibleWithMSVC(LangOptions::MSVC2012)) ? PLoc.getColumn() - 1 : PLoc.getColumn(); SourceLocation DiagLoc = SM.translateLineCol(FID, PLoc.getLine(), ColNo); // FIXME(llvm-project/issues/57366): Properly process #line directives. Locations.push_back( CharSourceRange{SourceRange{DiagLoc, DiagLoc}, /* ITR = */ false}); return Result.setLocations(Locations); } SarifRule SARIFDiagnostic::addDiagnosticLevelToRule(SarifRule Rule, DiagnosticsEngine::Level Level) { auto Config = SarifReportingConfiguration::create(); switch (Level) { case DiagnosticsEngine::Note: Config = Config.setLevel(SarifResultLevel::Note); break; case DiagnosticsEngine::Remark: Config = Config.setLevel(SarifResultLevel::None); break; case DiagnosticsEngine::Warning: Config = Config.setLevel(SarifResultLevel::Warning); break; case DiagnosticsEngine::Error: Config = Config.setLevel(SarifResultLevel::Error).setRank(50); break; case DiagnosticsEngine::Fatal: Config = Config.setLevel(SarifResultLevel::Error).setRank(100); break; case DiagnosticsEngine::Ignored: assert(false && "Invalid diagnostic type"); } return Rule.setDefaultConfiguration(Config); } llvm::StringRef SARIFDiagnostic::emitFilename(StringRef Filename, const SourceManager &SM) { if (DiagOpts->AbsolutePath) { llvm::ErrorOr File = SM.getFileManager().getFile(Filename); if (File) { // We want to print a simplified absolute path, i. e. without "dots". // // The hardest part here are the paths like "//../". // On Unix-like systems, we cannot just collapse "/..", because // paths are resolved sequentially, and, thereby, the path // "/" may point to a different location. That is why // we use FileManager::getCanonicalName(), which expands all indirections // with llvm::sys::fs::real_path() and caches the result. // // On the other hand, it would be better to preserve as much of the // original path as possible, because that helps a user to recognize it. // real_path() expands all links, which is sometimes too much. Luckily, // on Windows we can just use llvm::sys::path::remove_dots(), because, // on that system, both aforementioned paths point to the same place. #ifdef _WIN32 SmallString<256> TmpFilename = (*File)->getName(); llvm::sys::fs::make_absolute(TmpFilename); llvm::sys::path::native(TmpFilename); llvm::sys::path::remove_dots(TmpFilename, /* remove_dot_dot */ true); Filename = StringRef(TmpFilename.data(), TmpFilename.size()); #else Filename = SM.getFileManager().getCanonicalName(*File); #endif } } return Filename; } /// Print out the file/line/column information and include trace. /// /// This method handlen the emission of the diagnostic location information. /// This includes extracting as much location information as is present for /// the diagnostic and printing it, as well as any include stack or source /// ranges necessary. void SARIFDiagnostic::emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc, DiagnosticsEngine::Level Level, ArrayRef Ranges) { assert(false && "Not implemented in SARIF mode"); } void SARIFDiagnostic::emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) { assert(false && "Not implemented in SARIF mode"); } void SARIFDiagnostic::emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc, StringRef ModuleName) { assert(false && "Not implemented in SARIF mode"); } void SARIFDiagnostic::emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc, StringRef ModuleName) { assert(false && "Not implemented in SARIF mode"); } } // namespace clang