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