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

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