xref: /llvm-project/flang/lib/Parser/provenance.cpp (revision 46ade6d0ef8fea94fbc28c75bb4bed4d928fd64b)
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