/*
 * Copyright (C) 2013-2018 Tobias Brunner
 * Copyright (C) 2006-2013 Martin Willi
 * HSR Hochschule fuer Technik Rapperswil
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

#define _GNU_SOURCE
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <locale.h>
#ifdef HAVE_DLADDR
#include <dlfcn.h>
#endif
#include <time.h>
#include <errno.h>

#ifdef __APPLE__
#include <sys/mman.h>
#include <malloc/malloc.h>
/* overload some of our types clashing with mach */
#define host_t strongswan_host_t
#define processor_t strongswan_processor_t
#define thread_t strongswan_thread_t
#endif /* __APPLE__ */

#include "leak_detective.h"

#include <library.h>
#include <utils/utils.h>
#include <utils/debug.h>
#include <utils/backtrace.h>
#include <collections/hashtable.h>
#include <threading/thread_value.h>
#include <threading/spinlock.h>

typedef struct private_leak_detective_t private_leak_detective_t;

/**
 * private data of leak_detective
 */
struct private_leak_detective_t {

	/**
	 * public functions
	 */
	leak_detective_t public;

	/**
	 * Registered report() function
	 */
	leak_detective_report_cb_t report_cb;

	/**
	 * Registered report() summary function
	 */
	leak_detective_summary_cb_t report_scb;

	/**
	 * Registered user data for callbacks
	 */
	void *report_data;
};

/**
 * Magic value which helps to detect memory corruption. Yummy!
 */
#define MEMORY_HEADER_MAGIC 0x7ac0be11

/**
 * Magic written to tail of allocation
 */
#define MEMORY_TAIL_MAGIC 0xcafebabe

/**
 * Pattern which is filled in memory before freeing it
 */
#define MEMORY_FREE_PATTERN 0xFF

/**
 * Pattern which is filled in newly allocated memory
 */
#define MEMORY_ALLOC_PATTERN 0xEE

typedef struct memory_header_t memory_header_t;
typedef struct memory_tail_t memory_tail_t;

/**
 * Header which is prepended to each allocated memory block
 */
struct memory_header_t {

	/**
	 * Pointer to previous entry in linked list
	 */
	memory_header_t *previous;

	/**
	 * Pointer to next entry in linked list
	 */
	memory_header_t *next;

	/**
	 * backtrace taken during (re-)allocation
	 */
	backtrace_t *backtrace;

	/**
	 * Padding to make sizeof(memory_header_t) == 32
	 */
	uint32_t padding[sizeof(void*) == sizeof(uint32_t) ? 3 : 0];

	/**
	 * Number of bytes following after the header
	 */
	uint32_t bytes;

	/**
	 * magic bytes to detect bad free or heap underflow, MEMORY_HEADER_MAGIC
	 */
	uint32_t magic;

}__attribute__((__packed__));

/**
 * tail appended to each allocated memory block
 */
struct memory_tail_t {

	/**
	 * Magic bytes to detect heap overflow, MEMORY_TAIL_MAGIC
	 */
	uint32_t magic;

}__attribute__((__packed__));

/**
 * first mem header is just a dummy to chain
 * the others on it...
 */
static memory_header_t first_header = {
	.magic = MEMORY_HEADER_MAGIC,
};

/**
 * Spinlock to access header linked list
 */
static spinlock_t *lock;

/**
 * Is leak detection currently enabled?
 */
static bool enabled;

/**
 * Whether to report calls to free() with memory not allocated by us
 */
static bool ignore_unknown;

/**
 * Is leak detection disabled for the current thread?
 */
static thread_value_t *thread_disabled;

/**
 * Installs the malloc hooks, enables leak detection
 */
static void enable_leak_detective()
{
	enabled = TRUE;
}

/**
 * Uninstalls the malloc hooks, disables leak detection
 */
static void disable_leak_detective()
{
	enabled = FALSE;
}

/**
 * Enable/Disable leak detective for the current thread
 *
 * @return Previous value
 */
static bool enable_thread(bool enable)
{
	bool before;

	before = thread_disabled->get(thread_disabled) == NULL;
	thread_disabled->set(thread_disabled, enable ? NULL : (void*)TRUE);
	return before;
}

