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