<commit_before>/*
  +----------------------------------------------------------------------+
  | Memprof                                                              |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2012 <NAME>                              |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | <EMAIL> so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author: <NAME> <<EMAIL>>                        |
  +----------------------------------------------------------------------+
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "SAPI.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_memprof.h"
#include "zend_extensions.h"
#include <stdint.h>
#include <sys/queue.h>
#include "util.h"
#include <Judy.h>

#ifdef ZTS
#error "ZTS build not supported"
#endif

#define MEMPROF_DEBUG 0

#if MEMPROF_CONFIGURE_VERSION != 2
# error Please rebuild configure (run phpize and reconfigure)
#endif

#if HAVE_MALLOC_HOOKS
# include <malloc.h>
#else
# warning No support for malloc hooks, this build will not track persistent allocations
#endif

#ifdef __GNUC__
# define memprof_used __attribute__((used))
#else
# define memprof_used
#endif

typedef LIST_HEAD(_alloc_list_head, _alloc) alloc_list_head;

/* a call frame */
typedef struct _frame {
    char * name;
    size_t name_len;
    struct _frame * prev;
    size_t calls;
    HashTable next_cache;
    alloc_list_head allocs;
} frame;

/* an allocated block header */
typedef struct _alloc {
#if MEMPROF_DEBUG
    size_t canary_a;
#endif
    LIST_ENTRY(_alloc) list;
    size_t size;
#if MEMPROF_DEBUG
    size_t canary_b;
#endif
} alloc;

#if HAVE_MALLOC_HOOKS
static void * malloc_hook(size_t size, const void *caller);
static void * realloc_hook(void *ptr, size_t size, const void *caller);
static void free_hook(void *ptr, const void *caller);
static void * memalign_hook(size_t alignment, size_t size, const void *caller);

static void * (*old_malloc_hook) (size_t size, const void *caller) = NULL;
static void * (*old_realloc_hook) (void *ptr, size_t size, const void *caller) = NULL;
static void (*old_free_hook) (void *ptr, const void *caller) = NULL;
static void * (*old_memalign_hook) (size_t alignment, size_t size, const void *caller) = NULL;
#endif

static void (*old_zend_execute)(zend_op_array *op_array TSRMLS_DC);
static void (*old_zend_execute_internal)(zend_execute_data *execute_data_ptr, int return_value_used TSRMLS_DC);

static int memprof_reserved_offset;
static int memprof_initialized = 0;
static int track_mallocs = 0;

static size_t memory_usage = 0;
static size_t memory_usage_real = 0;
static size_t memory_usage_peak = 0;
static size_t memory_usage_peak_real = 0;

static frame default_frame;
static frame * current_frame = &default_frame;
static alloc_list_head * current_alloc_list = &default_frame.allocs;

static Pvoid_t allocs_set = (Pvoid_t) NULL;

static zend_mm_heap * orig_heap = NULL;

/* ZEND_DECLARE_MODULE_GLOBALS(memprof) */

/* returns the actual data of an allocated block */
#define ALLOC_DATA(alloc) alloc_data(alloc)

/* returns the block header, given a pointer to the begining of its data */
#define ALLOC_BLOCK(data) alloc_block(data)

/* size of a block header */
#define ALLOC_HEAD_SIZE \
    (ZEND_MM_ALIGNED_SIZE(sizeof(struct _alloc)))

/* actual size of a allocation, given the requested size (i.e. data size + header size and alignments) */
#define ALLOC_SIZE(size) \
    (size > SIZE_MAX - ALLOC_HEAD_SIZE ? 0 : ALLOC_HEAD_SIZE + size)

#define ALLOC_INIT(alloc, size) alloc_init(alloc, size)

#define ALLOC_LIST_INSERT_HEAD(head, elem) alloc_list_insert_head(head, elem)
#define ALLOC_LIST_REMOVE(elem) alloc_list_remove(elem, __FUNCTION__, __LINE__)

static inline void * alloc_data(alloc * a) {
    return (void*)(((uintptr_t)a)+ALLOC_HEAD_SIZE);
}

static inline alloc * alloc_block(void * a) {
    return (alloc*) &((char*)a)[-ALLOC_HEAD_SIZE];
}

static inline void alloc_init(alloc * alloc, size_t size) {
    alloc->size = size;
    alloc->list.le_next = NULL;
    alloc->list.le_prev = NULL;
#if MEMPROF_DEBUG
    alloc->canary_a = alloc->canary_b = size ^ 0x5a5a5a5a;
#endif
}

