From e9a3b76ba589a8a884e978273beaed0d97cf9861 Mon Sep 17 00:00:00 2001 From: Seth Cantrell Date: Tue, 17 Apr 2012 20:06:03 +0000 Subject: [PATCH] Nicer display of unprintable source, and fix caret display for non-ascii text Unprintable source in diagnostics is transformed to a printable form and then displayed with reversed colors if possible. Unprintable characters are displayed as while bytes that do not represent valid characters are shown as . Column adjustments to diagnostic carets, highlighted ranges, and fixups are made both for characters escaped as above and for characters which are printable but take up more than a single column. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@154946 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Basic/ConvertUTF.h | 2 +- include/clang/Frontend/TextDiagnostic.h | 11 +- lib/Frontend/TextDiagnostic.cpp | 593 +++++++++++++++++------- test/Misc/message-length.c | 3 +- test/Misc/unprintable.c | 16 + test/Misc/wrong-encoding.c | 16 + 6 files changed, 476 insertions(+), 165 deletions(-) create mode 100644 test/Misc/unprintable.c create mode 100644 test/Misc/wrong-encoding.c diff --git a/include/clang/Basic/ConvertUTF.h b/include/clang/Basic/ConvertUTF.h index ec6b973e6a..7fb5874027 100644 --- a/include/clang/Basic/ConvertUTF.h +++ b/include/clang/Basic/ConvertUTF.h @@ -151,9 +151,9 @@ ConversionResult ConvertUTF16toUTF32 ( ConversionResult ConvertUTF32toUTF16 ( const UTF32** sourceStart, const UTF32* sourceEnd, UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); +#endif Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); -#endif Boolean isLegalUTF8String(const UTF8 *source, const UTF8 *sourceEnd); diff --git a/include/clang/Frontend/TextDiagnostic.h b/include/clang/Frontend/TextDiagnostic.h index 519d3b61ce..314003bce2 100644 --- a/include/clang/Frontend/TextDiagnostic.h +++ b/include/clang/Frontend/TextDiagnostic.h @@ -18,6 +18,8 @@ #include "clang/Frontend/DiagnosticRenderer.h" +struct SourceColumnMap; + namespace clang { /// \brief Class to encapsulate the logic for formatting and printing a textual @@ -103,15 +105,16 @@ private: SmallVectorImpl& Ranges, ArrayRef Hints); + void emitSnippet(StringRef SourceLine); + void highlightRange(const CharSourceRange &R, unsigned LineNo, FileID FID, - const std::string &SourceLine, + const SourceColumnMap &map, std::string &CaretLine); + std::string buildFixItInsertionLine(unsigned LineNo, - const char *LineStart, - const char *LineEnd, + const SourceColumnMap &map, ArrayRef Hints); - void expandTabs(std::string &SourceLine, std::string &CaretLine); void emitParseableFixits(ArrayRef Hints); }; diff --git a/lib/Frontend/TextDiagnostic.cpp b/lib/Frontend/TextDiagnostic.cpp index 9f5dcb4838..54b2f37e2b 100644 --- a/lib/Frontend/TextDiagnostic.cpp +++ b/lib/Frontend/TextDiagnostic.cpp @@ -10,13 +10,17 @@ #include "clang/Frontend/TextDiagnostic.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" +#include "clang/Basic/ConvertUTF.h" #include "clang/Frontend/DiagnosticOptions.h" #include "clang/Lex/Lexer.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Locale.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" #include + using namespace clang; static const enum raw_ostream::Colors noteColor = @@ -36,23 +40,268 @@ static const enum raw_ostream::Colors savedColor = /// \brief Number of spaces to indent when word-wrapping. const unsigned WordWrapIndentation = 6; +int bytesSincePreviousTabOrLineBegin(StringRef SourceLine, size_t i) { + int bytes = 0; + while (0,bool> +printableTextForNextCharacter(StringRef SourceLine, size_t *i, + unsigned TabStop) { + assert(i && "i must not be null"); + assert(*i expandedTab; + expandedTab.assign(NumSpaces, ' '); + return std::make_pair(expandedTab, true); + } + + // FIXME: this data is copied from the private implementation of ConvertUTF.h + static const char trailingBytesForUTF8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 + }; + + unsigned char const *begin, *end; + begin = reinterpret_cast(&*(SourceLine.begin() + *i)); + end = begin + SourceLine.size(); + + if (isLegalUTF8Sequence(begin, end)) { + UTF32 c; + UTF32 *cptr = &c; + unsigned char const *original_begin = begin; + char trailingBytes = trailingBytesForUTF8[(unsigned char)SourceLine[*i]]; + unsigned char const *cp_end = begin+trailingBytes+1; + + ConversionResult res = ConvertUTF8toUTF32(&begin, cp_end, &cptr, cptr+1, + strictConversion); + assert(conversionOK==res); + assert(0 < begin-original_begin + && "we must be further along in the string now"); + *i += begin-original_begin; + + if (!llvm::sys::locale::isPrint(c)) { + // If next character is valid UTF-8, but not printable + SmallString<16> expandedCP(""); + while (c) { + expandedCP.insert(expandedCP.begin()+3, llvm::hexdigit(c%16)); + c/=16; + } + while (expandedCP.size() < 8) + expandedCP.insert(expandedCP.begin()+3, llvm::hexdigit(0)); + return std::make_pair(expandedCP, false); + } + + // If next character is valid UTF-8, and printable + return std::make_pair(SmallString<16>(original_begin, cp_end), true); + + } + + // If next byte is not valid UTF-8 (and therefore not printable) + SmallString<16> expandedByte(""); + unsigned char byte = SourceLine[*i]; + expandedByte[1] = llvm::hexdigit(byte / 16); + expandedByte[2] = llvm::hexdigit(byte % 16); + ++(*i); + return std::make_pair(expandedByte, false); +} + +void expandTabs(std::string &SourceLine, unsigned TabStop) { + size_t i = SourceLine.size(); + while (i>0) { + i--; + if (SourceLine[i]!='\t') + continue; + size_t tmp_i = i; + std::pair,bool> res + = printableTextForNextCharacter(SourceLine, &tmp_i, TabStop); + SourceLine.replace(i, 1, res.first.c_str()); + } +} + +/// This function takes a raw source line and produces a mapping from the bytes +/// of the printable representation of the line to the columns those printable +/// characters will appear at (numbering the first column as 0). +/// +/// If a byte 'i' corresponds to muliple columns (e.g. the byte contains a tab +/// character) then the the array will map that byte to the first column the +/// tab appears at and the next value in the map will have been incremented +/// more than once. +/// +/// If a byte is the first in a sequence of bytes that together map to a single +/// entity in the output, then the array will map that byte to the appropriate +/// column while the subsequent bytes will be -1. +/// +/// The last element in the array does not correspond to any byte in the input +/// and instead is the number of columns needed to display the source +/// +/// example: (given a tabstop of 8) +/// +/// "a \t \u3042" -> {0,1,2,8,9,-1,-1,11} +/// +/// (\u3042 is represented in UTF-8 by three bytes and takes two columns to +/// display) +void byteToColumn(StringRef SourceLine, unsigned TabStop, + SmallVectorImpl &out) { + out.clear(); + + if (SourceLine.empty()) { + out.resize(1u,0); + return; + } + + out.resize(SourceLine.size()+1, -1); + + int columns = 0; + size_t i = 0; + while (i,bool> res + = printableTextForNextCharacter(SourceLine, &i, TabStop); + columns += llvm::sys::locale::columnWidth(res.first); + } + out.back() = columns; +} + +/// This function takes a raw source line and produces a mapping from columns +/// to the byte of the source line that produced the character displaying at +/// that column. This is the inverse of the mapping produced by byteToColumn() +/// +/// The last element in the array is the number of bytes in the source string +/// +/// example: (given a tabstop of 8) +/// +/// "a \t \u3042" -> {0,1,2,-1,-1,-1,-1,-1,3,4,-1,7} +/// +/// (\u3042 is represented in UTF-8 by three bytes and takes two columns to +/// display) +void columnToByte(StringRef SourceLine, unsigned TabStop, + SmallVectorImpl &out) { + out.clear(); + + if (SourceLine.empty()) { + out.resize(1u, 0); + return; + } + + int columns = 0; + size_t i = 0; + while (i,bool> res + = printableTextForNextCharacter(SourceLine, &i, TabStop); + columns += llvm::sys::locale::columnWidth(res.first); + } + out.resize(columns+1, -1); + out.back() = i; +} + +struct SourceColumnMap { + SourceColumnMap(StringRef SourceLine, unsigned TabStop) + : m_SourceLine(SourceLine) { + + ::byteToColumn(SourceLine, TabStop, m_byteToColumn); + ::columnToByte(SourceLine, TabStop, m_columnToByte); + + assert(m_byteToColumn.size()==SourceLine.size()+1); + assert(0 < m_byteToColumn.size() && 0 < m_columnToByte.size()); + assert(m_byteToColumn.size() + == static_cast(m_columnToByte.back()+1)); + assert(static_cast(m_byteToColumn.back()+1) + == m_columnToByte.size()); + } + int columns() const { return m_byteToColumn.back(); } + int bytes() const { return m_columnToByte.back(); } + int byteToColumn(int n) const { + assert(0<=n && n(m_byteToColumn.size())); + return m_byteToColumn[n]; + } + int columnToByte(int n) const { + assert(0<=n && n(m_columnToByte.size())); + return m_columnToByte[n]; + } + StringRef getSourceLine() const { + return m_SourceLine; + } + +private: + const std::string m_SourceLine; + SmallVector m_byteToColumn; + SmallVector m_columnToByte; +}; + +// used in assert in selectInterestingSourceRegion() +namespace { +struct char_out_of_range { + const char lower,upper; + char_out_of_range(char lower, char upper) : + lower(lower), upper(upper) {} + bool operator()(char c) { return c < lower || upper < c; } +}; +} + /// \brief When the source code line we want to print is too long for /// the terminal, select the "interesting" region. static void selectInterestingSourceRegion(std::string &SourceLine, std::string &CaretLine, std::string &FixItInsertionLine, - unsigned EndOfCaretToken, - unsigned Columns) { - unsigned MaxSize = std::max(SourceLine.size(), - std::max(CaretLine.size(), - FixItInsertionLine.size())); - if (MaxSize > SourceLine.size()) - SourceLine.resize(MaxSize, ' '); - if (MaxSize > CaretLine.size()) - CaretLine.resize(MaxSize, ' '); - if (!FixItInsertionLine.empty() && MaxSize > FixItInsertionLine.size()) - FixItInsertionLine.resize(MaxSize, ' '); - + unsigned Columns, + const SourceColumnMap &map) { + unsigned MaxColumns = std::max(map.columns(), + std::max(CaretLine.size(), + FixItInsertionLine.size())); + // if the number of columns is less than the desired number we're done + if (MaxColumns <= Columns) + return; + + // no special characters allowed in CaretLine or FixItInsertionLine + assert(CaretLine.end() == + std::find_if(CaretLine.begin(), CaretLine.end(), + char_out_of_range(' ','~'))); + assert(FixItInsertionLine.end() == + std::find_if(FixItInsertionLine.begin(), FixItInsertionLine.end(), + char_out_of_range(' ','~'))); + // Find the slice that we need to display the full caret line // correctly. unsigned CaretStart = 0, CaretEnd = CaretLine.size(); @@ -64,10 +313,8 @@ static void selectInterestingSourceRegion(std::string &SourceLine, if (!isspace(CaretLine[CaretEnd - 1])) break; - // Make sure we don't chop the string shorter than the caret token - // itself. - if (CaretEnd < EndOfCaretToken) - CaretEnd = EndOfCaretToken; + // caret has already been inserted into CaretLine so the above whitespace + // check is guaranteed to include the caret // If we have a fix-it line, make sure the slice includes all of the // fix-it information. @@ -81,10 +328,8 @@ static void selectInterestingSourceRegion(std::string &SourceLine, if (!isspace(FixItInsertionLine[FixItEnd - 1])) break; - if (FixItStart < CaretStart) - CaretStart = FixItStart; - if (FixItEnd > CaretEnd) - CaretEnd = FixItEnd; + CaretStart = std::min(FixItStart, CaretStart); + CaretEnd = std::max(FixItEnd, CaretEnd); } // CaretLine[CaretStart, CaretEnd) contains all of the interesting @@ -92,62 +337,72 @@ static void selectInterestingSourceRegion(std::string &SourceLine, // number of columns we have, try to grow the slice to encompass // more context. - // If the end of the interesting region comes before we run out of - // space in the terminal, start at the beginning of the line. - if (Columns > 3 && CaretEnd < Columns - 3) - CaretStart = 0; + unsigned SourceStart = map.columnToByte(std::min(CaretStart, + map.columns())); + unsigned SourceEnd = map.columnToByte(std::min(CaretEnd, + map.columns())); + + unsigned CaretColumnsOutsideSource = CaretEnd-CaretStart + - (map.byteToColumn(SourceEnd)-map.byteToColumn(SourceStart)); + + char const *front_ellipse = " ..."; + char const *front_space = " "; + char const *back_ellipse = "..."; + unsigned ellipses_space = strlen(front_ellipse) + strlen(back_ellipse); unsigned TargetColumns = Columns; - if (TargetColumns > 8) - TargetColumns -= 8; // Give us extra room for the ellipses. - unsigned SourceLength = SourceLine.size(); - while ((CaretEnd - CaretStart) < TargetColumns) { + // Give us extra room for the ellipses + // and any of the caret line that extends past the source + if (TargetColumns > ellipses_space+CaretColumnsOutsideSource) + TargetColumns -= ellipses_space+CaretColumnsOutsideSource; + + while (SourceStart>0 || SourceEnd 1) { - unsigned NewStart = CaretStart - 1; + + if (SourceStart>0) { + unsigned NewStart = SourceStart-1; // Skip over any whitespace we see here; we're looking for // another bit of interesting text. - while (NewStart && isspace(SourceLine[NewStart])) + while (NewStart && + (map.byteToColumn(NewStart)==-1 || isspace(SourceLine[NewStart]))) --NewStart; // Skip over this bit of "interesting" text. - while (NewStart && !isspace(SourceLine[NewStart])) + while (NewStart && + (map.byteToColumn(NewStart)!=-1 && !isspace(SourceLine[NewStart]))) --NewStart; // Move up to the non-whitespace character we just saw. if (NewStart) ++NewStart; - // If we're still within our limit, update the starting - // position within the source/caret line. - if (CaretEnd - NewStart <= TargetColumns) { - CaretStart = NewStart; + unsigned NewColumns = map.byteToColumn(SourceEnd) - + map.byteToColumn(NewStart); + if (NewColumns <= TargetColumns) { + SourceStart = NewStart; ExpandedRegion = true; } } - // Move the end of the interesting region right until we've - // pulled in something else interesting. - if (CaretEnd != SourceLength) { - assert(CaretEnd < SourceLength && "Unexpected caret position!"); - unsigned NewEnd = CaretEnd; + if (SourceEnd CaretEnd) - FixItInsertionLine.erase(CaretEnd, std::string::npos); - - if (CaretStart > 2) { - SourceLine.replace(0, CaretStart, " ..."); - CaretLine.replace(0, CaretStart, " "); - if (FixItInsertionLine.size() >= CaretStart) - FixItInsertionLine.replace(0, CaretStart, " "); + + assert(CaretStart!=(unsigned)-1 && CaretEnd!=(unsigned)-1 && + SourceStart!=(unsigned)-1 && SourceEnd!=(unsigned)-1); + assert(SourceStart <= SourceEnd); + assert(CaretStart <= CaretEnd); + + unsigned BackColumnsRemoved + = map.byteToColumn(SourceLine.size())-map.byteToColumn(SourceEnd); + unsigned FrontColumnsRemoved = CaretStart; + unsigned ColumnsKept = CaretEnd-CaretStart; + + // We checked up front that the line needed truncation + assert(FrontColumnsRemoved+ColumnsKept+BackColumnsRemoved > Columns); + + // The line needs some trunctiona, and we'd prefer to keep the front + // if possible, so remove the back + if (BackColumnsRemoved) + SourceLine.replace(SourceEnd, std::string::npos, back_ellipse); + + // If that's enough then we're done + if (FrontColumnsRemoved+ColumnsKept <= Columns) + return; + + // Otherwise remove the front as well + if (FrontColumnsRemoved) { + SourceLine.replace(0, SourceStart, front_ellipse); + CaretLine.replace(0, CaretStart, front_space); + if (!FixItInsertionLine.empty()) + FixItInsertionLine.replace(0, CaretStart, front_space); } } @@ -596,19 +871,30 @@ void TextDiagnostic::emitSnippetAndCaret( // length as the line of source code. std::string CaretLine(LineEnd-LineStart, ' '); + const SourceColumnMap sourceColMap(SourceLine, DiagOpts.TabStop); + // Highlight all of the characters covered by Ranges with ~ characters. for (SmallVectorImpl::iterator I = Ranges.begin(), E = Ranges.end(); I != E; ++I) - highlightRange(*I, LineNo, FID, SourceLine, CaretLine); + highlightRange(*I, LineNo, FID, sourceColMap, CaretLine); // Next, insert the caret itself. - if (ColNo-1 < CaretLine.size()) - CaretLine[ColNo-1] = '^'; - else - CaretLine.push_back('^'); + ColNo = sourceColMap.byteToColumn(ColNo-1); + if (CaretLine.size() Columns) - selectInterestingSourceRegion(SourceLine, CaretLine, FixItInsertionLine, - CaretEndColNo, Columns); - // Finally, remove any blank spaces from the end of CaretLine. while (CaretLine[CaretLine.size()-1] == ' ') CaretLine.erase(CaretLine.end()-1); // Emit what we have computed. - OS << SourceLine << '\n'; + emitSnippet(SourceLine); if (DiagOpts.ShowColors) OS.changeColor(caretColor, true); @@ -658,13 +933,49 @@ void TextDiagnostic::emitSnippetAndCaret( emitParseableFixits(Hints); } +void TextDiagnostic::emitSnippet(StringRef line) +{ + if (line.empty()) + return; + + size_t i = 0; + + std::string to_print; + bool print_reversed = false; + + while (i,bool> res + = printableTextForNextCharacter(line, &i, DiagOpts.TabStop); + bool was_printable = res.second; + + if (DiagOpts.ShowColors + && was_printable==print_reversed) { + if (print_reversed) + OS.reverseColor(); + OS << to_print; + to_print.clear(); + if (DiagOpts.ShowColors) + OS.resetColor(); + } + + print_reversed = !was_printable; + to_print += res.first.str(); + } + + if (print_reversed && DiagOpts.ShowColors) + OS.reverseColor(); + OS << to_print; + if (print_reversed && DiagOpts.ShowColors) + OS.resetColor(); + + OS << '\n'; +} + /// \brief Highlight a SourceRange (with ~'s) for any characters on LineNo. void TextDiagnostic::highlightRange(const CharSourceRange &R, unsigned LineNo, FileID FID, - const std::string &SourceLine, + const SourceColumnMap &map, std::string &CaretLine) { - assert(CaretLine.size() == SourceLine.size() && - "Expect a correspondence between source and caret line!"); if (!R.isValid()) return; SourceLocation Begin = SM.getExpansionLoc(R.getBegin()); @@ -714,15 +1025,17 @@ void TextDiagnostic::highlightRange(const CharSourceRange &R, // Check that a token range does not highlight only whitespace. if (R.isTokenRange()) { // Pick the first non-whitespace column. - while (StartColNo < SourceLine.size() && - (SourceLine[StartColNo] == ' ' || SourceLine[StartColNo] == '\t')) + while (StartColNo < map.getSourceLine().size() && + (map.getSourceLine()[StartColNo] == ' ' || + map.getSourceLine()[StartColNo] == '\t')) ++StartColNo; // Pick the last non-whitespace column. - if (EndColNo > SourceLine.size()) - EndColNo = SourceLine.size(); + if (EndColNo > map.getSourceLine().size()) + EndColNo = map.getSourceLine().size(); while (EndColNo-1 && - (SourceLine[EndColNo-1] == ' ' || SourceLine[EndColNo-1] == '\t')) + (map.getSourceLine()[EndColNo-1] == ' ' || + map.getSourceLine()[EndColNo-1] == '\t')) --EndColNo; // If the start/end passed each other, then we are trying to highlight a @@ -732,14 +1045,20 @@ void TextDiagnostic::highlightRange(const CharSourceRange &R, } // Fill the range with ~'s. - for (unsigned i = StartColNo; i < EndColNo; ++i) - CaretLine[i] = '~'; + StartColNo = map.byteToColumn(StartColNo); + EndColNo = map.byteToColumn(EndColNo); + + assert(StartColNo <= EndColNo && "Invalid range!"); + if (CaretLine.size() < EndColNo) + CaretLine.resize(EndColNo,' '); + std::fill(CaretLine.begin()+StartColNo,CaretLine.begin()+EndColNo,'~'); } -std::string TextDiagnostic::buildFixItInsertionLine(unsigned LineNo, - const char *LineStart, - const char *LineEnd, - ArrayRef Hints) { +std::string TextDiagnostic::buildFixItInsertionLine( + unsigned LineNo, + const SourceColumnMap &map, + ArrayRef Hints) { + std::string FixItInsertionLine; if (Hints.empty() || !DiagOpts.ShowFixits) return FixItInsertionLine; @@ -755,13 +1074,32 @@ std::string TextDiagnostic::buildFixItInsertionLine(unsigned LineNo, // Insert the new code into the line just below the code // that the user wrote. unsigned HintColNo - = SM.getColumnNumber(HintLocInfo.first, HintLocInfo.second); + = SM.getColumnNumber(HintLocInfo.first, HintLocInfo.second) - 1; + // hint must start inside the source or right at the end + assert(HintColNo(map.bytes())+1); + HintColNo = map.byteToColumn(HintColNo); + + // FIXME: if the fixit includes tabs or other characters that do not + // take up a single column per byte when displayed then + // I->CodeToInsert.size() is not a column number and we're mixing + // units (columns + bytes). We should get printable versions + // of each fixit before using them. unsigned LastColumnModified - = HintColNo - 1 + I->CodeToInsert.size(); + = HintColNo + I->CodeToInsert.size(); + + if (LastColumnModified > static_cast(map.bytes())) { + unsigned LastExistingColumn = map.byteToColumn(map.bytes()); + unsigned AddedColumns = LastColumnModified-LastExistingColumn; + LastColumnModified = LastExistingColumn + AddedColumns; + } else { + LastColumnModified = map.byteToColumn(LastColumnModified); + } + if (LastColumnModified > FixItInsertionLine.size()) FixItInsertionLine.resize(LastColumnModified, ' '); + assert(HintColNo+I->CodeToInsert.size() <= FixItInsertionLine.size()); std::copy(I->CodeToInsert.begin(), I->CodeToInsert.end(), - FixItInsertionLine.begin() + HintColNo - 1); + FixItInsertionLine.begin() + HintColNo); } else { FixItInsertionLine.clear(); break; @@ -769,72 +1107,11 @@ std::string TextDiagnostic::buildFixItInsertionLine(unsigned LineNo, } } - if (FixItInsertionLine.empty()) - return FixItInsertionLine; - - // Now that we have the entire fixit line, expand the tabs in it. - // Since we don't want to insert spaces in the middle of a word, - // find each word and the column it should line up with and insert - // spaces until they match. - unsigned FixItPos = 0; - unsigned LinePos = 0; - unsigned TabExpandedCol = 0; - unsigned LineLength = LineEnd - LineStart; - - while (FixItPos < FixItInsertionLine.size() && LinePos < LineLength) { - // Find the next word in the FixIt line. - while (FixItPos < FixItInsertionLine.size() && - FixItInsertionLine[FixItPos] == ' ') - ++FixItPos; - unsigned CharDistance = FixItPos - TabExpandedCol; - - // Walk forward in the source line, keeping track of - // the tab-expanded column. - for (unsigned I = 0; I < CharDistance; ++I, ++LinePos) - if (LinePos >= LineLength || LineStart[LinePos] != '\t') - ++TabExpandedCol; - else - TabExpandedCol = - (TabExpandedCol/DiagOpts.TabStop + 1) * DiagOpts.TabStop; - - // Adjust the fixit line to match this column. - FixItInsertionLine.insert(FixItPos, TabExpandedCol-FixItPos, ' '); - FixItPos = TabExpandedCol; - - // Walk to the end of the word. - while (FixItPos < FixItInsertionLine.size() && - FixItInsertionLine[FixItPos] != ' ') - ++FixItPos; - } + expandTabs(FixItInsertionLine, DiagOpts.TabStop); return FixItInsertionLine; } -void TextDiagnostic::expandTabs(std::string &SourceLine, - std::string &CaretLine) { - // Scan the source line, looking for tabs. If we find any, manually expand - // them to spaces and update the CaretLine to match. - for (unsigned i = 0; i != SourceLine.size(); ++i) { - if (SourceLine[i] != '\t') continue; - - // Replace this tab with at least one space. - SourceLine[i] = ' '; - - // Compute the number of spaces we need to insert. - unsigned TabStop = DiagOpts.TabStop; - assert(0 < TabStop && TabStop <= DiagnosticOptions::MaxTabStop && - "Invalid -ftabstop value"); - unsigned NumSpaces = ((i+TabStop)/TabStop * TabStop) - (i+1); - assert(NumSpaces < TabStop && "Invalid computation of space amt"); - - // Insert spaces into the SourceLine. - SourceLine.insert(i+1, NumSpaces, ' '); - - // Insert spaces or ~'s into CaretLine. - CaretLine.insert(i+1, NumSpaces, CaretLine[i] == '~' ? '~' : ' '); - } -} - void TextDiagnostic::emitParseableFixits(ArrayRef Hints) { if (!DiagOpts.ShowParseableFixits) return; diff --git a/test/Misc/message-length.c b/test/Misc/message-length.c index 3e69b6a206..a6f4f44e6b 100644 --- a/test/Misc/message-length.c +++ b/test/Misc/message-length.c @@ -27,9 +27,8 @@ void a_very_long_line(int *ip, float *FloatPointer) { #pragma STDC CX_LIMITED_RANGE // some long comment text and a brace, eh {} - // CHECK: FILE:23:78 -// CHECK: {{^ ...// some long comment text and a brace, eh {} }} +// CHECK: {{^ ...// some long comment text and a brace, eh {}}} struct A { int x; }; void h(struct A *a) { diff --git a/test/Misc/unprintable.c b/test/Misc/unprintable.c new file mode 100644 index 0000000000..860503e63c --- /dev/null +++ b/test/Misc/unprintable.c @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 %s 2>&1 | FileCheck -strict-whitespace %s + +int main() { + int i; + if((i==/*👿*/1)); + +// CHECK: {{^ if\(\(i==/\*\*/1\)\);}} + +// CHECK: {{^ ~\^~~~~~~~~~~~~~~~}} +// CHECK: {{^ ~ \^ ~}} + + /* 👿 */ "👿berhund"; + +// CHECK: {{^ /\* \*/ "berhund";}} +// CHECK: {{^ \^~~~~~~~~~~~~~~~~~}} +} \ No newline at end of file diff --git a/test/Misc/wrong-encoding.c b/test/Misc/wrong-encoding.c new file mode 100644 index 0000000000..bd1cf3dc02 --- /dev/null +++ b/test/Misc/wrong-encoding.c @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -fsyntax-only %s 2>&1 | FileCheck -strict-whitespace %s + +void foo() { + + "§Ã"; // ø +// CHECK: {{^ ""; // }} +// CHECK: {{^ \^}} + + /* þ« */ const char *d = "¥"; + +// CHECK: {{^ /\* \*/ const char \*d = "";}} +// CHECK: {{^ \^}} + +// CHECK: {{^ ""; // }} +// CHECK: {{^ \^~~~~~~~~~}} +} -- 2.40.0