brintos

brintos / llvm-project-archived public Read only

0
0
Text · 58.1 KiB · 1003218 Raw
1633 lines · cpp
1//===--- TextDiagnostic.cpp - Text Diagnostic Pretty-Printing -------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "clang/Frontend/TextDiagnostic.h"10#include "clang/Basic/CharInfo.h"11#include "clang/Basic/DiagnosticOptions.h"12#include "clang/Basic/FileManager.h"13#include "clang/Basic/SourceManager.h"14#include "clang/Lex/Lexer.h"15#include "clang/Lex/Preprocessor.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 <algorithm>21#include <optional>22 23using namespace clang;24 25static constexpr raw_ostream::Colors NoteColor = raw_ostream::CYAN;26static constexpr raw_ostream::Colors RemarkColor = raw_ostream::BLUE;27static constexpr raw_ostream::Colors FixitColor = raw_ostream::GREEN;28static constexpr raw_ostream::Colors CaretColor = raw_ostream::GREEN;29static constexpr raw_ostream::Colors WarningColor = raw_ostream::MAGENTA;30static constexpr raw_ostream::Colors TemplateColor = raw_ostream::CYAN;31static constexpr raw_ostream::Colors ErrorColor = raw_ostream::RED;32static constexpr raw_ostream::Colors FatalColor = raw_ostream::RED;33// Used for changing only the bold attribute.34static constexpr raw_ostream::Colors SavedColor = raw_ostream::SAVEDCOLOR;35 36// Magenta is taken for 'warning'. Red is already 'error' and 'cyan'37// is already taken for 'note'. Green is already used to underline38// source ranges. White and black are bad because of the usual39// terminal backgrounds. Which leaves us only with TWO options.40static constexpr raw_ostream::Colors CommentColor = raw_ostream::YELLOW;41static constexpr raw_ostream::Colors LiteralColor = raw_ostream::GREEN;42static constexpr raw_ostream::Colors KeywordColor = raw_ostream::BLUE;43 44namespace {45template <typename Sub> class ColumnsOrBytes {46public:47  int V = 0;48  ColumnsOrBytes(int V) : V(V) {}49  bool isValid() const { return V != -1; }50  Sub next() const { return Sub(V + 1); }51  Sub prev() const { return Sub(V - 1); }52 53  bool operator>(Sub O) const { return V > O.V; }54  bool operator<(Sub O) const { return V < O.V; }55  bool operator<=(Sub B) const { return V <= B.V; }56  bool operator!=(Sub C) const { return C.V != V; }57 58  Sub operator+(Sub B) const { return Sub(V + B.V); }59  Sub &operator+=(Sub B) {60    V += B.V;61    return *static_cast<Sub *>(this);62  }63  Sub operator-(Sub B) const { return Sub(V - B.V); }64  Sub &operator-=(Sub B) {65    V -= B.V;66    return *static_cast<Sub *>(this);67  }68};69 70class Bytes final : public ColumnsOrBytes<Bytes> {71public:72  Bytes(int V) : ColumnsOrBytes(V) {}73};74 75class Columns final : public ColumnsOrBytes<Columns> {76public:77  Columns(int V) : ColumnsOrBytes(V) {}78};79} // namespace80 81/// Add highlights to differences in template strings.82static void applyTemplateHighlighting(raw_ostream &OS, StringRef Str,83                                      bool &Normal, bool Bold) {84  while (true) {85    size_t Pos = Str.find(ToggleHighlight);86    OS << Str.slice(0, Pos);87    if (Pos == StringRef::npos)88      break;89 90    Str = Str.substr(Pos + 1);91    if (Normal)92      OS.changeColor(TemplateColor, true);93    else {94      OS.resetColor();95      if (Bold)96        OS.changeColor(SavedColor, true);97    }98    Normal = !Normal;99  }100}101 102/// Number of spaces to indent when word-wrapping.103const unsigned WordWrapIndentation = 6;104 105static int bytesSincePreviousTabOrLineBegin(StringRef SourceLine, size_t i) {106  int bytes = 0;107  while (0<i) {108    if (SourceLine[--i]=='\t')109      break;110    ++bytes;111  }112  return bytes;113}114 115/// returns a printable representation of first item from input range116///117/// This function returns a printable representation of the next item in a line118///  of source. If the next byte begins a valid and printable character, that119///  character is returned along with 'true'.120///121/// Otherwise, if the next byte begins a valid, but unprintable character, a122///  printable, escaped representation of the character is returned, along with123///  'false'. Otherwise a printable, escaped representation of the next byte124///  is returned along with 'false'.125///126/// \note The index is updated to be used with a subsequent call to127///        printableTextForNextCharacter.128///129/// \param SourceLine The line of source130/// \param I Pointer to byte index,131/// \param TabStop used to expand tabs132/// \return pair(printable text, 'true' iff original text was printable)133///134static std::pair<SmallString<16>, bool>135printableTextForNextCharacter(StringRef SourceLine, size_t *I,136                              unsigned TabStop) {137  assert(I && "I must not be null");138  assert(*I < SourceLine.size() && "must point to a valid index");139 140  if (SourceLine[*I] == '\t') {141    assert(0 < TabStop && TabStop <= DiagnosticOptions::MaxTabStop &&142           "Invalid -ftabstop value");143    unsigned LineBytes = bytesSincePreviousTabOrLineBegin(SourceLine, *I);144    unsigned NumSpaces = TabStop - (LineBytes % TabStop);145    assert(0 < NumSpaces && NumSpaces <= TabStop146           && "Invalid computation of space amt");147    ++(*I);148 149    SmallString<16> ExpandedTab;150    ExpandedTab.assign(NumSpaces, ' ');151    return std::make_pair(ExpandedTab, true);152  }153 154  const unsigned char *Begin = SourceLine.bytes_begin() + *I;155 156  // Fast path for the common ASCII case.157  if (*Begin < 0x80 && llvm::sys::locale::isPrint(*Begin)) {158    ++(*I);159    return std::make_pair(SmallString<16>(Begin, Begin + 1), true);160  }161  unsigned CharSize = llvm::getNumBytesForUTF8(*Begin);162  const unsigned char *End = Begin + CharSize;163 164  // Convert it to UTF32 and check if it's printable.165  if (End <= SourceLine.bytes_end() && llvm::isLegalUTF8Sequence(Begin, End)) {166    llvm::UTF32 C;167    llvm::UTF32 *CPtr = &C;168 169    // Begin and end before conversion.170    unsigned char const *OriginalBegin = Begin;171    llvm::ConversionResult Res = llvm::ConvertUTF8toUTF32(172        &Begin, End, &CPtr, CPtr + 1, llvm::strictConversion);173    (void)Res;174    assert(Res == llvm::conversionOK);175    assert(OriginalBegin < Begin);176    assert(unsigned(Begin - OriginalBegin) == CharSize);177 178    (*I) += (Begin - OriginalBegin);179 180    // Valid, multi-byte, printable UTF8 character.181    if (llvm::sys::locale::isPrint(C))182      return std::make_pair(SmallString<16>(OriginalBegin, End), true);183 184    // Valid but not printable.185    SmallString<16> Str("<U+>");186    while (C) {187      Str.insert(Str.begin() + 3, llvm::hexdigit(C % 16));188      C /= 16;189    }190    while (Str.size() < 8)191      Str.insert(Str.begin() + 3, llvm::hexdigit(0));192    return std::make_pair(Str, false);193  }194 195  // Otherwise, not printable since it's not valid UTF8.196  SmallString<16> ExpandedByte("<XX>");197  unsigned char Byte = SourceLine[*I];198  ExpandedByte[1] = llvm::hexdigit(Byte / 16);199  ExpandedByte[2] = llvm::hexdigit(Byte % 16);200  ++(*I);201  return std::make_pair(ExpandedByte, false);202}203 204static void expandTabs(std::string &SourceLine, unsigned TabStop) {205  size_t I = SourceLine.size();206  while (I > 0) {207    I--;208    if (SourceLine[I] != '\t')209      continue;210    size_t TmpI = I;211    auto [Str, Printable] =212        printableTextForNextCharacter(SourceLine, &TmpI, TabStop);213    SourceLine.replace(I, 1, Str.c_str());214  }215}216 217/// \p BytesOut:218///  A mapping from columns to the byte of the source line that produced the219///  character displaying at that column. This is the inverse of \p ColumnsOut.220///221/// The last element in the array is the number of bytes in the source string.222///223/// example: (given a tabstop of 8)224///225///    "a \t \u3042" -> {0,1,2,-1,-1,-1,-1,-1,3,4,-1,7}226///227///  (\\u3042 is represented in UTF-8 by three bytes and takes two columns to228///   display)229///230/// \p ColumnsOut:231///  A mapping from the bytes232///  of the printable representation of the line to the columns those printable233///  characters will appear at (numbering the first column as 0).234///235/// If a byte 'i' corresponds to multiple columns (e.g. the byte contains a tab236///  character) then the array will map that byte to the first column the237///  tab appears at and the next value in the map will have been incremented238///  more than once.239///240/// If a byte is the first in a sequence of bytes that together map to a single241///  entity in the output, then the array will map that byte to the appropriate242///  column while the subsequent bytes will be -1.243///244/// The last element in the array does not correspond to any byte in the input245///  and instead is the number of columns needed to display the source246///247/// example: (given a tabstop of 8)248///249///    "a \t \u3042" -> {0,1,2,8,9,-1,-1,11}250///251///  (\\u3042 is represented in UTF-8 by three bytes and takes two columns to252///   display)253static void genColumnByteMapping(StringRef SourceLine, unsigned TabStop,254                                 SmallVectorImpl<Bytes> &BytesOut,255                                 SmallVectorImpl<Columns> &ColumnsOut) {256  assert(BytesOut.empty());257  assert(ColumnsOut.empty());258 259  if (SourceLine.empty()) {260    BytesOut.resize(1u, Bytes(0));261    ColumnsOut.resize(1u, Columns(0));262    return;263  }264 265  ColumnsOut.resize(SourceLine.size() + 1, -1);266 267  Columns NumColumns = 0;268  size_t I = 0;269  while (I < SourceLine.size()) {270    ColumnsOut[I] = NumColumns;271    BytesOut.resize(NumColumns.V + 1, -1);272    BytesOut.back() = Bytes(I);273    auto [Str, Printable] =274        printableTextForNextCharacter(SourceLine, &I, TabStop);275    NumColumns += Columns(llvm::sys::locale::columnWidth(Str));276  }277 278  ColumnsOut.back() = NumColumns;279  BytesOut.resize(NumColumns.V + 1, -1);280  BytesOut.back() = Bytes(I);281}282 283namespace {284struct SourceColumnMap {285  SourceColumnMap(StringRef SourceLine, unsigned TabStop)286      : SourceLine(SourceLine) {287 288    genColumnByteMapping(SourceLine, TabStop, ColumnToByte, ByteToColumn);289 290    assert(ByteToColumn.size() == SourceLine.size() + 1);291    assert(0 < ByteToColumn.size() && 0 < ColumnToByte.size());292    assert(ByteToColumn.size() ==293           static_cast<unsigned>(ColumnToByte.back().V + 1));294    assert(static_cast<unsigned>(ByteToColumn.back().V + 1) ==295           ColumnToByte.size());296  }297  Columns columns() const { return ByteToColumn.back(); }298  Bytes bytes() const { return ColumnToByte.back(); }299 300  /// Map a byte to the column which it is at the start of, or return -1301  /// if it is not at the start of a column (for a UTF-8 trailing byte).302  Columns byteToColumn(Bytes N) const {303    assert(0 <= N.V && N.V < static_cast<int>(ByteToColumn.size()));304    return ByteToColumn[N.V];305  }306 307  /// Map a byte to the first column which contains it.308  Columns byteToContainingColumn(Bytes N) const {309    assert(0 <= N.V && N.V < static_cast<int>(ByteToColumn.size()));310    while (!ByteToColumn[N.V].isValid())311      --N.V;312    return ByteToColumn[N.V];313  }314 315  /// Map a column to the byte which starts the column, or return -1 if316  /// the column the second or subsequent column of an expanded tab or similar317  /// multi-column entity.318  Bytes columnToByte(Columns N) const {319    assert(0 <= N.V && N.V < static_cast<int>(ColumnToByte.size()));320    return ColumnToByte[N.V];321  }322 323  /// Map from a byte index to the next byte which starts a column.324  Bytes startOfNextColumn(Bytes N) const {325    assert(0 <= N.V && N.V < static_cast<int>(ByteToColumn.size() - 1));326    N = N.next();327    while (!byteToColumn(N).isValid())328      N = N.next();329    return N;330  }331 332  /// Map from a byte index to the previous byte which starts a column.333  Bytes startOfPreviousColumn(Bytes N) const {334    assert(0 < N.V && N.V < static_cast<int>(ByteToColumn.size()));335    N = N.prev();336    while (!byteToColumn(N).isValid())337      N = N.prev();338    return N;339  }340 341  StringRef getSourceLine() const { return SourceLine; }342 343private:344  StringRef SourceLine;345  SmallVector<Columns, 200> ByteToColumn;346  SmallVector<Bytes, 200> ColumnToByte;347};348} // end anonymous namespace349 350/// When the source code line we want to print is too long for351/// the terminal, select the "interesting" region.352static void selectInterestingSourceRegion(353    std::string &SourceLine, std::string &CaretLine,354    std::string &FixItInsertionLine, Columns NonGutterColumns,355    const SourceColumnMap &Map,356    SmallVectorImpl<clang::TextDiagnostic::StyleRange> &Styles) {357  Columns CaretColumns = CaretLine.size();358  Columns FixItColumns = llvm::sys::locale::columnWidth(FixItInsertionLine);359  Columns MaxColumns =360      std::max({Map.columns().V, CaretColumns.V, FixItColumns.V});361  // if the number of columns is less than the desired number we're done362  if (MaxColumns <= NonGutterColumns)363    return;364 365  // No special characters are allowed in CaretLine.366  assert(llvm::none_of(CaretLine, [](char c) { return c < ' ' || '~' < c; }));367 368  // Find the slice that we need to display the full caret line369  // correctly.370  Columns CaretStart = 0, CaretEnd = CaretLine.size();371  while (CaretStart != CaretEnd && isWhitespace(CaretLine[CaretStart.V]))372    CaretStart = CaretStart.next();373 374  while (CaretEnd != CaretStart && isWhitespace(CaretLine[CaretEnd.V]))375    CaretEnd = CaretEnd.prev();376 377  // caret has already been inserted into CaretLine so the above whitespace378  // check is guaranteed to include the caret379 380  // If we have a fix-it line, make sure the slice includes all of the381  // fix-it information.382  if (!FixItInsertionLine.empty()) {383    // We can safely use the byte offset FixItStart as the column offset384    // because the characters up until FixItStart are all ASCII whitespace385    // characters.386    Bytes FixItStart = 0;387    Bytes FixItEnd = Bytes(FixItInsertionLine.size());388    while (FixItStart != FixItEnd &&389           isWhitespace(FixItInsertionLine[FixItStart.V]))390      FixItStart = FixItStart.next();391 392    while (FixItEnd != FixItStart &&393           isWhitespace(FixItInsertionLine[FixItEnd.V - 1]))394      FixItEnd = FixItEnd.prev();395 396    Columns FixItStartCol = Columns(FixItStart.V);397    Columns FixItEndCol = Columns(llvm::sys::locale::columnWidth(398        FixItInsertionLine.substr(0, FixItEnd.V)));399 400    CaretStart = std::min(FixItStartCol.V, CaretStart.V);401    CaretEnd = std::max(FixItEndCol.V, CaretEnd.V);402  }403 404  // CaretEnd may have been set at the middle of a character405  // If it's not at a character's first column then advance it past the current406  //   character.407  while (CaretEnd < Map.columns() && !Map.columnToByte(CaretEnd).isValid())408    CaretEnd = CaretEnd.next();409 410  assert(411      (CaretStart > Map.columns() || Map.columnToByte(CaretStart).isValid()) &&412      "CaretStart must not point to a column in the middle of a source"413      " line character");414  assert((CaretEnd > Map.columns() || Map.columnToByte(CaretEnd).isValid()) &&415         "CaretEnd must not point to a column in the middle of a source line"416         " character");417 418  // CaretLine[CaretStart, CaretEnd) contains all of the interesting419  // parts of the caret line. While this slice is smaller than the420  // number of columns we have, try to grow the slice to encompass421  // more context.422 423  Bytes SourceStart = Map.columnToByte(std::min(CaretStart.V, Map.columns().V));424  Bytes SourceEnd = Map.columnToByte(std::min(CaretEnd.V, Map.columns().V));425 426  Columns CaretColumnsOutsideSource =427      CaretEnd - CaretStart -428      (Map.byteToColumn(SourceEnd) - Map.byteToColumn(SourceStart));429 430  constexpr StringRef FrontEllipse = "  ...";431  constexpr StringRef FrontSpace = "     ";432  constexpr StringRef BackEllipse = "...";433  Columns EllipsesColumns = Columns(FrontEllipse.size() + BackEllipse.size());434 435  Columns TargetColumns = NonGutterColumns;436  // Give us extra room for the ellipses437  //  and any of the caret line that extends past the source438  if (TargetColumns > EllipsesColumns + CaretColumnsOutsideSource)439    TargetColumns -= EllipsesColumns + CaretColumnsOutsideSource;440 441  while (SourceStart > 0 || SourceEnd < SourceLine.size()) {442    bool ExpandedRegion = false;443 444    if (SourceStart > 0) {445      Bytes NewStart = Map.startOfPreviousColumn(SourceStart);446 447      // Skip over any whitespace we see here; we're looking for448      // another bit of interesting text.449      // FIXME: Detect non-ASCII whitespace characters too.450      while (NewStart > 0 && isWhitespace(SourceLine[NewStart.V]))451        NewStart = Map.startOfPreviousColumn(NewStart);452 453      // Skip over this bit of "interesting" text.454      while (NewStart > 0) {455        Bytes Prev = Map.startOfPreviousColumn(NewStart);456        if (isWhitespace(SourceLine[Prev.V]))457          break;458        NewStart = Prev;459      }460 461      assert(Map.byteToColumn(NewStart).isValid());462      Columns NewColumns =463          Map.byteToColumn(SourceEnd) - Map.byteToColumn(NewStart);464      if (NewColumns <= TargetColumns) {465        SourceStart = NewStart;466        ExpandedRegion = true;467      }468    }469 470    if (SourceEnd < SourceLine.size()) {471      Bytes NewEnd = Map.startOfNextColumn(SourceEnd);472 473      // Skip over any whitespace we see here; we're looking for474      // another bit of interesting text.475      // FIXME: Detect non-ASCII whitespace characters too.476      while (NewEnd < SourceLine.size() && isWhitespace(SourceLine[NewEnd.V]))477        NewEnd = Map.startOfNextColumn(NewEnd);478 479      // Skip over this bit of "interesting" text.480      while (NewEnd < SourceLine.size() && isWhitespace(SourceLine[NewEnd.V]))481        NewEnd = Map.startOfNextColumn(NewEnd);482 483      assert(Map.byteToColumn(NewEnd).isValid());484      Columns NewColumns =485          Map.byteToColumn(NewEnd) - Map.byteToColumn(SourceStart);486      if (NewColumns <= TargetColumns) {487        SourceEnd = NewEnd;488        ExpandedRegion = true;489      }490    }491 492    if (!ExpandedRegion)493      break;494  }495 496  CaretStart = Map.byteToColumn(SourceStart);497  CaretEnd = Map.byteToColumn(SourceEnd) + CaretColumnsOutsideSource;498 499  // [CaretStart, CaretEnd) is the slice we want. Update the various500  // output lines to show only this slice.501  assert(CaretStart.isValid() && CaretEnd.isValid() && SourceStart.isValid() &&502         SourceEnd.isValid());503  assert(SourceStart <= SourceEnd);504  assert(CaretStart <= CaretEnd);505 506  Columns BackColumnsRemoved =507      Map.byteToColumn(Bytes{static_cast<int>(SourceLine.size())}) -508      Map.byteToColumn(SourceEnd);509  Columns FrontColumnsRemoved = CaretStart;510  Columns ColumnsKept = CaretEnd - CaretStart;511 512  // We checked up front that the line needed truncation513  assert(FrontColumnsRemoved + ColumnsKept + BackColumnsRemoved >514         NonGutterColumns);515 516  // Since we've modified the SourceLine, we also need to adjust the line's517  // highlighting information. In particular, if we've removed518  // from the front of the line, we need to move the style ranges to the519  // left and remove unneeded ranges.520  // Note in particular that variables like CaretEnd are defined in the521  // CaretLine, which only contains ASCII, while the style ranges are defined in522  // the source line, where we have to care for the byte-index != column-index523  // case.524  Bytes BytesRemoved =525      FrontColumnsRemoved > FrontEllipse.size()526          ? (Map.columnToByte(FrontColumnsRemoved) - Bytes(FrontEllipse.size()))527          : 0;528  Bytes CodeEnd =529      CaretEnd < Map.columns() ? Map.columnToByte(CaretEnd.V) : CaretEnd.V;530  for (TextDiagnostic::StyleRange &R : Styles) {531    // Remove style ranges before and after the new truncated snippet.532    if (R.Start >= static_cast<unsigned>(CodeEnd.V) ||533        R.End < static_cast<unsigned>(BytesRemoved.V)) {534      R.Start = R.End = std::numeric_limits<int>::max();535      continue;536    }537    // Move them left. (Note that this can wrap R.Start, but that doesn't538    // matter).539    R.Start -= BytesRemoved.V;540    R.End -= BytesRemoved.V;541 542    // Don't leak into the ellipse at the end.543    if (R.Start < static_cast<unsigned>(CodeEnd.V) &&544        R.End > static_cast<unsigned>(CodeEnd.V))545      R.End = CodeEnd.V + 1; // R.End is inclusive.546  }547 548  // The line needs some truncation, and we'd prefer to keep the front549  //  if possible, so remove the back550  if (BackColumnsRemoved > Columns(BackEllipse.size()))551    SourceLine.replace(SourceEnd.V, std::string::npos, BackEllipse);552 553  // If that's enough then we're done554  if (FrontColumnsRemoved + ColumnsKept <= NonGutterColumns)555    return;556 557  // Otherwise remove the front as well558  if (FrontColumnsRemoved > Columns(FrontEllipse.size())) {559    SourceLine.replace(0, SourceStart.V, FrontEllipse);560    CaretLine.replace(0, CaretStart.V, FrontSpace);561    if (!FixItInsertionLine.empty())562      FixItInsertionLine.replace(0, CaretStart.V, FrontSpace);563  }564}565 566/// Skip over whitespace in the string, starting at the given567/// index.568///569/// \returns The index of the first non-whitespace character that is570/// greater than or equal to Idx or, if no such character exists,571/// returns the end of the string.572static unsigned skipWhitespace(unsigned Idx, StringRef Str, unsigned Length) {573  while (Idx < Length && isWhitespace(Str[Idx]))574    ++Idx;575  return Idx;576}577 578/// If the given character is the start of some kind of579/// balanced punctuation (e.g., quotes or parentheses), return the580/// character that will terminate the punctuation.581///582/// \returns The ending punctuation character, if any, or the NULL583/// character if the input character does not start any punctuation.584static inline char findMatchingPunctuation(char c) {585  switch (c) {586  case '\'': return '\'';587  case '`': return '\'';588  case '"':  return '"';589  case '(':  return ')';590  case '[': return ']';591  case '{': return '}';592  default: break;593  }594 595  return 0;596}597 598/// Find the end of the word starting at the given offset599/// within a string.600///601/// \returns the index pointing one character past the end of the602/// word.603static unsigned findEndOfWord(unsigned Start, StringRef Str,604                              unsigned Length, unsigned Column,605                              unsigned Columns) {606  assert(Start < Str.size() && "Invalid start position!");607  unsigned End = Start + 1;608 609  // If we are already at the end of the string, take that as the word.610  if (End == Str.size())611    return End;612 613  // Determine if the start of the string is actually opening614  // punctuation, e.g., a quote or parentheses.615  char EndPunct = findMatchingPunctuation(Str[Start]);616  if (!EndPunct) {617    // This is a normal word. Just find the first space character.618    while (End < Length && !isWhitespace(Str[End]))619      ++End;620    return End;621  }622 623  // We have the start of a balanced punctuation sequence (quotes,624  // parentheses, etc.). Determine the full sequence is.625  SmallString<16> PunctuationEndStack;626  PunctuationEndStack.push_back(EndPunct);627  while (End < Length && !PunctuationEndStack.empty()) {628    if (Str[End] == PunctuationEndStack.back())629      PunctuationEndStack.pop_back();630    else if (char SubEndPunct = findMatchingPunctuation(Str[End]))631      PunctuationEndStack.push_back(SubEndPunct);632 633    ++End;634  }635 636  // Find the first space character after the punctuation ended.637  while (End < Length && !isWhitespace(Str[End]))638    ++End;639 640  unsigned PunctWordLength = End - Start;641  if (// If the word fits on this line642      Column + PunctWordLength <= Columns ||643      // ... or the word is "short enough" to take up the next line644      // without too much ugly white space645      PunctWordLength < Columns/3)646    return End; // Take the whole thing as a single "word".647 648  // The whole quoted/parenthesized string is too long to print as a649  // single "word". Instead, find the "word" that starts just after650  // the punctuation and use that end-point instead. This will recurse651  // until it finds something small enough to consider a word.652  return findEndOfWord(Start + 1, Str, Length, Column + 1, Columns);653}654 655/// Print the given string to a stream, word-wrapping it to656/// some number of columns in the process.657///658/// \param OS the stream to which the word-wrapping string will be659/// emitted.660/// \param Str the string to word-wrap and output.661/// \param Columns the number of columns to word-wrap to.662/// \param Column the column number at which the first character of \p663/// Str will be printed. This will be non-zero when part of the first664/// line has already been printed.665/// \param Bold if the current text should be bold666/// \returns true if word-wrapping was required, or false if the667/// string fit on the first line.668static bool printWordWrapped(raw_ostream &OS, StringRef Str, unsigned Columns,669                             unsigned Column, bool Bold) {670  const unsigned Length = std::min(Str.find('\n'), Str.size());671  bool TextNormal = true;672 673  bool Wrapped = false;674  for (unsigned WordStart = 0, WordEnd; WordStart < Length;675       WordStart = WordEnd) {676    // Find the beginning of the next word.677    WordStart = skipWhitespace(WordStart, Str, Length);678    if (WordStart == Length)679      break;680 681    // Find the end of this word.682    WordEnd = findEndOfWord(WordStart, Str, Length, Column, Columns);683 684    // Does this word fit on the current line?685    unsigned WordLength = WordEnd - WordStart;686    if (Column + WordLength < Columns) {687      // This word fits on the current line; print it there.688      if (WordStart) {689        OS << ' ';690        Column += 1;691      }692      applyTemplateHighlighting(OS, Str.substr(WordStart, WordLength),693                                TextNormal, Bold);694      Column += WordLength;695      continue;696    }697 698    // This word does not fit on the current line, so wrap to the next699    // line.700    OS << '\n';701    OS.indent(WordWrapIndentation);702    applyTemplateHighlighting(OS, Str.substr(WordStart, WordLength),703                              TextNormal, Bold);704    Column = WordWrapIndentation + WordLength;705    Wrapped = true;706  }707 708  // Append any remaning text from the message with its existing formatting.709  applyTemplateHighlighting(OS, Str.substr(Length), TextNormal, Bold);710 711  assert(TextNormal && "Text highlighted at end of diagnostic message.");712 713  return Wrapped;714}715 716TextDiagnostic::TextDiagnostic(raw_ostream &OS, const LangOptions &LangOpts,717                               DiagnosticOptions &DiagOpts,718                               const Preprocessor *PP)719    : DiagnosticRenderer(LangOpts, DiagOpts), OS(OS), PP(PP) {}720 721TextDiagnostic::~TextDiagnostic() {}722 723void TextDiagnostic::emitDiagnosticMessage(724    FullSourceLoc Loc, PresumedLoc PLoc, DiagnosticsEngine::Level Level,725    StringRef Message, ArrayRef<clang::CharSourceRange> Ranges,726    DiagOrStoredDiag D) {727  uint64_t StartOfLocationInfo = OS.getColumn();728 729  // Emit the location of this particular diagnostic.730  if (Loc.isValid())731    emitDiagnosticLoc(Loc, PLoc, Level, Ranges);732 733  if (DiagOpts.ShowColors)734    OS.resetColor();735 736  if (DiagOpts.ShowLevel)737    printDiagnosticLevel(OS, Level, DiagOpts.ShowColors);738  printDiagnosticMessage(OS,739                         /*IsSupplemental*/ Level == DiagnosticsEngine::Note,740                         Message, OS.getColumn() - StartOfLocationInfo,741                         DiagOpts.MessageLength, DiagOpts.ShowColors);742  // We use a formatted ostream, which does its own buffering. Flush here743  // so we keep the proper order of output.744  OS.flush();745}746 747/*static*/ void748TextDiagnostic::printDiagnosticLevel(raw_ostream &OS,749                                     DiagnosticsEngine::Level Level,750                                     bool ShowColors) {751  if (ShowColors) {752    // Print diagnostic category in bold and color753    switch (Level) {754    case DiagnosticsEngine::Ignored:755      llvm_unreachable("Invalid diagnostic type");756    case DiagnosticsEngine::Note:757      OS.changeColor(NoteColor, true);758      break;759    case DiagnosticsEngine::Remark:760      OS.changeColor(RemarkColor, true);761      break;762    case DiagnosticsEngine::Warning:763      OS.changeColor(WarningColor, true);764      break;765    case DiagnosticsEngine::Error:766      OS.changeColor(ErrorColor, true);767      break;768    case DiagnosticsEngine::Fatal:769      OS.changeColor(FatalColor, true);770      break;771    }772  }773 774  switch (Level) {775  case DiagnosticsEngine::Ignored:776    llvm_unreachable("Invalid diagnostic type");777  case DiagnosticsEngine::Note:    OS << "note: "; break;778  case DiagnosticsEngine::Remark:  OS << "remark: "; break;779  case DiagnosticsEngine::Warning: OS << "warning: "; break;780  case DiagnosticsEngine::Error:   OS << "error: "; break;781  case DiagnosticsEngine::Fatal:   OS << "fatal error: "; break;782  }783 784  if (ShowColors)785    OS.resetColor();786}787 788/*static*/789void TextDiagnostic::printDiagnosticMessage(raw_ostream &OS,790                                            bool IsSupplemental,791                                            StringRef Message,792                                            unsigned CurrentColumn,793                                            unsigned Columns, bool ShowColors) {794  bool Bold = false;795  if (ShowColors && !IsSupplemental) {796    // Print primary diagnostic messages in bold and without color, to visually797    // indicate the transition from continuation notes and other output.798    OS.changeColor(SavedColor, true);799    Bold = true;800  }801 802  if (Columns)803    printWordWrapped(OS, Message, Columns, CurrentColumn, Bold);804  else {805    bool Normal = true;806    applyTemplateHighlighting(OS, Message, Normal, Bold);807    assert(Normal && "Formatting should have returned to normal");808  }809 810  if (ShowColors)811    OS.resetColor();812  OS << '\n';813}814 815void TextDiagnostic::emitFilename(StringRef Filename, const SourceManager &SM) {816#ifdef _WIN32817  SmallString<4096> TmpFilename;818#endif819  if (DiagOpts.AbsolutePath) {820    auto File = SM.getFileManager().getOptionalFileRef(Filename);821    if (File) {822      // We want to print a simplified absolute path, i. e. without "dots".823      //824      // The hardest part here are the paths like "<part1>/<link>/../<part2>".825      // On Unix-like systems, we cannot just collapse "<link>/..", because826      // paths are resolved sequentially, and, thereby, the path827      // "<part1>/<part2>" may point to a different location. That is why828      // we use FileManager::getCanonicalName(), which expands all indirections829      // with llvm::sys::fs::real_path() and caches the result.830      //831      // On the other hand, it would be better to preserve as much of the832      // original path as possible, because that helps a user to recognize it.833      // real_path() expands all links, which sometimes too much. Luckily,834      // on Windows we can just use llvm::sys::path::remove_dots(), because,835      // on that system, both aforementioned paths point to the same place.836#ifdef _WIN32837      TmpFilename = File->getName();838      llvm::sys::fs::make_absolute(TmpFilename);839      llvm::sys::path::native(TmpFilename);840      llvm::sys::path::remove_dots(TmpFilename, /* remove_dot_dot */ true);841      Filename = StringRef(TmpFilename.data(), TmpFilename.size());842#else843      Filename = SM.getFileManager().getCanonicalName(*File);844#endif845    }846  }847 848  OS << Filename;849}850 851/// Print out the file/line/column information and include trace.852///853/// This method handles the emission of the diagnostic location information.854/// This includes extracting as much location information as is present for855/// the diagnostic and printing it, as well as any include stack or source856/// ranges necessary.857void TextDiagnostic::emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,858                                       DiagnosticsEngine::Level Level,859                                       ArrayRef<CharSourceRange> Ranges) {860  if (PLoc.isInvalid()) {861    // At least print the file name if available:862    if (FileID FID = Loc.getFileID(); FID.isValid()) {863      if (OptionalFileEntryRef FE = Loc.getFileEntryRef()) {864        emitFilename(FE->getName(), Loc.getManager());865        OS << ": ";866      }867    }868    return;869  }870  unsigned LineNo = PLoc.getLine();871 872  if (!DiagOpts.ShowLocation)873    return;874 875  if (DiagOpts.ShowColors)876    OS.changeColor(SavedColor, true);877 878  emitFilename(PLoc.getFilename(), Loc.getManager());879  switch (DiagOpts.getFormat()) {880  case DiagnosticOptions::SARIF:881  case DiagnosticOptions::Clang:882    if (DiagOpts.ShowLine)883      OS << ':' << LineNo;884    break;885  case DiagnosticOptions::MSVC:  OS << '('  << LineNo; break;886  case DiagnosticOptions::Vi:    OS << " +" << LineNo; break;887  }888 889  if (DiagOpts.ShowColumn)890    // Compute the column number.891    if (unsigned ColNo = PLoc.getColumn()) {892      if (DiagOpts.getFormat() == DiagnosticOptions::MSVC) {893        OS << ',';894        // Visual Studio 2010 or earlier expects column number to be off by one895        if (LangOpts.MSCompatibilityVersion &&896            !LangOpts.isCompatibleWithMSVC(LangOptions::MSVC2012))897          ColNo--;898      } else899        OS << ':';900      OS << ColNo;901    }902  switch (DiagOpts.getFormat()) {903  case DiagnosticOptions::SARIF:904  case DiagnosticOptions::Clang:905  case DiagnosticOptions::Vi:    OS << ':';    break;906  case DiagnosticOptions::MSVC:907    // MSVC2013 and before print 'file(4) : error'. MSVC2015 gets rid of the908    // space and prints 'file(4): error'.909    OS << ')';910    if (LangOpts.MSCompatibilityVersion &&911        !LangOpts.isCompatibleWithMSVC(LangOptions::MSVC2015))912      OS << ' ';913    OS << ':';914    break;915  }916 917  if (DiagOpts.ShowSourceRanges && !Ranges.empty()) {918    FileID CaretFileID = Loc.getExpansionLoc().getFileID();919    bool PrintedRange = false;920    const SourceManager &SM = Loc.getManager();921 922    for (const auto &R : Ranges) {923      // Ignore invalid ranges.924      if (!R.isValid())925        continue;926 927      SourceLocation B = SM.getExpansionLoc(R.getBegin());928      CharSourceRange ERange = SM.getExpansionRange(R.getEnd());929      SourceLocation E = ERange.getEnd();930 931      // If the start or end of the range is in another file, just932      // discard it.933      if (SM.getFileID(B) != CaretFileID || SM.getFileID(E) != CaretFileID)934        continue;935 936      // Add in the length of the token, so that we cover multi-char937      // tokens.938      unsigned TokSize = 0;939      if (ERange.isTokenRange())940        TokSize = Lexer::MeasureTokenLength(E, SM, LangOpts);941 942      FullSourceLoc BF(B, SM), EF(E, SM);943      OS << '{'944         << BF.getLineNumber() << ':' << BF.getColumnNumber() << '-'945         << EF.getLineNumber() << ':' << (EF.getColumnNumber() + TokSize)946         << '}';947      PrintedRange = true;948    }949 950    if (PrintedRange)951      OS << ':';952  }953  OS << ' ';954}955 956void TextDiagnostic::emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) {957  if (DiagOpts.ShowLocation && PLoc.isValid()) {958    OS << "In file included from ";959    emitFilename(PLoc.getFilename(), Loc.getManager());960    OS << ':' << PLoc.getLine() << ":\n";961  } else962    OS << "In included file:\n";963}964 965void TextDiagnostic::emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,966                                        StringRef ModuleName) {967  if (DiagOpts.ShowLocation && PLoc.isValid())968    OS << "In module '" << ModuleName << "' imported from "969       << PLoc.getFilename() << ':' << PLoc.getLine() << ":\n";970  else971    OS << "In module '" << ModuleName << "':\n";972}973 974void TextDiagnostic::emitBuildingModuleLocation(FullSourceLoc Loc,975                                                PresumedLoc PLoc,976                                                StringRef ModuleName) {977  if (DiagOpts.ShowLocation && PLoc.isValid())978    OS << "While building module '" << ModuleName << "' imported from "979      << PLoc.getFilename() << ':' << PLoc.getLine() << ":\n";980  else981    OS << "While building module '" << ModuleName << "':\n";982}983 984/// Find the suitable set of lines to show to include a set of ranges.985static std::optional<std::pair<unsigned, unsigned>>986findLinesForRange(const CharSourceRange &R, FileID FID,987                  const SourceManager &SM) {988  if (!R.isValid())989    return std::nullopt;990 991  SourceLocation Begin = R.getBegin();992  SourceLocation End = R.getEnd();993  if (SM.getFileID(Begin) != FID || SM.getFileID(End) != FID)994    return std::nullopt;995 996  return std::make_pair(SM.getExpansionLineNumber(Begin),997                        SM.getExpansionLineNumber(End));998}999 1000/// Add as much of range B into range A as possible without exceeding a maximum1001/// size of MaxRange. Ranges are inclusive.1002static std::pair<unsigned, unsigned>1003maybeAddRange(std::pair<unsigned, unsigned> A, std::pair<unsigned, unsigned> B,1004              unsigned MaxRange) {1005  // If A is already the maximum size, we're done.1006  unsigned Slack = MaxRange - (A.second - A.first + 1);1007  if (Slack == 0)1008    return A;1009 1010  // Easy case: merge succeeds within MaxRange.1011  unsigned Min = std::min(A.first, B.first);1012  unsigned Max = std::max(A.second, B.second);1013  if (Max - Min + 1 <= MaxRange)1014    return {Min, Max};1015 1016  // If we can't reach B from A within MaxRange, there's nothing to do.1017  // Don't add lines to the range that contain nothing interesting.1018  if ((B.first > A.first && B.first - A.first + 1 > MaxRange) ||1019      (B.second < A.second && A.second - B.second + 1 > MaxRange))1020    return A;1021 1022  // Otherwise, expand A towards B to produce a range of size MaxRange. We1023  // attempt to expand by the same amount in both directions if B strictly1024  // contains A.1025 1026  // Expand downwards by up to half the available amount, then upwards as1027  // much as possible, then downwards as much as possible.1028  A.second = std::min(A.second + (Slack + 1) / 2, Max);1029  Slack = MaxRange - (A.second - A.first + 1);1030  A.first = std::max(Min + Slack, A.first) - Slack;1031  A.second = std::min(A.first + MaxRange - 1, Max);1032  return A;1033}1034 1035struct LineRange {1036  unsigned LineNo;1037  Bytes StartByte;1038  Bytes EndByte;1039};1040 1041/// Highlight \p R (with ~'s) on the current source line.1042static void highlightRange(const LineRange &R, const SourceColumnMap &Map,1043                           std::string &CaretLine) {1044  // Pick the first non-whitespace column.1045  Bytes StartByte = R.StartByte;1046  while (StartByte < Map.bytes() && (Map.getSourceLine()[StartByte.V] == ' ' ||1047                                     Map.getSourceLine()[StartByte.V] == '\t'))1048    StartByte = Map.startOfNextColumn(StartByte);1049 1050  // Pick the last non-whitespace column.1051  Bytes EndByte = std::min(R.EndByte.V, Map.bytes().V);1052  while (EndByte.V != 0 && (Map.getSourceLine()[EndByte.V - 1] == ' ' ||1053                            Map.getSourceLine()[EndByte.V - 1] == '\t'))1054    EndByte = Map.startOfPreviousColumn(EndByte);1055 1056  // If the start/end passed each other, then we are trying to highlight a1057  // range that just exists in whitespace. That most likely means we have1058  // a multi-line highlighting range that covers a blank line.1059  if (StartByte > EndByte)1060    return;1061 1062  assert(StartByte <= EndByte && "Invalid range!");1063  // Fill the range with ~'s.1064  Columns StartCol = Map.byteToContainingColumn(StartByte);1065  Columns EndCol = Map.byteToContainingColumn(EndByte);1066 1067  if (CaretLine.size() < static_cast<size_t>(EndCol.V))1068    CaretLine.resize(EndCol.V, ' ');1069 1070  std::fill(CaretLine.begin() + StartCol.V, CaretLine.begin() + EndCol.V, '~');1071}1072 1073static std::string buildFixItInsertionLine(FileID FID, unsigned LineNo,1074                                           const SourceColumnMap &map,1075                                           ArrayRef<FixItHint> Hints,1076                                           const SourceManager &SM,1077                                           const DiagnosticOptions &DiagOpts) {1078  std::string FixItInsertionLine;1079  if (Hints.empty() || !DiagOpts.ShowFixits)1080    return FixItInsertionLine;1081  Columns PrevHintEndCol = 0;1082 1083  for (const auto &H : Hints) {1084    if (H.CodeToInsert.empty())1085      continue;1086 1087    // We have an insertion hint. Determine whether the inserted1088    // code contains no newlines and is on the same line as the caret.1089    FileIDAndOffset HintLocInfo =1090        SM.getDecomposedExpansionLoc(H.RemoveRange.getBegin());1091    if (FID == HintLocInfo.first &&1092        LineNo == SM.getLineNumber(HintLocInfo.first, HintLocInfo.second) &&1093        StringRef(H.CodeToInsert).find_first_of("\n\r") == StringRef::npos) {1094      // Insert the new code into the line just below the code1095      // that the user wrote.1096      // Note: When modifying this function, be very careful about what is a1097      // "column" (printed width, platform-dependent) and what is a1098      // "byte offset" (SourceManager "column").1099      Bytes HintByteOffset =1100          Bytes(SM.getColumnNumber(HintLocInfo.first, HintLocInfo.second))1101              .prev();1102 1103      // The hint must start inside the source or right at the end1104      assert(HintByteOffset < map.bytes().next());1105      Columns HintCol = map.byteToContainingColumn(HintByteOffset);1106 1107      // If we inserted a long previous hint, push this one forwards, and add1108      // an extra space to show that this is not part of the previous1109      // completion. This is sort of the best we can do when two hints appear1110      // to overlap.1111      //1112      // Note that if this hint is located immediately after the previous1113      // hint, no space will be added, since the location is more important.1114      if (HintCol < PrevHintEndCol)1115        HintCol = PrevHintEndCol + 1;1116 1117      // This should NOT use HintByteOffset, because the source might have1118      // Unicode characters in earlier columns.1119      Columns NewFixItLineSize = Columns(FixItInsertionLine.size()) +1120                                 (HintCol - PrevHintEndCol) +1121                                 Columns(H.CodeToInsert.size());1122      if (NewFixItLineSize > FixItInsertionLine.size())1123        FixItInsertionLine.resize(NewFixItLineSize.V, ' ');1124 1125      std::copy(H.CodeToInsert.begin(), H.CodeToInsert.end(),1126                FixItInsertionLine.end() - H.CodeToInsert.size());1127 1128      PrevHintEndCol = HintCol + llvm::sys::locale::columnWidth(H.CodeToInsert);1129    }1130  }1131 1132  expandTabs(FixItInsertionLine, DiagOpts.TabStop);1133 1134  return FixItInsertionLine;1135}1136 1137static unsigned getNumDisplayWidth(unsigned N) {1138  unsigned L = 1u, M = 10u;1139  while (M <= N && ++L != std::numeric_limits<unsigned>::digits10 + 1)1140    M *= 10u;1141 1142  return L;1143}1144 1145/// Filter out invalid ranges, ranges that don't fit into the window of1146/// source lines we will print, and ranges from other files.1147///1148/// For the remaining ranges, convert them to simple LineRange structs,1149/// which only cover one line at a time.1150static SmallVector<LineRange>1151prepareAndFilterRanges(const SmallVectorImpl<CharSourceRange> &Ranges,1152                       const SourceManager &SM,1153                       const std::pair<unsigned, unsigned> &Lines, FileID FID,1154                       const LangOptions &LangOpts) {1155  SmallVector<LineRange> LineRanges;1156 1157  for (const CharSourceRange &R : Ranges) {1158    if (R.isInvalid())1159      continue;1160    SourceLocation Begin = R.getBegin();1161    SourceLocation End = R.getEnd();1162 1163    unsigned StartLineNo = SM.getExpansionLineNumber(Begin);1164    if (StartLineNo > Lines.second || SM.getFileID(Begin) != FID)1165      continue;1166 1167    unsigned EndLineNo = SM.getExpansionLineNumber(End);1168    if (EndLineNo < Lines.first || SM.getFileID(End) != FID)1169      continue;1170 1171    Bytes StartByte = SM.getExpansionColumnNumber(Begin);1172    Bytes EndByte = SM.getExpansionColumnNumber(End);1173    assert(StartByte.V != 0 && "StartByte must be valid, 0 is invalid");1174    assert(EndByte.V != 0 && "EndByte must be valid, 0 is invalid");1175    if (R.isTokenRange())1176      EndByte += Bytes(Lexer::MeasureTokenLength(End, SM, LangOpts));1177 1178    // Only a single line.1179    if (StartLineNo == EndLineNo) {1180      LineRanges.push_back({StartLineNo, StartByte.prev(), EndByte.prev()});1181      continue;1182    }1183 1184    // Start line.1185    LineRanges.push_back(1186        {StartLineNo, StartByte.prev(), std::numeric_limits<int>::max()});1187 1188    // Middle lines.1189    for (unsigned S = StartLineNo + 1; S != EndLineNo; ++S)1190      LineRanges.push_back({S, 0, std::numeric_limits<int>::max()});1191 1192    // End line.1193    LineRanges.push_back({EndLineNo, 0, EndByte.prev()});1194  }1195 1196  return LineRanges;1197}1198 1199/// Creates syntax highlighting information in form of StyleRanges.1200///1201/// The returned unique ptr has always exactly size1202/// (\p EndLineNumber - \p StartLineNumber + 1). Each SmallVector in there1203/// corresponds to syntax highlighting information in one line. In each line,1204/// the StyleRanges are non-overlapping and sorted from start to end of the1205/// line.1206static std::unique_ptr<llvm::SmallVector<TextDiagnostic::StyleRange>[]>1207highlightLines(StringRef FileData, unsigned StartLineNumber,1208               unsigned EndLineNumber, const Preprocessor *PP,1209               const LangOptions &LangOpts, bool ShowColors, FileID FID,1210               const SourceManager &SM) {1211  assert(StartLineNumber <= EndLineNumber);1212  auto SnippetRanges =1213      std::make_unique<SmallVector<TextDiagnostic::StyleRange>[]>(1214          EndLineNumber - StartLineNumber + 1);1215 1216  if (!PP || !ShowColors)1217    return SnippetRanges;1218 1219  // Might cause emission of another diagnostic.1220  if (PP->getIdentifierTable().getExternalIdentifierLookup())1221    return SnippetRanges;1222 1223  auto Buff = llvm::MemoryBuffer::getMemBuffer(FileData);1224  Lexer L{FID, *Buff, SM, LangOpts};1225  L.SetKeepWhitespaceMode(true);1226 1227  const char *FirstLineStart =1228      FileData.data() +1229      SM.getDecomposedLoc(SM.translateLineCol(FID, StartLineNumber, 1)).second;1230  if (const char *CheckPoint = PP->getCheckPoint(FID, FirstLineStart)) {1231    assert(CheckPoint >= Buff->getBufferStart() &&1232           CheckPoint <= Buff->getBufferEnd());1233    assert(CheckPoint <= FirstLineStart);1234    size_t Offset = CheckPoint - Buff->getBufferStart();1235    L.seek(Offset, /*IsAtStartOfLine=*/false);1236  }1237 1238  // Classify the given token and append it to the given vector.1239  auto appendStyle =1240      [PP, &LangOpts](SmallVector<TextDiagnostic::StyleRange> &Vec,1241                      const Token &T, unsigned Start, unsigned Length) -> void {1242    if (T.is(tok::raw_identifier)) {1243      StringRef RawIdent = T.getRawIdentifier();1244      // Special case true/false/nullptr/... literals, since they will otherwise1245      // be treated as keywords.1246      // FIXME: It would be good to have a programmatic way of getting this1247      // list.1248      if (llvm::StringSwitch<bool>(RawIdent)1249              .Case("true", true)1250              .Case("false", true)1251              .Case("nullptr", true)1252              .Case("__func__", true)1253              .Case("__objc_yes__", true)1254              .Case("__objc_no__", true)1255              .Case("__null", true)1256              .Case("__FUNCDNAME__", true)1257              .Case("__FUNCSIG__", true)1258              .Case("__FUNCTION__", true)1259              .Case("__FUNCSIG__", true)1260              .Default(false)) {1261        Vec.emplace_back(Start, Start + Length, LiteralColor);1262      } else {1263        const IdentifierInfo *II = PP->getIdentifierInfo(RawIdent);1264        assert(II);1265        if (II->isKeyword(LangOpts))1266          Vec.emplace_back(Start, Start + Length, KeywordColor);1267      }1268    } else if (tok::isLiteral(T.getKind())) {1269      Vec.emplace_back(Start, Start + Length, LiteralColor);1270    } else {1271      assert(T.is(tok::comment));1272      Vec.emplace_back(Start, Start + Length, CommentColor);1273    }1274  };1275 1276  bool Stop = false;1277  while (!Stop) {1278    Token T;1279    Stop = L.LexFromRawLexer(T);1280    if (T.is(tok::unknown))1281      continue;1282 1283    // We are only interested in identifiers, literals and comments.1284    if (!T.is(tok::raw_identifier) && !T.is(tok::comment) &&1285        !tok::isLiteral(T.getKind()))1286      continue;1287 1288    bool Invalid = false;1289    unsigned TokenEndLine = SM.getSpellingLineNumber(T.getEndLoc(), &Invalid);1290    if (Invalid || TokenEndLine < StartLineNumber)1291      continue;1292 1293    assert(TokenEndLine >= StartLineNumber);1294 1295    unsigned TokenStartLine =1296        SM.getSpellingLineNumber(T.getLocation(), &Invalid);1297    if (Invalid)1298      continue;1299    // If this happens, we're done.1300    if (TokenStartLine > EndLineNumber)1301      break;1302 1303    Bytes StartCol = SM.getSpellingColumnNumber(T.getLocation(), &Invalid) - 1;1304    if (Invalid)1305      continue;1306 1307    // Simple tokens.1308    if (TokenStartLine == TokenEndLine) {1309      SmallVector<TextDiagnostic::StyleRange> &LineRanges =1310          SnippetRanges[TokenStartLine - StartLineNumber];1311      appendStyle(LineRanges, T, StartCol.V, T.getLength());1312      continue;1313    }1314    assert((TokenEndLine - TokenStartLine) >= 1);1315 1316    // For tokens that span multiple lines (think multiline comments), we1317    // divide them into multiple StyleRanges.1318    Bytes EndCol = SM.getSpellingColumnNumber(T.getEndLoc(), &Invalid) - 1;1319    if (Invalid)1320      continue;1321 1322    std::string Spelling = Lexer::getSpelling(T, SM, LangOpts);1323 1324    unsigned L = TokenStartLine;1325    unsigned LineLength = 0;1326    for (unsigned I = 0; I <= Spelling.size(); ++I) {1327      // This line is done.1328      if (I == Spelling.size() || isVerticalWhitespace(Spelling[I])) {1329        if (L >= StartLineNumber) {1330          SmallVector<TextDiagnostic::StyleRange> &LineRanges =1331              SnippetRanges[L - StartLineNumber];1332 1333          if (L == TokenStartLine) // First line1334            appendStyle(LineRanges, T, StartCol.V, LineLength);1335          else if (L == TokenEndLine) // Last line1336            appendStyle(LineRanges, T, 0, EndCol.V);1337          else1338            appendStyle(LineRanges, T, 0, LineLength);1339        }1340 1341        ++L;1342        if (L > EndLineNumber)1343          break;1344        LineLength = 0;1345        continue;1346      }1347      ++LineLength;1348    }1349  }1350 1351  return SnippetRanges;1352}1353 1354/// Emit a code snippet and caret line.1355///1356/// This routine emits a single line's code snippet and caret line..1357///1358/// \param Loc The location for the caret.1359/// \param Ranges The underlined ranges for this code snippet.1360/// \param Hints The FixIt hints active for this diagnostic.1361void TextDiagnostic::emitSnippetAndCaret(1362    FullSourceLoc Loc, DiagnosticsEngine::Level Level,1363    SmallVectorImpl<CharSourceRange> &Ranges, ArrayRef<FixItHint> Hints) {1364  assert(Loc.isValid() && "must have a valid source location here");1365  assert(Loc.isFileID() && "must have a file location here");1366 1367  // If caret diagnostics are enabled and we have location, we want to1368  // emit the caret.  However, we only do this if the location moved1369  // from the last diagnostic, if the last diagnostic was a note that1370  // was part of a different warning or error diagnostic, or if the1371  // diagnostic has ranges.  We don't want to emit the same caret1372  // multiple times if one loc has multiple diagnostics.1373  if (!DiagOpts.ShowCarets)1374    return;1375  if (Loc == LastLoc && Ranges.empty() && Hints.empty() &&1376      (LastLevel != DiagnosticsEngine::Note || Level == LastLevel))1377    return;1378 1379  FileID FID = Loc.getFileID();1380  const SourceManager &SM = Loc.getManager();1381 1382  // Get information about the buffer it points into.1383  bool Invalid = false;1384  StringRef BufData = Loc.getBufferData(&Invalid);1385  if (Invalid)1386    return;1387  const char *BufStart = BufData.data();1388  const char *BufEnd = BufStart + BufData.size();1389 1390  unsigned CaretLineNo = Loc.getLineNumber();1391  Bytes CaretByte = Loc.getColumnNumber();1392 1393  // Arbitrarily stop showing snippets when the line is too long.1394  static const size_t MaxLineLengthToPrint = 4096;1395  if (CaretByte > MaxLineLengthToPrint)1396    return;1397 1398  // Find the set of lines to include.1399  const unsigned MaxLines = DiagOpts.SnippetLineLimit;1400  std::pair<unsigned, unsigned> Lines = {CaretLineNo, CaretLineNo};1401  unsigned DisplayLineNo = Loc.getPresumedLoc().getLine();1402  for (const auto &I : Ranges) {1403    if (auto OptionalRange = findLinesForRange(I, FID, SM))1404      Lines = maybeAddRange(Lines, *OptionalRange, MaxLines);1405 1406    DisplayLineNo =1407        std::min(DisplayLineNo, SM.getPresumedLineNumber(I.getBegin()));1408  }1409 1410  // Our line numbers look like:1411  // " [number] | "1412  // Where [number] is MaxLineNoDisplayWidth columns1413  // and the full thing is therefore MaxLineNoDisplayWidth + 4 columns.1414  unsigned MaxLineNoDisplayWidth =1415      DiagOpts.ShowLineNumbers1416          ? std::max(4u, getNumDisplayWidth(DisplayLineNo + MaxLines))1417          : 0;1418  auto indentForLineNumbers = [&] {1419    if (MaxLineNoDisplayWidth > 0)1420      OS.indent(MaxLineNoDisplayWidth + 2) << "| ";1421  };1422 1423  Columns MessageLength = DiagOpts.MessageLength;1424  // If we don't have enough columns available, just abort now.1425  if (MessageLength != 0 && MessageLength <= Columns(MaxLineNoDisplayWidth + 4))1426    return;1427 1428  // Prepare source highlighting information for the lines we're about to1429  // emit, starting from the first line.1430  std::unique_ptr<SmallVector<StyleRange>[]> SourceStyles =1431      highlightLines(BufData, Lines.first, Lines.second, PP, LangOpts,1432                     DiagOpts.ShowColors, FID, SM);1433 1434  SmallVector<LineRange> LineRanges =1435      prepareAndFilterRanges(Ranges, SM, Lines, FID, LangOpts);1436 1437  for (unsigned LineNo = Lines.first; LineNo != Lines.second + 1;1438       ++LineNo, ++DisplayLineNo) {1439    // Rewind from the current position to the start of the line.1440    const char *LineStart =1441        BufStart +1442        SM.getDecomposedLoc(SM.translateLineCol(FID, LineNo, 1)).second;1443    if (LineStart == BufEnd)1444      break;1445 1446    // Compute the line end.1447    const char *LineEnd = LineStart;1448    while (*LineEnd != '\n' && *LineEnd != '\r' && LineEnd != BufEnd)1449      ++LineEnd;1450 1451    // Arbitrarily stop showing snippets when the line is too long.1452    // FIXME: Don't print any lines in this case.1453    if (size_t(LineEnd - LineStart) > MaxLineLengthToPrint)1454      return;1455 1456    // Copy the line of code into an std::string for ease of manipulation.1457    std::string SourceLine(LineStart, LineEnd);1458    // Remove trailing null bytes.1459    while (!SourceLine.empty() && SourceLine.back() == '\0' &&1460           (LineNo != CaretLineNo ||1461            SourceLine.size() > static_cast<size_t>(CaretByte.V)))1462      SourceLine.pop_back();1463 1464    // Build the byte to column map.1465    const SourceColumnMap SourceColMap(SourceLine, DiagOpts.TabStop);1466 1467    std::string CaretLine;1468    // Highlight all of the characters covered by Ranges with ~ characters.1469    for (const auto &LR : LineRanges) {1470      if (LR.LineNo == LineNo)1471        highlightRange(LR, SourceColMap, CaretLine);1472    }1473 1474    // Next, insert the caret itself.1475    if (CaretLineNo == LineNo) {1476      Columns Col = SourceColMap.byteToContainingColumn(CaretByte.prev());1477      CaretLine.resize(1478          std::max(static_cast<size_t>(Col.V) + 1, CaretLine.size()), ' ');1479      CaretLine[Col.V] = '^';1480    }1481 1482    std::string FixItInsertionLine =1483        buildFixItInsertionLine(FID, LineNo, SourceColMap, Hints, SM, DiagOpts);1484 1485    // If the source line is too long for our terminal, select only the1486    // "interesting" source region within that line.1487    if (MessageLength != 0) {1488      Columns NonGutterColumns = MessageLength;1489      if (MaxLineNoDisplayWidth != 0)1490        NonGutterColumns -= Columns(MaxLineNoDisplayWidth + 4);1491      selectInterestingSourceRegion(SourceLine, CaretLine, FixItInsertionLine,1492                                    NonGutterColumns, SourceColMap,1493                                    SourceStyles[LineNo - Lines.first]);1494    }1495 1496    // If we are in -fdiagnostics-print-source-range-info mode, we are trying1497    // to produce easily machine parsable output.  Add a space before the1498    // source line and the caret to make it trivial to tell the main diagnostic1499    // line from what the user is intended to see.1500    if (DiagOpts.ShowSourceRanges && !SourceLine.empty()) {1501      SourceLine = ' ' + SourceLine;1502      CaretLine = ' ' + CaretLine;1503    }1504 1505    // Emit what we have computed.1506    emitSnippet(SourceLine, MaxLineNoDisplayWidth, LineNo, DisplayLineNo,1507                SourceStyles[LineNo - Lines.first]);1508 1509    if (!CaretLine.empty()) {1510      indentForLineNumbers();1511      if (DiagOpts.ShowColors)1512        OS.changeColor(CaretColor, true);1513      OS << CaretLine << '\n';1514      if (DiagOpts.ShowColors)1515        OS.resetColor();1516    }1517 1518    if (!FixItInsertionLine.empty()) {1519      indentForLineNumbers();1520      if (DiagOpts.ShowColors)1521        // Print fixit line in color1522        OS.changeColor(FixitColor, false);1523      if (DiagOpts.ShowSourceRanges)1524        OS << ' ';1525      OS << FixItInsertionLine << '\n';1526      if (DiagOpts.ShowColors)1527        OS.resetColor();1528    }1529  }1530 1531  // Print out any parseable fixit information requested by the options.1532  emitParseableFixits(Hints, SM);1533}1534 1535void TextDiagnostic::emitSnippet(StringRef SourceLine,1536                                 unsigned MaxLineNoDisplayWidth,1537                                 unsigned LineNo, unsigned DisplayLineNo,1538                                 ArrayRef<StyleRange> Styles) {1539  // Emit line number.1540  if (MaxLineNoDisplayWidth > 0) {1541    unsigned LineNoDisplayWidth = getNumDisplayWidth(DisplayLineNo);1542    OS.indent(MaxLineNoDisplayWidth - LineNoDisplayWidth + 1)1543        << DisplayLineNo << " | ";1544  }1545 1546  // Print the source line one character at a time.1547  bool PrintReversed = false;1548  std::optional<llvm::raw_ostream::Colors> CurrentColor;1549  size_t I = 0; // Bytes.1550  while (I < SourceLine.size()) {1551    auto [Str, WasPrintable] =1552        printableTextForNextCharacter(SourceLine, &I, DiagOpts.TabStop);1553 1554    // Toggle inverted colors on or off for this character.1555    if (DiagOpts.ShowColors) {1556      if (WasPrintable == PrintReversed) {1557        PrintReversed = !PrintReversed;1558        if (PrintReversed)1559          OS.reverseColor();1560        else {1561          OS.resetColor();1562          CurrentColor = std::nullopt;1563        }1564      }1565 1566      // Apply syntax highlighting information.1567      const auto *CharStyle = llvm::find_if(Styles, [I](const StyleRange &R) {1568        return (R.Start < I && R.End >= I);1569      });1570 1571      if (CharStyle != Styles.end()) {1572        if (!CurrentColor ||1573            (CurrentColor && *CurrentColor != CharStyle->Color)) {1574          OS.changeColor(CharStyle->Color);1575          CurrentColor = CharStyle->Color;1576        }1577      } else if (CurrentColor) {1578        OS.resetColor();1579        CurrentColor = std::nullopt;1580      }1581    }1582 1583    OS << Str;1584  }1585 1586  if (DiagOpts.ShowColors)1587    OS.resetColor();1588 1589  OS << '\n';1590}1591 1592void TextDiagnostic::emitParseableFixits(ArrayRef<FixItHint> Hints,1593                                         const SourceManager &SM) {1594  if (!DiagOpts.ShowParseableFixits)1595    return;1596 1597  // We follow FixItRewriter's example in not (yet) handling1598  // fix-its in macros.1599  for (const auto &H : Hints) {1600    if (H.RemoveRange.isInvalid() || H.RemoveRange.getBegin().isMacroID() ||1601        H.RemoveRange.getEnd().isMacroID())1602      return;1603  }1604 1605  for (const auto &H : Hints) {1606    SourceLocation BLoc = H.RemoveRange.getBegin();1607    SourceLocation ELoc = H.RemoveRange.getEnd();1608 1609    FileIDAndOffset BInfo = SM.getDecomposedLoc(BLoc);1610    FileIDAndOffset EInfo = SM.getDecomposedLoc(ELoc);1611 1612    // Adjust for token ranges.1613    if (H.RemoveRange.isTokenRange())1614      EInfo.second += Lexer::MeasureTokenLength(ELoc, SM, LangOpts);1615 1616    // We specifically do not do word-wrapping or tab-expansion here,1617    // because this is supposed to be easy to parse.1618    PresumedLoc PLoc = SM.getPresumedLoc(BLoc);1619    if (PLoc.isInvalid())1620      break;1621 1622    OS << "fix-it:\"";1623    OS.write_escaped(PLoc.getFilename());1624    OS << "\":{" << SM.getLineNumber(BInfo.first, BInfo.second)1625      << ':' << SM.getColumnNumber(BInfo.first, BInfo.second)1626      << '-' << SM.getLineNumber(EInfo.first, EInfo.second)1627      << ':' << SM.getColumnNumber(EInfo.first, EInfo.second)1628      << "}:\"";1629    OS.write_escaped(H.CodeToInsert);1630    OS << "\"\n";1631  }1632}1633