b1dca6
commit 785969a047ad2f23f758901c6816422573544453
b1dca6
Author: Florian Weimer <fweimer@redhat.com>
b1dca6
Date:   Fri Dec 4 09:13:43 2020 +0100
b1dca6
b1dca6
    elf: Implement a string table for ldconfig, with tail merging
b1dca6
    
b1dca6
    This will be used in ldconfig to reduce the ld.so.cache size slightly.
b1dca6
    
b1dca6
    Tail merging is an optimization where a pointer points into another
b1dca6
    string if the first string is a suffix of the second string.
b1dca6
    
b1dca6
    The hash function FNV-1a was chosen because it is simple and achieves
b1dca6
    good dispersion even for short strings (so that the hash table bucket
b1dca6
    count can be a power of two).  It is clearly superior to the hsearch
b1dca6
    hash and the ELF hash in this regard.
b1dca6
    
b1dca6
    The hash table uses chaining for collision resolution.
b1dca6
    
b1dca6
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
b1dca6
b1dca6
diff --git a/elf/Makefile b/elf/Makefile
b1dca6
index f795617780b393ec..abb3e9d1179ef5cd 100644
b1dca6
--- a/elf/Makefile
b1dca6
+++ b/elf/Makefile
b1dca6
@@ -163,7 +163,7 @@ tests-container = \
b1dca6
 
b1dca6
 tests := tst-tls9 tst-leaks1 \
b1dca6
 	tst-array1 tst-array2 tst-array3 tst-array4 tst-array5 \
b1dca6
-	tst-auxv
b1dca6
+	tst-auxv tst-stringtable
b1dca6
 tests-internal := tst-tls1 tst-tls2 $(tests-static-internal)
b1dca6
 tests-static := $(tests-static-normal) $(tests-static-internal)
b1dca6
 
