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-exception
6//
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 underline
38// source ranges. White and black are bad because of the usual
39// 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} // namespace
80
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(C: ToggleHighlight);
86 OS << Str.slice(Start: 0, End: Pos);
87 if (Pos == StringRef::npos)
88 break;
89
90 Str = Str.substr(Start: Pos + 1);
91 if (Normal)
92 OS.changeColor(Color: TemplateColor, Bold: true);
93 else {
94 OS.resetColor();
95 if (Bold)
96 OS.changeColor(Color: SavedColor, Bold: 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 range
116///
117/// This function returns a printable representation of the next item in a line
118/// of source. If the next byte begins a valid and printable character, that
119/// character is returned along with 'true'.
120///
121/// Otherwise, if the next byte begins a valid, but unprintable character, a
122/// printable, escaped representation of the character is returned, along with
123/// 'false'. Otherwise a printable, escaped representation of the next byte
124/// is returned along with 'false'.
125///
126/// \note The index is updated to be used with a subsequent call to
127/// printableTextForNextCharacter.
128///
129/// \param SourceLine The line of source
130/// \param I Pointer to byte index,
131/// \param TabStop used to expand tabs
132/// \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: *I);
144 unsigned NumSpaces = TabStop - (LineBytes % TabStop);
145 assert(0 < NumSpaces && NumSpaces <= TabStop
146 && "Invalid computation of space amt");
147 ++(*I);
148
149 SmallString<16> ExpandedTab;
150 ExpandedTab.assign(NumElts: NumSpaces, Elt: ' ');
151 return std::make_pair(x&: ExpandedTab, y: 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(c: *Begin)) {
158 ++(*I);
159 return std::make_pair(x: SmallString<16>(Begin, Begin + 1), y: true);
160 }
161 unsigned CharSize = llvm::getNumBytesForUTF8(firstByte: *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(source: Begin, sourceEnd: 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 sourceStart: &Begin, sourceEnd: End, targetStart: &CPtr, targetEnd: CPtr + 1, flags: 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: C))
182 return std::make_pair(x: SmallString<16>(OriginalBegin, End), y: true);
183
184 // Valid but not printable.
185 SmallString<16> Str("<U+>");
186 while (C) {
187 Str.insert(I: Str.begin() + 3, Elt: llvm::hexdigit(X: C % 16));
188 C /= 16;
189 }
190 while (Str.size() < 8)
191 Str.insert(I: Str.begin() + 3, Elt: llvm::hexdigit(X: 0));
192 return std::make_pair(x&: Str, y: 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(X: Byte / 16);
199 ExpandedByte[2] = llvm::hexdigit(X: Byte % 16);
200 ++(*I);
201 return std::make_pair(x&: ExpandedByte, y: 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, I: &TmpI, TabStop);
213 SourceLine.replace(pos: I, n1: 1, s: Str.c_str());
214 }
215}
216
217/// \p BytesOut:
218/// A mapping from columns to the byte of the source line that produced the
219/// 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 to
228/// display)
229///
230/// \p ColumnsOut:
231/// A mapping from the bytes
232/// of the printable representation of the line to the columns those printable
233/// 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 tab
236/// character) then the array will map that byte to the first column the
237/// tab appears at and the next value in the map will have been incremented
238/// more than once.
239///
240/// If a byte is the first in a sequence of bytes that together map to a single
241/// entity in the output, then the array will map that byte to the appropriate
242/// column while the subsequent bytes will be -1.
243///
244/// The last element in the array does not correspond to any byte in the input
245/// and instead is the number of columns needed to display the source
246///
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 to
252/// 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(N: 1u, NV: Bytes(0));
261 ColumnsOut.resize(N: 1u, NV: Columns(0));
262 return;
263 }
264
265 ColumnsOut.resize(N: SourceLine.size() + 1, NV: -1);
266
267 Columns NumColumns = 0;
268 size_t I = 0;
269 while (I < SourceLine.size()) {
270 ColumnsOut[I] = NumColumns;
271 BytesOut.resize(N: NumColumns.V + 1, NV: -1);
272 BytesOut.back() = Bytes(I);
273 auto [Str, Printable] =
274 printableTextForNextCharacter(SourceLine, I: &I, TabStop);
275 NumColumns += Columns(llvm::sys::locale::columnWidth(s: Str));
276 }
277
278 ColumnsOut.back() = NumColumns;
279 BytesOut.resize(N: NumColumns.V + 1, NV: -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, BytesOut&: ColumnToByte, ColumnsOut&: 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 -1
301 /// 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 if
316 /// the column the second or subsequent column of an expanded tab or similar
317 /// 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 namespace
349
350/// When the source code line we want to print is too long for
351/// 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(s: FixItInsertionLine);
359 Columns MaxColumns =
360 std::max(l: {Map.columns().V, CaretColumns.V, FixItColumns.V});
361 // if the number of columns is less than the desired number we're done
362 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 line
369 // correctly.
370 Columns CaretStart = 0, CaretEnd = CaretLine.size();
371 while (CaretStart != CaretEnd && isWhitespace(c: CaretLine[CaretStart.V]))
372 CaretStart = CaretStart.next();
373
374 while (CaretEnd != CaretStart && isWhitespace(c: CaretLine[CaretEnd.V]))
375 CaretEnd = CaretEnd.prev();
376
377 // caret has already been inserted into CaretLine so the above whitespace
378 // check is guaranteed to include the caret
379
380 // If we have a fix-it line, make sure the slice includes all of the
381 // fix-it information.
382 if (!FixItInsertionLine.empty()) {
383 // We can safely use the byte offset FixItStart as the column offset
384 // because the characters up until FixItStart are all ASCII whitespace
385 // characters.
386 Bytes FixItStart = 0;
387 Bytes FixItEnd = Bytes(FixItInsertionLine.size());
388 while (FixItStart != FixItEnd &&
389 isWhitespace(c: FixItInsertionLine[FixItStart.V]))
390 FixItStart = FixItStart.next();
391
392 while (FixItEnd != FixItStart &&
393 isWhitespace(c: FixItInsertionLine[FixItEnd.V - 1]))
394 FixItEnd = FixItEnd.prev();
395
396 Columns FixItStartCol = Columns(FixItStart.V);
397 Columns FixItEndCol = Columns(llvm::sys::locale::columnWidth(
398 s: FixItInsertionLine.substr(pos: 0, n: FixItEnd.V)));
399
400 CaretStart = std::min(a: FixItStartCol.V, b: CaretStart.V);
401 CaretEnd = std::max(a: FixItEndCol.V, b: CaretEnd.V);
402 }
403
404 // CaretEnd may have been set at the middle of a character
405 // If it's not at a character's first column then advance it past the current
406 // character.
407 while (CaretEnd < Map.columns() && !Map.columnToByte(N: 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 interesting
419 // parts of the caret line. While this slice is smaller than the
420 // number of columns we have, try to grow the slice to encompass
421 // more context.
422
423 Bytes SourceStart = Map.columnToByte(N: std::min(a: CaretStart.V, b: Map.columns().V));
424 Bytes SourceEnd = Map.columnToByte(N: std::min(a: CaretEnd.V, b: Map.columns().V));
425
426 Columns CaretColumnsOutsideSource =
427 CaretEnd - CaretStart -
428 (Map.byteToColumn(N: SourceEnd) - Map.byteToColumn(N: 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 ellipses
437 // and any of the caret line that extends past the source
438 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(N: SourceStart);
446
447 // Skip over any whitespace we see here; we're looking for
448 // another bit of interesting text.
449 // FIXME: Detect non-ASCII whitespace characters too.
450 while (NewStart > 0 && isWhitespace(c: SourceLine[NewStart.V]))
451 NewStart = Map.startOfPreviousColumn(N: NewStart);
452
453 // Skip over this bit of "interesting" text.
454 while (NewStart > 0) {
455 Bytes Prev = Map.startOfPreviousColumn(N: NewStart);
456 if (isWhitespace(c: SourceLine[Prev.V]))
457 break;
458 NewStart = Prev;
459 }
460
461 assert(Map.byteToColumn(NewStart).isValid());
462 Columns NewColumns =
463 Map.byteToColumn(N: SourceEnd) - Map.byteToColumn(N: NewStart);
464 if (NewColumns <= TargetColumns) {
465 SourceStart = NewStart;
466 ExpandedRegion = true;
467 }
468 }
469
470 if (SourceEnd < SourceLine.size()) {
471 Bytes NewEnd = Map.startOfNextColumn(N: SourceEnd);
472
473 // Skip over any whitespace we see here; we're looking for
474 // another bit of interesting text.
475 // FIXME: Detect non-ASCII whitespace characters too.
476 while (NewEnd < SourceLine.size() && isWhitespace(c: SourceLine[NewEnd.V]))
477 NewEnd = Map.startOfNextColumn(N: NewEnd);
478
479 // Skip over this bit of "interesting" text.
480 while (NewEnd < SourceLine.size() && isWhitespace(c: SourceLine[NewEnd.V]))
481 NewEnd = Map.startOfNextColumn(N: NewEnd);
482
483 assert(Map.byteToColumn(NewEnd).isValid());
484 Columns NewColumns =
485 Map.byteToColumn(N: NewEnd) - Map.byteToColumn(N: 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(N: SourceStart);
497 CaretEnd = Map.byteToColumn(N: SourceEnd) + CaretColumnsOutsideSource;
498
499 // [CaretStart, CaretEnd) is the slice we want. Update the various
500 // 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(N: Bytes{static_cast<int>(SourceLine.size())}) -
508 Map.byteToColumn(N: SourceEnd);
509 Columns FrontColumnsRemoved = CaretStart;
510 Columns ColumnsKept = CaretEnd - CaretStart;
511
512 // We checked up front that the line needed truncation
513 assert(FrontColumnsRemoved + ColumnsKept + BackColumnsRemoved >
514 NonGutterColumns);
515
516 // Since we've modified the SourceLine, we also need to adjust the line's
517 // highlighting information. In particular, if we've removed
518 // from the front of the line, we need to move the style ranges to the
519 // left and remove unneeded ranges.
520 // Note in particular that variables like CaretEnd are defined in the
521 // CaretLine, which only contains ASCII, while the style ranges are defined in
522 // the source line, where we have to care for the byte-index != column-index
523 // case.
524 Bytes BytesRemoved =
525 FrontColumnsRemoved > FrontEllipse.size()
526 ? (Map.columnToByte(N: FrontColumnsRemoved) - Bytes(FrontEllipse.size()))
527 : 0;
528 Bytes CodeEnd =
529 CaretEnd < Map.columns() ? Map.columnToByte(N: 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't
538 // 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 front
549 // if possible, so remove the back
550 if (BackColumnsRemoved > Columns(BackEllipse.size()))
551 SourceLine.replace(pos: SourceEnd.V, n: std::string::npos, svt: BackEllipse);
552
553 // If that's enough then we're done
554 if (FrontColumnsRemoved + ColumnsKept <= NonGutterColumns)
555 return;
556
557 // Otherwise remove the front as well
558 if (FrontColumnsRemoved > Columns(FrontEllipse.size())) {
559 SourceLine.replace(pos: 0, n: SourceStart.V, svt: FrontEllipse);
560 CaretLine.replace(pos: 0, n: CaretStart.V, svt: FrontSpace);
561 if (!FixItInsertionLine.empty())
562 FixItInsertionLine.replace(pos: 0, n: CaretStart.V, svt: FrontSpace);
563 }
564}
565
566/// Skip over whitespace in the string, starting at the given
567/// index.
568///
569/// \returns The index of the first non-whitespace character that is
570/// 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(c: Str[Idx]))
574 ++Idx;
575 return Idx;
576}
577
578/// If the given character is the start of some kind of
579/// balanced punctuation (e.g., quotes or parentheses), return the
580/// character that will terminate the punctuation.
581///
582/// \returns The ending punctuation character, if any, or the NULL
583/// 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 offset
599/// within a string.
600///
601/// \returns the index pointing one character past the end of the
602/// 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 opening
614 // punctuation, e.g., a quote or parentheses.
615 char EndPunct = findMatchingPunctuation(c: Str[Start]);
616 if (!EndPunct) {
617 // This is a normal word. Just find the first space character.
618 while (End < Length && !isWhitespace(c: 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(Elt: EndPunct);
627 while (End < Length && !PunctuationEndStack.empty()) {
628 if (Str[End] == PunctuationEndStack.back())
629 PunctuationEndStack.pop_back();
630 else if (char SubEndPunct = findMatchingPunctuation(c: Str[End]))
631 PunctuationEndStack.push_back(Elt: SubEndPunct);
632
633 ++End;
634 }
635
636 // Find the first space character after the punctuation ended.
637 while (End < Length && !isWhitespace(c: Str[End]))
638 ++End;
639
640 unsigned PunctWordLength = End - Start;
641 if (// If the word fits on this line
642 Column + PunctWordLength <= Columns ||
643 // ... or the word is "short enough" to take up the next line
644 // without too much ugly white space
645 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 a
649 // single "word". Instead, find the "word" that starts just after
650 // the punctuation and use that end-point instead. This will recurse
651 // until it finds something small enough to consider a word.
652 return findEndOfWord(Start: Start + 1, Str, Length, Column: Column + 1, Columns);
653}
654
655/// Print the given string to a stream, word-wrapping it to
656/// some number of columns in the process.
657///
658/// \param OS the stream to which the word-wrapping string will be
659/// 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 \p
663/// Str will be printed. This will be non-zero when part of the first
664/// line has already been printed.
665/// \param Bold if the current text should be bold
666/// \returns true if word-wrapping was required, or false if the
667/// 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(a: Str.find(C: '\n'), b: 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(Idx: WordStart, Str, Length);
678 if (WordStart == Length)
679 break;
680
681 // Find the end of this word.
682 WordEnd = findEndOfWord(Start: 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: Str.substr(Start: WordStart, N: WordLength),
693 Normal&: TextNormal, Bold);
694 Column += WordLength;
695 continue;
696 }
697
698 // This word does not fit on the current line, so wrap to the next
699 // line.
700 OS << '\n';
701 OS.indent(NumSpaces: WordWrapIndentation);
702 applyTemplateHighlighting(OS, Str: Str.substr(Start: WordStart, N: WordLength),
703 Normal&: 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: Str.substr(Start: Length), Normal&: 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, ShowColors: DiagOpts.ShowColors);
738 printDiagnosticMessage(OS,
739 /*IsSupplemental*/ Level == DiagnosticsEngine::Note,
740 Message, CurrentColumn: OS.getColumn() - StartOfLocationInfo,
741 Columns: DiagOpts.MessageLength, ShowColors: DiagOpts.ShowColors);
742 // We use a formatted ostream, which does its own buffering. Flush here
743 // so we keep the proper order of output.
744 OS.flush();
745}
746
747/*static*/ void
748TextDiagnostic::printDiagnosticLevel(raw_ostream &OS,
749 DiagnosticsEngine::Level Level,
750 bool ShowColors) {
751 if (ShowColors) {
752 // Print diagnostic category in bold and color
753 switch (Level) {
754 case DiagnosticsEngine::Ignored:
755 llvm_unreachable("Invalid diagnostic type");
756 case DiagnosticsEngine::Note:
757 OS.changeColor(Color: NoteColor, Bold: true);
758 break;
759 case DiagnosticsEngine::Remark:
760 OS.changeColor(Color: RemarkColor, Bold: true);
761 break;
762 case DiagnosticsEngine::Warning:
763 OS.changeColor(Color: WarningColor, Bold: true);
764 break;
765 case DiagnosticsEngine::Error:
766 OS.changeColor(Color: ErrorColor, Bold: true);
767 break;
768 case DiagnosticsEngine::Fatal:
769 OS.changeColor(Color: FatalColor, Bold: 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 visually
797 // indicate the transition from continuation notes and other output.
798 OS.changeColor(Color: SavedColor, Bold: true);
799 Bold = true;
800 }
801
802 if (Columns)
803 printWordWrapped(OS, Str: Message, Columns, Column: CurrentColumn, Bold);
804 else {
805 bool Normal = true;
806 applyTemplateHighlighting(OS, Str: 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 _WIN32
817 SmallString<4096> TmpFilename;
818#endif
819 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>/..", because
826 // paths are resolved sequentially, and, thereby, the path
827 // "<part1>/<part2>" may point to a different location. That is why
828 // we use FileManager::getCanonicalName(), which expands all indirections
829 // 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 the
832 // 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 _WIN32
837 TmpFilename = File->getName();
838 SM.getFileManager().makeAbsolutePath(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#else
843 Filename = SM.getFileManager().getCanonicalName(File: *File);
844#endif
845 }
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 for
855/// the diagnostic and printing it, as well as any include stack or source
856/// 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(Filename: FE->getName(), SM: 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(Color: SavedColor, Bold: true);
877
878 emitFilename(Filename: PLoc.getFilename(), SM: 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 one
895 if (LangOpts.MSCompatibilityVersion &&
896 !LangOpts.isCompatibleWithMSVC(MajorVersion: LangOptions::MSVC2012))
897 ColNo--;
898 } else
899 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 the
908 // space and prints 'file(4): error'.
909 OS << ')';
910 if (LangOpts.MSCompatibilityVersion &&
911 !LangOpts.isCompatibleWithMSVC(MajorVersion: 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(Loc: R.getBegin());
928 CharSourceRange ERange = SM.getExpansionRange(Loc: R.getEnd());
929 SourceLocation E = ERange.getEnd();
930
931 // If the start or end of the range is in another file, just
932 // discard it.
933 if (SM.getFileID(SpellingLoc: B) != CaretFileID || SM.getFileID(SpellingLoc: E) != CaretFileID)
934 continue;
935
936 // Add in the length of the token, so that we cover multi-char
937 // tokens.
938 unsigned TokSize = 0;
939 if (ERange.isTokenRange())
940 TokSize = Lexer::MeasureTokenLength(Loc: 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(Filename: PLoc.getFilename(), SM: Loc.getManager());
960 OS << ':' << PLoc.getLine() << ":\n";
961 } else
962 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 else
971 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 else
981 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(SpellingLoc: Begin) != FID || SM.getFileID(SpellingLoc: End) != FID)
994 return std::nullopt;
995
996 return std::make_pair(x: SM.getExpansionLineNumber(Loc: Begin),
997 y: SM.getExpansionLineNumber(Loc: End));
998}
999
1000/// Add as much of range B into range A as possible without exceeding a maximum
1001/// 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: A.first, b: B.first);
1012 unsigned Max = std::max(a: A.second, b: 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. We
1023 // attempt to expand by the same amount in both directions if B strictly
1024 // contains A.
1025
1026 // Expand downwards by up to half the available amount, then upwards as
1027 // much as possible, then downwards as much as possible.
1028 A.second = std::min(a: A.second + (Slack + 1) / 2, b: Max);
1029 Slack = MaxRange - (A.second - A.first + 1);
1030 A.first = std::max(a: Min + Slack, b: A.first) - Slack;
1031 A.second = std::min(a: A.first + MaxRange - 1, b: 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(N: StartByte);
1049
1050 // Pick the last non-whitespace column.
1051 Bytes EndByte = std::min(a: R.EndByte.V, b: Map.bytes().V);
1052 while (EndByte.V != 0 && (Map.getSourceLine()[EndByte.V - 1] == ' ' ||
1053 Map.getSourceLine()[EndByte.V - 1] == '\t'))
1054 EndByte = Map.startOfPreviousColumn(N: EndByte);
1055
1056 // If the start/end passed each other, then we are trying to highlight a
1057 // range that just exists in whitespace. That most likely means we have
1058 // 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(N: StartByte);
1065 Columns EndCol = Map.byteToContainingColumn(N: EndByte);
1066
1067 if (CaretLine.size() < static_cast<size_t>(EndCol.V))
1068 CaretLine.resize(n: EndCol.V, c: ' ');
1069
1070 std::fill(first: CaretLine.begin() + StartCol.V, last: CaretLine.begin() + EndCol.V, value: '~');
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 inserted
1088 // code contains no newlines and is on the same line as the caret.
1089 FileIDAndOffset HintLocInfo =
1090 SM.getDecomposedExpansionLoc(Loc: H.RemoveRange.getBegin());
1091 if (FID == HintLocInfo.first &&
1092 LineNo == SM.getLineNumber(FID: HintLocInfo.first, FilePos: HintLocInfo.second) &&
1093 StringRef(H.CodeToInsert).find_first_of(Chars: "\n\r") == StringRef::npos) {
1094 // Insert the new code into the line just below the code
1095 // that the user wrote.
1096 // Note: When modifying this function, be very careful about what is a
1097 // "column" (printed width, platform-dependent) and what is a
1098 // "byte offset" (SourceManager "column").
1099 Bytes HintByteOffset =
1100 Bytes(SM.getColumnNumber(FID: HintLocInfo.first, FilePos: HintLocInfo.second))
1101 .prev();
1102
1103 // The hint must start inside the source or right at the end
1104 assert(HintByteOffset < map.bytes().next());
1105 Columns HintCol = map.byteToContainingColumn(N: HintByteOffset);
1106
1107 // If we inserted a long previous hint, push this one forwards, and add
1108 // an extra space to show that this is not part of the previous
1109 // completion. This is sort of the best we can do when two hints appear
1110 // to overlap.
1111 //
1112 // Note that if this hint is located immediately after the previous
1113 // 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 have
1118 // 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(n: NewFixItLineSize.V, c: ' ');
1124
1125 std::copy(first: H.CodeToInsert.begin(), last: H.CodeToInsert.end(),
1126 result: FixItInsertionLine.end() - H.CodeToInsert.size());
1127
1128 PrevHintEndCol = HintCol + llvm::sys::locale::columnWidth(s: H.CodeToInsert);
1129 }
1130 }
1131
1132 expandTabs(SourceLine&: FixItInsertionLine, TabStop: 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 of
1146/// 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(Loc: Begin);
1164 if (StartLineNo > Lines.second || SM.getFileID(SpellingLoc: Begin) != FID)
1165 continue;
1166
1167 unsigned EndLineNo = SM.getExpansionLineNumber(Loc: End);
1168 if (EndLineNo < Lines.first || SM.getFileID(SpellingLoc: End) != FID)
1169 continue;
1170
1171 Bytes StartByte = SM.getExpansionColumnNumber(Loc: Begin);
1172 Bytes EndByte = SM.getExpansionColumnNumber(Loc: 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(Loc: End, SM, LangOpts));
1177
1178 // Only a single line.
1179 if (StartLineNo == EndLineNo) {
1180 LineRanges.push_back(Elt: {.LineNo: StartLineNo, .StartByte: StartByte.prev(), .EndByte: EndByte.prev()});
1181 continue;
1182 }
1183
1184 // Start line.
1185 LineRanges.push_back(
1186 Elt: {.LineNo: StartLineNo, .StartByte: StartByte.prev(), .EndByte: std::numeric_limits<int>::max()});
1187
1188 // Middle lines.
1189 for (unsigned S = StartLineNo + 1; S != EndLineNo; ++S)
1190 LineRanges.push_back(Elt: {.LineNo: S, .StartByte: 0, .EndByte: std::numeric_limits<int>::max()});
1191
1192 // End line.
1193 LineRanges.push_back(Elt: {.LineNo: EndLineNo, .StartByte: 0, .EndByte: 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 size
1202/// (\p EndLineNumber - \p StartLineNumber + 1). Each SmallVector in there
1203/// corresponds to syntax highlighting information in one line. In each line,
1204/// the StyleRanges are non-overlapping and sorted from start to end of the
1205/// 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 num: 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(InputData: FileData);
1224 Lexer L{FID, *Buff, SM, LangOpts};
1225 L.SetKeepWhitespaceMode(true);
1226
1227 const char *FirstLineStart =
1228 FileData.data() +
1229 SM.getDecomposedLoc(Loc: SM.translateLineCol(FID, Line: StartLineNumber, Col: 1)).second;
1230 if (const char *CheckPoint = PP->getCheckPoint(FID, Start: 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(K: tok::raw_identifier)) {
1243 StringRef RawIdent = T.getRawIdentifier();
1244 // Special case true/false/nullptr/... literals, since they will otherwise
1245 // be treated as keywords.
1246 // FIXME: It would be good to have a programmatic way of getting this
1247 // list.
1248 if (llvm::StringSwitch<bool>(RawIdent)
1249 .Case(S: "true", Value: true)
1250 .Case(S: "false", Value: true)
1251 .Case(S: "nullptr", Value: true)
1252 .Case(S: "__func__", Value: true)
1253 .Case(S: "__objc_yes__", Value: true)
1254 .Case(S: "__objc_no__", Value: true)
1255 .Case(S: "__null", Value: true)
1256 .Case(S: "__FUNCDNAME__", Value: true)
1257 .Case(S: "__FUNCSIG__", Value: true)
1258 .Case(S: "__FUNCTION__", Value: true)
1259 .Case(S: "__FUNCSIG__", Value: true)
1260 .Default(Value: false)) {
1261 Vec.emplace_back(Args&: Start, Args: Start + Length, Args: LiteralColor);
1262 } else {
1263 const IdentifierInfo *II = PP->getIdentifierInfo(Name: RawIdent);
1264 assert(II);
1265 if (II->isKeyword(LangOpts))
1266 Vec.emplace_back(Args&: Start, Args: Start + Length, Args: KeywordColor);
1267 }
1268 } else if (tok::isLiteral(K: T.getKind())) {
1269 Vec.emplace_back(Args&: Start, Args: Start + Length, Args: LiteralColor);
1270 } else {
1271 assert(T.is(tok::comment));
1272 Vec.emplace_back(Args&: Start, Args: Start + Length, Args: CommentColor);
1273 }
1274 };
1275
1276 bool Stop = false;
1277 while (!Stop) {
1278 Token T;
1279 Stop = L.LexFromRawLexer(Result&: T);
1280 if (T.is(K: tok::unknown))
1281 continue;
1282
1283 // We are only interested in identifiers, literals and comments.
1284 if (!T.is(K: tok::raw_identifier) && !T.is(K: tok::comment) &&
1285 !tok::isLiteral(K: T.getKind()))
1286 continue;
1287
1288 bool Invalid = false;
1289 unsigned TokenEndLine = SM.getSpellingLineNumber(Loc: T.getEndLoc(), Invalid: &Invalid);
1290 if (Invalid || TokenEndLine < StartLineNumber)
1291 continue;
1292
1293 assert(TokenEndLine >= StartLineNumber);
1294
1295 unsigned TokenStartLine =
1296 SM.getSpellingLineNumber(Loc: T.getLocation(), Invalid: &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(Loc: T.getLocation(), Invalid: &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), we
1317 // divide them into multiple StyleRanges.
1318 Bytes EndCol = SM.getSpellingColumnNumber(Loc: T.getEndLoc(), Invalid: &Invalid) - 1;
1319 if (Invalid)
1320 continue;
1321
1322 std::string Spelling = Lexer::getSpelling(Tok: T, SourceMgr: 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(c: Spelling[I])) {
1329 if (L >= StartLineNumber) {
1330 SmallVector<TextDiagnostic::StyleRange> &LineRanges =
1331 SnippetRanges[L - StartLineNumber];
1332
1333 if (L == TokenStartLine) // First line
1334 appendStyle(LineRanges, T, StartCol.V, LineLength);
1335 else if (L == TokenEndLine) // Last line
1336 appendStyle(LineRanges, T, 0, EndCol.V);
1337 else
1338 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 to
1368 // emit the caret. However, we only do this if the location moved
1369 // from the last diagnostic, if the last diagnostic was a note that
1370 // was part of a different warning or error diagnostic, or if the
1371 // diagnostic has ranges. We don't want to emit the same caret
1372 // 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: &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(R: I, FID, SM))
1404 Lines = maybeAddRange(A: Lines, B: *OptionalRange, MaxRange: MaxLines);
1405
1406 DisplayLineNo =
1407 std::min(a: DisplayLineNo, b: SM.getPresumedLineNumber(Loc: I.getBegin()));
1408 }
1409
1410 // Our line numbers look like:
1411 // " [number] | "
1412 // Where [number] is MaxLineNoDisplayWidth columns
1413 // and the full thing is therefore MaxLineNoDisplayWidth + 4 columns.
1414 unsigned MaxLineNoDisplayWidth =
1415 DiagOpts.ShowLineNumbers
1416 ? std::max(a: 4u, b: getNumDisplayWidth(N: DisplayLineNo + MaxLines))
1417 : 0;
1418 auto indentForLineNumbers = [&] {
1419 if (MaxLineNoDisplayWidth > 0)
1420 OS.indent(NumSpaces: 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 to
1429 // emit, starting from the first line.
1430 std::unique_ptr<SmallVector<StyleRange>[]> SourceStyles =
1431 highlightLines(FileData: BufData, StartLineNumber: Lines.first, EndLineNumber: Lines.second, PP, LangOpts,
1432 ShowColors: 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(Loc: SM.translateLineCol(FID, Line: LineNo, Col: 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(R: LR, Map: SourceColMap, CaretLine);
1472 }
1473
1474 // Next, insert the caret itself.
1475 if (CaretLineNo == LineNo) {
1476 Columns Col = SourceColMap.byteToContainingColumn(N: CaretByte.prev());
1477 CaretLine.resize(
1478 n: std::max(a: static_cast<size_t>(Col.V) + 1, b: CaretLine.size()), c: ' ');
1479 CaretLine[Col.V] = '^';
1480 }
1481
1482 std::string FixItInsertionLine =
1483 buildFixItInsertionLine(FID, LineNo, map: SourceColMap, Hints, SM, DiagOpts);
1484
1485 // If the source line is too long for our terminal, select only the
1486 // "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, Map: SourceColMap,
1493 Styles&: SourceStyles[LineNo - Lines.first]);
1494 }
1495
1496 // If we are in -fdiagnostics-print-source-range-info mode, we are trying
1497 // to produce easily machine parsable output. Add a space before the
1498 // source line and the caret to make it trivial to tell the main diagnostic
1499 // 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 Styles: SourceStyles[LineNo - Lines.first]);
1508
1509 if (!CaretLine.empty()) {
1510 indentForLineNumbers();
1511 if (DiagOpts.ShowColors)
1512 OS.changeColor(Color: CaretColor, Bold: 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 color
1522 OS.changeColor(Color: FixitColor, Bold: 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(N: DisplayLineNo);
1542 OS.indent(NumSpaces: 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: &I, TabStop: 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(Range&: Styles, P: [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(Color: 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) handling
1598 // 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(Loc: BLoc);
1610 FileIDAndOffset EInfo = SM.getDecomposedLoc(Loc: ELoc);
1611
1612 // Adjust for token ranges.
1613 if (H.RemoveRange.isTokenRange())
1614 EInfo.second += Lexer::MeasureTokenLength(Loc: 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(Loc: BLoc);
1619 if (PLoc.isInvalid())
1620 break;
1621
1622 OS << "fix-it:\"";
1623 OS.write_escaped(Str: PLoc.getFilename());
1624 OS << "\":{" << SM.getLineNumber(FID: BInfo.first, FilePos: BInfo.second)
1625 << ':' << SM.getColumnNumber(FID: BInfo.first, FilePos: BInfo.second)
1626 << '-' << SM.getLineNumber(FID: EInfo.first, FilePos: EInfo.second)
1627 << ':' << SM.getColumnNumber(FID: EInfo.first, FilePos: EInfo.second)
1628 << "}:\"";
1629 OS.write_escaped(Str: H.CodeToInsert);
1630 OS << "\"\n";
1631 }
1632}
1633