[5/7,applied] comparison: Represent changed unreachable anonymous unions, structs & enums

Message ID 87r0lp629r.fsf@redhat.com
State New
Headers
Series Support the Linux Kernel UAPI checker project |

Commit Message

Dodji Seketeli Oct. 20, 2023, 10:01 a.m. UTC
  Hello,

Following the changes to represent changed anonymous unreachable enums,
this patch does the same for anonymous unreachable unions, classes and
structs.

Basically, without this patch, this change:

    union
    {
      int a;
      int b;
    };

    ------

    union
    {
      int a;
      int b;
      int c;
    };

yields:

    1 removed type unreachable from any public interface:

      [D] 'union {int a; int b;}' at test_1.c:1:1

    1 added type unreachable from any public interface:

      [A] 'union {int a; int b; int c;}' at test_2.c:1:1

But with the patch, it does yield:

    1 changed type unreachable from any public interface:

      [C] 'union {int a; int b;}' changed:
	type size hasn't changed
	1 data member insertion:
	  'int c' at test-anon-union-v1.c:5:1
	type changed from:
	  union {int a; int b;}
	to:
	  union {int a; int b; int c;}

	* include/abg-fwd.h (class_or_union_types_of_same_kind)
	(is_data_member_of_anonymous_class_or_union): Declare new
	functions.
	* include/abg-ir.h (lookup_data_member): Likewise, declare a new overload.
	* src/abg-ir.cc (class_or_union_types_of_same_kind)
	(lookup_data_member, is_data_member_of_anonymous_class_or_union):
	Define news functions & overloads.
	* src/abg-reporter-priv.cc (represent): When representing a change
	in the name of a data member, if the context is an anonymous type,
	use the non-qualified name of the data member, not its qualified
	name.
	* src/abg-comparison.cc
	(corpus_diff::priv::ensure_lookup_tables_populated): Handle
	deleted/added anonymous enums, unions, classes and structs
	similarly.  That is, if an anonymous type was removed and another
	one got added, if they both have data members (or enumerators) in
	common, then we are probably looking at an anonymous type that was
	changed.  This is because these anonymous types are named using
	their flat representation.
	* tests/data/test-abidiff-exit/test-anon-types-report-1.txt: New
	reference test comparison output.
	* tests/data/test-abidiff-exit/test-anon-types-v{0,1}.o: New
	binary tests input files.
	* tests/data/test-abidiff-exit/test-anon-types-v{0,1}.c: Source
	code of new binary test input.
	* tests/data/Makefile.am: Add the new test material above to
	source distribution.
	* tests/test-abidiff-exit.cc (in_out_specs): Add the test inputs
	above to this test harness.

Signed-off-by: Dodji Seketeli <dodji@redhat.com>
Applied to master.
---
 include/abg-fwd.h                             |  17 ++
 include/abg-ir.h                              |   4 +
 src/abg-comparison.cc                         | 162 ++++++++++++------
 src/abg-ir.cc                                 |  92 ++++++++++
 src/abg-reporter-priv.cc                      |   8 +-
 tests/data/Makefile.am                        |   5 +
 .../test-anon-types-report-1.txt              |  31 ++++
 .../test-abidiff-exit/test-anon-types-v0.c    |  42 +++++
 .../test-abidiff-exit/test-anon-types-v0.o    | Bin 0 -> 3240 bytes
 .../test-abidiff-exit/test-anon-types-v1.c    |  33 ++++
 .../test-abidiff-exit/test-anon-types-v1.o    | Bin 0 -> 3168 bytes
 tests/test-abidiff-exit.cc                    |  16 ++
 12 files changed, 360 insertions(+), 50 deletions(-)
 create mode 100644 tests/data/test-abidiff-exit/test-anon-types-report-1.txt
 create mode 100644 tests/data/test-abidiff-exit/test-anon-types-v0.c
 create mode 100644 tests/data/test-abidiff-exit/test-anon-types-v0.o
 create mode 100644 tests/data/test-abidiff-exit/test-anon-types-v1.c
 create mode 100644 tests/data/test-abidiff-exit/test-anon-types-v1.o

