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