1 | //===-- lib/Parser/message.cpp --------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "flang/Parser/message.h" |
10 | #include "flang/Common/idioms.h" |
11 | #include "flang/Parser/char-set.h" |
12 | #include "llvm/Support/raw_ostream.h" |
13 | #include <algorithm> |
14 | #include <cstdarg> |
15 | #include <cstddef> |
16 | #include <cstdio> |
17 | #include <cstring> |
18 | #include <string> |
19 | #include <tuple> |
20 | #include <vector> |
21 | |
22 | namespace Fortran::parser { |
23 | |
24 | llvm::raw_ostream &operator<<(llvm::raw_ostream &o, const MessageFixedText &t) { |
25 | std::size_t n{t.text().size()}; |
26 | for (std::size_t j{0}; j < n; ++j) { |
27 | o << t.text()[j]; |
28 | } |
29 | return o; |
30 | } |
31 | |
32 | void MessageFormattedText::Format(const MessageFixedText *text, ...) { |
33 | const char *p{text->text().begin()}; |
34 | std::string asString; |
35 | if (*text->text().end() != '\0') { |
36 | // not NUL-terminated |
37 | asString = text->text().NULTerminatedToString(); |
38 | p = asString.c_str(); |
39 | } |
40 | va_list ap; |
41 | va_start(ap, text); |
42 | #ifdef _MSC_VER |
43 | // Microsoft has a separate function for "positional arguments", which is |
44 | // used in some messages. |
45 | int need{_vsprintf_p(nullptr, 0, p, ap)}; |
46 | #else |
47 | int need{vsnprintf(nullptr, 0, p, ap)}; |
48 | #endif |
49 | |
50 | CHECK(need >= 0); |
51 | char *buffer{ |
52 | static_cast<char *>(std::malloc(static_cast<std::size_t>(need) + 1))}; |
53 | CHECK(buffer); |
54 | va_end(ap); |
55 | va_start(ap, text); |
56 | #ifdef _MSC_VER |
57 | // Use positional argument variant of printf. |
58 | int need2{_vsprintf_p(buffer, need + 1, p, ap)}; |
59 | #else |
60 | int need2{vsnprintf(buffer, need + 1, p, ap)}; |
61 | #endif |
62 | CHECK(need2 == need); |
63 | va_end(ap); |
64 | string_ = buffer; |
65 | std::free(buffer); |
66 | conversions_.clear(); |
67 | } |
68 | |
69 | const char *MessageFormattedText::Convert(const std::string &s) { |
70 | conversions_.emplace_front(s); |
71 | return conversions_.front().c_str(); |
72 | } |
73 | |
74 | const char *MessageFormattedText::Convert(std::string &&s) { |
75 | conversions_.emplace_front(std::move(s)); |
76 | return conversions_.front().c_str(); |
77 | } |
78 | |
79 | const char *MessageFormattedText::Convert(const std::string_view &s) { |
80 | conversions_.emplace_front(s); |
81 | return conversions_.front().c_str(); |
82 | } |
83 | |
84 | const char *MessageFormattedText::Convert(std::string_view &&s) { |
85 | conversions_.emplace_front(s); |
86 | return conversions_.front().c_str(); |
87 | } |
88 | |
89 | const char *MessageFormattedText::Convert(CharBlock x) { |
90 | return Convert(x.ToString()); |
91 | } |
92 | |
93 | std::string MessageExpectedText::ToString() const { |
94 | return common::visit( |
95 | common::visitors{ |
96 | [](CharBlock cb) { |
97 | return MessageFormattedText("expected '%s'"_err_en_US , cb) |
98 | .MoveString(); |
99 | }, |
100 | [](const SetOfChars &set) { |
101 | SetOfChars expect{set}; |
102 | if (expect.Has('\n')) { |
103 | expect = expect.Difference('\n'); |
104 | if (expect.empty()) { |
105 | return "expected end of line"_err_en_US .text().ToString(); |
106 | } else { |
107 | std::string s{expect.ToString()}; |
108 | if (s.size() == 1) { |
109 | return MessageFormattedText( |
110 | "expected end of line or '%s'"_err_en_US , s) |
111 | .MoveString(); |
112 | } else { |
113 | return MessageFormattedText( |
114 | "expected end of line or one of '%s'"_err_en_US , s) |
115 | .MoveString(); |
116 | } |
117 | } |
118 | } |
119 | std::string s{expect.ToString()}; |
120 | if (s.size() != 1) { |
121 | return MessageFormattedText("expected one of '%s'"_err_en_US , s) |
122 | .MoveString(); |
123 | } else { |
124 | return MessageFormattedText("expected '%s'"_err_en_US , s) |
125 | .MoveString(); |
126 | } |
127 | }, |
128 | }, |
129 | u_); |
130 | } |
131 | |
132 | bool MessageExpectedText::Merge(const MessageExpectedText &that) { |
133 | return common::visit(common::visitors{ |
134 | [](SetOfChars &s1, const SetOfChars &s2) { |
135 | s1 = s1.Union(s2); |
136 | return true; |
137 | }, |
138 | [](const auto &, const auto &) { return false; }, |
139 | }, |
140 | u_, that.u_); |
141 | } |
142 | |
143 | bool Message::SortBefore(const Message &that) const { |
144 | // Messages from prescanning have ProvenanceRange values for their locations, |
145 | // while messages from later phases have CharBlock values, since the |
146 | // conversion of cooked source stream locations to provenances is not |
147 | // free and needs to be deferred, and many messages created during parsing |
148 | // are speculative. Messages with ProvenanceRange locations are ordered |
149 | // before others for sorting. |
150 | return common::visit( |
151 | common::visitors{ |
152 | [](CharBlock cb1, CharBlock cb2) { |
153 | return cb1.begin() < cb2.begin(); |
154 | }, |
155 | [](CharBlock, const ProvenanceRange &) { return false; }, |
156 | [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) { |
157 | return pr1.start() < pr2.start(); |
158 | }, |
159 | [](const ProvenanceRange &, CharBlock) { return true; }, |
160 | }, |
161 | location_, that.location_); |
162 | } |
163 | |
164 | bool Message::IsFatal() const { |
165 | return severity() == Severity::Error || severity() == Severity::Todo; |
166 | } |
167 | |
168 | Severity Message::severity() const { |
169 | return common::visit( |
170 | common::visitors{ |
171 | [](const MessageExpectedText &) { return Severity::Error; }, |
172 | [](const MessageFixedText &x) { return x.severity(); }, |
173 | [](const MessageFormattedText &x) { return x.severity(); }, |
174 | }, |
175 | text_); |
176 | } |
177 | |
178 | Message &Message::set_severity(Severity severity) { |
179 | common::visit( |
180 | common::visitors{ |
181 | [](const MessageExpectedText &) {}, |
182 | [severity](MessageFixedText &x) { x.set_severity(severity); }, |
183 | [severity](MessageFormattedText &x) { x.set_severity(severity); }, |
184 | }, |
185 | text_); |
186 | return *this; |
187 | } |
188 | |
189 | std::optional<common::LanguageFeature> Message::languageFeature() const { |
190 | return languageFeature_; |
191 | } |
192 | |
193 | Message &Message::set_languageFeature(common::LanguageFeature feature) { |
194 | languageFeature_ = feature; |
195 | return *this; |
196 | } |
197 | |
198 | std::optional<common::UsageWarning> Message::usageWarning() const { |
199 | return usageWarning_; |
200 | } |
201 | |
202 | Message &Message::set_usageWarning(common::UsageWarning warning) { |
203 | usageWarning_ = warning; |
204 | return *this; |
205 | } |
206 | |
207 | std::string Message::ToString() const { |
208 | return common::visit( |
209 | common::visitors{ |
210 | [](const MessageFixedText &t) { |
211 | return t.text().NULTerminatedToString(); |
212 | }, |
213 | [](const MessageFormattedText &t) { return t.string(); }, |
214 | [](const MessageExpectedText &e) { return e.ToString(); }, |
215 | }, |
216 | text_); |
217 | } |
218 | |
219 | void Message::ResolveProvenances(const AllCookedSources &allCooked) { |
220 | if (CharBlock * cb{std::get_if<CharBlock>(&location_)}) { |
221 | if (std::optional<ProvenanceRange> resolved{ |
222 | allCooked.GetProvenanceRange(*cb)}) { |
223 | location_ = *resolved; |
224 | } |
225 | } |
226 | if (Message * attachment{attachment_.get()}) { |
227 | attachment->ResolveProvenances(allCooked); |
228 | } |
229 | } |
230 | |
231 | std::optional<ProvenanceRange> Message::GetProvenanceRange( |
232 | const AllCookedSources &allCooked) const { |
233 | return common::visit( |
234 | common::visitors{ |
235 | [&](CharBlock cb) { return allCooked.GetProvenanceRange(cb); }, |
236 | [](const ProvenanceRange &pr) { return std::make_optional(pr); }, |
237 | }, |
238 | location_); |
239 | } |
240 | |
241 | static std::string Prefix(Severity severity) { |
242 | switch (severity) { |
243 | case Severity::Error: |
244 | return "error: " ; |
245 | case Severity::Warning: |
246 | return "warning: " ; |
247 | case Severity::Portability: |
248 | return "portability: " ; |
249 | case Severity::Because: |
250 | return "because: " ; |
251 | case Severity::Context: |
252 | return "in the context: " ; |
253 | case Severity::Todo: |
254 | return "error: not yet implemented: " ; |
255 | case Severity::None: |
256 | break; |
257 | } |
258 | return "" ; |
259 | } |
260 | |
261 | static llvm::raw_ostream::Colors PrefixColor(Severity severity) { |
262 | switch (severity) { |
263 | case Severity::Error: |
264 | case Severity::Todo: |
265 | return llvm::raw_ostream::RED; |
266 | case Severity::Warning: |
267 | case Severity::Portability: |
268 | return llvm::raw_ostream::MAGENTA; |
269 | default: |
270 | // TODO: Set the color. |
271 | break; |
272 | } |
273 | return llvm::raw_ostream::SAVEDCOLOR; |
274 | } |
275 | |
276 | static constexpr int MAX_CONTEXTS_EMITTED{2}; |
277 | static constexpr bool OMIT_SHARED_CONTEXTS{true}; |
278 | |
279 | void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked, |
280 | bool echoSourceLine) const { |
281 | std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)}; |
282 | const AllSources &sources{allCooked.allSources()}; |
283 | sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()), |
284 | PrefixColor(severity()), echoSourceLine); |
285 | // Refers to whether the attachment in the loop below is a context, but can't |
286 | // be declared inside the loop because the previous iteration's |
287 | // attachment->attachmentIsContext_ indicates this. |
288 | bool isContext{attachmentIsContext_}; |
289 | int contextsEmitted{0}; |
290 | // Emit attachments. |
291 | for (const Message *attachment{attachment_.get()}; attachment; |
292 | isContext = attachment->attachmentIsContext_, |
293 | attachment = attachment->attachment_.get()) { |
294 | Severity severity = isContext ? Severity::Context : attachment->severity(); |
295 | auto emitAttachment = [&]() { |
296 | sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked), |
297 | attachment->ToString(), Prefix(severity), PrefixColor(severity), |
298 | echoSourceLine); |
299 | }; |
300 | |
301 | if (isContext) { |
302 | // Truncate the number of contexts emitted. |
303 | if (contextsEmitted < MAX_CONTEXTS_EMITTED) { |
304 | emitAttachment(); |
305 | ++contextsEmitted; |
306 | } |
307 | if constexpr (OMIT_SHARED_CONTEXTS) { |
308 | // Skip less specific contexts at the same location. |
309 | for (const Message *next_attachment{attachment->attachment_.get()}; |
310 | next_attachment && next_attachment->attachmentIsContext_ && |
311 | next_attachment->AtSameLocation(*attachment); |
312 | next_attachment = next_attachment->attachment_.get()) { |
313 | attachment = next_attachment; |
314 | } |
315 | // NB, this loop increments `attachment` one more time after the |
316 | // previous loop is done advancing it to the last context at the same |
317 | // location. |
318 | } |
319 | } else { |
320 | emitAttachment(); |
321 | } |
322 | } |
323 | } |
324 | |
325 | // Messages are equal if they're for the same location and text, and the user |
326 | // visible aspects of their attachments are the same |
327 | bool Message::operator==(const Message &that) const { |
328 | if (!AtSameLocation(that) || ToString() != that.ToString() || |
329 | severity() != that.severity() || |
330 | attachmentIsContext_ != that.attachmentIsContext_) { |
331 | return false; |
332 | } |
333 | const Message *thatAttachment{that.attachment_.get()}; |
334 | for (const Message *attachment{attachment_.get()}; attachment; |
335 | attachment = attachment->attachment_.get()) { |
336 | if (!thatAttachment || !attachment->AtSameLocation(*thatAttachment) || |
337 | attachment->ToString() != thatAttachment->ToString() || |
338 | attachment->severity() != thatAttachment->severity()) { |
339 | return false; |
340 | } |
341 | thatAttachment = thatAttachment->attachment_.get(); |
342 | } |
343 | return !thatAttachment; |
344 | } |
345 | |
346 | bool Message::Merge(const Message &that) { |
347 | return AtSameLocation(that) && |
348 | (!that.attachment_.get() || |
349 | attachment_.get() == that.attachment_.get()) && |
350 | common::visit( |
351 | common::visitors{ |
352 | [](MessageExpectedText &e1, const MessageExpectedText &e2) { |
353 | return e1.Merge(e2); |
354 | }, |
355 | [](const auto &, const auto &) { return false; }, |
356 | }, |
357 | text_, that.text_); |
358 | } |
359 | |
360 | Message &Message::Attach(Message *m) { |
361 | if (!attachment_) { |
362 | attachment_ = m; |
363 | } else { |
364 | if (attachment_->references() > 1) { |
365 | // Don't attach to a shared context attachment; copy it first. |
366 | attachment_ = new Message{*attachment_}; |
367 | } |
368 | attachment_->Attach(m); |
369 | } |
370 | return *this; |
371 | } |
372 | |
373 | Message &Message::Attach(std::unique_ptr<Message> &&m) { |
374 | return Attach(m.release()); |
375 | } |
376 | |
377 | bool Message::AtSameLocation(const Message &that) const { |
378 | return common::visit( |
379 | common::visitors{ |
380 | [](CharBlock cb1, CharBlock cb2) { |
381 | return cb1.begin() == cb2.begin(); |
382 | }, |
383 | [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) { |
384 | return pr1.start() == pr2.start(); |
385 | }, |
386 | [](const auto &, const auto &) { return false; }, |
387 | }, |
388 | location_, that.location_); |
389 | } |
390 | |
391 | bool Messages::Merge(const Message &msg) { |
392 | if (msg.IsMergeable()) { |
393 | for (auto &m : messages_) { |
394 | if (m.Merge(msg)) { |
395 | return true; |
396 | } |
397 | } |
398 | } |
399 | return false; |
400 | } |
401 | |
402 | void Messages::Merge(Messages &&that) { |
403 | if (messages_.empty()) { |
404 | *this = std::move(that); |
405 | } else { |
406 | while (!that.messages_.empty()) { |
407 | if (Merge(that.messages_.front())) { |
408 | that.messages_.pop_front(); |
409 | } else { |
410 | auto next{that.messages_.begin()}; |
411 | ++next; |
412 | messages_.splice( |
413 | messages_.end(), that.messages_, that.messages_.begin(), next); |
414 | } |
415 | } |
416 | } |
417 | } |
418 | |
419 | void Messages::Copy(const Messages &that) { |
420 | for (const Message &m : that.messages_) { |
421 | Message copy{m}; |
422 | Say(std::move(copy)); |
423 | } |
424 | } |
425 | |
426 | void Messages::ResolveProvenances(const AllCookedSources &allCooked) { |
427 | for (Message &m : messages_) { |
428 | m.ResolveProvenances(allCooked); |
429 | } |
430 | } |
431 | |
432 | void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked, |
433 | bool echoSourceLines) const { |
434 | std::vector<const Message *> sorted; |
435 | for (const auto &msg : messages_) { |
436 | sorted.push_back(&msg); |
437 | } |
438 | std::stable_sort(sorted.begin(), sorted.end(), |
439 | [](const Message *x, const Message *y) { return x->SortBefore(*y); }); |
440 | const Message *lastMsg{nullptr}; |
441 | for (const Message *msg : sorted) { |
442 | if (lastMsg && *msg == *lastMsg) { |
443 | // Don't emit two identical messages for the same location |
444 | continue; |
445 | } |
446 | msg->Emit(o, allCooked, echoSourceLines); |
447 | lastMsg = msg; |
448 | } |
449 | } |
450 | |
451 | void Messages::AttachTo(Message &msg, std::optional<Severity> severity) { |
452 | for (Message &m : messages_) { |
453 | Message m2{std::move(m)}; |
454 | if (severity) { |
455 | m2.set_severity(*severity); |
456 | } |
457 | msg.Attach(std::move(m2)); |
458 | } |
459 | messages_.clear(); |
460 | } |
461 | |
462 | bool Messages::AnyFatalError() const { |
463 | for (const auto &msg : messages_) { |
464 | if (msg.IsFatal()) { |
465 | return true; |
466 | } |
467 | } |
468 | return false; |
469 | } |
470 | } // namespace Fortran::parser |
471 | |