zval aws_php_invoke_callback()

in ext/crt.c [20:133]


zval aws_php_invoke_callback(zval *callback, const char *arg_types, ...) {

    char *error = NULL;
    zend_fcall_info fci = {0};
    zend_fcall_info_cache fcc = empty_fcall_info_cache;
    if (zend_fcall_info_init(callback, IS_CALLABLE_CHECK_SYNTAX_ONLY, &fci, &fcc, NULL, &error) == FAILURE) {
        aws_php_throw_exception("Unable to initialize callback from callable via zend_fcall_info_init: %s", error);
    }

    /* Allocate the stack frame of zval arguments and fill them in */
    const size_t num_args = strlen(arg_types);
    zval *stack = alloca(sizeof(zval) * num_args);
    int arg_idx = 0;
    va_list va;
    va_start(va, arg_types);
    while (arg_idx < num_args) {
        const char arg_type = arg_types[arg_idx];
        switch (arg_type) {
            /* zval types */
            case 'a':
            case 'A':
            case 'n':
            case 'o':
            case 'r':
            case 'z': {
                zval *zval_val = va_arg(va, zval *);
                ZVAL_ZVAL(&stack[arg_idx], zval_val, 0, 0);
                break;
            }
            /* buffers/strings (char *, size_t) */
            case 'p':
            case 's': {
                const char *buf = va_arg(va, const char *);
                const size_t len = va_arg(va, size_t);
                aws_php_zval_stringl(&stack[arg_idx], buf, len);
                break;
            }
            /* other primitives */
            case 'b': {
                zend_bool bool_val = va_arg(va, int);
                ZVAL_BOOL(&stack[arg_idx], bool_val);
                break;
            }
            case 'd': {
                double double_val = va_arg(va, double);
                ZVAL_DOUBLE(&stack[arg_idx], double_val);
                break;
            }
            case 'l': {
                zend_ulong long_val = va_arg(va, zend_ulong);
                ZVAL_LONG(&stack[arg_idx], long_val);
                break;
            }
            /* strings (zend_string), not supported in PHP 5.6, therefore not supported */
            case 'P':
            case 'S':
            /* unsupported */
            case 'C':
            case 'f':
            case 'h':
            case 'H':
            case 'O':
                aws_php_throw_exception("Unsupported argument type to aws_php_invoke_callback: %c", arg_type);
                break;
            default:
                aws_php_throw_exception("Unsupported argument type to aws_php_invoke_callback: %c", arg_type);
                break;
        }
        ++arg_idx;
    }
    va_end(va);

    /* set up the stack for the call */
#if AWS_PHP_AT_LEAST_7
    zend_fcall_info_argp(&fci, num_args, stack);
#else
    /* PHP5.6 may mutate the arguments due to coercion */
    zval **arg_ptrs = alloca(sizeof(zval *) * num_args);
    zval ***args = alloca(sizeof(zval **) * num_args);
    for (int arg_idx = 0; arg_idx < num_args; ++arg_idx) {
        arg_ptrs[arg_idx] = &stack[arg_idx];
        args[arg_idx] = &arg_ptrs[arg_idx];
    }
    fci.param_count = num_args;
    fci.params = args;
#endif

    zval retval;
    /* PHP5 allocates its own return value, 7+ uses an existing one we provide */
#if !AWS_PHP_AT_LEAST_7
    zval *retval5 = NULL;
    fci.retval_ptr_ptr = &retval5;
#else
    fci.retval = &retval;
#endif

    if (zend_call_function(&fci, &fcc) == FAILURE) {
        aws_php_throw_exception("zend_call_function failed in aws_php_invoke_callback");
    }

#if !AWS_PHP_AT_LEAST_7
    /* initialize the local retval from the retval in retval_ptr_ptr above */
    if (retval5) {
        ZVAL_ZVAL(&retval, retval5, 1, 1);
    }
#endif

    /* Clean up arguments */
#if AWS_PHP_AT_LEAST_7
    zend_fcall_info_args_clear(&fci, 1);
#endif

    return retval;
}