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