new file mode 100644
index 00000000..73716a1f
index 4c1eaea5..b9ea4792 100644
  

Patch

diff --git a/include/abg-fwd.h b/include/abg-fwd.h
index e494de07..0edc9927 100644
--- a/include/abg-fwd.h
+++ b/include/abg-fwd.h
@@ -495,6 +495,14 @@  is_class_or_union_type(const type_or_decl_base*);
 class_or_union_sptr
 is_class_or_union_type(const type_or_decl_base_sptr&);
 
+bool
+class_or_union_types_of_same_kind(const class_or_union *,
+				  const class_or_union*);
+
+bool
+class_or_union_types_of_same_kind(const class_or_union_sptr&,
+				  const class_or_union_sptr&);
+
 bool
 is_union_type(const type_or_decl_base&);
 
@@ -731,6 +739,15 @@  is_anonymous_data_member(const var_decl*);
 bool
 is_anonymous_data_member(const var_decl&);
 
+bool
+is_data_member_of_anonymous_class_or_union(const var_decl&);
+
+bool
+is_data_member_of_anonymous_class_or_union(const var_decl*);
+
+bool
+is_data_member_of_anonymous_class_or_union(const var_decl_sptr&);
+
 const var_decl_sptr
 get_first_non_anonymous_data_member(const var_decl_sptr);
 
diff --git a/include/abg-ir.h b/include/abg-ir.h
index b599ef3e..0cb378ad 100644
--- a/include/abg-ir.h
+++ b/include/abg-ir.h
@@ -4626,6 +4626,10 @@  const var_decl*
 lookup_data_member(const type_base* type,
 		   const char* dm_name);
 
+const var_decl_sptr
+lookup_data_member(const type_base_sptr& type,
+		   const var_decl_sptr&  dm);
+
 const function_decl::parameter*
 get_function_parameter(const decl_base* fun,
 		       unsigned parm_num);
