[3/3] Bug 25661 - Support data member replacement by anonymous data member

Message ID 86a725a2b1.fsf@seketeli.org
State Committed
Headers
Series [1/3] {default, leaf}-reporter: group data members changes reports together |

Commit Message

Dodji Seketeli May 18, 2020, 11:51 a.m. UTC
  Hello,

We ought to detect when a data member is replaced by an anonymous data
member in a way that doesn't change the ABI in an incompatible way,
especially when that change is non equivocal.

For instance, consider this ABI-visible struct:

    struct S
    {
      int a;
    };

Now, consider that it's changed into:

    struct S
    {
      union
      {
	int a;
	char b;
      };
    };

Stricto sensu, the bit-layout of struct S doesn't change and so that
change isn't ABI-incompatible.

The current version of libabigail however flags that change as a
/potential/ issue and asks the user for further review.  It appears
that this class of changes is frequent enough to be annoying,
especially in semi-automatic ABI compliance checking setups where we
want the least possible "false positives".

This patch detects that kind of change patterns where a data member is
replaced by an anonymous data member in a benign way, in terms of ABI.

So now let's look at a more complicated example where an ABI-visible
type looks like:

struct S
{
  int a;
  int b;
  int c;
};

Now suppose that type was changed into:

struct S
{
  union
  {
    int tag[3];
    struct
    {
      int a;
      int b;
      int c;
    };
  };
};

The patch allows abidiff to recognise that kind of pattern, filter out
the detected change and report by default that the two binaries are
ABI compatible.

Here are the output that we'd get:

$ abidiff test-v0.o test-v1.o
Functions changes summary: 0 Removed, 0 Changed (1 filtered out), 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable

When asked to show the detailed of the filtered out changes, we get:

$ abidiff --harmless test-v0.o test-v1.o
Functions changes summary: 0 Removed, 1 Changed, 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable

1 function with some indirect sub-type change:

  [C] 'function void foo(S*)' at test-v1.cc:18:1 has some indirect sub-type changes:
    parameter 1 of type 'S*' has sub-type changes:
      in pointed to type 'struct S' at test-v1.cc:1:1:
        type size hasn't changed
        data members 'S::a', 'S::b', 'S::c' were replaced by anonymous data member:
          'union {int tag[3]; struct {int a; int b; int c;};}'

And using the leaf-node reporter, that would give:

$ abidiff --leaf-changes-only --harmless test-v0.o test-v1.o
Leaf changes summary: 1 artifact changed
Changed leaf types summary: 1 leaf type changed
Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable

'struct S at test-v0.cc:1:1' changed:
  type size hasn't changed
  data members 'S::a', 'S::b', 'S::c' were replaced by anonymous data member:
    'union {int tag[3]; struct {int a; int b; int c;};}'

	* include/abg-comp-filter.h (has_data_member_replaced_by_anon_dm):
	Declare new function.
	* include/abg-comparison.h (changed_var_sptr)
	(changed_var_sptrs_type): Declare new typedefs.
	(HARMLESS_DATA_MEMBER_CHANGE_CATEGORY): Add a new enumerator to
	the diff_category enum.
	(EVERYTHING_CATEGORY): In the diff_category, adjust this
	enumerator to OR the new HARMLESS_DATA_MEMBER_CHANGE_CATEGORY into
	it.
	(SUPPRESSED_CATEGORY, PRIVATE_TYPE_CATEGORY)
	(SIZE_OR_OFFSET_CHANGE_CATEGORY, VIRTUAL_MEMBER_CHANGE_CATEGORY)
	(CLASS_DECL_ONLY_DEF_CHANGE_CATEGORY)
	(FN_PARM_TYPE_TOP_CV_CHANGE_CATEGORY)
	(FN_RETURN_TYPE_CV_CHANGE_CATEGORY, VAR_TYPE_CV_CHANGE_CATEGORY)
	(VOID_PTR_TO_PTR_CHANGE_CATEGORY)
	(BENIGN_INFINITE_ARRAY_CHANGE_CATEGORY): Adjust the value of these
	enumerators of the diff_category enum.
	(class_or_union_diff::{data_members_replaced_by_adms,
	ordered_data_members_replaced_by_adms}): Declare new member
	functions.
	* include/abg-fwd.h (var_decl_wptr): Declare new typedef.
	(get_next_data_member, get_first_non_anonymous_data_member)
	(find_data_member_from_anonymous_data_member)
	(get_absolute_data_member_offset): Declare new functions.
	* include/abg-ir.h (struct anonymous_dm_hash): Declare new type.
	(anonymous_data_member_sptr_set_type): Declare new typedef.
	(class decl_base): Befriend class class_or_union.
	(class dm_context_rel): Pimpl-ify this class.
	(dm_context_rel::{g,s}et_anonymous_data_member_types): Declare new
	member functions.
	(var_decl::get_anon_dm_reliable_name): Declare new member
	function.
	(class var_decl): Make get_absolute_data_member_offset,
	get_absolute_data_member_offset be friends of this.
	(class_or_union::maybe_fixup_members_of_anon_data_member): Declare
	new protected member function.
	* src/abg-comp-filter.cc (has_data_member_replaced_by_anon_dm):
	Define new function.
	(categorize_harmless_diff_node): Use the above.
	* src/abg-comparison-priv.h
	(class_or_union_diff::priv::{dms_replaced_by_adms_,
	changed_var_sptrs_type dms_replaced_by_adms_ordered_}): Add new
	data members.
	(data_member_comp::compare_data_members): Factorize this out of ...
	(data_member_comp::operator()(decl_base_sptr&, decl_base_sptr&)):
	... this.
	(data_member_comp::operator()(changed_var_sptr&,
	changed_var_sptr&)): Add new member function.
	(sort_changed_data_members): Declare ...
	* src/abg-comparison.cc (sort_changed_data_members): ... new
	function.
	(get_default_harmless_categories_bitmap): Adjust to take the new
	abigail::comparison::HARMLESS_DATA_MEMBER_CHANGE_CATEGORY into
	account.
	(operator<<(ostream& o, diff_category c)): Likewise.
	(class_or_union_diff::ensure_lookup_tables_populated): Handle
	Handle the insertion of anonymous data members to replace existing
	data members.
	(class_or_union_diff::{data_members_replaced_by_adms,
	ordered_data_members_replaced_by_adms}): Define new accessors.
	(suppression_categorization_visitor::visit_end): Propagate the
	SUPPRESSION_CATEGORIZATION_VISITOR from changes to the type of the
	data member if the data member doesn't have real local changes.
	* src/abg-default-reporter.cc (default_reporter::report): Report
	about anonymous data members that replace data members.
	* src/abg-ir.cc (struct dm_context_rel::priv): Define new data
	structure.
	(dm_context_rel::{dm_context_rel, get_is_laid_out,
	set_is_laid_out, get_offset_in_bits, set_offset_in_bits,
	operator==, operator!=, get_anonymous_data_member,
	set_anonymous_data_member}): Define the member functions here as
	they are not inline anymore.
	(class_or_union::maybe_fixup_members_of_anon_data_member): Define
	new member function.
	(class_or_union::add_data_member): Use it.
	(get_first_non_anonymous_data_member, get_next_data_member)
	(get_absolute_data_member_offset)
	(find_data_member_from_anonymous_data_member): Define new
	functions.
	* src/abg-reporter-priv.h
	(maybe_report_data_members_replaced_by_anon_dm): Declare ...
	* src/abg-reporter-priv.cc
	(maybe_report_data_members_replaced_by_anon_dm): ... new function.
	* src/abg-leaf-reporter.cc (leaf_reporter::report): Report data
	members replaced by anonymous data members.
	* tests/data/test-diff-filter/test-PR25661-[1-6]-report-[1-4].txt: New
	test reference outputs.
	* tests/data/test-diff-filter/test-PR25661-[1-6]-v{0,1}.c: Test
	source code files.
	* tests/data/test-diff-filter/test-PR25661-[1-6]-v{0,1}.o: Test
	binary input files.
	* tests/data/Makefile.am: Add the new test files above to source
	distribution.
	* tests/test-diff-filter.cc (in_out_specs): Add the binary test
	inputs above to this test harness.
	* tests/data/test-diff-dwarf/test45-anon-dm-change-report-0.txt:
	Adjust.

Signed-off-by: Dodji Seketeli <dodji@redhat.com>

Applied to master.

---
 include/abg-comp-filter.h                          |   3 +
 include/abg-comparison.h                           |  48 +++-
 include/abg-fwd.h                                  |  18 +-
 include/abg-ir.h                                   |  65 +++--
 src/abg-comp-filter.cc                             |  22 ++
 src/abg-comparison-priv.h                          |  60 ++++-
 src/abg-comparison.cc                              | 262 ++++++++++++++++++--
 src/abg-default-reporter.cc                        |   4 +
 src/abg-ir.cc                                      | 270 ++++++++++++++++++++-
 src/abg-leaf-reporter.cc                           |   4 +
 src/abg-reporter-priv.cc                           |  85 ++++++-
 src/abg-reporter-priv.h                            |   6 +
 tests/data/Makefile.am                             |  48 ++++
 .../test45-anon-dm-change-report-0.txt             |   4 +-
 .../test-diff-filter/test-PR25661-1-report-1.txt   |   3 +
 .../test-diff-filter/test-PR25661-1-report-2.txt   |  12 +
 .../test-diff-filter/test-PR25661-1-report-3.txt   |   5 +
 .../test-diff-filter/test-PR25661-1-report-4.txt   |   9 +
 tests/data/test-diff-filter/test-PR25661-1-v0.c    |  12 +
 tests/data/test-diff-filter/test-PR25661-1-v0.o    | Bin 0 -> 2760 bytes
 tests/data/test-diff-filter/test-PR25661-1-v1.c    |  20 ++
 tests/data/test-diff-filter/test-PR25661-1-v1.o    | Bin 0 -> 2960 bytes
 .../test-diff-filter/test-PR25661-2-report-1.txt   |   3 +
 .../test-diff-filter/test-PR25661-2-report-2.txt   |  12 +
 .../test-diff-filter/test-PR25661-2-report-3.txt   |   5 +
 .../test-diff-filter/test-PR25661-2-report-4.txt   |   9 +
 tests/data/test-diff-filter/test-PR25661-2-v0.c    |  12 +
 tests/data/test-diff-filter/test-PR25661-2-v0.o    | Bin 0 -> 2760 bytes
 tests/data/test-diff-filter/test-PR25661-2-v1.c    |  22 ++
 tests/data/test-diff-filter/test-PR25661-2-v1.o    | Bin 0 -> 2968 bytes
 .../test-diff-filter/test-PR25661-3-report-1.txt   |   3 +
 .../test-diff-filter/test-PR25661-3-report-2.txt   |  12 +
 .../test-diff-filter/test-PR25661-3-report-3.txt   |   5 +
 .../test-diff-filter/test-PR25661-3-report-4.txt   |   9 +
 tests/data/test-diff-filter/test-PR25661-3-v0.c    |  12 +
 tests/data/test-diff-filter/test-PR25661-3-v0.o    | Bin 0 -> 2760 bytes
 tests/data/test-diff-filter/test-PR25661-3-v1.c    |  23 ++
 tests/data/test-diff-filter/test-PR25661-3-v1.o    | Bin 0 -> 2920 bytes
 .../test-diff-filter/test-PR25661-4-report-1.txt   |   3 +
 .../test-diff-filter/test-PR25661-4-report-2.txt   |  12 +
 .../test-diff-filter/test-PR25661-4-report-3.txt   |   5 +
 .../test-diff-filter/test-PR25661-4-report-4.txt   |   9 +
 tests/data/test-diff-filter/test-PR25661-4-v0.c    |  15 ++
 tests/data/test-diff-filter/test-PR25661-4-v0.o    | Bin 0 -> 3400 bytes
 tests/data/test-diff-filter/test-PR25661-4-v1.c    |  21 ++
 tests/data/test-diff-filter/test-PR25661-4-v1.o    | Bin 0 -> 3480 bytes
 .../test-diff-filter/test-PR25661-5-report-1.txt   |   3 +
 .../test-diff-filter/test-PR25661-5-report-2.txt   |  12 +
 .../test-diff-filter/test-PR25661-5-report-3.txt   |   5 +
 .../test-diff-filter/test-PR25661-5-report-4.txt   |   9 +
 tests/data/test-diff-filter/test-PR25661-5-v0.c    |  14 ++
 tests/data/test-diff-filter/test-PR25661-5-v0.o    | Bin 0 -> 3152 bytes
 tests/data/test-diff-filter/test-PR25661-5-v1.c    |  25 ++
 tests/data/test-diff-filter/test-PR25661-5-v1.o    | Bin 0 -> 3280 bytes
 .../test-diff-filter/test-PR25661-6-report-1.txt   |   3 +
 .../test-diff-filter/test-PR25661-6-report-2.txt   |   5 +
 .../test-diff-filter/test-PR25661-6-report-3.txt   |  12 +
 .../test-diff-filter/test-PR25661-6-report-4.txt   |   9 +
 tests/data/test-diff-filter/test-PR25661-6-v0.c    |  10 +
 tests/data/test-diff-filter/test-PR25661-6-v0.o    | Bin 0 -> 2656 bytes
 tests/data/test-diff-filter/test-PR25661-6-v1.c    |  14 ++
 tests/data/test-diff-filter/test-PR25661-6-v1.o    | Bin 0 -> 2792 bytes
 tests/test-diff-filter.cc                          | 168 +++++++++++++
 63 files changed, 1361 insertions(+), 80 deletions(-)
 create mode 100644 tests/data/test-diff-filter/test-PR25661-1-report-1.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-1-report-2.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-1-report-3.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-1-report-4.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-1-v0.c
 create mode 100644 tests/data/test-diff-filter/test-PR25661-1-v0.o
 create mode 100644 tests/data/test-diff-filter/test-PR25661-1-v1.c
 create mode 100644 tests/data/test-diff-filter/test-PR25661-1-v1.o
 create mode 100644 tests/data/test-diff-filter/test-PR25661-2-report-1.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-2-report-2.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-2-report-3.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-2-report-4.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-2-v0.c
 create mode 100644 tests/data/test-diff-filter/test-PR25661-2-v0.o
 create mode 100644 tests/data/test-diff-filter/test-PR25661-2-v1.c
 create mode 100644 tests/data/test-diff-filter/test-PR25661-2-v1.o
 create mode 100644 tests/data/test-diff-filter/test-PR25661-3-report-1.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-3-report-2.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-3-report-3.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-3-report-4.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-3-v0.c
 create mode 100644 tests/data/test-diff-filter/test-PR25661-3-v0.o
 create mode 100644 tests/data/test-diff-filter/test-PR25661-3-v1.c
 create mode 100644 tests/data/test-diff-filter/test-PR25661-3-v1.o
 create mode 100644 tests/data/test-diff-filter/test-PR25661-4-report-1.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-4-report-2.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-4-report-3.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-4-report-4.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-4-v0.c
 create mode 100644 tests/data/test-diff-filter/test-PR25661-4-v0.o
 create mode 100644 tests/data/test-diff-filter/test-PR25661-4-v1.c
 create mode 100644 tests/data/test-diff-filter/test-PR25661-4-v1.o
 create mode 100644 tests/data/test-diff-filter/test-PR25661-5-report-1.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-5-report-2.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-5-report-3.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-5-report-4.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-5-v0.c
 create mode 100644 tests/data/test-diff-filter/test-PR25661-5-v0.o
 create mode 100644 tests/data/test-diff-filter/test-PR25661-5-v1.c
 create mode 100644 tests/data/test-diff-filter/test-PR25661-5-v1.o
 create mode 100644 tests/data/test-diff-filter/test-PR25661-6-report-1.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-6-report-2.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-6-report-3.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-6-report-4.txt
 create mode 100644 tests/data/test-diff-filter/test-PR25661-6-v0.c
 create mode 100644 tests/data/test-diff-filter/test-PR25661-6-v0.o
 create mode 100644 tests/data/test-diff-filter/test-PR25661-6-v1.c
 create mode 100644 tests/data/test-diff-filter/test-PR25661-6-v1.o
  

Patch

diff --git a/include/abg-comp-filter.h b/include/abg-comp-filter.h
index b8da315..8113003 100644
--- a/include/abg-comp-filter.h
+++ b/include/abg-comp-filter.h
@@ -89,6 +89,9 @@  has_anonymous_data_member_change(const diff *d);
 bool
 has_anonymous_data_member_change(const diff_sptr &d);
 
+bool
+has_data_member_replaced_by_anon_dm(const diff* diff);
+
 struct filter_base;
 /// Convenience typedef for a shared pointer to filter_base
 typedef shared_ptr<filter_base> filter_base_sptr;
diff --git a/include/abg-comparison.h b/include/abg-comparison.h
index cf95624..bb510a1 100644
--- a/include/abg-comparison.h
+++ b/include/abg-comparison.h
@@ -257,6 +257,15 @@  typedef unordered_map<string, var_decl*> string_var_ptr_map;
 /// the changed variable.
 typedef std::pair<var_decl*, var_decl*> changed_var_ptr;
 
