// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // -*- mode: C++ -*- // // Copyright 2020-2024 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: Maria Teguiani // Author: Giuliano Procida // Author: Ignes Simeonova #include "comparison.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "error.h" #include "graph.h" #include "order.h" #include "runtime.h" #include "scc.h" namespace stg { namespace diff { namespace { struct IgnoreDescriptor { std::string_view name; Ignore::Value value; }; constexpr std::array kIgnores{{ {"type_declaration_status", Ignore::TYPE_DECLARATION_STATUS }, {"symbol_type_presence", Ignore::SYMBOL_TYPE_PRESENCE }, {"primitive_type_encoding", Ignore::PRIMITIVE_TYPE_ENCODING }, {"member_size", Ignore::MEMBER_SIZE }, {"enum_underlying_type", Ignore::ENUM_UNDERLYING_TYPE }, {"qualifier", Ignore::QUALIFIER }, {"linux_symbol_crc", Ignore::SYMBOL_CRC }, {"interface_addition", Ignore::INTERFACE_ADDITION }, {"type_definition_addition", Ignore::TYPE_DEFINITION_ADDITION }, }}; } // namespace std::optional ParseIgnore(std::string_view ignore) { for (const auto& [name, value] : kIgnores) { if (name == ignore) { return {value}; } } return {}; } std::ostream& operator<<(std::ostream& os, IgnoreUsage) { os << "ignore options:"; for (const auto& [name, _] : kIgnores) { os << ' ' << name; } return os << '\n'; } namespace { struct Result { // Used when two nodes cannot be meaningfully compared. Result& MarkIncomparable() { same = false; diff.has_changes = true; return *this; } // Used when a node attribute has changed. void AddNodeDiff(const std::string& text) { same = false; diff.has_changes = true; diff.Add(text, {}); } // Used when a node attribute may have changed. template void MaybeAddNodeDiff( const std::string& text, const T& before, const T& after) { if (before != after) { std::ostringstream os; os << text << " changed from " << before << " to " << after; AddNodeDiff(os.str()); } } // Used when a node attribute may have changed, lazy version. template void MaybeAddNodeDiff(const std::function& text, const T& before, const T& after) { if (before != after) { std::ostringstream os; text(os); os << " changed from " << before << " to " << after; AddNodeDiff(os.str()); } } // Used when node attributes are optional values. template void MaybeAddNodeDiff(const std::string& text, const std::optional& before, const std::optional& after) { if (before && after) { MaybeAddNodeDiff(text, *before, *after); } else if (before) { std::ostringstream os; os << text << ' ' << *before << " was removed"; AddNodeDiff(os.str()); } else if (after) { std::ostringstream os; os << text << ' ' << *after << " was added"; AddNodeDiff(os.str()); } } // Used when an edge has been removed or added. void AddEdgeDiff(const std::string& text, const Comparison& comparison) { same = false; diff.Add(text, comparison); } // Used when an edge to a possible comparison is present. void MaybeAddEdgeDiff(const std::string& text, const std::pair& p) { same &= p.first; const auto& comparison = p.second; if (comparison != Comparison{}) { diff.Add(text, comparison); } } // Used when an edge to a possible comparison is present, lazy version. void MaybeAddEdgeDiff(const std::function& text, const std::pair& p) { same &= p.first; const auto& comparison = p.second; if (comparison != Comparison{}) { std::ostringstream os; text(os); diff.Add(os.str(), comparison); } } bool same = true; Diff diff; }; struct ResolveTypedef { ResolveTypedef(const Graph& graph, Id& id, std::vector& names) : graph(graph), id(id), names(names) {} bool operator()(const Typedef& x) const { id = x.referred_type_id; names.push_back(x.name); return true; } template bool operator()(const Node&) const { return false; } const Graph& graph; Id& id; std::vector& names; }; using Qualifiers = std::set; struct ResolveQualifier { ResolveQualifier(const Graph& graph, Id& id, Qualifiers& qualifiers) : graph(graph), id(id), qualifiers(qualifiers) {} bool operator()(const Qualified& x) const { id = x.qualified_type_id; qualifiers.insert(x.qualifier); return true; } bool operator()(const Array&) const { // There should be no qualifiers here. qualifiers.clear(); return false; } bool operator()(const Function&) const { // There should be no qualifiers here. qualifiers.clear(); return false; } template bool operator()(const Node&) const { return false; } const Graph& graph; Id& id; Qualifiers& qualifiers; }; // Separate qualifiers from underlying type. // // The caller must always be prepared to receive a different type as qualifiers // are sometimes discarded. std::pair ResolveQualifiers(const Graph& graph, Id id) { std::pair result = {id, {}}; ResolveQualifier resolve(graph, result.first, result.second); while (graph.Apply(resolve, result.first)) {} return result; } struct MatchingKey { explicit MatchingKey(const Graph& graph) : graph(graph) {} std::string operator()(Id id) const { return graph.Apply(*this, id); } std::string operator()(const BaseClass& x) const { return (*this)(x.type_id); } std::string operator()(const Method& x) const { return x.name + ',' + x.mangled_name; } std::string operator()(const Member& x) const { if (!x.name.empty()) { return x.name; } return (*this)(x.type_id); } std::string operator()(const VariantMember& x) const { return x.name; } std::string operator()(const StructUnion& x) const { if (!x.name.empty()) { return x.name; } if (x.definition) { const auto& members = x.definition->members; for (const auto& member : members) { const auto recursive = (*this)(member); if (!recursive.empty()) { return recursive + '+'; } } } return {}; } template std::string operator()(const Node&) const { return {}; } const Graph& graph; }; using KeyIndexPairs = std::vector>; KeyIndexPairs MatchingKeys(const Graph& graph, const std::vector& ids) { KeyIndexPairs keys; const auto size = ids.size(); keys.reserve(size); size_t anonymous_ix = 0; for (size_t ix = 0; ix < size; ++ix) { auto key = MatchingKey(graph)(ids[ix]); if (key.empty()) { key = "#anon#" + std::to_string(anonymous_ix++); } keys.emplace_back(key, ix); } std::stable_sort(keys.begin(), keys.end()); return keys; } KeyIndexPairs MatchingKeys(const Enumeration::Enumerators& enums) { KeyIndexPairs names; const auto size = enums.size(); names.reserve(size); for (size_t ix = 0; ix < size; ++ix) { const auto& name = enums[ix].first; names.emplace_back(name, ix); } std::stable_sort(names.begin(), names.end()); return names; } using MatchedPairs = std::vector, std::optional>>; MatchedPairs PairUp(const KeyIndexPairs& keys1, const KeyIndexPairs& keys2) { MatchedPairs pairs; pairs.reserve(std::max(keys1.size(), keys2.size())); auto it1 = keys1.begin(); auto it2 = keys2.begin(); const auto end1 = keys1.end(); const auto end2 = keys2.end(); while (it1 != end1 || it2 != end2) { if (it2 == end2 || (it1 != end1 && it1->first < it2->first)) { // removed pairs.push_back({{it1->second}, {}}); ++it1; } else if (it1 == end1 || (it2 != end2 && it1->first > it2->first)) { // added pairs.push_back({{}, {it2->second}}); ++it2; } else { // in both pairs.push_back({{it1->second}, {it2->second}}); ++it1; ++it2; } } return pairs; } std::string QualifiersMessage(Qualifier qualifier, const std::string& action) { std::ostringstream os; os << "qualifier " << qualifier << ' ' << action; return os.str(); } struct CompareWorker { CompareWorker(Runtime& runtime, const Ignore& ignore, const Graph& graph, Outcomes& outcomes) : ignore(ignore), graph(graph), outcomes(outcomes), queried(runtime, "compare.queried"), already_compared(runtime, "compare.already_compared"), being_compared(runtime, "compare.being_compared"), really_compared(runtime, "compare.really_compared"), equivalent(runtime, "compare.equivalent"), inequivalent(runtime, "compare.inequivalent"), scc_size(runtime, "compare.scc_size") {} /* * We compute a diff for every visited node. * * Each node has one of: * * * same == true; perhaps only tentative edge differences * * same == false; at least one definitive node or edge difference * * On the first visit to a node, the value of same is determined via recursive * comparison and the diff may contain local and edge differences. If an SCC * contains only internal edge differences (and equivalently same is true) * then the differences can all (eventually) be discarded. * * On exit from the first visit to a node, same reflects the tree of * comparisons below that node in the DFS and similarly, the diff graph * starting from the node contains a subtree of this tree plus potentially * edges to existing nodes to the side or below (already visited SCCs, * sharing), or above (back links forming cycles). * * When an SCC is closed, same is true results in the deletion of all the * nodes' diffs. The value of same is recorded for all nodes in the SCC. * * On other visits to a node, there are 2 cases. The node is still open * (meaning a recursive visit): return true and an edge diff. The node is * closed (meaning a repeat visit): return the stored value and an edge diff. */ std::pair operator()(Id id1, Id id2) { const Comparison comparison{{id1}, {id2}}; ++queried; // Check if the comparison has an already known result. const auto already_known = known.find(comparison); if (already_known != known.end()) { // Already visited and closed. ++already_compared; return already_known->second ? std::make_pair(true, Comparison{}) : std::make_pair(false, comparison); } // The comparison is either already open or has not been visited at all. // Record the comparison with the Strongly-Connected Component finder. const auto handle = scc.Open(comparison); if (!handle) { // Already open. // // Return a dummy true outcome and some tentative diffs. The diffs may end // up not being used and, while it would be nice to be lazier, they encode // all the cycling-breaking edges needed to recreate a full diff // structure. ++being_compared; return {true, comparison}; } // The comparison has now been opened, we must close it before returning. // Really compare. ++really_compared; const auto [same, diff] = CompareWithResolution(id1, id2); // Record the result and check for a complete Strongly-Connected Component. outcomes.insert({comparison, diff}); const auto comparisons = scc.Close(*handle); if (comparisons.empty()) { // Open SCC. // // Note that both same and diff are tentative as comparison is still // open. return {same, comparison}; } // Closed SCC. // // Note that same and diff now include every inequality and difference in // the SCC via the DFS spanning tree. const auto size = comparisons.size(); scc_size.Add(size); (same ? equivalent : inequivalent) += size; for (const auto& c : comparisons) { // Record equality / inequality. known.insert({c, same}); if (same) { // Discard provisional diff. outcomes.erase(c); } } return same ? std::make_pair(true, Comparison{}) : std::make_pair(false, comparison); } Result CompareWithResolution(Id id1, Id id2) { const auto [unqualified1, qualifiers1] = ResolveQualifiers(graph, id1); const auto [unqualified2, qualifiers2] = ResolveQualifiers(graph, id2); if (!qualifiers1.empty() || !qualifiers2.empty()) { // Qualified type difference. Result result; auto it1 = qualifiers1.begin(); auto it2 = qualifiers2.begin(); const auto end1 = qualifiers1.end(); const auto end2 = qualifiers2.end(); while (it1 != end1 || it2 != end2) { if (it2 == end2 || (it1 != end1 && *it1 < *it2)) { if (!ignore.Test(Ignore::QUALIFIER)) { result.AddNodeDiff(QualifiersMessage(*it1, "removed")); } ++it1; } else if (it1 == end1 || (it2 != end2 && *it1 > *it2)) { if (!ignore.Test(Ignore::QUALIFIER)) { result.AddNodeDiff(QualifiersMessage(*it2, "added")); } ++it2; } else { ++it1; ++it2; } } result.MaybeAddEdgeDiff("underlying", (*this)(unqualified1, unqualified2)); return result; } const auto [resolved1, typedefs1] = ResolveTypedefs(graph, unqualified1); const auto [resolved2, typedefs2] = ResolveTypedefs(graph, unqualified2); if (unqualified1 != resolved1 || unqualified2 != resolved2) { // Typedef difference. Result result; result.diff.holds_changes = !typedefs1.empty() && !typedefs2.empty() && typedefs1[0] == typedefs2[0]; result.MaybeAddEdgeDiff("resolved", (*this)(resolved1, resolved2)); return result; } // Compare nodes directly. return graph.Apply2(*this, unqualified1, unqualified2); } Comparison Removed(Id id) { Comparison comparison{{id}, {}}; outcomes.insert({comparison, {}}); return comparison; } Comparison Added(Id id) { Comparison comparison{{}, {id}}; outcomes.insert({comparison, {}}); return comparison; } void Defined(bool defined1, bool defined2, Result& result) { if (defined1 != defined2) { if (!ignore.Test(Ignore::TYPE_DECLARATION_STATUS) && !(ignore.Test(Ignore::TYPE_DEFINITION_ADDITION) && defined2)) { std::ostringstream os; os << "was " << (defined1 ? "fully defined" : "only declared") << ", is now " << (defined2 ? "fully defined" : "only declared"); result.AddNodeDiff(os.str()); } } } void Nodes(const std::vector& ids1, const std::vector& ids2, Result& result) { const auto keys1 = MatchingKeys(graph, ids1); const auto keys2 = MatchingKeys(graph, ids2); auto pairs = PairUp(keys1, keys2); Reorder(pairs); for (const auto& [index1, index2] : pairs) { if (index1 && !index2) { // removed const auto& x1 = ids1[*index1]; result.AddEdgeDiff("", Removed(x1)); } else if (!index1 && index2) { // added const auto& x2 = ids2[*index2]; result.AddEdgeDiff("", Added(x2)); } else if (index1 && index2) { // in both const auto& x1 = ids1[*index1]; const auto& x2 = ids2[*index2]; result.MaybeAddEdgeDiff("", (*this)(x1, x2)); } else { Die() << "CompareWorker::Nodes: impossible pair"; } } } void Nodes(const std::map& x1, const std::map& x2, bool ignore_added, Result& result) { // Group diffs into removed, added and changed symbols for readability. std::vector removed; std::vector added; std::vector> in_both; auto it1 = x1.begin(); auto it2 = x2.begin(); const auto end1 = x1.end(); const auto end2 = x2.end(); while (it1 != end1 || it2 != end2) { if (it2 == end2 || (it1 != end1 && it1->first < it2->first)) { // removed removed.push_back(it1->second); ++it1; } else if (it1 == end1 || (it2 != end2 && it1->first > it2->first)) { // added if (!ignore_added) { added.push_back(it2->second); } ++it2; } else { // in both in_both.emplace_back(it1->second, it2->second); ++it1; ++it2; } } for (const auto symbol1 : removed) { result.AddEdgeDiff("", Removed(symbol1)); } for (const auto symbol2 : added) { result.AddEdgeDiff("", Added(symbol2)); } for (const auto& [symbol1, symbol2] : in_both) { result.MaybeAddEdgeDiff("", (*this)(symbol1, symbol2)); } } Result Mismatch() { return Result().MarkIncomparable(); } Result operator()(const Special& x1, const Special& x2) { Result result; if (x1.kind != x2.kind) { return result.MarkIncomparable(); } return result; } Result operator()(const PointerReference& x1, const PointerReference& x2) { Result result; if (x1.kind != x2.kind) { return result.MarkIncomparable(); } const auto type_diff = (*this)(x1.pointee_type_id, x2.pointee_type_id); const char* text = x1.kind == PointerReference::Kind::POINTER ? "pointed-to" : "referred-to"; result.MaybeAddEdgeDiff(text, type_diff); return result; } Result operator()(const PointerToMember& x1, const PointerToMember& x2) { Result result; result.MaybeAddEdgeDiff( "containing", (*this)(x1.containing_type_id, x2.containing_type_id)); result.MaybeAddEdgeDiff( "", (*this)(x1.pointee_type_id, x2.pointee_type_id)); return result; } Result operator()(const Typedef&, const Typedef&) { // Compare will never attempt to directly compare Typedefs. Die() << "internal error: CompareWorker(Typedef)"; } Result operator()(const Qualified&, const Qualified&) { // Compare will never attempt to directly compare Qualifiers. Die() << "internal error: CompareWorker(Qualified)"; } Result operator()(const Primitive& x1, const Primitive& x2) { Result result; if (x1.name != x2.name) { return result.MarkIncomparable(); } result.diff.holds_changes = !x1.name.empty(); if (!ignore.Test(Ignore::PRIMITIVE_TYPE_ENCODING)) { result.MaybeAddNodeDiff("encoding", x1.encoding, x2.encoding); } result.MaybeAddNodeDiff("byte size", x1.bytesize, x2.bytesize); return result; } Result operator()(const Array& x1, const Array& x2) { Result result; result.MaybeAddNodeDiff("number of elements", x1.number_of_elements, x2.number_of_elements); const auto type_diff = (*this)(x1.element_type_id, x2.element_type_id); result.MaybeAddEdgeDiff("element", type_diff); return result; } Result operator()(const BaseClass& x1, const BaseClass& x2) { Result result; result.MaybeAddNodeDiff("inheritance", x1.inheritance, x2.inheritance); result.MaybeAddNodeDiff("offset", x1.offset, x2.offset); result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id)); return result; } Result operator()(const Method& x1, const Method& x2) { Result result; result.MaybeAddNodeDiff( "vtable offset", x1.vtable_offset, x2.vtable_offset); result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id)); return result; } Result operator()(const Member& x1, const Member& x2) { Result result; result.MaybeAddNodeDiff("offset", x1.offset, x2.offset); if (!ignore.Test(Ignore::MEMBER_SIZE)) { const bool bitfield1 = x1.bitsize > 0; const bool bitfield2 = x2.bitsize > 0; if (bitfield1 != bitfield2) { std::ostringstream os; os << "was " << (bitfield1 ? "a bit-field" : "not a bit-field") << ", is now " << (bitfield2 ? "a bit-field" : "not a bit-field"); result.AddNodeDiff(os.str()); } else { result.MaybeAddNodeDiff("bit-field size", x1.bitsize, x2.bitsize); } } result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id)); return result; } Result operator()(const VariantMember& x1, const VariantMember& x2) { Result result; result.MaybeAddNodeDiff("discriminant", x1.discriminant_value, x2.discriminant_value); result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id)); return result; } Result operator()(const StructUnion& x1, const StructUnion& x2) { Result result; // Compare two anonymous types recursively, not holding diffs. // Compare two identically named types recursively, holding diffs. // Everything else treated as distinct. No recursion. if (x1.kind != x2.kind || x1.name != x2.name) { return result.MarkIncomparable(); } result.diff.holds_changes = !x1.name.empty(); const auto& definition1 = x1.definition; const auto& definition2 = x2.definition; Defined(definition1.has_value(), definition2.has_value(), result); if (definition1.has_value() && definition2.has_value()) { result.MaybeAddNodeDiff( "byte size", definition1->bytesize, definition2->bytesize); Nodes(definition1->base_classes, definition2->base_classes, result); Nodes(definition1->methods, definition2->methods, result); Nodes(definition1->members, definition2->members, result); } return result; } Result operator()(const Enumeration& x1, const Enumeration& x2) { Result result; // Compare two anonymous types recursively, not holding diffs. // Compare two identically named types recursively, holding diffs. // Everything else treated as distinct. No recursion. if (x1.name != x2.name) { return result.MarkIncomparable(); } result.diff.holds_changes = !x1.name.empty(); const auto& definition1 = x1.definition; const auto& definition2 = x2.definition; Defined(definition1.has_value(), definition2.has_value(), result); if (definition1.has_value() && definition2.has_value()) { if (!ignore.Test(Ignore::ENUM_UNDERLYING_TYPE)) { const auto type_diff = (*this)(definition1->underlying_type_id, definition2->underlying_type_id); result.MaybeAddEdgeDiff("underlying", type_diff); } const auto enums1 = definition1->enumerators; const auto enums2 = definition2->enumerators; const auto keys1 = MatchingKeys(enums1); const auto keys2 = MatchingKeys(enums2); auto pairs = PairUp(keys1, keys2); Reorder(pairs); for (const auto& [index1, index2] : pairs) { if (index1 && !index2) { // removed const auto& enum1 = enums1[*index1]; std::ostringstream os; os << "enumerator '" << enum1.first << "' (" << enum1.second << ") was removed"; result.AddNodeDiff(os.str()); } else if (!index1 && index2) { // added const auto& enum2 = enums2[*index2]; std::ostringstream os; os << "enumerator '" << enum2.first << "' (" << enum2.second << ") was added"; result.AddNodeDiff(os.str()); } else if (index1 && index2) { // in both const auto& enum1 = enums1[*index1]; const auto& enum2 = enums2[*index2]; result.MaybeAddNodeDiff( [&](std::ostream& os) { os << "enumerator '" << enum1.first << "' value"; }, enum1.second, enum2.second); } else { Die() << "CompareWorker(Enumeration): impossible pair"; } } } return result; } Result operator()(const Variant& x1, const Variant& x2) { Result result; // Compare two identically named variants recursively, holding diffs. // Everything else treated as distinct. No recursion. if (x1.name != x2.name) { return result.MarkIncomparable(); } result.diff.holds_changes = true; // Anonymous variants are not allowed. result.MaybeAddNodeDiff("bytesize", x1.bytesize, x2.bytesize); if (x1.discriminant.has_value() && x2.discriminant.has_value()) { const auto type_diff = (*this)(x1.discriminant.value(), x2.discriminant.value()); result.MaybeAddEdgeDiff("discriminant", type_diff); } else if (x1.discriminant.has_value()) { result.AddEdgeDiff("", Removed(x1.discriminant.value())); } else if (x2.discriminant.has_value()) { result.AddEdgeDiff("", Added(x2.discriminant.value())); } Nodes(x1.members, x2.members, result); return result; } Result operator()(const Function& x1, const Function& x2) { Result result; const auto type_diff = (*this)(x1.return_type_id, x2.return_type_id); result.MaybeAddEdgeDiff("return", type_diff); const auto& parameters1 = x1.parameters; const auto& parameters2 = x2.parameters; const size_t min = std::min(parameters1.size(), parameters2.size()); for (size_t i = 0; i < min; ++i) { const Id p1 = parameters1.at(i); const Id p2 = parameters2.at(i); result.MaybeAddEdgeDiff( [&](std::ostream& os) { os << "parameter " << i + 1; }, (*this)(p1, p2)); } const bool added = parameters1.size() < parameters2.size(); const auto& which = added ? x2 : x1; const auto& parameters = which.parameters; for (size_t i = min; i < parameters.size(); ++i) { const Id parameter = parameters.at(i); std::ostringstream os; os << "parameter " << i + 1 << " of"; auto diff = added ? Added(parameter) : Removed(parameter); result.AddEdgeDiff(os.str(), diff); } return result; } Result operator()(const ElfSymbol& x1, const ElfSymbol& x2) { // ELF symbols have a lot of different attributes that can impact ABI // compatibility and others that either cannot or are subsumed by // information elsewhere. // // Not all attributes are exposed by elf_symbol and fewer still in ABI XML. // // name - definitely part of the key // // type - (ELF symbol type, not C type) one important distinction here would // be global vs thread-local variables // // section - not exposed (modulo aliasing information) and don't care // // value (address, usually) - not exposed (modulo aliasing information) and // don't care // // size - don't care (for variables, subsumed by type information) // // binding - global vs weak vs unique vs local // // visibility - default > protected > hidden > internal // // version / is-default-version - in theory the "hidden" bit (separate from // hidden and local above) can be set independently of the version, but in // practice at most one version of given name is non-hidden; version // (including its presence or absence) is definitely part of the key; we // should probably treat is-default-version as a non-key attribute // // defined - rather fundamental; libabigail currently doesn't track // undefined symbols but we should obviously be prepared in case it does // There are also some externalities which libabigail cares about, which may // or may not be exposed in the XML // // index - don't care // // is-common and friends - don't care // // aliases - exposed, but we don't really care; however we should see what // compilers do, if anything, in terms of propagating type information to // aliases // Linux kernel things. // // MODVERSIONS CRC - fundamental to ABI compatibility, if present // // Symbol namespace - fundamental to ABI compatibility, if present Result result; result.MaybeAddNodeDiff("name", x1.symbol_name, x2.symbol_name); if (x1.version_info && x2.version_info) { result.MaybeAddNodeDiff("version", x1.version_info->name, x2.version_info->name); result.MaybeAddNodeDiff("default version", x1.version_info->is_default, x2.version_info->is_default); } else { result.MaybeAddNodeDiff("has version", x1.version_info.has_value(), x2.version_info.has_value()); } result.MaybeAddNodeDiff("defined", x1.is_defined, x2.is_defined); result.MaybeAddNodeDiff("symbol type", x1.symbol_type, x2.symbol_type); result.MaybeAddNodeDiff("binding", x1.binding, x2.binding); result.MaybeAddNodeDiff("visibility", x1.visibility, x2.visibility); if (!ignore.Test(Ignore::SYMBOL_CRC)) { result.MaybeAddNodeDiff("CRC", x1.crc, x2.crc); } result.MaybeAddNodeDiff("namespace", x1.ns, x2.ns); if (x1.type_id && x2.type_id) { result.MaybeAddEdgeDiff("", (*this)(*x1.type_id, *x2.type_id)); } else if (x1.type_id) { if (!ignore.Test(Ignore::SYMBOL_TYPE_PRESENCE)) { result.AddEdgeDiff("", Removed(*x1.type_id)); } } else if (x2.type_id) { if (!ignore.Test(Ignore::SYMBOL_TYPE_PRESENCE)) { result.AddEdgeDiff("", Added(*x2.type_id)); } } else { // both types missing, we have nothing to say } return result; } Result operator()(const Interface& x1, const Interface& x2) { Result result; result.diff.holds_changes = true; const bool ignore_added = ignore.Test(Ignore::INTERFACE_ADDITION); Nodes(x1.symbols, x2.symbols, ignore_added, result); Nodes(x1.types, x2.types, ignore_added, result); return result; } const Ignore ignore; const Graph& graph; Outcomes& outcomes; std::unordered_map known; SCC scc; Counter queried; Counter already_compared; Counter being_compared; Counter really_compared; Counter equivalent; Counter inequivalent; Histogram scc_size; }; } // namespace std::pair> ResolveTypedefs( const Graph& graph, Id id) { std::pair> result = {id, {}}; ResolveTypedef resolve(graph, result.first, result.second); while (graph.Apply(resolve, result.first)) {} return result; } Comparison Compare(Runtime& runtime, Ignore ignore, const Graph& graph, Id root1, Id root2, Outcomes& outcomes) { // The root node (Comparison{{id1}, {id2}}) must be the last node to be // completely visited by the SCC finder and the SCC finder state must be empty // on return from this function call. In particular, the returns where the SCC // is "open" are impossible. The remaining cases (of which one is impossible // for the root node) both have the same two possible return values: // // * (true, Comparison{}) // * (false, Comparison{{id1}, {id2}} // // So the invariant value.first == (value.second == Comparison{}) holds and we // can unambiguously return value.second. return CompareWorker(runtime, ignore, graph, outcomes)(root1, root2).second; } } // namespace diff } // namespace stg