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
22namespace Fortran::parser {
23
24llvm::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
32void 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
69const char *MessageFormattedText::Convert(const std::string &s) {
70 conversions_.emplace_front(s);
71 return conversions_.front().c_str();
72}
73
74const char *MessageFormattedText::Convert(std::string &&s) {
75 conversions_.emplace_front(std::move(s));
76 return conversions_.front().c_str();
77}
78
79const char *MessageFormattedText::Convert(const std::string_view &s) {
80 conversions_.emplace_front(s);
81 return conversions_.front().c_str();
82}
83
84const char *MessageFormattedText::Convert(std::string_view &&s) {
85 conversions_.emplace_front(s);
86 return conversions_.front().c_str();
87}
88
89const char *MessageFormattedText::Convert(CharBlock x) {
90 return Convert(x.ToString());
91}
92
93std::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
132bool 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
143bool 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
164bool Message::IsFatal() const {
165 return severity() == Severity::Error || severity() == Severity::Todo;
166}
167
168Severity 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
178Message &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
189std::optional<common::LanguageFeature> Message::languageFeature() const {
190 return languageFeature_;
191}
192
193Message &Message::set_languageFeature(common::LanguageFeature feature) {
194 languageFeature_ = feature;
195 return *this;
196}
197
198std::optional<common::UsageWarning> Message::usageWarning() const {
199 return usageWarning_;
200}
201
202Message &Message::set_usageWarning(common::UsageWarning warning) {
203 usageWarning_ = warning;
204 return *this;
205}
206
207std::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
219void 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
231std::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
241static 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
261static 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
276static std::string HintLanguageControlFlag(
277 const common::LanguageFeatureControl *hintFlagPtr,
278 std::optional<common::LanguageFeature> feature,
279 std::optional<common::UsageWarning> warning) {
280 if (hintFlagPtr) {
281 std::string flag;
282 if (warning) {
283 flag = hintFlagPtr->getDefaultCliSpelling(*warning);
284 } else if (feature) {
285 flag = hintFlagPtr->getDefaultCliSpelling(*feature);
286 }
287 if (!flag.empty()) {
288 return " [-W" + flag + "]";
289 }
290 }
291 return "";
292}
293
294static constexpr int MAX_CONTEXTS_EMITTED{2};
295static constexpr bool OMIT_SHARED_CONTEXTS{true};
296
297void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
298 bool echoSourceLine,
299 const common::LanguageFeatureControl *hintFlagPtr) const {
300 std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
301 const AllSources &sources{allCooked.allSources()};
302 const std::string text{ToString()};
303 const std::string hint{
304 HintLanguageControlFlag(hintFlagPtr, languageFeature_, usageWarning_)};
305 sources.EmitMessage(o, provenanceRange, text + hint, Prefix(severity()),
306 PrefixColor(severity()), echoSourceLine);
307 // Refers to whether the attachment in the loop below is a context, but can't
308 // be declared inside the loop because the previous iteration's
309 // attachment->attachmentIsContext_ indicates this.
310 bool isContext{attachmentIsContext_};
311 int contextsEmitted{0};
312 // Emit attachments.
313 for (const Message *attachment{attachment_.get()}; attachment;
314 isContext = attachment->attachmentIsContext_,
315 attachment = attachment->attachment_.get()) {
316 Severity severity = isContext ? Severity::Context : attachment->severity();
317 auto emitAttachment = [&]() {
318 sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
319 attachment->ToString(), Prefix(severity), PrefixColor(severity),
320 echoSourceLine);
321 };
322
323 if (isContext) {
324 // Truncate the number of contexts emitted.
325 if (contextsEmitted < MAX_CONTEXTS_EMITTED) {
326 emitAttachment();
327 ++contextsEmitted;
328 }
329 if constexpr (OMIT_SHARED_CONTEXTS) {
330 // Skip less specific contexts at the same location.
331 for (const Message *next_attachment{attachment->attachment_.get()};
332 next_attachment && next_attachment->attachmentIsContext_ &&
333 next_attachment->AtSameLocation(*attachment);
334 next_attachment = next_attachment->attachment_.get()) {
335 attachment = next_attachment;
336 }
337 // NB, this loop increments `attachment` one more time after the
338 // previous loop is done advancing it to the last context at the same
339 // location.
340 }
341 } else {
342 emitAttachment();
343 }
344 }
345}
346
347// Messages are equal if they're for the same location and text, and the user
348// visible aspects of their attachments are the same
349bool Message::operator==(const Message &that) const {
350 if (!AtSameLocation(that) || ToString() != that.ToString() ||
351 severity() != that.severity() ||
352 attachmentIsContext_ != that.attachmentIsContext_) {
353 return false;
354 }
355 const Message *thatAttachment{that.attachment_.get()};
356 for (const Message *attachment{attachment_.get()}; attachment;
357 attachment = attachment->attachment_.get()) {
358 if (!thatAttachment || !attachment->AtSameLocation(*thatAttachment) ||
359 attachment->ToString() != thatAttachment->ToString() ||
360 attachment->severity() != thatAttachment->severity()) {
361 return false;
362 }
363 thatAttachment = thatAttachment->attachment_.get();
364 }
365 return !thatAttachment;
366}
367
368bool Message::Merge(const Message &that) {
369 return AtSameLocation(that) &&
370 (!that.attachment_.get() ||
371 attachment_.get() == that.attachment_.get()) &&
372 common::visit(
373 common::visitors{
374 [](MessageExpectedText &e1, const MessageExpectedText &e2) {
375 return e1.Merge(e2);
376 },
377 [](const auto &, const auto &) { return false; },
378 },
379 text_, that.text_);
380}
381
382Message &Message::Attach(Message *m) {
383 if (!attachment_) {
384 attachment_ = m;
385 } else {
386 if (attachment_->references() > 1) {
387 // Don't attach to a shared context attachment; copy it first.
388 attachment_ = new Message{*attachment_};
389 }
390 attachment_->Attach(m);
391 }
392 return *this;
393}
394
395Message &Message::Attach(std::unique_ptr<Message> &&m) {
396 return Attach(m.release());
397}
398
399bool Message::AtSameLocation(const Message &that) const {
400 return common::visit(
401 common::visitors{
402 [](CharBlock cb1, CharBlock cb2) {
403 return cb1.begin() == cb2.begin();
404 },
405 [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) {
406 return pr1.start() == pr2.start();
407 },
408 [](const auto &, const auto &) { return false; },
409 },
410 location_, that.location_);
411}
412
413bool Messages::Merge(const Message &msg) {
414 if (msg.IsMergeable()) {
415 for (auto &m : messages_) {
416 if (m.Merge(msg)) {
417 return true;
418 }
419 }
420 }
421 return false;
422}
423
424void Messages::Merge(Messages &&that) {
425 if (messages_.empty()) {
426 *this = std::move(that);
427 } else {
428 while (!that.messages_.empty()) {
429 if (Merge(that.messages_.front())) {
430 that.messages_.pop_front();
431 } else {
432 auto next{that.messages_.begin()};
433 ++next;
434 messages_.splice(
435 messages_.end(), that.messages_, that.messages_.begin(), next);
436 }
437 }
438 }
439}
440
441void Messages::Copy(const Messages &that) {
442 for (const Message &m : that.messages_) {
443 Message copy{m};
444 Say(std::move(copy));
445 }
446}
447
448void Messages::ResolveProvenances(const AllCookedSources &allCooked) {
449 for (Message &m : messages_) {
450 m.ResolveProvenances(allCooked);
451 }
452}
453
454void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
455 bool echoSourceLines, const common::LanguageFeatureControl *hintFlagPtr,
456 std::size_t maxErrorsToEmit) const {
457 std::vector<const Message *> sorted;
458 for (const auto &msg : messages_) {
459 sorted.push_back(&msg);
460 }
461 std::stable_sort(sorted.begin(), sorted.end(),
462 [](const Message *x, const Message *y) { return x->SortBefore(*y); });
463 const Message *lastMsg{nullptr};
464 std::size_t errorsEmitted{0};
465 for (const Message *msg : sorted) {
466 if (lastMsg && *msg == *lastMsg) {
467 // Don't emit two identical messages for the same location
468 continue;
469 }
470 msg->Emit(o, allCooked, echoSourceLines, hintFlagPtr);
471 lastMsg = msg;
472 if (msg->IsFatal()) {
473 ++errorsEmitted;
474 }
475 // If maxErrorsToEmit is 0, emit all errors, otherwise break after
476 // maxErrorsToEmit.
477 if (maxErrorsToEmit > 0 && errorsEmitted >= maxErrorsToEmit) {
478 break;
479 }
480 }
481}
482
483void Messages::AttachTo(Message &msg, std::optional<Severity> severity) {
484 for (Message &m : messages_) {
485 Message m2{std::move(m)};
486 if (severity) {
487 m2.set_severity(*severity);
488 }
489 msg.Attach(std::move(m2));
490 }
491 messages_.clear();
492}
493
494bool Messages::AnyFatalError() const {
495 for (const auto &msg : messages_) {
496 if (msg.IsFatal()) {
497 return true;
498 }
499 }
500 return false;
501}
502} // namespace Fortran::parser
503

source code of flang/lib/Parser/message.cpp