<commit_before>#include "hiredis/hiredis.h"
#include "php_phpiredis.h"
#include "php_phpiredis_struct.h"

#include "ext/standard/head.h"
#include "ext/standard/info.h"

#define PHPIREDIS_CONNECTION_NAME "phpredis connection"
#define PHPIREDIS_PERSISTENT_CONNECTION_NAME "phpredis connection persistent"
#define PHPIREDIS_READER_NAME "phpredis reader"

int le_redis_reader_context;
int le_redis_context;
int le_redis_persistent_context;

#include <zend_API.h>

#ifdef ZEND_ENGINE_3
    #define PHPIREDIS_LEN_TYPE size_t
    #define PHPIREDIS_RESOURCE_TYPE zend_resource
    #define PHPIREDIS_RETURN_RESOURCE(connection, context) \
        RETURN_RES(zend_register_resource(connection, context))
#else
    #define PHPIREDIS_LEN_TYPE int
    #define PHPIREDIS_RESOURCE_TYPE zend_rsrc_list_entry
    #define PHPIREDIS_RETURN_RESOURCE(connection, context) \
        ZEND_REGISTER_RESOURCE(return_value, connection, context)
#endif

typedef struct callback {

#ifdef ZEND_ENGINE_3
    zval function;
#else
    zval *function;
#endif
} callback;

static
void convert_redis_to_php(phpiredis_reader* reader, zval* return_value, redisReply* reply TSRMLS_DC);

static
void s_destroy_connection(phpiredis_connection *connection TSRMLS_DC)
{
    if (connection) {
        pefree(connection->ip, connection->is_persistent);
        if (connection->c != NULL) {
            redisFree(connection->c);
        }
        pefree(connection, connection->is_persistent);
    }
}

#ifdef ZEND_ENGINE_3

static
void php_redis_connection_dtor(zend_resource *rsrc TSRMLS_DC)
{
    phpiredis_connection *connection = (phpiredis_connection*) rsrc->ptr;
    s_destroy_connection(connection TSRMLS_CC);
}

#else

static
void php_redis_connection_dtor(PHPIREDIS_RESOURCE_TYPE *rsrc TSRMLS_DC)
{
    phpiredis_connection *connection = (phpiredis_connection*) rsrc->ptr;
    s_destroy_connection(connection TSRMLS_CC);
}

#endif

static
void php_redis_reader_dtor(PHPIREDIS_RESOURCE_TYPE *rsrc TSRMLS_DC)
{
    phpiredis_reader *_reader = (void *) rsrc->ptr;

    if (_reader) {
        if (_reader->bufferedReply != NULL) {
            freeReplyObject(_reader->bufferedReply);
        }

        if (_reader->reader != NULL) {
            redisReplyReaderFree(_reader->reader);
        }

        if (_reader->error_callback != NULL) {
#ifdef ZEND_ENGINE_3
            Z_TRY_DELREF_P(&((callback*) _reader->error_callback)->function);
#else
            efree(((callback*) _reader->error_callback)->function);
#endif
            efree(_reader->error_callback);
        }

        if (_reader->status_callback != NULL) {
#ifdef ZEND_ENGINE_3
            Z_TRY_DELREF_P(&((callback*) _reader->status_callback)->function);
#else
            efree(((callback*) _reader->status_callback)->function);
#endif
            efree(_reader->status_callback);
        }

        efree(_reader);
    }
}

static
phpiredis_connection *s_create_connection (const char *ip, int port, zend_bool is_persistent)
{
    redisContext *c;
    phpiredis_connection *connection;

    if (ip[0] == '/') {
        // We simply ignore the value of "port" if the string value in "ip"
        // starts with a slash character indicating a UNIX domain socket path.
        c = redisConnectUnix(ip);
    } else {
        c = redisConnect(ip, port);
    }

    if (!c || c->err) {
        redisFree(c);
        return NULL;
    }

    connection                = pemalloc(sizeof(phpiredis_connection), is_persistent);
    connection->c             = c;
    connection->ip            = pestrdup(ip, is_persistent);
    connection->port          = port;
    connection->is_persistent = is_persistent;

    return connection;
}

