1 //===-- lib/Parser/provenance.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/provenance.h" 10 #include "flang/Common/idioms.h" 11 #include "llvm/Support/raw_ostream.h" 12 #include <algorithm> 13 #include <utility> 14 15 namespace Fortran::parser { 16 17 ProvenanceRangeToOffsetMappings::ProvenanceRangeToOffsetMappings() {} 18 ProvenanceRangeToOffsetMappings::~ProvenanceRangeToOffsetMappings() {} 19 20 void ProvenanceRangeToOffsetMappings::Put( 21 ProvenanceRange range, std::size_t offset) { 22 auto fromTo{map_.equal_range(range)}; 23 for (auto iter{fromTo.first}; iter != fromTo.second; ++iter) { 24 if (range == iter->first) { 25 iter->second = std::min(offset, iter->second); 26 return; 27 } 28 } 29 if (fromTo.second != map_.end()) { 30 map_.emplace_hint(fromTo.second, range, offset); 31 } else { 32 map_.emplace(range, offset); 33 } 34 } 35 36 std::optional<std::size_t> ProvenanceRangeToOffsetMappings::Map( 37 ProvenanceRange range) const { 38 auto fromTo{map_.equal_range(range)}; 39 std::optional<std::size_t> result; 40 for (auto iter{fromTo.first}; iter != fromTo.second; ++iter) { 41 ProvenanceRange that{iter->first}; 42 if (that.Contains(range)) { 43 std::size_t offset{iter->second + that.MemberOffset(range.start())}; 44 if (!result || offset < *result) { 45 result = offset; 46 } 47 } 48 } 49 return result; 50 } 51 52 bool ProvenanceRangeToOffsetMappings::WhollyPrecedes::operator()( 53 ProvenanceRange before, ProvenanceRange after) const { 54 return before.start() + before.size() <= after.start(); 55 } 56 57 void OffsetToProvenanceMappings::clear() { provenanceMap_.clear(); } 58 59 void OffsetToProvenanceMappings::swap(OffsetToProvenanceMappings &that) { 60 provenanceMap_.swap(that.provenanceMap_); 61 } 62 63 void OffsetToProvenanceMappings::shrink_to_fit() { 64 provenanceMap_.shrink_to_fit(); 65 } 66 67 std::size_t OffsetToProvenanceMappings::SizeInBytes() const { 68 if (provenanceMap_.empty()) { 69 return 0; 70 } else { 71 const ContiguousProvenanceMapping &last{provenanceMap_.back()}; 72 return last.start + last.range.size(); 73 } 74 } 75 76 void OffsetToProvenanceMappings::Put(ProvenanceRange range) { 77 if (provenanceMap_.empty()) { 78 provenanceMap_.push_back({0, range}); 79 } else { 80 ContiguousProvenanceMapping &last{provenanceMap_.back()}; 81 if (!last.range.AnnexIfPredecessor(range)) { 82 provenanceMap_.push_back({last.start + last.range.size(), range}); 83 } 84 } 85 } 86 87 void OffsetToProvenanceMappings::Put(const OffsetToProvenanceMappings &that) { 88 for (const auto &map : that.provenanceMap_) { 89 Put(map.range); 90 } 91 } 92 93 ProvenanceRange OffsetToProvenanceMappings::Map(std::size_t at) const { 94 // CHECK(!provenanceMap_.empty()); 95 std::size_t low{0}, count{provenanceMap_.size()}; 96 while (count > 1) { 97 std::size_t mid{low + (count >> 1)}; 98 if (provenanceMap_[mid].start > at) { 99 count = mid - low; 100 } else { 101 count -= mid - low; 102 low = mid; 103 } 104 } 105 std::size_t offset{at - provenanceMap_[low].start}; 106 return provenanceMap_[low].range.Suffix(offset); 107 } 108 109 void OffsetToProvenanceMappings::RemoveLastBytes(std::size_t bytes) { 110 for (; bytes > 0; provenanceMap_.pop_back()) { 111 CHECK(!provenanceMap_.empty()); 112 ContiguousProvenanceMapping &last{provenanceMap_.back()}; 113 std::size_t chunk{last.range.size()}; 114 if (bytes < chunk) { 115 last.range = last.range.Prefix(chunk - bytes); 116 break; 117 } 118 bytes -= chunk; 119 } 120 } 121 122 ProvenanceRangeToOffsetMappings OffsetToProvenanceMappings::Invert( 123 const AllSources &allSources) const { 124 ProvenanceRangeToOffsetMappings result; 125 for (const auto &contig : provenanceMap_) { 126 ProvenanceRange range{contig.range}; 127 while (!range.empty()) { 128 ProvenanceRange source{allSources.IntersectionWithSourceFiles(range)}; 129 if (source.empty()) { 130 break; 131 } 132 result.Put( 133 source, contig.start + contig.range.MemberOffset(source.start())); 134 Provenance after{source.NextAfter()}; 135 if (range.Contains(after)) { 136 range = range.Suffix(range.MemberOffset(after)); 137 } else { 138 break; 139 } 140 } 141 } 142 return result; 143 } 144 145 AllSources::AllSources() : range_{1, 1} { 146 // Start the origin_ array with a dummy entry that has a forced provenance, 147 // so that provenance offset 0 remains reserved as an uninitialized 148 // value. 149 origin_.emplace_back(range_, std::string{'?'}); 150 } 151 152 AllSources::~AllSources() {} 153 154 const char &AllSources::operator[](Provenance at) const { 155 const Origin &origin{MapToOrigin(at)}; 156 return origin[origin.covers.MemberOffset(at)]; 157 } 158 159 void AllSources::AppendSearchPathDirectory(std::string directory) { 160 // gfortran and ifort append to current path, PGI prepends 161 searchPath_.push_back(directory); 162 } 163 164 const SourceFile *AllSources::Open(std::string path, llvm::raw_ostream &error, 165 std::optional<std::string> &&prependPath) { 166 std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)}; 167 if (prependPath) { 168 // Set to "." for the initial source file; set to the directory name 169 // of the including file for #include "quoted-file" directives & 170 // INCLUDE statements. 171 searchPath_.emplace_front(std::move(*prependPath)); 172 } 173 std::optional<std::string> found{LocateSourceFile(path, searchPath_)}; 174 if (prependPath) { 175 searchPath_.pop_front(); 176 } 177 if (!found) { 178 error << "Source file '" << path << "' was not found"; 179 return nullptr; 180 } else if (source->Open(*found, error)) { 181 return ownedSourceFiles_.emplace_back(std::move(source)).get(); 182 } else { 183 return nullptr; 184 } 185 } 186 187 const SourceFile *AllSources::ReadStandardInput(llvm::raw_ostream &error) { 188 std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)}; 189 if (source->ReadStandardInput(error)) { 190 return ownedSourceFiles_.emplace_back(std::move(source)).get(); 191 } 192 return nullptr; 193 } 194 195 ProvenanceRange AllSources::AddIncludedFile( 196 const SourceFile &source, ProvenanceRange from, bool isModule) { 197 ProvenanceRange covers{range_.NextAfter(), source.bytes()}; 198 CHECK(range_.AnnexIfPredecessor(covers)); 199 CHECK(origin_.back().covers.ImmediatelyPrecedes(covers)); 200 origin_.emplace_back(covers, source, from, isModule); 201 return covers; 202 } 203 204 ProvenanceRange AllSources::AddMacroCall( 205 ProvenanceRange def, ProvenanceRange use, const std::string &expansion) { 206 ProvenanceRange covers{range_.NextAfter(), expansion.size()}; 207 CHECK(range_.AnnexIfPredecessor(covers)); 208 CHECK(origin_.back().covers.ImmediatelyPrecedes(covers)); 209 origin_.emplace_back(covers, def, use, expansion); 210 return covers; 211 } 212 213 ProvenanceRange AllSources::AddCompilerInsertion(std::string text) { 214 ProvenanceRange covers{range_.NextAfter(), text.size()}; 215 CHECK(range_.AnnexIfPredecessor(covers)); 216 CHECK(origin_.back().covers.ImmediatelyPrecedes(covers)); 217 origin_.emplace_back(covers, text); 218 return covers; 219 } 220 221 void AllSources::EmitMessage(llvm::raw_ostream &o, 222 const std::optional<ProvenanceRange> &range, const std::string &message, 223 bool echoSourceLine) const { 224 if (!range) { 225 o << message << '\n'; 226 return; 227 } 228 CHECK(IsValid(*range)); 229 const Origin &origin{MapToOrigin(range->start())}; 230 std::visit( 231 common::visitors{ 232 [&](const Inclusion &inc) { 233 o << inc.source.path(); 234 std::size_t offset{origin.covers.MemberOffset(range->start())}; 235 SourcePosition pos{inc.source.FindOffsetLineAndColumn(offset)}; 236 o << ':' << pos.line << ':' << pos.column; 237 o << ": " << message << '\n'; 238 if (echoSourceLine) { 239 const char *text{inc.source.content().data() + 240 inc.source.GetLineStartOffset(pos.line)}; 241 o << " "; 242 for (const char *p{text}; *p != '\n'; ++p) { 243 o << *p; 244 } 245 o << "\n "; 246 for (int j{1}; j < pos.column; ++j) { 247 char ch{text[j - 1]}; 248 o << (ch == '\t' ? '\t' : ' '); 249 } 250 o << '^'; 251 if (range->size() > 1) { 252 auto last{range->start() + range->size() - 1}; 253 if (&MapToOrigin(last) == &origin) { 254 auto endOffset{origin.covers.MemberOffset(last)}; 255 auto endPos{inc.source.FindOffsetLineAndColumn(endOffset)}; 256 if (pos.line == endPos.line) { 257 for (int j{pos.column}; j < endPos.column; ++j) { 258 o << '^'; 259 } 260 } 261 } 262 } 263 o << '\n'; 264 } 265 if (IsValid(origin.replaces)) { 266 EmitMessage(o, origin.replaces, 267 inc.isModule ? "used here"s : "included here"s, 268 echoSourceLine); 269 } 270 }, 271 [&](const Macro &mac) { 272 EmitMessage(o, origin.replaces, message, echoSourceLine); 273 EmitMessage( 274 o, mac.definition, "in a macro defined here", echoSourceLine); 275 if (echoSourceLine) { 276 o << "that expanded to:\n " << mac.expansion << "\n "; 277 for (std::size_t j{0}; 278 origin.covers.OffsetMember(j) < range->start(); ++j) { 279 o << (mac.expansion[j] == '\t' ? '\t' : ' '); 280 } 281 o << "^\n"; 282 } 283 }, 284 [&](const CompilerInsertion &) { o << message << '\n'; }, 285 }, 286 origin.u); 287 } 288 289 const SourceFile *AllSources::GetSourceFile( 290 Provenance at, std::size_t *offset) const { 291 const Origin &origin{MapToOrigin(at)}; 292 return std::visit(common::visitors{ 293 [&](const Inclusion &inc) { 294 if (offset) { 295 *offset = origin.covers.MemberOffset(at); 296 } 297 return &inc.source; 298 }, 299 [&](const Macro &) { 300 return GetSourceFile(origin.replaces.start(), offset); 301 }, 302 [offset](const CompilerInsertion &) { 303 if (offset) { 304 *offset = 0; 305 } 306 return static_cast<const SourceFile *>(nullptr); 307 }, 308 }, 309 origin.u); 310 } 311 312 const char *AllSources::GetSource(ProvenanceRange range) const { 313 Provenance start{range.start()}; 314 const Origin &origin{MapToOrigin(start)}; 315 return origin.covers.Contains(range) 316 ? &origin[origin.covers.MemberOffset(start)] 317 : nullptr; 318 } 319 320 std::optional<SourcePosition> AllSources::GetSourcePosition( 321 Provenance prov) const { 322 const Origin &origin{MapToOrigin(prov)}; 323 if (const auto *inc{std::get_if<Inclusion>(&origin.u)}) { 324 std::size_t offset{origin.covers.MemberOffset(prov)}; 325 return inc->source.FindOffsetLineAndColumn(offset); 326 } else { 327 return std::nullopt; 328 } 329 } 330 331 std::optional<ProvenanceRange> AllSources::GetFirstFileProvenance() const { 332 for (const auto &origin : origin_) { 333 if (std::holds_alternative<Inclusion>(origin.u)) { 334 return origin.covers; 335 } 336 } 337 return std::nullopt; 338 } 339 340 std::string AllSources::GetPath(Provenance at) const { 341 const SourceFile *source{GetSourceFile(at)}; 342 return source ? source->path() : ""s; 343 } 344 345 int AllSources::GetLineNumber(Provenance at) const { 346 std::size_t offset{0}; 347 const SourceFile *source{GetSourceFile(at, &offset)}; 348 return source ? source->FindOffsetLineAndColumn(offset).line : 0; 349 } 350 351 Provenance AllSources::CompilerInsertionProvenance(char ch) { 352 auto iter{compilerInsertionProvenance_.find(ch)}; 353 if (iter != compilerInsertionProvenance_.end()) { 354 return iter->second; 355 } 356 ProvenanceRange newCharRange{AddCompilerInsertion(std::string{ch})}; 357 Provenance newCharProvenance{newCharRange.start()}; 358 compilerInsertionProvenance_.insert(std::make_pair(ch, newCharProvenance)); 359 return newCharProvenance; 360 } 361 362 ProvenanceRange AllSources::IntersectionWithSourceFiles( 363 ProvenanceRange range) const { 364 if (range.empty()) { 365 return {}; 366 } else { 367 const Origin &origin{MapToOrigin(range.start())}; 368 if (std::holds_alternative<Inclusion>(origin.u)) { 369 return range.Intersection(origin.covers); 370 } else { 371 auto skip{ 372 origin.covers.size() - origin.covers.MemberOffset(range.start())}; 373 return IntersectionWithSourceFiles(range.Suffix(skip)); 374 } 375 } 376 } 377 378 AllSources::Origin::Origin(ProvenanceRange r, const SourceFile &source) 379 : u{Inclusion{source}}, covers{r} {} 380 AllSources::Origin::Origin(ProvenanceRange r, const SourceFile &included, 381 ProvenanceRange from, bool isModule) 382 : u{Inclusion{included, isModule}}, covers{r}, replaces{from} {} 383 AllSources::Origin::Origin(ProvenanceRange r, ProvenanceRange def, 384 ProvenanceRange use, const std::string &expansion) 385 : u{Macro{def, expansion}}, covers{r}, replaces{use} {} 386 AllSources::Origin::Origin(ProvenanceRange r, const std::string &text) 387 : u{CompilerInsertion{text}}, covers{r} {} 388 389 const char &AllSources::Origin::operator[](std::size_t n) const { 390 return std::visit( 391 common::visitors{ 392 [n](const Inclusion &inc) -> const char & { 393 return inc.source.content()[n]; 394 }, 395 [n](const Macro &mac) -> const char & { return mac.expansion[n]; }, 396 [n](const CompilerInsertion &ins) -> const char & { 397 return ins.text[n]; 398 }, 399 }, 400 u); 401 } 402 403 const AllSources::Origin &AllSources::MapToOrigin(Provenance at) const { 404 CHECK(range_.Contains(at)); 405 std::size_t low{0}, count{origin_.size()}; 406 while (count > 1) { 407 std::size_t mid{low + (count >> 1)}; 408 if (at < origin_[mid].covers.start()) { 409 count = mid - low; 410 } else { 411 count -= mid - low; 412 low = mid; 413 } 414 } 415 CHECK(origin_[low].covers.Contains(at)); 416 return origin_[low]; 417 } 418 419 std::optional<ProvenanceRange> CookedSource::GetProvenanceRange( 420 CharBlock cookedRange) const { 421 if (!AsCharBlock().Contains(cookedRange)) { 422 return std::nullopt; 423 } 424 ProvenanceRange first{provenanceMap_.Map(cookedRange.begin() - &data_[0])}; 425 if (cookedRange.size() <= first.size()) { 426 return first.Prefix(cookedRange.size()); 427 } 428 ProvenanceRange last{provenanceMap_.Map(cookedRange.end() - &data_[0])}; 429 return {ProvenanceRange{first.start(), last.start() - first.start()}}; 430 } 431 432 std::optional<CharBlock> CookedSource::GetCharBlock( 433 ProvenanceRange range) const { 434 CHECK(!invertedMap_.empty() && 435 "CompileProvenanceRangeToOffsetMappings not called"); 436 if (auto to{invertedMap_.Map(range)}) { 437 return CharBlock{data_.c_str() + *to, range.size()}; 438 } else { 439 return std::nullopt; 440 } 441 } 442 443 std::size_t CookedSource::BufferedBytes() const { return buffer_.bytes(); } 444 445 void CookedSource::Marshal(AllCookedSources &allCookedSources) { 446 CHECK(provenanceMap_.SizeInBytes() == buffer_.bytes()); 447 provenanceMap_.Put(allCookedSources.allSources().AddCompilerInsertion( 448 "(after end of source)")); 449 data_ = buffer_.Marshal(); 450 buffer_.clear(); 451 allCookedSources.Register(*this); 452 } 453 454 void CookedSource::CompileProvenanceRangeToOffsetMappings( 455 AllSources &allSources) { 456 if (invertedMap_.empty()) { 457 invertedMap_ = provenanceMap_.Invert(allSources); 458 } 459 } 460 461 static void DumpRange(llvm::raw_ostream &o, const ProvenanceRange &r) { 462 o << "[" << r.start().offset() << ".." << r.Last().offset() << "] (" 463 << r.size() << " bytes)"; 464 } 465 466 llvm::raw_ostream &ProvenanceRangeToOffsetMappings::Dump( 467 llvm::raw_ostream &o) const { 468 for (const auto &m : map_) { 469 o << "provenances "; 470 DumpRange(o, m.first); 471 o << " -> offsets [" << m.second << ".." << (m.second + m.first.size() - 1) 472 << "]\n"; 473 } 474 return o; 475 } 476 477 llvm::raw_ostream &OffsetToProvenanceMappings::Dump( 478 llvm::raw_ostream &o) const { 479 for (const ContiguousProvenanceMapping &m : provenanceMap_) { 480 std::size_t n{m.range.size()}; 481 o << "offsets [" << m.start << ".." << (m.start + n - 1) 482 << "] -> provenances "; 483 DumpRange(o, m.range); 484 o << '\n'; 485 } 486 return o; 487 } 488 489 llvm::raw_ostream &AllSources::Dump(llvm::raw_ostream &o) const { 490 o << "AllSources range_ "; 491 DumpRange(o, range_); 492 o << '\n'; 493 for (const Origin &m : origin_) { 494 o << " "; 495 DumpRange(o, m.covers); 496 o << " -> "; 497 std::visit(common::visitors{ 498 [&](const Inclusion &inc) { 499 if (inc.isModule) { 500 o << "module "; 501 } 502 o << "file " << inc.source.path(); 503 }, 504 [&](const Macro &mac) { o << "macro " << mac.expansion; }, 505 [&](const CompilerInsertion &ins) { 506 o << "compiler '" << ins.text << '\''; 507 if (ins.text.length() == 1) { 508 int ch = ins.text[0]; 509 o << "(0x"; 510 o.write_hex(ch & 0xff) << ")"; 511 } 512 }, 513 }, 514 m.u); 515 if (IsValid(m.replaces)) { 516 o << " replaces "; 517 DumpRange(o, m.replaces); 518 } 519 o << '\n'; 520 } 521 return o; 522 } 523 524 llvm::raw_ostream &CookedSource::Dump(llvm::raw_ostream &o) const { 525 o << "CookedSource::provenanceMap_:\n"; 526 provenanceMap_.Dump(o); 527 o << "CookedSource::invertedMap_:\n"; 528 invertedMap_.Dump(o); 529 return o; 530 } 531 532 AllCookedSources::AllCookedSources(AllSources &s) : allSources_{s} {} 533 AllCookedSources::~AllCookedSources() {} 534 535 CookedSource &AllCookedSources::NewCookedSource() { 536 return cooked_.emplace_back(); 537 } 538 539 const CookedSource *AllCookedSources::Find(CharBlock x) const { 540 auto pair{index_.equal_range(x)}; 541 for (auto iter{pair.first}; iter != pair.second; ++iter) { 542 if (iter->second.AsCharBlock().Contains(x)) { 543 return &iter->second; 544 } 545 } 546 return nullptr; 547 } 548 549 std::optional<ProvenanceRange> AllCookedSources::GetProvenanceRange( 550 CharBlock cb) const { 551 if (const CookedSource * c{Find(cb)}) { 552 return c->GetProvenanceRange(cb); 553 } else { 554 return std::nullopt; 555 } 556 } 557 558 std::optional<CharBlock> AllCookedSources::GetCharBlockFromLineAndColumns( 559 int line, int startColumn, int endColumn) const { 560 // 2nd column is exclusive, meaning it is target column + 1. 561 CHECK(line > 0 && startColumn > 0 && endColumn > 0); 562 CHECK(startColumn < endColumn); 563 auto provenanceStart{allSources_.GetFirstFileProvenance().value().start()}; 564 if (auto sourceFile{allSources_.GetSourceFile(provenanceStart)}) { 565 CHECK(line <= static_cast<int>(sourceFile->lines())); 566 return GetCharBlock(ProvenanceRange(sourceFile->GetLineStartOffset(line) + 567 provenanceStart.offset() + startColumn - 1, 568 endColumn - startColumn)); 569 } 570 return std::nullopt; 571 } 572 573 std::optional<std::pair<SourcePosition, SourcePosition>> 574 AllCookedSources::GetSourcePositionRange(CharBlock cookedRange) const { 575 if (auto range{GetProvenanceRange(cookedRange)}) { 576 if (auto firstOffset{allSources_.GetSourcePosition(range->start())}) { 577 if (auto secondOffset{ 578 allSources_.GetSourcePosition(range->start() + range->size())}) { 579 return std::pair{*firstOffset, *secondOffset}; 580 } 581 } 582 } 583 return std::nullopt; 584 } 585 586 std::optional<CharBlock> AllCookedSources::GetCharBlock( 587 ProvenanceRange range) const { 588 for (const auto &c : cooked_) { 589 if (auto result{c.GetCharBlock(range)}) { 590 return result; 591 } 592 } 593 return nullptr; 594 } 595 596 void AllCookedSources::Dump(llvm::raw_ostream &o) const { 597 o << "AllSources:\n"; 598 allSources_.Dump(o); 599 for (const auto &c : cooked_) { 600 c.Dump(o); 601 } 602 } 603 604 bool AllCookedSources::Precedes(CharBlock x, CharBlock y) const { 605 const CookedSource *ySource{Find(y)}; 606 if (const CookedSource * xSource{Find(x)}) { 607 if (ySource) { 608 int xNum{xSource->number()}; 609 int yNum{ySource->number()}; 610 return xNum < yNum || (xNum == yNum && x.begin() < y.begin()); 611 } else { 612 return true; // by fiat, all cooked source < anything outside 613 } 614 } else if (ySource) { 615 return false; 616 } else { 617 // Both names are compiler-created (SaveTempName). 618 return x < y; 619 } 620 } 621 622 void AllCookedSources::Register(CookedSource &cooked) { 623 index_.emplace(cooked.AsCharBlock(), cooked); 624 cooked.set_number(static_cast<int>(index_.size())); 625 } 626 627 } // namespace Fortran::parser 628