b1dca6
diff --git a/elf/stringtable.c b/elf/stringtable.c
b1dca6
new file mode 100644
b1dca6
index 0000000000000000..099347d73ee70b8f
b1dca6
--- /dev/null
b1dca6
+++ b/elf/stringtable.c
b1dca6
@@ -0,0 +1,209 @@
b1dca6
+/* String tables for ld.so.cache construction.  Implementation.
b1dca6
+   Copyright (C) 2020 Free Software Foundation, Inc.
b1dca6
+   This file is part of the GNU C Library.
b1dca6
+
b1dca6
+   This program is free software; you can redistribute it and/or modify
b1dca6
+   it under the terms of the GNU General Public License as published
b1dca6
+   by the Free Software Foundation; version 2 of the License, or
b1dca6
+   (at your option) any later version.
b1dca6
+
b1dca6
+   This program is distributed in the hope that it will be useful,
b1dca6
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
b1dca6
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
b1dca6
+   GNU General Public License for more details.
b1dca6
+
b1dca6
+   You should have received a copy of the GNU General Public License
b1dca6
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
b1dca6
+
b1dca6
+#include <assert.h>
b1dca6
+#include <error.h>
b1dca6
+#include <ldconfig.h>
b1dca6
+#include <libintl.h>
b1dca6
+#include <stdlib.h>
b1dca6
+#include <string.h>
b1dca6
+#include <stringtable.h>
b1dca6
+
b1dca6
+static void
b1dca6
+stringtable_init (struct stringtable *table)
b1dca6
+{
b1dca6
+  table->count = 0;
b1dca6
+
b1dca6
+  /* This needs to be a power of two.  128 is sufficient to keep track
b1dca6
+     of 42 DSOs without resizing (assuming two strings per DSOs).
b1dca6
+     glibc itself comes with more than 20 DSOs, so 64 would likely to
b1dca6
+     be too small.  */
b1dca6
+  table->allocated = 128;
b1dca6
+
b1dca6
+  table->entries = xcalloc (table->allocated, sizeof (table->entries[0]));
b1dca6
+}
b1dca6
+
b1dca6
+/* 32-bit FNV-1a hash function.  */
b1dca6
+static uint32_t
b1dca6
+fnv1a (const char *string, size_t length)
b1dca6
+{
b1dca6
+  const unsigned char *p = (const unsigned char *) string;
b1dca6
+  uint32_t hash = 2166136261U;
b1dca6
+  for (size_t i = 0; i < length; ++i)
b1dca6
+    {
b1dca6
+      hash ^= p[i];
b1dca6
+      hash *= 16777619U;
b1dca6
+    }
b1dca6
+  return hash;
b1dca6
+}
b1dca6
+
b1dca6
+/* Double the capacity of the hash table.  */
b1dca6
+static void
b1dca6
+stringtable_rehash (struct stringtable *table)
b1dca6
+{
b1dca6
+  /* This computation cannot overflow because the old total in-memory
b1dca6
+     size of the hash table is larger than the computed value.  */
b1dca6
+  uint32_t new_allocated = table->allocated * 2;
b1dca6
+  struct stringtable_entry **new_entries
b1dca6
+    = xcalloc (new_allocated, sizeof (table->entries[0]));
b1dca6
+
b1dca6
+  uint32_t mask = new_allocated - 1;
b1dca6
+  for (uint32_t i = 0; i < table->allocated; ++i)
b1dca6
+    for (struct stringtable_entry *e = table->entries[i]; e != NULL; )
b1dca6
+      {
b1dca6
+        struct stringtable_entry *next = e->next;
b1dca6
+        uint32_t hash = fnv1a (e->string, e->length);
b1dca6
+        uint32_t new_index = hash & mask;
b1dca6
+        e->next = new_entries[new_index];
b1dca6
+        new_entries[new_index] = e;
b1dca6
+        e = next;
b1dca6
+      }
b1dca6
+
b1dca6
+  free (table->entries);
b1dca6
+  table->entries = new_entries;
b1dca6
+  table->allocated = new_allocated;
b1dca6
+}
b1dca6
+
b1dca6
+struct stringtable_entry *
b1dca6
+stringtable_add (struct stringtable *table, const char *string)
b1dca6
+{
b1dca6
+  /* Check for a zero-initialized table.  */
b1dca6
+  if (table->allocated == 0)
b1dca6
+    stringtable_init (table);
b1dca6
+
b1dca6
+  size_t length = strlen (string);
b1dca6
+  if (length > (1U << 30))
b1dca6
+    error (EXIT_FAILURE, 0, _("String table string is too long"));
b1dca6
+  uint32_t hash = fnv1a (string, length);
b1dca6
+
b1dca6
+  /* Return a previously-existing entry.  */
b1dca6
+  for (struct stringtable_entry *e
b1dca6
+         = table->entries[hash & (table->allocated - 1)];
b1dca6
+       e != NULL; e = e->next)
b1dca6
+    if (e->length == length && memcmp (e->string, string, length) == 0)
b1dca6
+      return e;
b1dca6
+
b1dca6
+  /* Increase the size of the table if necessary.  Keep utilization
b1dca6
+     below two thirds.  */
b1dca6
+  if (table->count >= (1U << 30))
b1dca6
+    error (EXIT_FAILURE, 0, _("String table has too many entries"));
b1dca6
+  if (table->count * 3 > table->allocated * 2)
b1dca6
+    stringtable_rehash (table);
b1dca6
+
b1dca6
+  /* Add the new table entry.  */
b1dca6
+  ++table->count;
b1dca6
+  struct stringtable_entry *e
b1dca6
+    = xmalloc (offsetof (struct stringtable_entry, string) + length + 1);
b1dca6
+  uint32_t index = hash & (table->allocated - 1);
b1dca6
+  e->next = table->entries[index];
b1dca6
+  table->entries[index] = e;
b1dca6
+  e->length = length;
b1dca6
+  e->offset = 0;
b1dca6
+  memcpy (e->string, string, length + 1);
b1dca6
+  return e;
b1dca6
+}
b1dca6
+
b1dca6
+/* Sort reversed strings in reverse lexicographic order.  This is used
b1dca6
+   for tail merging.  */
b1dca6
+static int
b1dca6
+finalize_compare (const void *l, const void *r)
b1dca6
+{
b1dca6
+  struct stringtable_entry *left = *(struct stringtable_entry **) l;
b1dca6
+  struct stringtable_entry *right = *(struct stringtable_entry **) r;
b1dca6
+  size_t to_compare;
b1dca6
+  if (left->length < right->length)
b1dca6
+    to_compare = left->length;
b1dca6
+  else
b1dca6
+    to_compare = right->length;
b1dca6
+  for (size_t i = 1; i <= to_compare; ++i)
b1dca6
+    {
b1dca6
+      unsigned char lch = left->string[left->length - i];
b1dca6
+      unsigned char rch = right->string[right->length - i];
b1dca6
+      if (lch != rch)
b1dca6
+        return rch - lch;
b1dca6
+    }
b1dca6
+  if (left->length == right->length)
b1dca6
+    return 0;
b1dca6
+  else if (left->length < right->length)
b1dca6
+    /* Longer strings should come first.  */
b1dca6
+    return 1;
b1dca6
+  else
b1dca6
+    return -1;
b1dca6
+}
b1dca6
+
b1dca6
+void
b1dca6
+stringtable_finalize (struct stringtable *table,
b1dca6
+                      struct stringtable_finalized *result)
b1dca6
+{
b1dca6
+  if (table->count == 0)
b1dca6
+    {
b1dca6
+      result->strings = xstrdup ("");
b1dca6
+      result->size = 0;
b1dca6
+      return;
b1dca6
+    }
b1dca6
+
b1dca6
+  /* Optimize the order of the strings.  */
b1dca6
+  struct stringtable_entry **array = xcalloc (table->count, sizeof (*array));
b1dca6
+  {
b1dca6
+    size_t j = 0;
b1dca6
+    for (uint32_t i = 0; i < table->allocated; ++i)
b1dca6
+      for (struct stringtable_entry *e = table->entries[i]; e != NULL;
b1dca6
+           e = e->next)
b1dca6
+        {
b1dca6
+          array[j] = e;
b1dca6
+          ++j;
b1dca6
+        }
b1dca6
+    assert (j == table->count);
b1dca6
+  }
b1dca6
+  qsort (array, table->count, sizeof (*array), finalize_compare);
b1dca6
+
b1dca6
+  /* Assign offsets, using tail merging (sharing suffixes) if possible.  */
b1dca6
+  array[0]->offset = 0;
b1dca6
+  for (uint32_t j = 1; j < table->count; ++j)
b1dca6
+    {
b1dca6
+      struct stringtable_entry *previous = array[j - 1];
b1dca6
+      struct stringtable_entry *current = array[j];
b1dca6
+      if (previous->length >= current->length
b1dca6
+          && memcmp (&previous->string[previous->length - current->length],
b1dca6
+                     current->string, current->length) == 0)
b1dca6
+        current->offset = (previous->offset + previous->length
b1dca6
+                           - current->length);
b1dca6
+      else if (__builtin_add_overflow (previous->offset,
b1dca6
+                                       previous->length + 1,
b1dca6
+                                       &current->offset))
b1dca6
+        error (EXIT_FAILURE, 0, _("String table is too large"));
b1dca6
+    }
b1dca6
+
b1dca6
+  /* Allocate the result string.  */
b1dca6
+  {
b1dca6
+    struct stringtable_entry *last = array[table->count - 1];
b1dca6
+    if (__builtin_add_overflow (last->offset, last->length + 1,
b1dca6
+                                &result->size))
b1dca6
+      error (EXIT_FAILURE, 0, _("String table is too large"));
b1dca6
+  }
b1dca6
+  /* The strings are copied from the hash table, so the array is no
b1dca6
+     longer needed.  */
b1dca6
+  free (array);
b1dca6
+  result->strings = xcalloc (result->size, 1);
b1dca6
+
b1dca6
+  /* Copy the strings.  */
b1dca6
+  for (uint32_t i = 0; i < table->allocated; ++i)
b1dca6
+    for (struct stringtable_entry *e = table->entries[i]; e != NULL;
b1dca6
+         e = e->next)
b1dca6
+      if (result->strings[e->offset] == '\0')
b1dca6
+        memcpy (&result->strings[e->offset], e->string, e->length + 1);
b1dca6
+}
b1dca6
diff --git a/elf/stringtable.h b/elf/stringtable.h
b1dca6
new file mode 100644
b1dca6
index 0000000000000000..7d57d1bda9602947
b1dca6
--- /dev/null
b1dca6
+++ b/elf/stringtable.h
b1dca6
@@ -0,0 +1,64 @@
b1dca6
+/* String tables for ld.so.cache construction.
b1dca6
+   Copyright (C) 2020 Free Software Foundation, Inc.
b1dca6
+   This file is part of the GNU C Library.
b1dca6
+
b1dca6
+   This program is free software; you can redistribute it and/or modify
b1dca6
+   it under the terms of the GNU General Public License as published
b1dca6
+   by the Free Software Foundation; version 2 of the License, or
b1dca6
+   (at your option) any later version.
b1dca6
+
b1dca6
+   This program is distributed in the hope that it will be useful,
b1dca6
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
b1dca6
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
b1dca6
+   GNU General Public License for more details.
b1dca6
+
b1dca6
+   You should have received a copy of the GNU General Public License
b1dca6
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
b1dca6
+
b1dca6
+#ifndef _STRINGTABLE_H
b1dca6
+#define _STRINGTABLE_H
b1dca6
+
b1dca6
+#include <stddef.h>
b1dca6
+#include <stdint.h>
b1dca6
+
b1dca6
+/* An entry in the string table.  Only the length and string fields are
b1dca6
+   expected to be used outside the string table code.  */
b1dca6
+struct stringtable_entry
b1dca6
+{
b1dca6
+  struct stringtable_entry *next; /* For collision resolution.  */
b1dca6
+  uint32_t length;                /* Length of then string.  */
b1dca6
+  uint32_t offset;                /* From start of finalized table.  */
b1dca6
+  char string[];                  /* Null-terminated string.  */
b1dca6
+};
b1dca6
+
b1dca6
+/* A string table.  Zero-initialization produces a valid atable.  */
b1dca6
+struct stringtable
b1dca6
+{
b1dca6
+  struct stringtable_entry **entries;  /* Array of hash table buckets.  */
b1dca6
+  uint32_t count;                 /* Number of elements in the table.  */
b1dca6
+  uint32_t allocated;             /* Length of the entries array.  */
b1dca6
+};
b1dca6
+
b1dca6
+/* Adds STRING to TABLE.  May return the address of an existing entry.  */
b1dca6
+struct stringtable_entry *stringtable_add (struct stringtable *table,
b1dca6
+                                           const char *string);
b1dca6
+
b1dca6
+/* Result of stringtable_finalize.  SIZE bytes at STRINGS should be
b1dca6
+   written to the file.  */
b1dca6
+struct stringtable_finalized
b1dca6
+{
b1dca6
+  char *strings;
b1dca6
+  size_t size;
b1dca6
+};
b1dca6
+
b1dca6
+/* Assigns offsets to string table entries and computes the serialized
b1dca6
+   form of the string table.  */
b1dca6
+void stringtable_finalize (struct stringtable *table,
b1dca6
+                           struct stringtable_finalized *result);
b1dca6
+
b1dca6
+/* Deallocate the string table (but not the TABLE pointer itself).
b1dca6
+   (The table can be re-used for adding more strings without
b1dca6
+   initialization.)  */
b1dca6
+void stringtable_free (struct stringtable *table);
b1dca6
+
b1dca6
+#endif /* _STRINGTABLE_H */
b1dca6
diff --git a/elf/stringtable_free.c b/elf/stringtable_free.c
b1dca6
new file mode 100644
b1dca6
index 0000000000000000..8588a254705d4df8
b1dca6
--- /dev/null
b1dca6
+++ b/elf/stringtable_free.c
b1dca6
@@ -0,0 +1,33 @@
b1dca6
+/* String tables for ld.so.cache construction.  Deallocation (for tests only).
b1dca6
+   Copyright (C) 2020 Free Software Foundation, Inc.
b1dca6
+   This file is part of the GNU C Library.
b1dca6
+
b1dca6
+   This program is free software; you can redistribute it and/or modify
b1dca6
+   it under the terms of the GNU General Public License as published
b1dca6
+   by the Free Software Foundation; version 2 of the License, or
b1dca6
+   (at your option) any later version.
b1dca6
+
b1dca6
+   This program is distributed in the hope that it will be useful,
b1dca6
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
b1dca6
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
b1dca6
+   GNU General Public License for more details.
b1dca6
+
b1dca6
+   You should have received a copy of the GNU General Public License
b1dca6
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
b1dca6
+
b1dca6
+#include <stdlib.h>
b1dca6
+#include <stringtable.h>
b1dca6
+
b1dca6
+void
b1dca6
+stringtable_free (struct stringtable *table)
b1dca6
+{
b1dca6
+  for (uint32_t i = 0; i < table->allocated; ++i)
b1dca6
+    for (struct stringtable_entry *e = table->entries[i]; e != NULL; )
b1dca6
+      {
b1dca6
+        struct stringtable_entry *next = e->next;
b1dca6
+        free (e);
b1dca6
+        e = next;
b1dca6
+      }
b1dca6
+  free (table->entries);
b1dca6
+  *table = (struct stringtable) { 0, };
b1dca6
+}
b1dca6
diff --git a/elf/tst-stringtable.c b/elf/tst-stringtable.c
b1dca6
new file mode 100644
b1dca6
index 0000000000000000..3731086037567d57
b1dca6
--- /dev/null
b1dca6
+++ b/elf/tst-stringtable.c
b1dca6
@@ -0,0 +1,181 @@
b1dca6
+/* Unit test for ldconfig string tables.
b1dca6
+   Copyright (C) 2020 Free Software Foundation, Inc.
b1dca6
+   This file is part of the GNU C Library.
b1dca6
+
b1dca6
+   This program is free software; you can redistribute it and/or modify
b1dca6
+   it under the terms of the GNU General Public License as published
b1dca6
+   by the Free Software Foundation; version 2 of the License, or
b1dca6
+   (at your option) any later version.
b1dca6
+
b1dca6
+   This program is distributed in the hope that it will be useful,
b1dca6
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
b1dca6
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
b1dca6
+   GNU General Public License for more details.
b1dca6
+
b1dca6
+   You should have received a copy of the GNU General Public License
b1dca6
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
b1dca6
+
b1dca6
+#include <array_length.h>
b1dca6
+#include <stdlib.h>
b1dca6
+#include <string.h>
b1dca6
+#include <stringtable.h>
b1dca6
+#include <support/check.h>
b1dca6
+#include <support/support.h>
b1dca6
+
b1dca6
+static int
b1dca6
+do_test (void)
b1dca6
+{
b1dca6
+  /* Empty string table.  */
b1dca6
+  {
b1dca6
+    struct stringtable s = { 0, };
b1dca6
+    struct stringtable_finalized f;
b1dca6
+    stringtable_finalize (&s, &f);
b1dca6
+    TEST_COMPARE_STRING (f.strings, "");
b1dca6
+    TEST_COMPARE (f.size, 0);
b1dca6
+    free (f.strings);
b1dca6
+    stringtable_free (&s);
b1dca6
+  }
b1dca6
+
b1dca6
+  /* String table with one empty string.  */
b1dca6
+  {
b1dca6
+    struct stringtable s = { 0, };
b1dca6
+    struct stringtable_entry *e = stringtable_add (&s, "");
b1dca6
+    TEST_COMPARE_STRING (e->string, "");
b1dca6
+    TEST_COMPARE (e->length, 0);
b1dca6
+    TEST_COMPARE (s.count, 1);
b1dca6
+
b1dca6
+    struct stringtable_finalized f;
b1dca6
+    stringtable_finalize (&s, &f);
b1dca6
+    TEST_COMPARE (e->offset, 0);
b1dca6
+    TEST_COMPARE_STRING (f.strings, "");
b1dca6
+    TEST_COMPARE (f.size, 1);
b1dca6
+    free (f.strings);
b1dca6
+    stringtable_free (&s);
b1dca6
+  }
b1dca6
+
b1dca6
+  /* String table with one non-empty string.  */
b1dca6
+  {
b1dca6
+    struct stringtable s = { 0, };
b1dca6
+    struct stringtable_entry *e = stringtable_add (&s, "name");
b1dca6
+    TEST_COMPARE_STRING (e->string, "name");
b1dca6
+    TEST_COMPARE (e->length, 4);
b1dca6
+    TEST_COMPARE (s.count, 1);
b1dca6
+
b1dca6
+    struct stringtable_finalized f;
b1dca6
+    stringtable_finalize (&s, &f);
b1dca6
+    TEST_COMPARE (e->offset, 0);
b1dca6
+    TEST_COMPARE_STRING (f.strings, "name");
b1dca6
+    TEST_COMPARE (f.size, 5);
b1dca6
+    free (f.strings);
b1dca6
+    stringtable_free (&s);
b1dca6
+  }
b1dca6
+
b1dca6
+  /* Two strings, one is a prefix of the other.  Tail-merging can only
b1dca6
+     happen in one way in this case.  */
b1dca6
+  {
b1dca6
+    struct stringtable s = { 0, };
b1dca6
+    struct stringtable_entry *suffix = stringtable_add (&s, "suffix");
b1dca6
+    TEST_COMPARE_STRING (suffix->string, "suffix");
b1dca6
+    TEST_COMPARE (suffix->length, 6);
b1dca6
+    TEST_COMPARE (s.count, 1);
b1dca6
+
b1dca6
+    struct stringtable_entry *prefix
b1dca6
+      = stringtable_add (&s, "prefix-suffix");
b1dca6
+    TEST_COMPARE_STRING (prefix->string, "prefix-suffix");
b1dca6
+    TEST_COMPARE (prefix->length, strlen ("prefix-suffix"));
b1dca6
+    TEST_COMPARE (s.count, 2);
b1dca6
+
b1dca6
+    struct stringtable_finalized f;
b1dca6
+    stringtable_finalize (&s, &f);
b1dca6
+    TEST_COMPARE (prefix->offset, 0);
b1dca6
+    TEST_COMPARE (suffix->offset, strlen ("prefix-"));
b1dca6
+    TEST_COMPARE_STRING (f.strings, "prefix-suffix");
b1dca6
+    TEST_COMPARE (f.size, sizeof ("prefix-suffix"));
b1dca6
+    free (f.strings);
b1dca6
+    stringtable_free (&s);
b1dca6
+  }
b1dca6
+
b1dca6
+  /* String table with various shared prefixes.  Triggers hash
b1dca6
+     resizing.  */
b1dca6
+  {
b1dca6
+    enum { count = 1500 };
b1dca6
+    char *strings[2 * count];
b1dca6
+    struct stringtable_entry *entries[2 * count];
b1dca6
+    struct stringtable s = { 0, };
b1dca6
+    for (int i = 0; i < count; ++i)
b1dca6
+      {
b1dca6
+        strings[i] = xasprintf ("%d", i);
b1dca6
+        entries[i] = stringtable_add (&s, strings[i]);
b1dca6
+        TEST_COMPARE (entries[i]->length, strlen (strings[i]));
b1dca6
+        TEST_COMPARE_STRING (entries[i]->string, strings[i]);
b1dca6
+        strings[i + count] = xasprintf ("prefix/%d", i);
b1dca6
+        entries[i + count] = stringtable_add (&s, strings[i + count]);
b1dca6
+        TEST_COMPARE (entries[i + count]->length, strlen (strings[i + count]));
b1dca6
+        TEST_COMPARE_STRING (entries[i + count]->string, strings[i + count]);
b1dca6
+      }
b1dca6
+
b1dca6
+    struct stringtable_finalized f;
b1dca6
+    stringtable_finalize (&s, &f);
b1dca6
+
b1dca6
+    for (int i = 0; i < 2 * count; ++i)
b1dca6
+      {
b1dca6
+        TEST_COMPARE (entries[i]->length, strlen (strings[i]));
b1dca6
+        TEST_COMPARE_STRING (entries[i]->string, strings[i]);
b1dca6
+        TEST_COMPARE_STRING (f.strings + entries[i]->offset, strings[i]);
b1dca6
+        free (strings[i]);
b1dca6
+      }
b1dca6
+
b1dca6
+    free (f.strings);
b1dca6
+    stringtable_free (&s);
b1dca6
+  }
b1dca6
+
b1dca6
+  /* Verify that maximum tail merging happens.  */
b1dca6
+  {
b1dca6
+    struct stringtable s = { 0, };
b1dca6
+    const char *strings[] = {
b1dca6
+      "",
b1dca6
+      "a",
b1dca6
+      "b",
b1dca6
+      "aa",
b1dca6
+      "aaa",
b1dca6
+      "aa",
b1dca6
+      "bb",
b1dca6
+      "b",
b1dca6
+      "a",
b1dca6
+      "ba",
b1dca6
+      "baa",
b1dca6
+    };
b1dca6
+    struct stringtable_entry *entries[array_length (strings)];
b1dca6
+    for (int i = 0; i < array_length (strings); ++i)
b1dca6
+      entries[i] = stringtable_add (&s, strings[i]);
b1dca6
+    for (int i = 0; i < array_length (strings); ++i)
b1dca6
+      TEST_COMPARE_STRING (entries[i]->string, strings[i]);
b1dca6
+
b1dca6
+    struct stringtable_finalized f;
b1dca6
+    stringtable_finalize (&s, &f);
b1dca6
+
b1dca6
+    /* There are only four different strings, "aaa", "ba", "baa",
b1dca6
+       "bb".  The rest is shared in an unspecified fashion.  */
b1dca6
+    TEST_COMPARE (f.size, 4 + 3 + 4 + 3);
b1dca6
+
b1dca6
+    for (int i = 0; i < array_length (strings); ++i)
b1dca6
+      {
b1dca6
+        TEST_COMPARE_STRING (entries[i]->string, strings[i]);
b1dca6
+        TEST_COMPARE_STRING (f.strings + entries[i]->offset, strings[i]);
b1dca6
+      }
b1dca6
+
b1dca6
+    free (f.strings);
b1dca6
+    stringtable_free (&s);
b1dca6
+  }
b1dca6
+
b1dca6
+  return 0;
b1dca6
+}
b1dca6
+
b1dca6
+#include <support/test-driver.c>
b1dca6
+
b1dca6
+/* Re-compile the string table implementation here.  It is not
b1dca6
+   possible to link against the actual build because it was built for
b1dca6
+   use in ldconfig.  */
b1dca6
+#define _(arg) arg
b1dca6
+#include "stringtable.c"
b1dca6
+#include "stringtable_free.c"