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