/**
 * Add a header to the beginning of the list
 */
static void add_hdr(memory_header_t *hdr)
{
	lock->lock(lock);
	hdr->next = first_header.next;
	if (hdr->next)
	{
		hdr->next->previous = hdr;
	}
	hdr->previous = &first_header;
	first_header.next = hdr;
	lock->unlock(lock);
}

/**
 * Remove a header from the list
 */
static void remove_hdr(memory_header_t *hdr)
{
	lock->lock(lock);
	if (hdr->next)
	{
		hdr->next->previous = hdr->previous;
	}
	hdr->previous->next = hdr->next;
	lock->unlock(lock);
}

/**
 * Check if a header is in the list
 */
static bool has_hdr(memory_header_t *hdr)
{
	memory_header_t *current;
	bool found = FALSE;

	lock->lock(lock);
	for (current = &first_header; current != NULL; current = current->next)
	{
		if (current == hdr)
		{
			found = TRUE;
			break;
		}
	}
	lock->unlock(lock);

	return found;
}

#ifdef __APPLE__

/**
 * Copy of original default zone, with functions we call in hooks
 */
static malloc_zone_t original;

/**
 * Call original malloc()
 */
static void* real_malloc(size_t size)
{
	return original.malloc(malloc_default_zone(), size);
}

/**
 * Call original free()
 */
static void real_free(void *ptr)
{
	original.free(malloc_default_zone(), ptr);
}

/**
 * Call original realloc()
 */
static void* real_realloc(void *ptr, size_t size)
{
	return original.realloc(malloc_default_zone(), ptr, size);
}

/**
 * Hook definition: static function with _hook suffix, takes additional zone
 */
#define HOOK(ret, name, ...) \
	static ret name ## _hook(malloc_zone_t *_z, __VA_ARGS__)

/**
 * forward declaration of hooks
 */
HOOK(void*, malloc, size_t bytes);
HOOK(void*, calloc, size_t nmemb, size_t size);
HOOK(void*, valloc, size_t size);
HOOK(void, free, void *ptr);
HOOK(void*, realloc, void *old, size_t bytes);

/**
 * malloc zone size(), must consider the memory header prepended
 */
HOOK(size_t, size, const void *ptr)
{
	bool before;
	size_t size;

	if (enabled)
	{
		before = enable_thread(FALSE);
		if (before)
		{
			ptr -= sizeof(memory_header_t);
		}
	}
	size = original.size(malloc_default_zone(), ptr);
	if (enabled)
	{
		enable_thread(before);
	}
	return size;
}

/**
 * Version of malloc zones we currently support
 */
#define MALLOC_ZONE_VERSION 8 /* Snow Leopard */

/**
 * Hook-in our malloc functions into the default zone
 */
static bool register_hooks()
{
	static bool once = FALSE;
	malloc_zone_t *zone;
	void *page;

	if (once)
	{
		return TRUE;
	}
	once = TRUE;

	zone = malloc_default_zone();
	if (zone->version != MALLOC_ZONE_VERSION)
	{
		DBG1(DBG_CFG, "malloc zone version %d unsupported (requiring %d)",
			 zone->version, MALLOC_ZONE_VERSION);
		return FALSE;
	}

	original = *zone;

	page = (void*)((uintptr_t)zone / getpagesize() * getpagesize());
	if (mprotect(page, getpagesize(), PROT_WRITE | PROT_READ) != 0)
	{
		DBG1(DBG_CFG, "malloc zone unprotection failed: %s", strerror(errno));
		return FALSE;
	}

	zone->size = size_hook;
	zone->malloc = malloc_hook;
	zone->calloc = calloc_hook;
	zone->valloc = valloc_hook;
	zone->free = free_hook;
	zone->realloc = realloc_hook;

	/* those other functions can be NULLed out to not use them */
	zone->batch_malloc = NULL;
	zone->batch_free = NULL;
	zone->memalign = NULL;
	zone->free_definite_size = NULL;

	return TRUE;
}

#else /* !__APPLE__ */

/**
 * dlsym() might do a malloc(), but we can't do one before we get the malloc()
 * function pointer. Use this minimalistic malloc implementation instead.
 */
