• 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 <map>
28 #include <optional>
29 #include <ostream>
30 #include <sstream>
31 #include <string>
32 #include <string_view>
33 #include <utility>
34 #include <vector>
35 
36 #include "error.h"
37 #include "graph.h"
38 #include "order.h"
39 
40 namespace stg {
41 
42 struct IgnoreDescriptor {
43   std::string_view name;
44   Ignore::Value value;
45 };
46 
47 static constexpr std::array<IgnoreDescriptor, 9> kIgnores{{
48   {"type_declaration_status",  Ignore::TYPE_DECLARATION_STATUS  },
49   {"symbol_type_presence",     Ignore::SYMBOL_TYPE_PRESENCE     },
50   {"primitive_type_encoding",  Ignore::PRIMITIVE_TYPE_ENCODING  },
51   {"member_size",              Ignore::MEMBER_SIZE              },
52   {"enum_underlying_type",     Ignore::ENUM_UNDERLYING_TYPE     },
53   {"qualifier",                Ignore::QUALIFIER                },
54   {"linux_symbol_crc",         Ignore::SYMBOL_CRC               },
55   {"interface_addition",       Ignore::INTERFACE_ADDITION       },
56   {"type_definition_addition", Ignore::TYPE_DEFINITION_ADDITION },
57 }};
58 
ParseIgnore(std::string_view ignore)59 std::optional<Ignore::Value> ParseIgnore(std::string_view ignore) {
60   for (const auto& [name, value] : kIgnores) {
61     if (name == ignore) {
62       return {value};
63     }
64   }
65   return {};
66 }
67 
operator <<(std::ostream & os,IgnoreUsage)68 std::ostream& operator<<(std::ostream& os, IgnoreUsage) {
69   os << "ignore options:";
70   for (const auto& [name, _] : kIgnores) {
71     os << ' ' << name;
72   }
73   return os << '\n';
74 }
75 
QualifiersMessage(Qualifier qualifier,const std::string & action)76 std::string QualifiersMessage(Qualifier qualifier, const std::string& action) {
77   std::ostringstream os;
78   os << "qualifier " << qualifier << ' ' << action;
79   return os.str();
80 }
81 
82 /*
83  * We compute a diff for every visited node.
84  *
85  * Each node has one of:
86  * 1. equals = true; perhaps only tentative edge differences
87  * 2. equals = false; at least one definitive node or edge difference
88  *
89  * On the first visit to a node we can put a placeholder in, the equals value is
90  * irrelevant, the diff may contain local and edge differences. If an SCC
91  * contains only internal edge differences (and equivalently equals is true)
92  * then the differences can all (eventually) be discarded.
93  *
94  * On exit from the first visit to a node, equals reflects the tree of
95  * comparisons below that node in the DFS and similarly, the diff graph starting
96  * from the node contains a subtree of this tree plus potentially edges to
97  * existing nodes to the side or below (already visited SCCs, sharing), or above
98  * (back links forming cycles).
99  *
100  * When an SCC is closed, all equals implies deleting all diffs, any false
101  * implies updating all to false.
102  *
103  * On subsequent visits to a node, there are 2 cases. The node is still open:
104  * return true and an edge diff. The node is closed, return the stored value and
105  * an edge diff.
106  */
operator ()(Id id1,Id id2)107 std::pair<bool, std::optional<Comparison>> Compare::operator()(Id id1, Id id2) {
108   const Comparison comparison{{id1}, {id2}};
109   ++queried;
110 
111   // 1. Check if the comparison has an already known result.
112   auto already_known = known.find(comparison);
113   if (already_known != known.end()) {
114     // Already visited and closed.
115     ++already_compared;
116     if (already_known->second) {
117       return {true, {}};
118     } else  {
119       return {false, {comparison}};
120     }
121   }
122   // Either open or not visited at all
123 
124   // 2. Record node with Strongly-Connected Component finder.
125   auto handle = scc.Open(comparison);
126   if (!handle) {
127     // Already open.
128     //
129     // Return a dummy true outcome and some tentative diffs. The diffs may end
130     // up not being used and, while it would be nice to be lazier, they encode
131     // all the cycling-breaking edges needed to recreate a full diff structure.
132     ++being_compared;
133     return {true, {comparison}};
134   }
135   // Comparison opened, need to close it before returning.
136   ++really_compared;
137 
138   Result result;
139 
140   const auto [unqualified1, qualifiers1] = ResolveQualifiers(graph, id1);
141   const auto [unqualified2, qualifiers2] = ResolveQualifiers(graph, id2);
142   if (!qualifiers1.empty() || !qualifiers2.empty()) {
143     // 3.1 Qualified type difference.
144     auto it1 = qualifiers1.begin();
145     auto it2 = qualifiers2.begin();
146     const auto end1 = qualifiers1.end();
147     const auto end2 = qualifiers2.end();
148     while (it1 != end1 || it2 != end2) {
149       if (it2 == end2 || (it1 != end1 && *it1 < *it2)) {
150         if (!ignore.Test(Ignore::QUALIFIER)) {
151           result.AddNodeDiff(QualifiersMessage(*it1, "removed"));
152         }
153         ++it1;
154       } else if (it1 == end1 || (it2 != end2 && *it1 > *it2)) {
155         if (!ignore.Test(Ignore::QUALIFIER)) {
156           result.AddNodeDiff(QualifiersMessage(*it2, "added"));
157         }
158         ++it2;
159       } else {
160         ++it1;
161         ++it2;
162       }
163     }
164     const auto type_diff = (*this)(unqualified1, unqualified2);
165     result.MaybeAddEdgeDiff("underlying", type_diff);
166   } else {
167     const auto [resolved1, typedefs1] = ResolveTypedefs(graph, unqualified1);
168     const auto [resolved2, typedefs2] = ResolveTypedefs(graph, unqualified2);
169     if (unqualified1 != resolved1 || unqualified2 != resolved2) {
170       // 3.2 Typedef difference.
171       result.diff_.holds_changes = !typedefs1.empty() && !typedefs2.empty()
172                                    && typedefs1[0] == typedefs2[0];
173       result.MaybeAddEdgeDiff("resolved", (*this)(resolved1, resolved2));
174     } else {
175       // 4. Compare nodes, if possible.
176       result = graph.Apply2<Result>(*this, unqualified1, unqualified2);
177     }
178   }
179 
180   // 5. Update result and check for a complete Strongly-Connected Component.
181   provisional.insert({comparison, result.diff_});
182   auto comparisons = scc.Close(*handle);
183   auto size = comparisons.size();
184   if (size) {
185     scc_size.Add(size);
186     // Closed SCC.
187     //
188     // Note that result now incorporates every inequality and difference in the
189     // SCC via the DFS spanning tree.
190     for (auto& c : comparisons) {
191       // Record equality / inequality.
192       known.insert({c, result.equals_});
193       const auto it = provisional.find(c);
194       Check(it != provisional.end())
195           << "internal error: missing provisional diffs";
196       if (!result.equals_) {
197         // Record differences.
198         outcomes.insert(*it);
199       }
200       provisional.erase(it);
201     }
202     if (result.equals_) {
203       equivalent += size;
204       return {true, {}};
205     } else {
206       inequivalent += size;
207       return {false, {comparison}};
208     }
209   }
210 
211   // Note that both equals and diff are tentative as comparison is still open.
212   return {result.equals_, {comparison}};
213 }
214 
Removed(Id id)215 Comparison Compare::Removed(Id id) {
216   Comparison comparison{{id}, {}};
217   outcomes.insert({comparison, {}});
218   return comparison;
219 }
220 
Added(Id id)221 Comparison Compare::Added(Id id) {
222   Comparison comparison{{}, {id}};
223   outcomes.insert({comparison, {}});
224   return comparison;
225 }
226 
Mismatch()227 Result Compare::Mismatch() {
228   return Result().MarkIncomparable();
229 }
230 
operator ()(const Special & x1,const Special & x2)231 Result Compare::operator()(const Special& x1, const Special& x2) {
232   Result result;
233   if (x1.kind != x2.kind) {
234     return result.MarkIncomparable();
235   }
236   return result;
237 }
238 
operator ()(const PointerReference & x1,const PointerReference & x2)239 Result Compare::operator()(const PointerReference& x1,
240                            const PointerReference& x2) {
241   Result result;
242   if (x1.kind != x2.kind) {
243     return result.MarkIncomparable();
244   }
245   const auto type_diff = (*this)(x1.pointee_type_id, x2.pointee_type_id);
246   const auto text =
247       x1.kind == PointerReference::Kind::POINTER ? "pointed-to" : "referred-to";
248   result.MaybeAddEdgeDiff(text, type_diff);
249   return result;
250 }
251 
operator ()(const PointerToMember & x1,const PointerToMember & x2)252 Result Compare::operator()(const PointerToMember& x1,
253                            const PointerToMember& x2) {
254   Result result;
255   result.MaybeAddEdgeDiff(
256       "containing", (*this)(x1.containing_type_id, x2.containing_type_id));
257   result.MaybeAddEdgeDiff("", (*this)(x1.pointee_type_id, x2.pointee_type_id));
258   return result;
259 }
260 
operator ()(const Typedef &,const Typedef &)261 Result Compare::operator()(const Typedef&, const Typedef&) {
262   // Compare will never attempt to directly compare Typedefs.
263   Die() << "internal error: Compare(Typedef)";
264 }
265 
operator ()(const Qualified &,const Qualified &)266 Result Compare::operator()(const Qualified&, const Qualified&) {
267   // Compare will never attempt to directly compare Qualifiers.
268   Die() << "internal error: Compare(Qualified)";
269 }
270 
operator ()(const Primitive & x1,const Primitive & x2)271 Result Compare::operator()(const Primitive& x1, const Primitive& x2) {
272   Result result;
273   if (x1.name != x2.name) {
274     return result.MarkIncomparable();
275   }
276   result.diff_.holds_changes = !x1.name.empty();
277   if (!ignore.Test(Ignore::PRIMITIVE_TYPE_ENCODING)) {
278     result.MaybeAddNodeDiff("encoding", x1.encoding, x2.encoding);
279   }
280   result.MaybeAddNodeDiff("byte size", x1.bytesize, x2.bytesize);
281   return result;
282 }
283 
operator ()(const Array & x1,const Array & x2)284 Result Compare::operator()(const Array& x1, const Array& x2) {
285   Result result;
286   result.MaybeAddNodeDiff("number of elements",
287                           x1.number_of_elements, x2.number_of_elements);
288   const auto type_diff = (*this)(x1.element_type_id, x2.element_type_id);
289   result.MaybeAddEdgeDiff("element", type_diff);
290   return result;
291 }
292 
CompareDefined(bool defined1,bool defined2,Result & result)293 void Compare::CompareDefined(bool defined1, bool defined2, Result& result) {
294   if (defined1 != defined2) {
295     if (!ignore.Test(Ignore::TYPE_DECLARATION_STATUS)
296         && !(ignore.Test(Ignore::TYPE_DEFINITION_ADDITION) && defined2)) {
297       std::ostringstream os;
298       os << "was " << (defined1 ? "fully defined" : "only declared")
299          << ", is now " << (defined2 ? "fully defined" : "only declared");
300       result.AddNodeDiff(os.str());
301     }
302   }
303 }
304 
305 namespace {
306 
307 using KeyIndexPairs = std::vector<std::pair<std::string, size_t>>;
MatchingKeys(const Graph & graph,const std::vector<Id> & ids)308 KeyIndexPairs MatchingKeys(const Graph& graph, const std::vector<Id>& ids) {
309   KeyIndexPairs keys;
310   const auto size = ids.size();
311   keys.reserve(size);
312   size_t anonymous_ix = 0;
313   for (size_t ix = 0; ix < size; ++ix) {
314     auto key = MatchingKey(graph)(ids[ix]);
315     if (key.empty()) {
316       key = "#anon#" + std::to_string(anonymous_ix++);
317     }
318     keys.emplace_back(key, ix);
319   }
320   std::stable_sort(keys.begin(), keys.end());
321   return keys;
322 }
323 
324 using MatchedPairs =
325     std::vector<std::pair<std::optional<size_t>, std::optional<size_t>>>;
PairUp(const KeyIndexPairs & keys1,const KeyIndexPairs & keys2)326 MatchedPairs PairUp(const KeyIndexPairs& keys1, const KeyIndexPairs& keys2) {
327   MatchedPairs pairs;
328   pairs.reserve(std::max(keys1.size(), keys2.size()));
329   auto it1 = keys1.begin();
330   auto it2 = keys2.begin();
331   const auto end1 = keys1.end();
332   const auto end2 = keys2.end();
333   while (it1 != end1 || it2 != end2) {
334     if (it2 == end2 || (it1 != end1 && it1->first < it2->first)) {
335       // removed
336       pairs.push_back({{it1->second}, {}});
337       ++it1;
338     } else if (it1 == end1 || (it2 != end2 && it1->first > it2->first)) {
339       // added
340       pairs.push_back({{}, {it2->second}});
341       ++it2;
342     } else {
343       // in both
344       pairs.push_back({{it1->second}, {it2->second}});
345       ++it1;
346       ++it2;
347     }
348   }
349   return pairs;
350 }
351 
CompareNodes(Result & result,Compare & compare,const std::vector<Id> & ids1,const std::vector<Id> & ids2)352 void CompareNodes(Result& result, Compare& compare, const std::vector<Id>& ids1,
353                   const std::vector<Id>& ids2) {
354   const auto keys1 = MatchingKeys(compare.graph, ids1);
355   const auto keys2 = MatchingKeys(compare.graph, ids2);
356   auto pairs = PairUp(keys1, keys2);
357   Reorder(pairs);
358   for (const auto& [index1, index2] : pairs) {
359     if (index1 && !index2) {
360       // removed
361       const auto& x1 = ids1[*index1];
362       result.AddEdgeDiff("", compare.Removed(x1));
363     } else if (!index1 && index2) {
364       // added
365       const auto& x2 = ids2[*index2];
366       result.AddEdgeDiff("", compare.Added(x2));
367     } else if (index1 && index2) {
368       // in both
369       const auto& x1 = ids1[*index1];
370       const auto& x2 = ids2[*index2];
371       result.MaybeAddEdgeDiff("", compare(x1, x2));
372     } else {
373       Die() << "CompareNodes: impossible pair";
374     }
375   }
376 }
377 
CompareNodes(Result & result,Compare & compare,const std::map<std::string,Id> & x1,const std::map<std::string,Id> & x2,bool ignore_added)378 void CompareNodes(Result& result, Compare& compare,
379                   const std::map<std::string, Id>& x1,
380                   const std::map<std::string, Id>& x2,
381                   bool ignore_added) {
382   // Group diffs into removed, added and changed symbols for readability.
383   std::vector<Id> removed;
384   std::vector<Id> added;
385   std::vector<std::pair<Id, Id>> in_both;
386 
387   auto it1 = x1.begin();
388   auto it2 = x2.begin();
389   const auto end1 = x1.end();
390   const auto end2 = x2.end();
391   while (it1 != end1 || it2 != end2) {
392     if (it2 == end2 || (it1 != end1 && it1->first < it2->first)) {
393       // removed
394       removed.push_back(it1->second);
395       ++it1;
396     } else if (it1 == end1 || (it2 != end2 && it1->first > it2->first)) {
397       // added
398       if (!ignore_added) {
399         added.push_back(it2->second);
400       }
401       ++it2;
402     } else {
403       // in both
404       in_both.emplace_back(it1->second, it2->second);
405       ++it1;
406       ++it2;
407     }
408   }
409 
410   for (const auto symbol1 : removed) {
411     result.AddEdgeDiff("", compare.Removed(symbol1));
412   }
413   for (const auto symbol2 : added) {
414     result.AddEdgeDiff("", compare.Added(symbol2));
415   }
416   for (const auto& [symbol1, symbol2] : in_both) {
417     result.MaybeAddEdgeDiff("", compare(symbol1, symbol2));
418   }
419 }
420 
421 }  // namespace
422 
operator ()(const BaseClass & x1,const BaseClass & x2)423 Result Compare::operator()(const BaseClass& x1, const BaseClass& x2) {
424   Result result;
425   result.MaybeAddNodeDiff("inheritance", x1.inheritance, x2.inheritance);
426   result.MaybeAddNodeDiff("offset", x1.offset, x2.offset);
427   result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id));
428   return result;
429 }
430 
operator ()(const Method & x1,const Method & x2)431 Result Compare::operator()(const Method& x1, const Method& x2) {
432   Result result;
433   result.MaybeAddNodeDiff("vtable offset", x1.vtable_offset, x2.vtable_offset);
434   result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id));
435   return result;
436 }
437 
operator ()(const Member & x1,const Member & x2)438 Result Compare::operator()(const Member& x1, const Member& x2) {
439   Result result;
440   result.MaybeAddNodeDiff("offset", x1.offset, x2.offset);
441   if (!ignore.Test(Ignore::MEMBER_SIZE)) {
442     const bool bitfield1 = x1.bitsize > 0;
443     const bool bitfield2 = x2.bitsize > 0;
444     if (bitfield1 != bitfield2) {
445       std::ostringstream os;
446       os << "was " << (bitfield1 ? "a bit-field" : "not a bit-field")
447          << ", is now " << (bitfield2 ? "a bit-field" : "not a bit-field");
448       result.AddNodeDiff(os.str());
449     } else {
450       result.MaybeAddNodeDiff("bit-field size", x1.bitsize, x2.bitsize);
451     }
452   }
453   result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id));
454   return result;
455 }
456 
operator ()(const VariantMember & x1,const VariantMember & x2)457 Result Compare::operator()(const VariantMember& x1, const VariantMember& x2) {
458   Result result;
459   result.MaybeAddNodeDiff("discriminant", x1.discriminant_value,
460                           x2.discriminant_value);
461   result.MaybeAddEdgeDiff("", (*this)(x1.type_id, x2.type_id));
462   return result;
463 }
464 
operator ()(const StructUnion & x1,const StructUnion & x2)465 Result Compare::operator()(const StructUnion& x1, const StructUnion& x2) {
466   Result result;
467   // Compare two anonymous types recursively, not holding diffs.
468   // Compare two identically named types recursively, holding diffs.
469   // Everything else treated as distinct. No recursion.
470   if (x1.kind != x2.kind || x1.name != x2.name) {
471     return result.MarkIncomparable();
472   }
473   result.diff_.holds_changes = !x1.name.empty();
474 
475   const auto& definition1 = x1.definition;
476   const auto& definition2 = x2.definition;
477   CompareDefined(definition1.has_value(), definition2.has_value(), result);
478 
479   if (definition1.has_value() && definition2.has_value()) {
480     result.MaybeAddNodeDiff(
481         "byte size", definition1->bytesize, definition2->bytesize);
482     CompareNodes(
483         result, *this, definition1->base_classes, definition2->base_classes);
484     CompareNodes(result, *this, definition1->methods, definition2->methods);
485     CompareNodes(result, *this, definition1->members, definition2->members);
486   }
487 
488   return result;
489 }
490 
MatchingKeys(const Enumeration::Enumerators & enums)491 static KeyIndexPairs MatchingKeys(const Enumeration::Enumerators& enums) {
492   KeyIndexPairs names;
493   const auto size = enums.size();
494   names.reserve(size);
495   for (size_t ix = 0; ix < size; ++ix) {
496     const auto& name = enums[ix].first;
497     names.emplace_back(name, ix);
498   }
499   std::stable_sort(names.begin(), names.end());
500   return names;
501 }
502 
operator ()(const Enumeration & x1,const Enumeration & x2)503 Result Compare::operator()(const Enumeration& x1, const Enumeration& x2) {
504   Result result;
505   // Compare two anonymous types recursively, not holding diffs.
506   // Compare two identically named types recursively, holding diffs.
507   // Everything else treated as distinct. No recursion.
508   if (x1.name != x2.name) {
509     return result.MarkIncomparable();
510   }
511   result.diff_.holds_changes = !x1.name.empty();
512 
513   const auto& definition1 = x1.definition;
514   const auto& definition2 = x2.definition;
515   CompareDefined(definition1.has_value(), definition2.has_value(), result);
516 
517   if (definition1.has_value() && definition2.has_value()) {
518     if (!ignore.Test(Ignore::ENUM_UNDERLYING_TYPE)) {
519       const auto type_diff = (*this)(definition1->underlying_type_id,
520                                      definition2->underlying_type_id);
521       result.MaybeAddEdgeDiff("underlying", type_diff);
522     }
523 
524     const auto enums1 = definition1->enumerators;
525     const auto enums2 = definition2->enumerators;
526     const auto keys1 = MatchingKeys(enums1);
527     const auto keys2 = MatchingKeys(enums2);
528     auto pairs = PairUp(keys1, keys2);
529     Reorder(pairs);
530     for (const auto& [index1, index2] : pairs) {
531       if (index1 && !index2) {
532         // removed
533         const auto& enum1 = enums1[*index1];
534         std::ostringstream os;
535         os << "enumerator '" << enum1.first
536            << "' (" << enum1.second << ") was removed";
537         result.AddNodeDiff(os.str());
538       } else if (!index1 && index2) {
539         // added
540         const auto& enum2 = enums2[*index2];
541         std::ostringstream os;
542         os << "enumerator '" << enum2.first
543            << "' (" << enum2.second << ") was added";
544         result.AddNodeDiff(os.str());
545       } else if (index1 && index2) {
546         // in both
547         const auto& enum1 = enums1[*index1];
548         const auto& enum2 = enums2[*index2];
549         result.MaybeAddNodeDiff(
550             [&](std::ostream& os) {
551               os << "enumerator '" << enum1.first << "' value";
552             },
553             enum1.second, enum2.second);
554       } else {
555         Die() << "Compare(Enumeration): impossible pair";
556       }
557     }
558   }
559 
560   return result;
561 }
562 
operator ()(const Variant & x1,const Variant & x2)563 Result Compare::operator()(const Variant& x1, const Variant& x2) {
564   Result result;
565   // Compare two identically named variants recursively, holding diffs.
566   // Everything else treated as distinct. No recursion.
567   if (x1.name != x2.name) {
568     return result.MarkIncomparable();
569   }
570   result.diff_.holds_changes = true;  // Anonymous variants are not allowed.
571 
572   result.MaybeAddNodeDiff("bytesize", x1.bytesize, x2.bytesize);
573   if (x1.discriminant.has_value() && x2.discriminant.has_value()) {
574     const auto type_diff =
575         (*this)(x1.discriminant.value(), x2.discriminant.value());
576     result.MaybeAddEdgeDiff("discriminant", type_diff);
577   } else if (x1.discriminant.has_value()) {
578     result.AddEdgeDiff("", Removed(x1.discriminant.value()));
579   } else if (x2.discriminant.has_value()) {
580     result.AddEdgeDiff("", Added(x2.discriminant.value()));
581   }
582   CompareNodes(result, *this, x1.members, x2.members);
583   return result;
584 }
585 
operator ()(const Function & x1,const Function & x2)586 Result Compare::operator()(const Function& x1, const Function& x2) {
587   Result result;
588   const auto type_diff = (*this)(x1.return_type_id, x2.return_type_id);
589   result.MaybeAddEdgeDiff("return", type_diff);
590 
591   const auto& parameters1 = x1.parameters;
592   const auto& parameters2 = x2.parameters;
593   size_t min = std::min(parameters1.size(), parameters2.size());
594   for (size_t i = 0; i < min; ++i) {
595     const Id p1 = parameters1.at(i);
596     const Id p2 = parameters2.at(i);
597     result.MaybeAddEdgeDiff(
598         [&](std::ostream& os) {
599           os << "parameter " << i + 1;
600         },
601         (*this)(p1, p2));
602   }
603 
604   bool added = parameters1.size() < parameters2.size();
605   const auto& which = added ? x2 : x1;
606   const auto& parameters = which.parameters;
607   for (size_t i = min; i < parameters.size(); ++i) {
608     const Id parameter = parameters.at(i);
609     std::ostringstream os;
610     os << "parameter " << i + 1 << " of";
611     auto diff = added ? Added(parameter) : Removed(parameter);
612     result.AddEdgeDiff(os.str(), diff);
613   }
614 
615   return result;
616 }
617 
operator ()(const ElfSymbol & x1,const ElfSymbol & x2)618 Result Compare::operator()(const ElfSymbol& x1, const ElfSymbol& x2) {
619   // ELF symbols have a lot of different attributes that can impact ABI
620   // compatibility and others that either cannot or are subsumed by information
621   // elsewhere.
622   //
623   // Not all attributes are exposed by elf_symbol and fewer still in ABI XML.
624   //
625   // name - definitely part of the key
626   //
627   // type - (ELF symbol type, not C type) one important distinction here would
628   // be global vs thread-local variables
629   //
630   // section - not exposed (modulo aliasing information) and don't care
631   //
632   // value (address, usually) - not exposed (modulo aliasing information) and
633   // don't care
634   //
635   // size - don't care (for variables, subsumed by type information)
636   //
637   // binding - global vs weak vs unique vs local
638   //
639   // visibility - default > protected > hidden > internal
640   //
641   // version / is-default-version - in theory the "hidden" bit (separate from
642   // hidden and local above) can be set independently of the version, but in
643   // practice at most one version of given name is non-hidden; version
644   // (including its presence or absence) is definitely part of the key; we
645   // should probably treat is-default-version as a non-key attribute
646   //
647   // defined - rather fundamental; libabigail currently doesn't track undefined
648   // symbols but we should obviously be prepared in case it does
649 
650   // There are also some externalities which libabigail cares about, which may
651   // or may not be exposed in the XML
652   //
653   // index - don't care
654   //
655   // is-common and friends - don't care
656   //
657   // aliases - exposed, but we don't really care; however we should see what
658   // compilers do, if anything, in terms of propagating type information to
659   // aliases
660 
661   // Linux kernel things.
662   //
663   // MODVERSIONS CRC - fundamental to ABI compatibility, if present
664   //
665   // Symbol namespace - fundamental to ABI compatibility, if present
666 
667   Result result;
668   result.MaybeAddNodeDiff("name", x1.symbol_name, x2.symbol_name);
669 
670   if (x1.version_info && x2.version_info) {
671     result.MaybeAddNodeDiff("version", x1.version_info->name,
672                             x2.version_info->name);
673     result.MaybeAddNodeDiff("default version", x1.version_info->is_default,
674                             x2.version_info->is_default);
675   } else {
676     result.MaybeAddNodeDiff("has version", x1.version_info.has_value(),
677                             x2.version_info.has_value());
678   }
679 
680   result.MaybeAddNodeDiff("defined", x1.is_defined, x2.is_defined);
681   result.MaybeAddNodeDiff("symbol type", x1.symbol_type, x2.symbol_type);
682   result.MaybeAddNodeDiff("binding", x1.binding, x2.binding);
683   result.MaybeAddNodeDiff("visibility", x1.visibility, x2.visibility);
684   if (!ignore.Test(Ignore::SYMBOL_CRC)) {
685     result.MaybeAddNodeDiff("CRC", x1.crc, x2.crc);
686   }
687   result.MaybeAddNodeDiff("namespace", x1.ns, x2.ns);
688 
689   if (x1.type_id && x2.type_id) {
690     result.MaybeAddEdgeDiff("", (*this)(*x1.type_id, *x2.type_id));
691   } else if (x1.type_id) {
692     if (!ignore.Test(Ignore::SYMBOL_TYPE_PRESENCE)) {
693       result.AddEdgeDiff("", Removed(*x1.type_id));
694     }
695   } else if (x2.type_id) {
696     if (!ignore.Test(Ignore::SYMBOL_TYPE_PRESENCE)) {
697       result.AddEdgeDiff("", Added(*x2.type_id));
698     }
699   } else {
700     // both types missing, we have nothing to say
701   }
702 
703   return result;
704 }
705 
operator ()(const Interface & x1,const Interface & x2)706 Result Compare::operator()(const Interface& x1, const Interface& x2) {
707   Result result;
708   result.diff_.holds_changes = true;
709   const bool ignore_added = ignore.Test(Ignore::INTERFACE_ADDITION);
710   CompareNodes(result, *this, x1.symbols, x2.symbols, ignore_added);
711   CompareNodes(result, *this, x1.types, x2.types, ignore_added);
712   return result;
713 }
714 
ResolveQualifiers(const Graph & graph,Id id)715 std::pair<Id, Qualifiers> ResolveQualifiers(const Graph& graph, Id id) {
716   std::pair<Id, Qualifiers> result = {id, {}};
717   ResolveQualifier resolve(graph, result.first, result.second);
718   while (graph.Apply<bool>(resolve, result.first)) {
719   }
720   return result;
721 }
722 
operator ()(const Array &)723 bool ResolveQualifier::operator()(const Array&) {
724   // There should be no qualifiers here.
725   qualifiers.clear();
726   return false;
727 }
728 
operator ()(const Function &)729 bool ResolveQualifier::operator()(const Function&) {
730   // There should be no qualifiers here.
731   qualifiers.clear();
732   return false;
733 }
734 
operator ()(const Qualified & x)735 bool ResolveQualifier::operator()(const Qualified& x) {
736   id = x.qualified_type_id;
737   qualifiers.insert(x.qualifier);
738   return true;
739 }
740 
741 template <typename Node>
operator ()(const Node &)742 bool ResolveQualifier::operator()(const Node&) {
743   return false;
744 }
745 
ResolveTypedefs(const Graph & graph,Id id)746 std::pair<Id, std::vector<std::string>> ResolveTypedefs(
747     const Graph& graph, Id id) {
748   std::pair<Id, std::vector<std::string>> result = {id, {}};
749   ResolveTypedef resolve(graph, result.first, result.second);
750   while (graph.Apply<bool>(resolve, result.first)) {
751   }
752   return result;
753 }
754 
operator ()(const Typedef & x)755 bool ResolveTypedef::operator()(const Typedef& x) {
756   id = x.referred_type_id;
757   names.push_back(x.name);
758   return true;
759 }
760 
761 template <typename Node>
operator ()(const Node &)762 bool ResolveTypedef::operator()(const Node&) {
763   return false;
764 }
765 
operator ()(Id id)766 std::string MatchingKey::operator()(Id id) {
767   return graph.Apply<std::string>(*this, id);
768 }
769 
operator ()(const BaseClass & x)770 std::string MatchingKey::operator()(const BaseClass& x) {
771   return (*this)(x.type_id);
772 }
773 
operator ()(const Method & x)774 std::string MatchingKey::operator()(const Method& x) {
775   return x.name + ',' + x.mangled_name;
776 }
777 
operator ()(const Member & x)778 std::string MatchingKey::operator()(const Member& x) {
779   if (!x.name.empty()) {
780     return x.name;
781   }
782   return (*this)(x.type_id);
783 }
784 
operator ()(const VariantMember & x)785 std::string MatchingKey::operator()(const VariantMember& x) {
786   return x.name;
787 }
788 
operator ()(const StructUnion & x)789 std::string MatchingKey::operator()(const StructUnion& x) {
790   if (!x.name.empty()) {
791     return x.name;
792   }
793   if (x.definition) {
794     const auto& members = x.definition->members;
795     for (const auto& member : members) {
796       const auto recursive = (*this)(member);
797       if (!recursive.empty()) {
798         return recursive + '+';
799       }
800     }
801   }
802   return {};
803 }
804 
805 template <typename Node>
operator ()(const Node &)806 std::string MatchingKey::operator()(const Node&) {
807   return {};
808 }
809 
810 }  // namespace stg
811