blob: 9aafeb4bb2b497a32c3ecf8c6ec50f96c1772682 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- mode: C++ -*-
//
// Copyright 2022-2023 Google LLC
//
// Licensed under the Apache License v2.0 with LLVM Exceptions (the
// "License"); you may not use this file except in compliance with the
// License. You may obtain a copy of the License at
//
// https://llvm.org/LICENSE.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Siddharth Nayyar
#include "post_processing.h"
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <map>
#include <ostream>
#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace stg {
std::vector<std::string> SummariseCRCChanges(
const std::vector<std::string>& report, size_t limit) {
const std::regex symbol_changed_re("^.* symbol .* changed$");
const std::regex crc_re("^ CRC changed from [^ ]* to [^ ]*$");
const std::regex empty_re("^$");
const std::regex section_re("^[^ \\n].*$");
const std::regex symbol_re("^.* symbol .*$");
std::vector<std::string> new_report;
std::vector<std::pair<std::string, std::string>> pending;
auto emit_pending = [&]() {
const size_t crc_only_changes = pending.size();
for (size_t ix = 0; ix < std::min(crc_only_changes, limit); ++ix) {
new_report.push_back(pending[ix].first);
new_report.push_back(pending[ix].second);
new_report.emplace_back();
}
if (crc_only_changes > limit) {
std::ostringstream os;
os << "... " << crc_only_changes - limit << " omitted; "
<< crc_only_changes << " symbols have only CRC changes";
new_report.push_back(os.str());
new_report.emplace_back();
}
pending.clear();
};
for (size_t ix = 0; ix < report.size(); ++ix) {
if (std::regex_match(report[ix], section_re) &&
!std::regex_match(report[ix], symbol_re)) {
emit_pending();
new_report.push_back(report[ix]);
} else if (ix + 2 < report.size() &&
std::regex_match(report[ix], symbol_changed_re) &&
std::regex_match(report[ix + 1], crc_re) &&
std::regex_match(report[ix + 2], empty_re)) {
pending.emplace_back(report[ix], report[ix + 1]);
// consumed 3 lines in total => 2 extra lines
ix += 2;
} else {
new_report.push_back(report[ix]);
}
}
emit_pending();
return new_report;
}
std::vector<std::string> SummariseOffsetChanges(
const std::vector<std::string>& report) {
const std::regex re1("^( *)member ('.*') changed$");
const std::regex re2("^( *)offset changed from (\\d+) to (\\d+)$");
const std::regex re3("^( *).*$");
std::smatch match1;
std::smatch match2;
std::smatch match3;
size_t indent = 0;
int64_t offset = 0;
std::vector<std::string> vars;
std::vector<std::string> new_report;
auto emit_pending = [&]() {
if (vars.empty()) {
return;
}
std::ostringstream line1;
line1 << std::string(indent, ' ');
if (vars.size() == 1) {
line1 << "member " << vars.front() << " changed";
} else {
line1 << vars.size() << " members (" << vars.front() << " .. "
<< vars.back() << ") changed";
}
new_report.push_back(line1.str());
std::ostringstream line2;
line2 << std::string(indent, ' ') << " offset changed by " << offset;
new_report.push_back(line2.str());
vars.clear();
};
for (size_t ix = 0; ix < report.size(); ++ix) {
if (ix + 2 < report.size() && std::regex_match(report[ix], match1, re1) &&
std::regex_match(report[ix + 1], match2, re2) &&
std::regex_match(report[ix + 2], match3, re3)) {
const size_t indent1 = match1[1].length();
const size_t indent2 = match2[1].length();
const size_t indent3 = match3[1].length();
if (indent1 + 2 == indent2 && indent1 >= indent3) {
const auto new_indent = indent1;
int64_t new_offset =
std::stoll(match2[3].str()) - std::stoll(match2[2].str());
if (new_indent != indent || new_offset != offset) {
emit_pending();
indent = new_indent;
offset = new_offset;
}
vars.push_back(match1[2]);
// consumed 2 lines in total => 1 extra line
++ix;
continue;
}
}
emit_pending();
new_report.push_back(report[ix]);
}
emit_pending();
return new_report;
}
std::vector<std::string> GroupRemovedAddedSymbols(
const std::vector<std::string>& report) {
const std::regex symbol_re("^(.*) symbol (.*) was (added|removed)$");
const std::regex empty_re("^$");
std::vector<std::string> new_report;
std::unordered_map<std::string,
std::map<std::string, std::vector<std::string>>> pending;
auto emit_pending = [&]() {
for (const auto& which : {"removed", "added"}) {
auto& pending_kinds = pending[which];
for (auto& [kind, pending_symbols] : pending_kinds) {
if (!pending_symbols.empty()) {
std::ostringstream os;
os << pending_symbols.size() << ' ' << kind << " symbol(s) " << which;
new_report.push_back(os.str());
for (const auto& symbol : std::exchange(pending_symbols, {})) {
new_report.push_back(" " + symbol);
}
new_report.emplace_back();
}
}
}
};
for (size_t ix = 0; ix < report.size(); ++ix) {
std::smatch match;
if (ix + 1 < report.size() &&
std::regex_match(report[ix], match, symbol_re) &&
std::regex_match(report[ix + 1], empty_re)) {
pending[match[3].str()][match[1].str()].push_back(match[2].str());
// consumed 2 lines in total => 1 extra line (there is always an empty
// line after symbol added/removed line)
++ix;
} else {
emit_pending();
new_report.push_back(report[ix]);
}
}
emit_pending();
return new_report;
}
std::vector<std::string> PostProcess(const std::vector<std::string>& report,
size_t max_crc_only_changes) {
std::vector<std::string> new_report;
new_report = SummariseCRCChanges(report, max_crc_only_changes);
new_report = GroupRemovedAddedSymbols(new_report);
new_report = SummariseOffsetChanges(new_report);
return new_report;
}
} // namespace stg