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 int need{vsnprintf(nullptr, 0, p, ap)}; 42 CHECK(need >= 0); 43 char *buffer{ 44 static_cast<char *>(std::malloc(static_cast<std::size_t>(need) + 1))}; 45 CHECK(buffer); 46 va_end(ap); 47 va_start(ap, text); 48 int need2{vsnprintf(buffer, need + 1, p, ap)}; 49 CHECK(need2 == need); 50 va_end(ap); 51 string_ = buffer; 52 std::free(buffer); 53 conversions_.clear(); 54 } 55 56 const char *MessageFormattedText::Convert(const std::string &s) { 57 conversions_.emplace_front(s); 58 return conversions_.front().c_str(); 59 } 60 61 const char *MessageFormattedText::Convert(std::string &s) { 62 conversions_.emplace_front(s); 63 return conversions_.front().c_str(); 64 } 65 66 const char *MessageFormattedText::Convert(std::string &&s) { 67 conversions_.emplace_front(std::move(s)); 68 return conversions_.front().c_str(); 69 } 70 71 const char *MessageFormattedText::Convert(CharBlock x) { 72 return Convert(x.ToString()); 73 } 74 75 std::string MessageExpectedText::ToString() const { 76 return std::visit( 77 common::visitors{ 78 [](CharBlock cb) { 79 return MessageFormattedText("expected '%s'"_err_en_US, cb) 80 .MoveString(); 81 }, 82 [](const SetOfChars &set) { 83 SetOfChars expect{set}; 84 if (expect.Has('\n')) { 85 expect = expect.Difference('\n'); 86 if (expect.empty()) { 87 return "expected end of line"_err_en_US.text().ToString(); 88 } else { 89 std::string s{expect.ToString()}; 90 if (s.size() == 1) { 91 return MessageFormattedText( 92 "expected end of line or '%s'"_err_en_US, s) 93 .MoveString(); 94 } else { 95 return MessageFormattedText( 96 "expected end of line or one of '%s'"_err_en_US, s) 97 .MoveString(); 98 } 99 } 100 } 101 std::string s{expect.ToString()}; 102 if (s.size() != 1) { 103 return MessageFormattedText("expected one of '%s'"_err_en_US, s) 104 .MoveString(); 105 } else { 106 return MessageFormattedText("expected '%s'"_err_en_US, s) 107 .MoveString(); 108 } 109 }, 110 }, 111 u_); 112 } 113 114 bool MessageExpectedText::Merge(const MessageExpectedText &that) { 115 return std::visit( 116 common::visitors{ 117 [](SetOfChars &s1, const SetOfChars &s2) { 118 s1 = s1.Union(s2); 119 return true; 120 }, 121 [](const auto &, const auto &) { return false; }, 122 }, 123 u_, that.u_); 124 } 125 126 bool Message::SortBefore(const Message &that) const { 127 // Messages from prescanning have ProvenanceRange values for their locations, 128 // while messages from later phases have CharBlock values, since the 129 // conversion of cooked source stream locations to provenances is not 130 // free and needs to be deferred, and many messages created during parsing 131 // are speculative. Messages with ProvenanceRange locations are ordered 132 // before others for sorting. 133 return std::visit( 134 common::visitors{ 135 [](CharBlock cb1, CharBlock cb2) { 136 return cb1.begin() < cb2.begin(); 137 }, 138 [](CharBlock, const ProvenanceRange &) { return false; }, 139 [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) { 140 return pr1.start() < pr2.start(); 141 }, 142 [](const ProvenanceRange &, CharBlock) { return true; }, 143 }, 144 location_, that.location_); 145 } 146 147 bool Message::IsFatal() const { 148 return std::visit( 149 common::visitors{ 150 [](const MessageExpectedText &) { return true; }, 151 [](const MessageFixedText &x) { return x.isFatal(); }, 152 [](const MessageFormattedText &x) { return x.isFatal(); }, 153 }, 154 text_); 155 } 156 157 std::string Message::ToString() const { 158 return std::visit( 159 common::visitors{ 160 [](const MessageFixedText &t) { 161 return t.text().NULTerminatedToString(); 162 }, 163 [](const MessageFormattedText &t) { return t.string(); }, 164 [](const MessageExpectedText &e) { return e.ToString(); }, 165 }, 166 text_); 167 } 168 169 void Message::ResolveProvenances(const CookedSource &cooked) { 170 if (CharBlock * cb{std::get_if<CharBlock>(&location_)}) { 171 if (std::optional<ProvenanceRange> resolved{ 172 cooked.GetProvenanceRange(*cb)}) { 173 location_ = *resolved; 174 } 175 } 176 if (Message * attachment{attachment_.get()}) { 177 attachment->ResolveProvenances(cooked); 178 } 179 } 180 181 std::optional<ProvenanceRange> Message::GetProvenanceRange( 182 const CookedSource &cooked) const { 183 return std::visit( 184 common::visitors{ 185 [&](CharBlock cb) { return cooked.GetProvenanceRange(cb); }, 186 [](const ProvenanceRange &pr) { return std::make_optional(pr); }, 187 }, 188 location_); 189 } 190 191 void Message::Emit(llvm::raw_ostream &o, const CookedSource &cooked, 192 bool echoSourceLine) const { 193 std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(cooked)}; 194 std::string text; 195 if (IsFatal()) { 196 text += "error: "; 197 } 198 text += ToString(); 199 const AllSources &sources{cooked.allSources()}; 200 sources.EmitMessage(o, provenanceRange, text, echoSourceLine); 201 if (attachmentIsContext_) { 202 for (const Message *context{attachment_.get()}; context; 203 context = context->attachment_.get()) { 204 std::optional<ProvenanceRange> contextProvenance{ 205 context->GetProvenanceRange(cooked)}; 206 text = "in the context: "; 207 text += context->ToString(); 208 // TODO: don't echo the source lines of a context when it's the 209 // same line (or maybe just never echo source for context) 210 sources.EmitMessage(o, contextProvenance, text, 211 echoSourceLine && contextProvenance != provenanceRange); 212 provenanceRange = contextProvenance; 213 } 214 } else { 215 for (const Message *attachment{attachment_.get()}; attachment; 216 attachment = attachment->attachment_.get()) { 217 sources.EmitMessage(o, attachment->GetProvenanceRange(cooked), 218 attachment->ToString(), echoSourceLine); 219 } 220 } 221 } 222 223 bool Message::Merge(const Message &that) { 224 return AtSameLocation(that) && 225 (!that.attachment_.get() || 226 attachment_.get() == that.attachment_.get()) && 227 std::visit( 228 common::visitors{ 229 [](MessageExpectedText &e1, const MessageExpectedText &e2) { 230 return e1.Merge(e2); 231 }, 232 [](const auto &, const auto &) { return false; }, 233 }, 234 text_, that.text_); 235 } 236 237 Message &Message::Attach(Message *m) { 238 if (!attachment_) { 239 attachment_ = m; 240 } else { 241 attachment_->Attach(m); 242 } 243 return *this; 244 } 245 246 Message &Message::Attach(std::unique_ptr<Message> &&m) { 247 return Attach(m.release()); 248 } 249 250 bool Message::AtSameLocation(const Message &that) const { 251 return std::visit( 252 common::visitors{ 253 [](CharBlock cb1, CharBlock cb2) { 254 return cb1.begin() == cb2.begin(); 255 }, 256 [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) { 257 return pr1.start() == pr2.start(); 258 }, 259 [](const auto &, const auto &) { return false; }, 260 }, 261 location_, that.location_); 262 } 263 264 void Messages::clear() { 265 messages_.clear(); 266 ResetLastPointer(); 267 } 268 269 bool Messages::Merge(const Message &msg) { 270 if (msg.IsMergeable()) { 271 for (auto &m : messages_) { 272 if (m.Merge(msg)) { 273 return true; 274 } 275 } 276 } 277 return false; 278 } 279 280 void Messages::Merge(Messages &&that) { 281 if (messages_.empty()) { 282 *this = std::move(that); 283 } else { 284 while (!that.messages_.empty()) { 285 if (Merge(that.messages_.front())) { 286 that.messages_.pop_front(); 287 } else { 288 messages_.splice_after( 289 last_, that.messages_, that.messages_.before_begin()); 290 ++last_; 291 } 292 } 293 that.ResetLastPointer(); 294 } 295 } 296 297 void Messages::Copy(const Messages &that) { 298 for (const Message &m : that.messages_) { 299 Message copy{m}; 300 Say(std::move(copy)); 301 } 302 } 303 304 void Messages::ResolveProvenances(const CookedSource &cooked) { 305 for (Message &m : messages_) { 306 m.ResolveProvenances(cooked); 307 } 308 } 309 310 void Messages::Emit(llvm::raw_ostream &o, const CookedSource &cooked, 311 bool echoSourceLines) const { 312 std::vector<const Message *> sorted; 313 for (const auto &msg : messages_) { 314 sorted.push_back(&msg); 315 } 316 std::stable_sort(sorted.begin(), sorted.end(), 317 [](const Message *x, const Message *y) { return x->SortBefore(*y); }); 318 for (const Message *msg : sorted) { 319 msg->Emit(o, cooked, echoSourceLines); 320 } 321 } 322 323 void Messages::AttachTo(Message &msg) { 324 for (Message &m : messages_) { 325 msg.Attach(std::move(m)); 326 } 327 messages_.clear(); 328 } 329 330 bool Messages::AnyFatalError() const { 331 for (const auto &msg : messages_) { 332 if (msg.IsFatal()) { 333 return true; 334 } 335 } 336 return false; 337 } 338 } 339