• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::__anon182f30530211::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::__anon182f30530211::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::__anon182f30530211::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::__anon182f30530211::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::__anon182f30530211::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::__anon182f30530211::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::__anon182f30530211::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::__anon182f30530211::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::__anon182f30530211::ResolveTypedef176   ResolveTypedef(const Graph& graph, Id& id, std::vector<std::string>& names)
177       : graph(graph), id(id), names(names) {}
178 
operator ()stg::diff::__anon182f30530211::ResolveTypedef179   bool operator()(const Typedef& x) const {
180     id = x.referred_type_id;
181     names.push_back(x.name);
182     return true;
183   }
184 
185   template <typename Node>
operator ()stg::diff::__anon182f30530211::ResolveTypedef186   bool operator()(const Node&) const {
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::__anon182f30530211::ResolveQualifier198   ResolveQualifier(const Graph& graph, Id& id, Qualifiers& qualifiers)
199       : graph(graph), id(id), qualifiers(qualifiers) {}
200 
operator ()stg::diff::__anon182f30530211::ResolveQualifier201   bool operator()(const Qualified& x) const {
202     id = x.qualified_type_id;
203     qualifiers.insert(x.qualifier);
204     return true;
205   }
206 
operator ()stg::diff::__anon182f30530211::ResolveQualifier207   bool operator()(const Array&) const {
208     // There should be no qualifiers here.
209     qualifiers.clear();
210     return false;
211   }
212 
operator ()stg::diff::__anon182f30530211::ResolveQualifier213   bool operator()(const Function&) const {
214     // There should be no qualifiers here.
215     qualifiers.clear();
216     return false;
217   }
218 
219   template <typename Node>
operator ()stg::diff::__anon182f30530211::ResolveQualifier220   bool operator()(const Node&) const {
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::__anon182f30530211::MatchingKey241   explicit MatchingKey(const Graph& graph) : graph(graph) {}
242 
operator ()stg::diff::__anon182f30530211::MatchingKey243   std::string operator()(Id id) const {
244     return graph.Apply(*this, id);
245   }
246 
operator ()stg::diff::__anon182f30530211::MatchingKey247   std::string operator()(const BaseClass& x) const {
248     return (*this)(x.type_id);
249   }
250 
operator ()stg::diff::__anon182f30530211::MatchingKey251   std::string operator()(const Method& x) const {
252     return x.name + ',' + x.mangled_name;
253   }
254 
operator ()stg::diff::__anon182f30530211::MatchingKey255   std::string operator()(const Member& x) const {
256     if (!x.name.empty()) {
257       return x.name;
258     }
259     return (*this)(x.type_id);
260   }
261 
operator ()stg::diff::__anon182f30530211::MatchingKey262   std::string operator()(const VariantMember& x) const {
263     return x.name;
264   }
265 
operator ()stg::diff::__anon182f30530211::MatchingKey266   std::string operator()(const StructUnion& x) const {
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::__anon182f30530211::MatchingKey283   std::string operator()(const Node&) const {
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::__anon182f30530211::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    * * same == true; perhaps only tentative edge differences
373    * * same == false; at least one definitive node or edge difference
374    *
375    * On the first visit to a node, the value of same is determined via recursive
376    * comparison and 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, same is true results in the deletion of all the
387    * nodes' diffs. The value of same is recorded for all nodes in the SCC.
388    *
389    * On other visits to a node, there are 2 cases. The node is still open
390    * (meaning a recursive visit): return true and an edge diff. The node is
391    * closed (meaning a repeat visit): return the stored value and an edge diff.
392    */
operator ()stg::diff::__anon182f30530211::CompareWorker393   std::pair<bool, Comparison> operator()(Id id1, Id id2) {
394     const Comparison comparison{{id1}, {id2}};
395     ++queried;
396 
397     // 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     // The comparison is either already open or has not been visited at all.
407 
408     // Record the comparison with the 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     // The comparison has now been opened, we must close it before returning.
421 
422     // Really compare.
423     ++really_compared;
424     const auto [same, diff] = CompareWithResolution(id1, id2);
425 
426     // Record the result and check for a complete Strongly-Connected Component.
427     outcomes.insert({comparison, diff});
428     const auto comparisons = scc.Close(*handle);
429     if (comparisons.empty()) {
430       // Open SCC.
431       //
432       // Note that both same and diff are tentative as comparison is still
433       // open.
434       return {same, comparison};
435     }
436     // Closed SCC.
437     //
438     // Note that same and diff now include every inequality and difference in
439     // the SCC via the DFS spanning tree.
440     const auto size = comparisons.size();
441     scc_size.Add(size);
442     (same ? equivalent : inequivalent) += size;
443     for (const auto& c : comparisons) {
444       // Record equality / inequality.
445       known.insert({c, same});
446       if (same) {
447         // Discard provisional diff.
448         outcomes.erase(c);
449       }
450     }
451     return same
452         ? std::make_pair(true, Comparison{})
453         : std::make_pair(false, comparison);
454   }
455 
CompareWithResolutionstg::diff::__anon182f30530211::CompareWorker456   Result CompareWithResolution(Id id1, Id id2) {
457     const auto [unqualified1, qualifiers1] = ResolveQualifiers(graph, id1);
458     const auto [unqualified2, qualifiers2] = ResolveQualifiers(graph, id2);
459     if (!qualifiers1.empty() || !qualifiers2.empty()) {
460       // Qualified type difference.
461       Result result;
462       auto it1 = qualifiers1.begin();
463       auto it2 = qualifiers2.begin();
464       const auto end1 = qualifiers1.end();
465       const auto end2 = qualifiers2.end();
466       while (it1 != end1 || it2 != end2) {
467         if (it2 == end2 || (it1 != end1 && *it1 < *it2)) {
468           if (!ignore.Test(Ignore::QUALIFIER)) {
469             result.AddNodeDiff(QualifiersMessage(*it1, "removed"));
470           }
471           ++it1;
472         } else if (it1 == end1 || (it2 != end2 && *it1 > *it2)) {
473           if (!ignore.Test(Ignore::QUALIFIER)) {
474             result.AddNodeDiff(QualifiersMessage(*it2, "added"));
475           }
476           ++it2;
477         } else {
478           ++it1;
479           ++it2;
480         }
481       }
482       result.MaybeAddEdgeDiff("underlying",
483                               (*this)(unqualified1, unqualified2));
484       return result;
485     }
486 
487     const auto [resolved1, typedefs1] = ResolveTypedefs(graph, unqualified1);
488     const auto [resolved2, typedefs2] = ResolveTypedefs(graph, unqualified2);
489     if (unqualified1 != resolved1 || unqualified2 != resolved2) {
490       // Typedef difference.
491       Result result;
492       result.diff.holds_changes = !typedefs1.empty() && !typedefs2.empty()
493                                   && typedefs1[0] == typedefs2[0];
494       result.MaybeAddEdgeDiff("resolved", (*this)(resolved1, resolved2));
495       return result;
496     }
497 
498     // Compare nodes directly.
499     return graph.Apply2(*this, unqualified1, unqualified2);
500   }
501 
Removedstg::diff::__anon182f30530211::CompareWorker502   Comparison Removed(Id id) {
503     Comparison comparison{{id}, {}};
504     outcomes.insert({comparison, {}});
505     return comparison;
506   }
507 
Addedstg::diff::__anon182f30530211::CompareWorker508   Comparison Added(Id id) {
509     Comparison comparison{{}, {id}};
510     outcomes.insert({comparison, {}});
511     return comparison;
512   }
513 
Definedstg::diff::__anon182f30530211::CompareWorker514   void Defined(bool defined1, bool defined2, Result& result) {
515     if (defined1 != defined2) {
516       if (!ignore.Test(Ignore::TYPE_DECLARATION_STATUS)
517           && !(ignore.Test(Ignore::TYPE_DEFINITION_ADDITION) && defined2)) {
518         std::ostringstream os;
519         os << "was " << (defined1 ? "fully defined" : "only declared")
520            << ", is now " << (defined2 ? "fully defined" : "only declared");
521         result.AddNodeDiff(os.str());
522       }
523     }
524   }
525 
Nodesstg::diff::__anon182f30530211::CompareWorker526   void Nodes(const std::vector<Id>& ids1, const std::vector<Id>& ids2,
527              Result& result) {
528     const auto keys1 = MatchingKeys(graph, ids1);
529     const auto keys2 = MatchingKeys(graph, ids2);
530     auto pairs = PairUp(keys1, keys2);
531     Reorder(pairs);
532     for (const auto& [index1, index2] : pairs) {
533       if (index1 && !index2) {
534         // removed
535         const auto& x1 = ids1[*index1];
536         result.AddEdgeDiff("", Removed(x1));
537       } else if (!index1 && index2) {
538         // added
539         const auto& x2 = ids2[*index2];
540         result.AddEdgeDiff("", Added(x2));
541       } else if (index1 && index2) {
542         // in both
543         const auto& x1 = ids1[*index1];
544         const auto& x2 = ids2[*index2];
545         result.MaybeAddEdgeDiff("", (*this)(x1, x2));
546       } else {
547         Die() << "CompareWorker::Nodes: impossible pair";
548       }
549     }
550   }
551 
Nodesstg::diff::__anon182f30530211::CompareWorker552   void Nodes(const std::map<std::string, Id>& x1,
553              const std::map<std::string, Id>& x2,
554              bool ignore_added, Result& result) {
555     // Group diffs into removed, added and changed symbols for readability.
556     std::vector<Id> removed;
557     std::vector<Id> added;
558     std::vector<std::pair<Id, Id>> in_both;
559 
560     auto it1 = x1.begin();
561     auto it2 = x2.begin();
562     const auto end1 = x1.end();
563     const auto end2 = x2.end();
564     while (it1 != end1 || it2 != end2) {
565       if (it2 == end2 || (it1 != end1 && it1->first < it2->first)) {
566         // removed
567         removed.push_back(it1->second);
568         ++it1;
569       } else if (it1 == end1 || (it2 != end2 && it1->first > it2->first)) {
570         // added
571         if (!ignore_added) {
572           added.push_back(it2->second);
573         }
574         ++it2;
575       } else {
576         // in both
577         in_both.emplace_back(it1->second, it2->second);
578         ++it1;
579         ++it2;
580       }
581     }
582 
583     for (const auto symbol1 : removed) {
584       result.AddEdgeDiff("", Removed(symbol1));
585     }
586     for (const auto symbol2 : added) {
587       result.AddEdgeDiff("", Added(symbol2));
588     }
589     for (const auto& [symbol1, symbol2] : in_both) {
590       result.MaybeAddEdgeDiff("", (*this)(symbol1, symbol2));
591     }
592   }
593 
Mismatchstg::diff::__anon182f30530211::CompareWorker594   Result Mismatch() {
595     return Result().MarkIncomparable();
596   }
597 
operator ()stg::diff::__anon182f30530211::CompareWorker598   Result operator()(const Special& x1, const Special& x2) {
599     Result result;
600     if (x1.kind != x2.kind) {
601       return result.MarkIncomparable();
602     }
603     return result;
604   }
605 
operator ()stg::diff::__anon182f30530211::CompareWorker606   Result operator()(const PointerReference& x1, const PointerReference& x2) {
607     Result result;
608     if (x1.kind != x2.kind) {
609       return result.MarkIncomparable();
610     }
611     const auto type_diff = (*this)(x1.pointee_type_id, x2.pointee_type_id);
612     const char* text = x1.kind == PointerReference::Kind::POINTER
613                        ? "pointed-to" : "referred-to";
614     result.MaybeAddEdgeDiff(text, type_diff);
615     return result;
616   }
617 
operator ()stg::diff::__anon182f30530211::CompareWorker618   Result operator()(const PointerToMember& x1, const PointerToMember& x2) {
619     Result result;
620     result.MaybeAddEdgeDiff(
621         "containing", (*this)(x1.containing_type_id, x2.containing_type_id));
622     result.MaybeAddEdgeDiff(
623         "", (*this)(x1.pointee_type_id, x2.pointee_type_id));
624     return result;
625   }
626 
operator ()stg::diff::__anon182f30530211::CompareWorker627   Result operator()(const Typedef&, const Typedef&) {
628     // Compare will never attempt to directly compare Typedefs.
629     Die() << "internal error: CompareWorker(Typedef)";
630   }
631 
operator ()stg::diff::__anon182f30530211::CompareWorker632   Result operator()(const Qualified&, const Qualified&) {
633     // Compare will never attempt to directly compare Qualifiers.
634     Die() << "internal error: CompareWorker(Qualified)";
635   }
636 
operator ()stg::diff::__anon182f30530211::CompareWorker637   Result operator()(const Primitive& x1, const Primitive& x2) {
638     Result result;
639     if (x1.name != x2.name) {
640       return result.MarkIncomparable();
641     }
642     result.diff.holds_changes = !x1.name.empty();
643     if (!ignore.Test(Ignore::PRIMITIVE_TYPE_ENCODING)) {
644       result.MaybeAddNodeDiff("encoding", x1.encoding, x2.encoding);
645     }
646     result.MaybeAddNodeDiff("byte size", x1.bytesize, x2.bytesize);
647     return result;
648   }
649 
operator ()stg::diff::__anon182f30530211::CompareWorker650   Result operator()(const Array& x1, const Array& x2) {
651     Result result;
652     result.MaybeAddNodeDiff("number of elements",
653                             x1.number_of_elements, x2.number_of_elements);
654     const auto type_diff = (*this)(x1.element_type_id, x2.element_type_id);
655     result.MaybeAddEdgeDiff("element", type_diff);
656     return result;
657   }
658 
operator ()stg::diff::__anon182f30530211::CompareWorker659   Result operator()(const BaseClass& x1, const BaseClass& x2) {
660     Result result;
661     result.MaybeAddNodeDiff("inheritance", x1.inheritance, x2.inheritance);
662     result.MaybeAddNodeDiff("offset", x1.offset, x2.offset);
663     result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id));
664     return result;
665   }
666 
operator ()stg::diff::__anon182f30530211::CompareWorker667   Result operator()(const Method& x1, const Method& x2) {
668     Result result;
669     result.MaybeAddNodeDiff(
670         "vtable offset", x1.vtable_offset, x2.vtable_offset);
671     result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id));
672     return result;
673   }
674 
operator ()stg::diff::__anon182f30530211::CompareWorker675   Result operator()(const Member& x1, const Member& x2) {
676     Result result;
677     result.MaybeAddNodeDiff("offset", x1.offset, x2.offset);
678     if (!ignore.Test(Ignore::MEMBER_SIZE)) {
679       const bool bitfield1 = x1.bitsize > 0;
680       const bool bitfield2 = x2.bitsize > 0;
681       if (bitfield1 != bitfield2) {
682         std::ostringstream os;
683         os << "was " << (bitfield1 ? "a bit-field" : "not a bit-field")
684            << ", is now " << (bitfield2 ? "a bit-field" : "not a bit-field");
685         result.AddNodeDiff(os.str());
686       } else {
687         result.MaybeAddNodeDiff("bit-field size", x1.bitsize, x2.bitsize);
688       }
689     }
690     result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id));
691     return result;
692   }
693 
operator ()stg::diff::__anon182f30530211::CompareWorker694   Result operator()(const VariantMember& x1, const VariantMember& x2) {
695     Result result;
696     result.MaybeAddNodeDiff("discriminant", x1.discriminant_value,
697                             x2.discriminant_value);
698     result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id));
699     return result;
700   }
701 
operator ()stg::diff::__anon182f30530211::CompareWorker702   Result operator()(const StructUnion& x1, const StructUnion& x2) {
703     Result result;
704     // Compare two anonymous types recursively, not holding diffs.
705     // Compare two identically named types recursively, holding diffs.
706     // Everything else treated as distinct. No recursion.
707     if (x1.kind != x2.kind || x1.name != x2.name) {
708       return result.MarkIncomparable();
709     }
710     result.diff.holds_changes = !x1.name.empty();
711 
712     const auto& definition1 = x1.definition;
713     const auto& definition2 = x2.definition;
714     Defined(definition1.has_value(), definition2.has_value(), result);
715 
716     if (definition1.has_value() && definition2.has_value()) {
717       result.MaybeAddNodeDiff(
718           "byte size", definition1->bytesize, definition2->bytesize);
719       Nodes(definition1->base_classes, definition2->base_classes, result);
720       Nodes(definition1->methods, definition2->methods, result);
721       Nodes(definition1->members, definition2->members, result);
722     }
723 
724     return result;
725   }
726 
operator ()stg::diff::__anon182f30530211::CompareWorker727   Result operator()(const Enumeration& x1, const Enumeration& x2) {
728     Result result;
729     // Compare two anonymous types recursively, not holding diffs.
730     // Compare two identically named types recursively, holding diffs.
731     // Everything else treated as distinct. No recursion.
732     if (x1.name != x2.name) {
733       return result.MarkIncomparable();
734     }
735     result.diff.holds_changes = !x1.name.empty();
736 
737     const auto& definition1 = x1.definition;
738     const auto& definition2 = x2.definition;
739     Defined(definition1.has_value(), definition2.has_value(), result);
740 
741     if (definition1.has_value() && definition2.has_value()) {
742       if (!ignore.Test(Ignore::ENUM_UNDERLYING_TYPE)) {
743         const auto type_diff = (*this)(definition1->underlying_type_id,
744                                        definition2->underlying_type_id);
745         result.MaybeAddEdgeDiff("underlying", type_diff);
746       }
747 
748       const auto enums1 = definition1->enumerators;
749       const auto enums2 = definition2->enumerators;
750       const auto keys1 = MatchingKeys(enums1);
751       const auto keys2 = MatchingKeys(enums2);
752       auto pairs = PairUp(keys1, keys2);
753       Reorder(pairs);
754       for (const auto& [index1, index2] : pairs) {
755         if (index1 && !index2) {
756           // removed
757           const auto& enum1 = enums1[*index1];
758           std::ostringstream os;
759           os << "enumerator '" << enum1.first
760              << "' (" << enum1.second << ") was removed";
761           result.AddNodeDiff(os.str());
762         } else if (!index1 && index2) {
763           // added
764           const auto& enum2 = enums2[*index2];
765           std::ostringstream os;
766           os << "enumerator '" << enum2.first
767              << "' (" << enum2.second << ") was added";
768           result.AddNodeDiff(os.str());
769         } else if (index1 && index2) {
770           // in both
771           const auto& enum1 = enums1[*index1];
772           const auto& enum2 = enums2[*index2];
773           result.MaybeAddNodeDiff(
774               [&](std::ostream& os) {
775                 os << "enumerator '" << enum1.first << "' value";
776               },
777               enum1.second, enum2.second);
778         } else {
779           Die() << "CompareWorker(Enumeration): impossible pair";
780         }
781       }
782     }
783 
784     return result;
785   }
786 
operator ()stg::diff::__anon182f30530211::CompareWorker787   Result operator()(const Variant& x1, const Variant& x2) {
788     Result result;
789     // Compare two identically named variants recursively, holding diffs.
790     // Everything else treated as distinct. No recursion.
791     if (x1.name != x2.name) {
792       return result.MarkIncomparable();
793     }
794     result.diff.holds_changes = true;  // Anonymous variants are not allowed.
795 
796     result.MaybeAddNodeDiff("bytesize", x1.bytesize, x2.bytesize);
797     if (x1.discriminant.has_value() && x2.discriminant.has_value()) {
798       const auto type_diff =
799           (*this)(x1.discriminant.value(), x2.discriminant.value());
800       result.MaybeAddEdgeDiff("discriminant", type_diff);
801     } else if (x1.discriminant.has_value()) {
802       result.AddEdgeDiff("", Removed(x1.discriminant.value()));
803     } else if (x2.discriminant.has_value()) {
804       result.AddEdgeDiff("", Added(x2.discriminant.value()));
805     }
806     Nodes(x1.members, x2.members, result);
807     return result;
808   }
809 
operator ()stg::diff::__anon182f30530211::CompareWorker810   Result operator()(const Function& x1, const Function& x2) {
811     Result result;
812     const auto type_diff = (*this)(x1.return_type_id, x2.return_type_id);
813     result.MaybeAddEdgeDiff("return", type_diff);
814 
815     const auto& parameters1 = x1.parameters;
816     const auto& parameters2 = x2.parameters;
817     const size_t min = std::min(parameters1.size(), parameters2.size());
818     for (size_t i = 0; i < min; ++i) {
819       const Id p1 = parameters1.at(i);
820       const Id p2 = parameters2.at(i);
821       result.MaybeAddEdgeDiff(
822           [&](std::ostream& os) {
823             os << "parameter " << i + 1;
824           },
825           (*this)(p1, p2));
826     }
827 
828     const bool added = parameters1.size() < parameters2.size();
829     const auto& which = added ? x2 : x1;
830     const auto& parameters = which.parameters;
831     for (size_t i = min; i < parameters.size(); ++i) {
832       const Id parameter = parameters.at(i);
833       std::ostringstream os;
834       os << "parameter " << i + 1 << " of";
835       auto diff = added ? Added(parameter) : Removed(parameter);
836       result.AddEdgeDiff(os.str(), diff);
837     }
838 
839     return result;
840   }
841 
operator ()stg::diff::__anon182f30530211::CompareWorker842   Result operator()(const ElfSymbol& x1, const ElfSymbol& x2) {
843     // ELF symbols have a lot of different attributes that can impact ABI
844     // compatibility and others that either cannot or are subsumed by
845     // information elsewhere.
846     //
847     // Not all attributes are exposed by elf_symbol and fewer still in ABI XML.
848     //
849     // name - definitely part of the key
850     //
851     // type - (ELF symbol type, not C type) one important distinction here would
852     // be global vs thread-local variables
853     //
854     // section - not exposed (modulo aliasing information) and don't care
855     //
856     // value (address, usually) - not exposed (modulo aliasing information) and
857     // don't care
858     //
859     // size - don't care (for variables, subsumed by type information)
860     //
861     // binding - global vs weak vs unique vs local
862     //
863     // visibility - default > protected > hidden > internal
864     //
865     // version / is-default-version - in theory the "hidden" bit (separate from
866     // hidden and local above) can be set independently of the version, but in
867     // practice at most one version of given name is non-hidden; version
868     // (including its presence or absence) is definitely part of the key; we
869     // should probably treat is-default-version as a non-key attribute
870     //
871     // defined - rather fundamental; libabigail currently doesn't track
872     // undefined symbols but we should obviously be prepared in case it does
873 
874     // There are also some externalities which libabigail cares about, which may
875     // or may not be exposed in the XML
876     //
877     // index - don't care
878     //
879     // is-common and friends - don't care
880     //
881     // aliases - exposed, but we don't really care; however we should see what
882     // compilers do, if anything, in terms of propagating type information to
883     // aliases
884 
885     // Linux kernel things.
886     //
887     // MODVERSIONS CRC - fundamental to ABI compatibility, if present
888     //
889     // Symbol namespace - fundamental to ABI compatibility, if present
890 
891     Result result;
892     result.MaybeAddNodeDiff("name", x1.symbol_name, x2.symbol_name);
893 
894     if (x1.version_info && x2.version_info) {
895       result.MaybeAddNodeDiff("version", x1.version_info->name,
896                               x2.version_info->name);
897       result.MaybeAddNodeDiff("default version", x1.version_info->is_default,
898                               x2.version_info->is_default);
899     } else {
900       result.MaybeAddNodeDiff("has version", x1.version_info.has_value(),
901                               x2.version_info.has_value());
902     }
903 
904     result.MaybeAddNodeDiff("defined", x1.is_defined, x2.is_defined);
905     result.MaybeAddNodeDiff("symbol type", x1.symbol_type, x2.symbol_type);
906     result.MaybeAddNodeDiff("binding", x1.binding, x2.binding);
907     result.MaybeAddNodeDiff("visibility", x1.visibility, x2.visibility);
908     if (!ignore.Test(Ignore::SYMBOL_CRC)) {
909       result.MaybeAddNodeDiff("CRC", x1.crc, x2.crc);
910     }
911     result.MaybeAddNodeDiff("namespace", x1.ns, x2.ns);
912 
913     if (x1.type_id && x2.type_id) {
914       result.MaybeAddEdgeDiff("", (*this)(*x1.type_id, *x2.type_id));
915     } else if (x1.type_id) {
916       if (!ignore.Test(Ignore::SYMBOL_TYPE_PRESENCE)) {
917         result.AddEdgeDiff("", Removed(*x1.type_id));
918       }
919     } else if (x2.type_id) {
920       if (!ignore.Test(Ignore::SYMBOL_TYPE_PRESENCE)) {
921         result.AddEdgeDiff("", Added(*x2.type_id));
922       }
923     } else {
924       // both types missing, we have nothing to say
925     }
926 
927     return result;
928   }
929 
operator ()stg::diff::__anon182f30530211::CompareWorker930   Result operator()(const Interface& x1, const Interface& x2) {
931     Result result;
932     result.diff.holds_changes = true;
933     const bool ignore_added = ignore.Test(Ignore::INTERFACE_ADDITION);
934     Nodes(x1.symbols, x2.symbols, ignore_added, result);
935     Nodes(x1.types, x2.types, ignore_added, result);
936     return result;
937   }
938 
939   const Ignore ignore;
940   const Graph& graph;
941   Outcomes& outcomes;
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