static void alloc_list_insert_head(alloc_list_head * head, alloc * elem) {
    LIST_INSERT_HEAD(head, elem, list);
}

static void list_remove(alloc * elm) {
    LIST_REMOVE(elm, list);
}

static void alloc_list_remove(alloc * elem, const char * function, int line) {
    if (elem->list.le_prev || elem->list.le_next) {
        list_remove(elem);
        elem->list.le_next = NULL;
        elem->list.le_prev = NULL;
    }
}

#if MEMPROF_DEBUG

static void alloc_check_single(alloc * alloc, const char * function, int line) {
    if (alloc->canary_a != (alloc->size ^ 0x5a5a5a5a) || alloc->canary_a != alloc->canary_b) {
        fprintf(stderr, "canary mismatch for %p at %s:%d\n", alloc, function, line);
        abort();
    }
}

static void alloc_check(alloc * alloc, const char * function, int line) {
    /* fprintf(stderr, "checking %p at %s:%d\n", alloc, function, line); */
    alloc_check_single(alloc, function, line);
    for (alloc = current_alloc_list->lh_first; alloc; alloc = alloc->list.le_next) {
        alloc_check_single(alloc, function, line);
    }
}

# define ALLOC_CHECK(alloc) alloc_check(alloc, __FUNCTION__, __LINE__);
#else
# define ALLOC_CHECK(alloc)
#endif

static void destroy_frame(frame * f)
{
    alloc * a;

    free(f->name);

    while (f->allocs.lh_first) {
        a = f->allocs.lh_first;
        ALLOC_CHECK(a);
        ALLOC_LIST_REMOVE(a);
    }

    zend_hash_destroy(&f->next_cache);
}

/* HashTable destructor */
static void frame_dtor(void * pDest)
{
    frame * f = * (frame **) pDest;
    destroy_frame(f);
    free(f);
}

static void init_frame(frame * f, frame * prev, char * name, size_t name_len)
{
    zend_hash_init(&f->next_cache, 0, NULL, frame_dtor, 0);
    f->name = malloc(name_len+1);
    memcpy(f->name, name, name_len+1);
    f->name_len = name_len;
    f->calls = 0;
    f->prev = prev;
    LIST_INIT(&f->allocs);
}

static frame * new_frame(frame * prev, char * name, size_t name_len)
{
    frame * f = malloc(sizeof(*f));
    init_frame(f, prev, name, name_len);
    return f;
}

static frame * get_or_create_frame(zend_execute_data * current_execute_data, frame * prev)
{
    frame * f;
    frame ** f_pp;

    char name[256];
    size_t name_len;

    name_len = get_function_name(current_execute_data, name, sizeof(name));

    if (SUCCESS == zend_hash_find(&prev->next_cache, name, name_len+1, (void**) &f_pp)) {
        f = *f_pp;
    } else {
        f = new_frame(prev, name, name_len);
        zend_hash_add(&prev->next_cache, name, name_len+1, &f, sizeof(f), NULL);
    }

    return f;
}

static size_t frame_alloc_size(const frame * f)
{
    size_t size = 0;
    alloc * alloc;

    LIST_FOREACH(alloc, &f->allocs, list) {
        size += alloc->size;
    }

    return size;
}

static int frame_stack_depth(const frame * f)
{
    const frame * prev;
    int depth = 0;

    for (prev = f; prev; prev = prev->prev) {
        depth ++;
    }

    return depth;
}

static void incr_memory_usage(size_t size, size_t real_size)
{
    memory_usage += size;
    memory_usage_real += real_size;
    if (memory_usage_peak < memory_usage) {
        memory_usage_peak = memory_usage;
    }
    if (memory_usage_peak_real < memory_usage_real) {
        memory_usage_peak_real = memory_usage_real;
    }
}

static void decr_memory_usage(size_t size, size_t real_size)
{
    memory_usage -= size;
    memory_usage_real -= real_size;
}

static void mark_own_alloc(Pvoid_t * set, void * ptr)
{
    int ret;
    J1S(ret, *set, (Word_t)ptr);
}

static void unmark_own_alloc(Pvoid_t * set, void * ptr)
{
    int ret;
    J1U(ret, *set, (Word_t)ptr);
}

int is_own_alloc(Pvoid_t * set, void * ptr)
{
    int ret;
    J1T(ret, *set, (Word_t)ptr);
    return ret;
}

#if HAVE_MALLOC_HOOKS

#define MALLOC_HOOK_RESTORE_OLD() \
    /* Restore all old hooks */ \
    __malloc_hook = old_malloc_hook; \
    __free_hook = old_free_hook; \
    __realloc_hook = old_realloc_hook; \
    __memalign_hook = old_memalign_hook; \

