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