+/// Convenience typedef for a pair of @ref var_decl_sptr representing
+/// a @ref var_decl change.  The first member of the pair represents
+/// the initial variable and the second member represents the changed
+/// variable.
+typedef std::pair<var_decl_sptr, var_decl_sptr> changed_var_sptr;
+
+/// Convenience typedef for a vector of @changed_var_sptr.gg381
+typedef vector<changed_var_sptr> changed_var_sptrs_type;
+
 /// Convenience typedef for a map whose key is a string and whose
 /// value is an @ref elf_symbol_sptr.
 typedef unordered_map<string, elf_symbol_sptr> string_elf_symbol_map;
@@ -375,61 +384,69 @@  enum diff_category
   /// alias change that is harmless.
   HARMLESS_SYMBOL_ALIAS_CHANGE_CATEORY = 1 << 6,
 
+  /// This means that a diff node in the sub-tree carries a harmless
+  /// union change.
   HARMLESS_UNION_CHANGE_CATEGORY = 1 << 7,
 
+  /// This means that a diff node in the sub-tree carries a harmless
+  /// data member change.  An example of harmless data member change
+  /// is an anonymous data member that replaces a given data member
+  /// without locally changing the layout.
+  HARMLESS_DATA_MEMBER_CHANGE_CATEGORY = 1 << 8,
+
   /// This means that a diff node was marked as suppressed by a
   /// user-provided suppression specification.
-  SUPPRESSED_CATEGORY = 1 << 8,
+  SUPPRESSED_CATEGORY = 1 << 9,
 
   /// This means that a diff node was warked as being for a private
   /// type.  That is, the diff node is meant to be suppressed by a
   /// suppression specification that was auto-generated to filter out
   /// changes to private types.
-  PRIVATE_TYPE_CATEGORY = 1 << 9,
+  PRIVATE_TYPE_CATEGORY = 1 << 10,
 
   /// This means the diff node (or at least one of its descendant
   /// nodes) carries a change that modifies the size of a type or an
   /// offset of a type member.  Removal or changes of enumerators in a
   /// enum fall in this category too.
-  SIZE_OR_OFFSET_CHANGE_CATEGORY = 1 << 10,
+  SIZE_OR_OFFSET_CHANGE_CATEGORY = 1 << 11,
 
   /// This means that a diff node in the sub-tree carries an
   /// incompatible change to a vtable.
-  VIRTUAL_MEMBER_CHANGE_CATEGORY = 1 << 11,
+  VIRTUAL_MEMBER_CHANGE_CATEGORY = 1 << 12,
 
   /// A diff node in this category is redundant.  That means it's
   /// present as a child of a other nodes in the diff tree.
-  REDUNDANT_CATEGORY = 1 << 12,
+  REDUNDANT_CATEGORY = 1 << 13,
 
   /// This means that a diff node in the sub-tree carries a class type
   /// that was declaration-only and that is now defined, or vice
   /// versa.
-  CLASS_DECL_ONLY_DEF_CHANGE_CATEGORY = 1 << 13,
+  CLASS_DECL_ONLY_DEF_CHANGE_CATEGORY = 1 << 14,
 
   /// A diff node in this category is a function parameter type which
   /// top cv-qualifiers change.
-  FN_PARM_TYPE_TOP_CV_CHANGE_CATEGORY = 1 << 14,
+  FN_PARM_TYPE_TOP_CV_CHANGE_CATEGORY = 1 << 15,
 
   /// A diff node in this category has a function parameter type with a
   /// cv-qualifiers change.
-  FN_PARM_TYPE_CV_CHANGE_CATEGORY = 1 << 15,
+  FN_PARM_TYPE_CV_CHANGE_CATEGORY = 1 << 16,
 
   /// A diff node in this category is a function return type with a
   /// cv-qualifier change.
-  FN_RETURN_TYPE_CV_CHANGE_CATEGORY = 1 << 16,
+  FN_RETURN_TYPE_CV_CHANGE_CATEGORY = 1 << 17,
 
   /// A diff node in this category is for a variable which type holds
   /// a cv-qualifier change.
-  VAR_TYPE_CV_CHANGE_CATEGORY = 1 << 17,
+  VAR_TYPE_CV_CHANGE_CATEGORY = 1 << 18,
 
   /// A diff node in this category carries a change from void pointer
   /// to non-void pointer.
-  VOID_PTR_TO_PTR_CHANGE_CATEGORY = 1 << 18,
+  VOID_PTR_TO_PTR_CHANGE_CATEGORY = 1 << 19,
 
   /// A diff node in this category carries a change in the size of the
   /// array type of a global variable, but the ELF size of the
   /// variable didn't change.
-  BENIGN_INFINITE_ARRAY_CHANGE_CATEGORY = 1 << 19,
+  BENIGN_INFINITE_ARRAY_CHANGE_CATEGORY = 1 << 20,
   /// A special enumerator that is the logical 'or' all the
   /// enumerators above.
   ///
@@ -444,6 +461,7 @@  enum diff_category
   | HARMLESS_ENUM_CHANGE_CATEGORY
   | HARMLESS_SYMBOL_ALIAS_CHANGE_CATEORY
   | HARMLESS_UNION_CHANGE_CATEGORY
+  | HARMLESS_DATA_MEMBER_CHANGE_CATEGORY
   | SUPPRESSED_CATEGORY
   | PRIVATE_TYPE_CATEGORY
   | SIZE_OR_OFFSET_CHANGE_CATEGORY
@@ -1610,6 +1628,12 @@  public:
   size_t
   count_filtered_subtype_changed_data_members(bool local_only = false) const;
 
+  const string_decl_base_sptr_map&
+  data_members_replaced_by_adms() const;
+
+  const changed_var_sptrs_type&
+  ordered_data_members_replaced_by_adms() const;
+
   const edit_script&
   member_fn_tmpls_changes() const;
 
diff --git a/include/abg-fwd.h b/include/abg-fwd.h
index 90a8114..f6e0c5b 100644
--- a/include/abg-fwd.h
+++ b/include/abg-fwd.h
@@ -223,6 +223,9 @@  class var_decl;
 /// Convenience typedef for a shared pointer on a @ref var_decl
 typedef shared_ptr<var_decl> var_decl_sptr;
 
+/// Convenience typedef for a weak pointer on a @ref var_decl
+typedef weak_ptr<var_decl> var_decl_wptr;
+
 typedef unordered_map<interned_string,
 		      var_decl*,
 		      hash_interned_string> istring_var_decl_ptr_map_type;
@@ -614,6 +617,9 @@  is_data_member(const decl_base *);
 var_decl*
 is_data_member(const decl_base *);
 
+const var_decl_sptr
+get_next_data_member(const class_or_union_sptr&, const var_decl_sptr&);
+
 bool
 is_anonymous_data_member(const decl_base&);
 
@@ -638,7 +644,14 @@  is_anonymous_data_member(const var_decl*);
 bool
 is_anonymous_data_member(const var_decl&);
 
-const class_or_union*
+const var_decl_sptr
+get_first_non_anonymous_data_member(const var_decl_sptr);
+
+var_decl_sptr
+find_data_member_from_anonymous_data_member(const var_decl_sptr&,
+					    const string&);
+
+class_or_union*
 anonymous_data_member_to_class_or_union(const var_decl*);
 
 class_or_union_sptr
@@ -678,6 +691,9 @@  uint64_t
 get_data_member_offset(const decl_base_sptr);
 
 uint64_t
+get_absolute_data_member_offset(const var_decl&);
+
+uint64_t
 get_var_size_in_bits(const var_decl_sptr&);
 
 void
diff --git a/include/abg-ir.h b/include/abg-ir.h
index 15b0b40..ebcc4b8 100644
--- a/include/abg-ir.h
+++ b/include/abg-ir.h
@@ -1601,8 +1601,8 @@  public:
   friend void
   set_member_function_is_virtual(function_decl&, bool);
 
+  friend class class_or_union;
   friend class class_decl;
-
   friend class scope_decl;
 };// end class decl_base
 
@@ -2637,61 +2637,45 @@  public:
 class dm_context_rel : public context_rel
 {
 protected:
-  bool is_laid_out_;
-  size_t offset_in_bits_;
+  struct priv;
+  typedef shared_ptr<priv> priv_sptr;
+
+  priv_sptr priv_;
 
 public:
-  dm_context_rel()
-    : context_rel(),
-      is_laid_out_(!is_static_),
-      offset_in_bits_(0)
-  {}
+  dm_context_rel();
 
   dm_context_rel(scope_decl* s,
 		 bool is_laid_out,
 		 size_t offset_in_bits,
 		 access_specifier a,
-		 bool is_static)
-    : context_rel(s, a, is_static),
-      is_laid_out_(is_laid_out),
-      offset_in_bits_(offset_in_bits)
-  {}
+		 bool is_static);
 
-  dm_context_rel(scope_decl* s)
-    : context_rel(s),
-      is_laid_out_(!is_static_),
-      offset_in_bits_(0)
-  {}
+  dm_context_rel(scope_decl* s);
 
   bool
-  get_is_laid_out() const
-  {return is_laid_out_;}
+  get_is_laid_out() const;
 
   void
-  set_is_laid_out(bool f)
-  {is_laid_out_ = f;}
+  set_is_laid_out(bool f);
 
   size_t
-  get_offset_in_bits() const
-  {return offset_in_bits_;}
+  get_offset_in_bits() const;
 
   void
-  set_offset_in_bits(size_t o)
-  {offset_in_bits_ = o;}
+  set_offset_in_bits(size_t o);
 
-  bool
-  operator==(const dm_context_rel& o) const
-  {
-    if (!context_rel::operator==(o))
-      return false;
+  const var_decl*
+  get_anonymous_data_member() const;
 
-    return (is_laid_out_ == o.is_laid_out_
-	    && offset_in_bits_ == o.offset_in_bits_);
-  }
+  void
+  set_anonymous_data_member(var_decl *);
 
   bool
-  operator!=(const dm_context_rel& o) const
-  {return !operator==(o);}
+  operator==(const dm_context_rel& o) const;
+
+  bool
+  operator!=(const dm_context_rel& o) const;
 
   virtual ~dm_context_rel();
 };// end class class_decl::dm_context_rel
@@ -2783,6 +2767,12 @@  public:
   friend uint64_t
   get_data_member_offset(const var_decl& m);
 
+  friend uint64_t
+  get_absolute_data_member_offset(const var_decl& m);
+
+  friend uint64_t
+  get_absolute_data_member_offset(const var_decl_sptr& m);
+
   friend void
   set_data_member_is_laid_out(var_decl_sptr m, bool l);
 
@@ -3731,6 +3721,9 @@  protected:
   virtual void
   remove_member_decl(decl_base_sptr);
 
+  void
+  maybe_fixup_members_of_anon_data_member(var_decl_sptr& anon_dm);
+
 public:
   /// Hasher.
   struct hash;
diff --git a/src/abg-comp-filter.cc b/src/abg-comp-filter.cc
index 75df901..67c2a18 100644
--- a/src/abg-comp-filter.cc
+++ b/src/abg-comp-filter.cc
@@ -533,6 +533,25 @@  non_static_data_member_added_or_removed(const diff* diff)
     (dynamic_cast<const class_diff*>(diff));
 }
 
+/// Test if a @ref class_or_union_diff has a data member replaced by
+/// an anonymous data member in a harmless way.  That means, the new
+/// anonymous data member somehow contains the replaced data member
+/// and it doesn't break the layout of the containing class.
+///
+/// @param diff the diff node to consider.
+///
+/// @return true iff the @ref class_or_union_diff has a data member
+/// harmlessly replaced by an anonymous data member.
+bool
+has_data_member_replaced_by_anon_dm(const diff* diff)
+{
+  const class_or_union_diff *c = is_class_or_union_diff(diff);
+
+  if (!c)
+    return false;
+  return !c->data_members_replaced_by_adms().empty();
+}
+
 /// Test if a class_diff node has static members added or removed.
 ///
 /// @param diff the diff node to consider.
@@ -1521,6 +1540,9 @@  categorize_harmless_diff_node(diff *d, bool pre)
 	  || static_data_member_type_size_changed(f, s))
 	category |= STATIC_DATA_MEMBER_CHANGE_CATEGORY;
 
+      if (has_data_member_replaced_by_anon_dm(d))
+	category |= HARMLESS_DATA_MEMBER_CHANGE_CATEGORY;
+
       if ((has_enumerator_insertion(d)
 	   && !has_harmful_enum_change(d))
 	  || has_harmless_enum_to_int_change(d))
diff --git a/src/abg-comparison-priv.h b/src/abg-comparison-priv.h
index 565f024..420e701 100644
--- a/src/abg-comparison-priv.h
+++ b/src/abg-comparison-priv.h
@@ -502,6 +502,13 @@  struct class_or_union_diff::priv
   // member bar at offset N.
   unsigned_var_diff_sptr_map changed_dm_;
   var_diff_sptrs_type sorted_changed_dm_;
+
+  // This is a data structure to represent data members that have been
+  // replaced by anonymous data members.  It's a map that associates
+  // the name of the data member to the anonymous data member that
+  // replaced it.
+  string_decl_base_sptr_map dms_replaced_by_adms_;
+  mutable changed_var_sptrs_type dms_replaced_by_adms_ordered_;
   string_member_function_sptr_map deleted_member_functions_;
   string_member_function_sptr_map inserted_member_functions_;
   string_function_decl_diff_sptr_map changed_member_functions_;
@@ -549,18 +556,18 @@  struct class_or_union_diff::priv
 /// offset.
 struct data_member_comp
 {
-  /// @param f the first data member to take into account.
+
+  /// Compare two data members.
   ///
-  /// @param s the second data member to take into account.
+  /// First look at their offset and then their name.
   ///
-  /// @return true iff f is before s.
+  /// @parm first_dm the first data member to consider.
+  ///
+  /// @param second_dm the second data member to consider.
   bool
-  operator()(const decl_base_sptr& f,
-	     const decl_base_sptr& s) const
+  compare_data_members(const var_decl_sptr& first_dm,
+		       const var_decl_sptr& second_dm) const
   {
-    var_decl_sptr first_dm = is_data_member(f);
-    var_decl_sptr second_dm = is_data_member(s);
-
     ABG_ASSERT(first_dm);
     ABG_ASSERT(second_dm);
 
@@ -578,6 +585,40 @@  struct data_member_comp
     // sort them lexicographically.
     return first_dm_name < second_dm_name;
   }
+
+  /// Compare two data members.
+  ///
+  /// First look at their offset and then their name.
+  ///
+  /// @parm first_dm the first data member to consider.
+  ///
+  /// @param second_dm the second data member to consider.
+  bool
+  operator()(const decl_base_sptr& f,
+	     const decl_base_sptr& s) const
+  {
+    var_decl_sptr first_dm = is_data_member(f);
+    var_decl_sptr second_dm = is_data_member(s);
+
+    return compare_data_members(first_dm, second_dm);
+  }
+
+  /// Compare two data members.
+  ///
+  /// First look at their offset and then their name.
+  ///
+  /// @parm first_dm the first data member to consider.
+  ///
+  /// @param second_dm the second data member to consider.
+  bool
+  operator()(const changed_var_sptr& f,
+	     const changed_var_sptr& s) const
+  {
+    var_decl_sptr first_dm = is_data_member(is_decl(f.first));
+    var_decl_sptr second_dm = is_data_member(is_decl(s.first));
+
+    return compare_data_members(first_dm, second_dm);
+  }
 };//end struct data_member_comp
 
 /// The type of the private data (pimpl sub-object) of the @ref
@@ -1347,6 +1388,9 @@  sort_data_members(const string_decl_base_sptr_map &data_members,
 		  vector<decl_base_sptr>& sorted);
 
 void
+sort_changed_data_members(changed_var_sptrs_type& input);
+
+void
 sort_string_function_ptr_map(const string_function_ptr_map& map,
 			     vector<function_decl*>& sorted);
 
diff --git a/src/abg-comparison.cc b/src/abg-comparison.cc
index e1689e5..7305a71 100644
--- a/src/abg-comparison.cc
+++ b/src/abg-comparison.cc
@@ -171,8 +171,18 @@  sort_data_members(const string_decl_base_sptr_map &data_members,
   std::sort(sorted.begin(), sorted.end(), comp);
 }
 
-/// Sort a an instance of @ref string_function_ptr_map map and stuff
-/// a resulting sorted vector of pointers to function_decl.
+/// Sort (in place) a vector of changed data members.
+///
+/// @param to_sort the vector to sort.
+void
+sort_changed_data_members(changed_var_sptrs_type& to_sort)
+{
+  data_member_comp comp;
+  std::sort(to_sort.begin(), to_sort.end(), comp);
+}
+
+/// Sort an instance of @ref string_function_ptr_map map and stuff a
+/// resulting sorted vector of pointers to function_decl.
 ///
 /// @param map the map to sort.
 ///
@@ -2946,6 +2956,7 @@  get_default_harmless_categories_bitmap()
 	  | abigail::comparison::HARMLESS_ENUM_CHANGE_CATEGORY
 	  | abigail::comparison::HARMLESS_SYMBOL_ALIAS_CHANGE_CATEORY
 	  | abigail::comparison::HARMLESS_UNION_CHANGE_CATEGORY
+	  | abigail::comparison::HARMLESS_DATA_MEMBER_CHANGE_CATEGORY
 	  | abigail::comparison::CLASS_DECL_ONLY_DEF_CHANGE_CATEGORY
 	  | abigail::comparison::FN_PARM_TYPE_TOP_CV_CHANGE_CATEGORY
 	  | abigail::comparison::FN_PARM_TYPE_CV_CHANGE_CATEGORY
