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 462914 d65098f39b5febaa761696bd57cb7b348a54bab1
parent 462913 80ea3175bb5a1f1575219be6059db6a153372721
child 462915 b363831ee11a557331155f3ef1ae4e8fc6c1c9f6
push id41898
push userbmo:mh+mozilla@glandium.org
push dateWed, 18 Jan 2017 06:57:47 +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