PHP_FUNCTION(phpiredis_connect)
{
    phpiredis_connection *connection;
    char *ip;
    PHPIREDIS_LEN_TYPE ip_size;
    long port = 6379;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &ip, &ip_size, &port) == FAILURE) {
        return;
    }

    connection = s_create_connection(ip, port, 0);

    if (!connection) {
        RETURN_FALSE;
    }

    PHPIREDIS_RETURN_RESOURCE(connection, le_redis_context);
}

PHP_FUNCTION(phpiredis_pconnect)
{
    char *ip;
    PHPIREDIS_LEN_TYPE ip_size;
    long port = 6379;

    char *hashed_details = NULL;
    PHPIREDIS_LEN_TYPE hashed_details_length;
    phpiredis_connection *connection;
#ifdef ZEND_ENGINE_3
    zval *p_zval;
    zend_resource new_le;
#else
    zend_rsrc_list_entry new_le, *le;
#endif

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &ip, &ip_size, &port) == FAILURE) {
        return;
    }

    hashed_details_length = spprintf(&hashed_details, 0, "phpiredis_%s_%d", ip, (int)port);

#ifdef ZEND_ENGINE_3
    p_zval = zend_hash_str_find_ptr(&EG(persistent_list), hashed_details, hashed_details_length);

    if (p_zval != NULL) {
        if (Z_RES_P(p_zval)->type != le_redis_persistent_context) {
            RETURN_FALSE;
        }
        connection = (phpiredis_connection *) Z_RES_P(p_zval)->ptr;
#else
    if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length+1, (void **) &le)!=FAILURE) {
        if (Z_TYPE_P(le) != le_redis_persistent_context) {
            RETURN_FALSE;
        }
        connection = (phpiredis_connection *) le->ptr;
#endif
        efree(hashed_details);
        PHPIREDIS_RETURN_RESOURCE(connection, le_redis_persistent_context);
        return;
    }

    connection = s_create_connection(ip, port, 1);

    if (!connection) {
        efree(hashed_details);
        RETURN_FALSE;
    }

    new_le.type = le_redis_persistent_context;
    new_le.ptr = connection;

#ifdef ZEND_ENGINE_3  
    if (zend_hash_str_update_mem(&EG(persistent_list), hashed_details, hashed_details_length, &new_le, sizeof(zend_resource)) == NULL) {
#else
    if (zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length+1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL)==FAILURE) {
#endif

        s_destroy_connection (connection TSRMLS_CC);
        efree(hashed_details);
        RETURN_FALSE;
    }

    efree(hashed_details);
    
    PHPIREDIS_RETURN_RESOURCE(connection, le_redis_persistent_context);    
}

PHP_FUNCTION(phpiredis_disconnect)
{
    zval *connection;
    redisContext *c;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &connection) == FAILURE) {
        return;
    }

#ifdef ZEND_ENGINE_3
    c = (redisContext *)zend_fetch_resource2_ex(connection, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context);

    if (c == NULL) {
        //Resource fetching can fail.
        RETURN_FALSE;
    }
    zend_list_close(Z_RES_P(connection));
#else
    ZEND_FETCH_RESOURCE2(c, redisContext *, &connection, -1, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context);
    zend_list_delete(Z_LVAL_P(connection));
#endif

    RETURN_TRUE;
}

PHP_FUNCTION(phpiredis_multi_command)
{
    HashPosition pos;
    zval *resource;
    phpiredis_connection *connection;
    zval *p_arg2;
    
    int commands;
    int i;

#ifdef ZEND_ENGINE_3
    zend_resource *connection_resource;
    zval *p_zval;
#else 
    zval **tmp;
    zval temp;
#endif
    
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rz", &resource, &p_arg2) == FAILURE) {
        return;
    }
    
#ifdef ZEND_ENGINE_3
    connection_resource = Z_RES_P(resource);
    connection = (phpiredis_connection *)zend_fetch_resource2(connection_resource, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context);
    if (connection != NULL) {
        // Needs error check
    }
#else
    ZEND_FETCH_RESOURCE2(connection, phpiredis_connection *, &resource, -1, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context);
#endif

    zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(p_arg2), &pos);

    commands = 0;
    
