547 lines · cpp
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-exception6//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 22namespace Fortran::parser {23 24// The nextCh parser emits this, and Message::GetProvenanceRange() looks for it.25const MessageFixedText MessageFixedText::endOfFileMessage{26 "end of file"_err_en_US};27 28llvm::raw_ostream &operator<<(llvm::raw_ostream &o, const MessageFixedText &t) {29 std::size_t n{t.text().size()};30 for (std::size_t j{0}; j < n; ++j) {31 o << t.text()[j];32 }33 return o;34}35 36void MessageFormattedText::Format(const MessageFixedText *text, ...) {37 const char *p{text->text().begin()};38 std::string asString;39 if (*text->text().end() != '\0') {40 // not NUL-terminated41 asString = text->text().NULTerminatedToString();42 p = asString.c_str();43 }44 va_list ap;45 va_start(ap, text);46#ifdef _MSC_VER47 // Microsoft has a separate function for "positional arguments", which is48 // used in some messages.49 int need{_vsprintf_p(nullptr, 0, p, ap)};50#else51 int need{vsnprintf(nullptr, 0, p, ap)};52#endif53 54 CHECK(need >= 0);55 char *buffer{56 static_cast<char *>(std::malloc(static_cast<std::size_t>(need) + 1))};57 CHECK(buffer);58 va_end(ap);59 va_start(ap, text);60#ifdef _MSC_VER61 // Use positional argument variant of printf.62 int need2{_vsprintf_p(buffer, need + 1, p, ap)};63#else64 int need2{vsnprintf(buffer, need + 1, p, ap)};65#endif66 CHECK(need2 == need);67 va_end(ap);68 string_ = buffer;69 std::free(buffer);70 conversions_.clear();71}72 73const char *MessageFormattedText::Convert(const std::string &s) {74 conversions_.emplace_front(s);75 return conversions_.front().c_str();76}77 78const char *MessageFormattedText::Convert(std::string &&s) {79 conversions_.emplace_front(std::move(s));80 return conversions_.front().c_str();81}82 83const char *MessageFormattedText::Convert(const std::string_view &s) {84 conversions_.emplace_front(s);85 return conversions_.front().c_str();86}87 88const char *MessageFormattedText::Convert(std::string_view &&s) {89 conversions_.emplace_front(s);90 return conversions_.front().c_str();91}92 93const char *MessageFormattedText::Convert(CharBlock x) {94 return Convert(x.ToString());95}96 97std::string MessageExpectedText::ToString() const {98 return common::visit(99 common::visitors{100 [](CharBlock cb) {101 return MessageFormattedText("expected '%s'"_err_en_US, cb)102 .MoveString();103 },104 [](const SetOfChars &set) {105 SetOfChars expect{set};106 if (expect.Has('\n')) {107 expect = expect.Difference('\n');108 if (expect.empty()) {109 return "expected end of line"_err_en_US.text().ToString();110 } else {111 std::string s{expect.ToString()};112 if (s.size() == 1) {113 return MessageFormattedText(114 "expected end of line or '%s'"_err_en_US, s)115 .MoveString();116 } else {117 return MessageFormattedText(118 "expected end of line or one of '%s'"_err_en_US, s)119 .MoveString();120 }121 }122 }123 std::string s{expect.ToString()};124 if (s.size() != 1) {125 return MessageFormattedText("expected one of '%s'"_err_en_US, s)126 .MoveString();127 } else {128 return MessageFormattedText("expected '%s'"_err_en_US, s)129 .MoveString();130 }131 },132 },133 u_);134}135 136bool MessageExpectedText::Merge(const MessageExpectedText &that) {137 return common::visit(common::visitors{138 [](SetOfChars &s1, const SetOfChars &s2) {139 s1 = s1.Union(s2);140 return true;141 },142 [](const auto &, const auto &) { return false; },143 },144 u_, that.u_);145}146 147bool Message::SortBefore(const Message &that) const {148 // Messages from prescanning have ProvenanceRange values for their locations,149 // while messages from later phases have CharBlock values, since the150 // conversion of cooked source stream locations to provenances is not151 // free and needs to be deferred, and many messages created during parsing152 // are speculative. Messages with ProvenanceRange locations are ordered153 // before others for sorting.154 return common::visit(155 common::visitors{156 [](CharBlock cb1, CharBlock cb2) {157 return cb1.begin() < cb2.begin();158 },159 [](CharBlock, const ProvenanceRange &) { return false; },160 [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) {161 return pr1.start() < pr2.start();162 },163 [](const ProvenanceRange &, CharBlock) { return true; },164 },165 location_, that.location_);166}167 168bool Message::IsFatal() const {169 return severity() == Severity::Error || severity() == Severity::Todo;170}171 172Severity Message::severity() const {173 return common::visit(174 common::visitors{175 [](const MessageExpectedText &) { return Severity::Error; },176 [](const MessageFixedText &x) { return x.severity(); },177 [](const MessageFormattedText &x) { return x.severity(); },178 },179 text_);180}181 182Message &Message::set_severity(Severity severity) {183 common::visit(184 common::visitors{185 [](const MessageExpectedText &) {},186 [severity](MessageFixedText &x) { x.set_severity(severity); },187 [severity](MessageFormattedText &x) { x.set_severity(severity); },188 },189 text_);190 return *this;191}192 193std::optional<common::LanguageFeature> Message::languageFeature() const {194 return languageFeature_;195}196 197Message &Message::set_languageFeature(common::LanguageFeature feature) {198 languageFeature_ = feature;199 return *this;200}201 202std::optional<common::UsageWarning> Message::usageWarning() const {203 return usageWarning_;204}205 206Message &Message::set_usageWarning(common::UsageWarning warning) {207 usageWarning_ = warning;208 return *this;209}210 211std::string Message::ToString() const {212 return common::visit(213 common::visitors{214 [](const MessageFixedText &t) {215 return t.text().NULTerminatedToString();216 },217 [](const MessageFormattedText &t) { return t.string(); },218 [](const MessageExpectedText &e) { return e.ToString(); },219 },220 text_);221}222 223void Message::ResolveProvenances(const AllCookedSources &allCooked) {224 if (CharBlock * cb{std::get_if<CharBlock>(&location_)}) {225 if (std::optional<ProvenanceRange> resolved{226 allCooked.GetProvenanceRange(*cb)}) {227 location_ = *resolved;228 }229 }230 if (Message * attachment{attachment_.get()}) {231 attachment->ResolveProvenances(allCooked);232 }233}234 235std::optional<ProvenanceRange> Message::GetProvenanceRange(236 const AllCookedSources &allCooked) const {237 return common::visit(238 common::visitors{239 [&](CharBlock cb) -> std::optional<ProvenanceRange> {240 if (auto pr{allCooked.GetProvenanceRange(cb)}) {241 return pr;242 } else if (const auto *fixed{std::get_if<MessageFixedText>(&text_)};243 fixed &&244 fixed->text() == MessageFixedText::endOfFileMessage.text() &&245 cb.begin() && cb.size() == 1) {246 // Failure from "nextCh" due to reaching EOF. Back up one byte247 // to the terminal newline so that the output looks better.248 return allCooked.GetProvenanceRange(CharBlock{cb.begin() - 1, 1});249 } else {250 return std::nullopt;251 }252 },253 [](const ProvenanceRange &pr) { return std::make_optional(pr); },254 },255 location_);256}257 258static std::string Prefix(Severity severity) {259 switch (severity) {260 case Severity::Error:261 return "error: ";262 case Severity::Warning:263 return "warning: ";264 case Severity::Portability:265 return "portability: ";266 case Severity::Because:267 return "because: ";268 case Severity::Context:269 return "in the context: ";270 case Severity::Todo:271 return "error: not yet implemented: ";272 case Severity::None:273 break;274 }275 return "";276}277 278static llvm::raw_ostream::Colors PrefixColor(Severity severity) {279 switch (severity) {280 case Severity::Error:281 case Severity::Todo:282 return llvm::raw_ostream::RED;283 case Severity::Warning:284 case Severity::Portability:285 return llvm::raw_ostream::MAGENTA;286 default:287 // TODO: Set the color.288 break;289 }290 return llvm::raw_ostream::SAVEDCOLOR;291}292 293static std::string HintLanguageControlFlag(294 const common::LanguageFeatureControl *hintFlagPtr,295 std::optional<common::LanguageFeature> feature,296 std::optional<common::UsageWarning> warning) {297 if (hintFlagPtr) {298 std::string flag;299 if (warning) {300 flag = hintFlagPtr->getDefaultCliSpelling(*warning);301 } else if (feature) {302 flag = hintFlagPtr->getDefaultCliSpelling(*feature);303 }304 if (!flag.empty()) {305 return " [-W" + flag + "]";306 }307 }308 return "";309}310 311static constexpr int MAX_CONTEXTS_EMITTED{2};312static constexpr bool OMIT_SHARED_CONTEXTS{true};313 314void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,315 bool echoSourceLine,316 const common::LanguageFeatureControl *hintFlagPtr) const {317 std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};318 const AllSources &sources{allCooked.allSources()};319 const std::string text{ToString()};320 const std::string hint{321 HintLanguageControlFlag(hintFlagPtr, languageFeature_, usageWarning_)};322 sources.EmitMessage(o, provenanceRange, text + hint, Prefix(severity()),323 PrefixColor(severity()), echoSourceLine);324 // Refers to whether the attachment in the loop below is a context, but can't325 // be declared inside the loop because the previous iteration's326 // attachment->attachmentIsContext_ indicates this.327 bool isContext{attachmentIsContext_};328 int contextsEmitted{0};329 // Emit attachments.330 for (const Message *attachment{attachment_.get()}; attachment;331 isContext = attachment->attachmentIsContext_,332 attachment = attachment->attachment_.get()) {333 Severity severity = isContext ? Severity::Context : attachment->severity();334 auto emitAttachment = [&]() {335 sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),336 attachment->ToString(), Prefix(severity), PrefixColor(severity),337 echoSourceLine);338 };339 340 if (isContext) {341 // Truncate the number of contexts emitted.342 if (contextsEmitted < MAX_CONTEXTS_EMITTED) {343 emitAttachment();344 ++contextsEmitted;345 }346 if constexpr (OMIT_SHARED_CONTEXTS) {347 // Skip less specific contexts at the same location.348 for (const Message *next_attachment{attachment->attachment_.get()};349 next_attachment && next_attachment->attachmentIsContext_ &&350 next_attachment->AtSameLocation(*attachment);351 next_attachment = next_attachment->attachment_.get()) {352 attachment = next_attachment;353 }354 // NB, this loop increments `attachment` one more time after the355 // previous loop is done advancing it to the last context at the same356 // location.357 }358 } else {359 emitAttachment();360 }361 }362}363 364// Messages are equal if they're for the same location and text, and the user365// visible aspects of their attachments are the same366bool Message::operator==(const Message &that) const {367 if (!AtSameLocation(that) || ToString() != that.ToString() ||368 severity() != that.severity() ||369 attachmentIsContext_ != that.attachmentIsContext_) {370 return false;371 }372 const Message *thatAttachment{that.attachment_.get()};373 for (const Message *attachment{attachment_.get()}; attachment;374 attachment = attachment->attachment_.get()) {375 if (!thatAttachment || !attachment->AtSameLocation(*thatAttachment) ||376 attachment->ToString() != thatAttachment->ToString() ||377 attachment->severity() != thatAttachment->severity()) {378 return false;379 }380 thatAttachment = thatAttachment->attachment_.get();381 }382 return !thatAttachment;383}384 385bool Message::Merge(const Message &that) {386 return AtSameLocation(that) &&387 (!that.attachment_.get() ||388 attachment_.get() == that.attachment_.get()) &&389 common::visit(390 common::visitors{391 [](MessageExpectedText &e1, const MessageExpectedText &e2) {392 return e1.Merge(e2);393 },394 [](const auto &, const auto &) { return false; },395 },396 text_, that.text_);397}398 399Message &Message::Attach(Message *m) {400 if (!attachment_) {401 attachment_ = m;402 } else {403 if (attachment_->references() > 1) {404 // Don't attach to a shared context attachment; copy it first.405 attachment_ = new Message{*attachment_};406 }407 attachment_->Attach(m);408 }409 return *this;410}411 412Message &Message::Attach(std::unique_ptr<Message> &&m) {413 return Attach(m.release());414}415 416bool Message::AtSameLocation(const Message &that) const {417 return common::visit(418 common::visitors{419 [](CharBlock cb1, CharBlock cb2) {420 return cb1.begin() == cb2.begin();421 },422 [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) {423 return pr1.start() == pr2.start();424 },425 [](const auto &, const auto &) { return false; },426 },427 location_, that.location_);428}429 430bool Messages::Merge(const Message &msg) {431 if (msg.IsMergeable()) {432 for (auto &m : messages_) {433 if (m.Merge(msg)) {434 return true;435 }436 }437 }438 return false;439}440 441void Messages::Merge(Messages &&that) {442 if (messages_.empty()) {443 *this = std::move(that);444 } else {445 while (!that.messages_.empty()) {446 if (Merge(that.messages_.front())) {447 that.messages_.pop_front();448 } else {449 auto next{that.messages_.begin()};450 ++next;451 messages_.splice(452 messages_.end(), that.messages_, that.messages_.begin(), next);453 }454 }455 }456}457 458void Messages::Copy(const Messages &that) {459 for (const Message &m : that.messages_) {460 Message copy{m};461 Say(std::move(copy));462 }463}464 465void Messages::ResolveProvenances(const AllCookedSources &allCooked) {466 for (Message &m : messages_) {467 m.ResolveProvenances(allCooked);468 }469}470 471void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,472 bool echoSourceLines, const common::LanguageFeatureControl *hintFlagPtr,473 std::size_t maxErrorsToEmit, bool warningsAreErrors) const {474 std::vector<const Message *> sorted;475 for (const auto &msg : messages_) {476 sorted.push_back(&msg);477 }478 std::stable_sort(sorted.begin(), sorted.end(),479 [](const Message *x, const Message *y) { return x->SortBefore(*y); });480 std::vector<const Message *> msgsWithLastLocation;481 std::size_t errorsEmitted{0};482 for (const Message *msg : sorted) {483 bool shouldSkipMsg{false};484 // Don't emit two identical messages for the same location.485 // At the same location, messages are sorted by the order they were486 // added to the Messages buffer, which is a decent proxy for the487 // causality of the messages.488 if (!msgsWithLastLocation.empty()) {489 if (msgsWithLastLocation[0]->AtSameLocation(*msg)) {490 for (const Message *msgAtThisLocation : msgsWithLastLocation) {491 if (*msg == *msgAtThisLocation) {492 shouldSkipMsg = true; // continue loop over sorted messages493 break;494 }495 }496 } else {497 msgsWithLastLocation.clear();498 }499 }500 if (shouldSkipMsg) {501 continue;502 }503 msgsWithLastLocation.push_back(msg);504 msg->Emit(o, allCooked, echoSourceLines, hintFlagPtr);505 if (warningsAreErrors || msg->IsFatal()) {506 ++errorsEmitted;507 }508 // If maxErrorsToEmit is 0, emit all errors, otherwise break after509 // maxErrorsToEmit.510 if (maxErrorsToEmit > 0 && errorsEmitted >= maxErrorsToEmit) {511 break;512 }513 }514}515 516void Messages::AttachTo(Message &msg, std::optional<Severity> severity) {517 for (Message &m : messages_) {518 Message m2{std::move(m)};519 if (severity) {520 m2.set_severity(*severity);521 }522 msg.Attach(std::move(m2));523 }524 messages_.clear();525}526 527bool Messages::AnyFatalError(bool warningsAreErrors) const {528 // Short-circuit in the most common case.529 if (messages_.empty()) {530 return false;531 }532 // If warnings are errors and there are warnings or errors, this is fatal.533 // This preserves the compiler's current behavior of treating any non-fatal534 // message as a warning. We may want to refine this in the future.535 if (warningsAreErrors) {536 return true;537 }538 // Otherwise, check the message buffer for fatal errors.539 for (const auto &msg : messages_) {540 if (msg.IsFatal()) {541 return true;542 }543 }544 return false;545}546} // namespace Fortran::parser547