#define MALLOC_HOOK_SAVE_OLD() \
    /* Save underlying hooks */ \
    old_malloc_hook = __malloc_hook; \
    old_free_hook = __free_hook; \
    old_realloc_hook = __realloc_hook; \
    old_memalign_hook = __memalign_hook; \

#define MALLOC_HOOK_SET_OWN() \
    /* Restore our own hooks */ \
    __malloc_hook = malloc_hook; \
    __free_hook = free_hook; \
    __realloc_hook = realloc_hook; \
    __memalign_hook = memalign_hook; \

#else /* HAVE_MALLOC_HOOKS */

#define MALLOC_HOOK_RESTORE_OLD()
#define MALLOC_HOOK_SAVE_OLD()
#define MALLOC_HOOK_SET_OWN()

#endif

#define WITHOUT_MALLOC_TRACKING do { \
    int ___old_track_mallocs = track_mallocs; \
    track_mallocs = 0; \
    do

#define END_WITHOUT_MALLOC_TRACKING \
    while (0); \
    track_mallocs = ___old_track_mallocs; \
} while (0)

static void * malloc_hook(size_t size, const void *caller)
{
    void *result;
    size_t block_size;

    MALLOC_HOOK_RESTORE_OLD();

    if (0 == (block_size = ALLOC_SIZE(size))) {
        result = NULL;
    } else {
        result = malloc(block_size);
        if (result != NULL) {
            incr_memory_usage(size, block_size);
            ALLOC_INIT((alloc*)result, size);
            if (track_mallocs) {
                ALLOC_LIST_INSERT_HEAD(current_alloc_list, (alloc*)result);
            }
            ALLOC_CHECK((alloc*)result);
            result = ALLOC_DATA(result);
            mark_own_alloc(&allocs_set, result);
        }
    }

    MALLOC_HOOK_SAVE_OLD();
    MALLOC_HOOK_SET_OWN();

    return result;
}

static void * realloc_hook(void *ptr, size_t size, const void *caller)
{
    void *result;
    size_t block_size;

    MALLOC_HOOK_RESTORE_OLD();

    if (ptr != NULL && !is_own_alloc(&allocs_set, ptr)) {
        result = realloc(ptr, size);
    } else if (0 == (block_size = ALLOC_SIZE(size))) {
        result = NULL;
    } else {
        /* ptr may be freed by realloc, so we must remove it from list now */
        if (ptr != NULL) {
            ALLOC_CHECK(ALLOC_BLOCK(ptr));
            ALLOC_LIST_REMOVE(ALLOC_BLOCK(ptr));
            decr_memory_usage(ALLOC_BLOCK(ptr)->size, ALLOC_SIZE(ALLOC_BLOCK(ptr)->size));
            unmark_own_alloc(&allocs_set, ptr);
        }

        result = realloc(ptr ? ALLOC_BLOCK(ptr) : NULL, block_size);
        if (result != NULL) {
            /* succeeded; add result */
            ALLOC_INIT((alloc*)result, size);
            if (track_mallocs) {
                ALLOC_LIST_INSERT_HEAD(current_alloc_list, (alloc*)result);
            }
            incr_memory_usage(size, block_size);
            ALLOC_CHECK((alloc*)result);
            result = ALLOC_DATA(result);
            mark_own_alloc(&allocs_set, result);
        } else if (ptr != NULL) {
            /* failed, re-add ptr, since it hasn't been freed */
            incr_memory_usage(size, ALLOC_SIZE(ALLOC_BLOCK(ptr)->size));
            ALLOC_CHECK(ptr);
            if (track_mallocs) {
                ALLOC_LIST_INSERT_HEAD(current_alloc_list, ALLOC_BLOCK(ptr));
            }
            ALLOC_CHECK(ALLOC_BLOCK(ptr));
            mark_own_alloc(&allocs_set, ptr);
        }
    }

    MALLOC_HOOK_SAVE_OLD();
    MALLOC_HOOK_SET_OWN();

    return result;
}