#ifdef ZEND_ENGINE_3
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(p_arg2), p_zval) {
        ++commands;
        redisAppendCommand(connection->c, Z_STRVAL_P(p_zval));
    } ZEND_HASH_FOREACH_END();
#else
    while (zend_hash_get_current_data_ex(Z_ARRVAL_P(p_arg2), (void **) &tmp, &pos) == SUCCESS) {
        temp = **tmp;
        zval_copy_ctor(&temp);
        convert_to_string(&temp);
        ++commands;
        redisAppendCommand(connection->c, Z_STRVAL(temp));
        zend_hash_move_forward_ex(Z_ARRVAL_P(p_arg2), &pos);
        zval_dtor(&temp);
    }
#endif

    array_init(return_value);
    for (i = 0; i < commands; ++i) {
        redisReply *reply = NULL;
#ifdef ZEND_ENGINE_3
        zval result;
        zval *p_result = &result;
#else
        zval* p_result;
        MAKE_STD_ZVAL(p_result);
#endif

        if (redisGetReply(connection->c, (void *)&reply) != REDIS_OK) {
            for (; i < commands; ++i) {
                add_index_bool(return_value, i, 0);
            }

            if (reply) freeReplyObject(reply);
#ifndef ZEND_ENGINE_3
            efree(p_result);
#endif
            break;
        }

        convert_redis_to_php(NULL, p_result, reply TSRMLS_CC);
        add_index_zval(return_value, i, p_result);
        freeReplyObject(reply);
    }
}

#ifdef ZEND_ENGINE_3