static void* malloc_for_dlsym(size_t size)
{
	static char buf[1024] = {};
	static size_t used = 0;
	char *ptr;

	/* roundup to a multiple of 32 */
	size = (size - 1) / 32 * 32 + 32;

	if (used + size > sizeof(buf))
	{
		return NULL;
	}
	ptr = buf + used;
	used += size;
	return ptr;
}

/**
 * Lookup a malloc function, while disabling wrappers
 */
static void* get_malloc_fn(char *name)
{
	bool before = FALSE;
	void *fn;

	if (enabled)
	{
		before = enable_thread(FALSE);
	}
	fn = dlsym(RTLD_NEXT, name);
	if (enabled)
	{
		enable_thread(before);
	}
	return fn;
}

/**
 * Call original malloc()
 */
static void* real_malloc(size_t size)
{
	static void* (*fn)(size_t size);
	static int recursive = 0;

	if (!fn)
	{
		/* checking recursiveness should actually be thread-specific. But as
		 * it is very likely that the first allocation is done before we go
		 * multi-threaded, we keep it simple. */
		if (recursive)
		{
			return malloc_for_dlsym(size);
		}
		recursive++;
		fn = get_malloc_fn("malloc");
		recursive--;
	}
	return fn(size);
}

/**
 * Call original free()
 */
static void real_free(void *ptr)
{
	static void (*fn)(void *ptr);

	if (!fn)
	{
		fn = get_malloc_fn("free");
	}
	return fn(ptr);
}

/**
 * Call original realloc()
 */
static void* real_realloc(void *ptr, size_t size)
{
	static void* (*fn)(void *ptr, size_t size);

	if (!fn)
	{
		fn = get_malloc_fn("realloc");
	}
	return fn(ptr, size);
}

/**
 * Hook definition: plain function overloading existing malloc calls
 */
#define HOOK(ret, name, ...) ret name(__VA_ARGS__)

/**
 * Hook initialization when not using hooks, resolve functions.
 */
static bool register_hooks()
{
	void *buf = real_malloc(8);
	buf = real_realloc(buf, 16);
	real_free(buf);
	return TRUE;
}

#endif /* !__APPLE__ */

/**
 * Leak report white list
 *
 * List of functions using static allocation buffers or should be suppressed
 * otherwise on leak report.
 */