static void free_hook(void *ptr, const void *caller)
{
    MALLOC_HOOK_RESTORE_OLD();

    if (ptr != NULL) {
        if (!is_own_alloc(&allocs_set, ptr)) {
            free(ptr);
        } else {
            size_t size = ALLOC_BLOCK(ptr)->size;
            size_t block_size = ALLOC_SIZE(ALLOC_BLOCK(ptr)->size);
            ALLOC_CHECK(ALLOC_BLOCK(ptr));
            ALLOC_LIST_REMOVE(ALLOC_BLOCK(ptr));
#if MEMPROF_DEBUG
            memset(ALLOC_BLOCK(ptr), 0, ALLOC_SIZE(ALLOC_BLOCK(ptr)->size));
#endif
            free(ALLOC_BLOCK(ptr));
            decr_memory_usage(size, block_size);
            unmark_own_alloc(&allocs_set, ptr);
        }
    }

    MALLOC_HOOK_SAVE_OLD();
    MALLOC_HOOK_SET_OWN();
}

static memprof_used void * memalign_hook(size_t alignment, size_t size, const void *caller)
{
    /* TODO: would require special handling in free and realloc */
    return malloc_hook(size, caller);
}

void * zend_malloc_handler(size_t size)
{
    return malloc_hook(size, NULL);
}

void zend_free_handler(void * ptr)
{
    if (!ptr) {
        return;
    }

    if (is_own_alloc(&allocs_set, ptr)) {
        free_hook(ptr, NULL);
    } else {
        zend_mm_heap * heap = zend_mm_set_heap(orig_heap);
        zend_mm_free(heap, ptr);
        zend_mm_set_heap(heap);
    }
}

void * zend_realloc_handler(void * ptr, size_t size)
{
    if (!ptr) {
        return zend_malloc_handler(size);
    }

    if (is_own_alloc(&allocs_set, ptr)) {
        return realloc_hook(ptr, size, NULL);
    } else {
        zend_mm_heap * heap = zend_mm_set_heap(orig_heap);
        void * result = zend_mm_realloc(heap, ptr, size);
        zend_mm_set_heap(heap);
        return result;
    }
}

static void memprof_zend_execute(zend_op_array *op_array TSRMLS_DC)
{
    WITHOUT_MALLOC_TRACKING {

        current_frame = get_or_create_frame(EG(current_execute_data), current_frame);
        current_frame->calls++;
        current_alloc_list = &current_frame->allocs;

    } END_WITHOUT_MALLOC_TRACKING;

    old_zend_execute(op_array TSRMLS_CC);

    current_frame = current_frame->prev;
    current_alloc_list = &current_frame->allocs;
}

static void memprof_zend_execute_internal(zend_execute_data *execute_data_ptr, int return_value_used TSRMLS_DC)
{
    int ignore = 0;

    if (execute_data_ptr->function_state.function->common.function_name) {
        const char * name = execute_data_ptr->function_state.function->common.function_name;
        if (0 == memcmp(name, "call_user_func", sizeof("call_user_func"))
                || 0 == memcmp(name, "call_user_func_array", sizeof("call_user_func_array")))
        {
            ignore = 1;
        }
    }

    WITHOUT_MALLOC_TRACKING {

        if (!ignore) {
            current_frame = get_or_create_frame(execute_data_ptr, current_frame);
            current_frame->calls++;
            current_alloc_list = &current_frame->allocs;
        }

    } END_WITHOUT_MALLOC_TRACKING;

    if (!old_zend_execute_internal) {
        execute_internal(execute_data_ptr, return_value_used);
    } else {
        old_zend_execute_internal(execute_data_ptr, return_value_used);
    }

    if (!ignore) {
        current_frame = current_frame->prev;
        current_alloc_list = &current_frame->allocs;
    }
}

ZEND_DLEXPORT int memprof_zend_startup(zend_extension *extension)
{
    int ret;

    memprof_initialized = 1;

    if (is_zend_mm()) {
        zend_mm_heap * heap = zend_mm_startup();
        zend_mm_set_custom_handlers(heap, zend_malloc_handler, zend_free_handler, zend_realloc_handler);
        orig_heap = zend_mm_set_heap(heap);
    }

    init_frame(&default_frame, NULL, "root", sizeof("root")-1);
    default_frame.calls = 1;

    MALLOC_HOOK_SAVE_OLD();
    MALLOC_HOOK_SET_OWN();

    zend_hash_del(CG(function_table), "memory_get_usage", sizeof("memory_get_usage"));
    zend_hash_del(CG(function_table), "memory_get_peak_usage", sizeof("memory_get_peak_usage"));

    ret = zend_startup_module(&memprof_module_entry);

    if (-1 == zend_get_resource_handle(extension)) {
        fprintf(stderr, "zend_get_resource_handle failed");
        abort();
    }
    memprof_reserved_offset = extension->resource_number;

    return ret;
}
<commit_msg>fixed initialization order<commit_after>/*
  +----------------------------------------------------------------------+
  | Memprof                                                              |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2012 <NAME>                              |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | <EMAIL> so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author: <NAME> <<EMAIL>>                        |
  +----------------------------------------------------------------------+
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "SAPI.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_memprof.h"
#include "zend_extensions.h"
#include <stdint.h>
#include <sys/queue.h>
#include "util.h"
#include <Judy.h>

#ifdef ZTS
#error "ZTS build not supported"
#endif

#define MEMPROF_DEBUG 0

#if MEMPROF_CONFIGURE_VERSION != 2
# error Please rebuild configure (run phpize and reconfigure)
#endif

#if HAVE_MALLOC_HOOKS
# include <malloc.h>
#else
# warning No support for malloc hooks, this build will not track persistent allocations
#endif

#ifdef __GNUC__
# define memprof_used __attribute__((used))
#else
# define memprof_used
#endif

typedef LIST_HEAD(_alloc_list_head, _alloc) alloc_list_head;

/* a call frame */
typedef struct _frame {
    char * name;
    size_t name_len;
    struct _frame * prev;
    size_t calls;
    HashTable next_cache;
    alloc_list_head allocs;
} frame;