PHP_FUNCTION(phpiredis_multi_command_bs)
{
    zval *resource;
    phpiredis_connection *connection;
    zval *cmds;
    zval *p_cmdArgs;
    zval *p_cmdArg;

    int cmdPos;
    int cmdSize;
    char **cmdElements;
    size_t *cmdElementslen;

    int commands;
    int i;

    zend_resource *connection_resource;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ra", &resource, &cmds) == FAILURE) {
        return;
    }

    connection_resource = Z_RES_P(resource);
    connection = (phpiredis_connection *)zend_fetch_resource2(connection_resource, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context);

    if (connection == NULL) {
        //Resource fetching can fail.
        //RETURN_FALSE;
    }

    commands = 0;

    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(cmds), p_cmdArgs) {
        cmdPos = 0;
        cmdSize = zend_hash_num_elements(Z_ARRVAL_P(p_cmdArgs));
        cmdElements = emalloc(sizeof(char*) * cmdSize);
        cmdElementslen = emalloc(sizeof(size_t) * cmdSize);

        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(p_cmdArgs), p_cmdArg) {        
            zend_string *str = zval_get_string(p_cmdArg);
            cmdElementslen[cmdPos] = (size_t) str->len;
            cmdElements[cmdPos] = emalloc(sizeof(char) * cmdElementslen[cmdPos]);
            memcpy(cmdElements[cmdPos], str->val, cmdElementslen[cmdPos]);
            ++cmdPos;
        } ZEND_HASH_FOREACH_END();

        redisAppendCommandArgv(connection->c, cmdSize, (const char **)cmdElements, cmdElementslen);

        for (; cmdPos > 0; --cmdPos) {
           efree(cmdElements[cmdPos-1]);
        }
        efree(cmdElements);
        efree(cmdElementslen);

        ++commands;
    } ZEND_HASH_FOREACH_END();


    array_init(return_value);
    for (i = 0; i < commands; ++i) {
        redisReply *reply = NULL;
        zval result;

        if (redisGetReply(connection->c, (void *)&reply) != REDIS_OK) {
            for (; i < commands; ++i) {
                add_index_bool(return_value, i, 0);
            }

            if (reply) freeReplyObject(reply);

            break;
        }

        convert_redis_to_php(NULL, &result, reply TSRMLS_CC);
        add_index_zval(return_value, i, &result);<commit_msg>fix memory leaks of testcase client_009<commit_after>#include "hiredis/hiredis.h"
#include "php_phpiredis.h"
#include "php_phpiredis_struct.h"

#include "ext/standard/head.h"
#include "ext/standard/info.h"

#define PHPIREDIS_CONNECTION_NAME "phpredis connection"
#define PHPIREDIS_PERSISTENT_CONNECTION_NAME "phpredis connection persistent"
#define PHPIREDIS_READER_NAME "phpredis reader"

int le_redis_reader_context;
int le_redis_context;
int le_redis_persistent_context;

#include <zend_API.h>

#ifdef ZEND_ENGINE_3
    #define PHPIREDIS_LEN_TYPE size_t
    #define PHPIREDIS_RESOURCE_TYPE zend_resource
    #define PHPIREDIS_RETURN_RESOURCE(connection, context) \
        RETURN_RES(zend_register_resource(connection, context))
#else
    #define PHPIREDIS_LEN_TYPE int
    #define PHPIREDIS_RESOURCE_TYPE zend_rsrc_list_entry
    #define PHPIREDIS_RETURN_RESOURCE(connection, context) \
        ZEND_REGISTER_RESOURCE(return_value, connection, context)
#endif

typedef struct callback {

#ifdef ZEND_ENGINE_3
    zval function;
#else
    zval *function;
#endif
} callback;

static
void convert_redis_to_php(phpiredis_reader* reader, zval* return_value, redisReply* reply TSRMLS_DC);

static
void s_destroy_connection(phpiredis_connection *connection TSRMLS_DC)
{
    if (connection) {
        pefree(connection->ip, connection->is_persistent);
        if (connection->c != NULL) {
            redisFree(connection->c);
        }
        pefree(connection, connection->is_persistent);
    }
}

#ifdef ZEND_ENGINE_3

static
void php_redis_connection_dtor(zend_resource *rsrc TSRMLS_DC)
{
    phpiredis_connection *connection = (phpiredis_connection*) rsrc->ptr;
    s_destroy_connection(connection TSRMLS_CC);
}

#else

static
void php_redis_connection_dtor(PHPIREDIS_RESOURCE_TYPE *rsrc TSRMLS_DC)
{
    phpiredis_connection *connection = (phpiredis_connection*) rsrc->ptr;
    s_destroy_connection(connection TSRMLS_CC);
}

#endif

static
void php_redis_reader_dtor(PHPIREDIS_RESOURCE_TYPE *rsrc TSRMLS_DC)
{
    phpiredis_reader *_reader = (void *) rsrc->ptr;

    if (_reader) {
        if (_reader->bufferedReply != NULL) {
            freeReplyObject(_reader->bufferedReply);
        }

        if (_reader->reader != NULL) {
            redisReplyReaderFree(_reader->reader);
        }

        if (_reader->error_callback != NULL) {
#ifdef ZEND_ENGINE_3
            Z_TRY_DELREF_P(&((callback*) _reader->error_callback)->function);
#else
            efree(((callback*) _reader->error_callback)->function);
#endif
            efree(_reader->error_callback);
        }

        if (_reader->status_callback != NULL) {
#ifdef ZEND_ENGINE_3
            Z_TRY_DELREF_P(&((callback*) _reader->status_callback)->function);
#else
            efree(((callback*) _reader->status_callback)->function);
#endif
            efree(_reader->status_callback);
        }

        efree(_reader);
    }
}

static
phpiredis_connection *s_create_connection (const char *ip, int port, zend_bool is_persistent)
{
    redisContext *c;
    phpiredis_connection *connection;

    if (ip[0] == '/') {
        // We simply ignore the value of "port" if the string value in "ip"
        // starts with a slash character indicating a UNIX domain socket path.
        c = redisConnectUnix(ip);
    } else {
        c = redisConnect(ip, port);
    }

    if (!c || c->err) {
        redisFree(c);
        return NULL;
    }

    connection                = pemalloc(sizeof(phpiredis_connection), is_persistent);
    connection->c             = c;
    connection->ip            = pestrdup(ip, is_persistent);
    connection->port          = port;
    connection->is_persistent = is_persistent;

    return connection;
}

PHP_FUNCTION(phpiredis_connect)
{
    phpiredis_connection *connection;
    char *ip;
    PHPIREDIS_LEN_TYPE ip_size;
    long port = 6379;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &ip, &ip_size, &port) == FAILURE) {
        return;
    }

    connection = s_create_connection(ip, port, 0);

    if (!connection) {
        RETURN_FALSE;
    }

    PHPIREDIS_RETURN_RESOURCE(connection, le_redis_context);
}