static char *whitelist[] = {
	/* backtraces, including own */
	"backtrace_create",
	"strerror_safe",
	/* pthread stuff */
	"pthread_create",
	"pthread_setspecific",
	"__pthread_setspecific",
	/* glibc functions */
	"inet_ntoa",
	"strerror",
	"getprotobyname",
	"getprotobynumber",
	"getservbyport",
	"getservbyname",
	"gethostbyname",
	"gethostbyname2",
	"gethostbyname_r",
	"gethostbyname2_r",
	"getnetbyname",
	"getpwnam_r",
	"getgrnam_r",
	"register_printf_function",
	"register_printf_specifier",
	"syslog",
	"vsyslog",
	"__syslog_chk",
	"__vsyslog_chk",
	"__fprintf_chk",
	"getaddrinfo",
	"setlocale",
	"getpass",
	"getpwent_r",
	"setpwent",
	"endpwent",
	"getspnam_r",
	"getpwuid_r",
	"initgroups",
	"tzset",
	"_IO_file_doallocate",
	/* ignore dlopen, as we do not dlclose to get proper leak reports */
	"dlopen",
	"dlerror",
	"dlclose",
	"dlsym",
	/* mysql functions */
	"mysql_init_character_set",
	"init_client_errs",
	"my_thread_init",
	/* fastcgi library */
	"FCGX_Init",
	/* libxml */
	"xmlInitCharEncodingHandlers",
	"xmlInitParser",
	"xmlInitParserCtxt",
	/* libcurl */
	"Curl_client_write",
	/* libsoup */
	"soup_add_timeout",
	"soup_headers_parse_response",
	"soup_message_headers_append",
	"soup_message_headers_clear",
	"soup_message_headers_get_list",
	"soup_message_headers_get_one",
	"soup_session_abort",
	"soup_session_get_type",
	"soup_session_remove_feature",
	/* libldap */
	"ldap_int_initialize",
	/* ClearSilver */
	"nerr_init",
	/* libgcrypt */
	"gcrypt_plugin_create",
	"gcry_control",
	"gcry_check_version",
	"gcry_randomize",
	"gcry_create_nonce",
	/* OpenSSL: These are needed for unit-tests only, the openssl plugin
	 * does properly clean up any memory during destroy(). */
	"ECDSA_do_sign_ex",
	"ECDSA_verify",
	"RSA_new_method",
	/* OpenSSL 1.1.0 does not cleanup anymore until the library is unloaded */
	"OPENSSL_init_crypto",
	"OPENSSL_init_ssl",
	"CRYPTO_THREAD_lock_new",
	"ERR_add_error_data",
	"ERR_set_mark",
	"ENGINE_load_builtin_engines",
	"OPENSSL_load_builtin_modules",
	"CONF_modules_load_file",
	"CONF_module_add",
	"RAND_DRBG_bytes",
	"RAND_DRBG_generate",
	"RAND_DRBG_get0_master",
	"RAND_DRBG_get0_private",
	"RAND_DRBG_get0_public",
	/* We get this via libcurl and OpenSSL 1.1.1 */
	"CRYPTO_get_ex_new_index",
	/* OpenSSL libssl */
	"SSL_COMP_get_compression_methods",
	/* NSPR */
	"PR_CallOnce",
	/* libapr */
	"apr_pool_create_ex",
	/* glib */
	"g_output_stream_write",
	"g_resolver_lookup_by_name",
	"g_signal_connect_data",
	"g_socket_connection_factory_lookup_type",
	"g_type_init_with_debug_flags",
	"g_type_register_static",
	"g_type_class_ref",
	"g_type_create_instance",
	"g_type_add_interface_static",
	"g_type_interface_add_prerequisite",
	"g_private_set",
	"g_queue_pop_tail",
	/* libgpg */
	"gpg_err_init",
	/* gnutls */
	"gnutls_global_init",
	/* Ada runtime */
	"system__tasking__initialize",
	"system__tasking__initialization__abort_defer",
	"system__tasking__stages__create_task",
	"system__task_primitives__operations__register_foreign_thread__2",
	/* in case external threads call into our code */
	"thread_current_id",
	/* FHH IMCs and IMVs */
	"TNC_IMC_NotifyConnectionChange",
	"TNC_IMV_NotifyConnectionChange",
	/* Botan */
	"botan_public_key_load",
	"botan_privkey_create",
	"botan_privkey_load_ecdh",
	"botan_privkey_load",
};

/**
 * Some functions are hard to whitelist, as they don't use a symbol directly.
 * Use some static initialization to suppress them on leak reports
 */
static void init_static_allocations()
{
	struct tm tm;
	time_t t = 0;

	tzset();
	gmtime_r(&t, &tm);
	localtime_r(&t, &tm);
}

/**
 * Hashtable hash function
 */
static u_int hash(backtrace_t *key)
{
	enumerator_t *enumerator;
	void *addr;
	u_int hash = 0;

	enumerator = key->create_frame_enumerator(key);
	while (enumerator->enumerate(enumerator, &addr))
	{
		hash = chunk_hash_inc(chunk_from_thing(addr), hash);
	}
	enumerator->destroy(enumerator);

	return hash;
}

/**
 * Hashtable equals function
 */
static bool equals(backtrace_t *a, backtrace_t *b)
{
	return a->equals(a, b);
}

/**
 * Summarize and print backtraces
 */