diff --git a/src/abg-comparison.cc b/src/abg-comparison.cc
index 1126ed58..8a705c54 100644
--- a/src/abg-comparison.cc
+++ b/src/abg-comparison.cc
@@ -9630,69 +9630,135 @@  corpus_diff::priv::ensure_lookup_tables_populated()
     // to show what the user expects, namely, a changed anonymous
     // enum.
     {
-      std::set<enum_type_decl_sptr> deleted_anon_enums;
-      std::set<enum_type_decl_sptr> added_anon_enums;
+      std::set<type_base_sptr> deleted_anon_types;
+      std::set<type_base_sptr> added_anon_types;
 
       for (auto entry : deleted_unreachable_types_)
-	if (is_enum_type(entry.second)
-	    && is_enum_type(entry.second)->get_is_anonymous())
-	  deleted_anon_enums.insert(is_enum_type(entry.second));
-
-      for (auto entry : added_unreachable_types_)
-	if (is_enum_type(entry.second)
-	    && is_enum_type(entry.second)->get_is_anonymous())
-	  added_anon_enums.insert(is_enum_type(entry.second));
+	{
+	  if ((is_enum_type(entry.second)
+	       && is_enum_type(entry.second)->get_is_anonymous())
+	      || (is_class_or_union_type(entry.second)
+		  && is_class_or_union_type(entry.second)->get_is_anonymous()))
+	  deleted_anon_types.insert(entry.second);
+	}
 
-      string_type_base_sptr_map added_anon_enum_to_erase;
-      string_type_base_sptr_map removed_anon_enum_to_erase;
 
-      // Look for deleted anonymous enums which have enumerators
-      // present in an added anonymous enums ...
-      for (auto deleted_enum : deleted_anon_enums)
+      for (auto entry : added_unreachable_types_)
+	if ((is_enum_type(entry.second)
+	     && is_enum_type(entry.second)->get_is_anonymous())
+	    || (is_class_or_union_type(entry.second)
+		&& is_class_or_union_type(entry.second)->get_is_anonymous()))
+	  added_anon_types.insert(entry.second);
+
+      string_type_base_sptr_map added_anon_types_to_erase;
+      string_type_base_sptr_map removed_anon_types_to_erase;
+      enum_type_decl_sptr deleted_enum;
+      class_or_union_sptr deleted_class;
+
+      // Look for deleted anonymous types (enums, unions, structs &
+      // classes) which have enumerators or data members present in an
+      // added anonymous type ...
+      for (auto deleted: deleted_anon_types)
 	{
-	  // Look for any enumerator of 'deleted_enum' that is also
-	  // present in an added anonymous enum.
-	  for (auto enr : deleted_enum->get_enumerators())
+	  deleted_enum = is_enum_type(deleted);
+	  deleted_class = is_class_or_union_type(deleted);
+
+	  // For enums, look for any enumerator of 'deleted_enum' that
+	  // is also present in an added anonymous enum.
+	  if (deleted_enum)
 	    {
-	      bool this_enum_got_changed = false;
-	      for (auto added_enum : added_anon_enums)
+	      for (auto enr : deleted_enum->get_enumerators())
 		{
-		  if (is_enumerator_present_in_enum(enr, *added_enum))
+		  bool this_enum_got_changed = false;
+		  for (auto t : added_anon_types)
 		    {
-		      // So the enumerator 'enr' from the
-		      // 'deleted_enum' enum is also present in the
-		      // 'added_enum' enum so we assume that
-		      // 'deleted_enum' and 'added_enum' are the same
-		      // enum that got changed.  Let's represent it
-		      // using a diff node.
-		      diff_sptr d = compute_diff(deleted_enum,
-						 added_enum, ctxt);
-		      ABG_ASSERT(d->has_changes());
-		      string repr =
-			abigail::ir::get_pretty_representation(is_type(deleted_enum),
-							       /*internal=*/false);
-		      changed_unreachable_types_[repr]= d;
-		      this_enum_got_changed = true;
-		      string r1 = abigail::ir::get_pretty_representation(is_type(deleted_enum));
-		      string r2 = abigail::ir::get_pretty_representation(is_type(added_enum));
-		      removed_anon_enum_to_erase[r1] = deleted_enum;
-		      added_anon_enum_to_erase[r2] = added_enum;
-		      break;
+		      if (enum_type_decl_sptr added_enum = is_enum_type(t))
+			if (is_enumerator_present_in_enum(enr, *added_enum))
+			  {
+			    // So the enumerator 'enr' from the
+			    // 'deleted_enum' enum is also present in the
+			    // 'added_enum' enum so we assume that
+			    // 'deleted_enum' and 'added_enum' are the same
+			    // enum that got changed.  Let's represent it
+			    // using a diff node.
+			    diff_sptr d = compute_diff(deleted_enum,
+						       added_enum, ctxt);
+			    ABG_ASSERT(d->has_changes());
+			    string repr =
+			      abigail::ir::get_pretty_representation(is_type(deleted_enum),
+								     /*internal=*/false);
+			    changed_unreachable_types_[repr]= d;
+			    this_enum_got_changed = true;
+			    string r1 =
+			      abigail::ir::get_pretty_representation(is_type(deleted_enum),
+								     /*internal=*/false);
+			    string r2 =
+			      abigail::ir::get_pretty_representation(is_type(added_enum),
+								     /*internal=*/false);
+			    removed_anon_types_to_erase[r1] = deleted_enum;
+			    added_anon_types_to_erase[r2] = added_enum;
+			    break;
+			  }
 		    }
+		  if (this_enum_got_changed)
+		    break;
+		}
+	    }
+	  else if (deleted_class)
+	    {
+	      // For unions, structs & classes, look for any data
+	      // member of 'deleted_class' that is also present in an
+	      // added anonymous class.
+	      for (auto dm : deleted_class->get_data_members())
+		{
+		  bool this_class_got_changed = false;
+		  for (auto klass : added_anon_types)
+		    {
+		      if (class_or_union_sptr added_class =
+			  is_class_or_union_type(klass))
+			if (class_or_union_types_of_same_kind(deleted_class,
+							      added_class)
+			    && lookup_data_member(added_class, dm))
+			  {
+			    // So the data member 'dm' from the
+			    // 'deleted_class' class is also present in
+			    // the 'added_class' class so we assume that
+			    // 'deleted_class' and 'added_class' are the
+			    // same anonymous class that got changed.
+			    // Let's represent it using a diff node.
+			    diff_sptr d = compute_diff(is_type(deleted_class),
+						       is_type(added_class),
+						       ctxt);
+			    ABG_ASSERT(d->has_changes());
+			    string repr =
+			      abigail::ir::get_pretty_representation(is_type(deleted_class),
+								     /*internal=*/false);
+			    changed_unreachable_types_[repr]= d;
+			    this_class_got_changed = true;
+			    string r1 =
+			      abigail::ir::get_pretty_representation(is_type(deleted_class),
+								     /*internal=*/false);
+			    string r2 =
+			      abigail::ir::get_pretty_representation(is_type(added_class),
+								     /*internal=*/false);
+			    removed_anon_types_to_erase[r1] = deleted_class;
+			    added_anon_types_to_erase[r2] = added_class;
+			    break;
+			  }
+		    }
+		  if (this_class_got_changed)
+		    break;
 		}
-	      if (this_enum_got_changed)
-		break;
 	    }
 	}
 
-      // Now remove the added/removed anonymous enums from their maps,
-      // as they are now represented as a changed enum, not an added
-      // and removed enum.
-
-      for (auto entry : added_anon_enum_to_erase)
+      // Now remove the added/removed anonymous types from their maps,
+      // as they are now represented as a changed type, not an added
+      // and removed anonymous type.
+      for (auto entry : added_anon_types_to_erase)
 	added_unreachable_types_.erase(entry.first);
 
-      for (auto entry : removed_anon_enum_to_erase)
+      for (auto entry : removed_anon_types_to_erase)
 	deleted_unreachable_types_.erase(entry.first);
     }
   }
