|
36 | 36 | #include <cstring> |
37 | 37 | #include <fstream> |
38 | 38 | #include <iomanip> |
39 | | -#include <numeric> |
| 39 | +#include <ios> |
| 40 | +#include <list> |
40 | 41 | #include <sstream> |
41 | 42 | #include <string> |
42 | 43 | #include <unordered_map> |
43 | 44 | #include <utility> |
| 45 | +#include <vector> |
44 | 46 |
|
45 | 47 | #include "xml.h" |
46 | 48 |
|
@@ -605,13 +607,57 @@ std::string ErrorMessage::toXML() const |
605 | 607 | return printer.CStr(); |
606 | 608 | } |
607 | 609 |
|
| 610 | +// Byte offset of the start of each line, indexed by 1-based line number. |
| 611 | +// Building this once per file keeps readCode() cheap. Without it every call |
| 612 | +// rescans the file from the start, which is quadratic for a file that produces |
| 613 | +// many findings - vendor/generated headers routinely produce tens of thousands. |
| 614 | +static const std::vector<std::streamoff>* getLineOffsets(const std::string &file) |
| 615 | +{ |
| 616 | + // thread_local: the thread executor calls this concurrently |
| 617 | + static thread_local std::list<std::pair<std::string, std::vector<std::streamoff>>> cache; |
| 618 | + |
| 619 | + for (auto it = cache.begin(); it != cache.end(); ++it) { |
| 620 | + if (it->first == file) { |
| 621 | + // most recently used goes first |
| 622 | + cache.splice(cache.begin(), cache, it); |
| 623 | + return &cache.front().second; |
| 624 | + } |
| 625 | + } |
| 626 | + |
| 627 | + // Binary mode so the offsets match what the seek in readCode() expects. |
| 628 | + // A trailing '\r' is stripped by the caller. |
| 629 | + std::ifstream fin(file, std::ios::binary); |
| 630 | + if (!fin.is_open()) |
| 631 | + return nullptr; |
| 632 | + |
| 633 | + std::vector<std::streamoff> offsets{0, 0}; // index 0 is unused, line 1 starts at offset 0 |
| 634 | + std::array<char, 64 * 1024> buf; |
| 635 | + std::streamoff pos = 0; |
| 636 | + while (fin.read(buf.data(), buf.size()) || fin.gcount() > 0) { |
| 637 | + const std::streamsize n = fin.gcount(); |
| 638 | + for (std::streamsize i = 0; i < n; ++i) { |
| 639 | + if (buf[i] == '\n') |
| 640 | + offsets.push_back(pos + i + 1); |
| 641 | + } |
| 642 | + pos += n; |
| 643 | + } |
| 644 | + |
| 645 | + constexpr std::size_t maxCachedFiles = 4; |
| 646 | + if (cache.size() >= maxCachedFiles) |
| 647 | + cache.pop_back(); |
| 648 | + cache.emplace_front(file, std::move(offsets)); |
| 649 | + return &cache.front().second; |
| 650 | +} |
| 651 | + |
608 | 652 | // TODO: read info from some shared resource instead? |
609 | 653 | static std::string readCode(const std::string &file, int linenr, int column, const char endl[]) |
610 | 654 | { |
611 | | - std::ifstream fin(file); |
612 | 655 | std::string line; |
613 | | - while (linenr > 0 && std::getline(fin,line)) { |
614 | | - linenr--; |
| 656 | + const std::vector<std::streamoff>* const offsets = getLineOffsets(file); |
| 657 | + if (offsets && linenr > 0 && linenr < static_cast<int>(offsets->size())) { |
| 658 | + std::ifstream fin(file, std::ios::binary); |
| 659 | + fin.seekg((*offsets)[linenr]); |
| 660 | + std::getline(fin, line); |
615 | 661 | } |
616 | 662 | const std::string::size_type endPos = line.find_last_not_of("\r\n\t "); |
617 | 663 | if (endPos + 1 < line.size()) |
|
0 commit comments