static int print_traces(private_leak_detective_t *this,
						leak_detective_report_cb_t cb, void *user,
						int thresh, int thresh_count,
						bool detailed, int *whitelisted, size_t *sum)
{
	int leaks = 0;
	memory_header_t *hdr;
	enumerator_t *enumerator;
	hashtable_t *entries, *ignored = NULL;
	backtrace_t *bt;
	struct {
		/** associated backtrace */
		backtrace_t *backtrace;
		/** total size of all allocations */
		size_t bytes;
		/** number of allocations */
		u_int count;
	} *entry;
	bool before;

	before = enable_thread(FALSE);

	entries = hashtable_create((hashtable_hash_t)hash,
							   (hashtable_equals_t)equals, 1024);
	if (whitelisted)
	{
		ignored = hashtable_create((hashtable_hash_t)hash,
								   (hashtable_equals_t)equals, 1024);
	}

	lock->lock(lock);
	for (hdr = first_header.next; hdr != NULL; hdr = hdr->next)
	{
		if (whitelisted)
		{
			bt = ignored->get(ignored, hdr->backtrace);
			if (!bt)
			{
				if (hdr->backtrace->contains_function(hdr->backtrace, whitelist,
													  countof(whitelist)))
				{
					bt = hdr->backtrace;
					ignored->put(ignored, bt, bt);
				}
			}
			if (bt)
			{
				(*whitelisted)++;
				continue;
			}
		}
		entry = entries->get(entries, hdr->backtrace);
		if (entry)
		{
			entry->bytes += hdr->bytes;
			entry->count++;
		}
		else
		{
			INIT(entry,
				.backtrace = hdr->backtrace->clone(hdr->backtrace),
				.bytes = hdr->bytes,
				.count = 1,
			);
			entries->put(entries, entry->backtrace, entry);
		}
		if (sum)
		{
			*sum += hdr->bytes;
		}
		leaks++;
	}
	lock->unlock(lock);
	DESTROY_IF(ignored);

	enumerator = entries->create_enumerator(entries);
	while (enumerator->enumerate(enumerator, NULL, &entry))
	{
		if (cb)
		{
			if (!thresh || entry->bytes >= thresh)
			{
				if (!thresh_count || entry->count >= thresh_count)
				{
					cb(user, entry->count, entry->bytes, entry->backtrace,
					   detailed);
				}
			}
		}
		entry->backtrace->destroy(entry->backtrace);
		free(entry);
	}
	enumerator->destroy(enumerator);
	entries->destroy(entries);

	enable_thread(before);
	return leaks;
}

METHOD(leak_detective_t, report, void,
	private_leak_detective_t *this, bool detailed)
{
	if (lib->leak_detective)
	{
		int leaks, whitelisted = 0;
		size_t sum = 0;

		leaks = print_traces(this, this->report_cb, this->report_data,
							 0, 0, detailed, &whitelisted, &sum);
		if (this->report_scb)
		{
			this->report_scb(this->report_data, leaks, sum, whitelisted);
		}
	}
}

METHOD(leak_detective_t, set_report_cb, void,
	private_leak_detective_t *this, leak_detective_report_cb_t cb,
	leak_detective_summary_cb_t scb, void *user)
{
	this->report_cb = cb;
	this->report_scb = scb;
	this->report_data = user;
}

METHOD(leak_detective_t, leaks, int,
	private_leak_detective_t *this)
{
	int whitelisted = 0;

	return print_traces(this, NULL, NULL, 0, 0, FALSE, &whitelisted, NULL);
}

METHOD(leak_detective_t, set_state, bool,
	private_leak_detective_t *this, bool enable)
{
	return enable_thread(enable);
}

METHOD(leak_detective_t, usage, void,
	private_leak_detective_t *this, leak_detective_report_cb_t cb,
	leak_detective_summary_cb_t scb, void *user)
{
	bool detailed;
	int thresh, thresh_count, leaks, whitelisted = 0;
	size_t sum = 0;

	thresh = lib->settings->get_int(lib->settings,
						"%s.leak_detective.usage_threshold", 10240, lib->ns);
	thresh_count = lib->settings->get_int(lib->settings,
						"%s.leak_detective.usage_threshold_count", 0, lib->ns);
	detailed = lib->settings->get_bool(lib->settings,
						"%s.leak_detective.detailed", TRUE, lib->ns);

	leaks = print_traces(this, cb, user, thresh, thresh_count,
						 detailed, &whitelisted, &sum);
	if (scb)
	{
		scb(user, leaks, sum, whitelisted);
	}
}

/**
 * Wrapped malloc() function
 */
