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
operator <<(llvm::raw_ostream & o,const MessageFixedText & t)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
Format(const MessageFixedText * text,...)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
Convert(const std::string & s)68 const char *MessageFormattedText::Convert(const std::string &s) {
69 conversions_.emplace_front(s);
70 return conversions_.front().c_str();
71 }
72
Convert(std::string & s)73 const char *MessageFormattedText::Convert(std::string &s) {
74 conversions_.emplace_front(s);
75 return conversions_.front().c_str();
76 }
77
Convert(std::string && s)78 const char *MessageFormattedText::Convert(std::string &&s) {
79 conversions_.emplace_front(std::move(s));
80 return conversions_.front().c_str();
81 }
82
Convert(CharBlock x)83 const char *MessageFormattedText::Convert(CharBlock x) {
84 return Convert(x.ToString());
85 }
86
ToString() const87 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
Merge(const MessageExpectedText & that)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
SortBefore(const Message & that) const137 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
IsFatal() const158 bool Message::IsFatal() const {
159 return severity() == Severity::Error || severity() == Severity::Todo;
160 }
161
severity() const162 Severity Message::severity() const {
163 return common::visit(
164 common::visitors{
165 [](const MessageExpectedText &) { return Severity::Error; },
166 [](const MessageFixedText &x) { return x.severity(); },
167 [](const MessageFormattedText &x) { return x.severity(); },
168 },
169 text_);
170 }
171
set_severity(Severity severity)172 Message &Message::set_severity(Severity severity) {
173 common::visit(
174 common::visitors{
175 [](const MessageExpectedText &) {},
176 [severity](MessageFixedText &x) { x.set_severity(severity); },
177 [severity](MessageFormattedText &x) { x.set_severity(severity); },
178 },
179 text_);
180 return *this;
181 }
182
ToString() const183 std::string Message::ToString() const {
184 return common::visit(
185 common::visitors{
186 [](const MessageFixedText &t) {
187 return t.text().NULTerminatedToString();
188 },
189 [](const MessageFormattedText &t) { return t.string(); },
190 [](const MessageExpectedText &e) { return e.ToString(); },
191 },
192 text_);
193 }
194
ResolveProvenances(const AllCookedSources & allCooked)195 void Message::ResolveProvenances(const AllCookedSources &allCooked) {
196 if (CharBlock * cb{std::get_if<CharBlock>(&location_)}) {
197 if (std::optional<ProvenanceRange> resolved{
198 allCooked.GetProvenanceRange(*cb)}) {
199 location_ = *resolved;
200 }
201 }
202 if (Message * attachment{attachment_.get()}) {
203 attachment->ResolveProvenances(allCooked);
204 }
205 }
206
GetProvenanceRange(const AllCookedSources & allCooked) const207 std::optional<ProvenanceRange> Message::GetProvenanceRange(
208 const AllCookedSources &allCooked) const {
209 return common::visit(
210 common::visitors{
211 [&](CharBlock cb) { return allCooked.GetProvenanceRange(cb); },
212 [](const ProvenanceRange &pr) { return std::make_optional(pr); },
213 },
214 location_);
215 }
216
Prefix(Severity severity)217 static std::string Prefix(Severity severity) {
218 switch (severity) {
219 case Severity::Error:
220 return "error: ";
221 case Severity::Warning:
222 return "warning: ";
223 case Severity::Portability:
224 return "portability: ";
225 case Severity::Because:
226 return "because: ";
227 case Severity::Context:
228 return "in the context: ";
229 case Severity::Todo:
230 return "error: not yet implemented: ";
231 case Severity::None:
232 break;
233 }
234 return "";
235 }
236
Emit(llvm::raw_ostream & o,const AllCookedSources & allCooked,bool echoSourceLine) const237 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
238 bool echoSourceLine) const {
239 std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
240 const AllSources &sources{allCooked.allSources()};
241 sources.EmitMessage(
242 o, provenanceRange, Prefix(severity()) + ToString(), echoSourceLine);
243 bool isContext{attachmentIsContext_};
244 for (const Message *attachment{attachment_.get()}; attachment;
245 attachment = attachment->attachment_.get()) {
246 sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
247 Prefix(isContext ? Severity::Context : attachment->severity()) +
248 attachment->ToString(),
249 echoSourceLine);
250 }
251 }
252
253 // Messages are equal if they're for the same location and text, and the user
254 // visible aspects of their attachments are the same
operator ==(const Message & that) const255 bool Message::operator==(const Message &that) const {
256 if (!AtSameLocation(that) || ToString() != that.ToString() ||
257 severity() != that.severity() ||
258 attachmentIsContext_ != that.attachmentIsContext_) {
259 return false;
260 }
261 const Message *thatAttachment{that.attachment_.get()};
262 for (const Message *attachment{attachment_.get()}; attachment;
263 attachment = attachment->attachment_.get()) {
264 if (!thatAttachment || !attachment->AtSameLocation(*thatAttachment) ||
265 attachment->ToString() != thatAttachment->ToString() ||
266 attachment->severity() != thatAttachment->severity()) {
267 return false;
268 }
269 thatAttachment = thatAttachment->attachment_.get();
270 }
271 return !thatAttachment;
272 }
273
Merge(const Message & that)274 bool Message::Merge(const Message &that) {
275 return AtSameLocation(that) &&
276 (!that.attachment_.get() ||
277 attachment_.get() == that.attachment_.get()) &&
278 common::visit(
279 common::visitors{
280 [](MessageExpectedText &e1, const MessageExpectedText &e2) {
281 return e1.Merge(e2);
282 },
283 [](const auto &, const auto &) { return false; },
284 },
285 text_, that.text_);
286 }
287
Attach(Message * m)288 Message &Message::Attach(Message *m) {
289 if (!attachment_) {
290 attachment_ = m;
291 } else {
292 if (attachment_->references() > 1) {
293 // Don't attach to a shared context attachment; copy it first.
294 attachment_ = new Message{*attachment_};
295 }
296 attachment_->Attach(m);
297 }
298 return *this;
299 }
300
Attach(std::unique_ptr<Message> && m)301 Message &Message::Attach(std::unique_ptr<Message> &&m) {
302 return Attach(m.release());
303 }
304
AtSameLocation(const Message & that) const305 bool Message::AtSameLocation(const Message &that) const {
306 return common::visit(
307 common::visitors{
308 [](CharBlock cb1, CharBlock cb2) {
309 return cb1.begin() == cb2.begin();
310 },
311 [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) {
312 return pr1.start() == pr2.start();
313 },
314 [](const auto &, const auto &) { return false; },
315 },
316 location_, that.location_);
317 }
318
Merge(const Message & msg)319 bool Messages::Merge(const Message &msg) {
320 if (msg.IsMergeable()) {
321 for (auto &m : messages_) {
322 if (m.Merge(msg)) {
323 return true;
324 }
325 }
326 }
327 return false;
328 }
329
Merge(Messages && that)330 void Messages::Merge(Messages &&that) {
331 if (messages_.empty()) {
332 *this = std::move(that);
333 } else {
334 while (!that.messages_.empty()) {
335 if (Merge(that.messages_.front())) {
336 that.messages_.pop_front();
337 } else {
338 auto next{that.messages_.begin()};
339 ++next;
340 messages_.splice(
341 messages_.end(), that.messages_, that.messages_.begin(), next);
342 }
343 }
344 }
345 }
346
Copy(const Messages & that)347 void Messages::Copy(const Messages &that) {
348 for (const Message &m : that.messages_) {
349 Message copy{m};
350 Say(std::move(copy));
351 }
352 }
353
ResolveProvenances(const AllCookedSources & allCooked)354 void Messages::ResolveProvenances(const AllCookedSources &allCooked) {
355 for (Message &m : messages_) {
356 m.ResolveProvenances(allCooked);
357 }
358 }
359
Emit(llvm::raw_ostream & o,const AllCookedSources & allCooked,bool echoSourceLines) const360 void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
361 bool echoSourceLines) const {
362 std::vector<const Message *> sorted;
363 for (const auto &msg : messages_) {
364 sorted.push_back(&msg);
365 }
366 std::stable_sort(sorted.begin(), sorted.end(),
367 [](const Message *x, const Message *y) { return x->SortBefore(*y); });
368 const Message *lastMsg{nullptr};
369 for (const Message *msg : sorted) {
370 if (lastMsg && *msg == *lastMsg) {
371 // Don't emit two identical messages for the same location
372 continue;
373 }
374 msg->Emit(o, allCooked, echoSourceLines);
375 lastMsg = msg;
376 }
377 }
378
AttachTo(Message & msg,std::optional<Severity> severity)379 void Messages::AttachTo(Message &msg, std::optional<Severity> severity) {
380 for (Message &m : messages_) {
381 Message m2{std::move(m)};
382 if (severity) {
383 m2.set_severity(*severity);
384 }
385 msg.Attach(std::move(m2));
386 }
387 messages_.clear();
388 }
389
AnyFatalError() const390 bool Messages::AnyFatalError() const {
391 for (const auto &msg : messages_) {
392 if (msg.IsFatal()) {
393 return true;
394 }
395 }
396 return false;
397 }
398 } // namespace Fortran::parser
399