PHP_FUNCTION(phpiredis_pconnect)
{
    char *ip;
    PHPIREDIS_LEN_TYPE ip_size;
    long port = 6379;

    char *hashed_details = NULL;
    PHPIREDIS_LEN_TYPE hashed_details_length;
    phpiredis_connection *connection;
#ifdef ZEND_ENGINE_3
    zval *p_zval;
    zend_resource new_le;
#else
    zend_rsrc_list_entry new_le, *le;
#endif

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &ip, &ip_size, &port) == FAILURE) {
        return;
    }

    hashed_details_length = spprintf(&hashed_details, 0, "phpiredis_%s_%d", ip, (int)port);

#ifdef ZEND_ENGINE_3
    p_zval = zend_hash_str_find_ptr(&EG(persistent_list), hashed_details, hashed_details_length);

    if (p_zval != NULL) {
        if (Z_RES_P(p_zval)->type != le_redis_persistent_context) {
            RETURN_FALSE;
        }
        connection = (phpiredis_connection *) Z_RES_P(p_zval)->ptr;
#else
    if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length+1, (void **) &le)!=FAILURE) {
        if (Z_TYPE_P(le) != le_redis_persistent_context) {
            RETURN_FALSE;
        }
        connection = (phpiredis_connection *) le->ptr;
#endif
        efree(hashed_details);
        PHPIREDIS_RETURN_RESOURCE(connection, le_redis_persistent_context);
        return;
    }

    connection = s_create_connection(ip, port, 1);

    if (!connection) {
        efree(hashed_details);
        RETURN_FALSE;
    }

    new_le.type = le_redis_persistent_context;
    new_le.ptr = connection;

#ifdef ZEND_ENGINE_3  
    if (zend_hash_str_update_mem(&EG(persistent_list), hashed_details, hashed_details_length, &new_le, sizeof(zend_resource)) == NULL) {
#else
    if (zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length+1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL)==FAILURE) {
#endif

        s_destroy_connection (connection TSRMLS_CC);
        efree(hashed_details);
        RETURN_FALSE;
    }

    efree(hashed_details);
    
    PHPIREDIS_RETURN_RESOURCE(connection, le_redis_persistent_context);    
}

PHP_FUNCTION(phpiredis_disconnect)
{
    zval *connection;
    redisContext *c;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &connection) == FAILURE) {
        return;
    }

#ifdef ZEND_ENGINE_3
    c = (redisContext *)zend_fetch_resource2_ex(connection, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context);

    if (c == NULL) {
        //Resource fetching can fail.
        RETURN_FALSE;
    }
    zend_list_close(Z_RES_P(connection));
#else
    ZEND_FETCH_RESOURCE2(c, redisContext *, &connection, -1, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context);
    zend_list_delete(Z_LVAL_P(connection));
#endif

    RETURN_TRUE;
}

PHP_FUNCTION(phpiredis_multi_command)
{
    HashPosition pos;
    zval *resource;
    phpiredis_connection *connection;
    zval *p_arg2;
    
    int commands;
    int i;

#ifdef ZEND_ENGINE_3
    zend_resource *connection_resource;
    zval *p_zval;
#else 
    zval **tmp;
    zval temp;
#endif
    
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rz", &resource, &p_arg2) == FAILURE) {
        return;
    }
    
#ifdef ZEND_ENGINE_3
    connection_resource = Z_RES_P(resource);
    connection = (phpiredis_connection *)zend_fetch_resource2(connection_resource, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context);
    if (connection != NULL) {
        // Needs error check
    }
#else
    ZEND_FETCH_RESOURCE2(connection, phpiredis_connection *, &resource, -1, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context);
#endif

    zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(p_arg2), &pos);

    commands = 0;
    
#ifdef ZEND_ENGINE_3
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(p_arg2), p_zval) {
        ++commands;
        redisAppendCommand(connection->c, Z_STRVAL_P(p_zval));
    } ZEND_HASH_FOREACH_END();
