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