// 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 #include #include #include #include #include #include #include #include #include #include namespace stg { std::vector SummariseCRCChanges( const std::vector& 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 new_report; std::vector> 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 SummariseOffsetChanges( const std::vector& 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 vars; std::vector 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 GroupRemovedAddedSymbols( const std::vector& report) { const std::regex symbol_re("^(.*) symbol (.*) was (added|removed)$"); const std::regex empty_re("^$"); std::vector new_report; std::unordered_map>> 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 PostProcess(const std::vector& report, size_t max_crc_only_changes) { std::vector 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