@@ -3033,6 +3044,14 @@  operator<<(ostream& o, diff_category c)
       emitted_a_category |= true;
     }
 
+    if (c & HARMLESS_DATA_MEMBER_CHANGE_CATEGORY)
+    {
+      if (emitted_a_category)
+	o << "|";
+      o << "HARMLESS_DATA_MEMBER_CHANGE_CATEGORY";
+      emitted_a_category |= true;
+    }
+
   if (c & HARMLESS_SYMBOL_ALIAS_CHANGE_CATEORY)
     {
       if (emitted_a_category)
@@ -4688,21 +4707,179 @@  class_or_union_diff::ensure_lookup_tables_populated(void) const
 	    var_decl_sptr added_dm = is_var_decl(d);
 	    string name = added_dm->get_anon_dm_reliable_name();
 	    ABG_ASSERT(priv_->inserted_data_members_.find(name)
-		   == priv_->inserted_data_members_.end());
-	    string_decl_base_sptr_map::const_iterator j =
-	      priv_->deleted_data_members_.find(name);
-	    if (j != priv_->deleted_data_members_.end())
+		       == priv_->inserted_data_members_.end());
+
+	    bool ignore_added_anonymous_data_member = false;
+	    if (is_anonymous_data_member(added_dm))
 	      {
-		if (*j->second != *d)
+		//
+		// Handle insertion of anonymous data member to
+		// replace existing data members.
+		//
+		// For instance consider this:
+		//   struct S
+		//   {
+		//     int a;
+		//     int b;
+		//     int c;
+		//   };// end struct S
+		//
+		//   Where the data members 'a' and 'b' are replaced
+		//   by an anonymous data member without changing the
+		//   effective bit layout of the structure:
+		//
+		//   struct S
+		//   {
+		//     struct
+		//     {
+		//       union
+		//       {
+		//         int a;
+		//         char a_1;
+		//       };
+		//       union
+		//       {
+		//         int b;
+		//         char b_1;
+		//       };
+		//     };
+		//     int c;
+		//   }; // end struct S
+		//
+		var_decl_sptr replaced_dm, replacing_dm;
+		bool added_anon_dm_changes_dm = false;
+		// The vector of data members replaced by anonymous
+		// data members.
+		vector<var_decl_sptr> dms_replaced_by_anon_dm;
+
+		//
+		// Let's start collecting the set of data members
+		// which have been replaced by anonymous types in a
+		// harmless way.  These are going to be collected into
+		// dms_replaced_by_anon_dm and, ultimately, into
+		// priv_->dms_replaced_by_adms_
+		//
+		for (string_decl_base_sptr_map::const_iterator it =
+		       priv_->deleted_data_members_.begin();
+		     it != priv_->deleted_data_members_.end();
+		     ++it)
 		  {
-		    var_decl_sptr old_dm = is_var_decl(j->second);
-		    priv_->subtype_changed_dm_[name]=
-		      compute_diff(old_dm, added_dm, context());
+		    // We don't support this pattern for anonymous
+		    // data members themselves being replaced.  If
+		    // that occurs then we'll just report it verbatim.
+		    if (is_anonymous_data_member(it->second))
+		      continue;
+
+		    string deleted_dm_name = it->second->get_name();
+		    if ((replacing_dm =
+			 find_data_member_from_anonymous_data_member(added_dm,
+								     deleted_dm_name)))
+		      {
+			// So it looks like replacing_dm might have
+			// replaced the data member which name is
+			// 'deleted_dm_name'.  Let's look deeper to be
+			// sure.
+			//
+			// Note that replacing_dm is part (member) of
+			// an anonymous data member that might replace
+			// replaced_dm.
+
+			// So let's get that replaced data member.
+			replaced_dm = is_var_decl(it->second);
+			size_t replaced_dm_offset =
+			  get_data_member_offset(replaced_dm),
+			replacing_dm_offset =
+			  get_absolute_data_member_offset(replacing_dm);
+
+			if (replaced_dm_offset != replacing_dm_offset)
+			  {
+			    // So the replacing data member and the
+			    // replaced data member don't have the
+			    // same offset.  This is not the pattern we
+			    // are looking for.  Rather, it looks like
+			    // the anonymous data member has *changed*
+			    // the data member.
+			    added_anon_dm_changes_dm = true;
+			    break;
+			  }
+
+			if (replaced_dm->get_type()->get_size_in_bits()
+			    == replaced_dm->get_type()->get_size_in_bits())
+			  dms_replaced_by_anon_dm.push_back(replaced_dm);
+			else
+			  {
+			    added_anon_dm_changes_dm = true;
+			    break;
+			  }
+		      }
+		  }
+
+		// Now walk dms_replaced_by_anon_dm to fill up
+		// priv_->dms_replaced_by_adms_ with the set of data
+		// members replaced by anonymous data members.
+		if (!added_anon_dm_changes_dm
+		    && !dms_replaced_by_anon_dm.empty())
+		  {
+		    // See if the added data member isn't too big.
+		    type_base_sptr added_dm_type = added_dm->get_type();
+		    ABG_ASSERT(added_dm_type);
+		    var_decl_sptr new_next_dm =
+		      get_next_data_member(second_class_or_union(),
+					   added_dm);
+		    var_decl_sptr old_next_dm =
+		      first_class_or_union()->find_data_member(new_next_dm);
+
+		    if (!old_next_dm
+			|| (old_next_dm
+			    && (get_absolute_data_member_offset(old_next_dm)
+				== get_absolute_data_member_offset(new_next_dm))))
+		      {
+			// None of the data members that are replaced
+			// by the added union should be considered as
+			// having been deleted.
+			ignore_added_anonymous_data_member = true;
+			for (vector<var_decl_sptr>::const_iterator i =
+			       dms_replaced_by_anon_dm.begin();
+			     i != dms_replaced_by_anon_dm.end();
+			     ++i)
+			  {
+			    string n = (*i)->get_name();
+			    priv_->dms_replaced_by_adms_[n] =
+			      added_dm;
+			    priv_->deleted_data_members_.erase(n);
+			  }
+		      }
 		  }
-		priv_->deleted_data_members_.erase(j);
 	      }
-	    else
-	      priv_->inserted_data_members_[name] = d;
+
+	    if (!ignore_added_anonymous_data_member)
+	      {
+		// Detect changed data members.
+		//
+		// A changed data member (that we shall name D) is a data
+		// member that satisfies the conditions below:
+		//
+		// 1/ It must have been added.
+		//
+		// 2/ It must have been deleted as well.
+		//
+		// 3/ It there must be a non-empty difference between the
+		// deleted D and the added D.
+		string_decl_base_sptr_map::const_iterator j =
+		  priv_->deleted_data_members_.find(name);
+		if (j != priv_->deleted_data_members_.end())
+		  {
+		    if (*j->second != *d)
+		      {
+			var_decl_sptr old_dm = is_var_decl(j->second);
+			priv_->subtype_changed_dm_[name]=
+			  compute_diff(old_dm, added_dm, context());
+		      }
+		    priv_->deleted_data_members_.erase(j);
+		  }
+		else
+		  priv_->inserted_data_members_[name] = d;
+	      }
 	  }
       }
 
@@ -4987,6 +5164,47 @@  size_t
 class_or_union_diff::count_filtered_subtype_changed_data_members(bool local) const
 {return get_priv()->count_filtered_subtype_changed_dm(local);}
 
+/// Get the map of data members that got replaced by anonymous data
+/// members.
+///
+/// The key of a map entry is the name of the replaced data member and
+/// the value is the anonymous data member that replaces it.
+///
+/// @return the map of data members replaced by anonymous data
+/// members.
+const string_decl_base_sptr_map&
+class_or_union_diff::data_members_replaced_by_adms() const
+{return get_priv()->dms_replaced_by_adms_;}
+
+/// Get an ordered vector of of data members that got replaced by
+/// anonymous data members.
+///
+/// This returns a vector of pair of two data members: the one that
+/// was replaced, and the anonymous data member that replaced it.
+///
+/// @return the sorted vector data members replaced by anonymous data members.
+const changed_var_sptrs_type&
+class_or_union_diff::ordered_data_members_replaced_by_adms() const
+{
+  if (priv_->dms_replaced_by_adms_ordered_.empty())
+    {
+      for (string_decl_base_sptr_map::const_iterator it =
+	     priv_->dms_replaced_by_adms_.begin();
+	   it != priv_->dms_replaced_by_adms_.end();
+	   ++it)
+	{
+	  const var_decl_sptr dm =
+	    first_class_or_union()->find_data_member(it->first);
+	  ABG_ASSERT(dm);
+	  changed_var_sptr changed_dm(dm, is_data_member(it->second));
+	  priv_->dms_replaced_by_adms_ordered_.push_back(changed_dm);
+	}
+      sort_changed_data_members(priv_->dms_replaced_by_adms_ordered_);
+    }
+
+  return priv_->dms_replaced_by_adms_ordered_;
+}
+
 /// @return the edit script of the member function templates of the two
 /// @ref class_or_union.
 const edit_script&
@@ -11424,8 +11642,8 @@  void
 propagate_categories(corpus_diff_sptr diff_tree)
 {propagate_categories(diff_tree.get());}
 
-/// A tree node visitor that knows how to categorizes a given in the
-/// SUPPRESSED_CATEGORY category and how to propagate that
+/// A tree node visitor that knows how to categorizes a given diff
+/// node in the SUPPRESSED_CATEGORY category and how to propagate that
 /// categorization.
 struct suppression_categorization_visitor : public diff_node_visitor
 {
@@ -11501,6 +11719,12 @@  struct suppression_categorization_visitor : public diff_node_visitor
 	// Note that all pointer/reference diff node changes are
 	// potentially considered local, i.e, local changes of the
 	// pointed-to-type are considered local to the pointer itself.
+	//
+	// Similarly, changes local to the type of function parameters,
+	// variables (and data members) and classe (that are not of
+	// LOCAL_NON_TYPE_CHANGE_KIND kind) and that have been
+	// suppressed can propagate their SUPPRESSED_CATEGORY-ness to
+	// those kinds of diff node.
 	!(d->get_category() & SUPPRESSED_CATEGORY)
 	&& (!d->has_local_changes()
 	    || is_pointer_diff(d)
@@ -11512,6 +11736,10 @@  struct suppression_categorization_visitor : public diff_node_visitor
 	    || (is_fn_parm_diff(d)
 		&& (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND)))
 	    || (is_function_type_diff(d)
+		&& (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND)))
+	    || (is_var_diff(d)
+		&& (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND)))
+	    ||  (is_class_diff(d)
 		&& (!(d->has_local_changes() & LOCAL_NON_TYPE_CHANGE_KIND)))))
       {
 	// Note that we handle private diff nodes differently from
@@ -11544,8 +11772,8 @@  struct suppression_categorization_visitor : public diff_node_visitor
 		  has_private_child = true;
 		else if (child->get_class_of_equiv_category()
 			 & SUPPRESSED_CATEGORY)
-		  // Propagation of the SUPPRESSED_CATEGORY is going
-		  // to be handled later below.
+		  // Propagation of the SUPPRESSED_CATEGORY has been
+		  // handled above already.
 		  ;
 		else
 		  has_non_private_child = true;
diff --git a/src/abg-default-reporter.cc b/src/abg-default-reporter.cc
index b59e8d6..80cb663 100644
--- a/src/abg-default-reporter.cc
+++ b/src/abg-default-reporter.cc
@@ -1016,6 +1016,10 @@  default_reporter::report(const class_or_union_diff& d,
 	    if ((*it)->to_be_reported())
 	      represent(*it, ctxt, out, indent + "  ");
 	}
+
+      // Report about data members replaced by an anonymous union data
+      // member.
+      maybe_report_data_members_replaced_by_anon_dm(d, out, indent);
     }
 
   // member types
diff --git a/src/abg-ir.cc b/src/abg-ir.cc
index 85c27e3..aefbd08 100644
--- a/src/abg-ir.cc
+++ b/src/abg-ir.cc
@@ -2537,8 +2537,98 @@  elf_symbol::version::operator=(const elf_symbol::version& o)
 
 // </elf_symbol stuff>
 
+// <class dm_context_rel stuff>
+struct dm_context_rel::priv
+{
+  bool is_laid_out_;
+  size_t offset_in_bits_;
+  var_decl* anonymous_data_member_;
+
+  priv(bool is_static = false)
+    : is_laid_out_(!is_static),
+      offset_in_bits_(0),
+      anonymous_data_member_()
+  {}
+
+  priv(bool is_laid_out, size_t offset_in_bits)
+    : is_laid_out_(is_laid_out),
+      offset_in_bits_(offset_in_bits),
+      anonymous_data_member_()
+  {}
+}; //end struct dm_context_rel::priv
+
+dm_context_rel::dm_context_rel()
+  : context_rel(),
+    priv_(new priv)
+{}
+
+dm_context_rel::dm_context_rel(scope_decl* s,
+			       bool is_laid_out,
+			       size_t offset_in_bits,
+			       access_specifier a,
+			       bool is_static)
+  : context_rel(s, a, is_static),
+    priv_(new priv(is_laid_out, offset_in_bits))
+{}
+
+dm_context_rel::dm_context_rel(scope_decl* s)
+  : context_rel(s),
+    priv_(new priv())
+{}
+
+bool
+dm_context_rel::get_is_laid_out() const
+{return priv_->is_laid_out_;}
+
+void
+dm_context_rel::set_is_laid_out(bool f)
+{priv_->is_laid_out_ = f;}
+
+size_t
+dm_context_rel::get_offset_in_bits() const
+{return priv_->offset_in_bits_;}
+
+void
+dm_context_rel::set_offset_in_bits(size_t o)
+{priv_->offset_in_bits_ = o;}
+
+bool
+dm_context_rel::operator==(const dm_context_rel& o) const
+{
+  if (!context_rel::operator==(o))
+    return false;
+
+  return (priv_->is_laid_out_ == o.priv_->is_laid_out_
+	    && priv_->offset_in_bits_ == o.priv_->offset_in_bits_);
+}
+
+bool
+dm_context_rel::operator!=(const dm_context_rel& o) const
+{return !operator==(o);}
+
+/// Return a non-nil value if this data member context relationship
+/// has an anonymous data member.  That means, if the data member this
+/// relation belongs to is part of an anonymous data member.
+///
+/// @return the containing anonymous data member of this data member
+/// relationship.  Nil if there is none.
+const var_decl*
+dm_context_rel::get_anonymous_data_member() const
+{return priv_->anonymous_data_member_;}
+
+/// Set the containing anonymous data member of this data member
+/// context relationship. That means that the data member this
+/// relation belongs to is part of an anonymous data member.
+///
+/// @param anon_dm the containing anonymous data member of this data
+/// member relationship.  Nil if there is none.
+void
+dm_context_rel::set_anonymous_data_member(var_decl* anon_dm)
+{priv_->anonymous_data_member_ = anon_dm;}
+
 dm_context_rel::~dm_context_rel()
 {}
+// </class dm_context_rel stuff>
 
 // <environment stuff>
 
@@ -4423,6 +4513,71 @@  is_data_member(const decl_base *d)
   return 0;
 }
 
+/// Get the first non-anonymous data member of a given anonymous data
+/// member.
+///
+/// E.g:
+///
+///   struct S
+///   {
+///     union // <-- for this anonymous data member, the function
+///           // returns a.
+///     {
+///       int a;
+///       charb;
+///     };
+///   };
+///
+/// @return anon_dm the anonymous data member to consider.
+///
+/// @return the first non-anonymous data member of @p anon_dm.  If no
+/// data member was found then this function returns @p anon_dm.
+const var_decl_sptr
+get_first_non_anonymous_data_member(const var_decl_sptr anon_dm)
+{
+  if (!anon_dm || !is_anonymous_data_member(anon_dm))
+    return anon_dm;
+
+  class_or_union_sptr klass = anonymous_data_member_to_class_or_union(anon_dm);
+ var_decl_sptr first = *klass->get_non_static_data_members().begin();
+
+ if (is_anonymous_data_member(first))
+   return get_first_non_anonymous_data_member(first);
+
+ return first;
+}
+
+/// In the context of a given class or union, this function returns
+/// the data member that is located after a given data member.
+///
+/// @param klass the class or union to consider.
+///
+/// @param the data member to consider.
+///
+/// @return the data member that is located right after @p
+/// data_member.
+const var_decl_sptr
+get_next_data_member(const class_or_union_sptr &klass,
+		     const var_decl_sptr &data_member)
+{
+  if (!klass ||!data_member)
+    return var_decl_sptr();
+
+  for (class_or_union::data_members::const_iterator it =
+	 klass->get_non_static_data_members().begin();
+       it != klass->get_non_static_data_members().end();
+       ++it)
+    if (**it == *data_member)
+      {
+	++it;
+	if (it != klass->get_non_static_data_members().end())
+	  return get_first_non_anonymous_data_member(*it);
+	break;
+      }
+
+  return var_decl_sptr();
+}
+
 /// Test if a decl is an anonymous data member.
 ///
 /// @param d the decl to consider.
@@ -4544,7 +4699,7 @@  is_anonymous_data_member(const var_decl& d)
 ///
 /// @return the @ref class_or_union type of the anonymous data member
 /// @p d.
