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(s); 75 return conversions_.front().c_str(); 76 } 77 78 const char *MessageFormattedText::Convert(std::string &&s) { 79 conversions_.emplace_front(std::move(s)); 80 return conversions_.front().c_str(); 81 } 82 83 const char *MessageFormattedText::Convert(CharBlock x) { 84 return Convert(x.ToString()); 85 } 86 87 std::string MessageExpectedText::ToString() const { 88 return common::visit( 89 common::visitors{ 90 [](CharBlock cb) { 91 return MessageFormattedText("expected '%s'"_err_en_US, cb) 92 .MoveString(); 93 }, 94 [](const SetOfChars &set) { 95 SetOfChars expect{set}; 96 if (expect.Has('\n')) { 97 expect = expect.Difference('\n'); 98 if (expect.empty()) { 99 return "expected end of line"_err_en_US.text().ToString(); 100 } else { 101 std::string s{expect.ToString()}; 102 if (s.size() == 1) { 103 return MessageFormattedText( 104 "expected end of line or '%s'"_err_en_US, s) 105 .MoveString(); 106 } else { 107 return MessageFormattedText( 108 "expected end of line or one of '%s'"_err_en_US, s) 109 .MoveString(); 110 } 111 } 112 } 113 std::string s{expect.ToString()}; 114 if (s.size() != 1) { 115 return MessageFormattedText("expected one of '%s'"_err_en_US, s) 116 .MoveString(); 117 } else { 118 return MessageFormattedText("expected '%s'"_err_en_US, s) 119 .MoveString(); 120 } 121 }, 122 }, 123 u_); 124 } 125 126 bool MessageExpectedText::Merge(const MessageExpectedText &that) { 127 return common::visit(common::visitors{ 128 [](SetOfChars &s1, const SetOfChars &s2) { 129 s1 = s1.Union(s2); 130 return true; 131 }, 132 [](const auto &, const auto &) { return false; }, 133 }, 134 u_, that.u_); 135 } 136 137 bool Message::SortBefore(const Message &that) const { 138 // Messages from prescanning have ProvenanceRange values for their locations, 139 // while messages from later phases have CharBlock values, since the 140 // conversion of cooked source stream locations to provenances is not 141 // free and needs to be deferred, and many messages created during parsing 142 // are speculative. Messages with ProvenanceRange locations are ordered 143 // before others for sorting. 144 return common::visit( 145 common::visitors{ 146 [](CharBlock cb1, CharBlock cb2) { 147 return cb1.begin() < cb2.begin(); 148 }, 149 [](CharBlock, const ProvenanceRange &) { return false; }, 150 [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) { 151 return pr1.start() < pr2.start(); 152 }, 153 [](const ProvenanceRange &, CharBlock) { return true; }, 154 }, 155 location_, that.location_); 156 } 157 158 bool Message::IsFatal() const { 159 return severity() == Severity::Error || severity() == Severity::Todo; 160 } 161 162 Severity Message::severity() const { 163 return common::visit( 164 common::visitors{ 165 [](const MessageExpectedText &) { return Severity::Error; }, 166 [](const MessageFixedText &x) { return x.severity(); }, 167 [](const MessageFormattedText &x) { return x.severity(); }, 168 }, 169 text_); 170 } 171 172 Message &Message::set_severity(Severity severity) { 173 common::visit( 174 common::visitors{ 175 [](const MessageExpectedText &) {}, 176 [severity](MessageFixedText &x) { x.set_severity(severity); }, 177 [severity](MessageFormattedText &x) { x.set_severity(severity); }, 178 }, 179 text_); 180 return *this; 181 } 182 183 std::string Message::ToString() const { 184 return common::visit( 185 common::visitors{ 186 [](const MessageFixedText &t) { 187 return t.text().NULTerminatedToString(); 188 }, 189 [](const MessageFormattedText &t) { return t.string(); }, 190 [](const MessageExpectedText &e) { return e.ToString(); }, 191 }, 192 text_); 193 } 194 195 void Message::ResolveProvenances(const AllCookedSources &allCooked) { 196 if (CharBlock * cb{std::get_if<CharBlock>(&location_)}) { 197 if (std::optional<ProvenanceRange> resolved{ 198 allCooked.GetProvenanceRange(*cb)}) { 199 location_ = *resolved; 200 } 201 } 202 if (Message * attachment{attachment_.get()}) { 203 attachment->ResolveProvenances(allCooked); 204 } 205 } 206 207 std::optional<ProvenanceRange> Message::GetProvenanceRange( 208 const AllCookedSources &allCooked) const { 209 return common::visit( 210 common::visitors{ 211 [&](CharBlock cb) { return allCooked.GetProvenanceRange(cb); }, 212 [](const ProvenanceRange &pr) { return std::make_optional(pr); }, 213 }, 214 location_); 215 } 216 217 static std::string Prefix(Severity severity) { 218 switch (severity) { 219 case Severity::Error: 220 return "error: "; 221 case Severity::Warning: 222 return "warning: "; 223 case Severity::Portability: 224 return "portability: "; 225 case Severity::Because: 226 return "because: "; 227 case Severity::Context: 228 return "in the context: "; 229 case Severity::Todo: 230 return "error: not yet implemented: "; 231 case Severity::None: 232 break; 233 } 234 return ""; 235 } 236 237 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked, 238 bool echoSourceLine) const { 239 std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)}; 240 const AllSources &sources{allCooked.allSources()}; 241 sources.EmitMessage( 242 o, provenanceRange, Prefix(severity()) + ToString(), echoSourceLine); 243 bool isContext{attachmentIsContext_}; 244 for (const Message *attachment{attachment_.get()}; attachment; 245 attachment = attachment->attachment_.get()) { 246 sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked), 247 Prefix(isContext ? Severity::Context : attachment->severity()) + 248 attachment->ToString(), 249 echoSourceLine); 250 } 251 } 252 253 // Messages are equal if they're for the same location and text, and the user 254 // visible aspects of their attachments are the same 255 bool Message::operator==(const Message &that) const { 256 if (!AtSameLocation(that) || ToString() != that.ToString() || 257 severity() != that.severity() || 258 attachmentIsContext_ != that.attachmentIsContext_) { 259 return false; 260 } 261 const Message *thatAttachment{that.attachment_.get()}; 262 for (const Message *attachment{attachment_.get()}; attachment; 263 attachment = attachment->attachment_.get()) { 264 if (!thatAttachment || !attachment->AtSameLocation(*thatAttachment) || 265 attachment->ToString() != thatAttachment->ToString() || 266 attachment->severity() != thatAttachment->severity()) { 267 return false; 268 } 269 thatAttachment = thatAttachment->attachment_.get(); 270 } 271 return !thatAttachment; 272 } 273 274 bool Message::Merge(const Message &that) { 275 return AtSameLocation(that) && 276 (!that.attachment_.get() || 277 attachment_.get() == that.attachment_.get()) && 278 common::visit( 279 common::visitors{ 280 [](MessageExpectedText &e1, const MessageExpectedText &e2) { 281 return e1.Merge(e2); 282 }, 283 [](const auto &, const auto &) { return false; }, 284 }, 285 text_, that.text_); 286 } 287 288 Message &Message::Attach(Message *m) { 289 if (!attachment_) { 290 attachment_ = m; 291 } else { 292 if (attachment_->references() > 1) { 293 // Don't attach to a shared context attachment; copy it first. 294 attachment_ = new Message{*attachment_}; 295 } 296 attachment_->Attach(m); 297 } 298 return *this; 299 } 300 301 Message &Message::Attach(std::unique_ptr<Message> &&m) { 302 return Attach(m.release()); 303 } 304 305 bool Message::AtSameLocation(const Message &that) const { 306 return common::visit( 307 common::visitors{ 308 [](CharBlock cb1, CharBlock cb2) { 309 return cb1.begin() == cb2.begin(); 310 }, 311 [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) { 312 return pr1.start() == pr2.start(); 313 }, 314 [](const auto &, const auto &) { return false; }, 315 }, 316 location_, that.location_); 317 } 318 319 bool Messages::Merge(const Message &msg) { 320 if (msg.IsMergeable()) { 321 for (auto &m : messages_) { 322 if (m.Merge(msg)) { 323 return true; 324 } 325 } 326 } 327 return false; 328 } 329 330 void Messages::Merge(Messages &&that) { 331 if (messages_.empty()) { 332 *this = std::move(that); 333 } else { 334 while (!that.messages_.empty()) { 335 if (Merge(that.messages_.front())) { 336 that.messages_.pop_front(); 337 } else { 338 auto next{that.messages_.begin()}; 339 ++next; 340 messages_.splice( 341 messages_.end(), that.messages_, that.messages_.begin(), next); 342 } 343 } 344 } 345 } 346 347 void Messages::Copy(const Messages &that) { 348 for (const Message &m : that.messages_) { 349 Message copy{m}; 350 Say(std::move(copy)); 351 } 352 } 353 354 void Messages::ResolveProvenances(const AllCookedSources &allCooked) { 355 for (Message &m : messages_) { 356 m.ResolveProvenances(allCooked); 357 } 358 } 359 360 void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked, 361 bool echoSourceLines) const { 362 std::vector<const Message *> sorted; 363 for (const auto &msg : messages_) { 364 sorted.push_back(&msg); 365 } 366 std::stable_sort(sorted.begin(), sorted.end(), 367 [](const Message *x, const Message *y) { return x->SortBefore(*y); }); 368 const Message *lastMsg{nullptr}; 369 for (const Message *msg : sorted) { 370 if (lastMsg && *msg == *lastMsg) { 371 // Don't emit two identical messages for the same location 372 continue; 373 } 374 msg->Emit(o, allCooked, echoSourceLines); 375 lastMsg = msg; 376 } 377 } 378 379 void Messages::AttachTo(Message &msg, std::optional<Severity> severity) { 380 for (Message &m : messages_) { 381 Message m2{std::move(m)}; 382 if (severity) { 383 m2.set_severity(*severity); 384 } 385 msg.Attach(std::move(m2)); 386 } 387 messages_.clear(); 388 } 389 390 bool Messages::AnyFatalError() const { 391 for (const auto &msg : messages_) { 392 if (msg.IsFatal()) { 393 return true; 394 } 395 } 396 return false; 397 } 398 } // namespace Fortran::parser 399