1 // Copyright (c) 2018-2019, NVIDIA CORPORATION.  All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "provenance.h"
16 #include "../common/idioms.h"
17 #include <algorithm>
18 #include <utility>
19 
20 namespace Fortran::parser {
21 
ProvenanceRangeToOffsetMappings()22 ProvenanceRangeToOffsetMappings::ProvenanceRangeToOffsetMappings() {}
~ProvenanceRangeToOffsetMappings()23 ProvenanceRangeToOffsetMappings::~ProvenanceRangeToOffsetMappings() {}
24 
Put(ProvenanceRange range,std::size_t offset)25 void ProvenanceRangeToOffsetMappings::Put(
26     ProvenanceRange range, std::size_t offset) {
27   auto fromTo{map_.equal_range(range)};
28   for (auto iter{fromTo.first}; iter != fromTo.second; ++iter) {
29     if (range == iter->first) {
30       iter->second = std::min(offset, iter->second);
31       return;
32     }
33   }
34   if (fromTo.second != map_.end()) {
35     map_.emplace_hint(fromTo.second, range, offset);
36   } else {
37     map_.emplace(range, offset);
38   }
39 }
40 
Map(ProvenanceRange range) const41 std::optional<std::size_t> ProvenanceRangeToOffsetMappings::Map(
42     ProvenanceRange range) const {
43   auto fromTo{map_.equal_range(range)};
44   std::optional<std::size_t> result;
45   for (auto iter{fromTo.first}; iter != fromTo.second; ++iter) {
46     ProvenanceRange that{iter->first};
47     if (that.Contains(range)) {
48       std::size_t offset{iter->second + that.MemberOffset(range.start())};
49       if (!result.has_value() || offset < *result) {
50         result = offset;
51       }
52     }
53   }
54   return result;
55 }
56 
operator ()(ProvenanceRange before,ProvenanceRange after) const57 bool ProvenanceRangeToOffsetMappings::WhollyPrecedes::operator()(
58     ProvenanceRange before, ProvenanceRange after) const {
59   return before.start() + before.size() <= after.start();
60 }
61 
clear()62 void OffsetToProvenanceMappings::clear() { provenanceMap_.clear(); }
63 
swap(OffsetToProvenanceMappings & that)64 void OffsetToProvenanceMappings::swap(OffsetToProvenanceMappings &that) {
65   provenanceMap_.swap(that.provenanceMap_);
66 }
67 
shrink_to_fit()68 void OffsetToProvenanceMappings::shrink_to_fit() {
69   provenanceMap_.shrink_to_fit();
70 }
71 
SizeInBytes() const72 std::size_t OffsetToProvenanceMappings::SizeInBytes() const {
73   if (provenanceMap_.empty()) {
74     return 0;
75   } else {
76     const ContiguousProvenanceMapping &last{provenanceMap_.back()};
77     return last.start + last.range.size();
78   }
79 }
80 
Put(ProvenanceRange range)81 void OffsetToProvenanceMappings::Put(ProvenanceRange range) {
82   if (provenanceMap_.empty()) {
83     provenanceMap_.push_back({0, range});
84   } else {
85     ContiguousProvenanceMapping &last{provenanceMap_.back()};
86     if (!last.range.AnnexIfPredecessor(range)) {
87       provenanceMap_.push_back({last.start + last.range.size(), range});
88     }
89   }
90 }
91 
Put(const OffsetToProvenanceMappings & that)92 void OffsetToProvenanceMappings::Put(const OffsetToProvenanceMappings &that) {
93   for (const auto &map : that.provenanceMap_) {
94     Put(map.range);
95   }
96 }
97 
Map(std::size_t at) const98 ProvenanceRange OffsetToProvenanceMappings::Map(std::size_t at) const {
99   //  CHECK(!provenanceMap_.empty());
100   std::size_t low{0}, count{provenanceMap_.size()};
101   while (count > 1) {
102     std::size_t mid{low + (count >> 1)};
103     if (provenanceMap_[mid].start > at) {
104       count = mid - low;
105     } else {
106       count -= mid - low;
107       low = mid;
108     }
109   }
110   std::size_t offset{at - provenanceMap_[low].start};
111   return provenanceMap_[low].range.Suffix(offset);
112 }
113 
RemoveLastBytes(std::size_t bytes)114 void OffsetToProvenanceMappings::RemoveLastBytes(std::size_t bytes) {
115   for (; bytes > 0; provenanceMap_.pop_back()) {
116     CHECK(!provenanceMap_.empty());
117     ContiguousProvenanceMapping &last{provenanceMap_.back()};
118     std::size_t chunk{last.range.size()};
119     if (bytes < chunk) {
120       last.range = last.range.Prefix(chunk - bytes);
121       break;
122     }
123     bytes -= chunk;
124   }
125 }
126 
Invert(const AllSources & allSources) const127 ProvenanceRangeToOffsetMappings OffsetToProvenanceMappings::Invert(
128     const AllSources &allSources) const {
129   ProvenanceRangeToOffsetMappings result;
130   for (const auto &contig : provenanceMap_) {
131     ProvenanceRange range{contig.range};
132     while (!range.empty()) {
133       ProvenanceRange source{allSources.IntersectionWithSourceFiles(range)};
134       if (source.empty()) {
135         break;
136       }
137       result.Put(
138           source, contig.start + contig.range.MemberOffset(source.start()));
139       Provenance after{source.NextAfter()};
140       if (range.Contains(after)) {
141         range = range.Suffix(range.MemberOffset(after));
142       } else {
143         break;
144       }
145     }
146   }
147   return result;
148 }
149 
AllSources()150 AllSources::AllSources() : range_{1, 1} {
151   // Start the origin_ array with a dummy entry that has a forced provenance,
152   // so that provenance offset 0 remains reserved as an uninitialized
153   // value.
154   origin_.emplace_back(range_, std::string{'?'});
155 }
156 
~AllSources()157 AllSources::~AllSources() {}
158 
operator [](Provenance at) const159 const char &AllSources::operator[](Provenance at) const {
160   const Origin &origin{MapToOrigin(at)};
161   return origin[origin.covers.MemberOffset(at)];
162 }
163 
PushSearchPathDirectory(std::string directory)164 void AllSources::PushSearchPathDirectory(std::string directory) {
165   // gfortran and ifort append to current path, PGI prepends
166   searchPath_.push_back(directory);
167 }
168 
PopSearchPathDirectory()169 std::string AllSources::PopSearchPathDirectory() {
170   std::string directory{searchPath_.back()};
171   searchPath_.pop_back();
172   return directory;
173 }
174 
Open(std::string path,std::stringstream * error)175 const SourceFile *AllSources::Open(std::string path, std::stringstream *error) {
176   std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)};
177   source->Open(LocateSourceFile(path, searchPath_), error);
178   return ownedSourceFiles_.emplace_back(std::move(source)).get();
179 }
180 
ReadStandardInput(std::stringstream * error)181 const SourceFile *AllSources::ReadStandardInput(std::stringstream *error) {
182   std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)};
183   if (source->ReadStandardInput(error)) {
184     return ownedSourceFiles_.emplace_back(std::move(source)).get();
185   }
186   return nullptr;
187 }
188 
AddIncludedFile(const SourceFile & source,ProvenanceRange from,bool isModule)189 ProvenanceRange AllSources::AddIncludedFile(
190     const SourceFile &source, ProvenanceRange from, bool isModule) {
191   ProvenanceRange covers{range_.NextAfter(), source.bytes()};
192   CHECK(range_.AnnexIfPredecessor(covers));
193   CHECK(origin_.back().covers.ImmediatelyPrecedes(covers));
194   origin_.emplace_back(covers, source, from, isModule);
195   return covers;
196 }
197 
AddMacroCall(ProvenanceRange def,ProvenanceRange use,const std::string & expansion)198 ProvenanceRange AllSources::AddMacroCall(
199     ProvenanceRange def, ProvenanceRange use, const std::string &expansion) {
200   ProvenanceRange covers{range_.NextAfter(), expansion.size()};
201   CHECK(range_.AnnexIfPredecessor(covers));
202   CHECK(origin_.back().covers.ImmediatelyPrecedes(covers));
203   origin_.emplace_back(covers, def, use, expansion);
204   return covers;
205 }
206 
AddCompilerInsertion(std::string text)207 ProvenanceRange AllSources::AddCompilerInsertion(std::string text) {
208   ProvenanceRange covers{range_.NextAfter(), text.size()};
209   CHECK(range_.AnnexIfPredecessor(covers));
210   CHECK(origin_.back().covers.ImmediatelyPrecedes(covers));
211   origin_.emplace_back(covers, text);
212   return covers;
213 }
214 
EmitMessage(std::ostream & o,const std::optional<ProvenanceRange> & range,const std::string & message,bool echoSourceLine) const215 void AllSources::EmitMessage(std::ostream &o,
216     const std::optional<ProvenanceRange> &range, const std::string &message,
217     bool echoSourceLine) const {
218   if (!range.has_value()) {
219     o << message << '\n';
220     return;
221   }
222   CHECK(IsValid(*range));
223   const Origin &origin{MapToOrigin(range->start())};
224   std::visit(
225       common::visitors{
226           [&](const Inclusion &inc) {
227             o << inc.source.path();
228             std::size_t offset{origin.covers.MemberOffset(range->start())};
229             SourcePosition pos{inc.source.FindOffsetLineAndColumn(offset)};
230             o << ':' << pos.line << ':' << pos.column;
231             o << ": " << message << '\n';
232             if (echoSourceLine) {
233               const char *text{inc.source.content() +
234                   inc.source.GetLineStartOffset(pos.line)};
235               o << "  ";
236               for (const char *p{text}; *p != '\n'; ++p) {
237                 o << *p;
238               }
239               o << "\n  ";
240               for (int j{1}; j < pos.column; ++j) {
241                 char ch{text[j - 1]};
242                 o << (ch == '\t' ? '\t' : ' ');
243               }
244               o << '^';
245               if (range->size() > 1) {
246                 auto last{range->start() + range->size() - 1};
247                 if (&MapToOrigin(last) == &origin) {
248                   auto endOffset{origin.covers.MemberOffset(last)};
249                   auto endPos{inc.source.FindOffsetLineAndColumn(endOffset)};
250                   if (pos.line == endPos.line) {
251                     for (int j{pos.column}; j < endPos.column; ++j) {
252                       o << '^';
253                     }
254                   }
255                 }
256               }
257               o << '\n';
258             }
259             if (IsValid(origin.replaces)) {
260               EmitMessage(o, origin.replaces,
261                   inc.isModule ? "used here"s : "included here"s,
262                   echoSourceLine);
263             }
264           },
265           [&](const Macro &mac) {
266             EmitMessage(o, origin.replaces, message, echoSourceLine);
267             EmitMessage(
268                 o, mac.definition, "in a macro defined here", echoSourceLine);
269             if (echoSourceLine) {
270               o << "that expanded to:\n  " << mac.expansion << "\n  ";
271               for (std::size_t j{0};
272                    origin.covers.OffsetMember(j) < range->start(); ++j) {
273                 o << (mac.expansion[j] == '\t' ? '\t' : ' ');
274               }
275               o << "^\n";
276             }
277           },
278           [&](const CompilerInsertion &) { o << message << '\n'; },
279       },
280       origin.u);
281 }
282 
GetSourceFile(Provenance at,std::size_t * offset) const283 const SourceFile *AllSources::GetSourceFile(
284     Provenance at, std::size_t *offset) const {
285   const Origin &origin{MapToOrigin(at)};
286   return std::visit(
287       common::visitors{
288           [&](const Inclusion &inc) {
289             if (offset != nullptr) {
290               *offset = origin.covers.MemberOffset(at);
291             }
292             return &inc.source;
293           },
294           [&](const Macro &) {
295             return GetSourceFile(origin.replaces.start(), offset);
296           },
297           [offset](const CompilerInsertion &) {
298             if (offset != nullptr) {
299               *offset = 0;
300             }
301             return static_cast<const SourceFile *>(nullptr);
302           },
303       },
304       origin.u);
305 }
306 
GetSourcePosition(Provenance prov) const307 std::optional<SourcePosition> AllSources::GetSourcePosition(
308     Provenance prov) const {
309   const Origin &origin{MapToOrigin(prov)};
310   if (const auto *inc{std::get_if<Inclusion>(&origin.u)}) {
311     std::size_t offset{origin.covers.MemberOffset(prov)};
312     return inc->source.FindOffsetLineAndColumn(offset);
313   } else {
314     return std::nullopt;
315   }
316 }
317 
GetFirstFileProvenance() const318 std::optional<ProvenanceRange> AllSources::GetFirstFileProvenance() const {
319   for (const auto &origin : origin_) {
320     if (std::holds_alternative<Inclusion>(origin.u)) {
321       return origin.covers;
322     }
323   }
324   return std::nullopt;
325 }
326 
GetPath(Provenance at) const327 std::string AllSources::GetPath(Provenance at) const {
328   const SourceFile *source{GetSourceFile(at)};
329   return source ? source->path() : ""s;
330 }
331 
GetLineNumber(Provenance at) const332 int AllSources::GetLineNumber(Provenance at) const {
333   std::size_t offset{0};
334   const SourceFile *source{GetSourceFile(at, &offset)};
335   return source ? source->FindOffsetLineAndColumn(offset).line : 0;
336 }
337 
CompilerInsertionProvenance(char ch)338 Provenance AllSources::CompilerInsertionProvenance(char ch) {
339   auto iter{compilerInsertionProvenance_.find(ch)};
340   if (iter != compilerInsertionProvenance_.end()) {
341     return iter->second;
342   }
343   ProvenanceRange newCharRange{AddCompilerInsertion(std::string{ch})};
344   Provenance newCharProvenance{newCharRange.start()};
345   compilerInsertionProvenance_.insert(std::make_pair(ch, newCharProvenance));
346   return newCharProvenance;
347 }
348 
IntersectionWithSourceFiles(ProvenanceRange range) const349 ProvenanceRange AllSources::IntersectionWithSourceFiles(
350     ProvenanceRange range) const {
351   if (range.empty()) {
352     return {};
353   } else {
354     const Origin &origin{MapToOrigin(range.start())};
355     if (std::holds_alternative<Inclusion>(origin.u)) {
356       return range.Intersection(origin.covers);
357     } else {
358       auto skip{
359           origin.covers.size() - origin.covers.MemberOffset(range.start())};
360       return IntersectionWithSourceFiles(range.Suffix(skip));
361     }
362   }
363 }
364 
Origin(ProvenanceRange r,const SourceFile & source)365 AllSources::Origin::Origin(ProvenanceRange r, const SourceFile &source)
366   : u{Inclusion{source}}, covers{r} {}
Origin(ProvenanceRange r,const SourceFile & included,ProvenanceRange from,bool isModule)367 AllSources::Origin::Origin(ProvenanceRange r, const SourceFile &included,
368     ProvenanceRange from, bool isModule)
369   : u{Inclusion{included, isModule}}, covers{r}, replaces{from} {}
Origin(ProvenanceRange r,ProvenanceRange def,ProvenanceRange use,const std::string & expansion)370 AllSources::Origin::Origin(ProvenanceRange r, ProvenanceRange def,
371     ProvenanceRange use, const std::string &expansion)
372   : u{Macro{def, expansion}}, covers{r}, replaces{use} {}
Origin(ProvenanceRange r,const std::string & text)373 AllSources::Origin::Origin(ProvenanceRange r, const std::string &text)
374   : u{CompilerInsertion{text}}, covers{r} {}
375 
operator [](std::size_t n) const376 const char &AllSources::Origin::operator[](std::size_t n) const {
377   return std::visit(
378       common::visitors{
379           [n](const Inclusion &inc) -> const char & {
380             return inc.source.content()[n];
381           },
382           [n](const Macro &mac) -> const char & { return mac.expansion[n]; },
383           [n](const CompilerInsertion &ins) -> const char & {
384             return ins.text[n];
385           },
386       },
387       u);
388 }
389 
MapToOrigin(Provenance at) const390 const AllSources::Origin &AllSources::MapToOrigin(Provenance at) const {
391   CHECK(range_.Contains(at));
392   std::size_t low{0}, count{origin_.size()};
393   while (count > 1) {
394     std::size_t mid{low + (count >> 1)};
395     if (at < origin_[mid].covers.start()) {
396       count = mid - low;
397     } else {
398       count -= mid - low;
399       low = mid;
400     }
401   }
402   CHECK(origin_[low].covers.Contains(at));
403   return origin_[low];
404 }
405 
CookedSource(AllSources & s)406 CookedSource::CookedSource(AllSources &s) : allSources_{s} {}
~CookedSource()407 CookedSource::~CookedSource() {}
408 
GetProvenanceRange(CharBlock cookedRange) const409 std::optional<ProvenanceRange> CookedSource::GetProvenanceRange(
410     CharBlock cookedRange) const {
411   if (!IsValid(cookedRange)) {
412     return std::nullopt;
413   }
414   ProvenanceRange first{provenanceMap_.Map(cookedRange.begin() - &data_[0])};
415   if (cookedRange.size() <= first.size()) {
416     return first.Prefix(cookedRange.size());
417   }
418   ProvenanceRange last{provenanceMap_.Map(cookedRange.end() - &data_[0])};
419   return {ProvenanceRange{first.start(), last.start() - first.start()}};
420 }
421 
GetCharBlockFromLineAndColumns(int line,int startColumn,int endColumn) const422 std::optional<CharBlock> CookedSource::GetCharBlockFromLineAndColumns(
423     int line, int startColumn, int endColumn) const {
424   // 2nd column is exclusive, meaning it is target column + 1.
425   CHECK(line > 0 && startColumn > 0 && endColumn > 0);
426   CHECK(startColumn < endColumn);
427   auto provenanceStart{allSources_.GetFirstFileProvenance().value().start()};
428   if (auto sourceFile{allSources_.GetSourceFile(provenanceStart)}) {
429     CHECK(line <= static_cast<int>(sourceFile->lines()));
430     return GetCharBlock(ProvenanceRange(sourceFile->GetLineStartOffset(line) +
431             provenanceStart.offset() + startColumn - 1,
432         endColumn - startColumn));
433   }
434   return std::nullopt;
435 }
436 
437 std::optional<std::pair<SourcePosition, SourcePosition>>
GetSourcePositionRange(CharBlock cookedRange) const438 CookedSource::GetSourcePositionRange(CharBlock cookedRange) const {
439   if (auto range{GetProvenanceRange(cookedRange)}) {
440     if (auto firstOffset{allSources_.GetSourcePosition(range->start())}) {
441       if (auto secondOffset{
442               allSources_.GetSourcePosition(range->start() + range->size())}) {
443         return std::pair{*firstOffset, *secondOffset};
444       }
445     }
446   }
447   return std::nullopt;
448 }
449 
GetCharBlock(ProvenanceRange range) const450 std::optional<CharBlock> CookedSource::GetCharBlock(
451     ProvenanceRange range) const {
452   CHECK(!invertedMap_.empty() &&
453       "CompileProvenanceRangeToOffsetMappings not called");
454   if (auto to{invertedMap_.Map(range)}) {
455     return CharBlock{data_.c_str() + *to, range.size()};
456   } else {
457     return std::nullopt;
458   }
459 }
460 
Marshal()461 void CookedSource::Marshal() {
462   CHECK(provenanceMap_.SizeInBytes() == buffer_.bytes());
463   provenanceMap_.Put(allSources_.AddCompilerInsertion("(after end of source)"));
464   data_ = buffer_.Marshal();
465   buffer_.clear();
466 }
467 
CompileProvenanceRangeToOffsetMappings()468 void CookedSource::CompileProvenanceRangeToOffsetMappings() {
469   if (invertedMap_.empty()) {
470     invertedMap_ = provenanceMap_.Invert(allSources_);
471   }
472 }
473 
DumpRange(std::ostream & o,const ProvenanceRange & r)474 static void DumpRange(std::ostream &o, const ProvenanceRange &r) {
475   o << "[" << r.start().offset() << ".." << r.Last().offset() << "] ("
476     << r.size() << " bytes)";
477 }
478 
Dump(std::ostream & o) const479 std::ostream &ProvenanceRangeToOffsetMappings::Dump(std::ostream &o) const {
480   for (const auto &m : map_) {
481     o << "provenances ";
482     DumpRange(o, m.first);
483     o << " -> offsets [" << m.second << ".." << (m.second + m.first.size() - 1)
484       << "]\n";
485   }
486   return o;
487 }
488 
Dump(std::ostream & o) const489 std::ostream &OffsetToProvenanceMappings::Dump(std::ostream &o) const {
490   for (const ContiguousProvenanceMapping &m : provenanceMap_) {
491     std::size_t n{m.range.size()};
492     o << "offsets [" << m.start << ".." << (m.start + n - 1)
493       << "] -> provenances ";
494     DumpRange(o, m.range);
495     o << '\n';
496   }
497   return o;
498 }
499 
Dump(std::ostream & o) const500 std::ostream &AllSources::Dump(std::ostream &o) const {
501   o << "AllSources range_ ";
502   DumpRange(o, range_);
503   o << '\n';
504   for (const Origin &m : origin_) {
505     o << "   ";
506     DumpRange(o, m.covers);
507     o << " -> ";
508     std::visit(
509         common::visitors{
510             [&](const Inclusion &inc) {
511               if (inc.isModule) {
512                 o << "module ";
513               }
514               o << "file " << inc.source.path();
515             },
516             [&](const Macro &mac) { o << "macro " << mac.expansion; },
517             [&](const CompilerInsertion &ins) {
518               o << "compiler '" << ins.text << '\'';
519               if (ins.text.length() == 1) {
520                 int ch = ins.text[0];
521                 o << " (0x" << std::hex << (ch & 0xff) << std::dec << ")";
522               }
523             },
524         },
525         m.u);
526     if (IsValid(m.replaces)) {
527       o << " replaces ";
528       DumpRange(o, m.replaces);
529     }
530     o << '\n';
531   }
532   return o;
533 }
534 
Dump(std::ostream & o) const535 std::ostream &CookedSource::Dump(std::ostream &o) const {
536   o << "CookedSource:\n";
537   allSources_.Dump(o);
538   o << "CookedSource::provenanceMap_:\n";
539   provenanceMap_.Dump(o);
540   o << "CookedSource::invertedMap_:\n";
541   invertedMap_.Dump(o);
542   return o;
543 }
544 }
545