-const class_or_union*
+class_or_union*
 anonymous_data_member_to_class_or_union(const var_decl* d)
 {
   if ((d = is_anonymous_data_member(d)))
@@ -4657,6 +4812,53 @@  uint64_t
 get_data_member_offset(const decl_base_sptr d)
 {return get_data_member_offset(dynamic_pointer_cast<var_decl>(d));}
 
+/// Get the absolute offset of a data member.
+///
+/// If the data member is part of an anonymous data member then this
+/// returns the absolute offset -- relative to the beginning of the
+/// containing class of the anonymous data member.
+///
+/// @param m the data member to consider.
+///
+/// @return the aboslute offset of the data member @p m.
+uint64_t
+get_absolute_data_member_offset(const var_decl& m)
+{
+  ABG_ASSERT(is_data_member(m));
+  const dm_context_rel* ctxt_rel =
+    dynamic_cast<const dm_context_rel*>(m.get_context_rel());
+  ABG_ASSERT(ctxt_rel);
+
+  const var_decl *containing_anonymous_data_member =
+    ctxt_rel->get_anonymous_data_member();
+
+  uint64_t containing_anonymous_data_member_offset = 0;
+  if (containing_anonymous_data_member)
+    containing_anonymous_data_member_offset =
+      get_absolute_data_member_offset(*containing_anonymous_data_member);
+
+  return (ctxt_rel->get_offset_in_bits()
+	  +
+	  containing_anonymous_data_member_offset);
+}
+
+/// Get the absolute offset of a data member.
+///
+/// If the data member is part of an anonymous data member then this
+/// returns the absolute offset -- relative to the beginning of the
+/// containing class of the anonymous data member.
+///
+/// @param m the data member to consider.
+///
+/// @return the aboslute offset of the data member @p m.
+uint64_t
+get_absolute_data_member_offset(const var_decl_sptr& m)
+{
+  if (!m)
+    return 0;
+  return get_absolute_data_member_offset(*m);
+}
+
 /// Get the size of a given variable.
 ///
 /// @param v the variable to consider.
@@ -5621,7 +5823,7 @@  canonical_type_hash::operator()(const type_base_sptr& l) const
 ///
 /// @param l the canonical type to hash.
 ///
-/// @return the the pointer value of the canonical type of @p l.
+/// @return the pointer value of the canonical type of @p l.
 size_t
 canonical_type_hash::operator()(const type_base *l) const
 {return reinterpret_cast<size_t>(l);}
@@ -7405,6 +7607,29 @@  is_at_class_scope(const decl_base& decl)
   return 0;
 }
 
+/// Find a data member inside an anonymous data member.
+///
+/// An anonymous data member has a type which is a class or union.
+/// This function looks for a data member inside the type of that
+/// anonymous data member.
+///
+/// @param anon_dm the anonymous data member to consider.
+///
+/// @param name the name of the data member to look for.
+var_decl_sptr
+find_data_member_from_anonymous_data_member(const var_decl_sptr& anon_dm,
+					    const string& name)
+{
+  const class_or_union* containing_class_or_union =
+    anonymous_data_member_to_class_or_union(anon_dm.get());
+
+  if (!containing_class_or_union)
+    return var_decl_sptr();
+
+  var_decl_sptr result = containing_class_or_union->find_data_member(name);
+  return result;
+}
+
 /// Tests whether a given decl is at template scope.
 ///
 /// Note that only template parameters , types that are compositions,
@@ -18119,6 +18344,39 @@  class_or_union::remove_member_decl(decl_base_sptr decl)
   remove_member_type(t);
 }
 
+/// Fixup the members of the type of an anonymous data member.
+///
+/// Walk all data members of (the type of) a given anonymous data
+/// member and set a particular property of the relationship between
+/// each data member and its containing type.
+///
+/// That property records the fact that the data member belongs to the
+/// anonymous data member we consider.
+///
+/// In the future, if there are other properties of this relationship
+/// to set in this manner, they ought to be added here.
+///
+/// @param anon_dm the anonymous data member to consider.
+void
+class_or_union::maybe_fixup_members_of_anon_data_member(var_decl_sptr& anon_dm)
+{
+  class_or_union * anon_dm_type =
+    anonymous_data_member_to_class_or_union(anon_dm.get());
+  if (!anon_dm_type)
+    return;
+
+  for (class_or_union::data_members::const_iterator it =
+	 anon_dm_type->get_non_static_data_members().begin();
+       it != anon_dm_type->get_non_static_data_members().end();
+       ++it)
+    {
+      dm_context_rel *rel =
+	dynamic_cast<dm_context_rel*>((*it)->get_context_rel());
+      ABG_ASSERT(rel);
+      rel->set_anonymous_data_member(anon_dm.get());
+    }
+}
+
 /// Insert a member type.
 ///
 /// @param t the type to insert in the @ref class_or_union type.
@@ -18479,7 +18737,6 @@  class_or_union::add_data_member(var_decl_sptr v, access_specifier access,
   set_member_access_specifier(v, access);
   set_member_is_static(v, is_static);
 
-
   if (!is_static)
     {
       // If this is a non-static variable, add it to the set of
@@ -18497,6 +18754,13 @@  class_or_union::add_data_member(var_decl_sptr v, access_specifier access,
       if (!is_already_in)
 	priv_->non_static_data_members_.push_back(v);
     }
+
+  // If v is an anonymous data member, then fixup its data members.
+  // For now, the only thing the fixup does is to make the data
+  // members of the anonymous data member be aware of their containing
+  // anonymous data member.  That is helpful to compute the absolute
+  // bit offset of each of the members of the anonymous data member.
+  maybe_fixup_members_of_anon_data_member(v);
 }
 
 /// Get the data members of this @ref class_or_union.
diff --git a/src/abg-leaf-reporter.cc b/src/abg-leaf-reporter.cc
index afe5692..f9d905a 100644
--- a/src/abg-leaf-reporter.cc
+++ b/src/abg-leaf-reporter.cc
@@ -631,6 +631,10 @@  leaf_reporter::report(const class_or_union_diff& d,
 	    if (diff_to_be_reported((*it).get()))
 	      represent(*it, ctxt, out, indent + "  ", /*local_only=*/true);
 	}
+
+      // Report about data members replaced by an anonymous union data
+      // member.
+      maybe_report_data_members_replaced_by_anon_dm(d, out, indent);
     }
 }
 
diff --git a/src/abg-reporter-priv.cc b/src/abg-reporter-priv.cc
index df7df88..9f2c5a4 100644
--- a/src/abg-reporter-priv.cc
+++ b/src/abg-reporter-priv.cc
@@ -1415,5 +1415,88 @@  bool
 reporter_base::diff_to_be_reported(const diff *d) const
 {return d && d->to_be_reported();}
 
-} // namespace comparison
+/// Report about data members replaced by an anonymous data member
+/// without changing the overall bit-layout of the class or union in
+/// an ABI-meaningful way.
+///
+/// @param d the diff to consider.
+///
+/// @param out the output stream to emit the change report to.
+///
+/// @param indent the indentation string to use.
+void
+maybe_report_data_members_replaced_by_anon_dm(const class_or_union_diff &d,
+					      ostream			&out,
+					      const string		indent)
+{
+  const diff_context_sptr& ctxt = d.context();
+
+  if ((ctxt->get_allowed_category() & HARMLESS_DATA_MEMBER_CHANGE_CATEGORY)
+      && !d.data_members_replaced_by_adms().empty())
+    {
+      // Let's detect all the data members that are replaced by
+      // members of the same anonymous data member and report them
+      // in one go.
+      changed_var_sptrs_type::const_iterator i, j;
+      i = d.ordered_data_members_replaced_by_adms().begin();
+      // This contains the data members replaced by the same
+      // anonymous data member.
+      vector<var_decl_sptr> dms_replaced_by_same_anon_dm;
+      // This contains the anonymous data member that replaced the
+      // data members in the variable above.
+      var_decl_sptr anonymous_data_member;
+
+      while (i != d.ordered_data_members_replaced_by_adms().end())
+	{
+	  anonymous_data_member = i->second;
+	  // Let's look forward to see if the subsequent data
+	  // members were replaced by members of
+	  // anonymous_data_member.
+	  for (j = i;
+	       j != d.ordered_data_members_replaced_by_adms().end();
+	       ++j)
+	    {
+	      if (*i->second == *j->second)
+		dms_replaced_by_same_anon_dm.push_back(j->first);
+	      else
+		break;
+	    }
+
+	  bool several_data_members_replaced =
+	    dms_replaced_by_same_anon_dm.size() > 1;
+
+	  out << indent << "data member";
+	  if (several_data_members_replaced)
+	    out << "s";
+
+	  bool first_data_member = true;
+	  for (vector<var_decl_sptr>::const_iterator it =
+		 dms_replaced_by_same_anon_dm.begin();
+	       it != dms_replaced_by_same_anon_dm.end();
+	       ++it)
+	    {
+	      string name = (*it)->get_qualified_name();
+	      if (!first_data_member)
+		out << ",";
+	      out << " '" << name << "'";
+	      first_data_member = false;
+	    }
+
+	  if (several_data_members_replaced)
+	    out << " were ";
+	  else
+	    out << " was ";
+
+	  out << "replaced by anonymous data member:\n"
+	      << indent + "  "
+	      << "'"
+	      << i->second->get_pretty_representation()
+	      << "'\n";
+
+	  i = j;
+	}
+    }
+}
+
+} // Namespace comparison
 } // end namespace abigail
diff --git a/src/abg-reporter-priv.h b/src/abg-reporter-priv.h
index b221fdc..84cb238 100644
--- a/src/abg-reporter-priv.h
+++ b/src/abg-reporter-priv.h
@@ -250,6 +250,12 @@  maybe_report_interfaces_impacted_by_diff(const diff_sptr	&d,
 					 ostream		&out,
 					 const string		&indent);
 
+void
+maybe_report_data_members_replaced_by_anon_dm(const class_or_union_diff &d,
+					      ostream			&out,
+					      const string		indent);
+
+
 } // end namespace comparison
 } // end namespace abigail
 
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index 7e91f52..f5b551b 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -778,6 +778,54 @@  test-diff-filter/PR24787-libtwo.so \
 test-diff-filter/PR24787-one.c \
 test-diff-filter/PR24787-two.c \
 test-diff-filter/PR24787-report-0.txt \
+test-diff-filter/test-PR25661-1-report-1.txt \
+test-diff-filter/test-PR25661-1-report-2.txt \
+test-diff-filter/test-PR25661-1-report-3.txt \
+test-diff-filter/test-PR25661-1-report-4.txt \
+test-diff-filter/test-PR25661-1-v0.o \
+test-diff-filter/test-PR25661-1-v1.o \
+test-diff-filter/test-PR25661-1-v0.c \
+test-diff-filter/test-PR25661-1-v1.c \
+test-diff-filter/test-PR25661-2-report-1.txt \
+test-diff-filter/test-PR25661-2-report-2.txt \
+test-diff-filter/test-PR25661-2-report-3.txt \
+test-diff-filter/test-PR25661-2-report-4.txt \
+test-diff-filter/test-PR25661-2-v0.o \
+test-diff-filter/test-PR25661-2-v1.o \
+test-diff-filter/test-PR25661-2-v0.c \
+test-diff-filter/test-PR25661-2-v1.c \
+test-diff-filter/test-PR25661-3-report-1.txt \
+test-diff-filter/test-PR25661-3-report-2.txt \
+test-diff-filter/test-PR25661-3-report-3.txt \
+test-diff-filter/test-PR25661-3-report-4.txt \
+test-diff-filter/test-PR25661-3-v0.o \
+test-diff-filter/test-PR25661-3-v1.o \
+test-diff-filter/test-PR25661-3-v0.c \
+test-diff-filter/test-PR25661-3-v1.c \
+test-diff-filter/test-PR25661-4-report-1.txt \
+test-diff-filter/test-PR25661-4-report-2.txt \
+test-diff-filter/test-PR25661-4-report-3.txt \
+test-diff-filter/test-PR25661-4-report-4.txt \
+test-diff-filter/test-PR25661-4-v0.o \
+test-diff-filter/test-PR25661-4-v1.o \
+test-diff-filter/test-PR25661-4-v0.c \
+test-diff-filter/test-PR25661-4-v1.c \
+test-diff-filter/test-PR25661-5-v0.o \
+test-diff-filter/test-PR25661-5-v1.o \
+test-diff-filter/test-PR25661-5-v0.c \
+test-diff-filter/test-PR25661-5-v1.c \
+test-diff-filter/test-PR25661-5-report-1.txt \
+test-diff-filter/test-PR25661-5-report-2.txt \
+test-diff-filter/test-PR25661-5-report-3.txt \
+test-diff-filter/test-PR25661-5-report-4.txt \
+test-diff-filter/test-PR25661-6-v0.c \
+test-diff-filter/test-PR25661-6-v1.c \
+test-diff-filter/test-PR25661-6-v0.o \
+test-diff-filter/test-PR25661-6-v1.o \
+test-diff-filter/test-PR25661-6-report-1.txt \
+test-diff-filter/test-PR25661-6-report-2.txt \
+test-diff-filter/test-PR25661-6-report-3.txt \
+test-diff-filter/test-PR25661-6-report-4.txt \
 \
 test-diff-suppr/test0-type-suppr-v0.cc	\
 test-diff-suppr/test0-type-suppr-v1.cc	\
diff --git a/tests/data/test-diff-dwarf/test45-anon-dm-change-report-0.txt b/tests/data/test-diff-dwarf/test45-anon-dm-change-report-0.txt
index 4ffd8b3..ada9d3a 100644
--- a/tests/data/test-diff-dwarf/test45-anon-dm-change-report-0.txt
+++ b/tests/data/test-diff-dwarf/test45-anon-dm-change-report-0.txt
@@ -18,6 +18,6 @@  Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
     parameter 1 of type 'S0&' has sub-type changes:
       in referenced type 'struct S0':
         type size hasn't changed
-        1 data member change:
-          data member int S0::m0 at offset 0 (in bits) became anonymous data member 'union {int m0; char m01;}'
+        data member 'S0::m0' was replaced by anonymous data member:
+          'union {int m0; char m01;}'
 
