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 int need{vsnprintf(nullptr, 0, p, ap)};
42 CHECK(need >= 0);
43 char *buffer{
44 static_cast<char *>(std::malloc(static_cast<std::size_t>(need) + 1))};
45 CHECK(buffer);
46 va_end(ap);
47 va_start(ap, text);
48 int need2{vsnprintf(buffer, need + 1, p, ap)};
49 CHECK(need2 == need);
50 va_end(ap);
51 string_ = buffer;
52 std::free(buffer);
53 conversions_.clear();
54 }
55
Convert(const std::string & s)56 const char *MessageFormattedText::Convert(const std::string &s) {
57 conversions_.emplace_front(s);
58 return conversions_.front().c_str();
59 }
60
Convert(std::string & s)61 const char *MessageFormattedText::Convert(std::string &s) {
62 conversions_.emplace_front(s);
63 return conversions_.front().c_str();
64 }
65
Convert(std::string && s)66 const char *MessageFormattedText::Convert(std::string &&s) {
67 conversions_.emplace_front(std::move(s));
68 return conversions_.front().c_str();
69 }
70
Convert(CharBlock x)71 const char *MessageFormattedText::Convert(CharBlock x) {
72 return Convert(x.ToString());
73 }
74
ToString() const75 std::string MessageExpectedText::ToString() const {
76 return std::visit(
77 common::visitors{
78 [](CharBlock cb) {
79 return MessageFormattedText("expected '%s'"_err_en_US, cb)
80 .MoveString();
81 },
82 [](const SetOfChars &set) {
83 SetOfChars expect{set};
84 if (expect.Has('\n')) {
85 expect = expect.Difference('\n');
86 if (expect.empty()) {
87 return "expected end of line"_err_en_US.text().ToString();
88 } else {
89 std::string s{expect.ToString()};
90 if (s.size() == 1) {
91 return MessageFormattedText(
92 "expected end of line or '%s'"_err_en_US, s)
93 .MoveString();
94 } else {
95 return MessageFormattedText(
96 "expected end of line or one of '%s'"_err_en_US, s)
97 .MoveString();
98 }
99 }
100 }
101 std::string s{expect.ToString()};
102 if (s.size() != 1) {
103 return MessageFormattedText("expected one of '%s'"_err_en_US, s)
104 .MoveString();
105 } else {
106 return MessageFormattedText("expected '%s'"_err_en_US, s)
107 .MoveString();
108 }
109 },
110 },
111 u_);
112 }
113
Merge(const MessageExpectedText & that)114 bool MessageExpectedText::Merge(const MessageExpectedText &that) {
115 return std::visit(common::visitors{
116 [](SetOfChars &s1, const SetOfChars &s2) {
117 s1 = s1.Union(s2);
118 return true;
119 },
120 [](const auto &, const auto &) { return false; },
121 },
122 u_, that.u_);
123 }
124
SortBefore(const Message & that) const125 bool Message::SortBefore(const Message &that) const {
126 // Messages from prescanning have ProvenanceRange values for their locations,
127 // while messages from later phases have CharBlock values, since the
128 // conversion of cooked source stream locations to provenances is not
129 // free and needs to be deferred, and many messages created during parsing
130 // are speculative. Messages with ProvenanceRange locations are ordered
131 // before others for sorting.
132 return std::visit(
133 common::visitors{
134 [](CharBlock cb1, CharBlock cb2) {
135 return cb1.begin() < cb2.begin();
136 },
137 [](CharBlock, const ProvenanceRange &) { return false; },
138 [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) {
139 return pr1.start() < pr2.start();
140 },
141 [](const ProvenanceRange &, CharBlock) { return true; },
142 },
143 location_, that.location_);
144 }
145
IsFatal() const146 bool Message::IsFatal() const {
147 return std::visit(
148 common::visitors{
149 [](const MessageExpectedText &) { return true; },
150 [](const MessageFixedText &x) { return x.isFatal(); },
151 [](const MessageFormattedText &x) { return x.isFatal(); },
152 },
153 text_);
154 }
155
ToString() const156 std::string Message::ToString() const {
157 return std::visit(
158 common::visitors{
159 [](const MessageFixedText &t) {
160 return t.text().NULTerminatedToString();
161 },
162 [](const MessageFormattedText &t) { return t.string(); },
163 [](const MessageExpectedText &e) { return e.ToString(); },
164 },
165 text_);
166 }
167
ResolveProvenances(const AllCookedSources & allCooked)168 void Message::ResolveProvenances(const AllCookedSources &allCooked) {
169 if (CharBlock * cb{std::get_if<CharBlock>(&location_)}) {
170 if (std::optional<ProvenanceRange> resolved{
171 allCooked.GetProvenanceRange(*cb)}) {
172 location_ = *resolved;
173 }
174 }
175 if (Message * attachment{attachment_.get()}) {
176 attachment->ResolveProvenances(allCooked);
177 }
178 }
179
GetProvenanceRange(const AllCookedSources & allCooked) const180 std::optional<ProvenanceRange> Message::GetProvenanceRange(
181 const AllCookedSources &allCooked) const {
182 return std::visit(
183 common::visitors{
184 [&](CharBlock cb) { return allCooked.GetProvenanceRange(cb); },
185 [](const ProvenanceRange &pr) { return std::make_optional(pr); },
186 },
187 location_);
188 }
189
Emit(llvm::raw_ostream & o,const AllCookedSources & allCooked,bool echoSourceLine) const190 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
191 bool echoSourceLine) const {
192 std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
193 std::string text;
194 if (IsFatal()) {
195 text += "error: ";
196 }
197 text += ToString();
198 const AllSources &sources{allCooked.allSources()};
199 sources.EmitMessage(o, provenanceRange, text, echoSourceLine);
200 bool isContext{attachmentIsContext_};
201 for (const Message *attachment{attachment_.get()}; attachment;
202 attachment = attachment->attachment_.get()) {
203 text.clear();
204 if (isContext) {
205 text = "in the context: ";
206 }
207 text += attachment->ToString();
208 sources.EmitMessage(
209 o, attachment->GetProvenanceRange(allCooked), text, echoSourceLine);
210 isContext = attachment->attachmentIsContext_;
211 }
212 }
213
214 // Messages are equal if they're for the same location and text, and the user
215 // visible aspects of their attachments are the same
operator ==(const Message & that) const216 bool Message::operator==(const Message &that) const {
217 if (!AtSameLocation(that) || ToString() != that.ToString()) {
218 return false;
219 }
220 const Message *thatAttachment{that.attachment_.get()};
221 for (const Message *attachment{attachment_.get()}; attachment;
222 attachment = attachment->attachment_.get()) {
223 if (!thatAttachment ||
224 attachment->attachmentIsContext_ !=
225 thatAttachment->attachmentIsContext_ ||
226 *attachment != *thatAttachment) {
227 return false;
228 }
229 thatAttachment = thatAttachment->attachment_.get();
230 }
231 return true;
232 }
233
Merge(const Message & that)234 bool Message::Merge(const Message &that) {
235 return AtSameLocation(that) &&
236 (!that.attachment_.get() ||
237 attachment_.get() == that.attachment_.get()) &&
238 std::visit(
239 common::visitors{
240 [](MessageExpectedText &e1, const MessageExpectedText &e2) {
241 return e1.Merge(e2);
242 },
243 [](const auto &, const auto &) { return false; },
244 },
245 text_, that.text_);
246 }
247
Attach(Message * m)248 Message &Message::Attach(Message *m) {
249 if (!attachment_) {
250 attachment_ = m;
251 } else {
252 if (attachment_->references() > 1) {
253 // Don't attach to a shared context attachment; copy it first.
254 attachment_ = new Message{*attachment_};
255 }
256 attachment_->Attach(m);
257 }
258 return *this;
259 }
260
Attach(std::unique_ptr<Message> && m)261 Message &Message::Attach(std::unique_ptr<Message> &&m) {
262 return Attach(m.release());
263 }
264
AtSameLocation(const Message & that) const265 bool Message::AtSameLocation(const Message &that) const {
266 return std::visit(
267 common::visitors{
268 [](CharBlock cb1, CharBlock cb2) {
269 return cb1.begin() == cb2.begin();
270 },
271 [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) {
272 return pr1.start() == pr2.start();
273 },
274 [](const auto &, const auto &) { return false; },
275 },
276 location_, that.location_);
277 }
278
Merge(const Message & msg)279 bool Messages::Merge(const Message &msg) {
280 if (msg.IsMergeable()) {
281 for (auto &m : messages_) {
282 if (m.Merge(msg)) {
283 return true;
284 }
285 }
286 }
287 return false;
288 }
289
Merge(Messages && that)290 void Messages::Merge(Messages &&that) {
291 if (messages_.empty()) {
292 *this = std::move(that);
293 } else {
294 while (!that.messages_.empty()) {
295 if (Merge(that.messages_.front())) {
296 that.messages_.pop_front();
297 } else {
298 auto next{that.messages_.begin()};
299 ++next;
300 messages_.splice(
301 messages_.end(), that.messages_, that.messages_.begin(), next);
302 }
303 }
304 }
305 }
306
Copy(const Messages & that)307 void Messages::Copy(const Messages &that) {
308 for (const Message &m : that.messages_) {
309 Message copy{m};
310 Say(std::move(copy));
311 }
312 }
313
ResolveProvenances(const AllCookedSources & allCooked)314 void Messages::ResolveProvenances(const AllCookedSources &allCooked) {
315 for (Message &m : messages_) {
316 m.ResolveProvenances(allCooked);
317 }
318 }
319
Emit(llvm::raw_ostream & o,const AllCookedSources & allCooked,bool echoSourceLines) const320 void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
321 bool echoSourceLines) const {
322 std::vector<const Message *> sorted;
323 for (const auto &msg : messages_) {
324 sorted.push_back(&msg);
325 }
326 std::stable_sort(sorted.begin(), sorted.end(),
327 [](const Message *x, const Message *y) { return x->SortBefore(*y); });
328 const Message *lastMsg{nullptr};
329 for (const Message *msg : sorted) {
330 if (lastMsg && *msg == *lastMsg) {
331 // Don't emit two identical messages for the same location
332 continue;
333 }
334 msg->Emit(o, allCooked, echoSourceLines);
335 lastMsg = msg;
336 }
337 }
338
AttachTo(Message & msg)339 void Messages::AttachTo(Message &msg) {
340 for (Message &m : messages_) {
341 msg.Attach(std::move(m));
342 }
343 messages_.clear();
344 }
345
AnyFatalError() const346 bool Messages::AnyFatalError() const {
347 for (const auto &msg : messages_) {
348 if (msg.IsFatal()) {
349 return true;
350 }
351 }
352 return false;
353 }
354 } // namespace Fortran::parser
355