/* an allocated block header */
typedef struct _alloc {
#if MEMPROF_DEBUG
    size_t canary_a;
#endif
    LIST_ENTRY(_alloc) list;
    size_t size;
#if MEMPROF_DEBUG
    size_t canary_b;
#endif
} alloc;

#if HAVE_MALLOC_HOOKS
static void * malloc_hook(size_t size, const void *caller);
static void * realloc_hook(void *ptr, size_t size, const void *caller);
static void free_hook(void *ptr, const void *caller);
static void * memalign_hook(size_t alignment, size_t size, const void *caller);

static void * (*old_malloc_hook) (size_t size, const void *caller) = NULL;
static void * (*old_realloc_hook) (void *ptr, size_t size, const void *caller) = NULL;
static void (*old_free_hook) (void *ptr, const void *caller) = NULL;
static void * (*old_memalign_hook) (size_t alignment, size_t size, const void *caller) = NULL;
#endif

static void (*old_zend_execute)(zend_op_array *op_array TSRMLS_DC);
static void (*old_zend_execute_internal)(zend_execute_data *execute_data_ptr, int return_value_used TSRMLS_DC);

static int memprof_reserved_offset;
static int memprof_initialized = 0;
static int track_mallocs = 0;

static size_t memory_usage = 0;
static size_t memory_usage_real = 0;
static size_t memory_usage_peak = 0;
static size_t memory_usage_peak_real = 0;

static frame default_frame;
static frame * current_frame = &default_frame;
static alloc_list_head * current_alloc_list = &default_frame.allocs;

static Pvoid_t allocs_set = (Pvoid_t) NULL;

static zend_mm_heap * orig_heap = NULL;

/* ZEND_DECLARE_MODULE_GLOBALS(memprof) */

/* returns the actual data of an allocated block */
#define ALLOC_DATA(alloc) alloc_data(alloc)

/* returns the block header, given a pointer to the begining of its data */
#define ALLOC_BLOCK(data) alloc_block(data)

/* size of a block header */
#define ALLOC_HEAD_SIZE \
    (ZEND_MM_ALIGNED_SIZE(sizeof(struct _alloc)))

/* actual size of a allocation, given the requested size (i.e. data size + header size and alignments) */
#define ALLOC_SIZE(size) \
    (size > SIZE_MAX - ALLOC_HEAD_SIZE ? 0 : ALLOC_HEAD_SIZE + size)

#define ALLOC_INIT(alloc, size) alloc_init(alloc, size)

#define ALLOC_LIST_INSERT_HEAD(head, elem) alloc_list_insert_head(head, elem)
#define ALLOC_LIST_REMOVE(elem) alloc_list_remove(elem, __FUNCTION__, __LINE__)

static inline void * alloc_data(alloc * a) {
    return (void*)(((uintptr_t)a)+ALLOC_HEAD_SIZE);
}

static inline alloc * alloc_block(void * a) {
    return (alloc*) &((char*)a)[-ALLOC_HEAD_SIZE];
}

static inline void alloc_init(alloc * alloc, size_t size) {
    alloc->size = size;
    alloc->list.le_next = NULL;
    alloc->list.le_prev = NULL;
#if MEMPROF_DEBUG
    alloc->canary_a = alloc->canary_b = size ^ 0x5a5a5a5a;
#endif
}

static void alloc_list_insert_head(alloc_list_head * head, alloc * elem) {
    LIST_INSERT_HEAD(head, elem, list);
}