diff --git a/tests/data/test-diff-filter/test-PR25661-1-report-1.txt b/tests/data/test-diff-filter/test-PR25661-1-report-1.txt
new file mode 100644
index 0000000..9666a8f
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-1-report-1.txt
@@ -0,0 +1,3 @@ 
+Functions changes summary: 0 Removed, 0 Changed (1 filtered out), 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+
diff --git a/tests/data/test-diff-filter/test-PR25661-1-report-2.txt b/tests/data/test-diff-filter/test-PR25661-1-report-2.txt
new file mode 100644
index 0000000..3d9f663
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-1-report-2.txt
@@ -0,0 +1,12 @@ 
+Functions changes summary: 0 Removed, 1 Changed, 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+
+1 function with some indirect sub-type change:
+
+  [C] 'function void foo(S*)' at test-PR25661-1-v1.c:18:1 has some indirect sub-type changes:
+    parameter 1 of type 'S*' has sub-type changes:
+      in pointed to type 'struct S' at test-PR25661-1-v1.c:1:1:
+        type size hasn't changed
+        data members 'S::a', 'S::b', 'S::c' were replaced by anonymous data member:
+          'union {int tag[3]; struct {int a; int b; int c;};}'
+
diff --git a/tests/data/test-diff-filter/test-PR25661-1-report-3.txt b/tests/data/test-diff-filter/test-PR25661-1-report-3.txt
new file mode 100644
index 0000000..0927b7a
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-1-report-3.txt
@@ -0,0 +1,5 @@ 
+Leaf changes summary: 0 artifact changed (1 filtered out)
+Changed leaf types summary: 0 (1 filtered out) leaf type changed
+Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
+Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable
+
diff --git a/tests/data/test-diff-filter/test-PR25661-1-report-4.txt b/tests/data/test-diff-filter/test-PR25661-1-report-4.txt
new file mode 100644
index 0000000..577aff8
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-1-report-4.txt
@@ -0,0 +1,9 @@ 
+Leaf changes summary: 1 artifact changed
+Changed leaf types summary: 1 leaf type changed
+Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
+Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable
+
+'struct S at test-PR25661-1-v0.c:1:1' changed:
+  type size hasn't changed
+  data members 'S::a', 'S::b', 'S::c' were replaced by anonymous data member:
+    'union {int tag[3]; struct {int a; int b; int c;};}'
diff --git a/tests/data/test-diff-filter/test-PR25661-1-v0.c b/tests/data/test-diff-filter/test-PR25661-1-v0.c
new file mode 100644
index 0000000..9c672ba
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-1-v0.c
@@ -0,0 +1,12 @@ 
+struct S
+{
+  int a;
+  int b;
+  int c;
+  int d;
+};
+
+void
+foo(struct S* s __attribute__((unused)))
+{
+}
diff --git a/tests/data/test-diff-filter/test-PR25661-1-v0.o b/tests/data/test-diff-filter/test-PR25661-1-v0.o
new file mode 100644
index 0000000000000000000000000000000000000000..202c0673d80383920ecd2333224afbc6e6072ed4
GIT binary patch
literal 2760
zcmbtVTW=dh6h7neWt&Z0CkD|dJS-BZO~tM+KyZsv3=wGx2t}zX^#z5sy|x#8LH4$U
zn|J^r^{thV0D*)MFFYas13!ajB*YtUNIbwfvvcCfWFrua<UQYf=Q?LD{^+GwcXL7j
z4*}O;F=H0MDK2nZ!8TN34Q}n-`(^Lmhkx9E{im;x&MTFpA}lLxd|U(PZbC%}Cqhbw
zAY_RU$PS3Sf_d2mQBcs4JrG3&mm!}o#FGRH{pl3M(s896T73NlhAGZbcb-u0!4OK0
zPqD?Sc%oc&WqG5#D%MJsM^IZ8(sA9Z?sM)HS5z_o3P>lN-l(=PX#R0<-D|FN01AP4
zex0=!*C|H(!sjoplV7o5_~kADl+L5y4b;GsR0ZV+jA0C}*q{{L#jk|R_!Z*M@(O;{
z?%+xVB+)$a_Ya!SY;86ChJUvnbd^s*z_~OBIWLKia}xcstZT#RII8ugy*K09AWmwd
zxD$5bK^Tu}I&O{J=Cxj!gbMn-xZn5t@hFLAaQ(Gg-cI9LuTc-`LBng-oAvrr&6aoR
zAnJL0VWJg2DjS=gKThsUqU}L6iDq#Zr{SzS+&*e;`CCtW{s7?m&dycu5(eG;cM)-Q
z52*!XGu}G@aa0i-%O&SNHs?{~=|@Ag_Jwlg)7;KVaTg6zI`c~>C=b8?m8HZcX3@Ak
z$IU*Wm#E1aQeO*OC&9Q>hEhVwm@S}$bBl?AWV&L5Nuh+5QydWz%4hJWt}Hx^mTN{I
zj*o5D>7q-i2d``i=Zs0k8HlIV+$0jmYAS$vJyrP$Gtc|Kt_^61%-z`3JlUK3o}6LN
zwUK>6+mNVeY{$QH2JdN}YBB49VE*nn2|Fm0St^H&lISRbfNmNDo%tMsSu_fRUeviW
zIE*L#De4psJDpi{_k=o}g_A*a(q$A+B6h(P5Ok;GafFX$FqtM%fY;2QCt>$39Vi+e
z_GjT(y}P>j{~<QemwLth<WOx5j?ayr+c)bm<cYIy&@0;B7PkLIG@$+dul^16wPR9F
z&i@TfjA7f-Gf#hhZNrA(?erfJzSlgRE28*p;(vu2+xWF(lHxa|1$akG7Pjp^Dr}By
zLrvD%QA^vF)|pj5Ukdi8`ssDD>+d0F$3zMqX!2#67{j(bL|s<?y1ta$C(2LnhMoUy
z<R~WBiBOH7b)s*X(311Bd>?tjw*LqEGB^Lh{}b(RnZXYy9)pFJ^D^_L_P5P{)I^I7
zwdD0P{TYI+{_SP-k6wTF=e|-sc5aIOt0wn#!Sqhkzl!Zodt%=`q@YDb;o$c=ot-`_
S6gSKN4PF0t?5L&PzrO*xO1jSg

literal 0
HcmV?d00001

diff --git a/tests/data/test-diff-filter/test-PR25661-1-v1.c b/tests/data/test-diff-filter/test-PR25661-1-v1.c
new file mode 100644
index 0000000..24346b9
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-1-v1.c
@@ -0,0 +1,20 @@ 
+struct S
+{
+  union
+  {
+    int tag[3];
+    struct
+    {
+      int a;
+      int b;
+      int c;
+    };
+  };
+
+  int d;
+};
+
+void
+foo(struct S* s __attribute__((unused)))
+{
+}
diff --git a/tests/data/test-diff-filter/test-PR25661-1-v1.o b/tests/data/test-diff-filter/test-PR25661-1-v1.o
new file mode 100644
index 0000000000000000000000000000000000000000..28bb43d96bbde31452b190960d280810b594dc22
GIT binary patch
literal 2960
zcmbtWO^72!6n>S;PiH#m$t23|FdlkA*_ma#^P`TlE3-xiXJ=NCab&@RGIo+q(wcN6
z=~>4W@t_DR2)iJtAb9ZZMer<m@#tO!4;}=s-WEjG_qwZQDm5*NK1kK8@BP-RSJip%
z+2^-2jDaKuSKwqNQGg%wCwx0*+failT;I9%>&~rr@7;d&`=1fc#v&v3P>OkFFcm&i
zV)@+52yhRWVG;+t1FRUcS>6S<5VHnkv$-IQAXj3X-$b>1<PCu3io_QjGBn8iBm5O<
z$iRY8z!}V!pu(8(HL|4x0(cLYRUqXOl>3tju<G$%WpeC`pHObiP_{^1=FSinjXTHz
zTVjt`HIrK_))K20DrZnyW2Sl8eA2vRvKj^`12>Z8ZfyfyW*-LAykc?#Aa`a?HLt4T
zM#`{@4h+V2YvieT4%c<`upm5GxSty1h8nI!Mii#(vZt11<nzmvUHj~(&n%NCE2rd?
zISa73h&fR(xJdh>odM>jkgSCQ#q2hY94_F<*dJCI$GCH_h72Nq7CC$St;g5bn@-cY
z(R919O^!h^i9yCy_I`$=-DnaH?89&t3_`zW)A81ald)g#O?s~f^+6ETM?uHy1OqP^
z)rHqOg_zZQUgX8B(+m22ryq<We+pM$xNdJXpRk(^x8XMJR-@HuJlfi@SNGBXju#1m
zgTm&T?Tn+t(BB;Rp+60}xb&vo;pWlCy0iY6?F`a4b9HO$vb~Cd*8W|Tc>52;BNpi9
zKCq(-TPYQc+gPxN5T_qeT<)i>%7>Y)a(){X5;%3Fe$B$~_tS(}*fbcA?DzoY<Qyew
z#Kkq%WgL`8KoN3?ubYA-EQjJLi~yeO_>_!8`4pDuWAmpF$7`(9g9ucWIvlrhT7S|L
zAV}_P0oRfzk&Hk%?dC-Sk(QDPB>tsXQ+y(cSCb_yi<wSHJ&iSilfHWHA1k<8#cu_!
z`;j@5pPbO4sUmn?|I**!w!kSzHBWG7Z;vCdgL5=Z=Aq;w|0n{N?j^XL*$mvNKl0q3
z-#Hu{1Yv)IvY7WeovD9gPV7y+aNy6IjDpaYCKv+T?qodn@riK5N#whD0i9Xob>9$<
z{NX`=>WzJo`2U<%(4J}~>nYu}1#{d_Voka#ox=rT`ptSyRZ@7}{yfU45B{rt6K!>$
z6i&wfg%%3wy7Wn)k9b?rA=VI0^_{?}uSie!EPhvF+Qd=!NrH<4yc=ts47%<figb*$
z{}f>3#5Cq~UD{__{y&KPr9I_Oua=&F4>8>*Lg0wYaE%rU>ALhkBQ5?t5ntAc;?up>
z<G+a*`ILDg8`E1N{x=d3vy3n2cMwmrzaSc_^$+$R2zyNpUeOy;D9E@{|8rqK_jVxm
zny}xHiei@gm+&XZq}8t?E~HvN`YK3!iBmp$Z1Vk?h`%QiruUn^Ho85XN!nI_qok1J
XJD#jg_X_z<vtJeYe<cmYtk>@^Y+2C(

literal 0
HcmV?d00001

diff --git a/tests/data/test-diff-filter/test-PR25661-2-report-1.txt b/tests/data/test-diff-filter/test-PR25661-2-report-1.txt
new file mode 100644
index 0000000..9666a8f
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-2-report-1.txt
@@ -0,0 +1,3 @@ 
+Functions changes summary: 0 Removed, 0 Changed (1 filtered out), 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+
diff --git a/tests/data/test-diff-filter/test-PR25661-2-report-2.txt b/tests/data/test-diff-filter/test-PR25661-2-report-2.txt
new file mode 100644
index 0000000..31ce245
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-2-report-2.txt
@@ -0,0 +1,12 @@ 
+Functions changes summary: 0 Removed, 1 Changed, 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+
+1 function with some indirect sub-type change:
+
+  [C] 'function void foo(S*)' at test-PR25661-2-v1.c:20:1 has some indirect sub-type changes:
+    parameter 1 of type 'S*' has sub-type changes:
+      in pointed to type 'struct S' at test-PR25661-2-v1.c:1:1:
+        type size hasn't changed
+        data members 'S::b', 'S::c' were replaced by anonymous data member:
+          'struct {int b; struct {union {char added_char; int c;};};}'
+
diff --git a/tests/data/test-diff-filter/test-PR25661-2-report-3.txt b/tests/data/test-diff-filter/test-PR25661-2-report-3.txt
new file mode 100644
index 0000000..0927b7a
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-2-report-3.txt
@@ -0,0 +1,5 @@ 
+Leaf changes summary: 0 artifact changed (1 filtered out)
+Changed leaf types summary: 0 (1 filtered out) leaf type changed
+Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
+Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable
+
diff --git a/tests/data/test-diff-filter/test-PR25661-2-report-4.txt b/tests/data/test-diff-filter/test-PR25661-2-report-4.txt
new file mode 100644
index 0000000..9540c0a
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-2-report-4.txt
@@ -0,0 +1,9 @@ 
+Leaf changes summary: 1 artifact changed
+Changed leaf types summary: 1 leaf type changed
+Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
+Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable
+
+'struct S at test-PR25661-2-v0.c:1:1' changed:
+  type size hasn't changed
+  data members 'S::b', 'S::c' were replaced by anonymous data member:
+    'struct {int b; struct {union {char added_char; int c;};};}'
diff --git a/tests/data/test-diff-filter/test-PR25661-2-v0.c b/tests/data/test-diff-filter/test-PR25661-2-v0.c
new file mode 100644
index 0000000..9c672ba
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-2-v0.c
@@ -0,0 +1,12 @@ 
+struct S
+{
+  int a;
+  int b;
+  int c;
+  int d;
+};
+
+void
+foo(struct S* s __attribute__((unused)))
+{
+}
diff --git a/tests/data/test-diff-filter/test-PR25661-2-v0.o b/tests/data/test-diff-filter/test-PR25661-2-v0.o
new file mode 100644
index 0000000000000000000000000000000000000000..8f3768c5fb6b5649bbdc6c2dbe2031fab98516ae
GIT binary patch
literal 2760
zcmbtV&1)n@6n`}}AHC^hc9K!t5DzUVyAiwRLyfa5*&PjLv#UsmEO?RFGgC9uIv+CA
zyT&g(D8e3uT@X|hM7($s{|EmJ&w_aKCV0^Is_SJsl@5wNn0~K*@3UUjtNHMSmv(bP
z0F!{Luvjq);1m~fTZ?U|!W!J#yZg)D-4Fh__u5ZiA)MDLM@LxJa_Zxz<lHMz5yDXj
z$q<Arkp!{>BCo}~?1Ct0(UCn6MJ+BvK3|9?F%<gKDTt-xN;$On`U?zGoTKhMNxAz&
zC^<gG7OUd1a@CdPjq<8kD^(ssZB<Ceb+5S3x|dy1#r!KEon(2V+QOjuN5OTky3zqC
z1mfB{YcH--jP`}kpI;}xVj<<1y8uu+kA62$15Z#Dlpiogin!twq{UtQN_ZT<Li|}?
z!LL3Y+^K+A&0~N6p!xLHR>N=lcj`e``xFG6OLCC&lK3bm(XTd~j#aHU?Y$Az22ora
zMV+t{4Z>(tGcjrua9-<$ai~SV7xnvoKN`hq2G?J{<?S?{@f!7@9yGjWy;-k6*=%{2
z4ph(E3uB}3QQ6q^{BeAHqP7QWqGnMSm*K2C+&*e;`CCtU{vf;b>pMGFyi1s9^WSB`
z-94Z;jLmrW0K`#6Y%G_Yd)S<Z5vL!KZtZjB$|t#<mEtZMByi@JZcrY6|0_$0P0WJv
zc#fNWLN8I1MbeCgt&@-%s+3YfDYaUN63#6aDM=<fP9cdXVdWG?fQ0fH_^FYF2hnmZ
z)raG2n{^jyC=Kz>mT=87sW=1Sw3}-LBCRG282{9)DL)b8$qHcI2y{Z})7UgP*{9Dv
zKEs}CBm07}AyzT69slwfxMy&xC0!2$^LNH^*g+o85<X-pR!1=eG<^_s=5q*UY7_=N
z)ww-5j3)gl>a-koIx}_WggTsslYu(vGKwaOT`&a%-RXF&@UaXg(^v&KX8t@5yKkC6
zYIxY6g=78hn&STl+dyCH759@vwT*CmZ_L`BtVa+h%$}eZjlIom|BGlq=lx&(8|Z7t
zq@0}p8(O5uwxwsD{`}f08xmhl{t@AOgVVhti0>x;S7_SCuN{*VziBPN+lDi<ZTC@O
zV`Li|u+EN#+qSgNtonH<*q`dB*U7HGhnO7`A-HeAo3u!gZF`8ito(H|l-wuEPw$4E
z|1HEQCfA9grk_osZy7M0^D}=BaguHS_suXj|H1!b<8PV44<{ajg_iR&@}=>&&3`mN
ziwzCu{WJU-3R(Tz%jh4y{_M|vrF!h#6#G{L?wf+?ou+>k+n>(Fo;`%1MMWX;d!4LK
T-xZ3R<^Q^=|2uXx-0t7soGrUI

literal 0
HcmV?d00001

diff --git a/tests/data/test-diff-filter/test-PR25661-2-v1.c b/tests/data/test-diff-filter/test-PR25661-2-v1.c
new file mode 100644
index 0000000..f8eee3d
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-2-v1.c
@@ -0,0 +1,22 @@ 
+struct S
+{
+  int a;
+  struct
+  {
+    int b;
+    struct
+    {
+      union
+      {
+	char added_char;
+	int c;
+      };
+    };
+  };
+  int d;
+};
+
+void
+foo(struct S* s __attribute__((unused)))
+{
+}
diff --git a/tests/data/test-diff-filter/test-PR25661-2-v1.o b/tests/data/test-diff-filter/test-PR25661-2-v1.o
new file mode 100644
index 0000000000000000000000000000000000000000..0dbbc1faa6700ce64d9638d7c485e17d268650e3
GIT binary patch
literal 2968
zcmbtV&2Jk;6o2Dg+v}|DI&n}F1tB9qZBTdp0i<bBN+2Rl`BD_<2|={>uI;7%K=!r@
zAjAPw(OZ#_5E3`82#E{-00*udBP0$S_yZ7^N*v(5*?IALvJnVI+L`x$?{nVF+xh6l
zS9UW(01pAzV8$^DaGaab+mhUdDlEaxy?ejy-TUyt{kMMj1z}T)Ea{;r>BM9w=!_`}
z`BxDz2o$!Y%?^lS?hQh+LI%v4*>Ds=-W2)lErczHgpjv{Y1~Dk&<2q!NDJ#t<V{$3
z9V$Xt-;&S+VdP^W8Jh(kL`4Ecu)E_ih~<;La&qFUpHa0qMcFx`GN1LKWKk>-i{goL
z)i%m&<wdcSubfBed12dE?Pu*Pwy5G}N??>@_6D*hvet~vJ`T2h4ebGPinMSq)j1bd
z32sz3F`Gitn1F?<Fsx)W+ZE5Pa)1k~l+*Ug7cZ<*CdF(bbY=yhbPj`4QgDfOMf(Aa
zo)}z#1SH8_oDFyqXCeM9mvEN%1uG{Y3Z{{>f6#hnW25P`oI6doBW<z*3ULfFUKSr`
z47BrnKkyGbeQyHw{&*PF{jvW}Snq{VeGs<2cG&a6L0!44lkaKW_aaY{jvscrPB$Dx
z!6Y^A_1A9JcAA%K&4$}>o3&P>)o47`+N@nX2>jZf7b$^*!sdF-8Ai89!B#IA1(UFY
ztT*ZOw~jV9oQ<bzP7h?3aD8XzYV9I=UjKJ)VOee;OY{B#h@*;FE9R~HSgMB+r;mm#
z^z-G)-ONrYw~GofoI2C}G~xHZ(u7#jG#C%-r1W!gBW|`ij2J_;tu8P?_Mi@<e*P3j
zfB}Uw@bs%?K<Ny9?2wdUz^PvfJcN2ni8`D_O1qiV@@nwT7LaF}L>z%|+RfVp;#iCq
zVEiS;Q+yhXbNtm9mYF3_V_o5-pFH==2|QWFeGUIc@!zYJZa~sM?}hwll@2WxMeF)k
z&cJI5C*Mi`;7;EgMqV4&XcDh|rlQ~|0+$*V-1c+|?j#s^t{=2-_YT8RcZ@Pgd+qil
zxHBjACf=wQ%$p3tQNSh`0^H7cI1KPLa7W`PaIq<!Y2<ZoDMvy7usiXFflB;;E^BB{
zwc>iRyKP1AIWw+z;-e@aOg?X{gzL)*I!WsG7f?v|<G<QB(N_0K@i_jk#3Y8UOOFIS
z=GzG!A|J#rJRB>W?iE3Nm+_^MV+&{9CkZYo@JFdNn{-{e_c}(}e<-jqGnJICOZ!aA
z|0mTHY)|>q-%QWnM@;vL5Im-uf%~5@8oKTwVrlXDLUElaKJ~jE|6S#i^F&sTp^E<<
z18U{?yncW<(YpO3>O!^t!Tu9vuc^Vlej$|t$7TI5mHphm12MMWWJR^|{u%xpiM0By
zsD@0|k6sJ5XPolUW0UVsRsQ=bVfwGrE2P`gnWSy?n3BT5|M!?Xeg5P(&Ay@X|C$Ze
Hs@Lytw-L>m

literal 0
HcmV?d00001

diff --git a/tests/data/test-diff-filter/test-PR25661-3-report-1.txt b/tests/data/test-diff-filter/test-PR25661-3-report-1.txt
new file mode 100644
index 0000000..9666a8f
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-3-report-1.txt
@@ -0,0 +1,3 @@ 
+Functions changes summary: 0 Removed, 0 Changed (1 filtered out), 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+
diff --git a/tests/data/test-diff-filter/test-PR25661-3-report-2.txt b/tests/data/test-diff-filter/test-PR25661-3-report-2.txt
new file mode 100644
index 0000000..f275382
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-3-report-2.txt
@@ -0,0 +1,12 @@ 
+Functions changes summary: 0 Removed, 1 Changed, 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+
+1 function with some indirect sub-type change:
+
+  [C] 'function void foo(S*)' at test-PR25661-3-v1.c:21:1 has some indirect sub-type changes:
+    parameter 1 of type 'S*' has sub-type changes:
+      in pointed to type 'struct S' at test-PR25661-3-v1.c:1:1:
+        type size hasn't changed
+        data member 'S::a' was replaced by anonymous data member:
+          'union {struct {struct {int a;};}; int new_union_member;}'
+
diff --git a/tests/data/test-diff-filter/test-PR25661-3-report-3.txt b/tests/data/test-diff-filter/test-PR25661-3-report-3.txt
new file mode 100644
index 0000000..0927b7a
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-3-report-3.txt
@@ -0,0 +1,5 @@ 
+Leaf changes summary: 0 artifact changed (1 filtered out)
+Changed leaf types summary: 0 (1 filtered out) leaf type changed
+Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
+Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable
+
diff --git a/tests/data/test-diff-filter/test-PR25661-3-report-4.txt b/tests/data/test-diff-filter/test-PR25661-3-report-4.txt
new file mode 100644
index 0000000..bad754e
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-3-report-4.txt
@@ -0,0 +1,9 @@ 
+Leaf changes summary: 1 artifact changed
+Changed leaf types summary: 1 leaf type changed
+Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
+Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable
+
+'struct S at test-PR25661-3-v0.c:1:1' changed:
+  type size hasn't changed
+  data member 'S::a' was replaced by anonymous data member:
+    'union {struct {struct {int a;};}; int new_union_member;}'
diff --git a/tests/data/test-diff-filter/test-PR25661-3-v0.c b/tests/data/test-diff-filter/test-PR25661-3-v0.c
new file mode 100644
index 0000000..bd7a6df
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-3-v0.c
@@ -0,0 +1,12 @@ 
+ struct S
+{
+  int a;
+  int b;
+  int c;
+  int d;
+};
+
+void
+foo(struct S* s __attribute__((unused)))
+{
+}
diff --git a/tests/data/test-diff-filter/test-PR25661-3-v0.o b/tests/data/test-diff-filter/test-PR25661-3-v0.o
new file mode 100644
index 0000000000000000000000000000000000000000..269bfca7a6337d91fc8268a9518eb6483c0908da
GIT binary patch
literal 2760
zcmbtV&2Jk;6o2FKSF(xg#2^}l!y<v&RP6er5Zt1)hKMu;grZcHdO_*h9@`84K=!tT
zFL3}N_0~#AfIvcs3n#??z@Nbx3320w!~x!$ofmH=8&S1S^1e5}_c?FojX!<u&7F)8
zz$D-b%vX#86!UYrrNtIhU=?oe-ur#`-Y0+GfBV;O5zcB=QAb$Pa^mA8<jf5y3$dUO
zk^u-=APHm}L{^Jg*#VK$Vo`QM<h8g2*=#NxN094HCLk7%D&^4PyRR`!eula;BxN29
zpiuNEwpbC*l`4)bt(R8BYN7lTYAZq(9p|$1vUAB170ka3vKTL~SDF|!`z$!l6-O2U
za=y5_#@cgh6r*+at5?^^FP}^JWzGT=&Y<6Q)WGvp1?2~fksz)(0cmjuzY;FsSBQT~
z%lOr&gF6)vsafRh?KfWB+^l;Sy}LEPqkVD$7UCS_yeK})Nc5`?CL>ktPP*@e)qWUN
zhhaNthy5TNR!xj51)Np8K@@1w>xR9a*9(V{nx-bZ_SQ{zyZ(|}ulY5<?lx+TTJ2(^
z>7L(LU3WK#jKV`@eZ%!e(Vel{>Z`Guh8<i6)6QV)u(|1NzTkR&fNR^^m)-LibmQS7
z;_jZ%8^&gQxDVp6EY_C_#rxQtrxB+gk#6lPrScb<?dALq8pLqwmu^rN{`xmfiA_v{
z@pz7!eM~P=lSa~vg{_m27^;L)LLsr5ixL**i-aWO9Vd`jl(2jPBS1pw6#T@<!jovZ
zn&`vPwN1N=G?a#TXN$OInN*yCaN5lc0+Ci?28>@dYsyc=c)S8wGXkAZ@-#LKPWH)j
zk4~}Y+Q>d<Y>1VOY{$QJ3ho-5YDv}u{_KNM6ts~?)0hufiqv5QK20C^?b!_csTu};
zSGDi-55jS8f;ue+?e<jNJ*Ez(!MLxEy9~pzVi!ySerGZoDSRyb@g!0{j+r-$g3h}p
zkQyBHrol+RyQcWZVC(2hz2bgysFo3q?u}X7ll2VZgxM4HhOxJq?SBpp=)51*zmC3k
zOv=gmzo$ilY+HKf>Cdl~up#kQ{ErAf8JzAFL3}syze3X%e(ji~_)}{E-Zz|?ZM%mG
z8zbAmfHihB+_t5Crq$0w!TwY~y-s%hUBv8|2*CpbzD|n-*|rC$OUqv~L&<%j{Pb?v
z`QJl~Vsf1*YWmG2`hfw%IY0A{5GUF8|JV$3<A3}=H~y9x{Cw;&m}@yNBi|T*+x!;;
zG}+K_-ao@%qL9|V-IV^(>(Bn&SE|R(O|kzl;GQX%-f8+*vHj^x?Ab#Inp6}Lzt{2V
U<Xxe-Y5uoO{Xep!;dcN218X(A0RR91

literal 0
HcmV?d00001

diff --git a/tests/data/test-diff-filter/test-PR25661-3-v1.c b/tests/data/test-diff-filter/test-PR25661-3-v1.c
new file mode 100644
index 0000000..476af8f
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-3-v1.c
@@ -0,0 +1,23 @@ 
+struct S
+{
+  union
+  {
+    struct
+    {
+      struct
+      {
+	int a;
+      };
+    };
+    int new_union_member;
+  };
+
+  int b;
+  int c;
+  int d;
+};
+
+void
+foo(struct S* s __attribute__((unused)))
+{
+}
diff --git a/tests/data/test-diff-filter/test-PR25661-3-v1.o b/tests/data/test-diff-filter/test-PR25661-3-v1.o
new file mode 100644
index 0000000000000000000000000000000000000000..604b7afec751ee544a9f3ef2ea77fea9c7ce23d9
GIT binary patch
literal 2920
zcmbtV&5I*N6n~Y<=X8?EOdNKH@esjfcf{^|tj=avW;gEO?CdHs%&-R!5;{pIX-zsy
z(zA|=cu<7BtssctLGhqBLHq+2{09U<(32Ozt2e=ezE@q9RBC%sv>;XQ{oeb%_v)pp
z`rQ{^+D|YBYB0D83r(Q_U!@mvONcEf!#dnPxcBS9y?6e&|LS)?BAgVatcZ{ovNl<o
zoJg`v<~jo01(r*bgzRL>4?;j&D)kELDO<qlB;!0pF>@2AEMY946O`OBlo-o?iI89b
z?*LmBfs?!otR%!N?*UsAVjfodlL@em`6g9W?9(4GiZw&qDoKeChmg-wTI8~G#j?eV
zTg5fDo++I{YnfTr73(?cvc<}{ivsX$q~0tyF=X;du&k>V$7)jrh_LjTB~FHL+z(QV
ziVBb`GoIB$uQ=@5rV4(3lL}~^{rLJOrL|I8+QeCa{3@oUsPHsxfr^hBCM25FcISW;
z_HpI#6wZwOSuEfzP6f7!LFmmw`|zmo?9NWzzG&aAJ6*v_F~~(Zh`hnxOK{{>hm)~a
z?M-^G`PG3RR!4rv?f3)NA5~?HDg~TXdv53o(eC;EzTNjnp*M|9cH`ySmA(3<O1<XP
zoO-2EYt(8N8_mjvBd=FEa6@UZ(OBQE*yHdd@OB4Y;7$E5s_wKq+&yjX*gMZu>;VMc
z8|{<8p9Jl(H|}6cxUsi)rE&qYZ2!9y0FQ|VU~}F&0(M$rTlq}(J~r!d#OX&vwEFpC
z>4U^xA-#_d5ga?yaZJMRf5jQGsc|s=UGwHI**RL`2A9{<v~kcbw`Sy!(X@rgA-7O8
zi9h0SaVThB3z0){p=c6^hn^1DK>NDZhxsv%JDYUF<xo3YL0!pYQW*%R-5e8$V=Y=h
z;n$>2`DrLT(f~H4K>v{bZ){7P{Pq8RsNt0NbE$tVS2K>Pnc}3R4=p7roBo#{fmb9>
zHR^SOGkbF!x*c4@X|xWN6ndv2ICM+F>C9%}Oudoo^t{f=pzR0!3EG70b~;n<?vmM^
zy1~F(ri}c+Qxr@APIodMd-&cv!6ft?ynXg8bi1$1K;E$3pSojD7XCk%E#y<LR6kX?
zmK5gaN$TXQk0($e%)D7I>Q0($^3S7<&cT1>*O6<+q;x9(&!lL>^rg=MeVkjG4~f4-
zUu<|FaXME7sWXe;6ft&jHe-_EoCLoWUW>`}Jw%g<k@rs$tSzWQGJR>EarJ*DFNNY$
z{q$;?_4g1nV<I&Fe%&HP8>Vj?ZE^X(m-$tnC_mk6GyfgLD5k0tMKO-$W&B0~a#i`&
z`ZnTm{FFRUjep?3FZqTWTrv-&bWnMf|0j~a^mZWDl>DZ0l&jjmf<Hnbu7AJ9^pCy@
zim&=g^_aOS_8po3P!>$@H+^kPKK&<gTYW%9;ZW~*q&<9AC~h2IJt$u&qFl}X{SB%O
B%isV2

literal 0
HcmV?d00001

diff --git a/tests/data/test-diff-filter/test-PR25661-4-report-1.txt b/tests/data/test-diff-filter/test-PR25661-4-report-1.txt
new file mode 100644
index 0000000..9666a8f
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-4-report-1.txt
@@ -0,0 +1,3 @@ 
+Functions changes summary: 0 Removed, 0 Changed (1 filtered out), 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+
diff --git a/tests/data/test-diff-filter/test-PR25661-4-report-2.txt b/tests/data/test-diff-filter/test-PR25661-4-report-2.txt
new file mode 100644
index 0000000..19f2582
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-4-report-2.txt
@@ -0,0 +1,12 @@ 
+Functions changes summary: 0 Removed, 1 Changed, 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+
+1 function with some indirect sub-type change:
+
+  [C] 'function void func(S*)' at test-PR25661-4-v1.c:18:1 has some indirect sub-type changes:
+    parameter 1 of type 'S*' has sub-type changes:
+      in pointed to type 'struct S' at test-PR25661-4-v1.c:3:1:
+        type size hasn't changed
+        data members 'S::a', 'S::marker', 'S::b' were replaced by anonymous data member:
+          'union {uint64_t marker[]; struct {uint64_t a; uint64_t b;};}'
+
diff --git a/tests/data/test-diff-filter/test-PR25661-4-report-3.txt b/tests/data/test-diff-filter/test-PR25661-4-report-3.txt
new file mode 100644
index 0000000..0927b7a
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-4-report-3.txt
@@ -0,0 +1,5 @@ 
+Leaf changes summary: 0 artifact changed (1 filtered out)
+Changed leaf types summary: 0 (1 filtered out) leaf type changed
+Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
+Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable
+
diff --git a/tests/data/test-diff-filter/test-PR25661-4-report-4.txt b/tests/data/test-diff-filter/test-PR25661-4-report-4.txt
new file mode 100644
index 0000000..578dcd5
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-4-report-4.txt
@@ -0,0 +1,9 @@ 
+Leaf changes summary: 1 artifact changed
+Changed leaf types summary: 1 leaf type changed
+Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
+Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable
+
+'struct S at test-PR25661-4-v0.c:3:1' changed:
+  type size hasn't changed
+  data members 'S::a', 'S::marker', 'S::b' were replaced by anonymous data member:
+    'union {uint64_t marker[]; struct {uint64_t a; uint64_t b;};}'
diff --git a/tests/data/test-diff-filter/test-PR25661-4-v0.c b/tests/data/test-diff-filter/test-PR25661-4-v0.c
new file mode 100644
index 0000000..b82973a
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-4-v0.c
@@ -0,0 +1,15 @@ 
+#include <inttypes.h>
+
+struct S
+{
+  uint64_t marker[0];
+  uint64_t a;
+  uint64_t b;
+  uint64_t c;
+};
+
+void
+func(struct S *s)
+{
+  s->a = 1;
+}
diff --git a/tests/data/test-diff-filter/test-PR25661-4-v0.o b/tests/data/test-diff-filter/test-PR25661-4-v0.o
new file mode 100644
index 0000000000000000000000000000000000000000..9362ec606da80b2cea9aba063c529d2bfdd62c4a
GIT binary patch
literal 3400
zcmb_e&2Jl35TCbe$D6KWoVb*v;jopc+DPo$4j~DxQbMCpM^q@JDu@HJ*7n+7YOjlT
zT_r_)NK~j5LJAV%L*fKyBo3&83l|Rb1UHU|3x5E&3WQ+h?R)O#Wg{WPNPaW(o6q;T
zerxmcvl?R{iNOVUph*<qw(&q-=i)k)U<R(%?)+A}^Tz$!dz<%bKk(4~SHAxXJu=+O
z;K}k_P7Eg!vc`CBP0JCeWnyN;j7@M*%M#NwSwDzCGq}bJX!iU?ii-U>q|YDs5XxSG
zS*9D`p~!QvY@TGuxxgm!yIgDl%W=`jHGvsiEP_GCAMrOysR54B!1Ci|k<GKu{y>8n
zqOC}h_CXi&#x3qW%}y6ere2sYOtYC>@e#B>2BvudA)JR0ao(8|pcl@W=gp_gtr9j}
z!j?@l1{eYpQwjU3iR~j`$N&Ni#<oi2I(Z7VN-KyQbJ){!GV7T+YG8f#<BN0TWM&iL
z+AKi+A#96E!{amsY911MjhdV;HzkIfDC_Vtj*R_Tn8J~tBU~m{9)-iQA2fTTmRD~0
zQ3w!uVPtRbEIzrsT(y_%8x^NXxuU)6Ug&h`;Dk{Nch?@_5X1;%8BE44QLQ}78t-J;
zmzs{X!tP)gS)(BIJAv1-=v*97%H2WVE4K!%SN(FwkIFs2;Wqq^>-Wl{i!!womRoM*
za?x)2?Y7<adyzMU-XQ2G*|Eg(Qaw?o*>#7&2kuQRh;BHTHsedrU$r)>D^|7QRGg}{
zSXr!8o>*M9&hFqGYHlP9HX5r7mfep=fw$K20&nOyQFVvS?%M9^vc0@y*&RUr8ur>A
z1x<+8gL<gfX$g6&aA{-XoOKqfF8sTaaGW*;iQU`*wp(QL`JAzjoAW4Q^dsU2;+aD6
zU2S7(;stbw;na~XO$L7dJI#pOm<Hpa8t?f5yN;GL(#2b&+VuG1t1xEaZ%Yb3c?csw
z2fS1%q{CDSO8$7qQ%HwH?;t#kelv-67+<!u)5AA9PRyS)6zOe}GFfB=!f7^l2}GKX
z8TTjrbD>jxBI$2&4PZ`i+M(obEC`(Zllxvt;FO;p2cngjjq?i?zcMk&y7T}MZN_Zu
zUl;mi1^-CsR}{P`j+Cq5?+d-H;3tKDq~N!OzNg@?34LF|Ga}DV3T_JgXMvNNTu-@o
zv=02MCj5#=_;-)MErC<+WPWhMy?*32kVnIqccm10yAe3_{(;j7LvV&(&vjZ}W7Mhp
zL3@BUF1wA!(7SQK><-<a;~fm?`GF@xumm{GLBH?egX9E*$aC-!vct%2UK5GDZoNHp
z`}C>)A7~zP(AvuNlquJRFuqqp7uzD;;1o)PsV~fnV~>M^8h-}ObRYg}d=+EWoK#WP
zPy9tA_>eDsyy@T1I{uRMjW|^IL~=S;<R|Yg{sZ8&hGSa&mjw8=@RLmS-994T6o;`Q
z0|i&(pF=#Y|F^|cE#s+wg4F(7h^aXdg7c#Oo1`R2^`&<vt^QY{zFa4&Pw$&r|2krn
zQ}&4>r*B35(@H_9O8z=}r^Wvw4%DK^Cz*_s{FaE9cFBu0sT7iN()1|`go$<~3^IOo
zjKqSRzl1+TA#MHcrK}%)9c8>+*R<M{yC(YI77f#PjJ~>RJne~k_Yi_l#Q8r-N`ln+
SQ{J@rUq%04Bw|Ta<NpEtS`j<|

literal 0
HcmV?d00001

diff --git a/tests/data/test-diff-filter/test-PR25661-4-v1.c b/tests/data/test-diff-filter/test-PR25661-4-v1.c
new file mode 100644
index 0000000..b3a2774
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-4-v1.c
@@ -0,0 +1,21 @@ 
+#include <inttypes.h>
+
+struct S
+{
+  union
+  {
+    uint64_t marker[0];
+    struct
+    {
+      uint64_t a;
+      uint64_t b;
+    };
+  };
+  uint64_t c;
+};
+
+void
+func(struct S *s)
+{
+  s->a = 1;
+}
diff --git a/tests/data/test-diff-filter/test-PR25661-4-v1.o b/tests/data/test-diff-filter/test-PR25661-4-v1.o
new file mode 100644
index 0000000000000000000000000000000000000000..df219fc03e618fa9278605cb21f2227ac2200a94
GIT binary patch
literal 3480
zcmb_ePiP}m82{c((@Z-}nl|fhyAjNS%eLT5l3Lrk(lu_W+r>p{mkJ6JC&?rkH<_%N
z$<nT<2UlScWCcNmy@;L!_vqDw2fgXhqZh%09s~~y9`yU(yq8X1XF<dd^4|CTzQ6C^
zH+g&e%Cj0{Ac?^RIFcj^@RM=GS2<gSJj}!X&h1}zZohed=l$*bJKu5FJFk3Ozz`GM
z%HY9fj+`UzWDL+4kF9BG0=0=~GNOr1a!^arq)((gKLpL-5>KF6g^T1B*>OlOoNy6J
z<(QHB0+~q%*mUjyIhd>iGdXKOI>%D_Hwg0VES)8O>K2(zlkpGuGbuK}%vD%my73#b
z8Hxy#4Pe<BvdTf005(6`G`Tc(cLA~40ChRyv=2LwGbk#sId(RiH}z~GJIChJxl^cp
z6io91Lbyu7VqFs_LC>BypE92`FXwSdd1jiC$WWZjfNn_bh6#{9HKsREAM>8d00o(5
z>~fym%`+&kquWd#7Z(qlAxjFFwb|2)GRC<@Dz>`t@x?`QpH0QGYYVs)v$zDx0FU8M
zqlHF$%Q+h4){Fqb+{9UjmvCh4uWSZKej{<ySZNpxN}k{74x4VN?u7wB=mw#+ySMVh
z+FHe0wQf}G2E_>vue*WWp(_}KO}r=85C=a(AjM!RDv5H<vy|~pihZT&m@DY?2VrsO
z2VUEEn?<@|V?wFZ@42OBzxkS1YI|X+>(!mQ*LJ*aNmNmyl7dpx2_4Q_O|R9mT3$DF
z2hi>NZ6!LUSX-@yi7OeUZgiXh@PS95vP6X(y!8BjajUXktd#AtT`8`VSIXtbS2l`E
zd$@)jClm?`m6he9)eDEdyV-Vqci=TpbOw#i=E26AwYFNc+JN#kthF`t8xZXW<*U_b
z3wfz<X>03zaS5|7|GSiQBuFQs3E0g&U<Wx?m`NLV@Q5BpoPI?7T0E1@y{BzuCSO2<
z2u>W)6+6oxe<vyNFq2@sTBDO6)9a{7B3<+@Ri^`QMk<U^c&|#pr;cF+=zvcvfpo|u
zpk&W`%rTt~$NDNfgjVyheHcBtq*ud_I|?gTHjSd>Nh325POJHvK%}{70*TWTCZhaA
z5-)NIU{TC;Lh;jB7C70*&%GSODenhDURQ7-;#VsonU~rS(RMVA>Zu9&ih_SB<f{sv
z6IaSn@Xv(YQt;ElK2-3#LOxXR*M)pf!6!tV-xb^x_@4qNF}|Pj>}VhOS5nyJ9$?>j
z0A3V0#g4}ZJ2>oxP95iP5X~LQh3-KJHg!R;>p=ka!0kGA)2$ENHP3JLQO9|wULUwO
z#?;Qh@!RgWOV{&V>4GW1ZuEOS7u%BU_e0mlR%8XC)3_!Axt&^T;PmLz|36RxW6<8p
z{gffAf*HLlA&X;?W^e{M!qg7);>cp0QT@-Mn%={I^{=3>8j~{0{EsL~MdY7w`ZrZ2
zrx+PxL%tUdbg#%xzFqtuz-bdlH6|&B0{oeT7^$|qs8caA-xrrr_6He4wS5j1N%j9I
zTDA12`Uz6&Zz86~L<rs$`QIWTMyf6KnWX$Ok=!TBPyJ2Je;qN3DeFX*lP)gAStX$q
z<@^R(C;2Z?QAjJ%G#aI!oNu8<@*^W;UKajwjCj@dQ-O{34#Zv+{u|^JBe{MFeS{2|
zRwW?j1z{N9Kl(aKe~D8)YHkYgiO9bz3a0NEeRWlTIurHnAq2mQrh1x$7^&;0xJmwH
Mk?8Z7FR`lsKLqa-nE(I)

literal 0
HcmV?d00001

diff --git a/tests/data/test-diff-filter/test-PR25661-5-report-1.txt b/tests/data/test-diff-filter/test-PR25661-5-report-1.txt
new file mode 100644
index 0000000..9666a8f
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-5-report-1.txt
@@ -0,0 +1,3 @@ 
+Functions changes summary: 0 Removed, 0 Changed (1 filtered out), 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+
diff --git a/tests/data/test-diff-filter/test-PR25661-5-report-2.txt b/tests/data/test-diff-filter/test-PR25661-5-report-2.txt
new file mode 100644
index 0000000..e0c2d44
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-5-report-2.txt
@@ -0,0 +1,12 @@ 
+Functions changes summary: 0 Removed, 1 Changed, 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+
+1 function with some indirect sub-type change:
+
+  [C] 'function void func(S*)' at test-PR25661-5-v1.c:22:1 has some indirect sub-type changes:
+    parameter 1 of type 'S*' has sub-type changes:
+      in pointed to type 'struct S' at test-PR25661-5-v1.c:3:1:
+        type size hasn't changed
+        data members 'S::a', 'S::b' were replaced by anonymous data member:
+          'struct {union {int a; char a_1;}; union {int b; char b_1;};}'
+
diff --git a/tests/data/test-diff-filter/test-PR25661-5-report-3.txt b/tests/data/test-diff-filter/test-PR25661-5-report-3.txt
new file mode 100644
index 0000000..0927b7a
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-5-report-3.txt
@@ -0,0 +1,5 @@ 
+Leaf changes summary: 0 artifact changed (1 filtered out)
+Changed leaf types summary: 0 (1 filtered out) leaf type changed
+Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
+Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable
+
diff --git a/tests/data/test-diff-filter/test-PR25661-5-report-4.txt b/tests/data/test-diff-filter/test-PR25661-5-report-4.txt
new file mode 100644
index 0000000..85b6e74
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-5-report-4.txt
@@ -0,0 +1,9 @@ 
+Leaf changes summary: 1 artifact changed
+Changed leaf types summary: 1 leaf type changed
+Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
+Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable
+
+'struct S at test-PR25661-5-v0.c:3:1' changed:
+  type size hasn't changed
+  data members 'S::a', 'S::b' were replaced by anonymous data member:
+    'struct {union {int a; char a_1;}; union {int b; char b_1;};}'
diff --git a/tests/data/test-diff-filter/test-PR25661-5-v0.c b/tests/data/test-diff-filter/test-PR25661-5-v0.c
new file mode 100644
index 0000000..db814df
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-5-v0.c
@@ -0,0 +1,14 @@ 
+#include <inttypes.h>
+
+struct S
+{
+  int a;
+  int b;
+  int c;
+};// end struct S
+
+void
+func(struct S *s)
+{
+  s->a = 1;
+}
diff --git a/tests/data/test-diff-filter/test-PR25661-5-v0.o b/tests/data/test-diff-filter/test-PR25661-5-v0.o
new file mode 100644
index 0000000000000000000000000000000000000000..332f35dac2ec19817f452a49b06504bb12c8c01a
GIT binary patch
literal 3152
zcmbtW&2Jl35TCc}jkn#zI5CKZaM&VH8x*fUplMo^5{N=mQ9+a{;sjxBuk8hU4eL!6
zKnTQ_sJH}yL_tUh_1rsmgy6sdiN67fE4NBLz|7lu&ib)P2%h9OGr#$K^FG$^Y+ZfE
z5CYT?a0!kjg#sKEkNFMBZa@VVU~l`zZ`(KCzO((w)}0@v=h2Jb|AYnwX=KY-vy#tD
z_hooP*d!T-%wQB!Vy8sRq&ka;S+K$=0mGJ(k?IQ9U{;v+m#DXV5LWR8GPPPDW+Yp%
z+8~OOwLwM`&SZhA4)OI5=u{koaLU9PpZ3AF2NX%ni$_Wo$1E+C=EXv>d_QU*2FJOC
z5SGL&E}>AtG92fk^MrH35f!Xq4oo{0uT<6?fEiCbxoDPB=*%L8+c@>b(~D$Wv@*uV
zDFFK(OmYP^utEh<DIgmqIhs`Q6M&g#k(%%*&O+QR&EYKf7Z)uc3C2lvXSaE7b+u7F
zSG`{M+S1Dsa3ak?&W^C`_bu^_VWRW6KZ+CgAR33gDCoGf15-k+KN<$L&ZzTBSnGvJ
zZ4kEnR@n2yL5&mDDC@Y^@e^ON)lS&$R=eRK31S$GqTY1$yc#DN+kHO<IY~#Vh6|J6
z<>&U?&Bl4RQTOUz!)?}^_4;GYHTUc;mcH#Ltf->0vFuid$w3sX_kt*h!#0Y3-0rU*
zuB}#AA9t%g=pICENY_t^;PU3?MfWV`SpIhju$TAB_G0Vb+y!x17E7~5`v|w<LB#1t
zLvG|#rSeC{=G@E<8l-UUOh>l>fBc!J#NEn+@i0ubc1mxeCU2O0d3Bwc-X9&tD4EA0
zqc!0~MmlCqz{`*`Oqk0-=`iC>%NZv8?O~C<T*&lca!K+|mE7MnuzVSiJ&8ZXlSXAA
zoK|z2KpgWak@keY&a%p<>|MqenbHnrcVn4xvd`{&J%dyH4_Q91;Vj~-oeq_k9u68?
zDVfx{&+=6b|CZ%z8m?ODYxqr;`x^ck%U@~uU6xfIy$`DARKLZxM9R$T_80yEcNwQ1
zXZsA^_>E!Ww~!`rO8bgSg2MzndY-^*jmO}{!NB)ALF=HmA4c5~>Ll;CT5)iFO6|vf
z)C;Cv24NH^7fb<Odo&yd_<nfNC<#1#KC0uyZ@<ccg8qIt_J;u%{y)hQ`ctn|KUKI5
zX4LsqIN4^$X%q<4UxvM`QHIz3&!CRZ@qhJipsya2(y9DElaLv@?F#DXug6BlhS>B!
z1HilNK<A3=)SbgGt{m$)>oG}jn!)#^)$yd;?x0G?$odw8>Rzh|x-EUY^XmVYH&ppk
z{q*YU^>+}{V<H5<GWa|RnW5X#`;eFaJI=5AMEU7m((}KD7{yd|A}hy3d>Kz_36-em
zTWFo<zrqKq$?+6Xeu{p;{z|XtOZtIih4NE&^goiYskZ~=m)U<!88TJtSMcY^<n`~D
zoc_^QP5G<7=H;f?H#z?f7fjzD`daG#v?qB>{hf-!@jyx@v%Y?co9oZ@-<o))jPCyz
DJ2>rX

literal 0
HcmV?d00001

diff --git a/tests/data/test-diff-filter/test-PR25661-5-v1.c b/tests/data/test-diff-filter/test-PR25661-5-v1.c
new file mode 100644
index 0000000..7f0eb6e
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-5-v1.c
@@ -0,0 +1,25 @@ 
+#include <inttypes.h>
+
+struct S
+{
+  struct
+  {
+    union
+    {
+      int a;
+      char a_1;
+    };
+    union
+    {
+      int b;
+      char b_1;
+    };
+  };
+  int c;
+}; // end struct S
+
+void
+func(struct S *s)
+{
+  s->a = 1;
+}
diff --git a/tests/data/test-diff-filter/test-PR25661-5-v1.o b/tests/data/test-diff-filter/test-PR25661-5-v1.o
new file mode 100644
index 0000000000000000000000000000000000000000..1d32777cc2733aa0c62652eaad9039ca4f0d2e0e
GIT binary patch
literal 3280
zcmbtW-HRJl6hC(+vzhE<)9gmuT~}}frCo6*AKGnN*&1EeZn4;~W${UtNis=BlZj+z
z%c_WqQY;Eq5JV6~eDOiw`>YTC55CwJ@!7XRAM~6%_hu)zQxH9nx##@O=RNl;@9kW9
zNn;EoF}MiFl0*T@<}u&mYzr!|0{go+|Jc3x?%mx_cJBVlJ#W4CO9><7xRJ?Y6_0%r
z?%@=mF&<je@&szRWH6I~EpSjXXwY*;5XPXHT+$L<F30l5r>HeNU<K<{&K=Z&Ef#%p
zLVeNrk<bpXQljsW{zRf*FqxtMjQaeD%<~i~2j(>>GTr>05ORh>7uZUkqMNWt06RHr
zlbj9v+Ct2VP*)~S`+NvRlVSr~W>1zXmR?#fEn}SWDbzjzmUR&!oS0y-5xG;$vMyN9
zS?4WQ!J?NyHxuzPiVKxFZGH(ZSDF^qRKa4?eucCjSuDEj`8B<iggU)OMYLAGd~uDE
zSVk&|whB-@fu&JCcp85iO#r8zCOPFaM*Pe8)8Q%nGIp=DgkQe5uo(<uKZ>39UgPZM
zX5BgKT(7%bash)uA<2P)ev%pHhX(sr)6qE^PNuPa7)HS$^m{g~{+v)9PR4$<H|f0|
zR0ly^9R(e)6AZjyR27M;lr^gMyx8Nc(+m22ryq=Bf0|j*EPi+BO@V(#lW@Rub7p2h
z)ri8l*`-(Z?d|$GyIyl^ZryIw8nxOpji!BO4@=+mVxe$QS>LdoaeNs1TLVAzr$HA*
zZ`vJh9W^(d&1Y?A0R6+T3(5Q`5nS5dzF?oh92@^G0dFn9Lt<oa?*Th1v-M)$yoJm0
zDB|=Z;*0r0seD7*URr3QK>}xf(G`!!Uw>yQamBJ=+z_+nozv^6$s%1`9;!|UejBJT
zM&Xx5241)iBR~f{5E-PyQU*%)ct$fwhvI!G^>QWEhuJ~N+WmZulMr%dbhk*JG%^F>
zG@G9YL|RS;eispbAY_?O+S>wO6C<rqdNno#PWI__ucdH`9|`%Kf(sE}jf7-gx;%(>
zl3`ZoXF|TB;NJ=Pnu5z#dJ29+$U_DHK*)C#{GO2i6r<V)*>kF&KQF?r`~ds&55R4K
zQ;*Yq1~+<Z9D5xc<7qMuB^UcgF}QR^f!m29aHsyrb9;X0aBvWW{R!$g?{zv;|N5NT
zn|k5EpLZDrp)Xx91-RYGc<kdH;)avhckv2wqS))cAp-fsgZ|VT^T$#Y|38Lx^rha)
ze#%fS!N~n7ak5R*<0ufO-VFO`qZF_DpGF<+<NxYkM_)B2rIYzr6s02a2b2E#w8$w%
zAB#=(zHp#@MRxMc;j7DO3%_biQVaxmn_C@!RNFSHRE(@26Pr<<YZ*bcr3XB#{^b7{
zfb^&O>DE>2?;)nfL<p{m{8vdxk!nl#SXTa~xZ-7>C_mjxYW_D7qnNTzWI25!@-He0
zr6|XD&^pWi2eF|VBAy(ipBz6D{!%Z;-_#8$6w*)HeT58RqFf2X9pT@cAqgYrFX1nc
z$?9J_qkr_ONq^bbtlSiPm4YI*MZxs`&}*ss)0$*Wbtv}#BdAZ4I)93r=`ZU4KJARW
G>i-W$y!Chh

literal 0
HcmV?d00001

diff --git a/tests/data/test-diff-filter/test-PR25661-6-report-1.txt b/tests/data/test-diff-filter/test-PR25661-6-report-1.txt
new file mode 100644
index 0000000..9666a8f
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-6-report-1.txt
@@ -0,0 +1,3 @@ 
+Functions changes summary: 0 Removed, 0 Changed (1 filtered out), 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+
diff --git a/tests/data/test-diff-filter/test-PR25661-6-report-2.txt b/tests/data/test-diff-filter/test-PR25661-6-report-2.txt
new file mode 100644
index 0000000..0927b7a
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-6-report-2.txt
@@ -0,0 +1,5 @@ 
+Leaf changes summary: 0 artifact changed (1 filtered out)
+Changed leaf types summary: 0 (1 filtered out) leaf type changed
+Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
+Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable
+
diff --git a/tests/data/test-diff-filter/test-PR25661-6-report-3.txt b/tests/data/test-diff-filter/test-PR25661-6-report-3.txt
new file mode 100644
index 0000000..e0044f6
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-6-report-3.txt
@@ -0,0 +1,12 @@ 
+Functions changes summary: 0 Removed, 1 Changed, 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+
+1 function with some indirect sub-type change:
+
+  [C] 'function void foo(S*)' at test-v1.c:11:1 has some indirect sub-type changes:
+    parameter 1 of type 'S*' has sub-type changes:
+      in pointed to type 'struct S' at test-v1.c:1:1:
+        type size hasn't changed
+        data member 'S::a' was replaced by anonymous data member:
+          'union {int a; char added;}'
+
diff --git a/tests/data/test-diff-filter/test-PR25661-6-report-4.txt b/tests/data/test-diff-filter/test-PR25661-6-report-4.txt
new file mode 100644
index 0000000..a3f4c72
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-6-report-4.txt
@@ -0,0 +1,9 @@ 
+Leaf changes summary: 1 artifact changed
+Changed leaf types summary: 1 leaf type changed
+Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
+Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable
+
+'struct S at test-v0.c:1:1' changed:
+  type size hasn't changed
+  data member 'S::a' was replaced by anonymous data member:
+    'union {int a; char added;}'
diff --git a/tests/data/test-diff-filter/test-PR25661-6-v0.c b/tests/data/test-diff-filter/test-PR25661-6-v0.c
new file mode 100644
index 0000000..4c93e56
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-6-v0.c
@@ -0,0 +1,10 @@ 
+struct S
+{
+  int a;
+};
+
+void
+foo(struct S *s)
+{
+  s->a = 1;
+}
diff --git a/tests/data/test-diff-filter/test-PR25661-6-v0.o b/tests/data/test-diff-filter/test-PR25661-6-v0.o
new file mode 100644
index 0000000000000000000000000000000000000000..ec1797da0d1daa3c1deb9d0be5af339154db6e6b
GIT binary patch
literal 2656
zcmbtV&ubGw6n>L!n$)#1Z4?dlu!7PS%%(p>Td78i+M;Nw7Q9H=B->=QNlLOURS^%0
zP`p$SR1g%ri2sHMJ^45E;$3eA5BlEhOgfovDd>aDy!XBLz4vBzX7c3n^(zs^KnjEN
zuxm*a;6rp*&vP{oDd>g8+07rbn~!&9UtHe#%9(e+tfRk!$HaN6J^7AuXD~!=K?h@(
z&@dce1}Vtyh<Y_2qRaI<uo#DMkTmwLiB@M5U8F=_Rv;G7P!#K7XA&vXNc1IoSZ^$O
z9K9)K#7%R`JZG{L7U>2fPR3kn!o)(S!8FgCMjRk&vkU3K*PkYK{>1A`Y2tK7Tb#%V
zfY=e_%pneEsXEmK<SQZ@RUocT;urx7N5+09x^d(?kNd;Gcbk4@eaJ5INazakqLF_3
zJ`%yu-GFSRUUjpjdg-2*Eqi`;#Va@kuk3g$+4+T$@my{=yV{`eCS1L-Xw6t-_Jlod
zjSLNs59LNit$_u%WX(E$fM-T02dzxiU#q#(Ww+)wyds)Tqga{Vn8;;vV^*dNaCK&8
z${N6Qg9m4$704}M4;O%KBw63#SbPf?dJ=u~BjGU@6Upb1neNV;7!bgrBb}iR`1L2u
zX@s6w{MO>*=o|w*VPc3=tNRS-)FEyg&^nrJegnGNY-t#PCoV(`=ng@t|HMA{;XOEI
z?p5|d?!R)b=rXldatPzqRmqHmQ}^TeBk2hykhn16n1+*|Bppn=lhzSNY*647r+(-8
zed1+r6t6rzklZJpUw`z`IZ^9cBAzf62fO*8>N^FT{YEfXq~^ODKG-G4cfc+*n_xHG
z6~`{Qg|+gMS6i;5kLymM&~Vq=+)l%(mECsAidS<b1xtWktXHcpK5urd?z=YLsZ7&%
ziuXk#x3aX{aH_6o{6D8Y<kOzW{gmnQLTQ~7VN<Lsr_dlwzcIJfAVt^t{ph3b{jdCC
z<mx%8oUH$l3`O)<dZOvSD6hnj`cpLYN#Jy@C{CVDeCN4L<EZB($9L`pz$?`zdMur9
z9V5cD0EgPxdt<MmKdk>Z!5=J0KJ`zpk=}m^v9S780n++Z(PNj;8CL(Bs4w@4>eIWS
z*Iz}9a>_nY<?>k+dMp7k%ldMDgm@UgUtDJU0RA(<*TP`2?J?N(%DNJHEBJc&9RW<p
zKrzepOZXKkVf(iovVZj2OTOG!>PN3lxwiy3FB+zIn*K#}K7AAY?jZ!*0z5*7BKrC%
QZy0|^^#4)elB)Cn0^_%<SpWb4

literal 0
HcmV?d00001

diff --git a/tests/data/test-diff-filter/test-PR25661-6-v1.c b/tests/data/test-diff-filter/test-PR25661-6-v1.c
new file mode 100644
index 0000000..9e31021
--- /dev/null
+++ b/tests/data/test-diff-filter/test-PR25661-6-v1.c
@@ -0,0 +1,14 @@ 
+struct S
+{
+  union
+  {
+    int  a;
+    char added;
+  };
+};
+
+void
+foo(struct S *s)
+{
+  s->a = 1;
+}
diff --git a/tests/data/test-diff-filter/test-PR25661-6-v1.o b/tests/data/test-diff-filter/test-PR25661-6-v1.o
new file mode 100644
index 0000000000000000000000000000000000000000..21a6a89e89dddeda3b3fea16180c78fce773952f
GIT binary patch
literal 2792
zcmbtV&2Jk;6o2FOSF*L6#8hn*4vPe8BGv08Z7jD{T0>Oo0xD_RN?fXF?cLZ5u^p|q
zRRAF>wL(N(Kp+qw5(n;xD`yV;3;Y8}oRGLh;?TY~JI@|ZSE>Xf?Y#GX?{hxh?8n#M
zdOafq@DOkXCK96n%lQf4lx!0!umE>kM}M}CK00oFcJ26g>3Q$BuTh_sHboignRqWq
zWin;5!g>Q)%L9=kMtX|;EmG&Qxo{9eUdZf)+mIC^M7&rfEzIl<hahap<HTXb4_~4*
zJ3<vPnJ;=^7vCW(F)v;zS8S`iRGt?Lg}KwHJuPhel6~106)d6zR*{4oC}dG6vygik
zZ2O9B6#?>!EdvxP!YWSOizT>GX<$7wuDH6GL^``jnVM(5d2Nw0%;v@!GG_qnlNgqg
z!8xjwY6MK74PKN6N%k_%8E|kG;@@%!XL*Xa*8<{T6jvXtxgBW<Gf7+&EL%MKF_S@K
z&-Vi#uHU@tY&jR)hP&acudZ#Z*4NKF%R7PZw7fXMtLHDSIMsf9e-LbTgFz659TdH&
z)7w03)T{LiPPMBg?DV_{YQ14UsQE+x-LTdT<Jx}M_S#|B3-@c=JL?<u`daNEq9R8C
z*SEGVIm=kc%Jb{M74UY54|YHt&WWWHh2rP9=oe8(9}Q{qYI*LH%vLG?HX0;w>P&Yt
z3;#SxQ(CE=fV-#;%Ji{NlQt~X4W`Zlt*JDn1qDr-uolcr1dX#`+yWX(b(W@N7A(N4
zn=&jo@eE3l7qorg{nw71nsB_5S+v(z#Ssao>RC6BJPpQgCL)|tl<|8?UR0FMYkZxN
zUr{*O>*st`!}VReW8m^xK<q%_W_+%f;^&kNkvYYi_Q}IUO*mbrqwJ}6y?$^<@AqS`
zjWmuD+G8#b4r6eAFZRG~k4E4|!M^AELHmApHyrE@Q73t?-Hw6>Q)(~r2HjxVWj`DQ
z?1Cx4?F{?<0H0TPFpL8iTd+Ecz0P|oP|(}mi@biI3jd#E3H@nLct1H@Q!(RvrDU?z
z$4e*>W_B2#r*2EU>3<e6x*z}5e=Q{@LcsaIBS9NxetO>N&(_pzh;1c5O87(Jbgu~F
zyNv$`IW}=NW0K-;sRj66Q6{GCHY!YvY>yPU%8rVfwr?VyR==Z~g8iv}dcDm0eZ<U|
z2*Ixk<bEZLhH1NtSX%yVm7n*C@>9Q?`43b~t`kK$j#Z*h8Bmn-GyM>8;!XdDs+ryA
z_&-+uh8g@k^%zXFoR^VrmA`5Jivk*Ks3@<W;jfTM+rM7Q{?Xro{dr%h9y2$^{!4+|
qs$hE8>91q@)0vpPhY&QVC>$pfGM>%#Q`|KFhpPUc#-0gd`acEuEVPsW

literal 0
HcmV?d00001

diff --git a/tests/test-diff-filter.cc b/tests/test-diff-filter.cc
index 215485b..3ddbdbc 100644
--- a/tests/test-diff-filter.cc
+++ b/tests/test-diff-filter.cc
@@ -570,6 +570,174 @@  InOutSpec in_out_specs[] =
     "data/test-diff-filter/PR24787-report-0.txt",
     "output/test-diff-filter/PR24787-report-0.txt",
   },
+  {
+   "data/test-diff-filter/test-PR25661-1-v0.o",
+   "data/test-diff-filter/test-PR25661-1-v1.o",
+   "--no-default-suppression",
+   "data/test-diff-filter/test-PR25661-1-report-1.txt",
+   "output/test-diff-filter/test-PR25661-1-report-1.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-1-v0.o",
+   "data/test-diff-filter/test-PR25661-1-v1.o",
+   "--no-default-suppression --harmless",
+   "data/test-diff-filter/test-PR25661-1-report-2.txt",
+   "output/test-diff-filter/test-PR25661-1-report-2.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-1-v0.o",
+   "data/test-diff-filter/test-PR25661-1-v1.o",
+   "--no-default-suppression --leaf-changes-only",
+   "data/test-diff-filter/test-PR25661-1-report-3.txt",
+   "output/test-diff-filter/test-PR25661-1-report-3.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-1-v0.o",
+   "data/test-diff-filter/test-PR25661-1-v1.o",
+   "--no-default-suppression --harmless --leaf-changes-only",
+   "data/test-diff-filter/test-PR25661-1-report-4.txt",
+   "output/test-diff-filter/test-PR25661-1-report-4.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-2-v0.o",
+   "data/test-diff-filter/test-PR25661-2-v1.o",
+   "--no-default-suppression",
+   "data/test-diff-filter/test-PR25661-2-report-1.txt",
+   "output/test-diff-filter/test-PR25661-2-report-1.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-2-v0.o",
+   "data/test-diff-filter/test-PR25661-2-v1.o",
+   "--no-default-suppression --harmless",
+   "data/test-diff-filter/test-PR25661-2-report-2.txt",
+   "output/test-diff-filter/test-PR25661-2-report-2.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-2-v0.o",
+   "data/test-diff-filter/test-PR25661-2-v1.o",
+   "--no-default-suppression --leaf-changes-only",
+   "data/test-diff-filter/test-PR25661-2-report-3.txt",
+   "output/test-diff-filter/test-PR25661-2-report-3.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-2-v0.o",
+   "data/test-diff-filter/test-PR25661-2-v1.o",
+   "--no-default-suppression --harmless --leaf-changes-only",
+   "data/test-diff-filter/test-PR25661-2-report-4.txt",
+   "output/test-diff-filter/test-PR25661-2-report-4.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-3-v0.o",
+   "data/test-diff-filter/test-PR25661-3-v1.o",
+   "--no-default-suppression",
+   "data/test-diff-filter/test-PR25661-3-report-1.txt",
+   "output/test-diff-filter/test-PR25661-3-report-1.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-3-v0.o",
+   "data/test-diff-filter/test-PR25661-3-v1.o",
+   "--no-default-suppression --harmless",
+   "data/test-diff-filter/test-PR25661-3-report-2.txt",
+   "output/test-diff-filter/test-PR25661-3-report-2.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-3-v0.o",
+   "data/test-diff-filter/test-PR25661-3-v1.o",
+   "--no-default-suppression --leaf-changes-only",
+   "data/test-diff-filter/test-PR25661-3-report-3.txt",
+   "output/test-diff-filter/test-PR25661-3-report-3.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-3-v0.o",
+   "data/test-diff-filter/test-PR25661-3-v1.o",
+   "--no-default-suppression --harmless --leaf-changes-only",
+   "data/test-diff-filter/test-PR25661-3-report-4.txt",
+   "output/test-diff-filter/test-PR25661-3-report-4.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-4-v0.o",
+   "data/test-diff-filter/test-PR25661-4-v1.o",
+   "--no-default-suppression",
+   "data/test-diff-filter/test-PR25661-4-report-1.txt",
+   "output/test-diff-filter/test-PR25661-4-report-1.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-4-v0.o",
+   "data/test-diff-filter/test-PR25661-4-v1.o",
+   "--no-default-suppression --harmless",
+   "data/test-diff-filter/test-PR25661-4-report-2.txt",
+   "output/test-diff-filter/test-PR25661-4-report-2.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-4-v0.o",
+   "data/test-diff-filter/test-PR25661-4-v1.o",
+   "--no-default-suppression --leaf-changes-only",
+   "data/test-diff-filter/test-PR25661-4-report-3.txt",
+   "output/test-diff-filter/test-PR25661-4-report-3.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-4-v0.o",
+   "data/test-diff-filter/test-PR25661-4-v1.o",
+   "--no-default-suppression --harmless --leaf-changes-only",
+   "data/test-diff-filter/test-PR25661-4-report-4.txt",
+   "output/test-diff-filter/test-PR25661-4-report-4.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-5-v0.o",
+   "data/test-diff-filter/test-PR25661-5-v1.o",
+   "--no-default-suppression",
+   "data/test-diff-filter/test-PR25661-5-report-1.txt",
+   "output/test-diff-filter/test-PR25661-5-report-1.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-5-v0.o",
+   "data/test-diff-filter/test-PR25661-5-v1.o",
+   "--no-default-suppression --harmless",
+   "data/test-diff-filter/test-PR25661-5-report-2.txt",
+   "output/test-diff-filter/test-PR25661-5-report-2.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-5-v0.o",
+   "data/test-diff-filter/test-PR25661-5-v1.o",
+   "--no-default-suppression --leaf-changes-only",
+   "data/test-diff-filter/test-PR25661-5-report-3.txt",
+   "output/test-diff-filter/test-PR25661-5-report-3.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-5-v0.o",
+   "data/test-diff-filter/test-PR25661-5-v1.o",
+   "--no-default-suppression --harmless --leaf-changes-only",
+   "data/test-diff-filter/test-PR25661-5-report-4.txt",
+   "output/test-diff-filter/test-PR25661-5-report-4.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-6-v0.o",
+   "data/test-diff-filter/test-PR25661-6-v1.o",
+   "--no-default-suppression",
+   "data/test-diff-filter/test-PR25661-6-report-1.txt",
+   "output/test-diff-filter/test-PR25661-6-report-1.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-6-v0.o",
+   "data/test-diff-filter/test-PR25661-6-v1.o",
+   "--no-default-suppression --leaf-changes-only",
+   "data/test-diff-filter/test-PR25661-6-report-2.txt",
+   "output/test-diff-filter/test-PR25661-6-report-2.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-6-v0.o",
+   "data/test-diff-filter/test-PR25661-6-v1.o",
+   "--no-default-suppression --harmless",
+   "data/test-diff-filter/test-PR25661-6-report-3.txt",
+   "output/test-diff-filter/test-PR25661-6-report-3.txt",
+  },
+  {
+   "data/test-diff-filter/test-PR25661-6-v0.o",
+   "data/test-diff-filter/test-PR25661-6-v1.o",
+   "--no-default-suppression --harmless --leaf-changes-only",
+   "data/test-diff-filter/test-PR25661-6-report-4.txt",
+   "output/test-diff-filter/test-PR25661-6-report-4.txt",
+  },
   // This should be the last entry
   {NULL, NULL, NULL, NULL, NULL}
 };