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::optional<common::LanguageFeature> Message::languageFeature() const { 189 return languageFeature_; 190 } 191 192 Message &Message::set_languageFeature(common::LanguageFeature feature) { 193 languageFeature_ = feature; 194 return *this; 195 } 196 197 std::optional<common::UsageWarning> Message::usageWarning() const { 198 return usageWarning_; 199 } 200 201 Message &Message::set_usageWarning(common::UsageWarning warning) { 202 usageWarning_ = warning; 203 return *this; 204 } 205 206 std::string Message::ToString() const { 207 return common::visit( 208 common::visitors{ 209 [](const MessageFixedText &t) { 210 return t.text().NULTerminatedToString(); 211 }, 212 [](const MessageFormattedText &t) { return t.string(); }, 213 [](const MessageExpectedText &e) { return e.ToString(); }, 214 }, 215 text_); 216 } 217 218 void Message::ResolveProvenances(const AllCookedSources &allCooked) { 219 if (CharBlock * cb{std::get_if<CharBlock>(&location_)}) { 220 if (std::optional<ProvenanceRange> resolved{ 221 allCooked.GetProvenanceRange(*cb)}) { 222 location_ = *resolved; 223 } 224 } 225 if (Message * attachment{attachment_.get()}) { 226 attachment->ResolveProvenances(allCooked); 227 } 228 } 229 230 std::optional<ProvenanceRange> Message::GetProvenanceRange( 231 const AllCookedSources &allCooked) const { 232 return common::visit( 233 common::visitors{ 234 [&](CharBlock cb) { return allCooked.GetProvenanceRange(cb); }, 235 [](const ProvenanceRange &pr) { return std::make_optional(pr); }, 236 }, 237 location_); 238 } 239 240 static std::string Prefix(Severity severity) { 241 switch (severity) { 242 case Severity::Error: 243 return "error: "; 244 case Severity::Warning: 245 return "warning: "; 246 case Severity::Portability: 247 return "portability: "; 248 case Severity::Because: 249 return "because: "; 250 case Severity::Context: 251 return "in the context: "; 252 case Severity::Todo: 253 return "error: not yet implemented: "; 254 case Severity::None: 255 break; 256 } 257 return ""; 258 } 259 260 static llvm::raw_ostream::Colors PrefixColor(Severity severity) { 261 switch (severity) { 262 case Severity::Error: 263 case Severity::Todo: 264 return llvm::raw_ostream::RED; 265 case Severity::Warning: 266 case Severity::Portability: 267 return llvm::raw_ostream::MAGENTA; 268 default: 269 // TODO: Set the color. 270 break; 271 } 272 return llvm::raw_ostream::SAVEDCOLOR; 273 } 274 275 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked, 276 bool echoSourceLine) const { 277 std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)}; 278 const AllSources &sources{allCooked.allSources()}; 279 sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()), 280 PrefixColor(severity()), echoSourceLine); 281 bool isContext{attachmentIsContext_}; 282 for (const Message *attachment{attachment_.get()}; attachment; 283 attachment = attachment->attachment_.get()) { 284 Severity severity = isContext ? Severity::Context : attachment->severity(); 285 sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked), 286 attachment->ToString(), Prefix(severity), PrefixColor(severity), 287 echoSourceLine); 288 } 289 } 290 291 // Messages are equal if they're for the same location and text, and the user 292 // visible aspects of their attachments are the same 293 bool Message::operator==(const Message &that) const { 294 if (!AtSameLocation(that) || ToString() != that.ToString() || 295 severity() != that.severity() || 296 attachmentIsContext_ != that.attachmentIsContext_) { 297 return false; 298 } 299 const Message *thatAttachment{that.attachment_.get()}; 300 for (const Message *attachment{attachment_.get()}; attachment; 301 attachment = attachment->attachment_.get()) { 302 if (!thatAttachment || !attachment->AtSameLocation(*thatAttachment) || 303 attachment->ToString() != thatAttachment->ToString() || 304 attachment->severity() != thatAttachment->severity()) { 305 return false; 306 } 307 thatAttachment = thatAttachment->attachment_.get(); 308 } 309 return !thatAttachment; 310 } 311 312 bool Message::Merge(const Message &that) { 313 return AtSameLocation(that) && 314 (!that.attachment_.get() || 315 attachment_.get() == that.attachment_.get()) && 316 common::visit( 317 common::visitors{ 318 [](MessageExpectedText &e1, const MessageExpectedText &e2) { 319 return e1.Merge(e2); 320 }, 321 [](const auto &, const auto &) { return false; }, 322 }, 323 text_, that.text_); 324 } 325 326 Message &Message::Attach(Message *m) { 327 if (!attachment_) { 328 attachment_ = m; 329 } else { 330 if (attachment_->references() > 1) { 331 // Don't attach to a shared context attachment; copy it first. 332 attachment_ = new Message{*attachment_}; 333 } 334 attachment_->Attach(m); 335 } 336 return *this; 337 } 338 339 Message &Message::Attach(std::unique_ptr<Message> &&m) { 340 return Attach(m.release()); 341 } 342 343 bool Message::AtSameLocation(const Message &that) const { 344 return common::visit( 345 common::visitors{ 346 [](CharBlock cb1, CharBlock cb2) { 347 return cb1.begin() == cb2.begin(); 348 }, 349 [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) { 350 return pr1.start() == pr2.start(); 351 }, 352 [](const auto &, const auto &) { return false; }, 353 }, 354 location_, that.location_); 355 } 356 357 bool Messages::Merge(const Message &msg) { 358 if (msg.IsMergeable()) { 359 for (auto &m : messages_) { 360 if (m.Merge(msg)) { 361 return true; 362 } 363 } 364 } 365 return false; 366 } 367 368 void Messages::Merge(Messages &&that) { 369 if (messages_.empty()) { 370 *this = std::move(that); 371 } else { 372 while (!that.messages_.empty()) { 373 if (Merge(that.messages_.front())) { 374 that.messages_.pop_front(); 375 } else { 376 auto next{that.messages_.begin()}; 377 ++next; 378 messages_.splice( 379 messages_.end(), that.messages_, that.messages_.begin(), next); 380 } 381 } 382 } 383 } 384 385 void Messages::Copy(const Messages &that) { 386 for (const Message &m : that.messages_) { 387 Message copy{m}; 388 Say(std::move(copy)); 389 } 390 } 391 392 void Messages::ResolveProvenances(const AllCookedSources &allCooked) { 393 for (Message &m : messages_) { 394 m.ResolveProvenances(allCooked); 395 } 396 } 397 398 void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked, 399 bool echoSourceLines) const { 400 std::vector<const Message *> sorted; 401 for (const auto &msg : messages_) { 402 sorted.push_back(&msg); 403 } 404 std::stable_sort(sorted.begin(), sorted.end(), 405 [](const Message *x, const Message *y) { return x->SortBefore(*y); }); 406 const Message *lastMsg{nullptr}; 407 for (const Message *msg : sorted) { 408 if (lastMsg && *msg == *lastMsg) { 409 // Don't emit two identical messages for the same location 410 continue; 411 } 412 msg->Emit(o, allCooked, echoSourceLines); 413 lastMsg = msg; 414 } 415 } 416 417 void Messages::AttachTo(Message &msg, std::optional<Severity> severity) { 418 for (Message &m : messages_) { 419 Message m2{std::move(m)}; 420 if (severity) { 421 m2.set_severity(*severity); 422 } 423 msg.Attach(std::move(m2)); 424 } 425 messages_.clear(); 426 } 427 428 bool Messages::AnyFatalError() const { 429 for (const auto &msg : messages_) { 430 if (msg.IsFatal()) { 431 return true; 432 } 433 } 434 return false; 435 } 436 } // namespace Fortran::parser 437