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