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(std::move(s)); |
75 | return conversions_.front().c_str(); |
76 | } |
77 | |
78 | const char *MessageFormattedText::Convert(const std::string_view &s) { |
79 | conversions_.emplace_front(s); |
80 | return conversions_.front().c_str(); |
81 | } |
82 | |
83 | const char *MessageFormattedText::Convert(std::string_view &&s) { |
84 | conversions_.emplace_front(s); |
85 | return conversions_.front().c_str(); |
86 | } |
87 | |
88 | const char *MessageFormattedText::Convert(CharBlock x) { |
89 | return Convert(x.ToString()); |
90 | } |
91 | |
92 | std::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 | |
131 | bool 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 | |
142 | bool 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 | |
163 | bool Message::IsFatal() const { |
164 | return severity() == Severity::Error || severity() == Severity::Todo; |
165 | } |
166 | |
167 | Severity 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 | |
177 | Message &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 | |
188 | std::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 | |
200 | void 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 | |
212 | std::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 | |
222 | static 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 | |
242 | static 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 | |
257 | void 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 |
275 | bool 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 | |
294 | bool 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 | |
308 | Message &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 | |
321 | Message &Message::Attach(std::unique_ptr<Message> &&m) { |
322 | return Attach(m.release()); |
323 | } |
324 | |
325 | bool 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 | |
339 | bool 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 | |
350 | void 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 | |
367 | void Messages::Copy(const Messages &that) { |
368 | for (const Message &m : that.messages_) { |
369 | Message copy{m}; |
370 | Say(std::move(copy)); |
371 | } |
372 | } |
373 | |
374 | void Messages::ResolveProvenances(const AllCookedSources &allCooked) { |
375 | for (Message &m : messages_) { |
376 | m.ResolveProvenances(allCooked); |
377 | } |
378 | } |
379 | |
380 | void 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 | |
399 | void 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 | |
410 | bool 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 | |