]> sourceware.org Git - libabigail.git/commitdiff
comparison: Represent changed unreachable anonymous unions, structs & enums
authorDodji Seketeli <dodji@redhat.com>
Fri, 13 Oct 2023 09:06:08 +0000 (11:06 +0200)
committerDodji Seketeli <dodji@redhat.com>
Tue, 17 Oct 2023 08:50:51 +0000 (10:50 +0200)
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>
12 files changed:
include/abg-fwd.h
include/abg-ir.h
src/abg-comparison.cc
src/abg-ir.cc
src/abg-reporter-priv.cc
tests/data/Makefile.am
tests/data/test-abidiff-exit/test-anon-types-report-1.txt [new file with mode: 0644]
tests/data/test-abidiff-exit/test-anon-types-v0.c [new file with mode: 0644]
tests/data/test-abidiff-exit/test-anon-types-v0.o [new file with mode: 0644]
tests/data/test-abidiff-exit/test-anon-types-v1.c [new file with mode: 0644]
tests/data/test-abidiff-exit/test-anon-types-v1.o [new file with mode: 0644]
tests/test-abidiff-exit.cc

index e494de07b55b3776c57c25b44f98b06631189073..0edc992741ab0e0d43a47a19349422fff3b77222 100644 (file)
@@ -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);
 
index b599ef3e7ea3d13c0af5495be2a0c0dfc695adf7..0cb378ad87c4730f0fb27712e8d871a95007ad96 100644 (file)
@@ -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);
index 1126ed58306d5f62acd833148f20a7685abfe803..8a705c547e79fb64068292c645416b1da7ecd73e 100644 (file)
@@ -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);
     }
   }
index 171267f829ad61ff89cf079cb1ccb93b1c690a26..bb9483c921c364d38d880c6264db933bd96575d7 100644 (file)
@@ -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.
index cc38f240ecb9de38111b1a05e1a94996fba96baf..c8122af3cf74196890f2b289afc285b1d322b4da 100644 (file)
@@ -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);
index adb6d8bd00f7896534c5201f9259c8709c4ab848..3134df7d8f30545a043957e896ebd8446857adbf 100644 (file)
@@ -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 (file)
index 0000000..916b880
--- /dev/null
@@ -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 (file)
index 0000000..2f9a259
--- /dev/null
@@ -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 (file)
index 0000000..32d7fdf
Binary files /dev/null and b/tests/data/test-abidiff-exit/test-anon-types-v0.o differ
diff --git a/tests/data/test-abidiff-exit/test-anon-types-v1.c b/tests/data/test-abidiff-exit/test-anon-types-v1.c
new file mode 100644 (file)
index 0000000..73716a1
--- /dev/null
@@ -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 (file)
index 0000000..868f00a
Binary files /dev/null and b/tests/data/test-abidiff-exit/test-anon-types-v1.o differ
index 4c1eaea59830692903bbcaa3a4dd61a1bd948f9d..b9ea47927490fc5bfcbe794c6cfdb79b31e2f749 100644 (file)
@@ -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",
This page took 0.068461 seconds and 5 git commands to generate.