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