• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- mode: C++ -*-
3 //
4 // Copyright 2022-2023 Google LLC
5 //
6 // Licensed under the Apache License v2.0 with LLVM Exceptions (the
7 // "License"); you may not use this file except in compliance with the
8 // License.  You may obtain a copy of the License at
9 //
10 //     https://llvm.org/LICENSE.txt
11 //
12 // Unless required by applicable law or agreed to in writing, software
13 // distributed under the License is distributed on an "AS IS" BASIS,
14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 // See the License for the specific language governing permissions and
16 // limitations under the License.
17 //
18 // Author: Siddharth Nayyar
19 
20 #include "post_processing.h"
21 
22 #include <algorithm>
23 #include <cstddef>
24 #include <iostream>
25 #include <map>
26 #include <ostream>
27 #include <regex>
28 #include <sstream>
29 #include <string>
30 #include <unordered_map>
31 #include <utility>
32 #include <vector>
33 
34 namespace stg {
35 
SummariseCRCChanges(const std::vector<std::string> & report,size_t limit)36 std::vector<std::string> SummariseCRCChanges(
37     const std::vector<std::string>& report, size_t limit) {
38   const std::regex symbol_changed_re("^.* symbol .* changed$");
39   const std::regex crc_re("^  CRC changed from [^ ]* to [^ ]*$");
40   const std::regex empty_re("^$");
41   const std::regex section_re("^[^ \\n].*$");
42   const std::regex symbol_re("^.* symbol .*$");
43 
44   std::vector<std::string> new_report;
45   std::vector<std::pair<std::string, std::string>> pending;
46 
47   auto emit_pending = [&]() {
48     const size_t crc_only_changes = pending.size();
49     for (size_t ix = 0; ix < std::min(crc_only_changes, limit); ++ix) {
50       new_report.push_back(pending[ix].first);
51       new_report.push_back(pending[ix].second);
52       new_report.emplace_back();
53     }
54     if (crc_only_changes > limit) {
55       std::ostringstream os;
56       os << "... " << crc_only_changes - limit << " omitted; "
57          << crc_only_changes << " symbols have only CRC changes";
58       new_report.push_back(os.str());
59       new_report.emplace_back();
60     }
61     pending.clear();
62   };
63 
64   for (size_t ix = 0; ix < report.size(); ++ix) {
65     if (std::regex_match(report[ix], section_re) &&
66         !std::regex_match(report[ix], symbol_re)) {
67       emit_pending();
68       new_report.push_back(report[ix]);
69     } else if (ix + 2 < report.size() &&
70                std::regex_match(report[ix], symbol_changed_re) &&
71                std::regex_match(report[ix + 1], crc_re) &&
72                std::regex_match(report[ix + 2], empty_re)) {
73       pending.emplace_back(report[ix], report[ix + 1]);
74       // consumed 3 lines in total => 2 extra lines
75       ix += 2;
76     } else {
77       new_report.push_back(report[ix]);
78     }
79   }
80 
81   emit_pending();
82   return new_report;
83 }
84 
SummariseOffsetChanges(const std::vector<std::string> & report)85 std::vector<std::string> SummariseOffsetChanges(
86     const std::vector<std::string>& report) {
87   const std::regex re1("^( *)member ('.*') changed$");
88   const std::regex re2("^( *)offset changed from (\\d+) to (\\d+)$");
89   const std::regex re3("^( *).*$");
90 
91   std::smatch match1;
92   std::smatch match2;
93   std::smatch match3;
94   size_t indent = 0;
95   int64_t offset = 0;
96   std::vector<std::string> vars;
97   std::vector<std::string> new_report;
98 
99   auto emit_pending = [&]() {
100     if (vars.empty()) {
101       return;
102     }
103     std::ostringstream line1;
104     line1 << std::string(indent, ' ');
105     if (vars.size() == 1) {
106       line1 << "member " << vars.front() << " changed";
107     } else {
108       line1 << vars.size() << " members (" << vars.front() << " .. "
109             << vars.back() << ") changed";
110     }
111     new_report.push_back(line1.str());
112     std::ostringstream line2;
113     line2 << std::string(indent, ' ') << "  offset changed by " << offset;
114     new_report.push_back(line2.str());
115     vars.clear();
116   };
117 
118   for (size_t ix = 0; ix < report.size(); ++ix) {
119     if (ix + 2 < report.size() && std::regex_match(report[ix], match1, re1) &&
120         std::regex_match(report[ix + 1], match2, re2) &&
121         std::regex_match(report[ix + 2], match3, re3)) {
122       const size_t indent1 = match1[1].length();
123       const size_t indent2 = match2[1].length();
124       const size_t indent3 = match3[1].length();
125       if (indent1 + 2 == indent2 && indent1 >= indent3) {
126         const auto new_indent = indent1;
127         int64_t new_offset =
128             std::stoll(match2[3].str()) - std::stoll(match2[2].str());
129         if (new_indent != indent || new_offset != offset) {
130           emit_pending();
131           indent = new_indent;
132           offset = new_offset;
133         }
134         vars.push_back(match1[2]);
135         // consumed 2 lines in total => 1 extra line
136         ++ix;
137         continue;
138       }
139     }
140     emit_pending();
141     new_report.push_back(report[ix]);
142   }
143 
144   emit_pending();
145   return new_report;
146 }
147 
GroupRemovedAddedSymbols(const std::vector<std::string> & report)148 std::vector<std::string> GroupRemovedAddedSymbols(
149     const std::vector<std::string>& report) {
150   const std::regex symbol_re("^(.*) symbol (.*) was (added|removed)$");
151   const std::regex empty_re("^$");
152 
153   std::vector<std::string> new_report;
154   std::unordered_map<std::string,
155       std::map<std::string, std::vector<std::string>>> pending;
156 
157   auto emit_pending = [&]() {
158     for (const auto& which : {"removed", "added"}) {
159       auto& pending_kinds = pending[which];
160       for (auto& [kind, pending_symbols] : pending_kinds) {
161         if (!pending_symbols.empty()) {
162           std::ostringstream os;
163           os << pending_symbols.size() << ' ' << kind << " symbol(s) " << which;
164           new_report.push_back(os.str());
165           for (const auto& symbol : std::exchange(pending_symbols, {})) {
166             new_report.push_back("  " + symbol);
167           }
168           new_report.emplace_back();
169         }
170       }
171     }
172   };
173 
174   for (size_t ix = 0; ix < report.size(); ++ix) {
175     std::smatch match;
176     if (ix + 1 < report.size() &&
177         std::regex_match(report[ix], match, symbol_re) &&
178         std::regex_match(report[ix + 1], empty_re)) {
179       pending[match[3].str()][match[1].str()].push_back(match[2].str());
180       // consumed 2 lines in total => 1 extra line (there is always an empty
181       // line after symbol added/removed line)
182       ++ix;
183     } else {
184       emit_pending();
185       new_report.push_back(report[ix]);
186     }
187   }
188 
189   emit_pending();
190   return new_report;
191 }
192 
PostProcess(const std::vector<std::string> & report,size_t max_crc_only_changes)193 std::vector<std::string> PostProcess(const std::vector<std::string>& report,
194                                      size_t max_crc_only_changes) {
195   std::vector<std::string> new_report;
196   new_report = SummariseCRCChanges(report, max_crc_only_changes);
197   new_report = GroupRemovedAddedSymbols(new_report);
198   new_report = SummariseOffsetChanges(new_report);
199   return new_report;
200 }
201 
202 }  // namespace stg
203