1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- Mode: C++ -*-
3 //
4 // Copyright (C) 2017-2020 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   if (symbol1->get_crc() != 0 && symbol2->get_crc() != 0
1153       && symbol1->get_crc() != symbol2->get_crc())
1154     {
1155       out << indent << "CRC (modversions) changed from "
1156 	  << std::showbase << std::hex
1157 	  << symbol1->get_crc() << " to " << symbol2->get_crc()
1158 	  << std::noshowbase << std::dec
1159 	  << "\n";
1160     }
1161 }
1162 
1163 /// For a given symbol, emit a string made of its name and version.
1164 /// The string also contains the list of symbols that alias this one.
1165 ///
1166 /// @param out the output string to emit the resulting string to.
1167 ///
1168 /// @param indent the indentation string to use before emitting the
1169 /// resulting string.
1170 ///
1171 /// @param symbol the symbol to emit the representation string for.
1172 ///
1173 /// @param sym_map the symbol map to consider to look for aliases of
1174 /// @p symbol.
1175 void
show_linkage_name_and_aliases(ostream & out,const string & indent,const elf_symbol & symbol,const string_elf_symbols_map_type & sym_map)1176 show_linkage_name_and_aliases(ostream& out,
1177 			      const string& indent,
1178 			      const elf_symbol& symbol,
1179 			      const string_elf_symbols_map_type& sym_map)
1180 {
1181   out << indent << symbol.get_id_string();
1182   string aliases =
1183     symbol.get_aliases_id_string(sym_map,
1184 				 /*include_symbol_itself=*/false);
1185   if (!aliases.empty())
1186     out << ", aliases " << aliases;
1187 }
1188 
1189 /// Report changes about types that are not reachable from global
1190 /// functions and variables, in a given @param corpus_diff.
1191 ///
1192 /// @param d the corpus_diff to consider.
1193 ///
1194 /// @param s the statistics of the changes, after filters and
1195 /// suppressions are reported.  This is typically what is returned by
1196 /// corpus_diff::apply_filters_and_suppressions_before_reporting().
1197 ///
1198 /// @param indent the indendation string (usually a string of white
1199 /// spaces) to use for indentation during the reporting.
1200 ///
1201 /// @param out the output stream to emit the report to.
1202 void
maybe_report_unreachable_type_changes(const corpus_diff & d,const corpus_diff::diff_stats & s,const string & indent,ostream & out)1203 maybe_report_unreachable_type_changes(const corpus_diff& d,
1204 				      const corpus_diff::diff_stats &s,
1205 				      const string& indent,
1206 				      ostream& out)
1207 {
1208   const diff_context_sptr& ctxt = d.context();
1209 
1210   if (!(ctxt->show_unreachable_types()
1211 	&& (!d.priv_->deleted_unreachable_types_.empty()
1212 	    || !d.priv_->added_unreachable_types_.empty()
1213 	    || !d.priv_->changed_unreachable_types_.empty())))
1214     // The user either doesn't want us to show changes about
1215     // unreachable types or there are not such changes.
1216     return;
1217 
1218   // Handle removed unreachable types.
1219   if (s.net_num_removed_unreachable_types() == 1)
1220     out << indent
1221 	<< "1 removed type unreachable from any public interface:\n\n";
1222   else if (s.net_num_removed_unreachable_types() > 1)
1223     out << indent
1224 	<< s.net_num_removed_unreachable_types()
1225 	<< " removed types unreachable from any public interface:\n\n";
1226 
1227   vector<type_base_sptr> sorted_removed_unreachable_types;
1228   sort_string_type_base_sptr_map(d.priv_->deleted_unreachable_types_,
1229 				 sorted_removed_unreachable_types);
1230   bool emitted = false;
1231   for (vector<type_base_sptr>::const_iterator i =
1232 	 sorted_removed_unreachable_types.begin();
1233        i != sorted_removed_unreachable_types.end();
1234        ++i)
1235     {
1236       if (d.priv_->deleted_unreachable_type_is_suppressed((*i).get()))
1237 	continue;
1238 
1239       out << indent << "  [D] '" << get_pretty_representation(*i) << "'";
1240       report_loc_info(*i, *ctxt, out);
1241       out << "\n";
1242       emitted = true;
1243     }
1244   if (emitted)
1245     out << "\n";
1246 
1247   // Handle changed unreachable types!
1248   if (s.net_num_changed_unreachable_types() == 1)
1249     out << indent
1250 	<< "1 changed type unreachable from any public interface:\n\n";
1251   else if (s.net_num_changed_unreachable_types() > 1)
1252     out << indent
1253 	<< s.net_num_changed_unreachable_types()
1254 	<< " changed types unreachable from any public interface:\n\n";
1255 
1256   diff_sptrs_type sorted_diff_sptrs;
1257   sort_string_diff_sptr_map(d.priv_->changed_unreachable_types_,
1258 			    sorted_diff_sptrs);
1259   for (diff_sptrs_type::const_iterator i = sorted_diff_sptrs.begin();
1260        i != sorted_diff_sptrs.end();
1261        ++i)
1262     {
1263       diff_sptr diff = *i;
1264       if (!diff || !diff->to_be_reported())
1265 	continue;
1266 
1267       string repr = diff->first_subject()->get_pretty_representation();
1268 
1269       out << indent << "  [C] '" << repr << "' changed:\n";
1270       diff->report(out, indent + "    ");
1271       // Extra spacing.
1272       out << "\n";
1273     }
1274   // Changed types have extra spacing already. No new line here.
1275 
1276   // Handle added unreachable types.
1277   if (s.net_num_added_unreachable_types() == 1)
1278     out << indent
1279 	<< "1 added type unreachable from any public interface:\n\n";
1280   else if (s.net_num_added_unreachable_types() > 1)
1281     out << indent
1282 	<< s.net_num_added_unreachable_types()
1283 	<< " added types unreachable from any public interface:\n\n";
1284 
1285   vector<type_base_sptr> sorted_added_unreachable_types;
1286   sort_string_type_base_sptr_map(d.priv_->added_unreachable_types_,
1287 				 sorted_added_unreachable_types);
1288   emitted = false;
1289   for (vector<type_base_sptr>::const_iterator i =
1290 	 sorted_added_unreachable_types.begin();
1291        i != sorted_added_unreachable_types.end();
1292        ++i)
1293     {
1294       if (d.priv_->added_unreachable_type_is_suppressed((*i).get()))
1295 	continue;
1296 
1297       out << indent << "  [A] '" << get_pretty_representation(*i) << "'";
1298       report_loc_info(*i, *ctxt, out);
1299       out << "\n";
1300       emitted = true;
1301     }
1302   if (emitted)
1303     out << "\n";
1304 }
1305 
1306 /// If a given diff node impacts some public interfaces, then report
1307 /// about those impacted interfaces on a given output stream.
1308 ///
1309 /// @param d the diff node to get the impacted interfaces for.
1310 ///
1311 /// @param out the output stream to report to.
1312 ///
1313 /// @param indent the white space string to use for indentation.
1314 void
maybe_report_interfaces_impacted_by_diff(const diff * d,ostream & out,const string & indent)1315 maybe_report_interfaces_impacted_by_diff(const diff	*d,
1316 					 ostream	&out,
1317 					 const string	&indent)
1318 {
1319   const diff_context_sptr &ctxt = d->context();
1320   const corpus_diff_sptr &corp_diff = ctxt->get_corpus_diff();
1321   if (!corp_diff)
1322     return;
1323 
1324   if (!ctxt->show_impacted_interfaces())
1325     return;
1326 
1327   const diff_maps &maps = corp_diff->get_leaf_diffs();
1328   artifact_sptr_set_type* impacted_artifacts =
1329     maps.lookup_impacted_interfaces(d);
1330   if (impacted_artifacts == 0)
1331     return;
1332 
1333   if (impacted_artifacts->empty())
1334     return;
1335 
1336   vector<type_or_decl_base_sptr> sorted_impacted_interfaces;
1337   sort_artifacts_set(*impacted_artifacts, sorted_impacted_interfaces);
1338 
1339   size_t num_impacted_interfaces = impacted_artifacts->size();
1340   if (num_impacted_interfaces == 1)
1341     out << indent << "one impacted interface:\n";
1342   else
1343     out << indent << num_impacted_interfaces << " impacted interfaces:\n";
1344 
1345   string cur_indent = indent + "  ";
1346   vector<type_or_decl_base_sptr>::const_iterator it;
1347   for (it = sorted_impacted_interfaces.begin();
1348        it != sorted_impacted_interfaces.end();
1349        ++it)
1350     {
1351       out << cur_indent << get_pretty_representation(*it) << "\n";
1352     }
1353 }
1354 
1355 /// If a given diff node impacts some public interfaces, then report
1356 /// about those impacted interfaces on standard output.
1357 ///
1358 /// @param d the diff node to get the impacted interfaces for.
1359 ///
1360 /// @param out the output stream to report to.
1361 ///
1362 /// @param indent the white space string to use for indentation.
1363 void
maybe_report_interfaces_impacted_by_diff(const diff_sptr & d,ostream & out,const string & indent)1364 maybe_report_interfaces_impacted_by_diff(const diff_sptr	&d,
1365 					 ostream		&out,
1366 					 const string		&indent)
1367 {return maybe_report_interfaces_impacted_by_diff(d.get(), out, indent);}
1368 
1369 /// Tests if the diff node is to be reported.
1370 ///
1371 /// @param p the diff to consider.
1372 ///
1373 /// @return true iff the diff is to be reported.
1374 bool
diff_to_be_reported(const diff * d) const1375 reporter_base::diff_to_be_reported(const diff *d) const
1376 {return d && d->to_be_reported();}
1377 
1378 /// Report about data members replaced by an anonymous data member
1379 /// without changing the overall bit-layout of the class or union in
1380 /// an ABI-meaningful way.
1381 ///
1382 /// @param d the diff to consider.
1383 ///
1384 /// @param out the output stream to emit the change report to.
1385 ///
1386 /// @param indent the indentation string to use.
1387 void
maybe_report_data_members_replaced_by_anon_dm(const class_or_union_diff & d,ostream & out,const string indent)1388 maybe_report_data_members_replaced_by_anon_dm(const class_or_union_diff &d,
1389 					      ostream			&out,
1390 					      const string		indent)
1391 {
1392   const diff_context_sptr& ctxt = d.context();
1393 
1394   if ((ctxt->get_allowed_category() & HARMLESS_DATA_MEMBER_CHANGE_CATEGORY)
1395       && !d.data_members_replaced_by_adms().empty())
1396     {
1397       // Let's detect all the data members that are replaced by
1398       // members of the same anonymous data member and report them
1399       // in one go.
1400       for (changed_var_sptrs_type::const_iterator i =
1401 	     d.ordered_data_members_replaced_by_adms().begin();
1402 	   i != d.ordered_data_members_replaced_by_adms().end();)
1403 	{
1404 	  // This contains the data members replaced by the same
1405 	  // anonymous data member.
1406 	  vector<var_decl_sptr> dms_replaced_by_same_anon_dm;
1407 	  dms_replaced_by_same_anon_dm.push_back(i->first);
1408 	  // This contains the anonymous data member that replaced the
1409 	  // data members in the variable above.
1410 	  var_decl_sptr anonymous_data_member = i->second;
1411 	  // Let's look forward to see if the subsequent data
1412 	  // members were replaced by members of
1413 	  // anonymous_data_member.
1414 	  for (++i;
1415 	       i != d.ordered_data_members_replaced_by_adms().end()
1416 		 && *i->second == *anonymous_data_member;
1417 	       ++i)
1418 	    dms_replaced_by_same_anon_dm.push_back(i->first);
1419 
1420 	  bool several_data_members_replaced =
1421 	    dms_replaced_by_same_anon_dm.size() > 1;
1422 
1423 	  out << indent << "data member";
1424 	  if (several_data_members_replaced)
1425 	    out << "s";
1426 
1427 	  bool first_data_member = true;
1428 	  for (vector<var_decl_sptr>::const_iterator it =
1429 		 dms_replaced_by_same_anon_dm.begin();
1430 	       it != dms_replaced_by_same_anon_dm.end();
1431 	       ++it)
1432 	    {
1433 	      string name = (*it)->get_qualified_name();
1434 	      if (!first_data_member)
1435 		out << ",";
1436 	      out << " '" << name << "'";
1437 	      first_data_member = false;
1438 	    }
1439 
1440 	  if (several_data_members_replaced)
1441 	    out << " were ";
1442 	  else
1443 	    out << " was ";
1444 
1445 	  out << "replaced by anonymous data member:\n"
1446 	      << indent + "  "
1447 	      << "'"
1448 	      << anonymous_data_member->get_pretty_representation()
1449 	      << "'\n";
1450 	}
1451     }
1452 }
1453 
1454 } // Namespace comparison
1455 } // end namespace abigail
1456