1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- Mode: C++ -*-
3 //
4 // Copyright (C) 2017-2023 Red Hat, Inc.
5 //
6 // Author: Dodji Seketeli
7
8 #include <libgen.h>
9 #include <algorithm>
10 #include "abg-comparison-priv.h"
11 #include "abg-reporter-priv.h"
12
13 namespace abigail
14 {
15
16 namespace comparison
17 {
18
19 /// Convert a number in bits into a number in bytes.
20 ///
21 /// @param bits the number in bits to convert.
22 ///
23 /// @return the number @p bits converted into bytes.
24 uint64_t
convert_bits_to_bytes(size_t bits)25 convert_bits_to_bytes(size_t bits)
26 {return bits / 8;}
27
28 /// Emit a numerical value to an output stream.
29 ///
30 /// Depending on the current @ref diff_context, the number is going to
31 /// be emitted either in decimal or hexadecimal base.
32 ///
33 /// @param value the value to emit.
34 ///
35 /// @param ctxt the current diff context.
36 ///
37 /// @param out the output stream to emit the numerical value to.
38 void
emit_num_value(uint64_t value,const diff_context & ctxt,ostream & out)39 emit_num_value(uint64_t value, const diff_context& ctxt, ostream& out)
40 {
41 if (ctxt.show_hex_values())
42 out << std::hex << std::showbase ;
43 else
44 out << std::dec;
45 out << value << std::dec << std::noshowbase;
46 }
47
48 /// Convert a bits value into a byte value if the current diff context
49 /// instructs us to do so.
50 ///
51 /// @param bits the bits value to convert.
52 ///
53 /// @param ctxt the current diff context to consider.
54 ///
55 /// @return the resulting bits or bytes value, depending on what the
56 /// diff context instructs us to do.
57 uint64_t
maybe_convert_bits_to_bytes(uint64_t bits,const diff_context & ctxt)58 maybe_convert_bits_to_bytes(uint64_t bits, const diff_context& ctxt)
59 {
60 if (ctxt.show_offsets_sizes_in_bits())
61 return bits;
62 return convert_bits_to_bytes(bits);
63 }
64
65 /// Emit a message showing the numerical change between two values, to
66 /// a given output stream.
67 ///
68 /// The function emits a message like
69 ///
70 /// "XXX changes from old_bits to new_bits (in bits)"
71 ///
72 /// or
73 ///
74 /// "XXX changes from old_bits to new_bits (in bytes)"
75 ///
76 /// Depending on if the current diff context instructs us to emit the
77 /// change in bits or bytes. XXX is the string content of the @p what
78 /// parameter.
79 ///
80 /// @param what the string that tells us what the change represents.
81 /// This is the "XXX" we refer to in the explanation above.
82 ///
83 /// @param old_bits the initial value (which changed) in bits.
84 ///
85 /// @param new_bits the final value (resulting from the change or @p
86 /// old_bits) in bits.
87 ///
88 /// @param ctxt the current diff context to consider.
89 ///
90 /// @param out the output stream to send the change message to.
91 ///
92 /// @param show_bits_or_byte if this is true, then the message is
93 /// going to precise if the changed value is in bits or bytes.
94 /// Otherwise, no mention of that is made.
95 void
show_numerical_change(const string & what,uint64_t old_bits,uint64_t new_bits,const diff_context & ctxt,ostream & out,bool show_bits_or_byte)96 show_numerical_change(const string& what,
97 uint64_t old_bits,
98 uint64_t new_bits,
99 const diff_context& ctxt,
100 ostream& out,
101 bool show_bits_or_byte)
102 {
103 bool can_convert_bits_to_bytes = (old_bits % 8 == 0 && new_bits % 8 == 0);
104 uint64_t o = can_convert_bits_to_bytes
105 ? maybe_convert_bits_to_bytes(old_bits, ctxt)
106 : old_bits;
107 uint64_t n = can_convert_bits_to_bytes
108 ? maybe_convert_bits_to_bytes(new_bits, ctxt)
109 : new_bits;
110 string bits_or_bytes =
111 (!can_convert_bits_to_bytes || ctxt.show_offsets_sizes_in_bits ())
112 ? "bits"
113 : "bytes";
114
115 out << what << " changed from ";
116 emit_num_value(o, ctxt, out);
117 out << " to ";
118 emit_num_value(n, ctxt, out);
119 if (show_bits_or_byte)
120 {
121 out << " (in ";
122 out << bits_or_bytes;
123 out << ")";
124 }
125 }
126
127 /// Emit a message showing the value of a numerical value representing
128 /// a size or an offset, preceded by a string. The message is ended
129 /// by a part which says if the value is in bits or bytes.
130 ///
131 /// @param what the string prefix of the message to emit.
132 ///
133 /// @param value the numerical value to emit.
134 ///
135 /// @param ctxt the diff context to take into account.
136 ///
137 /// @param out the output stream to emit the message to.
138 void
show_offset_or_size(const string & what,uint64_t value,const diff_context & ctxt,ostream & out)139 show_offset_or_size(const string& what,
140 uint64_t value,
141 const diff_context& ctxt,
142 ostream& out)
143 {
144 uint64_t v = value;
145 bool can_convert_bits_to_bytes = (value % 8 == 0);
146 if (can_convert_bits_to_bytes)
147 v = maybe_convert_bits_to_bytes(v, ctxt);
148 string bits_or_bytes =
149 (!can_convert_bits_to_bytes || ctxt.show_offsets_sizes_in_bits())
150 ? "bits"
151 : "bytes";
152
153 if (!what.empty())
154 out << what << " ";
155 emit_num_value(v, ctxt, out);
156 out << " (in " << bits_or_bytes << ")";
157 }
158
159 /// Emit a message showing the value of a numerical value representing
160 /// a size or an offset. The message is ended by a part which says if
161 /// the value is in bits or bytes.
162 ///
163 /// @param value the numerical value to emit.
164 ///
165 /// @param ctxt the diff context to take into account.
166 ///
167 /// @param out the output stream to emit the message to.
168 void
show_offset_or_size(uint64_t value,const diff_context & ctxt,ostream & out)169 show_offset_or_size(uint64_t value,
170 const diff_context& ctxt,
171 ostream& out)
172 {show_offset_or_size("", value, ctxt, out);}
173
174 /// Stream a string representation for a member function.
175 ///
176 /// @param ctxt the current diff context.
177 ///
178 /// @param mem_fn the member function to stream
179 ///
180 /// @param out the output stream to send the representation to
181 void
represent(const diff_context & ctxt,method_decl_sptr mem_fn,ostream & out)182 represent(const diff_context& ctxt,
183 method_decl_sptr mem_fn,
184 ostream& out)
185 {
186 if (!mem_fn || !is_member_function(mem_fn))
187 return;
188
189 method_decl_sptr meth =
190 dynamic_pointer_cast<method_decl>(mem_fn);
191 ABG_ASSERT(meth);
192
193 out << "'" << mem_fn->get_pretty_representation() << "'";
194 report_loc_info(meth, ctxt, out);
195 if (get_member_function_is_virtual(mem_fn))
196 {
197
198 ssize_t voffset = get_member_function_vtable_offset(mem_fn);
199 ssize_t biggest_voffset =
200 is_class_type(meth->get_type()->get_class_type())->
201 get_biggest_vtable_offset();
202 if (voffset > -1)
203 {
204 out << ", virtual at voffset ";
205 emit_num_value(get_member_function_vtable_offset(mem_fn),
206 ctxt, out);
207 out << "/";
208 emit_num_value(biggest_voffset, ctxt, out);
209 }
210 }
211
212 if (ctxt.show_linkage_names()
213 && (mem_fn->get_symbol()))
214 {
215 out << " {"
216 << mem_fn->get_symbol()->get_id_string()
217 << "}";
218 }
219 out << "\n";
220 }
221
222 /// Stream a string representation for a data member.
223 ///
224 /// @param d the data member to stream
225 ///
226 /// @param ctxt the current diff context.
227 ///
228 /// @param out the output stream to send the representation to
229 ///
230 /// @param indent the indentation string to use for the change report.
231 void
represent_data_member(var_decl_sptr d,const diff_context_sptr & ctxt,ostream & out,const string & indent)232 represent_data_member(var_decl_sptr d,
233 const diff_context_sptr& ctxt,
234 ostream& out,
235 const string& indent)
236 {
237 if (!is_data_member(d)
238 || (!get_member_is_static(d) && !get_data_member_is_laid_out(d)))
239 return;
240
241 out << indent
242 << "'"
243 << d->get_pretty_representation(/*internal=*/false,
244 /*qualified_name=*/false)
245 << "'";
246 if (!get_member_is_static(d))
247 {
248 // Do not emit offset information for data member of a union
249 // type because all data members of a union are supposed to be
250 // at offset 0.
251 if (!is_union_type(d->get_scope()))
252 show_offset_or_size(", at offset",
253 get_data_member_offset(d),
254 *ctxt, out);
255 report_loc_info(d, *ctxt, out);
256 }
257 out << "\n";
258 }
259
260 /// If a given @ref var_diff node carries a data member change in
261 /// which the offset of the data member actually changed, then emit a
262 /// string (to an output stream) that represents that offset change.
263 ///
264 /// For instance, if the offset of the data member increased by 32
265 /// bits then the string emitted is going to be "by +32 bits".
266 ///
267 /// If, on the other hand, the offset of the data member decreased by
268 /// 64 bits then the string emitted is going to be "by -64 bits".
269 ///
270 /// This function is a sub-routine used by the reporting system.
271 ///
272 /// @param diff the diff node that potentially carries the data member
273 /// change.
274 ///
275 /// @param ctxt the context in which the diff is being reported.
276 ///
277 /// @param out the output stream to emit the string to.
278 void
maybe_show_relative_offset_change(const var_diff_sptr & diff,diff_context & ctxt,ostream & out)279 maybe_show_relative_offset_change(const var_diff_sptr &diff,
280 diff_context& ctxt,
281 ostream& out)
282 {
283 if (!ctxt.show_relative_offset_changes())
284 return;
285
286 var_decl_sptr o = diff->first_var();
287 var_decl_sptr n = diff->second_var();
288
289 uint64_t first_offset = get_data_member_offset(o),
290 second_offset = get_data_member_offset(n);
291
292 string sign;
293 uint64_t change = 0;
294 if (first_offset < second_offset)
295 {
296 sign = "+";
297 change = second_offset - first_offset;
298 }
299 else if (first_offset > second_offset)
300 {
301 sign = "-";
302 change = first_offset - second_offset;
303 }
304 else
305 return;
306
307 if (!ctxt.show_offsets_sizes_in_bits())
308 change = convert_bits_to_bytes(change);
309
310 string bits_or_bytes = ctxt.show_offsets_sizes_in_bits()
311 ? "bits"
312 : "bytes";
313
314 out << " (by " << sign;
315 emit_num_value(change, ctxt, out);
316 out << " " << bits_or_bytes << ")";
317 }
318
319 /// If a given @ref var_diff node carries a hange in which the size of
320 /// the variable actually changed, then emit a string (to an output
321 /// stream) that represents that size change.
322 ///
323 /// For instance, if the size of the variable increased by 32 bits
324 /// then the string emitted is going to be "by +32 bits".
325 ///
326 /// If, on the other hand, the size of the variable decreased by 64
327 /// bits then the string emitted is going to be "by -64 bits".
328 ///
329 /// This function is a sub-routine used by the reporting system.
330 ///
331 /// @param diff the diff node that potentially carries the variable
332 /// change.
333 ///
334 /// @param ctxt the context in which the diff is being reported.
335 ///
336 /// @param out the output stream to emit the string to.
337 void
maybe_show_relative_size_change(const var_diff_sptr & diff,diff_context & ctxt,ostream & out)338 maybe_show_relative_size_change(const var_diff_sptr &diff,
339 diff_context& ctxt,
340 ostream& out)
341 {
342 if (!ctxt.show_relative_offset_changes())
343 return;
344
345 var_decl_sptr o = diff->first_var();
346 var_decl_sptr n = diff->second_var();
347
348 uint64_t first_size = get_var_size_in_bits(o),
349 second_size = get_var_size_in_bits(n);
350
351 string sign;
352 uint64_t change = 0;
353 if (first_size < second_size)
354 {
355 sign = "+";
356 change = second_size - first_size;
357 }
358 else if (first_size > second_size)
359 {
360 sign = "-";
361 change = first_size - second_size;
362 }
363 else
364 return;
365
366 if (!ctxt.show_offsets_sizes_in_bits())
367 change = convert_bits_to_bytes(change);
368
369 string bits_or_bytes = ctxt.show_offsets_sizes_in_bits()
370 ? "bits"
371 : "bytes";
372
373 out << " (by " << sign;
374 emit_num_value(change, ctxt, out);
375 out << " " << bits_or_bytes << ")";
376 }
377
378 /// Represent the changes carried by an instance of @ref var_diff that
379 /// represent a difference between two class data members.
380 ///
381 /// @param diff diff the diff node to represent.
382 ///
383 /// @param ctxt the diff context to use.
384 ///
385 /// @param local_only if true, only display local changes.
386 ///
387 /// @param out the output stream to send the representation to.
388 ///
389 /// @param indent the indentation string to use for the change report.
390 void
represent(const var_diff_sptr & diff,diff_context_sptr ctxt,ostream & out,const string & indent,bool local_only)391 represent(const var_diff_sptr &diff,
392 diff_context_sptr ctxt,
393 ostream& out,
394 const string& indent,
395 bool local_only)
396 {
397 if (!ctxt->get_reporter()->diff_to_be_reported(diff.get()))
398 return;
399
400 const var_decl_sptr o = diff->first_var();
401 const var_decl_sptr n = diff->second_var();
402 const bool o_anon = !!is_anonymous_data_member(o);
403 const bool n_anon = !!is_anonymous_data_member(n);
404 const bool is_strict_anonymous_data_member_change = o_anon && n_anon;
405 const string o_name = (is_data_member_of_anonymous_class_or_union(o)
406 ? o->get_name()
407 : o->get_qualified_name());
408 const string n_name = (is_data_member_of_anonymous_class_or_union(n)
409 ? n->get_name()
410 : n->get_qualified_name());
411 const uint64_t o_size = get_var_size_in_bits(o);
412 const uint64_t n_size = get_var_size_in_bits(n);
413 const uint64_t o_offset = get_data_member_offset(o);
414 const uint64_t n_offset = get_data_member_offset(n);
415 const string o_pretty_representation =
416 o->get_pretty_representation(/*internal=*/false, /*qualified_name=*/false);
417 // no n_pretty_representation here as it's only needed in a couple of places
418 const bool show_size_offset_changes = ctxt->get_allowed_category()
419 & SIZE_OR_OFFSET_CHANGE_CATEGORY;
420
421 // Has the main diff text been output?
422 bool emitted = false;
423 // Are we continuing on a new line? (implies emitted)
424 bool begin_with_and = false;
425 // Have we reported a size change already?
426 bool size_reported = false;
427
428 //----------------------------------------------------------------
429 // First we'll try to emit a report about the type change of this
430 // var_decl_diff.
431 //
432 // In the context of that type change report, we need to keep in
433 // mind that because we want to emit specific (useful) reports about
434 // anonymous data member changes, we'll try to detect the various
435 // scenarii that involve anonymous data member changes.
436 //
437 // Then, as a fallback method, we'll emit a more generic type change
438 // report for the other generic type changes.
439 //----------------------------------------------------------------
440
441 if (is_strict_anonymous_data_member_change)
442 {
443 const string n_pretty_representation =
444 n->get_pretty_representation(/*internal=*/false,
445 /*qualified_name=*/false);
446 const type_base_sptr o_type = o->get_type(), n_type = n->get_type();
447 if (o_pretty_representation != n_pretty_representation)
448 {
449 show_offset_or_size(indent + "anonymous data member at offset",
450 o_offset, *ctxt, out);
451
452 out << " changed from:\n"
453 << indent << " " << o_pretty_representation << "\n"
454 << indent << "to:\n"
455 << indent << " " << n_pretty_representation << "\n";
456
457 begin_with_and = true;
458 emitted = true;
459 }
460 else if (get_type_name(o_type) != get_type_name(n_type)
461 && is_decl(o_type) && is_decl(n_type)
462 && is_decl(o_type)->get_is_anonymous()
463 && is_decl(n_type)->get_is_anonymous())
464 {
465 out << indent << "while looking at anonymous data member '"
466 << o_pretty_representation << "':\n"
467 << indent << "the internal name of that anonymous data member"
468 " changed from:\n"
469 << indent << " " << get_type_name(o_type) << "\n"
470 << indent << "to:\n"
471 << indent << " " << get_type_name(n_type) << "\n"
472 << indent << " This is usually due to "
473 << "an anonymous member type being added or removed from "
474 << "the containing type\n";
475
476 begin_with_and = true;
477 emitted = true;
478 }
479 }
480 else if (filtering::has_anonymous_data_member_change(diff))
481 {
482 ABG_ASSERT(o_anon != n_anon);
483 // So we are looking at a non-anonymous data member change from
484 // or to an anonymous data member.
485 const string n_pretty_representation =
486 n->get_pretty_representation(/*internal=*/false,
487 /*qualified_name=*/false);
488 out << indent << (o_anon ? "anonymous " : "")
489 << "data member " << o_pretty_representation;
490 show_offset_or_size(" at offset", o_offset, *ctxt, out);
491 out << " became " << (n_anon ? "anonymous " : "")
492 << "data member '" << n_pretty_representation << "'\n";
493
494 begin_with_and = true;
495 emitted = true;
496 }
497
498 //
499 // If we haven't succeeded in emitting a specific type change report
500 // (mainly related to anonymous data data member changes) then let's
501 // try to emit a more generic report about the type change.
502 //
503 // This is the fallback method outlined in the comment at the
504 // beginning of this section.
505 //
506 if (!emitted)
507 if (const diff_sptr d = diff->type_diff())
508 {
509 if (ctxt->get_reporter()->diff_to_be_reported(d.get()))
510 {
511 if (local_only)
512 out << indent << "type '"
513 << get_pretty_representation(o->get_type())
514 << "' of '"
515 << (o_anon ?
516 string("anonymous data member")
517 : o->get_qualified_name())
518 << "' changed";
519 else
520 out << indent
521 << "type of '"<< (o_anon ? "anonymous data member ": "")
522 << o_pretty_representation << "' changed";
523
524 if (d->currently_reporting())
525 out << ", as being reported\n";
526 else if (d->reported_once())
527 out << ", as reported earlier\n";
528 else
529 {
530 out << ":\n";
531 d->report(out, indent + " ");
532 }
533
534 begin_with_and = true;
535 emitted = true;
536 size_reported = true;
537 }
538 }
539
540 //
541 // Okay, now we are done with report type changes. Let's report the
542 // other potential kinds of changes.
543 //
544
545 if (!filtering::has_anonymous_data_member_change(diff) && o_name != n_name)
546 {
547 if (filtering::has_harmless_name_change(o, n)
548 && !(ctxt->get_allowed_category()
549 & HARMLESS_DECL_NAME_CHANGE_CATEGORY))
550 ;
551 else
552 {
553 if (begin_with_and)
554 {
555 out << indent << "and ";
556 begin_with_and = false;
557 }
558 else if (!emitted)
559 out << indent;
560 else
561 out << ", ";
562 out << "name of '" << o_name << "' changed to '" << n_name << "'";
563 report_loc_info(n, *ctxt, out);
564 emitted = true;
565 }
566 }
567
568 if (get_data_member_is_laid_out(o)
569 != get_data_member_is_laid_out(n))
570 {
571 if (begin_with_and)
572 {
573 out << indent << "and ";
574 begin_with_and = false;
575 }
576 else if (!emitted)
577 out << indent << "'" << o_pretty_representation << "' ";
578 else
579 out << ", ";
580 if (get_data_member_is_laid_out(o))
581 out << "is no more laid out";
582 else
583 out << "now becomes laid out";
584 emitted = true;
585 }
586 if (show_size_offset_changes)
587 {
588 if (o_offset != n_offset)
589 {
590 if (begin_with_and)
591 {
592 out << indent << "and ";
593 begin_with_and = false;
594 }
595 else if (!emitted)
596 {
597 out << indent;
598 if (is_strict_anonymous_data_member_change)
599 out << "anonymous data member ";
600 out << "'" << o_pretty_representation << "' ";
601 }
602 else
603 out << ", ";
604
605 show_numerical_change("offset", o_offset, n_offset, *ctxt, out);
606 maybe_show_relative_offset_change(diff, *ctxt, out);
607 emitted = true;
608 }
609
610 if (!size_reported && o_size != n_size)
611 {
612 if (begin_with_and)
613 {
614 out << indent << "and ";
615 begin_with_and = false;
616 }
617 else if (!emitted)
618 {
619 out << indent;
620 if (is_strict_anonymous_data_member_change)
621 out << "anonymous data member ";
622 out << "'" << o_pretty_representation << "' ";
623 }
624 else
625 out << ", ";
626
627 show_numerical_change("size", o_size, n_size, *ctxt, out);
628 maybe_show_relative_size_change(diff, *ctxt, out);
629 emitted = true;
630 }
631 }
632 if (o->get_binding() != n->get_binding())
633 {
634 if (begin_with_and)
635 {
636 out << indent << "and ";
637 begin_with_and = false;
638 }
639 else if (!emitted)
640 out << indent << "'" << o_pretty_representation << "' ";
641 else
642 out << ", ";
643 out << "elf binding changed from " << o->get_binding()
644 << " to " << n->get_binding();
645 emitted = true;
646 }
647 if (o->get_visibility() != n->get_visibility())
648 {
649 if (begin_with_and)
650 {
651 out << indent << "and ";
652 begin_with_and = false;
653 }
654 else if (!emitted)
655 out << indent << "'" << o_pretty_representation << "' ";
656 else
657 out << ", ";
658 out << "visibility changed from " << o->get_visibility()
659 << " to " << n->get_visibility();
660 emitted = true;
661 }
662 if ((ctxt->get_allowed_category() & ACCESS_CHANGE_CATEGORY)
663 && (get_member_access_specifier(o)
664 != get_member_access_specifier(n)))
665 {
666 if (begin_with_and)
667 {
668 out << indent << "and ";
669 begin_with_and = false;
670 }
671 else if (!emitted)
672 out << indent << "'" << o_pretty_representation << "' ";
673 else
674 out << ", ";
675
676 out << "access changed from '"
677 << get_member_access_specifier(o)
678 << "' to '"
679 << get_member_access_specifier(n) << "'";
680 emitted = true;
681 }
682 if (get_member_is_static(o)
683 != get_member_is_static(n))
684 {
685 if (begin_with_and)
686 {
687 out << indent << "and ";
688 begin_with_and = false;
689 }
690 else if (!emitted)
691 out << indent << "'" << o_pretty_representation << "' ";
692 else
693 out << ", ";
694
695 if (get_member_is_static(o))
696 out << "is no more static";
697 else
698 out << "now becomes static";
699 emitted = true;
700 }
701
702 if (begin_with_and)
703 // do nothing as begin_with_and implies emitted
704 ;
705 else if (!emitted)
706 // We appear to have fallen off the edge of the map.
707 out << indent << "'" << o_pretty_representation
708 << "' has *some* difference - please report as a bug";
709 else
710 {
711 ;// do nothing
712 }
713 emitted = true;
714
715 if (!begin_with_and)
716 out << "\n";
717 }
718
719 /// Represent the changes carried by an instance of @ref subrange_diff
720 /// that represent a difference between two ranges.
721 ///
722 /// @param diff diff the diff node to represent.
723 ///
724 /// @param ctxt the diff context to use.
725 ///
726 /// @param local_only if true, only display local changes.
727 ///
728 /// @param out the output stream to send the representation to.
729 ///
730 /// @param indent the indentation string to use for the change report.
731 void
represent(const subrange_diff & d,const diff_context_sptr ctxt,ostream & out,const string & indent,bool local_only)732 represent(const subrange_diff& d,
733 const diff_context_sptr ctxt,
734 ostream& out,
735 const string& indent,
736 bool local_only)
737 {
738 array_type_def::subrange_sptr o = d.first_subrange();
739 array_type_def::subrange_sptr n = d.second_subrange();
740 string oor = o->get_pretty_representation();
741 string nr = n->get_pretty_representation();
742 string on = o->get_name();
743 string nn = n->get_name();
744 int64_t olb = o->get_lower_bound();
745 int64_t nlb = n->get_lower_bound();
746 int64_t oub = o->get_upper_bound();
747 int64_t nub = n->get_upper_bound();
748
749 if (on != nn)
750 {
751 out << indent << "name of range changed from '"
752 << on << "' to '" << nn << "'\n";
753 }
754
755 if (olb != nlb)
756 {
757 out << indent << "lower bound of range '"
758 << on
759 << "' change from '";
760 emit_num_value(olb, *ctxt, out);
761 out << "' to '";
762 emit_num_value(nlb, *ctxt, out);
763 out << "'\n";
764 }
765
766 if (oub != nub)
767 {
768 out << indent << "upper bound of range '"
769 << on
770 << "' change from '";
771 emit_num_value(oub, *ctxt, out);
772 out << "' to '";
773 emit_num_value(nub, *ctxt, out);
774 out << "'\n";
775 }
776
777 if (!local_only)
778 {
779 diff_sptr dif = d.underlying_type_diff();
780 if (dif && dif->to_be_reported())
781 {
782 // report range underlying type changes
783 out << indent << "underlying type of range '"
784 << oor << "' changed:\n";
785 dif->report(out, indent + " ");
786 }
787 }
788 }
789
790 /// Report the size and alignment changes of a type.
791 ///
792 /// @param first the first type to consider.
793 ///
794 /// @param second the second type to consider.
795 ///
796 /// @param ctxt the content of the current diff.
797 ///
798 /// @param out the output stream to report the change to.
799 ///
800 /// @param indent the string to use for indentation.
801 void
report_size_and_alignment_changes(type_or_decl_base_sptr first,type_or_decl_base_sptr second,diff_context_sptr ctxt,ostream & out,const string & indent)802 report_size_and_alignment_changes(type_or_decl_base_sptr first,
803 type_or_decl_base_sptr second,
804 diff_context_sptr ctxt,
805 ostream& out,
806 const string& indent)
807 {
808 type_base_sptr f = dynamic_pointer_cast<type_base>(first),
809 s = dynamic_pointer_cast<type_base>(second);
810
811 if (!s || !f)
812 return;
813
814 class_or_union_sptr first_class = is_class_or_union_type(first),
815 second_class = is_class_or_union_type(second);
816
817 if (filtering::has_class_decl_only_def_change(first_class, second_class))
818 // So these two classes differ only by the fact that one is the
819 // declaration-only form of the second. The declaration-only class
820 // is of unknown size (recorded as 0) and it is not meaningful to
821 // report a size change.
822 return;
823
824 unsigned fs = f->get_size_in_bits(), ss = s->get_size_in_bits(),
825 fa = f->get_alignment_in_bits(), sa = s->get_alignment_in_bits();
826 array_type_def_sptr first_array = is_array_type(is_type(first)),
827 second_array = is_array_type(is_type(second));
828 unsigned fdc = first_array ? first_array->get_dimension_count(): 0,
829 sdc = second_array ? second_array->get_dimension_count(): 0;
830
831 if (ctxt->get_allowed_category() & SIZE_OR_OFFSET_CHANGE_CATEGORY)
832 {
833 if (fs != ss || fdc != sdc)
834 {
835 if (first_array && second_array)
836 {
837 // We are looking at size or alignment changes between two
838 // arrays ...
839 out << indent << "array type size changed from ";
840 if (first_array->is_infinite())
841 out << "\'unknown\'";
842 else
843 emit_num_value(first_array->get_size_in_bits(), *ctxt, out);
844 out << " to ";
845 if (second_array->is_infinite())
846 out << "\'unknown\'";
847 else
848 emit_num_value(second_array->get_size_in_bits(), *ctxt, out);
849 out << "\n";
850
851 if (sdc != fdc)
852 {
853 out << indent + " "
854 << "number of dimensions changed from "
855 << fdc
856 << " to "
857 << sdc
858 << "\n";
859 }
860 array_type_def::subranges_type::const_iterator i, j;
861 for (i = first_array->get_subranges().begin(),
862 j = second_array->get_subranges().begin();
863 (i != first_array->get_subranges().end()
864 && j != second_array->get_subranges().end());
865 ++i, ++j)
866 {
867 if ((*i)->get_length() != (*j)->get_length())
868 {
869 out << indent
870 << "array type subrange "
871 << i - first_array->get_subranges().begin() + 1
872 << " changed length from ";
873
874 if ((*i)->is_infinite())
875 out << "\'unknown\'";
876 else
877 out << (*i)->get_length();
878
879 out << " to ";
880
881 if ((*j)->is_infinite())
882 out << "\'unknown\'";
883 else
884 out << (*j)->get_length();
885 out << "\n";
886 }
887 }
888 } // end if (first_array && second_array)
889 else if (fs != ss)
890 {
891 out << indent;
892 show_numerical_change("type size", fs, ss, *ctxt, out);
893 out << "\n";
894 }
895 } // end if (fs != ss || fdc != sdc)
896 else
897 if (ctxt->show_relative_offset_changes())
898 {
899 out << indent << "type size hasn't changed\n";
900 }
901 }
902 if ((ctxt->get_allowed_category() & SIZE_OR_OFFSET_CHANGE_CATEGORY)
903 && (fa != sa))
904 {
905 out << indent;
906 show_numerical_change("type alignment", fa, sa, *ctxt, out,
907 /*show_bits_or_bytes=*/false);
908 out << "\n";
909 }
910 }
911
912 /// @param tod the type or declaration to emit loc info about
913 ///
914 /// @param ctxt the content of the current diff.
915 ///
916 /// @param out the output stream to report the change to.
917 ///
918 /// @return true iff something was reported.
919 bool
report_loc_info(const type_or_decl_base_sptr & tod,const diff_context & ctxt,ostream & out)920 report_loc_info(const type_or_decl_base_sptr& tod,
921 const diff_context& ctxt,
922 ostream &out)
923 {
924 if (!ctxt.show_locs())
925 return false;
926
927 decl_base_sptr decl = is_decl(tod);
928
929 if (!decl)
930 return false;
931
932 location loc;
933 translation_unit* tu = get_translation_unit(decl);
934
935 if (tu && (loc = decl->get_location()))
936 {
937 string path;
938 unsigned line, column;
939
940 loc.expand(path, line, column);
941 //tu->get_loc_mgr().expand_location(loc, path, line, column);
942 path = basename(const_cast<char*>(path.c_str()));
943
944 out << " at " << path << ":" << line << ":" << column;
945
946 return true;
947 }
948 return false;
949 }
950
951 /// Report the name, size and alignment changes of a type.
952 ///
953 /// @param first the first type to consider.
954 ///
955 /// @param second the second type to consider.
956 ///
957 /// @param ctxt the content of the current diff.
958 ///
959 /// @param out the output stream to report the change to.
960 ///
961 /// @param indent the string to use for indentation.
962 void
report_name_size_and_alignment_changes(decl_base_sptr first,decl_base_sptr second,diff_context_sptr ctxt,ostream & out,const string & indent)963 report_name_size_and_alignment_changes(decl_base_sptr first,
964 decl_base_sptr second,
965 diff_context_sptr ctxt,
966 ostream& out,
967 const string& indent)
968 {
969 string fn = first->get_qualified_name(),
970 sn = second->get_qualified_name();
971
972 if (!(first->get_is_anonymous() && second->get_is_anonymous())
973 && fn != sn)
974 {
975 if (!(ctxt->get_allowed_category() & HARMLESS_DECL_NAME_CHANGE_CATEGORY)
976 && filtering::has_harmless_name_change(first, second))
977 // This is a harmless name change. but then
978 // HARMLESS_DECL_NAME_CHANGE_CATEGORY doesn't seem allowed.
979 ;
980 else
981 {
982 out << indent;
983 if (is_type(first))
984 out << "type";
985 else
986 out << "declaration";
987 out << " name changed from '" << fn << "' to '" << sn << "'";
988 out << "\n";
989 }
990 }
991
992 report_size_and_alignment_changes(first, second, ctxt, out, indent);
993 }
994
995 /// Output the header preceding the the report for
996 /// insertion/deletion/change of a part of a class. This is a
997 /// subroutine of class_diff::report.
998 ///
999 /// @param out the output stream to output the report to.
1000 ///
1001 /// @param number the number of insertion/deletion to refer to in the
1002 /// header.
1003 ///
1004 /// @param num_filtered the number of filtered changes.
1005 ///
1006 /// @param k the kind of diff (insertion/deletion/change) we want the
1007 /// head to introduce.
1008 ///
1009 /// @param section_name the name of the sub-part of the class to
1010 /// report about.
1011 ///
1012 /// @param indent the string to use as indentation prefix in the
1013 /// header.
1014 void
report_mem_header(ostream & out,size_t number,size_t num_filtered,diff_kind k,const string & section_name,const string & indent)1015 report_mem_header(ostream& out,
1016 size_t number,
1017 size_t num_filtered,
1018 diff_kind k,
1019 const string& section_name,
1020 const string& indent)
1021 {
1022 size_t net_number = number - num_filtered;
1023 string change;
1024 char colon_or_semi_colon = ':';
1025
1026 switch (k)
1027 {
1028 case del_kind:
1029 change = (number > 1) ? "deletions" : "deletion";
1030 break;
1031 case ins_kind:
1032 change = (number > 1) ? "insertions" : "insertion";
1033 break;
1034 case subtype_change_kind:
1035 case change_kind:
1036 change = (number > 1) ? "changes" : "change";
1037 break;
1038 }
1039
1040 if (net_number == 0)
1041 {
1042 out << indent << "no " << section_name << " " << change;
1043 colon_or_semi_colon = ';';
1044 }
1045 else if (net_number == 1)
1046 out << indent << "1 " << section_name << " " << change;
1047 else
1048 out << indent << net_number << " " << section_name
1049 << " " << change;
1050
1051 if (num_filtered)
1052 out << " (" << num_filtered << " filtered)";
1053 out << colon_or_semi_colon << "\n";
1054 }
1055
1056 /// Output the header preceding the the report for
1057 /// insertion/deletion/change of a part of a class. This is a
1058 /// subroutine of class_diff::report.
1059 ///
1060 /// @param out the output stream to output the report to.
1061 ///
1062 /// @param k the kind of diff (insertion/deletion/change) we want the
1063 /// head to introduce.
1064 ///
1065 /// @param section_name the name of the sub-part of the class to
1066 /// report about.
1067 ///
1068 /// @param indent the string to use as indentation prefix in the
1069 /// header.
1070 void
report_mem_header(ostream & out,diff_kind k,const string & section_name,const string & indent)1071 report_mem_header(ostream& out,
1072 diff_kind k,
1073 const string& section_name,
1074 const string& indent)
1075 {
1076 string change;
1077
1078 switch (k)
1079 {
1080 case del_kind:
1081 change = "deletions";
1082 break;
1083 case ins_kind:
1084 change = "insertions";
1085 break;
1086 case subtype_change_kind:
1087 case change_kind:
1088 change = "changes";
1089 break;
1090 }
1091
1092 out << indent << "there are " << section_name << " " << change << ":\n";
1093 }
1094
1095 /// Report the differences in access specifiers and static-ness for
1096 /// class members.
1097 ///
1098 /// @param decl1 the first class member to consider.
1099 ///
1100 /// @param decl2 the second class member to consider.
1101 ///
1102 /// @param out the output stream to send the report to.
1103 ///
1104 /// @param indent the indentation string to use for the report.
1105 ///
1106 /// @return true if something was reported, false otherwise.
1107 bool
maybe_report_diff_for_member(const decl_base_sptr & decl1,const decl_base_sptr & decl2,const diff_context_sptr & ctxt,ostream & out,const string & indent)1108 maybe_report_diff_for_member(const decl_base_sptr& decl1,
1109 const decl_base_sptr& decl2,
1110 const diff_context_sptr& ctxt,
1111 ostream& out,
1112 const string& indent)
1113
1114 {
1115 bool reported = false;
1116 if (!is_member_decl(decl1) || !is_member_decl(decl2))
1117 return reported;
1118
1119 string decl1_repr = decl1->get_pretty_representation(),
1120 decl2_repr = decl2->get_pretty_representation();
1121
1122 if (get_member_is_static(decl1) != get_member_is_static(decl2))
1123 {
1124 bool lost = get_member_is_static(decl1);
1125 out << indent << "'" << decl1_repr << "' ";
1126 if (report_loc_info(decl2, *ctxt, out))
1127 out << " ";
1128 if (lost)
1129 out << "became non-static";
1130 else
1131 out << "became static";
1132 out << "\n";
1133 reported = true;
1134 }
1135 if ((ctxt->get_allowed_category() & ACCESS_CHANGE_CATEGORY)
1136 && (get_member_access_specifier(decl1)
1137 != get_member_access_specifier(decl2)))
1138 {
1139 out << indent << "'" << decl1_repr << "' access changed from '"
1140 << get_member_access_specifier(decl1)
1141 << "' to '"
1142 << get_member_access_specifier(decl2)
1143 << "'\n";
1144 reported = true;
1145 }
1146 return reported;
1147 }
1148
1149 /// Report the differences between two generic variables.
1150 ///
1151 /// @param decl1 the first version of the variable.
1152 ///
1153 /// @param decl2 the second version of the variable.
1154 ///
1155 /// @param ctxt the context of the diff.
1156 ///
1157 /// @param out the output stream to emit the change report to.
1158 ///
1159 /// @param indent the indentation prefix to emit.
1160 ///
1161 /// @return true if any text has been emitted to the output stream.
1162 bool
maybe_report_diff_for_variable(const decl_base_sptr & decl1,const decl_base_sptr & decl2,const diff_context_sptr & ctxt,ostream & out,const string & indent)1163 maybe_report_diff_for_variable(const decl_base_sptr& decl1,
1164 const decl_base_sptr& decl2,
1165 const diff_context_sptr& ctxt,
1166 ostream& out,
1167 const string& indent)
1168 {
1169 bool reported = false;
1170
1171 var_decl_sptr var1 = is_var_decl(decl1);
1172 var_decl_sptr var2 = is_var_decl(decl2);
1173
1174 if (!var1 || !var2)
1175 return reported;
1176
1177 if (filtering::is_var_1_dim_unknown_size_array_change(var1, var2))
1178 {
1179 uint64_t var_size_in_bits = var1->get_symbol()->get_size() * 8;
1180
1181 out << indent;
1182 show_offset_or_size("size of variable symbol (",
1183 var_size_in_bits, *ctxt, out);
1184 out << ") hasn't changed\n"
1185 << indent << "but it does have a harmless type change\n";
1186 reported = true;
1187 }
1188
1189 return reported;
1190 }
1191
1192 /// Report the difference between two ELF symbols, if there is any.
1193 ///
1194 /// @param symbol1 the first symbol to consider.
1195 ///
1196 /// @param symbol2 the second symbol to consider.
1197 ///
1198 /// @param ctxt the diff context.
1199 ///
1200 /// @param the output stream to emit the report to.
1201 ///
1202 /// @param indent the indentation string to use.
1203 void
maybe_report_diff_for_symbol(const elf_symbol_sptr & symbol1,const elf_symbol_sptr & symbol2,const diff_context_sptr & ctxt,ostream & out,const string & indent)1204 maybe_report_diff_for_symbol(const elf_symbol_sptr& symbol1,
1205 const elf_symbol_sptr& symbol2,
1206 const diff_context_sptr& ctxt,
1207 ostream& out,
1208 const string& indent)
1209 {
1210 if (!symbol1 || !symbol2 || symbol1 == symbol2)
1211 return;
1212
1213 if (symbol1->get_size() != symbol2->get_size())
1214 {
1215 out << indent;
1216 show_numerical_change("size of symbol",
1217 symbol1->get_size(),
1218 symbol2->get_size(),
1219 *ctxt, out,
1220 /*show_bits_or_bytes=*/false);
1221 out << "\n";
1222 }
1223
1224 if (symbol1->get_name() != symbol2->get_name())
1225 {
1226 out << indent << "symbol name changed from "
1227 << symbol1->get_name()
1228 << " to "
1229 << symbol2->get_name()
1230 << "\n";
1231 }
1232
1233 if (symbol1->get_type() != symbol2->get_type())
1234 {
1235 out << indent << "symbol type changed from '"
1236 << symbol1->get_type()
1237 << "' to '"
1238 << symbol2->get_type()
1239 << "'\n";
1240 }
1241
1242 if (symbol1->is_public() != symbol2->is_public())
1243 {
1244 out << indent << "symbol became ";
1245 if (symbol2->is_public())
1246 out << "exported";
1247 else
1248 out << "non-exported";
1249 out << "\n";
1250 }
1251
1252 if (symbol1->is_defined() != symbol2->is_defined())
1253 {
1254 out << indent << "symbol became ";
1255 if (symbol2->is_defined())
1256 out << "defined";
1257 else
1258 out << "undefined";
1259 out << "\n";
1260 }
1261
1262 if (symbol1->get_version() != symbol2->get_version())
1263 {
1264 out << indent << "symbol version changed from "
1265 << symbol1->get_version().str()
1266 << " to "
1267 << symbol2->get_version().str()
1268 << "\n";
1269 }
1270
1271 const abg_compat::optional<uint32_t>& crc1 = symbol1->get_crc();
1272 const abg_compat::optional<uint32_t>& crc2 = symbol2->get_crc();
1273 if (crc1 != crc2)
1274 {
1275 const std::string none = "(none)";
1276 out << indent << "CRC (modversions) changed from "
1277 << std::showbase << std::hex;
1278 if (crc1.has_value())
1279 out << crc1.value();
1280 else
1281 out << none;
1282 out << " to ";
1283 if (crc2.has_value())
1284 out << crc2.value();
1285 else
1286 out << none;
1287 out << std::noshowbase << std::dec
1288 << "\n";
1289 }
1290
1291 const abg_compat::optional<std::string>& ns1 = symbol1->get_namespace();
1292 const abg_compat::optional<std::string>& ns2 = symbol2->get_namespace();
1293 if (ns1 != ns2)
1294 {
1295 const std::string none = "(none)";
1296 out << indent << "namespace changed from ";
1297 if (ns1.has_value())
1298 out << "'" << ns1.value() << "'";
1299 else
1300 out << none;
1301 out << " to ";
1302 if (ns2.has_value())
1303 out << "'" << ns2.value() << "'";
1304 else
1305 out << none;
1306 out << "\n";
1307 }
1308 }
1309
1310 /// For a given symbol, emit a string made of its name and version.
1311 /// The string also contains the list of symbols that alias this one.
1312 ///
1313 /// @param out the output string to emit the resulting string to.
1314 ///
1315 /// @param indent the indentation string to use before emitting the
1316 /// resulting string.
1317 ///
1318 /// @param symbol the symbol to emit the representation string for.
1319 ///
1320 /// @param sym_map the symbol map to consider to look for aliases of
1321 /// @p symbol.
1322 void
show_linkage_name_and_aliases(ostream & out,const string & indent,const elf_symbol & symbol,const string_elf_symbols_map_type & sym_map)1323 show_linkage_name_and_aliases(ostream& out,
1324 const string& indent,
1325 const elf_symbol& symbol,
1326 const string_elf_symbols_map_type& sym_map)
1327 {
1328 out << indent << symbol.get_id_string();
1329 string aliases =
1330 symbol.get_aliases_id_string(sym_map,
1331 /*include_symbol_itself=*/false);
1332 if (!aliases.empty())
1333 out << ", aliases " << aliases;
1334 }
1335
1336 /// Report changes about types that are not reachable from global
1337 /// functions and variables, in a given @param corpus_diff.
1338 ///
1339 /// @param d the corpus_diff to consider.
1340 ///
1341 /// @param s the statistics of the changes, after filters and
1342 /// suppressions are reported. This is typically what is returned by
1343 /// corpus_diff::apply_filters_and_suppressions_before_reporting().
1344 ///
1345 /// @param indent the indendation string (usually a string of white
1346 /// spaces) to use for indentation during the reporting.
1347 ///
1348 /// @param out the output stream to emit the report to.
1349 void
maybe_report_unreachable_type_changes(const corpus_diff & d,const corpus_diff::diff_stats & s,const string & indent,ostream & out)1350 maybe_report_unreachable_type_changes(const corpus_diff& d,
1351 const corpus_diff::diff_stats &s,
1352 const string& indent,
1353 ostream& out)
1354 {
1355 const diff_context_sptr& ctxt = d.context();
1356
1357 if (!(ctxt->show_unreachable_types()
1358 && (!d.priv_->deleted_unreachable_types_.empty()
1359 || !d.priv_->added_unreachable_types_.empty()
1360 || !d.priv_->changed_unreachable_types_.empty())))
1361 // The user either doesn't want us to show changes about
1362 // unreachable types or there are not such changes.
1363 return;
1364
1365 // Handle removed unreachable types.
1366 if (s.net_num_removed_unreachable_types() == 1)
1367 out << indent
1368 << "1 removed type unreachable from any public interface:\n\n";
1369 else if (s.net_num_removed_unreachable_types() > 1)
1370 out << indent
1371 << s.net_num_removed_unreachable_types()
1372 << " removed types unreachable from any public interface:\n\n";
1373
1374 vector<type_base_sptr> sorted_removed_unreachable_types;
1375 sort_string_type_base_sptr_map(d.priv_->deleted_unreachable_types_,
1376 sorted_removed_unreachable_types);
1377 bool emitted = false;
1378 for (vector<type_base_sptr>::const_iterator i =
1379 sorted_removed_unreachable_types.begin();
1380 i != sorted_removed_unreachable_types.end();
1381 ++i)
1382 {
1383 if (d.priv_->deleted_unreachable_type_is_suppressed((*i).get()))
1384 continue;
1385
1386 out << indent << " [D] '" << get_pretty_representation(*i) << "'";
1387 report_loc_info(*i, *ctxt, out);
1388 out << "\n";
1389 emitted = true;
1390 }
1391 if (emitted)
1392 out << "\n";
1393
1394 // Handle changed unreachable types!
1395 if (s.net_num_changed_unreachable_types() == 1)
1396 out << indent
1397 << "1 changed type unreachable from any public interface:\n\n";
1398 else if (s.net_num_changed_unreachable_types() > 1)
1399 out << indent
1400 << s.net_num_changed_unreachable_types()
1401 << " changed types unreachable from any public interface:\n\n";
1402
1403 diff_sptrs_type sorted_diff_sptrs;
1404 sort_string_diff_sptr_map(d.priv_->changed_unreachable_types_,
1405 sorted_diff_sptrs);
1406 for (diff_sptrs_type::const_iterator i = sorted_diff_sptrs.begin();
1407 i != sorted_diff_sptrs.end();
1408 ++i)
1409 {
1410 diff_sptr diff = *i;
1411 if (!diff || !diff->to_be_reported())
1412 continue;
1413
1414 string repr = diff->first_subject()->get_pretty_representation();
1415
1416 out << indent << " [C] '" << repr << "' changed:\n";
1417 diff->report(out, indent + " ");
1418 // Extra spacing.
1419 out << "\n";
1420 }
1421 // Changed types have extra spacing already. No new line here.
1422
1423 // Handle added unreachable types.
1424 if (s.net_num_added_unreachable_types() == 1)
1425 out << indent
1426 << "1 added type unreachable from any public interface:\n\n";
1427 else if (s.net_num_added_unreachable_types() > 1)
1428 out << indent
1429 << s.net_num_added_unreachable_types()
1430 << " added types unreachable from any public interface:\n\n";
1431
1432 vector<type_base_sptr> sorted_added_unreachable_types;
1433 sort_string_type_base_sptr_map(d.priv_->added_unreachable_types_,
1434 sorted_added_unreachable_types);
1435 emitted = false;
1436 for (vector<type_base_sptr>::const_iterator i =
1437 sorted_added_unreachable_types.begin();
1438 i != sorted_added_unreachable_types.end();
1439 ++i)
1440 {
1441 if (d.priv_->added_unreachable_type_is_suppressed((*i).get()))
1442 continue;
1443
1444 out << indent << " [A] '" << get_pretty_representation(*i) << "'";
1445 report_loc_info(*i, *ctxt, out);
1446 out << "\n";
1447 emitted = true;
1448 }
1449 if (emitted)
1450 out << "\n";
1451 }
1452
1453 /// If a given diff node impacts some public interfaces, then report
1454 /// about those impacted interfaces on a given output stream.
1455 ///
1456 /// @param d the diff node to get the impacted interfaces for.
1457 ///
1458 /// @param out the output stream to report to.
1459 ///
1460 /// @param indent the white space string to use for indentation.
1461 void
maybe_report_interfaces_impacted_by_diff(const diff * d,ostream & out,const string & indent)1462 maybe_report_interfaces_impacted_by_diff(const diff *d,
1463 ostream &out,
1464 const string &indent)
1465 {
1466 const diff_context_sptr &ctxt = d->context();
1467 const corpus_diff_sptr &corp_diff = ctxt->get_corpus_diff();
1468 if (!corp_diff)
1469 return;
1470
1471 if (!ctxt->show_impacted_interfaces())
1472 return;
1473
1474 const diff_maps &maps = corp_diff->get_leaf_diffs();
1475 artifact_sptr_set_type* impacted_artifacts =
1476 maps.lookup_impacted_interfaces(d);
1477 if (impacted_artifacts == 0)
1478 return;
1479
1480 if (impacted_artifacts->empty())
1481 return;
1482
1483 vector<type_or_decl_base_sptr> sorted_impacted_interfaces;
1484 sort_artifacts_set(*impacted_artifacts, sorted_impacted_interfaces);
1485
1486 size_t num_impacted_interfaces = impacted_artifacts->size();
1487 if (num_impacted_interfaces == 1)
1488 out << indent << "one impacted interface:\n";
1489 else
1490 out << indent << num_impacted_interfaces << " impacted interfaces:\n";
1491
1492 string cur_indent = indent + " ";
1493 vector<type_or_decl_base_sptr>::const_iterator it;
1494 for (it = sorted_impacted_interfaces.begin();
1495 it != sorted_impacted_interfaces.end();
1496 ++it)
1497 {
1498 out << cur_indent << get_pretty_representation(*it) << "\n";
1499 }
1500 }
1501
1502 /// If a given diff node impacts some public interfaces, then report
1503 /// about those impacted interfaces on standard output.
1504 ///
1505 /// @param d the diff node to get the impacted interfaces for.
1506 ///
1507 /// @param out the output stream to report to.
1508 ///
1509 /// @param indent the white space string to use for indentation.
1510 void
maybe_report_interfaces_impacted_by_diff(const diff_sptr & d,ostream & out,const string & indent)1511 maybe_report_interfaces_impacted_by_diff(const diff_sptr &d,
1512 ostream &out,
1513 const string &indent)
1514 {return maybe_report_interfaces_impacted_by_diff(d.get(), out, indent);}
1515
1516 /// Tests if the diff node is to be reported.
1517 ///
1518 /// @param p the diff to consider.
1519 ///
1520 /// @return true iff the diff is to be reported.
1521 bool
diff_to_be_reported(const diff * d) const1522 reporter_base::diff_to_be_reported(const diff *d) const
1523 {return d && d->to_be_reported();}
1524
1525 /// Report about data members replaced by an anonymous data member
1526 /// without changing the overall bit-layout of the class or union in
1527 /// an ABI-meaningful way.
1528 ///
1529 /// @param d the diff to consider.
1530 ///
1531 /// @param out the output stream to emit the change report to.
1532 ///
1533 /// @param indent the indentation string to use.
1534 void
maybe_report_data_members_replaced_by_anon_dm(const class_or_union_diff & d,ostream & out,const string & indent)1535 maybe_report_data_members_replaced_by_anon_dm(const class_or_union_diff &d,
1536 ostream &out,
1537 const string &indent)
1538 {
1539 const diff_context_sptr& ctxt = d.context();
1540
1541 if ((ctxt->get_allowed_category() & HARMLESS_DATA_MEMBER_CHANGE_CATEGORY)
1542 && !d.data_members_replaced_by_adms().empty())
1543 {
1544 // Let's detect all the data members that are replaced by
1545 // members of the same anonymous data member and report them
1546 // in one go.
1547 for (changed_var_sptrs_type::const_iterator i =
1548 d.ordered_data_members_replaced_by_adms().begin();
1549 i != d.ordered_data_members_replaced_by_adms().end();)
1550 {
1551 // This contains the data members replaced by the same
1552 // anonymous data member.
1553 vector<var_decl_sptr> dms_replaced_by_same_anon_dm;
1554 dms_replaced_by_same_anon_dm.push_back(i->first);
1555 // This contains the anonymous data member that replaced the
1556 // data members in the variable above.
1557 var_decl_sptr anonymous_data_member = i->second;
1558 // Let's look forward to see if the subsequent data
1559 // members were replaced by members of
1560 // anonymous_data_member.
1561 for (++i;
1562 i != d.ordered_data_members_replaced_by_adms().end()
1563 && *i->second == *anonymous_data_member;
1564 ++i)
1565 dms_replaced_by_same_anon_dm.push_back(i->first);
1566
1567 bool several_data_members_replaced =
1568 dms_replaced_by_same_anon_dm.size() > 1;
1569
1570 out << indent << "data member";
1571 if (several_data_members_replaced)
1572 out << "s";
1573
1574 bool first_data_member = true;
1575 for (vector<var_decl_sptr>::const_iterator it =
1576 dms_replaced_by_same_anon_dm.begin();
1577 it != dms_replaced_by_same_anon_dm.end();
1578 ++it)
1579 {
1580 string name = (*it)->get_qualified_name();
1581 if (!first_data_member)
1582 out << ",";
1583 out << " '" << name << "'";
1584 first_data_member = false;
1585 }
1586
1587 if (several_data_members_replaced)
1588 out << " were ";
1589 else
1590 out << " was ";
1591
1592 out << "replaced by anonymous data member:\n"
1593 << indent + " "
1594 << "'"
1595 << anonymous_data_member->get_pretty_representation()
1596 << "'\n";
1597 }
1598 }
1599 }
1600
1601 /// Report about the base classes of a class having been re-ordered.
1602 ///
1603 /// @param d the class diff to consider.
1604 ///
1605 /// @param out the output stream to report the change to.
1606 ///
1607 /// @param indent the indentation string to use.
1608 void
maybe_report_base_class_reordering(const class_diff & d,ostream & out,const string & indent)1609 maybe_report_base_class_reordering(const class_diff &d,
1610 ostream &out,
1611 const string &indent)
1612 {
1613 if (d.moved_bases().empty())
1614 return;
1615
1616 class_decl_sptr first = d.first_class_decl(),
1617 second = d.second_class_decl();
1618
1619 ABG_ASSERT(!first->get_base_specifiers().empty());
1620 ABG_ASSERT(!second->get_base_specifiers().empty());
1621
1622 out << indent << "base classes of '"
1623 << first->get_pretty_representation()
1624 << "' are re-ordered from: ";
1625
1626 vector<class_decl_sptr> classes = {first, second};
1627 unsigned nb_classes_seen = 0;
1628 for (auto &klass : classes)
1629 {
1630 if (nb_classes_seen >= 1)
1631 out << " to: ";
1632 out << "'";
1633 bool needs_comma = false;
1634 for (auto &b : klass->get_base_specifiers())
1635 {
1636 if (needs_comma)
1637 out << ", ";
1638 if (b->get_is_virtual())
1639 out << "virtual ";
1640 out << b->get_base_class()->get_qualified_name();
1641 needs_comma = true;
1642 }
1643 out << "'";
1644 nb_classes_seen++;
1645 }
1646 if (nb_classes_seen)
1647 out << "\n";
1648 }
1649 } // Namespace comparison
1650 } // end namespace abigail
1651