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