1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- Mode: C++ -*-
3 //
4 // Copyright (C) 2017-2022 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 = o->get_qualified_name();
406 const string n_name = n->get_qualified_name();
407 const uint64_t o_size = get_var_size_in_bits(o);
408 const uint64_t n_size = get_var_size_in_bits(n);
409 const uint64_t o_offset = get_data_member_offset(o);
410 const uint64_t n_offset = get_data_member_offset(n);
411 const string o_pretty_representation =
412 o->get_pretty_representation(/*internal=*/false, /*qualified_name=*/false);
413 // no n_pretty_representation here as it's only needed in a couple of places
414 const bool show_size_offset_changes = ctxt->get_allowed_category()
415 & SIZE_OR_OFFSET_CHANGE_CATEGORY;
416
417 // Has the main diff text been output?
418 bool emitted = false;
419 // Are we continuing on a new line? (implies emitted)
420 bool begin_with_and = false;
421 // Have we reported a size change already?
422 bool size_reported = false;
423
424 //----------------------------------------------------------------
425 // First we'll try to emit a report about the type change of this
426 // var_decl_diff.
427 //
428 // In the context of that type change report, we need to keep in
429 // mind that because we want to emit specific (useful) reports about
430 // anonymous data member changes, we'll try to detect the various
431 // scenarii that involve anonymous data member changes.
432 //
433 // Then, as a fallback method, we'll emit a more generic type change
434 // report for the other generic type changes.
435 //----------------------------------------------------------------
436
437 if (is_strict_anonymous_data_member_change)
438 {
439 const string n_pretty_representation =
440 n->get_pretty_representation(/*internal=*/false,
441 /*qualified_name=*/false);
442 const type_base_sptr o_type = o->get_type(), n_type = n->get_type();
443 if (o_pretty_representation != n_pretty_representation)
444 {
445 show_offset_or_size(indent + "anonymous data member at offset",
446 o_offset, *ctxt, out);
447
448 out << " changed from:\n"
449 << indent << " " << o_pretty_representation << "\n"
450 << indent << "to:\n"
451 << indent << " " << n_pretty_representation << "\n";
452
453 begin_with_and = true;
454 emitted = true;
455 }
456 else if (get_type_name(o_type) != get_type_name(n_type)
457 && is_decl(o_type) && is_decl(n_type)
458 && is_decl(o_type)->get_is_anonymous()
459 && is_decl(n_type)->get_is_anonymous())
460 {
461 out << indent << "while looking at anonymous data member '"
462 << o_pretty_representation << "':\n"
463 << indent << "the internal name of that anonymous data member"
464 " changed from:\n"
465 << indent << " " << get_type_name(o_type) << "\n"
466 << indent << "to:\n"
467 << indent << " " << get_type_name(n_type) << "\n"
468 << indent << " This is usually due to "
469 << "an anonymous member type being added or removed from "
470 << "the containing type\n";
471
472 begin_with_and = true;
473 emitted = true;
474 }
475 }
476 else if (filtering::has_anonymous_data_member_change(diff))
477 {
478 ABG_ASSERT(o_anon != n_anon);
479 // So we are looking at a non-anonymous data member change from
480 // or to an anonymous data member.
481 const string n_pretty_representation =
482 n->get_pretty_representation(/*internal=*/false,
483 /*qualified_name=*/false);
484 out << indent << (o_anon ? "anonymous " : "")
485 << "data member " << o_pretty_representation;
486 show_offset_or_size(" at offset", o_offset, *ctxt, out);
487 out << " became " << (n_anon ? "anonymous " : "")
488 << "data member '" << n_pretty_representation << "'\n";
489
490 begin_with_and = true;
491 emitted = true;
492 }
493
494 //
495 // If we haven't succeeded in emitting a specific type change report
496 // (mainly related to anonymous data data member changes) then let's
497 // try to emit a more generic report about the type change.
498 //
499 // This is the fallback method outlined in the comment at the
500 // beginning of this section.
501 //
502 if (!emitted)
503 if (const diff_sptr d = diff->type_diff())
504 {
505 if (ctxt->get_reporter()->diff_to_be_reported(d.get()))
506 {
507 if (local_only)
508 out << indent << "type '"
509 << get_pretty_representation(o->get_type())
510 << "' of '"
511 << (o_anon ?
512 string("anonymous data member")
513 : o->get_qualified_name())
514 << "' changed";
515 else
516 out << indent
517 << "type of '"<< (o_anon ? "anonymous data member ": "")
518 << o_pretty_representation << "' changed";
519
520 if (d->currently_reporting())
521 out << ", as being reported\n";
522 else if (d->reported_once())
523 out << ", as reported earlier\n";
524 else
525 {
526 out << ":\n";
527 d->report(out, indent + " ");
528 }
529
530 begin_with_and = true;
531 emitted = true;
532 size_reported = true;
533 }
534 }
535
536 //
537 // Okay, now we are done with report type changes. Let's report the
538 // other potential kinds of changes.
539 //
540
541 if (!filtering::has_anonymous_data_member_change(diff) && o_name != n_name)
542 {
543 if (filtering::has_harmless_name_change(o, n)
544 && !(ctxt->get_allowed_category()
545 & HARMLESS_DECL_NAME_CHANGE_CATEGORY))
546 ;
547 else
548 {
549 if (begin_with_and)
550 {
551 out << indent << "and ";
552 begin_with_and = false;
553 }
554 else if (!emitted)
555 out << indent;
556 else
557 out << ", ";
558 out << "name of '" << o_name << "' changed to '" << n_name << "'";
559 report_loc_info(n, *ctxt, out);
560 emitted = true;
561 }
562 }
563
564 if (get_data_member_is_laid_out(o)
565 != get_data_member_is_laid_out(n))
566 {
567 if (begin_with_and)
568 {
569 out << indent << "and ";
570 begin_with_and = false;
571 }
572 else if (!emitted)
573 out << indent << "'" << o_pretty_representation << "' ";
574 else
575 out << ", ";
576 if (get_data_member_is_laid_out(o))
577 out << "is no more laid out";
578 else
579 out << "now becomes laid out";
580 emitted = true;
581 }
582 if (show_size_offset_changes)
583 {
584 if (o_offset != n_offset)
585 {
586 if (begin_with_and)
587 {
588 out << indent << "and ";
589 begin_with_and = false;
590 }
591 else if (!emitted)
592 {
593 out << indent;
594 if (is_strict_anonymous_data_member_change)
595 out << "anonymous data member ";
596 out << "'" << o_pretty_representation << "' ";
597 }
598 else
599 out << ", ";
600
601 show_numerical_change("offset", o_offset, n_offset, *ctxt, out);
602 maybe_show_relative_offset_change(diff, *ctxt, out);
603 emitted = true;
604 }
605
606 if (!size_reported && o_size != n_size)
607 {
608 if (begin_with_and)
609 {
610 out << indent << "and ";
611 begin_with_and = false;
612 }
613 else if (!emitted)
614 {
615 out << indent;
616 if (is_strict_anonymous_data_member_change)
617 out << "anonymous data member ";
618 out << "'" << o_pretty_representation << "' ";
619 }
620 else
621 out << ", ";
622
623 show_numerical_change("size", o_size, n_size, *ctxt, out);
624 maybe_show_relative_size_change(diff, *ctxt, out);
625 emitted = true;
626 }
627 }
628 if (o->get_binding() != n->get_binding())
629 {
630 if (begin_with_and)
631 {
632 out << indent << "and ";
633 begin_with_and = false;
634 }
635 else if (!emitted)
636 out << indent << "'" << o_pretty_representation << "' ";
637 else
638 out << ", ";
639 out << "elf binding changed from " << o->get_binding()
640 << " to " << n->get_binding();
641 emitted = true;
642 }
643 if (o->get_visibility() != n->get_visibility())
644 {
645 if (begin_with_and)
646 {
647 out << indent << "and ";
648 begin_with_and = false;
649 }
650 else if (!emitted)
651 out << indent << "'" << o_pretty_representation << "' ";
652 else
653 out << ", ";
654 out << "visibility changed from " << o->get_visibility()
655 << " to " << n->get_visibility();
656 emitted = true;
657 }
658 if ((ctxt->get_allowed_category() & ACCESS_CHANGE_CATEGORY)
659 && (get_member_access_specifier(o)
660 != get_member_access_specifier(n)))
661 {
662 if (begin_with_and)
663 {
664 out << indent << "and ";
665 begin_with_and = false;
666 }
667 else if (!emitted)
668 out << indent << "'" << o_pretty_representation << "' ";
669 else
670 out << ", ";
671
672 out << "access changed from '"
673 << get_member_access_specifier(o)
674 << "' to '"
675 << get_member_access_specifier(n) << "'";
676 emitted = true;
677 }
678 if (get_member_is_static(o)
679 != get_member_is_static(n))
680 {
681 if (begin_with_and)
682 {
683 out << indent << "and ";
684 begin_with_and = false;
685 }
686 else if (!emitted)
687 out << indent << "'" << o_pretty_representation << "' ";
688 else
689 out << ", ";
690
691 if (get_member_is_static(o))
692 out << "is no more static";
693 else
694 out << "now becomes static";
695 emitted = true;
696 }
697
698 if (begin_with_and)
699 // do nothing as begin_with_and implies emitted
700 ;
701 else if (!emitted)
702 // We appear to have fallen off the edge of the map.
703 out << indent << "'" << o_pretty_representation
704 << "' has *some* difference - please report as a bug";
705 else
706 {
707 ;// do nothing
708 }
709 emitted = true;
710
711 if (!begin_with_and)
712 out << "\n";
713 }
714
715 /// Report the size and alignment changes of a type.
716 ///
717 /// @param first the first type to consider.
718 ///
719 /// @param second the second type to consider.
720 ///
721 /// @param ctxt the content of the current diff.
722 ///
723 /// @param out the output stream to report the change to.
724 ///
725 /// @param indent the string to use for indentation.
726 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)727 report_size_and_alignment_changes(type_or_decl_base_sptr first,
728 type_or_decl_base_sptr second,
729 diff_context_sptr ctxt,
730 ostream& out,
731 const string& indent)
732 {
733 type_base_sptr f = dynamic_pointer_cast<type_base>(first),
734 s = dynamic_pointer_cast<type_base>(second);
735
736 if (!s || !f)
737 return;
738
739 class_or_union_sptr first_class = is_class_or_union_type(first),
740 second_class = is_class_or_union_type(second);
741
742 if (filtering::has_class_decl_only_def_change(first_class, second_class))
743 // So these two classes differ only by the fact that one is the
744 // declaration-only form of the second. The declaration-only class
745 // is of unknown size (recorded as 0) and it is not meaningful to
746 // report a size change.
747 return;
748
749 unsigned fs = f->get_size_in_bits(), ss = s->get_size_in_bits(),
750 fa = f->get_alignment_in_bits(), sa = s->get_alignment_in_bits();
751 array_type_def_sptr first_array = is_array_type(is_type(first)),
752 second_array = is_array_type(is_type(second));
753 unsigned fdc = first_array ? first_array->get_dimension_count(): 0,
754 sdc = second_array ? second_array->get_dimension_count(): 0;
755
756 if (ctxt->get_allowed_category() & SIZE_OR_OFFSET_CHANGE_CATEGORY)
757 {
758 if (fs != ss || fdc != sdc)
759 {
760 if (first_array && second_array)
761 {
762 // We are looking at size or alignment changes between two
763 // arrays ...
764 out << indent << "array type size changed from ";
765 if (first_array->is_infinite())
766 out << "infinity";
767 else
768 emit_num_value(first_array->get_size_in_bits(), *ctxt, out);
769 out << " to ";
770 if (second_array->is_infinite())
771 out << "infinity";
772 else
773 emit_num_value(second_array->get_size_in_bits(), *ctxt, out);
774 out << "\n";
775
776 if (sdc != fdc)
777 {
778 out << indent + " "
779 << "number of dimensions changed from "
780 << fdc
781 << " to "
782 << sdc
783 << "\n";
784 }
785 array_type_def::subranges_type::const_iterator i, j;
786 for (i = first_array->get_subranges().begin(),
787 j = second_array->get_subranges().begin();
788 (i != first_array->get_subranges().end()
789 && j != second_array->get_subranges().end());
790 ++i, ++j)
791 {
792 if ((*i)->get_length() != (*j)->get_length())
793 {
794 out << indent
795 << "array type subrange "
796 << i - first_array->get_subranges().begin() + 1
797 << " changed length from ";
798
799 if ((*i)->is_infinite())
800 out << "infinity";
801 else
802 out << (*i)->get_length();
803
804 out << " to ";
805
806 if ((*j)->is_infinite())
807 out << "infinity";
808 else
809 out << (*j)->get_length();
810 out << "\n";
811 }
812 }
813 } // end if (first_array && second_array)
814 else if (fs != ss)
815 {
816 out << indent;
817 show_numerical_change("type size", fs, ss, *ctxt, out);
818 out << "\n";
819 }
820 } // end if (fs != ss || fdc != sdc)
821 else
822 if (ctxt->show_relative_offset_changes())
823 {
824 out << indent << "type size hasn't changed\n";
825 }
826 }
827 if ((ctxt->get_allowed_category() & SIZE_OR_OFFSET_CHANGE_CATEGORY)
828 && (fa != sa))
829 {
830 out << indent;
831 show_numerical_change("type alignment", fa, sa, *ctxt, out,
832 /*show_bits_or_bytes=*/false);
833 out << "\n";
834 }
835 }
836
837 /// @param tod the type or declaration to emit loc info about
838 ///
839 /// @param ctxt the content of the current diff.
840 ///
841 /// @param out the output stream to report the change to.
842 ///
843 /// @return true iff something was reported.
844 bool
report_loc_info(const type_or_decl_base_sptr & tod,const diff_context & ctxt,ostream & out)845 report_loc_info(const type_or_decl_base_sptr& tod,
846 const diff_context& ctxt,
847 ostream &out)
848 {
849 if (!ctxt.show_locs())
850 return false;
851
852 decl_base_sptr decl = is_decl(tod);
853
854 if (!decl)
855 return false;
856
857 location loc;
858 translation_unit* tu = get_translation_unit(decl);
859
860 if (tu && (loc = decl->get_location()))
861 {
862 string path;
863 unsigned line, column;
864
865 loc.expand(path, line, column);
866 //tu->get_loc_mgr().expand_location(loc, path, line, column);
867 path = basename(const_cast<char*>(path.c_str()));
868
869 out << " at " << path << ":" << line << ":" << column;
870
871 return true;
872 }
873 return false;
874 }
875
876 /// Report the name, size and alignment changes of a type.
877 ///
878 /// @param first the first type to consider.
879 ///
880 /// @param second the second type to consider.
881 ///
882 /// @param ctxt the content of the current diff.
883 ///
884 /// @param out the output stream to report the change to.
885 ///
886 /// @param indent the string to use for indentation.
887 void
report_name_size_and_alignment_changes(decl_base_sptr first,decl_base_sptr second,diff_context_sptr ctxt,ostream & out,const string & indent)888 report_name_size_and_alignment_changes(decl_base_sptr first,
889 decl_base_sptr second,
890 diff_context_sptr ctxt,
891 ostream& out,
892 const string& indent)
893 {
894 string fn = first->get_qualified_name(),
895 sn = second->get_qualified_name();
896
897 if (fn != sn)
898 {
899 if (!(ctxt->get_allowed_category() & HARMLESS_DECL_NAME_CHANGE_CATEGORY)
900 && filtering::has_harmless_name_change(first, second))
901 // This is a harmless name change. but then
902 // HARMLESS_DECL_NAME_CHANGE_CATEGORY doesn't seem allowed.
903 ;
904 else
905 {
906 out << indent;
907 if (is_type(first))
908 out << "type";
909 else
910 out << "declaration";
911 out << " name changed from '" << fn << "' to '" << sn << "'";
912 out << "\n";
913 }
914 }
915
916 report_size_and_alignment_changes(first, second, ctxt, out, indent);
917 }
918
919 /// Output the header preceding the the report for
920 /// insertion/deletion/change of a part of a class. This is a
921 /// subroutine of class_diff::report.
922 ///
923 /// @param out the output stream to output the report to.
924 ///
925 /// @param number the number of insertion/deletion to refer to in the
926 /// header.
927 ///
928 /// @param num_filtered the number of filtered changes.
929 ///
930 /// @param k the kind of diff (insertion/deletion/change) we want the
931 /// head to introduce.
932 ///
933 /// @param section_name the name of the sub-part of the class to
934 /// report about.
935 ///
936 /// @param indent the string to use as indentation prefix in the
937 /// header.
938 void
report_mem_header(ostream & out,size_t number,size_t num_filtered,diff_kind k,const string & section_name,const string & indent)939 report_mem_header(ostream& out,
940 size_t number,
941 size_t num_filtered,
942 diff_kind k,
943 const string& section_name,
944 const string& indent)
945 {
946 size_t net_number = number - num_filtered;
947 string change;
948 char colon_or_semi_colon = ':';
949
950 switch (k)
951 {
952 case del_kind:
953 change = (number > 1) ? "deletions" : "deletion";
954 break;
955 case ins_kind:
956 change = (number > 1) ? "insertions" : "insertion";
957 break;
958 case subtype_change_kind:
959 case change_kind:
960 change = (number > 1) ? "changes" : "change";
961 break;
962 }
963
964 if (net_number == 0)
965 {
966 out << indent << "no " << section_name << " " << change;
967 colon_or_semi_colon = ';';
968 }
969 else if (net_number == 1)
970 out << indent << "1 " << section_name << " " << change;
971 else
972 out << indent << net_number << " " << section_name
973 << " " << change;
974
975 if (num_filtered)
976 out << " (" << num_filtered << " filtered)";
977 out << colon_or_semi_colon << "\n";
978 }
979
980 /// Output the header preceding the the report for
981 /// insertion/deletion/change of a part of a class. This is a
982 /// subroutine of class_diff::report.
983 ///
984 /// @param out the output stream to output the report to.
985 ///
986 /// @param k the kind of diff (insertion/deletion/change) we want the
987 /// head to introduce.
988 ///
989 /// @param section_name the name of the sub-part of the class to
990 /// report about.
991 ///
992 /// @param indent the string to use as indentation prefix in the
993 /// header.
994 void
report_mem_header(ostream & out,diff_kind k,const string & section_name,const string & indent)995 report_mem_header(ostream& out,
996 diff_kind k,
997 const string& section_name,
998 const string& indent)
999 {
1000 string change;
1001
1002 switch (k)
1003 {
1004 case del_kind:
1005 change = "deletions";
1006 break;
1007 case ins_kind:
1008 change = "insertions";
1009 break;
1010 case subtype_change_kind:
1011 case change_kind:
1012 change = "changes";
1013 break;
1014 }
1015
1016 out << indent << "there are " << section_name << " " << change << ":\n";
1017 }
1018
1019 /// Report the differences in access specifiers and static-ness for
1020 /// class members.
1021 ///
1022 /// @param decl1 the first class member to consider.
1023 ///
1024 /// @param decl2 the second class member to consider.
1025 ///
1026 /// @param out the output stream to send the report to.
1027 ///
1028 /// @param indent the indentation string to use for the report.
1029 ///
1030 /// @return true if something was reported, false otherwise.
1031 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)1032 maybe_report_diff_for_member(const decl_base_sptr& decl1,
1033 const decl_base_sptr& decl2,
1034 const diff_context_sptr& ctxt,
1035 ostream& out,
1036 const string& indent)
1037
1038 {
1039 bool reported = false;
1040 if (!is_member_decl(decl1) || !is_member_decl(decl2))
1041 return reported;
1042
1043 string decl1_repr = decl1->get_pretty_representation(),
1044 decl2_repr = decl2->get_pretty_representation();
1045
1046 if (get_member_is_static(decl1) != get_member_is_static(decl2))
1047 {
1048 bool lost = get_member_is_static(decl1);
1049 out << indent << "'" << decl1_repr << "' ";
1050 if (report_loc_info(decl2, *ctxt, out))
1051 out << " ";
1052 if (lost)
1053 out << "became non-static";
1054 else
1055 out << "became static";
1056 out << "\n";
1057 reported = true;
1058 }
1059 if ((ctxt->get_allowed_category() & ACCESS_CHANGE_CATEGORY)
1060 && (get_member_access_specifier(decl1)
1061 != get_member_access_specifier(decl2)))
1062 {
1063 out << indent << "'" << decl1_repr << "' access changed from '"
1064 << get_member_access_specifier(decl1)
1065 << "' to '"
1066 << get_member_access_specifier(decl2)
1067 << "'\n";
1068 reported = true;
1069 }
1070 return reported;
1071 }
1072
1073 /// Report the difference between two ELF symbols, if there is any.
1074 ///
1075 /// @param symbol1 the first symbol to consider.
1076 ///
1077 /// @param symbol2 the second symbol to consider.
1078 ///
1079 /// @param ctxt the diff context.
1080 ///
1081 /// @param the output stream to emit the report to.
1082 ///
1083 /// @param indent the indentation string to use.
1084 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)1085 maybe_report_diff_for_symbol(const elf_symbol_sptr& symbol1,
1086 const elf_symbol_sptr& symbol2,
1087 const diff_context_sptr& ctxt,
1088 ostream& out,
1089 const string& indent)
1090 {
1091 if (!symbol1 || !symbol2 || symbol1 == symbol2)
1092 return;
1093
1094 if (symbol1->get_size() != symbol2->get_size())
1095 {
1096 out << indent;
1097 show_numerical_change("size of symbol",
1098 symbol1->get_size(),
1099 symbol2->get_size(),
1100 *ctxt, out,
1101 /*show_bits_or_bytes=*/false);
1102 out << "\n";
1103 }
1104
1105 if (symbol1->get_name() != symbol2->get_name())
1106 {
1107 out << indent << "symbol name changed from "
1108 << symbol1->get_name()
1109 << " to "
1110 << symbol2->get_name()
1111 << "\n";
1112 }
1113
1114 if (symbol1->get_type() != symbol2->get_type())
1115 {
1116 out << indent << "symbol type changed from '"
1117 << symbol1->get_type()
1118 << "' to '"
1119 << symbol2->get_type()
1120 << "'\n";
1121 }
1122
1123 if (symbol1->is_public() != symbol2->is_public())
1124 {
1125 out << indent << "symbol became ";
1126 if (symbol2->is_public())
1127 out << "exported";
1128 else
1129 out << "non-exported";
1130 out << "\n";
1131 }
1132
1133 if (symbol1->is_defined() != symbol2->is_defined())
1134 {
1135 out << indent << "symbol became ";
1136 if (symbol2->is_defined())
1137 out << "defined";
1138 else
1139 out << "undefined";
1140 out << "\n";
1141 }
1142
1143 if (symbol1->get_version() != symbol2->get_version())
1144 {
1145 out << indent << "symbol version changed from "
1146 << symbol1->get_version().str()
1147 << " to "
1148 << symbol2->get_version().str()
1149 << "\n";
1150 }
1151
1152 const abg_compat::optional<uint32_t>& crc1 = symbol1->get_crc();
1153 const abg_compat::optional<uint32_t>& crc2 = symbol2->get_crc();
1154 if (crc1 != crc2)
1155 {
1156 const std::string none = "(none)";
1157 out << indent << "CRC (modversions) changed from "
1158 << std::showbase << std::hex;
1159 if (crc1.has_value())
1160 out << crc1.value();
1161 else
1162 out << none;
1163 out << " to ";
1164 if (crc2.has_value())
1165 out << crc2.value();
1166 else
1167 out << none;
1168 out << std::noshowbase << std::dec
1169 << "\n";
1170 }
1171
1172 const abg_compat::optional<std::string>& ns1 = symbol1->get_namespace();
1173 const abg_compat::optional<std::string>& ns2 = symbol2->get_namespace();
1174 if (ns1 != ns2)
1175 {
1176 const std::string none = "(none)";
1177 out << indent << "namespace changed from ";
1178 if (ns1.has_value())
1179 out << "'" << ns1.value() << "'";
1180 else
1181 out << none;
1182 out << " to ";
1183 if (ns2.has_value())
1184 out << "'" << ns2.value() << "'";
1185 else
1186 out << none;
1187 out << "\n";
1188 }
1189 }
1190
1191 /// For a given symbol, emit a string made of its name and version.
1192 /// The string also contains the list of symbols that alias this one.
1193 ///
1194 /// @param out the output string to emit the resulting string to.
1195 ///
1196 /// @param indent the indentation string to use before emitting the
1197 /// resulting string.
1198 ///
1199 /// @param symbol the symbol to emit the representation string for.
1200 ///
1201 /// @param sym_map the symbol map to consider to look for aliases of
1202 /// @p symbol.
1203 void
show_linkage_name_and_aliases(ostream & out,const string & indent,const elf_symbol & symbol,const string_elf_symbols_map_type & sym_map)1204 show_linkage_name_and_aliases(ostream& out,
1205 const string& indent,
1206 const elf_symbol& symbol,
1207 const string_elf_symbols_map_type& sym_map)
1208 {
1209 out << indent << symbol.get_id_string();
1210 string aliases =
1211 symbol.get_aliases_id_string(sym_map,
1212 /*include_symbol_itself=*/false);
1213 if (!aliases.empty())
1214 out << ", aliases " << aliases;
1215 }
1216
1217 /// Report changes about types that are not reachable from global
1218 /// functions and variables, in a given @param corpus_diff.
1219 ///
1220 /// @param d the corpus_diff to consider.
1221 ///
1222 /// @param s the statistics of the changes, after filters and
1223 /// suppressions are reported. This is typically what is returned by
1224 /// corpus_diff::apply_filters_and_suppressions_before_reporting().
1225 ///
1226 /// @param indent the indendation string (usually a string of white
1227 /// spaces) to use for indentation during the reporting.
1228 ///
1229 /// @param out the output stream to emit the report to.
1230 void
maybe_report_unreachable_type_changes(const corpus_diff & d,const corpus_diff::diff_stats & s,const string & indent,ostream & out)1231 maybe_report_unreachable_type_changes(const corpus_diff& d,
1232 const corpus_diff::diff_stats &s,
1233 const string& indent,
1234 ostream& out)
1235 {
1236 const diff_context_sptr& ctxt = d.context();
1237
1238 if (!(ctxt->show_unreachable_types()
1239 && (!d.priv_->deleted_unreachable_types_.empty()
1240 || !d.priv_->added_unreachable_types_.empty()
1241 || !d.priv_->changed_unreachable_types_.empty())))
1242 // The user either doesn't want us to show changes about
1243 // unreachable types or there are not such changes.
1244 return;
1245
1246 // Handle removed unreachable types.
1247 if (s.net_num_removed_unreachable_types() == 1)
1248 out << indent
1249 << "1 removed type unreachable from any public interface:\n\n";
1250 else if (s.net_num_removed_unreachable_types() > 1)
1251 out << indent
1252 << s.net_num_removed_unreachable_types()
1253 << " removed types unreachable from any public interface:\n\n";
1254
1255 vector<type_base_sptr> sorted_removed_unreachable_types;
1256 sort_string_type_base_sptr_map(d.priv_->deleted_unreachable_types_,
1257 sorted_removed_unreachable_types);
1258 bool emitted = false;
1259 for (vector<type_base_sptr>::const_iterator i =
1260 sorted_removed_unreachable_types.begin();
1261 i != sorted_removed_unreachable_types.end();
1262 ++i)
1263 {
1264 if (d.priv_->deleted_unreachable_type_is_suppressed((*i).get()))
1265 continue;
1266
1267 out << indent << " [D] '" << get_pretty_representation(*i) << "'";
1268 report_loc_info(*i, *ctxt, out);
1269 out << "\n";
1270 emitted = true;
1271 }
1272 if (emitted)
1273 out << "\n";
1274
1275 // Handle changed unreachable types!
1276 if (s.net_num_changed_unreachable_types() == 1)
1277 out << indent
1278 << "1 changed type unreachable from any public interface:\n\n";
1279 else if (s.net_num_changed_unreachable_types() > 1)
1280 out << indent
1281 << s.net_num_changed_unreachable_types()
1282 << " changed types unreachable from any public interface:\n\n";
1283
1284 diff_sptrs_type sorted_diff_sptrs;
1285 sort_string_diff_sptr_map(d.priv_->changed_unreachable_types_,
1286 sorted_diff_sptrs);
1287 for (diff_sptrs_type::const_iterator i = sorted_diff_sptrs.begin();
1288 i != sorted_diff_sptrs.end();
1289 ++i)
1290 {
1291 diff_sptr diff = *i;
1292 if (!diff || !diff->to_be_reported())
1293 continue;
1294
1295 string repr = diff->first_subject()->get_pretty_representation();
1296
1297 out << indent << " [C] '" << repr << "' changed:\n";
1298 diff->report(out, indent + " ");
1299 // Extra spacing.
1300 out << "\n";
1301 }
1302 // Changed types have extra spacing already. No new line here.
1303
1304 // Handle added unreachable types.
1305 if (s.net_num_added_unreachable_types() == 1)
1306 out << indent
1307 << "1 added type unreachable from any public interface:\n\n";
1308 else if (s.net_num_added_unreachable_types() > 1)
1309 out << indent
1310 << s.net_num_added_unreachable_types()
1311 << " added types unreachable from any public interface:\n\n";
1312
1313 vector<type_base_sptr> sorted_added_unreachable_types;
1314 sort_string_type_base_sptr_map(d.priv_->added_unreachable_types_,
1315 sorted_added_unreachable_types);
1316 emitted = false;
1317 for (vector<type_base_sptr>::const_iterator i =
1318 sorted_added_unreachable_types.begin();
1319 i != sorted_added_unreachable_types.end();
1320 ++i)
1321 {
1322 if (d.priv_->added_unreachable_type_is_suppressed((*i).get()))
1323 continue;
1324
1325 out << indent << " [A] '" << get_pretty_representation(*i) << "'";
1326 report_loc_info(*i, *ctxt, out);
1327 out << "\n";
1328 emitted = true;
1329 }
1330 if (emitted)
1331 out << "\n";
1332 }
1333
1334 /// If a given diff node impacts some public interfaces, then report
1335 /// about those impacted interfaces on a given output stream.
1336 ///
1337 /// @param d the diff node to get the impacted interfaces for.
1338 ///
1339 /// @param out the output stream to report to.
1340 ///
1341 /// @param indent the white space string to use for indentation.
1342 void
maybe_report_interfaces_impacted_by_diff(const diff * d,ostream & out,const string & indent)1343 maybe_report_interfaces_impacted_by_diff(const diff *d,
1344 ostream &out,
1345 const string &indent)
1346 {
1347 const diff_context_sptr &ctxt = d->context();
1348 const corpus_diff_sptr &corp_diff = ctxt->get_corpus_diff();
1349 if (!corp_diff)
1350 return;
1351
1352 if (!ctxt->show_impacted_interfaces())
1353 return;
1354
1355 const diff_maps &maps = corp_diff->get_leaf_diffs();
1356 artifact_sptr_set_type* impacted_artifacts =
1357 maps.lookup_impacted_interfaces(d);
1358 if (impacted_artifacts == 0)
1359 return;
1360
1361 if (impacted_artifacts->empty())
1362 return;
1363
1364 vector<type_or_decl_base_sptr> sorted_impacted_interfaces;
1365 sort_artifacts_set(*impacted_artifacts, sorted_impacted_interfaces);
1366
1367 size_t num_impacted_interfaces = impacted_artifacts->size();
1368 if (num_impacted_interfaces == 1)
1369 out << indent << "one impacted interface:\n";
1370 else
1371 out << indent << num_impacted_interfaces << " impacted interfaces:\n";
1372
1373 string cur_indent = indent + " ";
1374 vector<type_or_decl_base_sptr>::const_iterator it;
1375 for (it = sorted_impacted_interfaces.begin();
1376 it != sorted_impacted_interfaces.end();
1377 ++it)
1378 {
1379 out << cur_indent << get_pretty_representation(*it) << "\n";
1380 }
1381 }
1382
1383 /// If a given diff node impacts some public interfaces, then report
1384 /// about those impacted interfaces on standard output.
1385 ///
1386 /// @param d the diff node to get the impacted interfaces for.
1387 ///
1388 /// @param out the output stream to report to.
1389 ///
1390 /// @param indent the white space string to use for indentation.
1391 void
maybe_report_interfaces_impacted_by_diff(const diff_sptr & d,ostream & out,const string & indent)1392 maybe_report_interfaces_impacted_by_diff(const diff_sptr &d,
1393 ostream &out,
1394 const string &indent)
1395 {return maybe_report_interfaces_impacted_by_diff(d.get(), out, indent);}
1396
1397 /// Tests if the diff node is to be reported.
1398 ///
1399 /// @param p the diff to consider.
1400 ///
1401 /// @return true iff the diff is to be reported.
1402 bool
diff_to_be_reported(const diff * d) const1403 reporter_base::diff_to_be_reported(const diff *d) const
1404 {return d && d->to_be_reported();}
1405
1406 /// Report about data members replaced by an anonymous data member
1407 /// without changing the overall bit-layout of the class or union in
1408 /// an ABI-meaningful way.
1409 ///
1410 /// @param d the diff to consider.
1411 ///
1412 /// @param out the output stream to emit the change report to.
1413 ///
1414 /// @param indent the indentation string to use.
1415 void
maybe_report_data_members_replaced_by_anon_dm(const class_or_union_diff & d,ostream & out,const string & indent)1416 maybe_report_data_members_replaced_by_anon_dm(const class_or_union_diff &d,
1417 ostream &out,
1418 const string &indent)
1419 {
1420 const diff_context_sptr& ctxt = d.context();
1421
1422 if ((ctxt->get_allowed_category() & HARMLESS_DATA_MEMBER_CHANGE_CATEGORY)
1423 && !d.data_members_replaced_by_adms().empty())
1424 {
1425 // Let's detect all the data members that are replaced by
1426 // members of the same anonymous data member and report them
1427 // in one go.
1428 for (changed_var_sptrs_type::const_iterator i =
1429 d.ordered_data_members_replaced_by_adms().begin();
1430 i != d.ordered_data_members_replaced_by_adms().end();)
1431 {
1432 // This contains the data members replaced by the same
1433 // anonymous data member.
1434 vector<var_decl_sptr> dms_replaced_by_same_anon_dm;
1435 dms_replaced_by_same_anon_dm.push_back(i->first);
1436 // This contains the anonymous data member that replaced the
1437 // data members in the variable above.
1438 var_decl_sptr anonymous_data_member = i->second;
1439 // Let's look forward to see if the subsequent data
1440 // members were replaced by members of
1441 // anonymous_data_member.
1442 for (++i;
1443 i != d.ordered_data_members_replaced_by_adms().end()
1444 && *i->second == *anonymous_data_member;
1445 ++i)
1446 dms_replaced_by_same_anon_dm.push_back(i->first);
1447
1448 bool several_data_members_replaced =
1449 dms_replaced_by_same_anon_dm.size() > 1;
1450
1451 out << indent << "data member";
1452 if (several_data_members_replaced)
1453 out << "s";
1454
1455 bool first_data_member = true;
1456 for (vector<var_decl_sptr>::const_iterator it =
1457 dms_replaced_by_same_anon_dm.begin();
1458 it != dms_replaced_by_same_anon_dm.end();
1459 ++it)
1460 {
1461 string name = (*it)->get_qualified_name();
1462 if (!first_data_member)
1463 out << ",";
1464 out << " '" << name << "'";
1465 first_data_member = false;
1466 }
1467
1468 if (several_data_members_replaced)
1469 out << " were ";
1470 else
1471 out << " was ";
1472
1473 out << "replaced by anonymous data member:\n"
1474 << indent + " "
1475 << "'"
1476 << anonymous_data_member->get_pretty_representation()
1477 << "'\n";
1478 }
1479 }
1480 }
1481
1482 /// Report about the base classes of a class having been re-ordered.
1483 ///
1484 /// @param d the class diff to consider.
1485 ///
1486 /// @param out the output stream to report the change to.
1487 ///
1488 /// @param indent the indentation string to use.
1489 void
maybe_report_base_class_reordering(const class_diff & d,ostream & out,const string & indent)1490 maybe_report_base_class_reordering(const class_diff &d,
1491 ostream &out,
1492 const string &indent)
1493 {
1494 if (d.moved_bases().empty())
1495 return;
1496
1497 class_decl_sptr first = d.first_class_decl(),
1498 second = d.second_class_decl();
1499
1500 ABG_ASSERT(!first->get_base_specifiers().empty());
1501 ABG_ASSERT(!second->get_base_specifiers().empty());
1502
1503 out << indent << "base classes of '"
1504 << first->get_pretty_representation()
1505 << "' are re-ordered from: ";
1506
1507 vector<class_decl_sptr> classes = {first, second};
1508 unsigned nb_classes_seen = 0;
1509 for (auto &klass : classes)
1510 {
1511 if (nb_classes_seen >= 1)
1512 out << " to: ";
1513 out << "'";
1514 bool needs_comma = false;
1515 for (auto &b : klass->get_base_specifiers())
1516 {
1517 if (needs_comma)
1518 out << ", ";
1519 if (b->get_is_virtual())
1520 out << "virtual ";
1521 out << b->get_base_class()->get_qualified_name();
1522 needs_comma = true;
1523 }
1524 out << "'";
1525 nb_classes_seen++;
1526 }
1527 if (nb_classes_seen)
1528 out << "\n";
1529 }
1530 } // Namespace comparison
1531 } // end namespace abigail
1532