static void list_remove(alloc * elm) {
    LIST_REMOVE(elm, list);
}

static void alloc_list_remove(alloc * elem, const char * function, int line) {
    if (elem->list.le_prev || elem->list.le_next) {
        list_remove(elem);
        elem->list.le_next = NULL;
        elem->list.le_prev = NULL;
    }
}

#if MEMPROF_DEBUG

static void alloc_check_single(alloc * alloc, const char * function, int line) {
    if (alloc->canary_a != (alloc->size ^ 0x5a5a5a5a) || alloc->canary_a != alloc->canary_b) {
        fprintf(stderr, "canary mismatch for %p at %s:%d\n", alloc, function, line);
        abort();
    }
}

static void alloc_check(alloc * alloc, const char * function, int line) {
    /* fprintf(stderr, "checking %p at %s:%d\n", alloc, function, line); */
    alloc_check_single(alloc, function, line);
    for (alloc = current_alloc_list->lh_first; alloc; alloc = alloc->list.le_next) {
        alloc_check_single(alloc, function, line);
    }
}

# define ALLOC_CHECK(alloc) alloc_check(alloc, __FUNCTION__, __LINE__);
#else
# define ALLOC_CHECK(alloc)
#endif

static void destroy_frame(frame * f)
{
    alloc * a;

    free(f->name);

    while (f->allocs.lh_first) {
        a = f->allocs.lh_first;
        ALLOC_CHECK(a);
        ALLOC_LIST_REMOVE(a);
    }

    zend_hash_destroy(&f->next_cache);
}

/* HashTable destructor */
static void frame_dtor(void * pDest)
{
    frame * f = * (frame **) pDest;
    destroy_frame(f);
    free(f);
}

static void init_frame(frame * f, frame * prev, char * name, size_t name_len)
{
    zend_hash_init(&f->next_cache, 0, NULL, frame_dtor, 0);
    f->name = malloc(name_len+1);
    memcpy(f->name, name, name_len+1);
    f->name_len = name_len;
    f->calls = 0;
    f->prev = prev;
    LIST_INIT(&f->allocs);
}

static frame * new_frame(frame * prev, char * name, size_t name_len)
{
    frame * f = malloc(sizeof(*f));
    init_frame(f, prev, name, name_len);
    return f;
}

static frame * get_or_create_frame(zend_execute_data * current_execute_data, frame * prev)
{
    frame * f;
    frame ** f_pp;

    char name[256];
    size_t name_len;

    name_len = get_function_name(current_execute_data, name, sizeof(name));

    if (SUCCESS == zend_hash_find(&prev->next_cache, name, name_len+1, (void**) &f_pp)) {
        f = *f_pp;
    } else {
        f = new_frame(prev, name, name_len);
        zend_hash_add(&prev->next_cache, name, name_len+1, &f, sizeof(f), NULL);
    }

    return f;
}

static size_t frame_alloc_size(const frame * f)
{
    size_t size = 0;
    alloc * alloc;

    LIST_FOREACH(alloc, &f->allocs, list) {
        size += alloc->size;
    }

    return size;
}

static int frame_stack_depth(const frame * f)
{
    const frame * prev;
    int depth = 0;

    for (prev = f; prev; prev = prev->prev) {
        depth ++;
    }

    return depth;
}

static void incr_memory_usage(size_t size, size_t real_size)
{
    memory_usage += size;
    memory_usage_real += real_size;
    if (memory_usage_peak < memory_usage) {
        memory_usage_peak = memory_usage;
    }
    if (memory_usage_peak_real < memory_usage_real) {
        memory_usage_peak_real = memory_usage_real;
    }
}

static void decr_memory_usage(size_t size, size_t real_size)
{
    memory_usage -= size;
    memory_usage_real -= real_size;
}

static void mark_own_alloc(Pvoid_t * set, void * ptr)
{
    int ret;
    J1S(ret, *set, (Word_t)ptr);
}

static void unmark_own_alloc(Pvoid_t * set, void * ptr)
{
    int ret;
    J1U(ret, *set, (Word_t)ptr);
}

int is_own_alloc(Pvoid_t * set, void * ptr)
{
    int ret;
    J1T(ret, *set, (Word_t)ptr);
    return ret;
}

#if HAVE_MALLOC_HOOKS

#define MALLOC_HOOK_RESTORE_OLD() \
    /* Restore all old hooks */ \
    __malloc_hook = old_malloc_hook; \
    __free_hook = old_free_hook; \
    __realloc_hook = old_realloc_hook; \
    __memalign_hook = old_memalign_hook; \

