xref: /llvm-project/flang/lib/Parser/message.cpp (revision cd03e96f00a8ab2489ae5af79375de2207f4e0ab)
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 <vector>
20 
21 namespace Fortran::parser {
22 
23 llvm::raw_ostream &operator<<(llvm::raw_ostream &o, const MessageFixedText &t) {
24   std::size_t n{t.text().size()};
25   for (std::size_t j{0}; j < n; ++j) {
26     o << t.text()[j];
27   }
28   return o;
29 }
30 
31 void MessageFormattedText::Format(const MessageFixedText *text, ...) {
32   const char *p{text->text().begin()};
33   std::string asString;
34   if (*text->text().end() != '\0') {
35     // not NUL-terminated
36     asString = text->text().NULTerminatedToString();
37     p = asString.c_str();
38   }
39   va_list ap;
40   va_start(ap, text);
41 #ifdef _MSC_VER
42   // Microsoft has a separate function for "positional arguments", which is
43   // used in some messages.
44   int need{_vsprintf_p(nullptr, 0, p, ap)};
45 #else
46   int need{vsnprintf(nullptr, 0, p, ap)};
47 #endif
48 
49   CHECK(need >= 0);
50   char *buffer{
51       static_cast<char *>(std::malloc(static_cast<std::size_t>(need) + 1))};
52   CHECK(buffer);
53   va_end(ap);
54   va_start(ap, text);
55 #ifdef _MSC_VER
56   // Use positional argument variant of printf.
57   int need2{_vsprintf_p(buffer, need + 1, p, ap)};
58 #else
59   int need2{vsnprintf(buffer, need + 1, p, ap)};
60 #endif
61   CHECK(need2 == need);
62   va_end(ap);
63   string_ = buffer;
64   std::free(buffer);
65   conversions_.clear();
66 }
67 
68 const char *MessageFormattedText::Convert(const std::string &s) {
69   conversions_.emplace_front(s);
70   return conversions_.front().c_str();
71 }
72 
73 const char *MessageFormattedText::Convert(std::string &s) {
74   conversions_.emplace_front(s);
75   return conversions_.front().c_str();
76 }
77 
78 const char *MessageFormattedText::Convert(std::string &&s) {
79   conversions_.emplace_front(std::move(s));
80   return conversions_.front().c_str();
81 }
82 
83 const char *MessageFormattedText::Convert(CharBlock x) {
84   return Convert(x.ToString());
85 }
86 
87 std::string MessageExpectedText::ToString() const {
88   return common::visit(
89       common::visitors{
90           [](CharBlock cb) {
91             return MessageFormattedText("expected '%s'"_err_en_US, cb)
92                 .MoveString();
93           },
94           [](const SetOfChars &set) {
95             SetOfChars expect{set};
96             if (expect.Has('\n')) {
97               expect = expect.Difference('\n');
98               if (expect.empty()) {
99                 return "expected end of line"_err_en_US.text().ToString();
100               } else {
101                 std::string s{expect.ToString()};
102                 if (s.size() == 1) {
103                   return MessageFormattedText(
104                       "expected end of line or '%s'"_err_en_US, s)
105                       .MoveString();
106                 } else {
107                   return MessageFormattedText(
108                       "expected end of line or one of '%s'"_err_en_US, s)
109                       .MoveString();
110                 }
111               }
112             }
113             std::string s{expect.ToString()};
114             if (s.size() != 1) {
115               return MessageFormattedText("expected one of '%s'"_err_en_US, s)
116                   .MoveString();
117             } else {
118               return MessageFormattedText("expected '%s'"_err_en_US, s)
119                   .MoveString();
120             }
121           },
122       },
123       u_);
124 }
125 
126 bool MessageExpectedText::Merge(const MessageExpectedText &that) {
127   return common::visit(common::visitors{
128                            [](SetOfChars &s1, const SetOfChars &s2) {
129                              s1 = s1.Union(s2);
130                              return true;
131                            },
132                            [](const auto &, const auto &) { return false; },
133                        },
134       u_, that.u_);
135 }
136 
137 bool Message::SortBefore(const Message &that) const {
138   // Messages from prescanning have ProvenanceRange values for their locations,
139   // while messages from later phases have CharBlock values, since the
140   // conversion of cooked source stream locations to provenances is not
141   // free and needs to be deferred, and many messages created during parsing
142   // are speculative.  Messages with ProvenanceRange locations are ordered
143   // before others for sorting.
144   return common::visit(
145       common::visitors{
146           [](CharBlock cb1, CharBlock cb2) {
147             return cb1.begin() < cb2.begin();
148           },
149           [](CharBlock, const ProvenanceRange &) { return false; },
150           [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) {
151             return pr1.start() < pr2.start();
152           },
153           [](const ProvenanceRange &, CharBlock) { return true; },
154       },
155       location_, that.location_);
156 }
157 
158 bool Message::IsFatal() const { return severity() == Severity::Error; }
159 
160 Severity Message::severity() const {
161   return common::visit(
162       common::visitors{
163           [](const MessageExpectedText &) { return Severity::Error; },
164           [](const MessageFixedText &x) { return x.severity(); },
165           [](const MessageFormattedText &x) { return x.severity(); },
166       },
167       text_);
168 }
169 
170 Message &Message::set_severity(Severity severity) {
171   common::visit(
172       common::visitors{
173           [](const MessageExpectedText &) {},
174           [severity](MessageFixedText &x) { x.set_severity(severity); },
175           [severity](MessageFormattedText &x) { x.set_severity(severity); },
176       },
177       text_);
178   return *this;
179 }
180 
181 std::string Message::ToString() const {
182   return common::visit(
183       common::visitors{
184           [](const MessageFixedText &t) {
185             return t.text().NULTerminatedToString();
186           },
187           [](const MessageFormattedText &t) { return t.string(); },
188           [](const MessageExpectedText &e) { return e.ToString(); },
189       },
190       text_);
191 }
192 
193 void Message::ResolveProvenances(const AllCookedSources &allCooked) {
194   if (CharBlock * cb{std::get_if<CharBlock>(&location_)}) {
195     if (std::optional<ProvenanceRange> resolved{
196             allCooked.GetProvenanceRange(*cb)}) {
197       location_ = *resolved;
198     }
199   }
200   if (Message * attachment{attachment_.get()}) {
201     attachment->ResolveProvenances(allCooked);
202   }
203 }
204 
205 std::optional<ProvenanceRange> Message::GetProvenanceRange(
206     const AllCookedSources &allCooked) const {
207   return common::visit(
208       common::visitors{
209           [&](CharBlock cb) { return allCooked.GetProvenanceRange(cb); },
210           [](const ProvenanceRange &pr) { return std::make_optional(pr); },
211       },
212       location_);
213 }
214 
215 static std::string Prefix(Severity severity) {
216   switch (severity) {
217   case Severity::Error:
218     return "error: ";
219   case Severity::Warning:
220     return "warning: ";
221   case Severity::Portability:
222     return "portability: ";
223   case Severity::Because:
224     return "because: ";
225   case Severity::Context:
226     return "in the context: ";
227   case Severity::None:
228     break;
229   }
230   return "";
231 }
232 
233 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
234     bool echoSourceLine) const {
235   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
236   const AllSources &sources{allCooked.allSources()};
237   sources.EmitMessage(
238       o, provenanceRange, Prefix(severity()) + ToString(), echoSourceLine);
239   bool isContext{attachmentIsContext_};
240   for (const Message *attachment{attachment_.get()}; attachment;
241        attachment = attachment->attachment_.get()) {
242     sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
243         Prefix(isContext ? Severity::Context : attachment->severity()) +
244             attachment->ToString(),
245         echoSourceLine);
246   }
247 }
248 
249 // Messages are equal if they're for the same location and text, and the user
250 // visible aspects of their attachments are the same
251 bool Message::operator==(const Message &that) const {
252   if (!AtSameLocation(that) || ToString() != that.ToString() ||
253       severity() != that.severity() ||
254       attachmentIsContext_ != that.attachmentIsContext_) {
255     return false;
256   }
257   const Message *thatAttachment{that.attachment_.get()};
258   for (const Message *attachment{attachment_.get()}; attachment;
259        attachment = attachment->attachment_.get()) {
260     if (!thatAttachment || !attachment->AtSameLocation(*thatAttachment) ||
261         attachment->ToString() != thatAttachment->ToString() ||
262         attachment->severity() != thatAttachment->severity()) {
263       return false;
264     }
265     thatAttachment = thatAttachment->attachment_.get();
266   }
267   return !thatAttachment;
268 }
269 
270 bool Message::Merge(const Message &that) {
271   return AtSameLocation(that) &&
272       (!that.attachment_.get() ||
273           attachment_.get() == that.attachment_.get()) &&
274       common::visit(
275           common::visitors{
276               [](MessageExpectedText &e1, const MessageExpectedText &e2) {
277                 return e1.Merge(e2);
278               },
279               [](const auto &, const auto &) { return false; },
280           },
281           text_, that.text_);
282 }
283 
284 Message &Message::Attach(Message *m) {
285   if (!attachment_) {
286     attachment_ = m;
287   } else {
288     if (attachment_->references() > 1) {
289       // Don't attach to a shared context attachment; copy it first.
290       attachment_ = new Message{*attachment_};
291     }
292     attachment_->Attach(m);
293   }
294   return *this;
295 }
296 
297 Message &Message::Attach(std::unique_ptr<Message> &&m) {
298   return Attach(m.release());
299 }
300 
301 bool Message::AtSameLocation(const Message &that) const {
302   return common::visit(
303       common::visitors{
304           [](CharBlock cb1, CharBlock cb2) {
305             return cb1.begin() == cb2.begin();
306           },
307           [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) {
308             return pr1.start() == pr2.start();
309           },
310           [](const auto &, const auto &) { return false; },
311       },
312       location_, that.location_);
313 }
314 
315 bool Messages::Merge(const Message &msg) {
316   if (msg.IsMergeable()) {
317     for (auto &m : messages_) {
318       if (m.Merge(msg)) {
319         return true;
320       }
321     }
322   }
323   return false;
324 }
325 
326 void Messages::Merge(Messages &&that) {
327   if (messages_.empty()) {
328     *this = std::move(that);
329   } else {
330     while (!that.messages_.empty()) {
331       if (Merge(that.messages_.front())) {
332         that.messages_.pop_front();
333       } else {
334         auto next{that.messages_.begin()};
335         ++next;
336         messages_.splice(
337             messages_.end(), that.messages_, that.messages_.begin(), next);
338       }
339     }
340   }
341 }
342 
343 void Messages::Copy(const Messages &that) {
344   for (const Message &m : that.messages_) {
345     Message copy{m};
346     Say(std::move(copy));
347   }
348 }
349 
350 void Messages::ResolveProvenances(const AllCookedSources &allCooked) {
351   for (Message &m : messages_) {
352     m.ResolveProvenances(allCooked);
353   }
354 }
355 
356 void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
357     bool echoSourceLines) const {
358   std::vector<const Message *> sorted;
359   for (const auto &msg : messages_) {
360     sorted.push_back(&msg);
361   }
362   std::stable_sort(sorted.begin(), sorted.end(),
363       [](const Message *x, const Message *y) { return x->SortBefore(*y); });
364   const Message *lastMsg{nullptr};
365   for (const Message *msg : sorted) {
366     if (lastMsg && *msg == *lastMsg) {
367       // Don't emit two identical messages for the same location
368       continue;
369     }
370     msg->Emit(o, allCooked, echoSourceLines);
371     lastMsg = msg;
372   }
373 }
374 
375 void Messages::AttachTo(Message &msg, std::optional<Severity> severity) {
376   for (Message &m : messages_) {
377     Message m2{std::move(m)};
378     if (severity) {
379       m2.set_severity(*severity);
380     }
381     msg.Attach(std::move(m2));
382   }
383   messages_.clear();
384 }
385 
386 bool Messages::AnyFatalError() const {
387   for (const auto &msg : messages_) {
388     if (msg.IsFatal()) {
389       return true;
390     }
391   }
392   return false;
393 }
394 } // namespace Fortran::parser
395