diff --git a/src/abg-ir.cc b/src/abg-ir.cc
index 171267f8..bb9483c9 100644
--- a/src/abg-ir.cc
+++ b/src/abg-ir.cc
@@ -6061,6 +6061,47 @@  is_anonymous_data_member(const var_decl& d)
 	  && is_class_or_union_type(d.get_type()));
 }
 
+/// Test if a @ref var_decl is a data member belonging to an anonymous
+/// type.
+///
+/// @param d the @ref var_decl to consider.
+///
+/// @return true iff @p d is a data member belonging to an anonymous
+/// type.
+bool
+is_data_member_of_anonymous_class_or_union(const var_decl& d)
+{
+  if (is_data_member(d))
+    {
+      scope_decl* scope = d.get_scope();
+      if (scope && scope->get_is_anonymous())
+	return true;
+    }
+  return false;
+}
+
+/// Test if a @ref var_decl is a data member belonging to an anonymous
+/// type.
+///
+/// @param d the @ref var_decl to consider.
+///
+/// @return true iff @p d is a data member belonging to an anonymous
+/// type.
+bool
+is_data_member_of_anonymous_class_or_union(const var_decl* d)
+{return is_data_member_of_anonymous_class_or_union(*d);}
+
+/// Test if a @ref var_decl is a data member belonging to an anonymous
+/// type.
+///
+/// @param d the @ref var_decl to consider.
+///
+/// @return true iff @p d is a data member belonging to an anonymous
+/// type.
+bool
+is_data_member_of_anonymous_class_or_union(const var_decl_sptr& d)
+{return is_data_member_of_anonymous_class_or_union(d.get());}
+
 /// Get the @ref class_or_union type of a given anonymous data member.
 ///
 /// @param d the anonymous data member to consider.
@@ -10791,6 +10832,36 @@  shared_ptr<class_or_union>
 is_class_or_union_type(const shared_ptr<type_or_decl_base>& t)
 {return dynamic_pointer_cast<class_or_union>(t);}
 