#define MALLOC_HOOK_SAVE_OLD() \
    /* Save underlying hooks */ \
    old_malloc_hook = __malloc_hook; \
    old_free_hook = __free_hook; \
    old_realloc_hook = __realloc_hook; \
    old_memalign_hook = __memalign_hook; \

#define MALLOC_HOOK_SET_OWN() \
    /* Restore our own hooks */ \
    __malloc_hook = malloc_hook; \
    __free_hook = free_hook; \
    __realloc_hook = realloc_hook; \
    __memalign_hook = memalign_hook; \

#else /* HAVE_MALLOC_HOOKS */

#define MALLOC_HOOK_RESTORE_OLD()
#define MALLOC_HOOK_SAVE_OLD()
#define MALLOC_HOOK_SET_OWN()

#endif

#define WITHOUT_MALLOC_TRACKING do { \
    int ___old_track_mallocs = track_mallocs; \
    track_mallocs = 0; \
    do

#define END_WITHOUT_MALLOC_TRACKING \
    while (0); \
    track_mallocs = ___old_track_mallocs; \
} while (0)

static void * malloc_hook(size_t size, const void *caller)
{
    void *result;
    size_t block_size;

    MALLOC_HOOK_RESTORE_OLD();

    if (0 == (block_size = ALLOC_SIZE(size))) {
        result = NULL;
    } else {
        result = malloc(block_size);
        if (result != NULL) {
            incr_memory_usage(size, block_size);
            ALLOC_INIT((alloc*)result, size);
            if (track_mallocs) {
                ALLOC_LIST_INSERT_HEAD(current_alloc_list, (alloc*)result);
            }
            ALLOC_CHECK((alloc*)result);
            result = ALLOC_DATA(result);
            mark_own_alloc(&allocs_set, result);
        }
    }

    MALLOC_HOOK_SAVE_OLD();
    MALLOC_HOOK_SET_OWN();

    return result;
}

static void * realloc_hook(void *ptr, size_t size, const void *caller)
{
    void *result;
    size_t block_size;

    MALLOC_HOOK_RESTORE_OLD();

    if (ptr != NULL && !is_own_alloc(&allocs_set, ptr)) {
        result = realloc(ptr, size);
    } else if (0 == (block_size = ALLOC_SIZE(size))) {
        result = NULL;
    } else {
        /* ptr may be freed by realloc, so we must remove it from list now */
        if (ptr != NULL) {
            ALLOC_CHECK(ALLOC_BLOCK(ptr));
            ALLOC_LIST_REMOVE(ALLOC_BLOCK(ptr));
            decr_memory_usage(ALLOC_BLOCK(ptr)->size, ALLOC_SIZE(ALLOC_BLOCK(ptr)->size));
            unmark_own_alloc(&allocs_set, ptr);
        }

        result = realloc(ptr ? ALLOC_BLOCK(ptr) : NULL, block_size);
        if (result != NULL) {
            /* succeeded; add result */
            ALLOC_INIT((alloc*)result, size);
            if (track_mallocs) {
                ALLOC_LIST_INSERT_HEAD(current_alloc_list, (alloc*)result);
            }
            incr_memory_usage(size, block_size);
            ALLOC_CHECK((alloc*)result);
            result = ALLOC_DATA(result);
            mark_own_alloc(&allocs_set, result);
        } else if (ptr != NULL) {
            /* failed, re-add ptr, since it hasn't been freed */
            incr_memory_usage(size, ALLOC_SIZE(ALLOC_BLOCK(ptr)->size));
            ALLOC_CHECK(ptr);
            if (track_mallocs) {
                ALLOC_LIST_INSERT_HEAD(current_alloc_list, ALLOC_BLOCK(ptr));
            }
            ALLOC_CHECK(ALLOC_BLOCK(ptr));
            mark_own_alloc(&allocs_set, ptr);
        }
    }

    MALLOC_HOOK_SAVE_OLD();
    MALLOC_HOOK_SET_OWN();

    return result;
}

static void free_hook(void *ptr, const void *caller)
{
    MALLOC_HOOK_RESTORE_OLD();

    if (ptr != NULL) {
        if (!is_own_alloc(&allocs_set, ptr)) {
            free(ptr);
        } else {
            size_t size = ALLOC_BLOCK(ptr)->size;
            size_t block_size = ALLOC_SIZE(ALLOC_BLOCK(ptr)->size);
            ALLOC_CHECK(ALLOC_BLOCK(ptr));
            ALLOC_LIST_REMOVE(ALLOC_BLOCK(ptr));
#if MEMPROF_DEBUG
            memset(ALLOC_BLOCK(ptr), 0, ALLOC_SIZE(ALLOC_BLOCK(ptr)->size));
#endif
            free(ALLOC_BLOCK(ptr));
            decr_memory_usage(size, block_size);
            unmark_own_alloc(&allocs_set, ptr);
        }
    }

    MALLOC_HOOK_SAVE_OLD();
    MALLOC_HOOK_SET_OWN();
}

