abitidy: remove excess members if requested am: edc8f44d4f

Original change: https://android-review.googlesource.com/c/platform/external/libabigail/+/2393799

Change-Id: Ia83b7a135f85cf24ed3676df29e2a560bea29bd5
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/tools/abitidy.cc b/tools/abitidy.cc
index 4f65271..e23795f 100644
--- a/tools/abitidy.cc
+++ b/tools/abitidy.cc
@@ -1157,6 +1157,54 @@
     renumber_type_ids(child, type_id_map);
 }
 
+/// Determine whether one XML element is the same as another.
+///
+/// XML elements representing members are sometimes emitted multiple
+/// times, identically.
+///
+/// @param left the first element to compare
+///
+/// @param right the second element to compare
+///
+/// @return whether the first element is the same as the second
+bool
+equal_tree(xmlNodePtr left, xmlNodePtr right)
+{
+  // Node names must match.
+  const char* left_name = from_libxml(left->name);
+  const char* right_name = from_libxml(right->name);
+  if (strcmp(left_name, right_name) != 0)
+    return false;
+
+  // Attributes must match.
+  std::unordered_set<std::string> attributes;
+  for (auto properties : {left->properties, right->properties})
+    for (auto property = properties; property; property = property->next)
+      attributes.insert(from_libxml(property->name));
+  for (const auto& attribute : attributes)
+    {
+      const char* name = attribute.c_str();
+      const auto left_value = get_attribute(left, name);
+      const auto right_value = get_attribute(right, name);
+      if (left_value != right_value)
+        return false;
+    }
+
+  // The left subelements must be the same as the right ones.
+  xmlNodePtr left_child = xmlFirstElementChild(left);
+  xmlNodePtr right_child = xmlFirstElementChild(right);
+  while (left_child && right_child)
+    {
+      if (!equal_tree(left_child, right_child))
+        return false;
+      left_child = xmlNextElementSibling(left_child);
+      right_child = xmlNextElementSibling(right_child);
+    }
+  if (left_child || right_child)
+    return false;
+  return true;
+}
+
 /// Determine whether one XML element is a subtree of another.
 ///
 /// XML elements representing types are sometimes emitted multiple
@@ -1234,6 +1282,67 @@
   return !left_child;
 }
 
+
+/// Handle excess data members.
+///
+/// @param eliminate whether to eliminate
+///
+/// @param root the root XML element
+///
+/// @return the number of excess members
+size_t handle_excess_members(bool eliminate, xmlNodePtr root)
+{
+  std::vector<xmlNodePtr> types;
+
+  // find all structs and unions
+  std::function<void(xmlNodePtr)> dfs = [&](xmlNodePtr node) {
+    if (node->type != XML_ELEMENT_NODE)
+      return;
+    const char* node_name = from_libxml(node->name);
+    // preorder in case we delete a nested type
+    for (auto child : get_children(node))
+      dfs(child);
+    if (strcmp(node_name, "class-decl") || strcmp(node_name, "union-decl"))
+      types.push_back(node);
+  };
+  dfs(root);
+
+  size_t count = 0;
+  for (const auto& node : types)
+    {
+      // filter data members (skipping things like comments)
+      std::vector<xmlNodePtr> data_members;
+      for (auto child : get_children(node))
+        if (strcmp(from_libxml(child->name), "data-member") == 0)
+          data_members.push_back(child);
+      // look for identical duplicate data members - O(n^2)
+      for (size_t i = 0; i < data_members.size(); ++i)
+        {
+          const xmlNodePtr i_node = data_members[i];
+          bool duplicate = false;
+          for (size_t j = 0; j < i; ++j)
+            {
+              const xmlNodePtr j_node = data_members[j];
+              if (j_node != nullptr && equal_tree(i_node, j_node))
+                {
+                  duplicate = true;
+                  break;
+                }
+            }
+          if (duplicate)
+            {
+              std::cerr << "found duplicate data-member\n";
+              if (eliminate) {
+                remove_element(i_node);
+                data_members[i] = nullptr;
+              }
+              ++count;
+            }
+        }
+    }
+  return count;
+}
+
 /// Eliminate non-conflicting / report conflicting duplicate definitions.
 ///
 /// This function can eliminate exact type duplicates and duplicates
@@ -1917,6 +2026,7 @@
   bool opt_report_untyped = false;
   bool opt_abort_on_untyped = false;
   bool opt_clear_non_reachable = false;
+  bool opt_eliminate_excess_members = false;
   bool opt_eliminate_duplicates = false;
   bool opt_report_conflicts = false;
   bool opt_sort = false;
@@ -1935,7 +2045,7 @@
               << "  [-S|--symbols file]\n"
               << "  [-L|--locations {column|line|file|none}]\n"
               << "  [-I|--indentation n]\n"
-              << "  [-a|--all] (implies -n -r -t -f -p -u -b -e -c -s -d)\n"
+              << "  [-a|--all] (implies -n -r -t -f -p -u -b -x -e -c -s -d)\n"
               << "  [-n|--[no-]normalise-anonymous]\n"
               << "  [-r|--[no-]reanonymise-anonymous]\n"
               << "  [-t|--[no-]discard-naming-typedefs]\n"
@@ -1944,6 +2054,7 @@
               << "  [-u|--[no-]report-untyped]\n"
               << "  [-U|--abort-on-untyped-symbols]\n"
               << "  [-b|--[no-]clear-non-reachable]\n"
+              << "  [-x|--[no-]eliminate-excess-members\n"
               << "  [-e|--[no-]eliminate-duplicates]\n"
               << "  [-c|--[no-]report-conflicts]\n"
               << "  [-s|--[no-]sort]\n"
@@ -1988,6 +2099,7 @@
                                 = opt_prune_unreachable
                                 = opt_report_untyped
                                 = opt_clear_non_reachable
+                                = opt_eliminate_excess_members
                                 = opt_eliminate_duplicates
                                 = opt_report_conflicts
                                 = opt_sort
@@ -2024,6 +2136,10 @@
         opt_clear_non_reachable = true;
       else if (arg == "--no-clear-non-reachable")
         opt_clear_non_reachable = false;
+      else if (arg == "-x" || arg == "--eliminate-excess-members")
+        opt_eliminate_excess_members = true;
+      else if (arg == "--no-eliminate-excess-members")
+        opt_eliminate_excess_members = false;
       else if (arg == "-e" || arg == "--eliminate-duplicates")
         opt_eliminate_duplicates = true;
       else if (arg == "--no-eliminate-duplicates")
@@ -2140,6 +2256,9 @@
   if (opt_clear_non_reachable)
     clear_non_reachable(root);
 
+  // Handle excess data members.
+  handle_excess_members(opt_eliminate_excess_members, root);
+
   // Eliminate complete duplicates and extra fragments of types.
   // Report conflicting duplicate defintions.
   // Record whether there are conflicting duplicate definitions.