+/// Test if two class or union types are of the same kind.
+///
+/// @param first the first type to consider.
+///
+/// @param second the second type to consider.
+///
+/// @return true iff @p first is of the same kind as @p second.
+bool
+class_or_union_types_of_same_kind(const class_or_union* first,
+				  const class_or_union* second)
+{
+  if ((is_class_type(first) && is_class_type(second))
+      || (is_union_type(first) && is_union_type(second)))
+    return true;
+
+  return false;
+}
+
+/// Test if two class or union types are of the same kind.
+///
+/// @param first the first type to consider.
+///
+/// @param second the second type to consider.
+///
+/// @return true iff @p first is of the same kind as @p second.
+bool
+class_or_union_types_of_same_kind(const class_or_union_sptr& first,
+				  const class_or_union_sptr& second)
+{return class_or_union_types_of_same_kind(first.get(), second.get());}
+
 /// Test if a type is a @ref union_decl.
 ///
 /// @param t the type to consider.
@@ -27430,6 +27501,27 @@  lookup_data_member(const type_base* type,
   return cou->find_data_member(dm_name).get();
 }
 
+/// Look for a data member of a given class, struct or union type and
+/// return it.
+///
+/// The data member is designated by its name.
+///
+/// @param type the class, struct or union type to consider.
+///
+/// @param dm the data member to lookup.
+///
+/// @return the data member iff it was found in @type or NULL if no
+/// data member with that name was found.
+const var_decl_sptr
+lookup_data_member(const type_base_sptr& type, const var_decl_sptr& dm)
+{
+  class_or_union_sptr cou = is_class_or_union_type(type);
+  if (!cou)
+    return var_decl_sptr();
+
+  return cou->find_data_member(dm);
+}
+
 /// Get the function parameter designated by its index.
 ///
 /// Note that the first function parameter has index 0.
