abitidy: normalise anonymous type names

Currently libabigail exposes the internal names used for anonymous
types in ABI XML. The names are not stable - they are subject to
renumbering - resulting in "harmless" diffs and difficulties in
keeping ABI XML under version control.

	* tools/abitidy.cc (set_attribute): Store an attribute
	value.
	(normalise_anonymous_type_names): Normalise anonymous type
	names by stripping off numerical suffices.
	(main): Add normalise-anonymous option processing and action.

Bug: 181269148
Change-Id: Ic4d29605a4d4f7f6951ff9f27b536e5f76f32c74
Signed-off-by: Giuliano Procida <gprocida@google.com>
diff --git a/tools/abitidy.cc b/tools/abitidy.cc
index fdb14c5..7c083bb 100644
--- a/tools/abitidy.cc
+++ b/tools/abitidy.cc
@@ -109,6 +109,23 @@
   return result;
 }
 
+/// Store an attribute value.
+///
+/// @param node the node
+///
+/// @param name the attribute name
+///
+/// @param value the attribute value, optionally
+static void
+set_attribute(xmlNodePtr node, const char* name,
+              const std::optional<std::string>& value)
+{
+  if (value)
+    xmlSetProp(node, to_libxml(name), to_libxml(value.value().c_str()));
+  else
+    xmlUnsetProp(node, to_libxml(name));
+}
+
 /// Remove text nodes, recursively.
 ///
 /// This simplifies subsequent analysis and manipulation. Removing and
@@ -444,6 +461,43 @@
     remove_unseen(node);
 }
 
+static const std::map<std::string, std::string> ANONYMOUS_TYPE_NAMES = {
+  {"enum-decl", "__anonymous_enum__"},
+  {"class-decl", "__anonymous_struct__"},
+  {"union-decl", "__anonymous_union__"},
+};
+
+/// Normalise anonymous type names by removing the numerical suffix.
+///
+/// Anonymous type names take the form __anonymous_foo__N where foo is
+/// one of enum, struct or union and N is an optional numerical suffix.
+/// The suffices are senstive to processing order and do not convey
+/// useful ABI information. They can cause spurious harmless diffs and
+/// make XML diffing and rebasing harder.
+///
+/// @param node the XML node to process
+static void
+normalise_anonymous_type_names(xmlNodePtr node)
+{
+  if (node->type != XML_ELEMENT_NODE)
+    return;
+
+  const auto it = ANONYMOUS_TYPE_NAMES.find(from_libxml(node->name));
+  if (it != ANONYMOUS_TYPE_NAMES.end())
+    if (const auto attribute = get_attribute(node, "name"))
+      {
+        const auto& anon = it->second;
+        const auto& name = attribute.value();
+        // __anonymous_foo__123 -> __anonymous_foo__
+        if (!name.compare(0, anon.size(), anon) &&
+            name.find_first_not_of("0123456789", anon.size()) == name.npos)
+          set_attribute(node, "name", anon);
+      }
+
+  for (auto child : get_children(node))
+    normalise_anonymous_type_names(child);
+}
+
 /// Main program.
 ///
 /// Read and write ABI XML, with optional extra processing passes.
@@ -462,6 +516,7 @@
   int opt_indentation = 2;
   bool opt_drop_empty = false;
   bool opt_prune_unreachable = false;
+  bool opt_normalise_anonymous = false;
 
   // Process command line.
   auto usage = [&]() -> int {
@@ -472,6 +527,7 @@
               << " [-a|--all]"
               << " [-d|--[no-]drop-empty]"
               << " [-p|--[no-]prune-unreachable]"
+              << " [-n|--[no-]normalise-anonymous]"
               << '\n';
     return 1;
   };
@@ -496,7 +552,7 @@
             exit(usage());
         }
       else if (!strcmp(arg, "-a") || !strcmp(arg, "--all"))
-        opt_drop_empty = opt_prune_unreachable = true;
+        opt_drop_empty = opt_prune_unreachable = opt_normalise_anonymous = true;
       else if (!strcmp(arg, "-d") || !strcmp(arg, "--drop-empty"))
         opt_drop_empty = true;
       else if (!strcmp(arg, "--no-drop-empty"))
@@ -505,6 +561,10 @@
         opt_prune_unreachable = true;
       else if (!strcmp(arg, "--no-prune-unreachable"))
         opt_prune_unreachable = false;
+      else if (!strcmp(arg, "-n") || !strcmp(arg, "--normalise-anonymous"))
+        opt_normalise_anonymous = true;
+      else if (!strcmp(arg, "--no-normalise-anonymous"))
+        opt_normalise_anonymous = false;
       else
         exit(usage());
     }
@@ -537,6 +597,11 @@
   for (xmlNodePtr node = document->children; node; node = node->next)
     strip_text(node);
 
+  // Normalise anonymous type names.
+  if (opt_normalise_anonymous)
+    for (xmlNodePtr node = document->children; node; node = node->next)
+      normalise_anonymous_type_names(node);
+
   // Prune unreachable elements.
   if (opt_prune_unreachable)
     prune_unreachable(document);