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
ProvenanceRangeToOffsetMappings()17 ProvenanceRangeToOffsetMappings::ProvenanceRangeToOffsetMappings() {}
~ProvenanceRangeToOffsetMappings()18 ProvenanceRangeToOffsetMappings::~ProvenanceRangeToOffsetMappings() {}
19
Put(ProvenanceRange range,std::size_t offset)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
Map(ProvenanceRange range) const36 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
operator ()(ProvenanceRange before,ProvenanceRange after) const52 bool ProvenanceRangeToOffsetMappings::WhollyPrecedes::operator()(
53 ProvenanceRange before, ProvenanceRange after) const {
54 return before.start() + before.size() <= after.start();
55 }
56
clear()57 void OffsetToProvenanceMappings::clear() { provenanceMap_.clear(); }
58
swap(OffsetToProvenanceMappings & that)59 void OffsetToProvenanceMappings::swap(OffsetToProvenanceMappings &that) {
60 provenanceMap_.swap(that.provenanceMap_);
61 }
62
shrink_to_fit()63 void OffsetToProvenanceMappings::shrink_to_fit() {
64 provenanceMap_.shrink_to_fit();
65 }
66
SizeInBytes() const67 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
Put(ProvenanceRange range)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
Put(const OffsetToProvenanceMappings & that)87 void OffsetToProvenanceMappings::Put(const OffsetToProvenanceMappings &that) {
88 for (const auto &map : that.provenanceMap_) {
89 Put(map.range);
90 }
91 }
92
Map(std::size_t at) const93 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
RemoveLastBytes(std::size_t bytes)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
Invert(const AllSources & allSources) const122 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
AllSources()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
~AllSources()152 AllSources::~AllSources() {}
153
operator [](Provenance at) const154 const char &AllSources::operator[](Provenance at) const {
155 const Origin &origin{MapToOrigin(at)};
156 return origin[origin.covers.MemberOffset(at)];
157 }
158
PushSearchPathDirectory(std::string directory)159 void AllSources::PushSearchPathDirectory(std::string directory) {
160 // gfortran and ifort append to current path, PGI prepends
161 searchPath_.push_back(directory);
162 }
163
PopSearchPathDirectory()164 std::string AllSources::PopSearchPathDirectory() {
165 std::string directory{searchPath_.back()};
166 searchPath_.pop_back();
167 return directory;
168 }
169
Open(std::string path,llvm::raw_ostream & error)170 const SourceFile *AllSources::Open(std::string path, llvm::raw_ostream &error) {
171 std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)};
172 if (source->Open(LocateSourceFile(path, searchPath_), error)) {
173 return ownedSourceFiles_.emplace_back(std::move(source)).get();
174 } else {
175 return nullptr;
176 }
177 }
178
ReadStandardInput(llvm::raw_ostream & error)179 const SourceFile *AllSources::ReadStandardInput(llvm::raw_ostream &error) {
180 std::unique_ptr<SourceFile> source{std::make_unique<SourceFile>(encoding_)};
181 if (source->ReadStandardInput(error)) {
182 return ownedSourceFiles_.emplace_back(std::move(source)).get();
183 }
184 return nullptr;
185 }
186
AddIncludedFile(const SourceFile & source,ProvenanceRange from,bool isModule)187 ProvenanceRange AllSources::AddIncludedFile(
188 const SourceFile &source, ProvenanceRange from, bool isModule) {
189 ProvenanceRange covers{range_.NextAfter(), source.bytes()};
190 CHECK(range_.AnnexIfPredecessor(covers));
191 CHECK(origin_.back().covers.ImmediatelyPrecedes(covers));
192 origin_.emplace_back(covers, source, from, isModule);
193 return covers;
194 }
195
AddMacroCall(ProvenanceRange def,ProvenanceRange use,const std::string & expansion)196 ProvenanceRange AllSources::AddMacroCall(
197 ProvenanceRange def, ProvenanceRange use, const std::string &expansion) {
198 ProvenanceRange covers{range_.NextAfter(), expansion.size()};
199 CHECK(range_.AnnexIfPredecessor(covers));
200 CHECK(origin_.back().covers.ImmediatelyPrecedes(covers));
201 origin_.emplace_back(covers, def, use, expansion);
202 return covers;
203 }
204
AddCompilerInsertion(std::string text)205 ProvenanceRange AllSources::AddCompilerInsertion(std::string text) {
206 ProvenanceRange covers{range_.NextAfter(), text.size()};
207 CHECK(range_.AnnexIfPredecessor(covers));
208 CHECK(origin_.back().covers.ImmediatelyPrecedes(covers));
209 origin_.emplace_back(covers, text);
210 return covers;
211 }
212
EmitMessage(llvm::raw_ostream & o,const std::optional<ProvenanceRange> & range,const std::string & message,bool echoSourceLine) const213 void AllSources::EmitMessage(llvm::raw_ostream &o,
214 const std::optional<ProvenanceRange> &range, const std::string &message,
215 bool echoSourceLine) const {
216 if (!range) {
217 o << message << '\n';
218 return;
219 }
220 CHECK(IsValid(*range));
221 const Origin &origin{MapToOrigin(range->start())};
222 std::visit(
223 common::visitors{
224 [&](const Inclusion &inc) {
225 o << inc.source.path();
226 std::size_t offset{origin.covers.MemberOffset(range->start())};
227 SourcePosition pos{inc.source.FindOffsetLineAndColumn(offset)};
228 o << ':' << pos.line << ':' << pos.column;
229 o << ": " << message << '\n';
230 if (echoSourceLine) {
231 const char *text{inc.source.content().data() +
232 inc.source.GetLineStartOffset(pos.line)};
233 o << " ";
234 for (const char *p{text}; *p != '\n'; ++p) {
235 o << *p;
236 }
237 o << "\n ";
238 for (int j{1}; j < pos.column; ++j) {
239 char ch{text[j - 1]};
240 o << (ch == '\t' ? '\t' : ' ');
241 }
242 o << '^';
243 if (range->size() > 1) {
244 auto last{range->start() + range->size() - 1};
245 if (&MapToOrigin(last) == &origin) {
246 auto endOffset{origin.covers.MemberOffset(last)};
247 auto endPos{inc.source.FindOffsetLineAndColumn(endOffset)};
248 if (pos.line == endPos.line) {
249 for (int j{pos.column}; j < endPos.column; ++j) {
250 o << '^';
251 }
252 }
253 }
254 }
255 o << '\n';
256 }
257 if (IsValid(origin.replaces)) {
258 EmitMessage(o, origin.replaces,
259 inc.isModule ? "used here"s : "included here"s,
260 echoSourceLine);
261 }
262 },
263 [&](const Macro &mac) {
264 EmitMessage(o, origin.replaces, message, echoSourceLine);
265 EmitMessage(
266 o, mac.definition, "in a macro defined here", echoSourceLine);
267 if (echoSourceLine) {
268 o << "that expanded to:\n " << mac.expansion << "\n ";
269 for (std::size_t j{0};
270 origin.covers.OffsetMember(j) < range->start(); ++j) {
271 o << (mac.expansion[j] == '\t' ? '\t' : ' ');
272 }
273 o << "^\n";
274 }
275 },
276 [&](const CompilerInsertion &) { o << message << '\n'; },
277 },
278 origin.u);
279 }
280
GetSourceFile(Provenance at,std::size_t * offset) const281 const SourceFile *AllSources::GetSourceFile(
282 Provenance at, std::size_t *offset) const {
283 const Origin &origin{MapToOrigin(at)};
284 return std::visit(common::visitors{
285 [&](const Inclusion &inc) {
286 if (offset) {
287 *offset = origin.covers.MemberOffset(at);
288 }
289 return &inc.source;
290 },
291 [&](const Macro &) {
292 return GetSourceFile(origin.replaces.start(), offset);
293 },
294 [offset](const CompilerInsertion &) {
295 if (offset) {
296 *offset = 0;
297 }
298 return static_cast<const SourceFile *>(nullptr);
299 },
300 },
301 origin.u);
302 }
303
GetSource(ProvenanceRange range) const304 const char *AllSources::GetSource(ProvenanceRange range) const {
305 Provenance start{range.start()};
306 const Origin &origin{MapToOrigin(start)};
307 return origin.covers.Contains(range)
308 ? &origin[origin.covers.MemberOffset(start)]
309 : nullptr;
310 }
311
GetSourcePosition(Provenance prov) const312 std::optional<SourcePosition> AllSources::GetSourcePosition(
313 Provenance prov) const {
314 const Origin &origin{MapToOrigin(prov)};
315 if (const auto *inc{std::get_if<Inclusion>(&origin.u)}) {
316 std::size_t offset{origin.covers.MemberOffset(prov)};
317 return inc->source.FindOffsetLineAndColumn(offset);
318 } else {
319 return std::nullopt;
320 }
321 }
322
GetFirstFileProvenance() const323 std::optional<ProvenanceRange> AllSources::GetFirstFileProvenance() const {
324 for (const auto &origin : origin_) {
325 if (std::holds_alternative<Inclusion>(origin.u)) {
326 return origin.covers;
327 }
328 }
329 return std::nullopt;
330 }
331
GetPath(Provenance at) const332 std::string AllSources::GetPath(Provenance at) const {
333 const SourceFile *source{GetSourceFile(at)};
334 return source ? source->path() : ""s;
335 }
336
GetLineNumber(Provenance at) const337 int AllSources::GetLineNumber(Provenance at) const {
338 std::size_t offset{0};
339 const SourceFile *source{GetSourceFile(at, &offset)};
340 return source ? source->FindOffsetLineAndColumn(offset).line : 0;
341 }
342
CompilerInsertionProvenance(char ch)343 Provenance AllSources::CompilerInsertionProvenance(char ch) {
344 auto iter{compilerInsertionProvenance_.find(ch)};
345 if (iter != compilerInsertionProvenance_.end()) {
346 return iter->second;
347 }
348 ProvenanceRange newCharRange{AddCompilerInsertion(std::string{ch})};
349 Provenance newCharProvenance{newCharRange.start()};
350 compilerInsertionProvenance_.insert(std::make_pair(ch, newCharProvenance));
351 return newCharProvenance;
352 }
353
IntersectionWithSourceFiles(ProvenanceRange range) const354 ProvenanceRange AllSources::IntersectionWithSourceFiles(
355 ProvenanceRange range) const {
356 if (range.empty()) {
357 return {};
358 } else {
359 const Origin &origin{MapToOrigin(range.start())};
360 if (std::holds_alternative<Inclusion>(origin.u)) {
361 return range.Intersection(origin.covers);
362 } else {
363 auto skip{
364 origin.covers.size() - origin.covers.MemberOffset(range.start())};
365 return IntersectionWithSourceFiles(range.Suffix(skip));
366 }
367 }
368 }
369
Origin(ProvenanceRange r,const SourceFile & source)370 AllSources::Origin::Origin(ProvenanceRange r, const SourceFile &source)
371 : u{Inclusion{source}}, covers{r} {}
Origin(ProvenanceRange r,const SourceFile & included,ProvenanceRange from,bool isModule)372 AllSources::Origin::Origin(ProvenanceRange r, const SourceFile &included,
373 ProvenanceRange from, bool isModule)
374 : u{Inclusion{included, isModule}}, covers{r}, replaces{from} {}
Origin(ProvenanceRange r,ProvenanceRange def,ProvenanceRange use,const std::string & expansion)375 AllSources::Origin::Origin(ProvenanceRange r, ProvenanceRange def,
376 ProvenanceRange use, const std::string &expansion)
377 : u{Macro{def, expansion}}, covers{r}, replaces{use} {}
Origin(ProvenanceRange r,const std::string & text)378 AllSources::Origin::Origin(ProvenanceRange r, const std::string &text)
379 : u{CompilerInsertion{text}}, covers{r} {}
380
operator [](std::size_t n) const381 const char &AllSources::Origin::operator[](std::size_t n) const {
382 return std::visit(
383 common::visitors{
384 [n](const Inclusion &inc) -> const char & {
385 return inc.source.content()[n];
386 },
387 [n](const Macro &mac) -> const char & { return mac.expansion[n]; },
388 [n](const CompilerInsertion &ins) -> const char & {
389 return ins.text[n];
390 },
391 },
392 u);
393 }
394
MapToOrigin(Provenance at) const395 const AllSources::Origin &AllSources::MapToOrigin(Provenance at) const {
396 CHECK(range_.Contains(at));
397 std::size_t low{0}, count{origin_.size()};
398 while (count > 1) {
399 std::size_t mid{low + (count >> 1)};
400 if (at < origin_[mid].covers.start()) {
401 count = mid - low;
402 } else {
403 count -= mid - low;
404 low = mid;
405 }
406 }
407 CHECK(origin_[low].covers.Contains(at));
408 return origin_[low];
409 }
410
GetProvenanceRange(CharBlock cookedRange) const411 std::optional<ProvenanceRange> CookedSource::GetProvenanceRange(
412 CharBlock cookedRange) const {
413 if (!AsCharBlock().Contains(cookedRange)) {
414 return std::nullopt;
415 }
416 ProvenanceRange first{provenanceMap_.Map(cookedRange.begin() - &data_[0])};
417 if (cookedRange.size() <= first.size()) {
418 return first.Prefix(cookedRange.size());
419 }
420 ProvenanceRange last{provenanceMap_.Map(cookedRange.end() - &data_[0])};
421 return {ProvenanceRange{first.start(), last.start() - first.start()}};
422 }
423
GetCharBlock(ProvenanceRange range) const424 std::optional<CharBlock> CookedSource::GetCharBlock(
425 ProvenanceRange range) const {
426 CHECK(!invertedMap_.empty() &&
427 "CompileProvenanceRangeToOffsetMappings not called");
428 if (auto to{invertedMap_.Map(range)}) {
429 return CharBlock{data_.c_str() + *to, range.size()};
430 } else {
431 return std::nullopt;
432 }
433 }
434
BufferedBytes() const435 std::size_t CookedSource::BufferedBytes() const { return buffer_.bytes(); }
436
Marshal(AllSources & allSources)437 void CookedSource::Marshal(AllSources &allSources) {
438 CHECK(provenanceMap_.SizeInBytes() == buffer_.bytes());
439 provenanceMap_.Put(allSources.AddCompilerInsertion("(after end of source)"));
440 data_ = buffer_.Marshal();
441 buffer_.clear();
442 }
443
CompileProvenanceRangeToOffsetMappings(AllSources & allSources)444 void CookedSource::CompileProvenanceRangeToOffsetMappings(
445 AllSources &allSources) {
446 if (invertedMap_.empty()) {
447 invertedMap_ = provenanceMap_.Invert(allSources);
448 }
449 }
450
DumpRange(llvm::raw_ostream & o,const ProvenanceRange & r)451 static void DumpRange(llvm::raw_ostream &o, const ProvenanceRange &r) {
452 o << "[" << r.start().offset() << ".." << r.Last().offset() << "] ("
453 << r.size() << " bytes)";
454 }
455
Dump(llvm::raw_ostream & o) const456 llvm::raw_ostream &ProvenanceRangeToOffsetMappings::Dump(
457 llvm::raw_ostream &o) const {
458 for (const auto &m : map_) {
459 o << "provenances ";
460 DumpRange(o, m.first);
461 o << " -> offsets [" << m.second << ".." << (m.second + m.first.size() - 1)
462 << "]\n";
463 }
464 return o;
465 }
466
Dump(llvm::raw_ostream & o) const467 llvm::raw_ostream &OffsetToProvenanceMappings::Dump(
468 llvm::raw_ostream &o) const {
469 for (const ContiguousProvenanceMapping &m : provenanceMap_) {
470 std::size_t n{m.range.size()};
471 o << "offsets [" << m.start << ".." << (m.start + n - 1)
472 << "] -> provenances ";
473 DumpRange(o, m.range);
474 o << '\n';
475 }
476 return o;
477 }
478
Dump(llvm::raw_ostream & o) const479 llvm::raw_ostream &AllSources::Dump(llvm::raw_ostream &o) const {
480 o << "AllSources range_ ";
481 DumpRange(o, range_);
482 o << '\n';
483 for (const Origin &m : origin_) {
484 o << " ";
485 DumpRange(o, m.covers);
486 o << " -> ";
487 std::visit(common::visitors{
488 [&](const Inclusion &inc) {
489 if (inc.isModule) {
490 o << "module ";
491 }
492 o << "file " << inc.source.path();
493 },
494 [&](const Macro &mac) { o << "macro " << mac.expansion; },
495 [&](const CompilerInsertion &ins) {
496 o << "compiler '" << ins.text << '\'';
497 if (ins.text.length() == 1) {
498 int ch = ins.text[0];
499 o << "(0x";
500 o.write_hex(ch & 0xff) << ")";
501 }
502 },
503 },
504 m.u);
505 if (IsValid(m.replaces)) {
506 o << " replaces ";
507 DumpRange(o, m.replaces);
508 }
509 o << '\n';
510 }
511 return o;
512 }
513
Dump(llvm::raw_ostream & o) const514 llvm::raw_ostream &CookedSource::Dump(llvm::raw_ostream &o) const {
515 o << "CookedSource::provenanceMap_:\n";
516 provenanceMap_.Dump(o);
517 o << "CookedSource::invertedMap_:\n";
518 invertedMap_.Dump(o);
519 return o;
520 }
521
AllCookedSources(AllSources & s)522 AllCookedSources::AllCookedSources(AllSources &s) : allSources_{s} {}
~AllCookedSources()523 AllCookedSources::~AllCookedSources() {}
524
NewCookedSource()525 CookedSource &AllCookedSources::NewCookedSource() {
526 return cooked_.emplace_back();
527 }
528
GetProvenanceRange(CharBlock cb) const529 std::optional<ProvenanceRange> AllCookedSources::GetProvenanceRange(
530 CharBlock cb) const {
531 if (const CookedSource * c{Find(cb)}) {
532 return c->GetProvenanceRange(cb);
533 } else {
534 return std::nullopt;
535 }
536 }
537
GetCharBlockFromLineAndColumns(int line,int startColumn,int endColumn) const538 std::optional<CharBlock> AllCookedSources::GetCharBlockFromLineAndColumns(
539 int line, int startColumn, int endColumn) const {
540 // 2nd column is exclusive, meaning it is target column + 1.
541 CHECK(line > 0 && startColumn > 0 && endColumn > 0);
542 CHECK(startColumn < endColumn);
543 auto provenanceStart{allSources_.GetFirstFileProvenance().value().start()};
544 if (auto sourceFile{allSources_.GetSourceFile(provenanceStart)}) {
545 CHECK(line <= static_cast<int>(sourceFile->lines()));
546 return GetCharBlock(ProvenanceRange(sourceFile->GetLineStartOffset(line) +
547 provenanceStart.offset() + startColumn - 1,
548 endColumn - startColumn));
549 }
550 return std::nullopt;
551 }
552
553 std::optional<std::pair<SourcePosition, SourcePosition>>
GetSourcePositionRange(CharBlock cookedRange) const554 AllCookedSources::GetSourcePositionRange(CharBlock cookedRange) const {
555 if (auto range{GetProvenanceRange(cookedRange)}) {
556 if (auto firstOffset{allSources_.GetSourcePosition(range->start())}) {
557 if (auto secondOffset{
558 allSources_.GetSourcePosition(range->start() + range->size())}) {
559 return std::pair{*firstOffset, *secondOffset};
560 }
561 }
562 }
563 return std::nullopt;
564 }
565
GetCharBlock(ProvenanceRange range) const566 std::optional<CharBlock> AllCookedSources::GetCharBlock(
567 ProvenanceRange range) const {
568 for (const auto &c : cooked_) {
569 if (auto result{c.GetCharBlock(range)}) {
570 return result;
571 }
572 }
573 return nullptr;
574 }
575
Dump(llvm::raw_ostream & o) const576 void AllCookedSources::Dump(llvm::raw_ostream &o) const {
577 o << "AllSources:\n";
578 allSources_.Dump(o);
579 for (const auto &c : cooked_) {
580 c.Dump(o);
581 }
582 }
583
584 } // namespace Fortran::parser
585