HOOK(void*, malloc, size_t bytes)
{
	memory_header_t *hdr;
	memory_tail_t *tail;
	bool before;

	if (!enabled || thread_disabled->get(thread_disabled))
	{
		return real_malloc(bytes);
	}

	hdr = real_malloc(sizeof(memory_header_t) + bytes + sizeof(memory_tail_t));
	tail = ((void*)hdr) + bytes + sizeof(memory_header_t);
	/* set to something which causes crashes */
	memset(hdr, MEMORY_ALLOC_PATTERN,
		   sizeof(memory_header_t) + bytes + sizeof(memory_tail_t));

	before = enable_thread(FALSE);
	hdr->backtrace = backtrace_create(2);
	enable_thread(before);

	hdr->magic = MEMORY_HEADER_MAGIC;
	hdr->bytes = bytes;
	tail->magic = MEMORY_TAIL_MAGIC;

	add_hdr(hdr);

	return hdr + 1;
}

/**
 * Wrapped calloc() function
 */
HOOK(void*, calloc, size_t nmemb, size_t size)
{
	void *ptr;
	volatile size_t total;

	total = nmemb * size;
	ptr = malloc(total);
	memset(ptr, 0, total);

	return ptr;
}

/**
 * Wrapped valloc(), TODO: currently not supported
 */
HOOK(void*, valloc, size_t size)
{
	DBG1(DBG_LIB, "valloc() used, but leak-detective hook missing");
	return NULL;
}

/**
 * Wrapped free() function
 */
HOOK(void, free, void *ptr)
{
	memory_header_t *hdr;
	memory_tail_t *tail;
	backtrace_t *backtrace;
	bool before;

	if (!enabled || thread_disabled->get(thread_disabled))
	{
		/* after deinitialization we might have to free stuff we allocated
		 * while we were enabled */
		if (!first_header.magic && ptr)
		{
			hdr = ptr - sizeof(memory_header_t);
			tail = ptr + hdr->bytes;
			if (hdr->magic == MEMORY_HEADER_MAGIC &&
				tail->magic == MEMORY_TAIL_MAGIC)
			{
				ptr = hdr;
			}
		}
		real_free(ptr);
		return;
	}
	/* allow freeing of NULL */
	if (!ptr)
	{
		return;
	}
	hdr = ptr - sizeof(memory_header_t);
	tail = ptr + hdr->bytes;

	before = enable_thread(FALSE);
	if (hdr->magic != MEMORY_HEADER_MAGIC ||
		tail->magic != MEMORY_TAIL_MAGIC)
	{
		bool bt = TRUE;

		/* check if memory appears to be allocated by our hooks */
		if (has_hdr(hdr))
		{
			fprintf(stderr, "freeing corrupted memory (%p): "
					"%u bytes, header magic 0x%x, tail magic 0x%x:\n",
					ptr, hdr->bytes, hdr->magic, tail->magic);
			remove_hdr(hdr);

			if (hdr->magic == MEMORY_HEADER_MAGIC)
			{	/* only access the old backtrace if header magic is valid */
				hdr->backtrace->log(hdr->backtrace, stderr, TRUE);
				hdr->backtrace->destroy(hdr->backtrace);
			}
			else
			{
				fprintf(stderr, " header magic invalid, ignore backtrace of "
						"allocation\n");
			}
		}
		else
		{
			/* just free this block of unknown memory */
			hdr = ptr;

			if (ignore_unknown)
			{
				bt = FALSE;
			}
			else
			{
				fprintf(stderr, "freeing unknown memory (%p):\n", ptr);
			}
		}
		if (bt)
		{
			backtrace = backtrace_create(2);
			backtrace->log(backtrace, stderr, TRUE);
			backtrace->destroy(backtrace);
		}
	}
	else
	{
		remove_hdr(hdr);

		hdr->backtrace->destroy(hdr->backtrace);

		/* set mem to something remarkable */
		memset(hdr, MEMORY_FREE_PATTERN,
			   sizeof(memory_header_t) + hdr->bytes + sizeof(memory_tail_t));
	}
	real_free(hdr);
	enable_thread(before);
}

/**
 * Wrapped realloc() function
 */