diff --git a/src/abg-reporter-priv.cc b/src/abg-reporter-priv.cc
index cc38f240..c8122af3 100644
--- a/src/abg-reporter-priv.cc
+++ b/src/abg-reporter-priv.cc
@@ -402,8 +402,12 @@  represent(const var_diff_sptr	&diff,
   const bool o_anon = !!is_anonymous_data_member(o);
   const bool n_anon = !!is_anonymous_data_member(n);
   const bool is_strict_anonymous_data_member_change = o_anon && n_anon;
-  const string o_name = o->get_qualified_name();
-  const string n_name = n->get_qualified_name();
+  const string o_name = (is_data_member_of_anonymous_class_or_union(o)
+			 ? o->get_name()
+			 : o->get_qualified_name());
+  const string n_name = (is_data_member_of_anonymous_class_or_union(n)
+			 ? n->get_name()
+			 : n->get_qualified_name());
   const uint64_t o_size = get_var_size_in_bits(o);
   const uint64_t n_size = get_var_size_in_bits(n);
   const uint64_t o_offset = get_data_member_offset(o);
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index adb6d8bd..3134df7d 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -362,6 +362,11 @@  test-abidiff-exit/test-anonymous-enums-change-v0.c \
 test-abidiff-exit/test-anonymous-enums-change-v0.o \
 test-abidiff-exit/test-anonymous-enums-change-v1.c \
 test-abidiff-exit/test-anonymous-enums-change-v1.o \
+test-abidiff-exit/test-anon-types-report-1.txt \
+test-abidiff-exit/test-anon-types-v0.c \
+test-abidiff-exit/test-anon-types-v0.o \
+test-abidiff-exit/test-anon-types-v1.c \
+test-abidiff-exit/test-anon-types-v1.o \
 \
 test-diff-dwarf/test0-v0.cc		\
 test-diff-dwarf/test0-v0.o			\
diff --git a/tests/data/test-abidiff-exit/test-anon-types-report-1.txt b/tests/data/test-abidiff-exit/test-anon-types-report-1.txt
new file mode 100644
index 00000000..916b880d
--- /dev/null
+++ b/tests/data/test-abidiff-exit/test-anon-types-report-1.txt
@@ -0,0 +1,31 @@ 
+Functions changes summary: 0 Removed, 0 Changed, 0 Added function
+Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
+Unreachable types summary: 2 removed, 3 changed, 0 added types
+
+2 removed types unreachable from any public interface:
+
+  [D] 'struct {char z; int w;}' at test-anon-types-v0.c:34:1
+  [D] 'union {char x; unsigned int y;}' at test-anon-types-v0.c:28:1
+
+3 changed types unreachable from any public interface:
+
+  [C] 'struct {int a; int b;}' changed:
+    type size changed from 64 to 96 (in bits)
+    1 data member insertion:
+      'int c', at offset 64 (in bits) at test-anon-types-v1.c:18:1
+
+  [C] 'enum {a=0, b=1, c=2, d=3, }' changed:
+    type size hasn't changed
+    2 enumerator insertions:
+      'e' value '4'
+      'f' value '5'
+
+  [C] 'union {int a; int b; int d;}' changed:
+    type size hasn't changed
+    1 data member change:
+      name of 'd' changed to 'c' at test-anon-types-v1.c:11:1
+    type changed from:
+      union {int a; int b; int d;}
+    to:
+      union {int a; int b; int c;}
+
diff --git a/tests/data/test-abidiff-exit/test-anon-types-v0.c b/tests/data/test-abidiff-exit/test-anon-types-v0.c
new file mode 100644
index 00000000..2f9a2599
--- /dev/null
+++ b/tests/data/test-abidiff-exit/test-anon-types-v0.c
@@ -0,0 +1,42 @@ 
+/**
+ * Compile with:
+ *
+ * gcc -c -g -fno-eliminate-unused-debug-types test-anon-types-v{0,1}.c
+ */
+
+union
+{
+  int a;
+  int b;
+  int d;
+};
+
+struct
+{
+  int a;
+  int b;
+};
+
+enum
+{
+  a,
+  b,
+  c,
+  d
+};
+
+union
+{
+  char x;
+  unsigned y;
+};
+
+struct
+{
+  char z;
+  int w;
+};
+
+void
+fun()
+{}
diff --git a/tests/data/test-abidiff-exit/test-anon-types-v0.o b/tests/data/test-abidiff-exit/test-anon-types-v0.o
new file mode 100644
index 0000000000000000000000000000000000000000..32d7fdfc3631aa467094410a8df1e50d39792972
GIT binary patch
literal 3240
zcmbtWPj4Gl5TCbeCr&m=orKbs1ng9;NGf(6hf+eSgg`_S0;;M?<%Z0z{bDcJ>u7f!
zk|0#U0SR#_5=U-ufEx(D0Uv=&B+f{P11e_@Fta<)-7K#vDkJUA%y0g_H~XG__q8`~
z7(##|0UPi@Qz*bUqYvcGMBIcYU>5G|eERFXw}1K_p(2P9SrX5u6Q9g06Ay*aTWAq3
zjHQAH<EhXBD-obnDEdJR#i<Y-4HwEOY<Mu8NCgZnd`?Nu3Sy><j|akRf?>A6gpop0
zurU8UIUT|?r9?P_$^`kth!`POy!bP|jbpf!*ui)oE^~qLy+HtCX}B3`0pd##lVFzC
z7Q}VyCF}KBj6bEXTG-1Xdd#CxI47)yLh8F91UeUI5#i_*8My?OwPBf4xOday^3<$l
zR^}?xVrH~_4xq$V+}p8&*sv@txn@ym$~Nx6%NMYj1fY=3^T!u(Nzc);@WJ@xtzK#s
zN>_Xrg(CW1K`w;&Yig1hEgl~fD#<<=qGFs$(<XsM0b@heToOfN*D%q25JY}EkgX~W
zar@?->Q?>5YQ63}@6@X+%PTAO`bxdJcpDvd+?Wma)upQ4iwA*RZ_7Z2eiKzUY<AWU
zSFhREuG$AHRl8la_kzAHyME6P+*sO!U=Ycc-ICs*ZO2FZGJ<Bu4I!3MY`Z}}NJ0Bx
z*=a(p)9=YztKWLpueJTS*7ZHt^V_c9t$8wzWtc`ZyKWTK_Cv}MJzkKuycUG*t*y1{
zB5u;sf7l^Bb18}A+xS_R#oWYb=^oBz9$|Wjk{Q1;RsO`-njCo*9a32Prm+^_&%bp>
zoTLuM(>0v=DZ7al9hq1`M|etgFlNzzr6!C$#2*iuhD?~ypm;a|Pnw2IIHN(yfAU}W
zlZeb@_Gj3!cF1&<q@-psj%s%GssiD(N~O=FVL&)Nil{!3!q28EJjI+Qmz~lT#>rpx
zK+qQ%JiEI;=I{?$|Al!zzgi3BFR~A@GRt}Yi;S!OcUh-<i%8*jQx)pW6|VZe&Uik5
zm2ujG>^^`K9ra?@Lmr1I?<gsjhcP%UH+I4Cq6nN&c3mgAS{i<^*GC&&GY!}CLV0k?
z?1pa8#=9pQ()9zWLa>Aq32r2Mv)}7Uyda&RA4>;s06U7^=6jq(b{c!3+mpPI|H<Ys
zp7u!1Nu}FlVfg*BPQF=MK!vdU3E$2-W#oMPvuLC5?s4O3{`s6#PSyW|lnmv4>ARv^
zXfxwO;y!<@CyY-cCqH#Y&NIG_JfD+HFEIEO`ze<9P5$sGV}_rw<8lTgF(3a1V!Hl!
zc%Ukt`lngu`)_G-A_OHq95+eHP`-WxZMyo0T>l-8rTTP_<m>MvMmbfVC?@n4*Z)`n
z%vF8GKR{d;f5P$KJ|h0Jhs1xxA7t$j@pRv)Ub3#=<v(aP=6$I@!noaG3KhRPL{dTR
zpMt+eLAU=Wy#MMurTwR0F%_?HU2V$!8~5MghUxc)ejW4iG*jJDZ}It2zdPubP=5W?
QmoEN3_x}UODVC4_2gPCpxBvhE

