xref: /aosp_15_r20/external/stg/comparison.cc (revision 9e3b08ae94a55201065475453d799e8b1378bea6)
1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- mode: C++ -*-
3 //
4 // Copyright 2020-2024 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: Maria Teguiani
19 // Author: Giuliano Procida
20 // Author: Ignes Simeonova
21 
22 #include "comparison.h"
23 
24 #include <algorithm>
25 #include <array>
26 #include <cstddef>
27 #include <functional>
28 #include <map>
29 #include <optional>
30 #include <ostream>
31 #include <set>
32 #include <sstream>
33 #include <string>
34 #include <string_view>
35 #include <unordered_map>
36 #include <utility>
37 #include <vector>
38 
39 #include "error.h"
40 #include "graph.h"
41 #include "order.h"
42 #include "runtime.h"
43 #include "scc.h"
44 
45 namespace stg {
46 namespace diff {
47 
48 namespace {
49 
50 struct IgnoreDescriptor {
51   std::string_view name;
52   Ignore::Value value;
53 };
54 
55 constexpr std::array<IgnoreDescriptor, 9> kIgnores{{
56   {"type_declaration_status",  Ignore::TYPE_DECLARATION_STATUS  },
57   {"symbol_type_presence",     Ignore::SYMBOL_TYPE_PRESENCE     },
58   {"primitive_type_encoding",  Ignore::PRIMITIVE_TYPE_ENCODING  },
59   {"member_size",              Ignore::MEMBER_SIZE              },
60   {"enum_underlying_type",     Ignore::ENUM_UNDERLYING_TYPE     },
61   {"qualifier",                Ignore::QUALIFIER                },
62   {"linux_symbol_crc",         Ignore::SYMBOL_CRC               },
63   {"interface_addition",       Ignore::INTERFACE_ADDITION       },
64   {"type_definition_addition", Ignore::TYPE_DEFINITION_ADDITION },
65 }};
66 
67 }  // namespace
68 
ParseIgnore(std::string_view ignore)69 std::optional<Ignore::Value> ParseIgnore(std::string_view ignore) {
70   for (const auto& [name, value] : kIgnores) {
71     if (name == ignore) {
72       return {value};
73     }
74   }
75   return {};
76 }
77 
operator <<(std::ostream & os,IgnoreUsage)78 std::ostream& operator<<(std::ostream& os, IgnoreUsage) {
79   os << "ignore options:";
80   for (const auto& [name, _] : kIgnores) {
81     os << ' ' << name;
82   }
83   return os << '\n';
84 }
85 
86 namespace {
87 
88 struct Result {
89   // Used when two nodes cannot be meaningfully compared.
MarkIncomparablestg::diff::__anon19fbf5d30211::Result90   Result& MarkIncomparable() {
91     same = false;
92     diff.has_changes = true;
93     return *this;
94   }
95 
96   // Used when a node attribute has changed.
AddNodeDiffstg::diff::__anon19fbf5d30211::Result97   void AddNodeDiff(const std::string& text) {
98     same = false;
99     diff.has_changes = true;
100     diff.Add(text, {});
101   }
102 
103   // Used when a node attribute may have changed.
104   template <typename T>
MaybeAddNodeDiffstg::diff::__anon19fbf5d30211::Result105   void MaybeAddNodeDiff(
106       const std::string& text, const T& before, const T& after) {
107     if (before != after) {
108       std::ostringstream os;
109       os << text << " changed from " << before << " to " << after;
110       AddNodeDiff(os.str());
111     }
112   }
113 
114   // Used when a node attribute may have changed, lazy version.
115   template <typename T>
MaybeAddNodeDiffstg::diff::__anon19fbf5d30211::Result116   void MaybeAddNodeDiff(const std::function<void(std::ostream&)>& text,
117                         const T& before, const T& after) {
118     if (before != after) {
119       std::ostringstream os;
120       text(os);
121       os << " changed from " << before << " to " << after;
122       AddNodeDiff(os.str());
123     }
124   }
125 
126   // Used when node attributes are optional values.
127   template <typename T>
MaybeAddNodeDiffstg::diff::__anon19fbf5d30211::Result128   void MaybeAddNodeDiff(const std::string& text, const std::optional<T>& before,
129                         const std::optional<T>& after) {
130     if (before && after) {
131       MaybeAddNodeDiff(text, *before, *after);
132     } else if (before) {
133       std::ostringstream os;
134       os << text << ' ' << *before << " was removed";
135       AddNodeDiff(os.str());
136     } else if (after) {
137       std::ostringstream os;
138       os << text << ' ' << *after << " was added";
139       AddNodeDiff(os.str());
140     }
141   }
142 
143   // Used when an edge has been removed or added.
AddEdgeDiffstg::diff::__anon19fbf5d30211::Result144   void AddEdgeDiff(const std::string& text, const Comparison& comparison) {
145     same = false;
146     diff.Add(text, comparison);
147   }
148 
149   // Used when an edge to a possible comparison is present.
MaybeAddEdgeDiffstg::diff::__anon19fbf5d30211::Result150   void MaybeAddEdgeDiff(const std::string& text,
151                         const std::pair<bool, Comparison>& p) {
152     same &= p.first;
153     const auto& comparison = p.second;
154     if (comparison != Comparison{}) {
155       diff.Add(text, comparison);
156     }
157   }
158 
159   // Used when an edge to a possible comparison is present, lazy version.
MaybeAddEdgeDiffstg::diff::__anon19fbf5d30211::Result160   void MaybeAddEdgeDiff(const std::function<void(std::ostream&)>& text,
161                         const std::pair<bool, Comparison>& p) {
162     same &= p.first;
163     const auto& comparison = p.second;
164     if (comparison != Comparison{}) {
165       std::ostringstream os;
166       text(os);
167       diff.Add(os.str(), comparison);
168     }
169   }
170 
171   bool same = true;
172   Diff diff;
173 };
174 
175 struct ResolveTypedef {
ResolveTypedefstg::diff::__anon19fbf5d30211::ResolveTypedef176   ResolveTypedef(const Graph& graph, Id& id, std::vector<std::string>& names)
177       : graph(graph), id(id), names(names) {}
178 
operator ()stg::diff::__anon19fbf5d30211::ResolveTypedef179   bool operator()(const Typedef& x) {
180     id = x.referred_type_id;
181     names.push_back(x.name);
182     return true;
183   }
184 
185   template <typename Node>
operator ()stg::diff::__anon19fbf5d30211::ResolveTypedef186   bool operator()(const Node&) {
187     return false;
188   }
189 
190   const Graph& graph;
191   Id& id;
192   std::vector<std::string>& names;
193 };
194 
195 using Qualifiers = std::set<Qualifier>;
196 
197 struct ResolveQualifier {
ResolveQualifierstg::diff::__anon19fbf5d30211::ResolveQualifier198   ResolveQualifier(const Graph& graph, Id& id, Qualifiers& qualifiers)
199       : graph(graph), id(id), qualifiers(qualifiers) {}
200 
operator ()stg::diff::__anon19fbf5d30211::ResolveQualifier201   bool operator()(const Qualified& x) {
202     id = x.qualified_type_id;
203     qualifiers.insert(x.qualifier);
204     return true;
205   }
206 
operator ()stg::diff::__anon19fbf5d30211::ResolveQualifier207   bool operator()(const Array&) {
208     // There should be no qualifiers here.
209     qualifiers.clear();
210     return false;
211   }
212 
operator ()stg::diff::__anon19fbf5d30211::ResolveQualifier213   bool operator()(const Function&) {
214     // There should be no qualifiers here.
215     qualifiers.clear();
216     return false;
217   }
218 
219   template <typename Node>
operator ()stg::diff::__anon19fbf5d30211::ResolveQualifier220   bool operator()(const Node&) {
221     return false;
222   }
223 
224   const Graph& graph;
225   Id& id;
226   Qualifiers& qualifiers;
227 };
228 
229 // Separate qualifiers from underlying type.
230 //
231 // The caller must always be prepared to receive a different type as qualifiers
232 // are sometimes discarded.
ResolveQualifiers(const Graph & graph,Id id)233 std::pair<Id, Qualifiers> ResolveQualifiers(const Graph& graph, Id id) {
234   std::pair<Id, Qualifiers> result = {id, {}};
235   ResolveQualifier resolve(graph, result.first, result.second);
236   while (graph.Apply(resolve, result.first)) {}
237   return result;
238 }
239 
240 struct MatchingKey {
MatchingKeystg::diff::__anon19fbf5d30211::MatchingKey241   explicit MatchingKey(const Graph& graph) : graph(graph) {}
242 
operator ()stg::diff::__anon19fbf5d30211::MatchingKey243   std::string operator()(Id id) {
244     return graph.Apply(*this, id);
245   }
246 
operator ()stg::diff::__anon19fbf5d30211::MatchingKey247   std::string operator()(const BaseClass& x) {
248     return (*this)(x.type_id);
249   }
250 
operator ()stg::diff::__anon19fbf5d30211::MatchingKey251   std::string operator()(const Method& x) {
252     return x.name + ',' + x.mangled_name;
253   }
254 
operator ()stg::diff::__anon19fbf5d30211::MatchingKey255   std::string operator()(const Member& x) {
256     if (!x.name.empty()) {
257       return x.name;
258     }
259     return (*this)(x.type_id);
260   }
261 
operator ()stg::diff::__anon19fbf5d30211::MatchingKey262   std::string operator()(const VariantMember& x) {
263     return x.name;
264   }
265 
operator ()stg::diff::__anon19fbf5d30211::MatchingKey266   std::string operator()(const StructUnion& x) {
267     if (!x.name.empty()) {
268       return x.name;
269     }
270     if (x.definition) {
271       const auto& members = x.definition->members;
272       for (const auto& member : members) {
273         const auto recursive = (*this)(member);
274         if (!recursive.empty()) {
275           return recursive + '+';
276         }
277       }
278     }
279     return {};
280   }
281 
282   template <typename Node>
operator ()stg::diff::__anon19fbf5d30211::MatchingKey283   std::string operator()(const Node&) {
284     return {};
285   }
286 
287   const Graph& graph;
288 };
289 
290 using KeyIndexPairs = std::vector<std::pair<std::string, size_t>>;
291 
MatchingKeys(const Graph & graph,const std::vector<Id> & ids)292 KeyIndexPairs MatchingKeys(const Graph& graph, const std::vector<Id>& ids) {
293   KeyIndexPairs keys;
294   const auto size = ids.size();
295   keys.reserve(size);
296   size_t anonymous_ix = 0;
297   for (size_t ix = 0; ix < size; ++ix) {
298     auto key = MatchingKey(graph)(ids[ix]);
299     if (key.empty()) {
300       key = "#anon#" + std::to_string(anonymous_ix++);
301     }
302     keys.emplace_back(key, ix);
303   }
304   std::stable_sort(keys.begin(), keys.end());
305   return keys;
306 }
307 
MatchingKeys(const Enumeration::Enumerators & enums)308 KeyIndexPairs MatchingKeys(const Enumeration::Enumerators& enums) {
309   KeyIndexPairs names;
310   const auto size = enums.size();
311   names.reserve(size);
312   for (size_t ix = 0; ix < size; ++ix) {
313     const auto& name = enums[ix].first;
314     names.emplace_back(name, ix);
315   }
316   std::stable_sort(names.begin(), names.end());
317   return names;
318 }
319 
320 using MatchedPairs =
321     std::vector<std::pair<std::optional<size_t>, std::optional<size_t>>>;
322 
PairUp(const KeyIndexPairs & keys1,const KeyIndexPairs & keys2)323 MatchedPairs PairUp(const KeyIndexPairs& keys1, const KeyIndexPairs& keys2) {
324   MatchedPairs pairs;
325   pairs.reserve(std::max(keys1.size(), keys2.size()));
326   auto it1 = keys1.begin();
327   auto it2 = keys2.begin();
328   const auto end1 = keys1.end();
329   const auto end2 = keys2.end();
330   while (it1 != end1 || it2 != end2) {
331     if (it2 == end2 || (it1 != end1 && it1->first < it2->first)) {
332       // removed
333       pairs.push_back({{it1->second}, {}});
334       ++it1;
335     } else if (it1 == end1 || (it2 != end2 && it1->first > it2->first)) {
336       // added
337       pairs.push_back({{}, {it2->second}});
338       ++it2;
339     } else {
340       // in both
341       pairs.push_back({{it1->second}, {it2->second}});
342       ++it1;
343       ++it2;
344     }
345   }
346   return pairs;
347 }
348 
QualifiersMessage(Qualifier qualifier,const std::string & action)349 std::string QualifiersMessage(Qualifier qualifier, const std::string& action) {
350   std::ostringstream os;
351   os << "qualifier " << qualifier << ' ' << action;
352   return os.str();
353 }
354 
355 struct CompareWorker {
CompareWorkerstg::diff::__anon19fbf5d30211::CompareWorker356   CompareWorker(Runtime& runtime, const Ignore& ignore, const Graph& graph,
357                 Outcomes& outcomes)
358       : ignore(ignore), graph(graph), outcomes(outcomes),
359         queried(runtime, "compare.queried"),
360         already_compared(runtime, "compare.already_compared"),
361         being_compared(runtime, "compare.being_compared"),
362         really_compared(runtime, "compare.really_compared"),
363         equivalent(runtime, "compare.equivalent"),
364         inequivalent(runtime, "compare.inequivalent"),
365         scc_size(runtime, "compare.scc_size") {}
366 
367   /*
368    * We compute a diff for every visited node.
369    *
370    * Each node has one of:
371    *
372    * 1. same == true; perhaps only tentative edge differences
373    * 2. same == false; at least one definitive node or edge difference
374    *
375    * On the first visit to a node we can put a placeholder in, the value of same
376    * is irrelevant, the diff may contain local and edge differences. If an SCC
377    * contains only internal edge differences (and equivalently same is true)
378    * then the differences can all (eventually) be discarded.
379    *
380    * On exit from the first visit to a node, same reflects the tree of
381    * comparisons below that node in the DFS and similarly, the diff graph
382    * starting from the node contains a subtree of this tree plus potentially
383    * edges to existing nodes to the side or below (already visited SCCs,
384    * sharing), or above (back links forming cycles).
385    *
386    * When an SCC is closed, all same implies deleting all diffs, any not same
387    * implies updating all to false.
388    *
389    * On subsequent visits to a node, there are 2 cases. The node is still open:
390    * return true and an edge diff. The node is closed, return the stored value
391    * and an edge diff.
392    */
operator ()stg::diff::__anon19fbf5d30211::CompareWorker393   std::pair<bool, Comparison> operator()(Id id1, Id id2) {
394     const Comparison comparison{{id1}, {id2}};
395     ++queried;
396 
397     // 1. Check if the comparison has an already known result.
398     const auto already_known = known.find(comparison);
399     if (already_known != known.end()) {
400       // Already visited and closed.
401       ++already_compared;
402       return already_known->second
403           ? std::make_pair(true, Comparison{})
404           : std::make_pair(false, comparison);
405     }
406     // Either open or not visited at all
407 
408     // 2. Record node with Strongly-Connected Component finder.
409     const auto handle = scc.Open(comparison);
410     if (!handle) {
411       // Already open.
412       //
413       // Return a dummy true outcome and some tentative diffs. The diffs may end
414       // up not being used and, while it would be nice to be lazier, they encode
415       // all the cycling-breaking edges needed to recreate a full diff
416       // structure.
417       ++being_compared;
418       return {true, comparison};
419     }
420     // Comparison opened, need to close it before returning.
421     ++really_compared;
422 
423     Result result;
424 
425     const auto [unqualified1, qualifiers1] = ResolveQualifiers(graph, id1);
426     const auto [unqualified2, qualifiers2] = ResolveQualifiers(graph, id2);
427     if (!qualifiers1.empty() || !qualifiers2.empty()) {
428       // 3.1 Qualified type difference.
429       auto it1 = qualifiers1.begin();
430       auto it2 = qualifiers2.begin();
431       const auto end1 = qualifiers1.end();
432       const auto end2 = qualifiers2.end();
433       while (it1 != end1 || it2 != end2) {
434         if (it2 == end2 || (it1 != end1 && *it1 < *it2)) {
435           if (!ignore.Test(Ignore::QUALIFIER)) {
436             result.AddNodeDiff(QualifiersMessage(*it1, "removed"));
437           }
438           ++it1;
439         } else if (it1 == end1 || (it2 != end2 && *it1 > *it2)) {
440           if (!ignore.Test(Ignore::QUALIFIER)) {
441             result.AddNodeDiff(QualifiersMessage(*it2, "added"));
442           }
443           ++it2;
444         } else {
445           ++it1;
446           ++it2;
447         }
448       }
449       const auto type_diff = (*this)(unqualified1, unqualified2);
450       result.MaybeAddEdgeDiff("underlying", type_diff);
451     } else {
452       const auto [resolved1, typedefs1] = ResolveTypedefs(graph, unqualified1);
453       const auto [resolved2, typedefs2] = ResolveTypedefs(graph, unqualified2);
454       if (unqualified1 != resolved1 || unqualified2 != resolved2) {
455         // 3.2 Typedef difference.
456         result.diff.holds_changes = !typedefs1.empty() && !typedefs2.empty()
457                                     && typedefs1[0] == typedefs2[0];
458         result.MaybeAddEdgeDiff("resolved", (*this)(resolved1, resolved2));
459       } else {
460         // 4. Compare nodes, if possible.
461         result = graph.Apply2(*this, unqualified1, unqualified2);
462       }
463     }
464 
465     // 5. Update result and check for a complete Strongly-Connected Component.
466     const bool same = result.same;
467     provisional.insert({comparison, result.diff});
468     const auto comparisons = scc.Close(*handle);
469     if (comparisons.empty()) {
470       // Open SCC.
471       //
472       // Note that both same and diff are tentative as comparison is still
473       // open.
474       return {same, comparison};
475     }
476 
477     // Closed SCC.
478     //
479     // Note that result now incorporates every inequality and difference in the
480     // SCC via the DFS spanning tree.
481     const auto size = comparisons.size();
482     scc_size.Add(size);
483     for (const auto& c : comparisons) {
484       // Record equality / inequality.
485       known.insert({c, same});
486       const auto it = provisional.find(c);
487       Check(it != provisional.end())
488           << "internal error: missing provisional diffs";
489       if (!same) {
490         // Record differences.
491         outcomes.insert(*it);
492       }
493       provisional.erase(it);
494     }
495     (same ? equivalent : inequivalent) += size;
496     return same
497         ? std::make_pair(true, Comparison{})
498         : std::make_pair(false, comparison);
499   }
500 
Removedstg::diff::__anon19fbf5d30211::CompareWorker501   Comparison Removed(Id id) {
502     Comparison comparison{{id}, {}};
503     outcomes.insert({comparison, {}});
504     return comparison;
505   }
506 
Addedstg::diff::__anon19fbf5d30211::CompareWorker507   Comparison Added(Id id) {
508     Comparison comparison{{}, {id}};
509     outcomes.insert({comparison, {}});
510     return comparison;
511   }
512 
Definedstg::diff::__anon19fbf5d30211::CompareWorker513   void Defined(bool defined1, bool defined2, Result& result) {
514     if (defined1 != defined2) {
515       if (!ignore.Test(Ignore::TYPE_DECLARATION_STATUS)
516           && !(ignore.Test(Ignore::TYPE_DEFINITION_ADDITION) && defined2)) {
517         std::ostringstream os;
518         os << "was " << (defined1 ? "fully defined" : "only declared")
519            << ", is now " << (defined2 ? "fully defined" : "only declared");
520         result.AddNodeDiff(os.str());
521       }
522     }
523   }
524 
Nodesstg::diff::__anon19fbf5d30211::CompareWorker525   void Nodes(const std::vector<Id>& ids1, const std::vector<Id>& ids2,
526              Result& result) {
527     const auto keys1 = MatchingKeys(graph, ids1);
528     const auto keys2 = MatchingKeys(graph, ids2);
529     auto pairs = PairUp(keys1, keys2);
530     Reorder(pairs);
531     for (const auto& [index1, index2] : pairs) {
532       if (index1 && !index2) {
533         // removed
534         const auto& x1 = ids1[*index1];
535         result.AddEdgeDiff("", Removed(x1));
536       } else if (!index1 && index2) {
537         // added
538         const auto& x2 = ids2[*index2];
539         result.AddEdgeDiff("", Added(x2));
540       } else if (index1 && index2) {
541         // in both
542         const auto& x1 = ids1[*index1];
543         const auto& x2 = ids2[*index2];
544         result.MaybeAddEdgeDiff("", (*this)(x1, x2));
545       } else {
546         Die() << "CompareWorker::Nodes: impossible pair";
547       }
548     }
549   }
550 
Nodesstg::diff::__anon19fbf5d30211::CompareWorker551   void Nodes(const std::map<std::string, Id>& x1,
552              const std::map<std::string, Id>& x2,
553              bool ignore_added, Result& result) {
554     // Group diffs into removed, added and changed symbols for readability.
555     std::vector<Id> removed;
556     std::vector<Id> added;
557     std::vector<std::pair<Id, Id>> in_both;
558 
559     auto it1 = x1.begin();
560     auto it2 = x2.begin();
561     const auto end1 = x1.end();
562     const auto end2 = x2.end();
563     while (it1 != end1 || it2 != end2) {
564       if (it2 == end2 || (it1 != end1 && it1->first < it2->first)) {
565         // removed
566         removed.push_back(it1->second);
567         ++it1;
568       } else if (it1 == end1 || (it2 != end2 && it1->first > it2->first)) {
569         // added
570         if (!ignore_added) {
571           added.push_back(it2->second);
572         }
573         ++it2;
574       } else {
575         // in both
576         in_both.emplace_back(it1->second, it2->second);
577         ++it1;
578         ++it2;
579       }
580     }
581 
582     for (const auto symbol1 : removed) {
583       result.AddEdgeDiff("", Removed(symbol1));
584     }
585     for (const auto symbol2 : added) {
586       result.AddEdgeDiff("", Added(symbol2));
587     }
588     for (const auto& [symbol1, symbol2] : in_both) {
589       result.MaybeAddEdgeDiff("", (*this)(symbol1, symbol2));
590     }
591   }
592 
Mismatchstg::diff::__anon19fbf5d30211::CompareWorker593   Result Mismatch() {
594     return Result().MarkIncomparable();
595   }
596 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker597   Result operator()(const Special& x1, const Special& x2) {
598     Result result;
599     if (x1.kind != x2.kind) {
600       return result.MarkIncomparable();
601     }
602     return result;
603   }
604 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker605   Result operator()(const PointerReference& x1, const PointerReference& x2) {
606     Result result;
607     if (x1.kind != x2.kind) {
608       return result.MarkIncomparable();
609     }
610     const auto type_diff = (*this)(x1.pointee_type_id, x2.pointee_type_id);
611     const char* text = x1.kind == PointerReference::Kind::POINTER
612                        ? "pointed-to" : "referred-to";
613     result.MaybeAddEdgeDiff(text, type_diff);
614     return result;
615   }
616 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker617   Result operator()(const PointerToMember& x1, const PointerToMember& x2) {
618     Result result;
619     result.MaybeAddEdgeDiff(
620         "containing", (*this)(x1.containing_type_id, x2.containing_type_id));
621     result.MaybeAddEdgeDiff(
622         "", (*this)(x1.pointee_type_id, x2.pointee_type_id));
623     return result;
624   }
625 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker626   Result operator()(const Typedef&, const Typedef&) {
627     // Compare will never attempt to directly compare Typedefs.
628     Die() << "internal error: CompareWorker(Typedef)";
629   }
630 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker631   Result operator()(const Qualified&, const Qualified&) {
632     // Compare will never attempt to directly compare Qualifiers.
633     Die() << "internal error: CompareWorker(Qualified)";
634   }
635 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker636   Result operator()(const Primitive& x1, const Primitive& x2) {
637     Result result;
638     if (x1.name != x2.name) {
639       return result.MarkIncomparable();
640     }
641     result.diff.holds_changes = !x1.name.empty();
642     if (!ignore.Test(Ignore::PRIMITIVE_TYPE_ENCODING)) {
643       result.MaybeAddNodeDiff("encoding", x1.encoding, x2.encoding);
644     }
645     result.MaybeAddNodeDiff("byte size", x1.bytesize, x2.bytesize);
646     return result;
647   }
648 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker649   Result operator()(const Array& x1, const Array& x2) {
650     Result result;
651     result.MaybeAddNodeDiff("number of elements",
652                             x1.number_of_elements, x2.number_of_elements);
653     const auto type_diff = (*this)(x1.element_type_id, x2.element_type_id);
654     result.MaybeAddEdgeDiff("element", type_diff);
655     return result;
656   }
657 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker658   Result operator()(const BaseClass& x1, const BaseClass& x2) {
659     Result result;
660     result.MaybeAddNodeDiff("inheritance", x1.inheritance, x2.inheritance);
661     result.MaybeAddNodeDiff("offset", x1.offset, x2.offset);
662     result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id));
663     return result;
664   }
665 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker666   Result operator()(const Method& x1, const Method& x2) {
667     Result result;
668     result.MaybeAddNodeDiff(
669         "vtable offset", x1.vtable_offset, x2.vtable_offset);
670     result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id));
671     return result;
672   }
673 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker674   Result operator()(const Member& x1, const Member& x2) {
675     Result result;
676     result.MaybeAddNodeDiff("offset", x1.offset, x2.offset);
677     if (!ignore.Test(Ignore::MEMBER_SIZE)) {
678       const bool bitfield1 = x1.bitsize > 0;
679       const bool bitfield2 = x2.bitsize > 0;
680       if (bitfield1 != bitfield2) {
681         std::ostringstream os;
682         os << "was " << (bitfield1 ? "a bit-field" : "not a bit-field")
683            << ", is now " << (bitfield2 ? "a bit-field" : "not a bit-field");
684         result.AddNodeDiff(os.str());
685       } else {
686         result.MaybeAddNodeDiff("bit-field size", x1.bitsize, x2.bitsize);
687       }
688     }
689     result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id));
690     return result;
691   }
692 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker693   Result operator()(const VariantMember& x1, const VariantMember& x2) {
694     Result result;
695     result.MaybeAddNodeDiff("discriminant", x1.discriminant_value,
696                             x2.discriminant_value);
697     result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id));
698     return result;
699   }
700 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker701   Result operator()(const StructUnion& x1, const StructUnion& x2) {
702     Result result;
703     // Compare two anonymous types recursively, not holding diffs.
704     // Compare two identically named types recursively, holding diffs.
705     // Everything else treated as distinct. No recursion.
706     if (x1.kind != x2.kind || x1.name != x2.name) {
707       return result.MarkIncomparable();
708     }
709     result.diff.holds_changes = !x1.name.empty();
710 
711     const auto& definition1 = x1.definition;
712     const auto& definition2 = x2.definition;
713     Defined(definition1.has_value(), definition2.has_value(), result);
714 
715     if (definition1.has_value() && definition2.has_value()) {
716       result.MaybeAddNodeDiff(
717           "byte size", definition1->bytesize, definition2->bytesize);
718       Nodes(definition1->base_classes, definition2->base_classes, result);
719       Nodes(definition1->methods, definition2->methods, result);
720       Nodes(definition1->members, definition2->members, result);
721     }
722 
723     return result;
724   }
725 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker726   Result operator()(const Enumeration& x1, const Enumeration& x2) {
727     Result result;
728     // Compare two anonymous types recursively, not holding diffs.
729     // Compare two identically named types recursively, holding diffs.
730     // Everything else treated as distinct. No recursion.
731     if (x1.name != x2.name) {
732       return result.MarkIncomparable();
733     }
734     result.diff.holds_changes = !x1.name.empty();
735 
736     const auto& definition1 = x1.definition;
737     const auto& definition2 = x2.definition;
738     Defined(definition1.has_value(), definition2.has_value(), result);
739 
740     if (definition1.has_value() && definition2.has_value()) {
741       if (!ignore.Test(Ignore::ENUM_UNDERLYING_TYPE)) {
742         const auto type_diff = (*this)(definition1->underlying_type_id,
743                                        definition2->underlying_type_id);
744         result.MaybeAddEdgeDiff("underlying", type_diff);
745       }
746 
747       const auto enums1 = definition1->enumerators;
748       const auto enums2 = definition2->enumerators;
749       const auto keys1 = MatchingKeys(enums1);
750       const auto keys2 = MatchingKeys(enums2);
751       auto pairs = PairUp(keys1, keys2);
752       Reorder(pairs);
753       for (const auto& [index1, index2] : pairs) {
754         if (index1 && !index2) {
755           // removed
756           const auto& enum1 = enums1[*index1];
757           std::ostringstream os;
758           os << "enumerator '" << enum1.first
759              << "' (" << enum1.second << ") was removed";
760           result.AddNodeDiff(os.str());
761         } else if (!index1 && index2) {
762           // added
763           const auto& enum2 = enums2[*index2];
764           std::ostringstream os;
765           os << "enumerator '" << enum2.first
766              << "' (" << enum2.second << ") was added";
767           result.AddNodeDiff(os.str());
768         } else if (index1 && index2) {
769           // in both
770           const auto& enum1 = enums1[*index1];
771           const auto& enum2 = enums2[*index2];
772           result.MaybeAddNodeDiff(
773               [&](std::ostream& os) {
774                 os << "enumerator '" << enum1.first << "' value";
775               },
776               enum1.second, enum2.second);
777         } else {
778           Die() << "CompareWorker(Enumeration): impossible pair";
779         }
780       }
781     }
782 
783     return result;
784   }
785 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker786   Result operator()(const Variant& x1, const Variant& x2) {
787     Result result;
788     // Compare two identically named variants recursively, holding diffs.
789     // Everything else treated as distinct. No recursion.
790     if (x1.name != x2.name) {
791       return result.MarkIncomparable();
792     }
793     result.diff.holds_changes = true;  // Anonymous variants are not allowed.
794 
795     result.MaybeAddNodeDiff("bytesize", x1.bytesize, x2.bytesize);
796     if (x1.discriminant.has_value() && x2.discriminant.has_value()) {
797       const auto type_diff =
798           (*this)(x1.discriminant.value(), x2.discriminant.value());
799       result.MaybeAddEdgeDiff("discriminant", type_diff);
800     } else if (x1.discriminant.has_value()) {
801       result.AddEdgeDiff("", Removed(x1.discriminant.value()));
802     } else if (x2.discriminant.has_value()) {
803       result.AddEdgeDiff("", Added(x2.discriminant.value()));
804     }
805     Nodes(x1.members, x2.members, result);
806     return result;
807   }
808 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker809   Result operator()(const Function& x1, const Function& x2) {
810     Result result;
811     const auto type_diff = (*this)(x1.return_type_id, x2.return_type_id);
812     result.MaybeAddEdgeDiff("return", type_diff);
813 
814     const auto& parameters1 = x1.parameters;
815     const auto& parameters2 = x2.parameters;
816     const size_t min = std::min(parameters1.size(), parameters2.size());
817     for (size_t i = 0; i < min; ++i) {
818       const Id p1 = parameters1.at(i);
819       const Id p2 = parameters2.at(i);
820       result.MaybeAddEdgeDiff(
821           [&](std::ostream& os) {
822             os << "parameter " << i + 1;
823           },
824           (*this)(p1, p2));
825     }
826 
827     const bool added = parameters1.size() < parameters2.size();
828     const auto& which = added ? x2 : x1;
829     const auto& parameters = which.parameters;
830     for (size_t i = min; i < parameters.size(); ++i) {
831       const Id parameter = parameters.at(i);
832       std::ostringstream os;
833       os << "parameter " << i + 1 << " of";
834       auto diff = added ? Added(parameter) : Removed(parameter);
835       result.AddEdgeDiff(os.str(), diff);
836     }
837 
838     return result;
839   }
840 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker841   Result operator()(const ElfSymbol& x1, const ElfSymbol& x2) {
842     // ELF symbols have a lot of different attributes that can impact ABI
843     // compatibility and others that either cannot or are subsumed by
844     // information elsewhere.
845     //
846     // Not all attributes are exposed by elf_symbol and fewer still in ABI XML.
847     //
848     // name - definitely part of the key
849     //
850     // type - (ELF symbol type, not C type) one important distinction here would
851     // be global vs thread-local variables
852     //
853     // section - not exposed (modulo aliasing information) and don't care
854     //
855     // value (address, usually) - not exposed (modulo aliasing information) and
856     // don't care
857     //
858     // size - don't care (for variables, subsumed by type information)
859     //
860     // binding - global vs weak vs unique vs local
861     //
862     // visibility - default > protected > hidden > internal
863     //
864     // version / is-default-version - in theory the "hidden" bit (separate from
865     // hidden and local above) can be set independently of the version, but in
866     // practice at most one version of given name is non-hidden; version
867     // (including its presence or absence) is definitely part of the key; we
868     // should probably treat is-default-version as a non-key attribute
869     //
870     // defined - rather fundamental; libabigail currently doesn't track
871     // undefined symbols but we should obviously be prepared in case it does
872 
873     // There are also some externalities which libabigail cares about, which may
874     // or may not be exposed in the XML
875     //
876     // index - don't care
877     //
878     // is-common and friends - don't care
879     //
880     // aliases - exposed, but we don't really care; however we should see what
881     // compilers do, if anything, in terms of propagating type information to
882     // aliases
883 
884     // Linux kernel things.
885     //
886     // MODVERSIONS CRC - fundamental to ABI compatibility, if present
887     //
888     // Symbol namespace - fundamental to ABI compatibility, if present
889 
890     Result result;
891     result.MaybeAddNodeDiff("name", x1.symbol_name, x2.symbol_name);
892 
893     if (x1.version_info && x2.version_info) {
894       result.MaybeAddNodeDiff("version", x1.version_info->name,
895                               x2.version_info->name);
896       result.MaybeAddNodeDiff("default version", x1.version_info->is_default,
897                               x2.version_info->is_default);
898     } else {
899       result.MaybeAddNodeDiff("has version", x1.version_info.has_value(),
900                               x2.version_info.has_value());
901     }
902 
903     result.MaybeAddNodeDiff("defined", x1.is_defined, x2.is_defined);
904     result.MaybeAddNodeDiff("symbol type", x1.symbol_type, x2.symbol_type);
905     result.MaybeAddNodeDiff("binding", x1.binding, x2.binding);
906     result.MaybeAddNodeDiff("visibility", x1.visibility, x2.visibility);
907     if (!ignore.Test(Ignore::SYMBOL_CRC)) {
908       result.MaybeAddNodeDiff("CRC", x1.crc, x2.crc);
909     }
910     result.MaybeAddNodeDiff("namespace", x1.ns, x2.ns);
911 
912     if (x1.type_id && x2.type_id) {
913       result.MaybeAddEdgeDiff("", (*this)(*x1.type_id, *x2.type_id));
914     } else if (x1.type_id) {
915       if (!ignore.Test(Ignore::SYMBOL_TYPE_PRESENCE)) {
916         result.AddEdgeDiff("", Removed(*x1.type_id));
917       }
918     } else if (x2.type_id) {
919       if (!ignore.Test(Ignore::SYMBOL_TYPE_PRESENCE)) {
920         result.AddEdgeDiff("", Added(*x2.type_id));
921       }
922     } else {
923       // both types missing, we have nothing to say
924     }
925 
926     return result;
927   }
928 
operator ()stg::diff::__anon19fbf5d30211::CompareWorker929   Result operator()(const Interface& x1, const Interface& x2) {
930     Result result;
931     result.diff.holds_changes = true;
932     const bool ignore_added = ignore.Test(Ignore::INTERFACE_ADDITION);
933     Nodes(x1.symbols, x2.symbols, ignore_added, result);
934     Nodes(x1.types, x2.types, ignore_added, result);
935     return result;
936   }
937 
938   const Ignore ignore;
939   const Graph& graph;
940   Outcomes& outcomes;
941   Outcomes provisional;
942   std::unordered_map<Comparison, bool, HashComparison> known;
943   SCC<Comparison, HashComparison> scc;
944   Counter queried;
945   Counter already_compared;
946   Counter being_compared;
947   Counter really_compared;
948   Counter equivalent;
949   Counter inequivalent;
950   Histogram scc_size;
951 };
952 
953 }  // namespace
954 
ResolveTypedefs(const Graph & graph,Id id)955 std::pair<Id, std::vector<std::string>> ResolveTypedefs(
956     const Graph& graph, Id id) {
957   std::pair<Id, std::vector<std::string>> result = {id, {}};
958   ResolveTypedef resolve(graph, result.first, result.second);
959   while (graph.Apply(resolve, result.first)) {}
960   return result;
961 }
962 
Compare(Runtime & runtime,Ignore ignore,const Graph & graph,Id root1,Id root2,Outcomes & outcomes)963 Comparison Compare(Runtime& runtime, Ignore ignore, const Graph& graph,
964                    Id root1, Id root2, Outcomes& outcomes) {
965   // The root node (Comparison{{id1}, {id2}}) must be the last node to be
966   // completely visited by the SCC finder and the SCC finder state must be empty
967   // on return from this function call. In particular, the returns where the SCC
968   // is "open" are impossible. The remaining cases (of which one is impossible
969   // for the root node) both have the same two possible return values:
970   //
971   // * (true, Comparison{})
972   // * (false, Comparison{{id1}, {id2}}
973   //
974   // So the invariant value.first == (value.second == Comparison{}) holds and we
975   // can unambiguously return value.second.
976   return CompareWorker(runtime, ignore, graph, outcomes)(root1, root2).second;
977 }
978 
979 }  // namespace diff
980 }  // namespace stg
981