Path: blob/main/contrib/llvm-project/clang/lib/Frontend/TextDiagnostic.cpp
35234 views
//===--- TextDiagnostic.cpp - Text Diagnostic Pretty-Printing -------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/Frontend/TextDiagnostic.h"9#include "clang/Basic/CharInfo.h"10#include "clang/Basic/DiagnosticOptions.h"11#include "clang/Basic/FileManager.h"12#include "clang/Basic/SourceManager.h"13#include "clang/Lex/Lexer.h"14#include "clang/Lex/Preprocessor.h"15#include "llvm/ADT/SmallString.h"16#include "llvm/ADT/StringExtras.h"17#include "llvm/Support/ConvertUTF.h"18#include "llvm/Support/ErrorHandling.h"19#include "llvm/Support/Locale.h"20#include "llvm/Support/Path.h"21#include "llvm/Support/raw_ostream.h"22#include <algorithm>23#include <optional>2425using namespace clang;2627static const enum raw_ostream::Colors noteColor = raw_ostream::CYAN;28static const enum raw_ostream::Colors remarkColor =29raw_ostream::BLUE;30static const enum raw_ostream::Colors fixitColor =31raw_ostream::GREEN;32static const enum raw_ostream::Colors caretColor =33raw_ostream::GREEN;34static const enum raw_ostream::Colors warningColor =35raw_ostream::MAGENTA;36static const enum raw_ostream::Colors templateColor =37raw_ostream::CYAN;38static const enum raw_ostream::Colors errorColor = raw_ostream::RED;39static const enum raw_ostream::Colors fatalColor = raw_ostream::RED;40// Used for changing only the bold attribute.41static const enum raw_ostream::Colors savedColor =42raw_ostream::SAVEDCOLOR;4344// Magenta is taken for 'warning'. Red is already 'error' and 'cyan'45// is already taken for 'note'. Green is already used to underline46// source ranges. White and black are bad because of the usual47// terminal backgrounds. Which leaves us only with TWO options.48static constexpr raw_ostream::Colors CommentColor = raw_ostream::YELLOW;49static constexpr raw_ostream::Colors LiteralColor = raw_ostream::GREEN;50static constexpr raw_ostream::Colors KeywordColor = raw_ostream::BLUE;5152/// Add highlights to differences in template strings.53static void applyTemplateHighlighting(raw_ostream &OS, StringRef Str,54bool &Normal, bool Bold) {55while (true) {56size_t Pos = Str.find(ToggleHighlight);57OS << Str.slice(0, Pos);58if (Pos == StringRef::npos)59break;6061Str = Str.substr(Pos + 1);62if (Normal)63OS.changeColor(templateColor, true);64else {65OS.resetColor();66if (Bold)67OS.changeColor(savedColor, true);68}69Normal = !Normal;70}71}7273/// Number of spaces to indent when word-wrapping.74const unsigned WordWrapIndentation = 6;7576static int bytesSincePreviousTabOrLineBegin(StringRef SourceLine, size_t i) {77int bytes = 0;78while (0<i) {79if (SourceLine[--i]=='\t')80break;81++bytes;82}83return bytes;84}8586/// returns a printable representation of first item from input range87///88/// This function returns a printable representation of the next item in a line89/// of source. If the next byte begins a valid and printable character, that90/// character is returned along with 'true'.91///92/// Otherwise, if the next byte begins a valid, but unprintable character, a93/// printable, escaped representation of the character is returned, along with94/// 'false'. Otherwise a printable, escaped representation of the next byte95/// is returned along with 'false'.96///97/// \note The index is updated to be used with a subsequent call to98/// printableTextForNextCharacter.99///100/// \param SourceLine The line of source101/// \param I Pointer to byte index,102/// \param TabStop used to expand tabs103/// \return pair(printable text, 'true' iff original text was printable)104///105static std::pair<SmallString<16>, bool>106printableTextForNextCharacter(StringRef SourceLine, size_t *I,107unsigned TabStop) {108assert(I && "I must not be null");109assert(*I < SourceLine.size() && "must point to a valid index");110111if (SourceLine[*I] == '\t') {112assert(0 < TabStop && TabStop <= DiagnosticOptions::MaxTabStop &&113"Invalid -ftabstop value");114unsigned Col = bytesSincePreviousTabOrLineBegin(SourceLine, *I);115unsigned NumSpaces = TabStop - (Col % TabStop);116assert(0 < NumSpaces && NumSpaces <= TabStop117&& "Invalid computation of space amt");118++(*I);119120SmallString<16> ExpandedTab;121ExpandedTab.assign(NumSpaces, ' ');122return std::make_pair(ExpandedTab, true);123}124125const unsigned char *Begin = SourceLine.bytes_begin() + *I;126127// Fast path for the common ASCII case.128if (*Begin < 0x80 && llvm::sys::locale::isPrint(*Begin)) {129++(*I);130return std::make_pair(SmallString<16>(Begin, Begin + 1), true);131}132unsigned CharSize = llvm::getNumBytesForUTF8(*Begin);133const unsigned char *End = Begin + CharSize;134135// Convert it to UTF32 and check if it's printable.136if (End <= SourceLine.bytes_end() && llvm::isLegalUTF8Sequence(Begin, End)) {137llvm::UTF32 C;138llvm::UTF32 *CPtr = &C;139140// Begin and end before conversion.141unsigned char const *OriginalBegin = Begin;142llvm::ConversionResult Res = llvm::ConvertUTF8toUTF32(143&Begin, End, &CPtr, CPtr + 1, llvm::strictConversion);144(void)Res;145assert(Res == llvm::conversionOK);146assert(OriginalBegin < Begin);147assert(unsigned(Begin - OriginalBegin) == CharSize);148149(*I) += (Begin - OriginalBegin);150151// Valid, multi-byte, printable UTF8 character.152if (llvm::sys::locale::isPrint(C))153return std::make_pair(SmallString<16>(OriginalBegin, End), true);154155// Valid but not printable.156SmallString<16> Str("<U+>");157while (C) {158Str.insert(Str.begin() + 3, llvm::hexdigit(C % 16));159C /= 16;160}161while (Str.size() < 8)162Str.insert(Str.begin() + 3, llvm::hexdigit(0));163return std::make_pair(Str, false);164}165166// Otherwise, not printable since it's not valid UTF8.167SmallString<16> ExpandedByte("<XX>");168unsigned char Byte = SourceLine[*I];169ExpandedByte[1] = llvm::hexdigit(Byte / 16);170ExpandedByte[2] = llvm::hexdigit(Byte % 16);171++(*I);172return std::make_pair(ExpandedByte, false);173}174175static void expandTabs(std::string &SourceLine, unsigned TabStop) {176size_t I = SourceLine.size();177while (I > 0) {178I--;179if (SourceLine[I] != '\t')180continue;181size_t TmpI = I;182auto [Str, Printable] =183printableTextForNextCharacter(SourceLine, &TmpI, TabStop);184SourceLine.replace(I, 1, Str.c_str());185}186}187188/// \p BytesOut:189/// A mapping from columns to the byte of the source line that produced the190/// character displaying at that column. This is the inverse of \p ColumnsOut.191///192/// The last element in the array is the number of bytes in the source string.193///194/// example: (given a tabstop of 8)195///196/// "a \t \u3042" -> {0,1,2,-1,-1,-1,-1,-1,3,4,-1,7}197///198/// (\\u3042 is represented in UTF-8 by three bytes and takes two columns to199/// display)200///201/// \p ColumnsOut:202/// A mapping from the bytes203/// of the printable representation of the line to the columns those printable204/// characters will appear at (numbering the first column as 0).205///206/// If a byte 'i' corresponds to multiple columns (e.g. the byte contains a tab207/// character) then the array will map that byte to the first column the208/// tab appears at and the next value in the map will have been incremented209/// more than once.210///211/// If a byte is the first in a sequence of bytes that together map to a single212/// entity in the output, then the array will map that byte to the appropriate213/// column while the subsequent bytes will be -1.214///215/// The last element in the array does not correspond to any byte in the input216/// and instead is the number of columns needed to display the source217///218/// example: (given a tabstop of 8)219///220/// "a \t \u3042" -> {0,1,2,8,9,-1,-1,11}221///222/// (\\u3042 is represented in UTF-8 by three bytes and takes two columns to223/// display)224static void genColumnByteMapping(StringRef SourceLine, unsigned TabStop,225SmallVectorImpl<int> &BytesOut,226SmallVectorImpl<int> &ColumnsOut) {227assert(BytesOut.empty());228assert(ColumnsOut.empty());229230if (SourceLine.empty()) {231BytesOut.resize(1u, 0);232ColumnsOut.resize(1u, 0);233return;234}235236ColumnsOut.resize(SourceLine.size() + 1, -1);237238int Columns = 0;239size_t I = 0;240while (I < SourceLine.size()) {241ColumnsOut[I] = Columns;242BytesOut.resize(Columns + 1, -1);243BytesOut.back() = I;244auto [Str, Printable] =245printableTextForNextCharacter(SourceLine, &I, TabStop);246Columns += llvm::sys::locale::columnWidth(Str);247}248249ColumnsOut.back() = Columns;250BytesOut.resize(Columns + 1, -1);251BytesOut.back() = I;252}253254namespace {255struct SourceColumnMap {256SourceColumnMap(StringRef SourceLine, unsigned TabStop)257: m_SourceLine(SourceLine) {258259genColumnByteMapping(SourceLine, TabStop, m_columnToByte, m_byteToColumn);260261assert(m_byteToColumn.size()==SourceLine.size()+1);262assert(0 < m_byteToColumn.size() && 0 < m_columnToByte.size());263assert(m_byteToColumn.size()264== static_cast<unsigned>(m_columnToByte.back()+1));265assert(static_cast<unsigned>(m_byteToColumn.back()+1)266== m_columnToByte.size());267}268int columns() const { return m_byteToColumn.back(); }269int bytes() const { return m_columnToByte.back(); }270271/// Map a byte to the column which it is at the start of, or return -1272/// if it is not at the start of a column (for a UTF-8 trailing byte).273int byteToColumn(int n) const {274assert(0<=n && n<static_cast<int>(m_byteToColumn.size()));275return m_byteToColumn[n];276}277278/// Map a byte to the first column which contains it.279int byteToContainingColumn(int N) const {280assert(0 <= N && N < static_cast<int>(m_byteToColumn.size()));281while (m_byteToColumn[N] == -1)282--N;283return m_byteToColumn[N];284}285286/// Map a column to the byte which starts the column, or return -1 if287/// the column the second or subsequent column of an expanded tab or similar288/// multi-column entity.289int columnToByte(int n) const {290assert(0<=n && n<static_cast<int>(m_columnToByte.size()));291return m_columnToByte[n];292}293294/// Map from a byte index to the next byte which starts a column.295int startOfNextColumn(int N) const {296assert(0 <= N && N < static_cast<int>(m_byteToColumn.size() - 1));297while (byteToColumn(++N) == -1) {}298return N;299}300301/// Map from a byte index to the previous byte which starts a column.302int startOfPreviousColumn(int N) const {303assert(0 < N && N < static_cast<int>(m_byteToColumn.size()));304while (byteToColumn(--N) == -1) {}305return N;306}307308StringRef getSourceLine() const {309return m_SourceLine;310}311312private:313const std::string m_SourceLine;314SmallVector<int,200> m_byteToColumn;315SmallVector<int,200> m_columnToByte;316};317} // end anonymous namespace318319/// When the source code line we want to print is too long for320/// the terminal, select the "interesting" region.321static void selectInterestingSourceRegion(std::string &SourceLine,322std::string &CaretLine,323std::string &FixItInsertionLine,324unsigned Columns,325const SourceColumnMap &map) {326unsigned CaretColumns = CaretLine.size();327unsigned FixItColumns = llvm::sys::locale::columnWidth(FixItInsertionLine);328unsigned MaxColumns = std::max(static_cast<unsigned>(map.columns()),329std::max(CaretColumns, FixItColumns));330// if the number of columns is less than the desired number we're done331if (MaxColumns <= Columns)332return;333334// No special characters are allowed in CaretLine.335assert(llvm::none_of(CaretLine, [](char c) { return c < ' ' || '~' < c; }));336337// Find the slice that we need to display the full caret line338// correctly.339unsigned CaretStart = 0, CaretEnd = CaretLine.size();340for (; CaretStart != CaretEnd; ++CaretStart)341if (!isWhitespace(CaretLine[CaretStart]))342break;343344for (; CaretEnd != CaretStart; --CaretEnd)345if (!isWhitespace(CaretLine[CaretEnd - 1]))346break;347348// caret has already been inserted into CaretLine so the above whitespace349// check is guaranteed to include the caret350351// If we have a fix-it line, make sure the slice includes all of the352// fix-it information.353if (!FixItInsertionLine.empty()) {354unsigned FixItStart = 0, FixItEnd = FixItInsertionLine.size();355for (; FixItStart != FixItEnd; ++FixItStart)356if (!isWhitespace(FixItInsertionLine[FixItStart]))357break;358359for (; FixItEnd != FixItStart; --FixItEnd)360if (!isWhitespace(FixItInsertionLine[FixItEnd - 1]))361break;362363// We can safely use the byte offset FixItStart as the column offset364// because the characters up until FixItStart are all ASCII whitespace365// characters.366unsigned FixItStartCol = FixItStart;367unsigned FixItEndCol368= llvm::sys::locale::columnWidth(FixItInsertionLine.substr(0, FixItEnd));369370CaretStart = std::min(FixItStartCol, CaretStart);371CaretEnd = std::max(FixItEndCol, CaretEnd);372}373374// CaretEnd may have been set at the middle of a character375// If it's not at a character's first column then advance it past the current376// character.377while (static_cast<int>(CaretEnd) < map.columns() &&378-1 == map.columnToByte(CaretEnd))379++CaretEnd;380381assert((static_cast<int>(CaretStart) > map.columns() ||382-1!=map.columnToByte(CaretStart)) &&383"CaretStart must not point to a column in the middle of a source"384" line character");385assert((static_cast<int>(CaretEnd) > map.columns() ||386-1!=map.columnToByte(CaretEnd)) &&387"CaretEnd must not point to a column in the middle of a source line"388" character");389390// CaretLine[CaretStart, CaretEnd) contains all of the interesting391// parts of the caret line. While this slice is smaller than the392// number of columns we have, try to grow the slice to encompass393// more context.394395unsigned SourceStart = map.columnToByte(std::min<unsigned>(CaretStart,396map.columns()));397unsigned SourceEnd = map.columnToByte(std::min<unsigned>(CaretEnd,398map.columns()));399400unsigned CaretColumnsOutsideSource = CaretEnd-CaretStart401- (map.byteToColumn(SourceEnd)-map.byteToColumn(SourceStart));402403char const *front_ellipse = " ...";404char const *front_space = " ";405char const *back_ellipse = "...";406unsigned ellipses_space = strlen(front_ellipse) + strlen(back_ellipse);407408unsigned TargetColumns = Columns;409// Give us extra room for the ellipses410// and any of the caret line that extends past the source411if (TargetColumns > ellipses_space+CaretColumnsOutsideSource)412TargetColumns -= ellipses_space+CaretColumnsOutsideSource;413414while (SourceStart>0 || SourceEnd<SourceLine.size()) {415bool ExpandedRegion = false;416417if (SourceStart>0) {418unsigned NewStart = map.startOfPreviousColumn(SourceStart);419420// Skip over any whitespace we see here; we're looking for421// another bit of interesting text.422// FIXME: Detect non-ASCII whitespace characters too.423while (NewStart && isWhitespace(SourceLine[NewStart]))424NewStart = map.startOfPreviousColumn(NewStart);425426// Skip over this bit of "interesting" text.427while (NewStart) {428unsigned Prev = map.startOfPreviousColumn(NewStart);429if (isWhitespace(SourceLine[Prev]))430break;431NewStart = Prev;432}433434assert(map.byteToColumn(NewStart) != -1);435unsigned NewColumns = map.byteToColumn(SourceEnd) -436map.byteToColumn(NewStart);437if (NewColumns <= TargetColumns) {438SourceStart = NewStart;439ExpandedRegion = true;440}441}442443if (SourceEnd<SourceLine.size()) {444unsigned NewEnd = map.startOfNextColumn(SourceEnd);445446// Skip over any whitespace we see here; we're looking for447// another bit of interesting text.448// FIXME: Detect non-ASCII whitespace characters too.449while (NewEnd < SourceLine.size() && isWhitespace(SourceLine[NewEnd]))450NewEnd = map.startOfNextColumn(NewEnd);451452// Skip over this bit of "interesting" text.453while (NewEnd < SourceLine.size() && isWhitespace(SourceLine[NewEnd]))454NewEnd = map.startOfNextColumn(NewEnd);455456assert(map.byteToColumn(NewEnd) != -1);457unsigned NewColumns = map.byteToColumn(NewEnd) -458map.byteToColumn(SourceStart);459if (NewColumns <= TargetColumns) {460SourceEnd = NewEnd;461ExpandedRegion = true;462}463}464465if (!ExpandedRegion)466break;467}468469CaretStart = map.byteToColumn(SourceStart);470CaretEnd = map.byteToColumn(SourceEnd) + CaretColumnsOutsideSource;471472// [CaretStart, CaretEnd) is the slice we want. Update the various473// output lines to show only this slice.474assert(CaretStart!=(unsigned)-1 && CaretEnd!=(unsigned)-1 &&475SourceStart!=(unsigned)-1 && SourceEnd!=(unsigned)-1);476assert(SourceStart <= SourceEnd);477assert(CaretStart <= CaretEnd);478479unsigned BackColumnsRemoved480= map.byteToColumn(SourceLine.size())-map.byteToColumn(SourceEnd);481unsigned FrontColumnsRemoved = CaretStart;482unsigned ColumnsKept = CaretEnd-CaretStart;483484// We checked up front that the line needed truncation485assert(FrontColumnsRemoved+ColumnsKept+BackColumnsRemoved > Columns);486487// The line needs some truncation, and we'd prefer to keep the front488// if possible, so remove the back489if (BackColumnsRemoved > strlen(back_ellipse))490SourceLine.replace(SourceEnd, std::string::npos, back_ellipse);491492// If that's enough then we're done493if (FrontColumnsRemoved+ColumnsKept <= Columns)494return;495496// Otherwise remove the front as well497if (FrontColumnsRemoved > strlen(front_ellipse)) {498SourceLine.replace(0, SourceStart, front_ellipse);499CaretLine.replace(0, CaretStart, front_space);500if (!FixItInsertionLine.empty())501FixItInsertionLine.replace(0, CaretStart, front_space);502}503}504505/// Skip over whitespace in the string, starting at the given506/// index.507///508/// \returns The index of the first non-whitespace character that is509/// greater than or equal to Idx or, if no such character exists,510/// returns the end of the string.511static unsigned skipWhitespace(unsigned Idx, StringRef Str, unsigned Length) {512while (Idx < Length && isWhitespace(Str[Idx]))513++Idx;514return Idx;515}516517/// If the given character is the start of some kind of518/// balanced punctuation (e.g., quotes or parentheses), return the519/// character that will terminate the punctuation.520///521/// \returns The ending punctuation character, if any, or the NULL522/// character if the input character does not start any punctuation.523static inline char findMatchingPunctuation(char c) {524switch (c) {525case '\'': return '\'';526case '`': return '\'';527case '"': return '"';528case '(': return ')';529case '[': return ']';530case '{': return '}';531default: break;532}533534return 0;535}536537/// Find the end of the word starting at the given offset538/// within a string.539///540/// \returns the index pointing one character past the end of the541/// word.542static unsigned findEndOfWord(unsigned Start, StringRef Str,543unsigned Length, unsigned Column,544unsigned Columns) {545assert(Start < Str.size() && "Invalid start position!");546unsigned End = Start + 1;547548// If we are already at the end of the string, take that as the word.549if (End == Str.size())550return End;551552// Determine if the start of the string is actually opening553// punctuation, e.g., a quote or parentheses.554char EndPunct = findMatchingPunctuation(Str[Start]);555if (!EndPunct) {556// This is a normal word. Just find the first space character.557while (End < Length && !isWhitespace(Str[End]))558++End;559return End;560}561562// We have the start of a balanced punctuation sequence (quotes,563// parentheses, etc.). Determine the full sequence is.564SmallString<16> PunctuationEndStack;565PunctuationEndStack.push_back(EndPunct);566while (End < Length && !PunctuationEndStack.empty()) {567if (Str[End] == PunctuationEndStack.back())568PunctuationEndStack.pop_back();569else if (char SubEndPunct = findMatchingPunctuation(Str[End]))570PunctuationEndStack.push_back(SubEndPunct);571572++End;573}574575// Find the first space character after the punctuation ended.576while (End < Length && !isWhitespace(Str[End]))577++End;578579unsigned PunctWordLength = End - Start;580if (// If the word fits on this line581Column + PunctWordLength <= Columns ||582// ... or the word is "short enough" to take up the next line583// without too much ugly white space584PunctWordLength < Columns/3)585return End; // Take the whole thing as a single "word".586587// The whole quoted/parenthesized string is too long to print as a588// single "word". Instead, find the "word" that starts just after589// the punctuation and use that end-point instead. This will recurse590// until it finds something small enough to consider a word.591return findEndOfWord(Start + 1, Str, Length, Column + 1, Columns);592}593594/// Print the given string to a stream, word-wrapping it to595/// some number of columns in the process.596///597/// \param OS the stream to which the word-wrapping string will be598/// emitted.599/// \param Str the string to word-wrap and output.600/// \param Columns the number of columns to word-wrap to.601/// \param Column the column number at which the first character of \p602/// Str will be printed. This will be non-zero when part of the first603/// line has already been printed.604/// \param Bold if the current text should be bold605/// \returns true if word-wrapping was required, or false if the606/// string fit on the first line.607static bool printWordWrapped(raw_ostream &OS, StringRef Str, unsigned Columns,608unsigned Column, bool Bold) {609const unsigned Length = std::min(Str.find('\n'), Str.size());610bool TextNormal = true;611612bool Wrapped = false;613for (unsigned WordStart = 0, WordEnd; WordStart < Length;614WordStart = WordEnd) {615// Find the beginning of the next word.616WordStart = skipWhitespace(WordStart, Str, Length);617if (WordStart == Length)618break;619620// Find the end of this word.621WordEnd = findEndOfWord(WordStart, Str, Length, Column, Columns);622623// Does this word fit on the current line?624unsigned WordLength = WordEnd - WordStart;625if (Column + WordLength < Columns) {626// This word fits on the current line; print it there.627if (WordStart) {628OS << ' ';629Column += 1;630}631applyTemplateHighlighting(OS, Str.substr(WordStart, WordLength),632TextNormal, Bold);633Column += WordLength;634continue;635}636637// This word does not fit on the current line, so wrap to the next638// line.639OS << '\n';640OS.indent(WordWrapIndentation);641applyTemplateHighlighting(OS, Str.substr(WordStart, WordLength),642TextNormal, Bold);643Column = WordWrapIndentation + WordLength;644Wrapped = true;645}646647// Append any remaning text from the message with its existing formatting.648applyTemplateHighlighting(OS, Str.substr(Length), TextNormal, Bold);649650assert(TextNormal && "Text highlighted at end of diagnostic message.");651652return Wrapped;653}654655TextDiagnostic::TextDiagnostic(raw_ostream &OS, const LangOptions &LangOpts,656DiagnosticOptions *DiagOpts,657const Preprocessor *PP)658: DiagnosticRenderer(LangOpts, DiagOpts), OS(OS), PP(PP) {}659660TextDiagnostic::~TextDiagnostic() {}661662void TextDiagnostic::emitDiagnosticMessage(663FullSourceLoc Loc, PresumedLoc PLoc, DiagnosticsEngine::Level Level,664StringRef Message, ArrayRef<clang::CharSourceRange> Ranges,665DiagOrStoredDiag D) {666uint64_t StartOfLocationInfo = OS.tell();667668// Emit the location of this particular diagnostic.669if (Loc.isValid())670emitDiagnosticLoc(Loc, PLoc, Level, Ranges);671672if (DiagOpts->ShowColors)673OS.resetColor();674675if (DiagOpts->ShowLevel)676printDiagnosticLevel(OS, Level, DiagOpts->ShowColors);677printDiagnosticMessage(OS,678/*IsSupplemental*/ Level == DiagnosticsEngine::Note,679Message, OS.tell() - StartOfLocationInfo,680DiagOpts->MessageLength, DiagOpts->ShowColors);681}682683/*static*/ void684TextDiagnostic::printDiagnosticLevel(raw_ostream &OS,685DiagnosticsEngine::Level Level,686bool ShowColors) {687if (ShowColors) {688// Print diagnostic category in bold and color689switch (Level) {690case DiagnosticsEngine::Ignored:691llvm_unreachable("Invalid diagnostic type");692case DiagnosticsEngine::Note: OS.changeColor(noteColor, true); break;693case DiagnosticsEngine::Remark: OS.changeColor(remarkColor, true); break;694case DiagnosticsEngine::Warning: OS.changeColor(warningColor, true); break;695case DiagnosticsEngine::Error: OS.changeColor(errorColor, true); break;696case DiagnosticsEngine::Fatal: OS.changeColor(fatalColor, true); break;697}698}699700switch (Level) {701case DiagnosticsEngine::Ignored:702llvm_unreachable("Invalid diagnostic type");703case DiagnosticsEngine::Note: OS << "note: "; break;704case DiagnosticsEngine::Remark: OS << "remark: "; break;705case DiagnosticsEngine::Warning: OS << "warning: "; break;706case DiagnosticsEngine::Error: OS << "error: "; break;707case DiagnosticsEngine::Fatal: OS << "fatal error: "; break;708}709710if (ShowColors)711OS.resetColor();712}713714/*static*/715void TextDiagnostic::printDiagnosticMessage(raw_ostream &OS,716bool IsSupplemental,717StringRef Message,718unsigned CurrentColumn,719unsigned Columns, bool ShowColors) {720bool Bold = false;721if (ShowColors && !IsSupplemental) {722// Print primary diagnostic messages in bold and without color, to visually723// indicate the transition from continuation notes and other output.724OS.changeColor(savedColor, true);725Bold = true;726}727728if (Columns)729printWordWrapped(OS, Message, Columns, CurrentColumn, Bold);730else {731bool Normal = true;732applyTemplateHighlighting(OS, Message, Normal, Bold);733assert(Normal && "Formatting should have returned to normal");734}735736if (ShowColors)737OS.resetColor();738OS << '\n';739}740741void TextDiagnostic::emitFilename(StringRef Filename, const SourceManager &SM) {742#ifdef _WIN32743SmallString<4096> TmpFilename;744#endif745if (DiagOpts->AbsolutePath) {746auto File = SM.getFileManager().getOptionalFileRef(Filename);747if (File) {748// We want to print a simplified absolute path, i. e. without "dots".749//750// The hardest part here are the paths like "<part1>/<link>/../<part2>".751// On Unix-like systems, we cannot just collapse "<link>/..", because752// paths are resolved sequentially, and, thereby, the path753// "<part1>/<part2>" may point to a different location. That is why754// we use FileManager::getCanonicalName(), which expands all indirections755// with llvm::sys::fs::real_path() and caches the result.756//757// On the other hand, it would be better to preserve as much of the758// original path as possible, because that helps a user to recognize it.759// real_path() expands all links, which sometimes too much. Luckily,760// on Windows we can just use llvm::sys::path::remove_dots(), because,761// on that system, both aforementioned paths point to the same place.762#ifdef _WIN32763TmpFilename = File->getName();764llvm::sys::fs::make_absolute(TmpFilename);765llvm::sys::path::native(TmpFilename);766llvm::sys::path::remove_dots(TmpFilename, /* remove_dot_dot */ true);767Filename = StringRef(TmpFilename.data(), TmpFilename.size());768#else769Filename = SM.getFileManager().getCanonicalName(*File);770#endif771}772}773774OS << Filename;775}776777/// Print out the file/line/column information and include trace.778///779/// This method handles the emission of the diagnostic location information.780/// This includes extracting as much location information as is present for781/// the diagnostic and printing it, as well as any include stack or source782/// ranges necessary.783void TextDiagnostic::emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,784DiagnosticsEngine::Level Level,785ArrayRef<CharSourceRange> Ranges) {786if (PLoc.isInvalid()) {787// At least print the file name if available:788if (FileID FID = Loc.getFileID(); FID.isValid()) {789if (OptionalFileEntryRef FE = Loc.getFileEntryRef()) {790emitFilename(FE->getName(), Loc.getManager());791OS << ": ";792}793}794return;795}796unsigned LineNo = PLoc.getLine();797798if (!DiagOpts->ShowLocation)799return;800801if (DiagOpts->ShowColors)802OS.changeColor(savedColor, true);803804emitFilename(PLoc.getFilename(), Loc.getManager());805switch (DiagOpts->getFormat()) {806case DiagnosticOptions::SARIF:807case DiagnosticOptions::Clang:808if (DiagOpts->ShowLine)809OS << ':' << LineNo;810break;811case DiagnosticOptions::MSVC: OS << '(' << LineNo; break;812case DiagnosticOptions::Vi: OS << " +" << LineNo; break;813}814815if (DiagOpts->ShowColumn)816// Compute the column number.817if (unsigned ColNo = PLoc.getColumn()) {818if (DiagOpts->getFormat() == DiagnosticOptions::MSVC) {819OS << ',';820// Visual Studio 2010 or earlier expects column number to be off by one821if (LangOpts.MSCompatibilityVersion &&822!LangOpts.isCompatibleWithMSVC(LangOptions::MSVC2012))823ColNo--;824} else825OS << ':';826OS << ColNo;827}828switch (DiagOpts->getFormat()) {829case DiagnosticOptions::SARIF:830case DiagnosticOptions::Clang:831case DiagnosticOptions::Vi: OS << ':'; break;832case DiagnosticOptions::MSVC:833// MSVC2013 and before print 'file(4) : error'. MSVC2015 gets rid of the834// space and prints 'file(4): error'.835OS << ')';836if (LangOpts.MSCompatibilityVersion &&837!LangOpts.isCompatibleWithMSVC(LangOptions::MSVC2015))838OS << ' ';839OS << ':';840break;841}842843if (DiagOpts->ShowSourceRanges && !Ranges.empty()) {844FileID CaretFileID = Loc.getExpansionLoc().getFileID();845bool PrintedRange = false;846const SourceManager &SM = Loc.getManager();847848for (const auto &R : Ranges) {849// Ignore invalid ranges.850if (!R.isValid())851continue;852853SourceLocation B = SM.getExpansionLoc(R.getBegin());854CharSourceRange ERange = SM.getExpansionRange(R.getEnd());855SourceLocation E = ERange.getEnd();856857// If the start or end of the range is in another file, just858// discard it.859if (SM.getFileID(B) != CaretFileID || SM.getFileID(E) != CaretFileID)860continue;861862// Add in the length of the token, so that we cover multi-char863// tokens.864unsigned TokSize = 0;865if (ERange.isTokenRange())866TokSize = Lexer::MeasureTokenLength(E, SM, LangOpts);867868FullSourceLoc BF(B, SM), EF(E, SM);869OS << '{'870<< BF.getLineNumber() << ':' << BF.getColumnNumber() << '-'871<< EF.getLineNumber() << ':' << (EF.getColumnNumber() + TokSize)872<< '}';873PrintedRange = true;874}875876if (PrintedRange)877OS << ':';878}879OS << ' ';880}881882void TextDiagnostic::emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) {883if (DiagOpts->ShowLocation && PLoc.isValid()) {884OS << "In file included from ";885emitFilename(PLoc.getFilename(), Loc.getManager());886OS << ':' << PLoc.getLine() << ":\n";887} else888OS << "In included file:\n";889}890891void TextDiagnostic::emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,892StringRef ModuleName) {893if (DiagOpts->ShowLocation && PLoc.isValid())894OS << "In module '" << ModuleName << "' imported from "895<< PLoc.getFilename() << ':' << PLoc.getLine() << ":\n";896else897OS << "In module '" << ModuleName << "':\n";898}899900void TextDiagnostic::emitBuildingModuleLocation(FullSourceLoc Loc,901PresumedLoc PLoc,902StringRef ModuleName) {903if (DiagOpts->ShowLocation && PLoc.isValid())904OS << "While building module '" << ModuleName << "' imported from "905<< PLoc.getFilename() << ':' << PLoc.getLine() << ":\n";906else907OS << "While building module '" << ModuleName << "':\n";908}909910/// Find the suitable set of lines to show to include a set of ranges.911static std::optional<std::pair<unsigned, unsigned>>912findLinesForRange(const CharSourceRange &R, FileID FID,913const SourceManager &SM) {914if (!R.isValid())915return std::nullopt;916917SourceLocation Begin = R.getBegin();918SourceLocation End = R.getEnd();919if (SM.getFileID(Begin) != FID || SM.getFileID(End) != FID)920return std::nullopt;921922return std::make_pair(SM.getExpansionLineNumber(Begin),923SM.getExpansionLineNumber(End));924}925926/// Add as much of range B into range A as possible without exceeding a maximum927/// size of MaxRange. Ranges are inclusive.928static std::pair<unsigned, unsigned>929maybeAddRange(std::pair<unsigned, unsigned> A, std::pair<unsigned, unsigned> B,930unsigned MaxRange) {931// If A is already the maximum size, we're done.932unsigned Slack = MaxRange - (A.second - A.first + 1);933if (Slack == 0)934return A;935936// Easy case: merge succeeds within MaxRange.937unsigned Min = std::min(A.first, B.first);938unsigned Max = std::max(A.second, B.second);939if (Max - Min + 1 <= MaxRange)940return {Min, Max};941942// If we can't reach B from A within MaxRange, there's nothing to do.943// Don't add lines to the range that contain nothing interesting.944if ((B.first > A.first && B.first - A.first + 1 > MaxRange) ||945(B.second < A.second && A.second - B.second + 1 > MaxRange))946return A;947948// Otherwise, expand A towards B to produce a range of size MaxRange. We949// attempt to expand by the same amount in both directions if B strictly950// contains A.951952// Expand downwards by up to half the available amount, then upwards as953// much as possible, then downwards as much as possible.954A.second = std::min(A.second + (Slack + 1) / 2, Max);955Slack = MaxRange - (A.second - A.first + 1);956A.first = std::max(Min + Slack, A.first) - Slack;957A.second = std::min(A.first + MaxRange - 1, Max);958return A;959}960961struct LineRange {962unsigned LineNo;963unsigned StartCol;964unsigned EndCol;965};966967/// Highlight \p R (with ~'s) on the current source line.968static void highlightRange(const LineRange &R, const SourceColumnMap &Map,969std::string &CaretLine) {970// Pick the first non-whitespace column.971unsigned StartColNo = R.StartCol;972while (StartColNo < Map.getSourceLine().size() &&973(Map.getSourceLine()[StartColNo] == ' ' ||974Map.getSourceLine()[StartColNo] == '\t'))975StartColNo = Map.startOfNextColumn(StartColNo);976977// Pick the last non-whitespace column.978unsigned EndColNo =979std::min(static_cast<size_t>(R.EndCol), Map.getSourceLine().size());980while (EndColNo && (Map.getSourceLine()[EndColNo - 1] == ' ' ||981Map.getSourceLine()[EndColNo - 1] == '\t'))982EndColNo = Map.startOfPreviousColumn(EndColNo);983984// If the start/end passed each other, then we are trying to highlight a985// range that just exists in whitespace. That most likely means we have986// a multi-line highlighting range that covers a blank line.987if (StartColNo > EndColNo)988return;989990// Fill the range with ~'s.991StartColNo = Map.byteToContainingColumn(StartColNo);992EndColNo = Map.byteToContainingColumn(EndColNo);993994assert(StartColNo <= EndColNo && "Invalid range!");995if (CaretLine.size() < EndColNo)996CaretLine.resize(EndColNo, ' ');997std::fill(CaretLine.begin() + StartColNo, CaretLine.begin() + EndColNo, '~');998}9991000static std::string buildFixItInsertionLine(FileID FID,1001unsigned LineNo,1002const SourceColumnMap &map,1003ArrayRef<FixItHint> Hints,1004const SourceManager &SM,1005const DiagnosticOptions *DiagOpts) {1006std::string FixItInsertionLine;1007if (Hints.empty() || !DiagOpts->ShowFixits)1008return FixItInsertionLine;1009unsigned PrevHintEndCol = 0;10101011for (const auto &H : Hints) {1012if (H.CodeToInsert.empty())1013continue;10141015// We have an insertion hint. Determine whether the inserted1016// code contains no newlines and is on the same line as the caret.1017std::pair<FileID, unsigned> HintLocInfo =1018SM.getDecomposedExpansionLoc(H.RemoveRange.getBegin());1019if (FID == HintLocInfo.first &&1020LineNo == SM.getLineNumber(HintLocInfo.first, HintLocInfo.second) &&1021StringRef(H.CodeToInsert).find_first_of("\n\r") == StringRef::npos) {1022// Insert the new code into the line just below the code1023// that the user wrote.1024// Note: When modifying this function, be very careful about what is a1025// "column" (printed width, platform-dependent) and what is a1026// "byte offset" (SourceManager "column").1027unsigned HintByteOffset =1028SM.getColumnNumber(HintLocInfo.first, HintLocInfo.second) - 1;10291030// The hint must start inside the source or right at the end1031assert(HintByteOffset < static_cast<unsigned>(map.bytes()) + 1);1032unsigned HintCol = map.byteToContainingColumn(HintByteOffset);10331034// If we inserted a long previous hint, push this one forwards, and add1035// an extra space to show that this is not part of the previous1036// completion. This is sort of the best we can do when two hints appear1037// to overlap.1038//1039// Note that if this hint is located immediately after the previous1040// hint, no space will be added, since the location is more important.1041if (HintCol < PrevHintEndCol)1042HintCol = PrevHintEndCol + 1;10431044// This should NOT use HintByteOffset, because the source might have1045// Unicode characters in earlier columns.1046unsigned NewFixItLineSize = FixItInsertionLine.size() +1047(HintCol - PrevHintEndCol) +1048H.CodeToInsert.size();1049if (NewFixItLineSize > FixItInsertionLine.size())1050FixItInsertionLine.resize(NewFixItLineSize, ' ');10511052std::copy(H.CodeToInsert.begin(), H.CodeToInsert.end(),1053FixItInsertionLine.end() - H.CodeToInsert.size());10541055PrevHintEndCol = HintCol + llvm::sys::locale::columnWidth(H.CodeToInsert);1056}1057}10581059expandTabs(FixItInsertionLine, DiagOpts->TabStop);10601061return FixItInsertionLine;1062}10631064static unsigned getNumDisplayWidth(unsigned N) {1065unsigned L = 1u, M = 10u;1066while (M <= N && ++L != std::numeric_limits<unsigned>::digits10 + 1)1067M *= 10u;10681069return L;1070}10711072/// Filter out invalid ranges, ranges that don't fit into the window of1073/// source lines we will print, and ranges from other files.1074///1075/// For the remaining ranges, convert them to simple LineRange structs,1076/// which only cover one line at a time.1077static SmallVector<LineRange>1078prepareAndFilterRanges(const SmallVectorImpl<CharSourceRange> &Ranges,1079const SourceManager &SM,1080const std::pair<unsigned, unsigned> &Lines, FileID FID,1081const LangOptions &LangOpts) {1082SmallVector<LineRange> LineRanges;10831084for (const CharSourceRange &R : Ranges) {1085if (R.isInvalid())1086continue;1087SourceLocation Begin = R.getBegin();1088SourceLocation End = R.getEnd();10891090unsigned StartLineNo = SM.getExpansionLineNumber(Begin);1091if (StartLineNo > Lines.second || SM.getFileID(Begin) != FID)1092continue;10931094unsigned EndLineNo = SM.getExpansionLineNumber(End);1095if (EndLineNo < Lines.first || SM.getFileID(End) != FID)1096continue;10971098unsigned StartColumn = SM.getExpansionColumnNumber(Begin);1099unsigned EndColumn = SM.getExpansionColumnNumber(End);1100if (R.isTokenRange())1101EndColumn += Lexer::MeasureTokenLength(End, SM, LangOpts);11021103// Only a single line.1104if (StartLineNo == EndLineNo) {1105LineRanges.push_back({StartLineNo, StartColumn - 1, EndColumn - 1});1106continue;1107}11081109// Start line.1110LineRanges.push_back({StartLineNo, StartColumn - 1, ~0u});11111112// Middle lines.1113for (unsigned S = StartLineNo + 1; S != EndLineNo; ++S)1114LineRanges.push_back({S, 0, ~0u});11151116// End line.1117LineRanges.push_back({EndLineNo, 0, EndColumn - 1});1118}11191120return LineRanges;1121}11221123/// Creates syntax highlighting information in form of StyleRanges.1124///1125/// The returned unique ptr has always exactly size1126/// (\p EndLineNumber - \p StartLineNumber + 1). Each SmallVector in there1127/// corresponds to syntax highlighting information in one line. In each line,1128/// the StyleRanges are non-overlapping and sorted from start to end of the1129/// line.1130static std::unique_ptr<llvm::SmallVector<TextDiagnostic::StyleRange>[]>1131highlightLines(StringRef FileData, unsigned StartLineNumber,1132unsigned EndLineNumber, const Preprocessor *PP,1133const LangOptions &LangOpts, bool ShowColors, FileID FID,1134const SourceManager &SM) {1135assert(StartLineNumber <= EndLineNumber);1136auto SnippetRanges =1137std::make_unique<SmallVector<TextDiagnostic::StyleRange>[]>(1138EndLineNumber - StartLineNumber + 1);11391140if (!PP || !ShowColors)1141return SnippetRanges;11421143// Might cause emission of another diagnostic.1144if (PP->getIdentifierTable().getExternalIdentifierLookup())1145return SnippetRanges;11461147auto Buff = llvm::MemoryBuffer::getMemBuffer(FileData);1148Lexer L{FID, *Buff, SM, LangOpts};1149L.SetKeepWhitespaceMode(true);11501151const char *FirstLineStart =1152FileData.data() +1153SM.getDecomposedLoc(SM.translateLineCol(FID, StartLineNumber, 1)).second;1154if (const char *CheckPoint = PP->getCheckPoint(FID, FirstLineStart)) {1155assert(CheckPoint >= Buff->getBufferStart() &&1156CheckPoint <= Buff->getBufferEnd());1157assert(CheckPoint <= FirstLineStart);1158size_t Offset = CheckPoint - Buff->getBufferStart();1159L.seek(Offset, /*IsAtStartOfLine=*/false);1160}11611162// Classify the given token and append it to the given vector.1163auto appendStyle =1164[PP, &LangOpts](SmallVector<TextDiagnostic::StyleRange> &Vec,1165const Token &T, unsigned Start, unsigned Length) -> void {1166if (T.is(tok::raw_identifier)) {1167StringRef RawIdent = T.getRawIdentifier();1168// Special case true/false/nullptr/... literals, since they will otherwise1169// be treated as keywords.1170// FIXME: It would be good to have a programmatic way of getting this1171// list.1172if (llvm::StringSwitch<bool>(RawIdent)1173.Case("true", true)1174.Case("false", true)1175.Case("nullptr", true)1176.Case("__func__", true)1177.Case("__objc_yes__", true)1178.Case("__objc_no__", true)1179.Case("__null", true)1180.Case("__FUNCDNAME__", true)1181.Case("__FUNCSIG__", true)1182.Case("__FUNCTION__", true)1183.Case("__FUNCSIG__", true)1184.Default(false)) {1185Vec.emplace_back(Start, Start + Length, LiteralColor);1186} else {1187const IdentifierInfo *II = PP->getIdentifierInfo(RawIdent);1188assert(II);1189if (II->isKeyword(LangOpts))1190Vec.emplace_back(Start, Start + Length, KeywordColor);1191}1192} else if (tok::isLiteral(T.getKind())) {1193Vec.emplace_back(Start, Start + Length, LiteralColor);1194} else {1195assert(T.is(tok::comment));1196Vec.emplace_back(Start, Start + Length, CommentColor);1197}1198};11991200bool Stop = false;1201while (!Stop) {1202Token T;1203Stop = L.LexFromRawLexer(T);1204if (T.is(tok::unknown))1205continue;12061207// We are only interested in identifiers, literals and comments.1208if (!T.is(tok::raw_identifier) && !T.is(tok::comment) &&1209!tok::isLiteral(T.getKind()))1210continue;12111212bool Invalid = false;1213unsigned TokenEndLine = SM.getSpellingLineNumber(T.getEndLoc(), &Invalid);1214if (Invalid || TokenEndLine < StartLineNumber)1215continue;12161217assert(TokenEndLine >= StartLineNumber);12181219unsigned TokenStartLine =1220SM.getSpellingLineNumber(T.getLocation(), &Invalid);1221if (Invalid)1222continue;1223// If this happens, we're done.1224if (TokenStartLine > EndLineNumber)1225break;12261227unsigned StartCol =1228SM.getSpellingColumnNumber(T.getLocation(), &Invalid) - 1;1229if (Invalid)1230continue;12311232// Simple tokens.1233if (TokenStartLine == TokenEndLine) {1234SmallVector<TextDiagnostic::StyleRange> &LineRanges =1235SnippetRanges[TokenStartLine - StartLineNumber];1236appendStyle(LineRanges, T, StartCol, T.getLength());1237continue;1238}1239assert((TokenEndLine - TokenStartLine) >= 1);12401241// For tokens that span multiple lines (think multiline comments), we1242// divide them into multiple StyleRanges.1243unsigned EndCol = SM.getSpellingColumnNumber(T.getEndLoc(), &Invalid) - 1;1244if (Invalid)1245continue;12461247std::string Spelling = Lexer::getSpelling(T, SM, LangOpts);12481249unsigned L = TokenStartLine;1250unsigned LineLength = 0;1251for (unsigned I = 0; I <= Spelling.size(); ++I) {1252// This line is done.1253if (I == Spelling.size() || isVerticalWhitespace(Spelling[I])) {1254SmallVector<TextDiagnostic::StyleRange> &LineRanges =1255SnippetRanges[L - StartLineNumber];12561257if (L >= StartLineNumber) {1258if (L == TokenStartLine) // First line1259appendStyle(LineRanges, T, StartCol, LineLength);1260else if (L == TokenEndLine) // Last line1261appendStyle(LineRanges, T, 0, EndCol);1262else1263appendStyle(LineRanges, T, 0, LineLength);1264}12651266++L;1267if (L > EndLineNumber)1268break;1269LineLength = 0;1270continue;1271}1272++LineLength;1273}1274}12751276return SnippetRanges;1277}12781279/// Emit a code snippet and caret line.1280///1281/// This routine emits a single line's code snippet and caret line..1282///1283/// \param Loc The location for the caret.1284/// \param Ranges The underlined ranges for this code snippet.1285/// \param Hints The FixIt hints active for this diagnostic.1286void TextDiagnostic::emitSnippetAndCaret(1287FullSourceLoc Loc, DiagnosticsEngine::Level Level,1288SmallVectorImpl<CharSourceRange> &Ranges, ArrayRef<FixItHint> Hints) {1289assert(Loc.isValid() && "must have a valid source location here");1290assert(Loc.isFileID() && "must have a file location here");12911292// If caret diagnostics are enabled and we have location, we want to1293// emit the caret. However, we only do this if the location moved1294// from the last diagnostic, if the last diagnostic was a note that1295// was part of a different warning or error diagnostic, or if the1296// diagnostic has ranges. We don't want to emit the same caret1297// multiple times if one loc has multiple diagnostics.1298if (!DiagOpts->ShowCarets)1299return;1300if (Loc == LastLoc && Ranges.empty() && Hints.empty() &&1301(LastLevel != DiagnosticsEngine::Note || Level == LastLevel))1302return;13031304FileID FID = Loc.getFileID();1305const SourceManager &SM = Loc.getManager();13061307// Get information about the buffer it points into.1308bool Invalid = false;1309StringRef BufData = Loc.getBufferData(&Invalid);1310if (Invalid)1311return;1312const char *BufStart = BufData.data();1313const char *BufEnd = BufStart + BufData.size();13141315unsigned CaretLineNo = Loc.getLineNumber();1316unsigned CaretColNo = Loc.getColumnNumber();13171318// Arbitrarily stop showing snippets when the line is too long.1319static const size_t MaxLineLengthToPrint = 4096;1320if (CaretColNo > MaxLineLengthToPrint)1321return;13221323// Find the set of lines to include.1324const unsigned MaxLines = DiagOpts->SnippetLineLimit;1325std::pair<unsigned, unsigned> Lines = {CaretLineNo, CaretLineNo};1326unsigned DisplayLineNo = Loc.getPresumedLoc().getLine();1327for (const auto &I : Ranges) {1328if (auto OptionalRange = findLinesForRange(I, FID, SM))1329Lines = maybeAddRange(Lines, *OptionalRange, MaxLines);13301331DisplayLineNo =1332std::min(DisplayLineNo, SM.getPresumedLineNumber(I.getBegin()));1333}13341335// Our line numbers look like:1336// " [number] | "1337// Where [number] is MaxLineNoDisplayWidth columns1338// and the full thing is therefore MaxLineNoDisplayWidth + 4 columns.1339unsigned MaxLineNoDisplayWidth =1340DiagOpts->ShowLineNumbers1341? std::max(4u, getNumDisplayWidth(DisplayLineNo + MaxLines))1342: 0;1343auto indentForLineNumbers = [&] {1344if (MaxLineNoDisplayWidth > 0)1345OS.indent(MaxLineNoDisplayWidth + 2) << "| ";1346};13471348// Prepare source highlighting information for the lines we're about to1349// emit, starting from the first line.1350std::unique_ptr<SmallVector<StyleRange>[]> SourceStyles =1351highlightLines(BufData, Lines.first, Lines.second, PP, LangOpts,1352DiagOpts->ShowColors, FID, SM);13531354SmallVector<LineRange> LineRanges =1355prepareAndFilterRanges(Ranges, SM, Lines, FID, LangOpts);13561357for (unsigned LineNo = Lines.first; LineNo != Lines.second + 1;1358++LineNo, ++DisplayLineNo) {1359// Rewind from the current position to the start of the line.1360const char *LineStart =1361BufStart +1362SM.getDecomposedLoc(SM.translateLineCol(FID, LineNo, 1)).second;1363if (LineStart == BufEnd)1364break;13651366// Compute the line end.1367const char *LineEnd = LineStart;1368while (*LineEnd != '\n' && *LineEnd != '\r' && LineEnd != BufEnd)1369++LineEnd;13701371// Arbitrarily stop showing snippets when the line is too long.1372// FIXME: Don't print any lines in this case.1373if (size_t(LineEnd - LineStart) > MaxLineLengthToPrint)1374return;13751376// Copy the line of code into an std::string for ease of manipulation.1377std::string SourceLine(LineStart, LineEnd);1378// Remove trailing null bytes.1379while (!SourceLine.empty() && SourceLine.back() == '\0' &&1380(LineNo != CaretLineNo || SourceLine.size() > CaretColNo))1381SourceLine.pop_back();13821383// Build the byte to column map.1384const SourceColumnMap sourceColMap(SourceLine, DiagOpts->TabStop);13851386std::string CaretLine;1387// Highlight all of the characters covered by Ranges with ~ characters.1388for (const auto &LR : LineRanges) {1389if (LR.LineNo == LineNo)1390highlightRange(LR, sourceColMap, CaretLine);1391}13921393// Next, insert the caret itself.1394if (CaretLineNo == LineNo) {1395size_t Col = sourceColMap.byteToContainingColumn(CaretColNo - 1);1396CaretLine.resize(std::max(Col + 1, CaretLine.size()), ' ');1397CaretLine[Col] = '^';1398}13991400std::string FixItInsertionLine = buildFixItInsertionLine(1401FID, LineNo, sourceColMap, Hints, SM, DiagOpts.get());14021403// If the source line is too long for our terminal, select only the1404// "interesting" source region within that line.1405unsigned Columns = DiagOpts->MessageLength;1406if (Columns)1407selectInterestingSourceRegion(SourceLine, CaretLine, FixItInsertionLine,1408Columns, sourceColMap);14091410// If we are in -fdiagnostics-print-source-range-info mode, we are trying1411// to produce easily machine parsable output. Add a space before the1412// source line and the caret to make it trivial to tell the main diagnostic1413// line from what the user is intended to see.1414if (DiagOpts->ShowSourceRanges && !SourceLine.empty()) {1415SourceLine = ' ' + SourceLine;1416CaretLine = ' ' + CaretLine;1417}14181419// Emit what we have computed.1420emitSnippet(SourceLine, MaxLineNoDisplayWidth, LineNo, DisplayLineNo,1421SourceStyles[LineNo - Lines.first]);14221423if (!CaretLine.empty()) {1424indentForLineNumbers();1425if (DiagOpts->ShowColors)1426OS.changeColor(caretColor, true);1427OS << CaretLine << '\n';1428if (DiagOpts->ShowColors)1429OS.resetColor();1430}14311432if (!FixItInsertionLine.empty()) {1433indentForLineNumbers();1434if (DiagOpts->ShowColors)1435// Print fixit line in color1436OS.changeColor(fixitColor, false);1437if (DiagOpts->ShowSourceRanges)1438OS << ' ';1439OS << FixItInsertionLine << '\n';1440if (DiagOpts->ShowColors)1441OS.resetColor();1442}1443}14441445// Print out any parseable fixit information requested by the options.1446emitParseableFixits(Hints, SM);1447}14481449void TextDiagnostic::emitSnippet(StringRef SourceLine,1450unsigned MaxLineNoDisplayWidth,1451unsigned LineNo, unsigned DisplayLineNo,1452ArrayRef<StyleRange> Styles) {1453// Emit line number.1454if (MaxLineNoDisplayWidth > 0) {1455unsigned LineNoDisplayWidth = getNumDisplayWidth(DisplayLineNo);1456OS.indent(MaxLineNoDisplayWidth - LineNoDisplayWidth + 1)1457<< DisplayLineNo << " | ";1458}14591460// Print the source line one character at a time.1461bool PrintReversed = false;1462std::optional<llvm::raw_ostream::Colors> CurrentColor;1463size_t I = 0;1464while (I < SourceLine.size()) {1465auto [Str, WasPrintable] =1466printableTextForNextCharacter(SourceLine, &I, DiagOpts->TabStop);14671468// Toggle inverted colors on or off for this character.1469if (DiagOpts->ShowColors) {1470if (WasPrintable == PrintReversed) {1471PrintReversed = !PrintReversed;1472if (PrintReversed)1473OS.reverseColor();1474else {1475OS.resetColor();1476CurrentColor = std::nullopt;1477}1478}14791480// Apply syntax highlighting information.1481const auto *CharStyle = llvm::find_if(Styles, [I](const StyleRange &R) {1482return (R.Start < I && R.End >= I);1483});14841485if (CharStyle != Styles.end()) {1486if (!CurrentColor ||1487(CurrentColor && *CurrentColor != CharStyle->Color)) {1488OS.changeColor(CharStyle->Color, false);1489CurrentColor = CharStyle->Color;1490}1491} else if (CurrentColor) {1492OS.resetColor();1493CurrentColor = std::nullopt;1494}1495}14961497OS << Str;1498}14991500if (DiagOpts->ShowColors)1501OS.resetColor();15021503OS << '\n';1504}15051506void TextDiagnostic::emitParseableFixits(ArrayRef<FixItHint> Hints,1507const SourceManager &SM) {1508if (!DiagOpts->ShowParseableFixits)1509return;15101511// We follow FixItRewriter's example in not (yet) handling1512// fix-its in macros.1513for (const auto &H : Hints) {1514if (H.RemoveRange.isInvalid() || H.RemoveRange.getBegin().isMacroID() ||1515H.RemoveRange.getEnd().isMacroID())1516return;1517}15181519for (const auto &H : Hints) {1520SourceLocation BLoc = H.RemoveRange.getBegin();1521SourceLocation ELoc = H.RemoveRange.getEnd();15221523std::pair<FileID, unsigned> BInfo = SM.getDecomposedLoc(BLoc);1524std::pair<FileID, unsigned> EInfo = SM.getDecomposedLoc(ELoc);15251526// Adjust for token ranges.1527if (H.RemoveRange.isTokenRange())1528EInfo.second += Lexer::MeasureTokenLength(ELoc, SM, LangOpts);15291530// We specifically do not do word-wrapping or tab-expansion here,1531// because this is supposed to be easy to parse.1532PresumedLoc PLoc = SM.getPresumedLoc(BLoc);1533if (PLoc.isInvalid())1534break;15351536OS << "fix-it:\"";1537OS.write_escaped(PLoc.getFilename());1538OS << "\":{" << SM.getLineNumber(BInfo.first, BInfo.second)1539<< ':' << SM.getColumnNumber(BInfo.first, BInfo.second)1540<< '-' << SM.getLineNumber(EInfo.first, EInfo.second)1541<< ':' << SM.getColumnNumber(EInfo.first, EInfo.second)1542<< "}:\"";1543OS.write_escaped(H.CodeToInsert);1544OS << "\"\n";1545}1546}154715481549