literal 0
HcmV?d00001

diff --git a/tests/data/test-abidiff-exit/test-anon-types-v1.c b/tests/data/test-abidiff-exit/test-anon-types-v1.c
--- /dev/null
+++ b/tests/data/test-abidiff-exit/test-anon-types-v1.c
@@ -0,0 +1,33 @@ 
+/**
+ * Compile with:
+ *
+ * gcc -c -g -fno-eliminate-unused-debug-types test-anon-types-v{0,1}.c
+ */
+
+union
+{
+  int a;
+  int b;
+  int c;
+};
+
+struct
+{
+  int a;
+  int b;
+  int c;
+};
+
+enum
+{
+  a,
+  b,
+  c,
+  d,
+  e,
+  f
+};
+
+void
+fun()
+{}
diff --git a/tests/data/test-abidiff-exit/test-anon-types-v1.o b/tests/data/test-abidiff-exit/test-anon-types-v1.o
new file mode 100644
index 0000000000000000000000000000000000000000..868f00a98192c96378714174dffadd9680393fc1
GIT binary patch
literal 3168
zcmb_e&2Jl35TEC@V>_FqPF(ea6lH@zBo(`kO(`K&f+2#mDJrN^kI3D%pX~*E9eLML
zT0Y7N3317RBN7Kt1>y!5jv&;(qG$d9&KzK7cV4o2SyTj!v^z7u`FQi*y!Un=yz$nX
zIUzuifGs%I6bf*+cq}(#u?c6P3U{u5_S@mRzy5+y0Yr%`d3rrfuQa9Np^z6v@eW#Z
zE|g>L!F0^qU=oL^e8CSQD9(sE@geGl3zgVlcrZ^w-e|)D2_+~M^IxMN&WV`0D0(17
z8*-uphLDgKT`aDgk4u>DP680t(HvD>;!6-KV3aPISk^2G8_VLF`O0$fMiqxY39O?}
zeqKB`S2c~L#ie<%P^_HC1voEEbHl_aQ((~4vtXKArh$u6te&ux@?y(0%^THY=Q2Cb
z0Zcpa>O~wsPDHtz7w%ug3BG_&4j-Cxn$!%^7|FZ%6wvoFav{VYbF;)~;`pGj6t6)}
zEalE5Y0-ia0?J3KVTnR+H)o*xBnbUpAUhWIW#`r%YrFokRj)fQI(4hD)@ao0jk>jR
z8y&8@5gY6)tCl^CCV|}S$v}?%HmdHp-QPS|ziMB-V(&LByJy+mU}VdIKlB4PlJ+E+
zgtB9Iq&Ml=(Y-wxLajd<%35dCdC#x){HQkYJ=gPlu0N=GGK%Ebc7su1w+C(**7nAc
z45JX}o#1g2d%y0q|Hn+3>0xJkd&646^;!J~i-XI3HePh>$Nk$N4k}`CrdT?}##}<2
z9-_Fxugz6H&27(4y^anEocX4n=Hd50vy9lrEEo*lR=UwZOBNZ)5kF-zVCob`fRW7L
zl+A$hDJ=2F^OZpc%w(XHA5TpN8E_^8CI8t|_&G!tQtNYcs50A3cSu627t^SwXRk&e
zoMx%?g(M6Jr{^WsX?!AuCmO(0EKui;jwQ;M87F^L13?c{czShzU|i3mYNk8|_90eb
zS@(a2aaHFo>vTC0Df~gALY=w7zhM0ub3MPsIL$RZUvR>E!^riJN8^O|l@!T?2%L@^
zx!`zV2+mjzTqnLiT7J+Sp^fg6mg{+Axqrg!j@_V#7fd>2;0IEL;1EhAxE1T|(Qqj7
z7IcD9Bpp2Kb{M(s_c@8|x4L6@D0w3Pku738t&!@JO4nrJXrHo9zG+%Ug)sdj@1&hl
zvL63D+Gv0M+j#1~o|A@C<3A!LMY=ETEBc{mrhG_j@@D;+@p<Irr_RC!#y64cImtA`
z;8*OYnC?rz7&=D2-!r(T0-5W+HxbXOUo}+4Q~lISz5WhjdQODkF@v{ANs;c`LR;4O
zYrLWEax9Hc_k}+G9%7VJ)rn$Et9&>=QUG%`zTzJso)!O)<G*`C{O5lY|2A*P+7sgG
zzEQQLT|d@0G#hnas*kV}cM<BJaQr&QDW=v>>EED`wf={^{%W7n`qS4+#Vb5(Y|8x;
qkKgBl>AON-K|P*&nl)96&yV`Ppj%A({Hd<2_;0!XA307jJ^n9btofJ#

literal 0
HcmV?d00001

diff --git a/tests/test-abidiff-exit.cc b/tests/test-abidiff-exit.cc
--- a/tests/test-abidiff-exit.cc
+++ b/tests/test-abidiff-exit.cc
@@ -1064,6 +1064,22 @@  InOutSpec in_out_specs[] =
     "data/test-abidiff-exit/test-anonymous-enums-change-report-v1.txt",
     "output/test-abidiff-exit/test-anonymous-enums-change-report-v1.txt"
   },
+  {
+    "data/test-abidiff-exit/test-anon-types-v0.o",
+    "data/test-abidiff-exit/test-anon-types-v1.o",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "--no-default-suppression --harmless --non-reachable-types",
+    abigail::tools_utils::ABIDIFF_ABI_CHANGE
+    | abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE,
+    "data/test-abidiff-exit/test-anon-types-report-1.txt",
+    "output/test-abidiff-exit/test-anon-types-report-1.txt"
+  },
 #ifdef WITH_BTF
   {
     "data/test-abidiff-exit/btf/test0-v0.o",