HOOK(void*, realloc, void *old, size_t bytes)
{
	memory_header_t *hdr;
	memory_tail_t *tail;
	backtrace_t *backtrace;
	bool before, have_backtrace = TRUE;

	if (!enabled || thread_disabled->get(thread_disabled))
	{
		return real_realloc(old, bytes);
	}
	/* allow reallocation of NULL */
	if (!old)
	{
		return malloc(bytes);
	}
	/* handle zero size as a free() */
	if (!bytes)
	{
		free(old);
		return NULL;
	}

	hdr = old - sizeof(memory_header_t);
	tail = old + hdr->bytes;

	before = enable_thread(FALSE);
	if (hdr->magic != MEMORY_HEADER_MAGIC ||
		tail->magic != MEMORY_TAIL_MAGIC)
	{
		bool bt = TRUE;

		/* check if memory appears to be allocated by our hooks */
		if (has_hdr(hdr))
		{
			fprintf(stderr, "reallocating corrupted memory (%p, %u bytes): "
					"%zu bytes, header magic 0x%x, tail magic 0x%x:\n",
					old, hdr->bytes, bytes, hdr->magic, tail->magic);
			remove_hdr(hdr);

			if (hdr->magic == MEMORY_HEADER_MAGIC)
			{	/* only access header fields (backtrace, bytes) if header magic
				 * is still valid */
				hdr->backtrace->log(hdr->backtrace, stderr, TRUE);
				memset(&tail->magic, MEMORY_ALLOC_PATTERN, sizeof(tail->magic));
			}
			else
			{
				fprintf(stderr, " header magic invalid, ignore backtrace of "
						"allocation\n");
				have_backtrace = FALSE;
				hdr->magic = MEMORY_HEADER_MAGIC;
			}
		}
		else
		{
			/* adopt this block of unknown memory */
			hdr = old;
			have_backtrace = FALSE;

			if (ignore_unknown)
			{
				bt = FALSE;
			}
			else
			{
				fprintf(stderr, "reallocating unknown memory (%p): %zu bytes:\n",
						old, bytes);
			}
		}
		if (bt)
		{
			backtrace = backtrace_create(2);
			backtrace->log(backtrace, stderr, TRUE);
			backtrace->destroy(backtrace);
		}
	}
	else
	{
		remove_hdr(hdr);
		/* clear tail magic, allocate, set tail magic */
		memset(&tail->magic, MEMORY_ALLOC_PATTERN, sizeof(tail->magic));
	}

	hdr = real_realloc(hdr,
					   sizeof(memory_header_t) + bytes + sizeof(memory_tail_t));
	tail = ((void*)hdr) + bytes + sizeof(memory_header_t);
	tail->magic = MEMORY_TAIL_MAGIC;

	/* update statistics */
	hdr->bytes = bytes;

	if (have_backtrace)
	{
		hdr->backtrace->destroy(hdr->backtrace);
	}
	hdr->backtrace = backtrace_create(2);
	enable_thread(before);

	add_hdr(hdr);

	return hdr + 1;
}

METHOD(leak_detective_t, destroy, void,
	private_leak_detective_t *this)
{
	disable_leak_detective();
	lock->destroy(lock);
	thread_disabled->destroy(thread_disabled);
	free(this);
	first_header.magic = 0;
	first_header.next = NULL;
}

/*
 * see header file
 */
leak_detective_t *leak_detective_create()
{
	private_leak_detective_t *this;

	INIT(this,
		.public = {
			.report = _report,
			.set_report_cb = _set_report_cb,
			.usage = _usage,
			.leaks = _leaks,
			.set_state = _set_state,
			.destroy = _destroy,
		},
	);

	if (getenv("LEAK_DETECTIVE_DISABLE") != NULL)
	{
		free(this);
		return NULL;
	}
	ignore_unknown = getenv("LEAK_DETECTIVE_IGNORE_UNKNOWN") != NULL;

	lock = spinlock_create();
	thread_disabled = thread_value_create(NULL);

	init_static_allocations();

	if (register_hooks())
	{
		enable_leak_detective();
	}
	return &this->public;
}
