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