Skip to content

GcovCounter.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include "GcovCounter.hpp"
#include "YAMLParser.hpp"

void GcovCounter::count(const std::filesystem::path& coverage, Entity& root) {
  /*
  * A sketch of the format is:
  * ```
  *        -:    0:Source:/path/to/source/file.cpp
  *        -:    1:#include <iotream>>
  *        -:    4:
  *        -:    5:using namespace std;
  *        -:    6:
  *        1:    7:int main(int argc, char** argv) {
  *        1:    8:  if (true) {
  *        1:    9:    std::cout << "Hello, world!" << std::endl;
  *        -:   10:  } else {
  *    #####:   11:    std::cout << "Goodbye, world!" << std::endl;
  *        -:   12:  }
  *        1:   13:  return 0;
  *        -:   14:}
  * ```
  * The first column gives counts, `#####` indicating zero count on an
  * executable line. The second column gives line numbers, with the count
  * starting from one. Counts followed by `*` indicate an aggregate count over
  * function template instantiations; the function template instantiations
  * then follow, separated by lines of dashes, with disaggregated counts.
  * These disaggregated counts do not always seem complete, and so the
  * aggregated counts are used instead---the disagregated counts are readily
  * recognized and ignored due to the repetition of line numbers seen before.
  */
  static const std::regex regex_source("^\\s*-:\\s*0:Source:(.*)$",
      regex_flags);
  static const std::regex regex_covered("^\\s*(\\d+)\\*?:\\s*(\\d+):.*$",
      regex_flags);

  std::ifstream in(coverage);
  if (!in.is_open()) {
    throw std::runtime_error("could not read file " + coverage.string());
  }

  std::filesystem::path path;
  bool include = false;

  std::list<Entity*> es;
  Entity* file = nullptr;
  size_t nlines = 0;  // number of lines in file
  int max_line_number = 0;  // highest line number seen so far
  std::string line;
  std::smatch match;

  while (std::getline(in, line)) {
    if (std::regex_match(line, match, regex_source)) {
      /* start of new file */
      path = match[1].str();
      if (path.is_absolute()) {
        path = std::filesystem::relative(path);
      }
      include = root.exists(path);
      if (include) {
        es = root.get(path);
        file = es.back();
        nlines = file->line_counts.size();
        max_line_number = 0;
      }
    } else if (include && std::regex_match(line, match, regex_covered)) {
      /* line data */
      int line_number = std::stoi(match[2]) - 1;
      int count = std::stoi(match[1]);
      if (line_number < 0 || size_t(line_number) >= nlines) {
        warn("in " << file << ", " << path << ":" << line_number <<
            " does not exist; ignoring, are source and coverage" <<
            " files in sync?");
      } else if (count > 0 && line_number > max_line_number) {
        // ^ the check against max_line_number means skipping lines with
        //   disaggregated counts that have already been accounted for in
        //   aggregated counts
        int& line_count = file->line_counts[line_number];
        if (line_count == 0) {
          /* line is included in report and first time seeing it covered,
           * update aggregate counts for whole path */
          for (auto e : es) {
            ++e->lines_covered;
          }
        }
        if (line_count >= 0) {
          /* line is included in report, update count */
          line_count += count;
        }
      }
      max_line_number = std::max(max_line_number, line_number);
    }
  }
}