#else
    while (zend_hash_get_current_data_ex(Z_ARRVAL_P(p_arg2), (void **) &tmp, &pos) == SUCCESS) {
        temp = **tmp;
        zval_copy_ctor(&temp);
        convert_to_string(&temp);
        ++commands;
        redisAppendCommand(connection->c, Z_STRVAL(temp));
        zend_hash_move_forward_ex(Z_ARRVAL_P(p_arg2), &pos);
        zval_dtor(&temp);
    }
#endif

    array_init(return_value);
    for (i = 0; i < commands; ++i) {
        redisReply *reply = NULL;
#ifdef ZEND_ENGINE_3
        zval result;
        zval *p_result = &result;
#else
        zval* p_result;
        MAKE_STD_ZVAL(p_result);
#endif

        if (redisGetReply(connection->c, (void *)&reply) != REDIS_OK) {
            for (; i < commands; ++i) {
                add_index_bool(return_value, i, 0);
            }

            if (reply) freeReplyObject(reply);
#ifndef ZEND_ENGINE_3
            efree(p_result);
#endif
            break;
        }

        convert_redis_to_php(NULL, p_result, reply TSRMLS_CC);
        add_index_zval(return_value, i, p_result);
        freeReplyObject(reply);
    }
}

#ifdef ZEND_ENGINE_3

PHP_FUNCTION(phpiredis_multi_command_bs)
{
    zval *resource;
    phpiredis_connection *connection;
    zval *cmds;
    zval *p_cmdArgs;
    zval *p_cmdArg;

    int cmdPos;
    int cmdSize;
    char **cmdElements;
    size_t *cmdElementslen;

    int commands;
    int i;

    zend_resource *connection_resource;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ra", &resource, &cmds) == FAILURE) {
        return;
    }

    connection_resource = Z_RES_P(resource);
    connection = (phpiredis_connection *)zend_fetch_resource2(connection_resource, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context);

    if (connection == NULL) {
        //Resource fetching can fail.
        //RETURN_FALSE;
    }

    commands = 0;

    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(cmds), p_cmdArgs) {
        cmdPos = 0;
        cmdSize = zend_hash_num_elements(Z_ARRVAL_P(p_cmdArgs));
        cmdElements = emalloc(sizeof(char*) * cmdSize);
        cmdElementslen = emalloc(sizeof(size_t) * cmdSize);

        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(p_cmdArgs), p_cmdArg) {
            zend_string *str;
            int need_free = 0;
            if (Z_TYPE_P(p_cmdArg) != IS_STRING) {
                str = zval_get_string(p_cmdArg);
                need_free = 1;
            } else {
                str = Z_STR_P(p_cmdArg);
            }
            cmdElementslen[cmdPos] = (size_t) str->len;
            cmdElements[cmdPos] = emalloc(sizeof(char) * cmdElementslen[cmdPos]);
            memcpy(cmdElements[cmdPos], str->val, cmdElementslen[cmdPos]);
            ++cmdPos;

            if (need_free) {
                zend_string_release(str);
            }
        } ZEND_HASH_FOREACH_END();

        redisAppendCommandArgv(connection->c, cmdSize, (const char **)cmdElements, cmdElementslen);

        for (; cmdPos > 0; --cmdPos) {
           efree(cmdElements[cmdPos-1]);
        }
        efree(cmdElements);
        efree(cmdElementslen);

        ++commands;
    } ZEND_HASH_FOREACH_END();


    array_init(return_value);
    for (i = 0; i < commands; ++i) {
        redisReply *reply = NULL;
        zval result;

        if (redisGetReply(connection->c, (void *)&reply) != REDIS_OK) {
            for (; i < commands; ++i) {
                add_index_bool(return_value, i, 0);
            }

            if (reply) freeReplyObject(reply);

            break;
        }

        convert_redis_to_php(NULL, &result, reply TSRMLS_CC);
        add_index_zval(return_value, i, &result);
{'fraction_non_alphanumeric': 0.105932572523739, 'fraction_numerical': 0.004660684728634898, 'mean_word_length': 3.269059129788025, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 85, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1939874', 'n_tokens_mistral': 8306, 'n_tokens_neox': 7979, 'n_words': 1383}