/src/hermes/lib/Support/SourceErrorManager.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #include "hermes/Support/SourceErrorManager.h" |
9 | | #include "hermes/Support/UTF8.h" |
10 | | |
11 | | #include "llvh/ADT/DenseMap.h" |
12 | | #include "llvh/Support/raw_ostream.h" |
13 | | |
14 | | namespace hermes { |
15 | | |
16 | | static const char sTooManyErrors[] = "too many errors emitted"; |
17 | | |
18 | 0 | SourceErrorManager::ICoordTranslator::~ICoordTranslator() = default; |
19 | | |
20 | | SourceErrorManager::SourceErrorManager() |
21 | 54 | : warningStatuses_((unsigned)Warning::_NumWarnings, true), |
22 | 54 | warningsAreErrors_((unsigned)Warning::_NumWarnings, false) { |
23 | 54 | sm_.setDiagHandler(SourceErrorManager::printDiagnostic, this); |
24 | 54 | } |
25 | | |
26 | | void SourceErrorManager::BufferedMessage::addNote( |
27 | | std::vector<MessageData> &bufferedNotes, |
28 | | DiagKind dk, |
29 | | SMLoc loc, |
30 | | SMRange sm, |
31 | 0 | std::string &&msg) { |
32 | 0 | bufferedNotes.emplace_back(dk, loc, sm, std::move(msg)); |
33 | |
|
34 | 0 | if (!noteCount_) |
35 | 0 | firstNote_ = bufferedNotes.size() - 1; |
36 | 0 | ++noteCount_; |
37 | 0 | } |
38 | | |
39 | | llvh::iterator_range<const SourceErrorManager::MessageData *> |
40 | | SourceErrorManager::BufferedMessage::notes( |
41 | 10 | const std::vector<MessageData> &bufferedNotes) const { |
42 | 10 | if (!noteCount_) |
43 | 10 | return {nullptr, nullptr}; |
44 | 0 | return { |
45 | 0 | bufferedNotes.data() + firstNote_, |
46 | 0 | bufferedNotes.data() + firstNote_ + noteCount_}; |
47 | 10 | } |
48 | | |
49 | 41 | void SourceErrorManager::enableBuffering() { |
50 | 41 | ++bufferingEnabled_; |
51 | 41 | assert(bufferingEnabled_ != 0 && "unsigned counter overflow"); |
52 | 41 | } |
53 | | |
54 | 41 | void SourceErrorManager::disableBuffering() { |
55 | 41 | assert(bufferingEnabled_ != 0 && "unsigned counter underflow"); |
56 | | |
57 | 41 | if (--bufferingEnabled_ != 0) |
58 | 0 | return; |
59 | | |
60 | | // Sort all messages. |
61 | 41 | std::sort( |
62 | 41 | bufferedMessages_.begin(), |
63 | 41 | bufferedMessages_.end(), |
64 | 41 | [](const BufferedMessage &a, const BufferedMessage &b) { |
65 | | // Make sure the "too many errors" message is always last. |
66 | 5 | if (a.dk == DK_Error && !a.loc.isValid() && a.msg == sTooManyErrors) |
67 | 5 | return false; |
68 | 0 | if (b.dk == DK_Error && !b.loc.isValid() && b.msg == sTooManyErrors) |
69 | 0 | return true; |
70 | 0 | return a.loc.getPointer() < b.loc.getPointer(); |
71 | 0 | }); |
72 | | |
73 | | // Print them. |
74 | 41 | for (const auto &bm : bufferedMessages_) { |
75 | 10 | doPrintMessage(bm.dk, bm.loc, bm.sm, bm.msg); |
76 | 10 | for (const auto ¬e : bm.notes(bufferedNotes_)) |
77 | 0 | doPrintMessage(note.dk, note.loc, note.sm, note.msg); |
78 | 10 | } |
79 | | |
80 | | // Clean the buffer. |
81 | 41 | bufferedMessages_.clear(); |
82 | 41 | bufferedNotes_.clear(); |
83 | 41 | } |
84 | | |
85 | | unsigned SourceErrorManager::addNewSourceBuffer( |
86 | 101 | std::unique_ptr<llvh::MemoryBuffer> f) { |
87 | 101 | unsigned bufId = sm_.AddNewSourceBuffer(std::move(f), SMLoc{}); |
88 | 101 | assert( |
89 | 101 | !isVirtualBufferId(bufId) && "unexpected virtual buf id from SourceMgr"); |
90 | 101 | return bufId; |
91 | 101 | } |
92 | | |
93 | | /// Add a source buffer which maps to a filename. It doesn't contain any |
94 | | /// source and the only operation that can be performed on that buffer is to |
95 | | /// obtain the filename. |
96 | | unsigned SourceErrorManager::addNewVirtualSourceBuffer( |
97 | 0 | llvh::StringRef fileName) { |
98 | 0 | return indexToVirtualBufferId(virtualBufferNames_.insert(fileName)); |
99 | 0 | } |
100 | | |
101 | 36 | llvh::StringRef SourceErrorManager::getBufferFileName(unsigned bufId) const { |
102 | 36 | if (isVirtualBufferId(bufId)) |
103 | 0 | return virtualBufferNames_[virtualBufferIdToIndex(bufId)]; |
104 | 36 | else |
105 | 36 | return sm_.getMemoryBuffer(bufId)->getBufferIdentifier(); |
106 | 36 | } |
107 | | |
108 | | void SourceErrorManager::dumpCoords( |
109 | | llvh::raw_ostream &OS, |
110 | 0 | const SourceCoords &coords) { |
111 | 0 | if (coords.isValid()) { |
112 | 0 | OS << getSourceUrl(coords.bufId) << ":" << coords.line << "," << coords.col; |
113 | 0 | } else { |
114 | 0 | OS << "none:0,0"; |
115 | 0 | } |
116 | 0 | } |
117 | | |
118 | 0 | void SourceErrorManager::dumpCoords(llvh::raw_ostream &OS, SMLoc loc) { |
119 | 0 | SourceCoords coords; |
120 | 0 | findBufferLineAndLoc(loc, coords); |
121 | 0 | dumpCoords(OS, coords); |
122 | 0 | } |
123 | | |
124 | | void SourceErrorManager::countAndGenMessage( |
125 | | DiagKind dk, |
126 | | SMLoc loc, |
127 | | SMRange sm, |
128 | 166 | const Twine &msg) { |
129 | 166 | ++messageCount_[dk]; |
130 | 166 | doGenMessage(dk, loc, sm, msg); |
131 | | |
132 | 166 | if (LLVM_UNLIKELY(dk == DK_Error && messageCount_[DK_Error] == errorLimit_)) { |
133 | 18 | errorLimitReached_ = true; |
134 | 18 | doGenMessage(DK_Error, {}, {}, sTooManyErrors); |
135 | 18 | } |
136 | 166 | } |
137 | | |
138 | | void SourceErrorManager::doGenMessage( |
139 | | hermes::SourceErrorManager::DiagKind dk, |
140 | | llvh::SMLoc loc, |
141 | | llvh::SMRange sm, |
142 | 184 | llvh::Twine const &msg) { |
143 | 184 | if (bufferingEnabled_) { |
144 | | // If this message is a note, try to associate it with the last message. |
145 | | // Note that theoretically the first buffered message could be a note, so |
146 | | // we play it safe here (even though it should never happen). |
147 | 10 | if (dk == DK_Note && !bufferedMessages_.empty()) { |
148 | 0 | bufferedMessages_.back().addNote(bufferedNotes_, dk, loc, sm, msg.str()); |
149 | 10 | } else { |
150 | 10 | bufferedMessages_.emplace_back(dk, loc, sm, msg.str()); |
151 | 10 | } |
152 | 174 | } else { |
153 | 174 | doPrintMessage(dk, loc, sm, msg); |
154 | 174 | } |
155 | 184 | } |
156 | | |
157 | | void SourceErrorManager::doPrintMessage( |
158 | | DiagKind dk, |
159 | | SMLoc loc, |
160 | | SMRange sm, |
161 | 184 | const Twine &msg) { |
162 | 184 | sm_.PrintMessage( |
163 | 184 | loc, |
164 | 184 | static_cast<llvh::SourceMgr::DiagKind>(dk), |
165 | 184 | msg, |
166 | 184 | sm.isValid() ? llvh::ArrayRef<SMRange>(sm) |
167 | 184 | : llvh::ArrayRef<SMRange>(llvh::None), |
168 | 184 | llvh::None, |
169 | 184 | outputOptions_.showColors); |
170 | 184 | } |
171 | | |
172 | | void SourceErrorManager::message( |
173 | | hermes::SourceErrorManager::DiagKind dk, |
174 | | llvh::SMLoc loc, |
175 | | llvh::SMRange sm, |
176 | | llvh::Twine const &msg, |
177 | | hermes::Warning w, |
178 | 173 | Subsystem subsystem) { |
179 | 173 | assert(dk <= DK_Note); |
180 | 173 | if (suppressMessages_) { |
181 | 0 | if (*suppressMessages_ == Subsystem::Unspecified) { |
182 | 0 | return; |
183 | 0 | } |
184 | 0 | if (subsystem == *suppressMessages_) { |
185 | 0 | return; |
186 | 0 | } |
187 | 0 | } |
188 | | // Suppress all messages once the error limit has been reached. |
189 | 173 | if (LLVM_UNLIKELY(errorLimitReached_)) |
190 | 7 | return; |
191 | 166 | if (dk == DK_Warning && !isWarningEnabled(w)) { |
192 | 0 | lastMessageSuppressed_ = true; |
193 | 0 | return; |
194 | 0 | } |
195 | | // Automatically suppress notes if the last message was suppressed. |
196 | 166 | if (dk == DK_Note && lastMessageSuppressed_) |
197 | 0 | return; |
198 | 166 | lastMessageSuppressed_ = false; |
199 | | |
200 | | /// Optionally upgrade warnings into errors. |
201 | 166 | if (dk == DK_Warning && isWarningAnError(w)) { |
202 | 0 | dk = DK_Error; |
203 | 0 | } |
204 | 166 | assert(static_cast<unsigned>(dk) < kMessageCountSize && "bounds check"); |
205 | | |
206 | 166 | if (externalMessageBuffer_) { |
207 | 0 | externalMessageBuffer_->addMessage(dk, loc, sm, msg); |
208 | 0 | return; |
209 | 0 | } |
210 | | |
211 | 166 | countAndGenMessage(dk, loc, sm, msg); |
212 | 166 | } |
213 | | |
214 | | void SourceErrorManager::message( |
215 | | DiagKind dk, |
216 | | SMLoc loc, |
217 | | SMRange sm, |
218 | | const Twine &msg, |
219 | 25 | Subsystem subsystem) { |
220 | 25 | message(dk, loc, sm, msg, Warning::NoWarning, subsystem); |
221 | 25 | } |
222 | | |
223 | | void SourceErrorManager::message( |
224 | | DiagKind dk, |
225 | | SMRange sm, |
226 | | const Twine &msg, |
227 | 4 | Subsystem subsystem) { |
228 | 4 | message(dk, sm.Start, sm, msg, subsystem); |
229 | 4 | } |
230 | | |
231 | | void SourceErrorManager::message( |
232 | | DiagKind dk, |
233 | | SMLoc loc, |
234 | | const Twine &msg, |
235 | 19 | Subsystem subsystem) { |
236 | 19 | message(dk, loc, SMRange{}, msg, subsystem); |
237 | 19 | } |
238 | | |
239 | | auto SourceErrorManager::findBufferAndLine(SMLoc loc) const |
240 | 3.76k | -> llvh::Optional<LineCoord> { |
241 | 3.76k | if (!loc.isValid()) |
242 | 0 | return llvh::None; |
243 | | |
244 | 3.76k | auto bufId = sm_.FindBufferContainingLoc(loc); |
245 | 3.76k | if (!bufId) |
246 | 0 | return llvh::None; |
247 | | |
248 | 3.76k | auto lineRefAndNo = sm_.FindLine(loc, bufId); |
249 | | |
250 | 3.76k | return LineCoord{bufId, lineRefAndNo.second, lineRefAndNo.first}; |
251 | 3.76k | } |
252 | | |
253 | | /// Adjust the source location backwards making sure it doesn't point to \r or |
254 | | /// in the middle of a utf-8 sequence. |
255 | 950k | static inline SMLoc adjustSourceLocation(const char *bufStart, SMLoc loc) { |
256 | 950k | const char *ptr = loc.getPointer(); |
257 | | // In the very unlikely case that `loc` points to a '\r', we skip backwards |
258 | | // until we find another character, while being careful not to fall off the |
259 | | // beginning of the buffer. |
260 | 950k | if (LLVM_UNLIKELY(*ptr == '\r') || |
261 | 950k | LLVM_UNLIKELY(isUTF8ContinuationByte(*ptr))) { |
262 | 2 | do { |
263 | 2 | if (LLVM_UNLIKELY(ptr == bufStart)) { |
264 | | // This is highly unlikely but theoretically possible. There were only |
265 | | // '\r' between `loc` and the start of the buffer. |
266 | 0 | break; |
267 | 0 | } |
268 | 2 | --ptr; |
269 | 2 | } while (*ptr == '\r' || isUTF8ContinuationByte(*ptr)); |
270 | 1 | } |
271 | 0 | return SMLoc::getFromPointer(ptr); |
272 | 950k | } |
273 | | |
274 | 956k | static bool locInside(llvh::StringRef str, SMLoc loc) { |
275 | 956k | const char *ptr = loc.getPointer(); |
276 | 956k | return ptr >= str.begin() && ptr < str.end(); |
277 | 956k | } |
278 | | |
279 | | inline void SourceErrorManager::FindLineCache::fillCoords( |
280 | | SMLoc loc, |
281 | 950k | SourceCoords &result) { |
282 | 950k | loc = adjustSourceLocation(lineRef.data(), loc); |
283 | 950k | result.bufId = bufferId; |
284 | 950k | result.line = lineNo; |
285 | 950k | result.col = loc.getPointer() - lineRef.data() + 1; |
286 | 950k | } |
287 | | |
288 | 950k | bool SourceErrorManager::findBufferLineAndLoc(SMLoc loc, SourceCoords &result) { |
289 | 950k | if (!loc.isValid()) { |
290 | 0 | result.bufId = 0; |
291 | 0 | return false; |
292 | 0 | } |
293 | | |
294 | 950k | if (findLineCache_.bufferId) { |
295 | | // Check the cache with the hope that the lookup is within the last line or |
296 | | // the next line. |
297 | 950k | if (locInside(findLineCache_.lineRef, loc)) { |
298 | 944k | findLineCache_.fillCoords(loc, result); |
299 | 944k | return true; |
300 | 944k | } |
301 | 5.81k | if (locInside(findLineCache_.nextLineRef, loc)) { |
302 | 2.08k | ++findLineCache_.lineNo; |
303 | 2.08k | findLineCache_.lineRef = findLineCache_.nextLineRef; |
304 | 2.08k | findLineCache_.nextLineRef = |
305 | 2.08k | sm_.getLineRef(findLineCache_.lineNo + 1, findLineCache_.bufferId); |
306 | | |
307 | 2.08k | findLineCache_.fillCoords(loc, result); |
308 | 2.08k | return true; |
309 | 2.08k | } |
310 | | |
311 | 3.73k | findLineCache_.bufferId = 0; |
312 | 3.73k | } |
313 | | |
314 | 3.76k | auto lineCoord = findBufferAndLine(loc); |
315 | 3.76k | if (!lineCoord) { |
316 | 0 | result.bufId = 0; |
317 | 0 | return false; |
318 | 0 | } |
319 | | |
320 | | // Populate the cache. |
321 | 3.76k | findLineCache_.bufferId = lineCoord->bufId; |
322 | 3.76k | findLineCache_.lineNo = lineCoord->lineNo; |
323 | 3.76k | findLineCache_.lineRef = lineCoord->lineRef; |
324 | 3.76k | findLineCache_.nextLineRef = |
325 | 3.76k | sm_.getLineRef(findLineCache_.lineNo + 1, lineCoord->bufId); |
326 | | |
327 | 3.76k | findLineCache_.fillCoords(loc, result); |
328 | 3.76k | return true; |
329 | 3.76k | } |
330 | | |
331 | | bool SourceErrorManager::findBufferLineAndLoc( |
332 | | llvh::SMLoc loc, |
333 | | hermes::SourceErrorManager::SourceCoords &result, |
334 | 950k | bool translate) { |
335 | 950k | if (!findBufferLineAndLoc(loc, result)) |
336 | 0 | return false; |
337 | 950k | if (translate && translator_) |
338 | 0 | translator_->translate(result); |
339 | 950k | return true; |
340 | 950k | } |
341 | | |
342 | 0 | uint32_t SourceErrorManager::findBufferIdForLoc(SMLoc loc) const { |
343 | 0 | return sm_.FindBufferContainingLoc(loc); |
344 | 0 | } |
345 | | |
346 | | const llvh::MemoryBuffer *SourceErrorManager::findBufferForLoc( |
347 | 0 | SMLoc loc) const { |
348 | 0 | uint32_t bufID = findBufferIdForLoc(loc); |
349 | 0 | if (bufID == 0) { |
350 | 0 | return nullptr; |
351 | 0 | } |
352 | 0 | return sm_.getMemoryBuffer(bufID); |
353 | 0 | } |
354 | | |
355 | 0 | SMLoc SourceErrorManager::findSMLocFromCoords(SourceCoords coords) { |
356 | 0 | if (!coords.isValid()) |
357 | 0 | return {}; |
358 | | |
359 | | // TODO: optimize this with caching, etc. |
360 | 0 | auto *buffer = getSourceBuffer(coords.bufId); |
361 | 0 | if (!buffer) |
362 | 0 | return {}; |
363 | | |
364 | 0 | const char *cur = buffer->getBufferStart(); |
365 | 0 | const char *end = buffer->getBufferEnd(); |
366 | | |
367 | | // Loop until we find the line or we reach EOF. |
368 | 0 | unsigned lineNumber = 1; |
369 | 0 | const char *lineEnd; |
370 | 0 | while ((lineEnd = (const char *)std::memchr(cur, '\n', end - cur)) != |
371 | 0 | nullptr && |
372 | 0 | lineNumber != coords.line) { |
373 | 0 | ++lineNumber; |
374 | 0 | cur = lineEnd + 1; |
375 | 0 | } |
376 | | |
377 | | // If we didn't find LF, the end of the buffer is the end of the line. |
378 | 0 | if (!lineEnd) |
379 | 0 | lineEnd = end; |
380 | | |
381 | | // The last line we found is [cur..lineEnd) and its number is lineNumber. |
382 | | // Is it the right one? |
383 | 0 | if (lineNumber != coords.line) |
384 | 0 | return {}; |
385 | | |
386 | | // Trim a CR at start and end to account for all crazy line endings. |
387 | 0 | if (cur != lineEnd && *cur == '\r') |
388 | 0 | ++cur; |
389 | 0 | if (cur != lineEnd && *(lineEnd - 1) == '\r') |
390 | 0 | --lineEnd; |
391 | | |
392 | | // Special case for empty line. |
393 | 0 | if (cur == lineEnd) { |
394 | | // Column 1 or 0 in an empty line should work. |
395 | 0 | if (coords.col <= 1) |
396 | 0 | return SMLoc::getFromPointer(cur); |
397 | 0 | return {}; |
398 | 0 | } |
399 | | |
400 | | // Check for presence of UTF-8. |
401 | 0 | bool utf8 = false; |
402 | 0 | for (const char *p = cur; p != lineEnd; ++p) { |
403 | 0 | if (LLVM_UNLIKELY(*p & 0x80)) { |
404 | 0 | utf8 = true; |
405 | 0 | break; |
406 | 0 | } |
407 | 0 | } |
408 | | |
409 | | // ASCII is easy - just add the offset. |
410 | 0 | if (LLVM_LIKELY(!utf8)) { |
411 | | // Is the column in range? |
412 | 0 | if (coords.col > (size_t)(lineEnd - cur)) |
413 | 0 | return {}; |
414 | 0 | return SMLoc::getFromPointer(cur + coords.col - 1); |
415 | 0 | } |
416 | | |
417 | | // Scan for the column while accounting for multi-byte characters. |
418 | 0 | unsigned column = 0; |
419 | 0 | for (; cur != lineEnd; ++cur) { |
420 | | // Skip continuation bytes. |
421 | 0 | if (isUTF8ContinuationByte(*cur)) |
422 | 0 | continue; |
423 | 0 | if (++column == coords.col) |
424 | 0 | return SMLoc::getFromPointer(cur); |
425 | 0 | } |
426 | | |
427 | 0 | return {}; |
428 | 0 | } |
429 | | |
430 | | /// Given an SMDiagnostic, return {sourceLine, caretLine}, respecting the error |
431 | | /// output options |
432 | | std::pair<std::string, std::string> SourceErrorManager::buildSourceAndCaretLine( |
433 | | const llvh::SMDiagnostic &diag, |
434 | 0 | SourceErrorOutputOptions opts) { |
435 | | // Decode our source line to UTF-32 |
436 | | // Ignore errors (UTF-8 errors will become replacement character) |
437 | | // Don't try to decode past embedded nulls |
438 | | // Map from narrow byte to column as we go |
439 | 0 | std::vector<uint32_t> narrowByteToColumn; |
440 | 0 | std::u32string sourceLine; |
441 | 0 | std::string narrowSourceLine = diag.getLineContents(); |
442 | 0 | const char *cursor = narrowSourceLine.c_str(); |
443 | 0 | while (*cursor) { |
444 | 0 | const char *prev = cursor; |
445 | 0 | sourceLine.push_back(decodeUTF8<true>(cursor, [](const llvh::Twine &) {})); |
446 | 0 | while (prev++ < cursor) { |
447 | 0 | narrowByteToColumn.push_back(sourceLine.size() - 1); |
448 | 0 | } |
449 | 0 | } |
450 | 0 | const size_t numColumns = sourceLine.size(); |
451 | | |
452 | | // Widening helper |
453 | 0 | auto widenColumn = [&](unsigned narrowColumn) -> unsigned { |
454 | 0 | return narrowColumn < narrowByteToColumn.size() |
455 | 0 | ? narrowByteToColumn[narrowColumn] |
456 | 0 | : numColumns; |
457 | 0 | }; |
458 | | |
459 | | // Widen the caret column and ranges using our map |
460 | | // Ranges are of the form [first, last) |
461 | 0 | assert(diag.getColumnNo() >= 0); |
462 | 0 | const size_t columnNo = widenColumn(diag.getColumnNo()); |
463 | 0 | std::vector<std::pair<unsigned, unsigned>> ranges; |
464 | 0 | for (const auto &r : diag.getRanges()) { |
465 | 0 | ranges.emplace_back(widenColumn(r.first), widenColumn(r.second)); |
466 | 0 | } |
467 | | |
468 | | // Build the line with the caret and ranges. |
469 | 0 | std::string caretLine(numColumns + 1, ' '); |
470 | 0 | for (const auto &range : ranges) { |
471 | 0 | if (range.first < caretLine.size()) { |
472 | 0 | std::fill( |
473 | 0 | &caretLine[range.first], |
474 | 0 | &caretLine[std::min((size_t)range.second, caretLine.size())], |
475 | 0 | '~'); |
476 | 0 | } |
477 | 0 | } |
478 | 0 | caretLine[std::min(size_t(columnNo), numColumns)] = '^'; |
479 | 0 | caretLine.erase(caretLine.find_last_not_of(' ') + 1); |
480 | | |
481 | | // Expand tabs to spaces in both the source and caret line |
482 | 0 | const size_t tabStop = SourceErrorOutputOptions::TabStop; |
483 | 0 | for (size_t pos = sourceLine.find('\t'); pos < sourceLine.size(); |
484 | 0 | pos = sourceLine.find('\t', pos)) { |
485 | 0 | size_t expandCount = tabStop - (pos % tabStop); |
486 | 0 | sourceLine.replace(pos, 1, expandCount, ' '); |
487 | 0 | if (pos < caretLine.size()) { |
488 | | // Reuse the character in the caretLine, so that tabs in tildes expand to |
489 | | // more tildes |
490 | 0 | caretLine.replace(pos, 1, expandCount, caretLine[pos]); |
491 | 0 | } |
492 | 0 | pos += expandCount; |
493 | 0 | } |
494 | | |
495 | | // Trim the lines to respect preferredMaxErrorWidth |
496 | | // "Focus" around the caret, and any range intersecting it |
497 | | // Note ranges are of the form [start, end) and not [start, length) |
498 | 0 | int focusStart = columnNo; |
499 | 0 | int focusLength = 1; |
500 | 0 | for (const auto &r : ranges) { |
501 | 0 | if (r.first <= size_t(columnNo) && size_t(columnNo) < r.second) { |
502 | 0 | focusStart = r.first; |
503 | 0 | focusLength = r.second - r.first; |
504 | 0 | break; |
505 | 0 | } |
506 | 0 | } |
507 | 0 | size_t desiredLineLength = std::max( |
508 | 0 | opts.preferredMaxErrorWidth, |
509 | 0 | focusLength + SourceErrorOutputOptions::MinimumSourceContext); |
510 | 0 | if (sourceLine.size() > desiredLineLength) { |
511 | 0 | int focusCenter = focusStart + focusLength / 2; |
512 | 0 | int leftTrimAmount = focusCenter - desiredLineLength / 2; |
513 | 0 | if (leftTrimAmount > 0) { |
514 | 0 | caretLine.erase(0, leftTrimAmount); |
515 | 0 | sourceLine.erase(0, leftTrimAmount); |
516 | 0 | std::fill(sourceLine.begin(), sourceLine.begin() + 3, '.'); |
517 | 0 | } |
518 | 0 | if (sourceLine.size() > desiredLineLength) { |
519 | | // Trim on the right |
520 | 0 | caretLine.erase(std::min(caretLine.size(), desiredLineLength)); |
521 | 0 | sourceLine.erase(desiredLineLength); |
522 | 0 | std::fill(sourceLine.end() - 3, sourceLine.end(), '.'); |
523 | 0 | } |
524 | 0 | } |
525 | | |
526 | | // Convert sourceLine back to narrow |
527 | 0 | narrowSourceLine.clear(); |
528 | 0 | for (uint32_t c : sourceLine) { |
529 | 0 | char buffer[UTF8CodepointMaxBytes] = {}; |
530 | 0 | char *buffCursor = buffer; |
531 | 0 | encodeUTF8(buffCursor, c); |
532 | 0 | narrowSourceLine.append(buffer, buffCursor); |
533 | 0 | } |
534 | 0 | return {std::move(narrowSourceLine), std::move(caretLine)}; |
535 | 0 | } |
536 | | |
537 | | void SourceErrorManager::printDiagnostic( |
538 | | const llvh::SMDiagnostic &diag, |
539 | 0 | void *ctx) { |
540 | 0 | using llvh::raw_ostream; |
541 | 0 | const SourceErrorManager *self = static_cast<SourceErrorManager *>(ctx); |
542 | 0 | const SourceErrorOutputOptions opts = self->outputOptions_; |
543 | 0 | auto &S = llvh::errs(); |
544 | |
|
545 | 0 | llvh::StringRef filename = diag.getFilename(); |
546 | 0 | int lineNo = diag.getLineNo(); |
547 | 0 | int columnNo = diag.getColumnNo(); |
548 | | |
549 | | // Helpers to conditionally set or reset a color |
550 | 0 | auto changeColor = [&](raw_ostream::Colors color) { |
551 | 0 | if (opts.showColors) |
552 | 0 | S.changeColor(color, true); |
553 | 0 | }; |
554 | |
|
555 | 0 | auto resetColor = [&]() { |
556 | 0 | if (opts.showColors) |
557 | 0 | S.resetColor(); |
558 | 0 | }; |
559 | |
|
560 | 0 | changeColor(raw_ostream::SAVEDCOLOR); |
561 | 0 | if (!filename.empty()) { |
562 | 0 | S << (filename == "-" ? "<stdin>" : filename); |
563 | 0 | if (lineNo != -1) { |
564 | 0 | S << ':' << lineNo; |
565 | 0 | if (columnNo != -1) |
566 | 0 | S << ':' << (columnNo + 1); |
567 | 0 | } |
568 | 0 | S << ": "; |
569 | 0 | } |
570 | |
|
571 | 0 | switch (diag.getKind()) { |
572 | 0 | case llvh::SourceMgr::DK_Error: |
573 | 0 | changeColor(raw_ostream::RED); |
574 | 0 | S << "error: "; |
575 | 0 | break; |
576 | 0 | case llvh::SourceMgr::DK_Warning: |
577 | 0 | changeColor(raw_ostream::MAGENTA); |
578 | 0 | S << "warning: "; |
579 | 0 | break; |
580 | 0 | case llvh::SourceMgr::DK_Note: |
581 | 0 | changeColor(raw_ostream::BLACK); |
582 | 0 | S << "note: "; |
583 | 0 | break; |
584 | 0 | case llvh::SourceMgr::DK_Remark: |
585 | 0 | changeColor(raw_ostream::BLACK); |
586 | 0 | S << "remark: "; |
587 | 0 | break; |
588 | 0 | } |
589 | | |
590 | 0 | resetColor(); |
591 | 0 | changeColor(raw_ostream::SAVEDCOLOR); |
592 | 0 | S << diag.getMessage() << '\n'; |
593 | 0 | resetColor(); |
594 | |
|
595 | 0 | if (lineNo == -1 || columnNo == -1) |
596 | 0 | return; |
597 | | |
598 | 0 | std::string sourceLine; |
599 | 0 | std::string caretLine; |
600 | 0 | std::tie(sourceLine, caretLine) = buildSourceAndCaretLine(diag, opts); |
601 | | |
602 | | // Check for non-ASCII characters, which may have a width > 1 |
603 | | // If we find them, don't try to show the caret line |
604 | | // TODO: bravely teach buildSourceAndCaretLine to use wcwidth(), lifting this |
605 | | // restriction |
606 | 0 | bool showCaret = isAllASCII(sourceLine.begin(), sourceLine.end()); |
607 | |
|
608 | 0 | S << sourceLine << '\n'; |
609 | 0 | if (showCaret) { |
610 | 0 | changeColor(raw_ostream::GREEN); |
611 | 0 | S << caretLine << '\n'; |
612 | 0 | resetColor(); |
613 | 0 | } |
614 | 0 | } |
615 | | |
616 | 1.33k | SMLoc SourceErrorManager::convertEndToLocation(SMRange range) { |
617 | | // If the range is empty, return the starting point. |
618 | 1.33k | if (range.Start == range.End) |
619 | 0 | return range.Start; |
620 | | |
621 | 1.33k | return SMLoc::getFromPointer(range.End.getPointer() - 1); |
622 | 1.33k | } |
623 | | |
624 | | } // namespace hermes |