Bug 1286613 - Add dummy implementations for most remaining OSX zone allocator functions. r?njn draft
authorMike Hommey <mh+mozilla@glandium.org>
Wed, 18 Jan 2017 14:35:11 +0900
changeset 462911 d20b9dbf961ca49584fee3b0421127946c271b7f
parent 462910 e5367fe69d779eca6f43c8ae6ca0d0cbbf7d0cff
child 462912 168f1439a595502a548cb918e0ae18abfbb519f3
push id41897
push userbmo:mh+mozilla@glandium.org
push dateWed, 18 Jan 2017 06:55:36 +0000
reviewersnjn
bugs1286613
milestone53.0a1
Bug 1286613 - Add dummy implementations for most remaining OSX zone allocator functions. r?njn Some system libraries are using malloc_default_zone() and then using some of the malloc_zone_* API. Under normal conditions, those functions check the malloc_zone_t/malloc_introspection_t struct for the values that are allowed to be NULL, so that a NULL deref doesn't happen. As of OSX 10.12, malloc_default_zone() doesn't return the actual default zone anymore, but returns a fake, wrapper zone. The wrapper zone defines all the possible functions in the malloc_zone_t/malloc_introspection_t struct (almost), and calls the function from the registered default zone (jemalloc in our case) on its own. Without checking whether the pointers are NULL. This means that a system library that calls e.g. malloc_zone_batch_malloc(malloc_default_zone(), ...) ends up trying to call jemalloc_zone.batch_malloc, which is NULL, and crash follows. So as of OSX 10.12, the default zone is required to have all the functions available (really, the same as the wrapper zone), even if they do nothing. This is arguably a bug in libsystem_malloc in OSX 10.12, but jemalloc still needs to work in that case. [Adapted from https://github.com/jemalloc/jemalloc/commit/c6943acb3c56d1b3d1e82dd43b3fcfeae7771990]
memory/build/zone.c
--- a/memory/build/zone.c
+++ b/memory/build/zone.c
@@ -168,22 +168,79 @@ zone_valloc(malloc_zone_t *zone, size_t 
 
 static void
 zone_destroy(malloc_zone_t *zone)
 {
   /* This function should never be called. */
   MOZ_CRASH();
 }
 
+static unsigned
+zone_batch_malloc(malloc_zone_t *zone, size_t size, void **results,
+    unsigned num_requested)
+{
+  unsigned i;
+
+  for (i = 0; i < num_requested; i++) {
+    results[i] = malloc_impl(size);
+    if (!results[i])
+      break;
+  }
+
+  return i;
+}
+
+static void
+zone_batch_free(malloc_zone_t *zone, void **to_be_freed,
+    unsigned num_to_be_freed)
+{
+  unsigned i;
+
+  for (i = 0; i < num_to_be_freed; i++) {
+    zone_free(zone, to_be_freed[i]);
+    to_be_freed[i] = NULL;
+  }
+}
+
+static size_t
+zone_pressure_relief(malloc_zone_t *zone, size_t goal)
+{
+  return 0;
+}
+
 static size_t
 zone_good_size(malloc_zone_t *zone, size_t size)
 {
   return malloc_good_size_impl(size);
 }
 
+static kern_return_t
+zone_enumerator(task_t task, void *data, unsigned type_mask,
+    vm_address_t zone_address, memory_reader_t reader,
+    vm_range_recorder_t recorder)
+{
+  return KERN_SUCCESS;
+}
+
+static boolean_t
+zone_check(malloc_zone_t *zone)
+{
+  return true;
+}
+
+static void
+zone_print(malloc_zone_t *zone, boolean_t verbose)
+{
+}
+
+static void
+zone_log(malloc_zone_t *zone, void *address)
+{
+}
+
 #ifdef MOZ_JEMALLOC
 
 #include "jemalloc/internal/jemalloc_internal.h"
 
 static void
 zone_force_lock(malloc_zone_t *zone)
 {
   /* /!\ This calls into jemalloc. It works because we're linked in the
@@ -212,16 +269,41 @@ zone_force_lock(malloc_zone_t *zone)
 
 static void
 zone_force_unlock(malloc_zone_t *zone)
 {
 }
 
 #endif
 
+static void
+zone_statistics(malloc_zone_t *zone, malloc_statistics_t *stats)
+{
+  /* We make no effort to actually fill the values */
+  stats->blocks_in_use = 0;
+  stats->size_in_use = 0;
+  stats->max_size_in_use = 0;
+  stats->size_allocated = 0;
+}
+
+static boolean_t
+zone_locked(malloc_zone_t *zone)
+{
+  /* Pretend no lock is being held */
+  return false;
+}
+
+static void
+zone_reinit_lock(malloc_zone_t *zone)
+{
+  /* As of OSX 10.12, this function is only used when force_unlock would
+   * be used if the zone version were < 9. So just use force_unlock. */
+  zone_force_unlock(zone);
+}
+
 static malloc_zone_t zone;
 static struct malloc_introspection_t zone_introspect;
 
 static malloc_zone_t *get_default_zone()
 {
   malloc_zone_t **zones = NULL;
   unsigned int num_zones = 0;
 
@@ -262,40 +344,41 @@ register_zone(void)
   zone.free = zone_free;
   zone.realloc = zone_realloc;
   zone.destroy = zone_destroy;
 #ifdef MOZ_REPLACE_MALLOC
   zone.zone_name = "replace_malloc_zone";
 #else
   zone.zone_name = "jemalloc_zone";
 #endif
-  zone.batch_malloc = NULL;
-  zone.batch_free = NULL;
+  zone.batch_malloc = zone_batch_malloc;
+  zone.batch_free = zone_batch_free;
   zone.introspect = &zone_introspect;
-  zone.version = 8;
+  zone.version = 9;
   zone.memalign = zone_memalign;
   zone.free_definite_size = zone_free_definite_size;
-  zone.pressure_relief = NULL;
-  zone_introspect.enumerator = NULL;
+  zone.pressure_relief = zone_pressure_relief;
+  zone_introspect.enumerator = zone_enumerator;
   zone_introspect.good_size = zone_good_size;
-  zone_introspect.check = NULL;
-  zone_introspect.print = NULL;
-  zone_introspect.log = NULL;
+  zone_introspect.check = zone_check;
+  zone_introspect.print = zone_print;
+  zone_introspect.log = zone_log;
   zone_introspect.force_lock = zone_force_lock;
   zone_introspect.force_unlock = zone_force_unlock;
-  zone_introspect.statistics = NULL;
-  zone_introspect.zone_locked = NULL;
+  zone_introspect.statistics = zone_statistics;
+  zone_introspect.zone_locked = zone_locked;
   zone_introspect.enable_discharge_checking = NULL;
   zone_introspect.disable_discharge_checking = NULL;
   zone_introspect.discharge = NULL;
 #ifdef __BLOCKS__
   zone_introspect.enumerate_discharged_pointers = NULL;
 #else
   zone_introspect.enumerate_unavailable_without_blocks = NULL;
 #endif
+  zone_introspect.reinit_lock = zone_reinit_lock;
 
   /*
    * The default purgeable zone is created lazily by OSX's libc.  It uses
    * the default zone when it is created for "small" allocations
    * (< 15 KiB), but assumes the default zone is a scalable_zone.  This
    * obviously fails when the default zone is the jemalloc zone, so
    * malloc_default_purgeable_zone is called beforehand so that the
    * default purgeable zone is created when the default zone is still