static memprof_used void * memalign_hook(size_t alignment, size_t size, const void *caller)
{
    /* TODO: would require special handling in free and realloc */
    return malloc_hook(size, caller);
}

void * zend_malloc_handler(size_t size)
{
    return malloc_hook(size, NULL);
}

void zend_free_handler(void * ptr)
{
    if (!ptr) {
        return;
    }

    if (is_own_alloc(&allocs_set, ptr)) {
        free_hook(ptr, NULL);
    } else {
        zend_mm_heap * heap = zend_mm_set_heap(orig_heap);
        zend_mm_free(heap, ptr);
        zend_mm_set_heap(heap);
    }
}

void * zend_realloc_handler(void * ptr, size_t size)
{
    if (!ptr) {
        return zend_malloc_handler(size);
    }

    if (is_own_alloc(&allocs_set, ptr)) {
        return realloc_hook(ptr, size, NULL);
    } else {
        zend_mm_heap * heap = zend_mm_set_heap(orig_heap);
        void * result = zend_mm_realloc(heap, ptr, size);
        zend_mm_set_heap(heap);
        return result;
    }
}

static void memprof_zend_execute(zend_op_array *op_array TSRMLS_DC)
{
    WITHOUT_MALLOC_TRACKING {

        current_frame = get_or_create_frame(EG(current_execute_data), current_frame);
        current_frame->calls++;
        current_alloc_list = &current_frame->allocs;

    } END_WITHOUT_MALLOC_TRACKING;

    old_zend_execute(op_array TSRMLS_CC);

    current_frame = current_frame->prev;
    current_alloc_list = &current_frame->allocs;
}

static void memprof_zend_execute_internal(zend_execute_data *execute_data_ptr, int return_value_used TSRMLS_DC)
{
    int ignore = 0;

    if (execute_data_ptr->function_state.function->common.function_name) {
        const char * name = execute_data_ptr->function_state.function->common.function_name;
        if (0 == memcmp(name, "call_user_func", sizeof("call_user_func"))
                || 0 == memcmp(name, "call_user_func_array", sizeof("call_user_func_array")))
        {
            ignore = 1;
        }
    }

    WITHOUT_MALLOC_TRACKING {

        if (!ignore) {
            current_frame = get_or_create_frame(execute_data_ptr, current_frame);
            current_frame->calls++;
            current_alloc_list = &current_frame->allocs;
        }

    } END_WITHOUT_MALLOC_TRACKING;

    if (!old_zend_execute_internal) {
        execute_internal(execute_data_ptr, return_value_used);
    } else {
        old_zend_execute_internal(execute_data_ptr, return_value_used);
    }

    if (!ignore) {
        current_frame = current_frame->prev;
        current_alloc_list = &current_frame->allocs;
    }
}

ZEND_DLEXPORT int memprof_zend_startup(zend_extension *extension)
{
    int ret;

    memprof_initialized = 1;

    MALLOC_HOOK_SAVE_OLD();
    MALLOC_HOOK_SET_OWN();

    if (is_zend_mm()) {
        zend_mm_heap * heap = zend_mm_startup();
        zend_mm_set_custom_handlers(heap, zend_malloc_handler, zend_free_handler, zend_realloc_handler);
        orig_heap = zend_mm_set_heap(heap);
    }

    init_frame(&default_frame, NULL, "root", sizeof("root")-1);
    default_frame.calls = 1;

    zend_hash_del(CG(function_table), "memory_get_usage", sizeof("memory_get_usage"));
    zend_hash_del(CG(function_table), "memory_get_peak_usage", sizeof("memory_get_peak_usage"));

    ret = zend_startup_module(&memprof_module_entry);

    if (-1 == zend_get_resource_handle(extension)) {
        fprintf(stderr, "zend_get_resource_handle failed");
        abort();
    }
    memprof_reserved_offset = extension->resource_number;

    return ret;
}

{'fraction_non_alphanumeric': 0.13190686744380198, 'fraction_numerical': 0.003834142419838595, 'mean_word_length': 2.890532900276675, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 123, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6659971', 'n_tokens_mistral': 11325, 'n_tokens_neox': 11018, 'n_words': 2384}