raising the head after a three-weeks refactoring
[m6w6/ext-psi] / src / call.c
diff --git a/src/call.c b/src/call.c
new file mode 100644 (file)
index 0000000..46a1dde
--- /dev/null
@@ -0,0 +1,545 @@
+/*******************************************************************************
+ Copyright (c) 2016, Michael Wallner <mike@php.net>.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+     * Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+     * Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*******************************************************************************/
+
+#include "php_psi_stdinc.h"
+#include "context.h"
+#include "data.h"
+#include "call.h"
+
+#include "php.h"
+#include "zend_exceptions.h"
+
+struct psi_call_frame_argument *psi_call_frame_argument_init(struct psi_impl_arg *spec,
+               impl_val *ival, zval *zptr, int is_vararg) {
+       struct psi_call_frame_argument *frame_arg = ecalloc(1, sizeof(*frame_arg));
+
+
+       if ((frame_arg->zval_ptr = zptr)) {
+               ZVAL_DEREF(frame_arg->zval_ptr);
+
+               /* use userland type if the declared type of the vararg is mixed */
+               if (is_vararg) {
+                       if (spec->type->type == PSI_T_MIXED) {
+                               switch (Z_TYPE_P(zptr)) {
+                               case IS_TRUE:
+                                       frame_arg->va_type = PSI_T_BOOL;
+                                       ival->zend.bval = 1;
+                                       break;
+                               case IS_FALSE:
+                                       frame_arg->va_type = PSI_T_BOOL;
+                                       ival->zend.bval = 0;
+                                       break;
+                               case IS_LONG:
+                                       frame_arg->va_type = PSI_T_INT;
+                                       ival->zend.lval = Z_LVAL_P(zptr);
+                                       break;
+                               case IS_DOUBLE:
+                                       frame_arg->va_type = PSI_T_FLOAT;
+                                       ival->dval = Z_DVAL_P(zptr);
+                                       break;
+                               default:
+                                       frame_arg->va_type = PSI_T_STRING;
+                                       ival->zend.str = zval_get_string(zptr);
+                                       break;
+                               }
+                       } else {
+                               frame_arg->va_type = spec->type->type;
+                       }
+               }
+       }
+
+       frame_arg->ival = *ival;
+       frame_arg->ival_ptr = &frame_arg->ival;
+       frame_arg->spec = spec;
+
+       return frame_arg;
+}
+
+void psi_call_frame_argument_free(struct psi_call_frame_argument *arg) {
+       switch (arg->spec->type->type) {
+       case PSI_T_STRING:
+               if (arg->ival.zend.str) {
+                       zend_string_release(arg->ival.zend.str);
+               }
+               break;
+       case PSI_T_CALLABLE:
+               if (arg->ival.zend.cb) {
+                       if (arg->ival.zend.cb->fci.size) {
+                               zend_fcall_info_args_clear(&arg->ival.zend.cb->fci, 1);
+                       }
+                       efree(arg->ival.zend.cb);
+               }
+               break;
+       default:
+               break;
+       }
+       if (arg->ival_ptr && arg->ival_ptr != &arg->temp_val && arg->ival_ptr != &arg->ival) {
+               efree(arg->ival_ptr);
+       }
+       efree(arg);
+}
+
+struct psi_call_frame_symbol *psi_call_frame_symbol_init(struct psi_decl_var *dvar) {
+       struct psi_call_frame_symbol *frame_sym;
+       size_t size = psi_decl_type_get_size(dvar->arg->type, NULL);
+
+       frame_sym = ecalloc(1, sizeof(*frame_sym) + size);
+       frame_sym->ptr = frame_sym->ival_ptr = &frame_sym->temp_val;
+
+       return frame_sym;
+}
+
+void psi_call_frame_symbol_free(struct psi_call_frame_symbol *sym) {
+       efree(sym);
+}
+
+static void psi_call_frame_free_argument(zval *zptr) {
+       psi_call_frame_argument_free(Z_PTR_P(zptr));
+}
+
+static void psi_call_frame_free_symbol(zval *zptr) {
+       psi_call_frame_symbol_free(Z_PTR_P(zptr));
+}
+
+struct psi_call_frame_auto_free {
+       void *data;
+       void (*dtor)(void *);
+};
+
+static void psi_call_frame_free_temp(void *ptr) {
+       struct psi_call_frame_auto_free *f = ptr;
+
+       if (f->data) {
+               if (f->dtor) {
+                       f->dtor(&f->data);
+               } else {
+                       efree(f->data);
+               }
+       }
+}
+
+struct psi_call_frame *psi_call_frame_init(struct psi_context *C, struct psi_decl *decl, struct psi_impl *impl) {
+       struct psi_call_frame *frame = ecalloc(1, sizeof(*frame));
+
+       frame->context = C;
+       frame->decl = decl;
+       frame->impl = impl;
+
+       zend_hash_init(&frame->arguments, 8, NULL, psi_call_frame_free_argument, 0);
+       zend_hash_init(&frame->symbols, 8, NULL, psi_call_frame_free_symbol, 0);
+       zend_llist_init(&frame->temp, sizeof(struct psi_call_frame_auto_free), psi_call_frame_free_temp, 0);
+
+       return frame;
+}
+
+struct psi_call_frame_symbol *psi_call_frame_fetch_symbol(
+               struct psi_call_frame *frame, struct psi_decl_var *dvar) {
+       struct psi_call_frame_symbol *frame_sym;
+
+       frame_sym = zend_hash_str_find_ptr(&frame->symbols, dvar->fqn, strlen(dvar->fqn));
+       if (!frame_sym) {
+               frame_sym = zend_hash_str_add_ptr(&frame->symbols, dvar->fqn, strlen(dvar->fqn),
+                               psi_call_frame_symbol_init(dvar));
+       }
+       return frame_sym;
+}
+
+zval *psi_call_frame_new_argument(struct psi_call_frame *frame,
+               struct psi_call_frame_argument *frame_arg) {
+       if (frame_arg->va_type) {
+               /* varargs are just appended with numeric indices */
+               return zend_hash_next_index_insert_ptr(&frame->arguments, frame_arg);
+       } else {
+               return zend_hash_str_add_ptr(&frame->arguments,
+                               frame_arg->spec->var->name, strlen(frame_arg->spec->var->name),
+                               frame_arg);
+       }
+}
+
+zval *psi_call_frame_sub_argument(struct psi_call_frame *frame,
+               struct psi_impl_var *inner_var, zval *outer_zval, const char *name) {
+       struct psi_call_frame_argument *iarg;
+       zval *inner_zval = zend_symtable_str_find(Z_ARRVAL_P(outer_zval),
+                       &inner_var->name[1], strlen(&inner_var->name[1]));
+
+       if (!inner_zval) {
+               zval empty_zval;
+
+               ZVAL_NULL(&empty_zval);
+               inner_zval = zend_symtable_str_update(Z_ARRVAL_P(outer_zval),
+                               &inner_var->name[1], strlen(&inner_var->name[1]),
+                               &empty_zval);
+       }
+
+       iarg = psi_call_frame_get_argument(frame, name);
+
+       if (!iarg) {
+               struct psi_call_frame_argument *frame_arg;
+               impl_val empty_val = {0};
+               struct psi_impl_arg *carg_spec = psi_impl_arg_init(
+                               psi_impl_type_init(PSI_T_MIXED, "mixed"),
+                               psi_impl_var_copy(inner_var), NULL);
+
+               psi_call_frame_push_auto_ex(frame, carg_spec, (void(*)(void*)) psi_impl_arg_free);
+               frame_arg = psi_call_frame_argument_init(carg_spec, &empty_val, inner_zval, 0);
+               zend_hash_str_add_ptr(&frame->arguments, name, strlen(name), frame_arg);
+       }
+
+       return inner_zval;
+}
+
+struct psi_call_frame_argument *psi_call_frame_get_argument(
+               struct psi_call_frame *frame, const char *name) {
+       return zend_hash_str_find_ptr(&frame->arguments, name, strlen(name));
+}
+
+size_t psi_call_frame_num_var_args(struct psi_call_frame *frame) {
+       return zend_hash_next_free_element(&frame->arguments);
+}
+
+size_t psi_call_frame_num_fixed_args(struct psi_call_frame *frame) {
+       return psi_plist_count(frame->decl->args);
+}
+
+struct psi_call_frame_argument *psi_call_frame_get_var_argument(
+               struct psi_call_frame *frame, zend_long index) {
+       return zend_hash_index_find_ptr(&frame->arguments, index);
+}
+
+void **psi_call_frame_get_arg_pointers(struct psi_call_frame *frame) {
+       return frame->pointers;
+}
+
+ZEND_RESULT_CODE psi_call_frame_parse_args(struct psi_call_frame *frame,
+               zend_execute_data *execute_data) {
+       size_t i, argc = psi_plist_count(frame->impl->func->args);
+       zend_error_handling zeh;
+
+       zend_replace_error_handling(EH_THROW, zend_exception_get_default(), &zeh);
+
+       if (!argc) {
+               ZEND_RESULT_CODE rv;
+
+               rv = zend_parse_parameters_none();
+               zend_restore_error_handling(&zeh);
+               return rv;
+       }
+
+       ZEND_PARSE_PARAMETERS_START(
+                       psi_impl_num_min_args(frame->impl),
+                       frame->impl->func->vararg ? -1 : argc
+       )
+       nextarg: {
+
+               struct psi_impl_arg *iarg;
+               impl_val ival = {0};
+
+               if (frame->impl->func->vararg && _i >= argc) {
+                       iarg = frame->impl->func->vararg;
+                       Z_PARAM_OPTIONAL;
+               } else {
+                       psi_plist_get(frame->impl->func->args, _i, &iarg);
+                       if (iarg->def) {
+                               Z_PARAM_OPTIONAL;
+                       }
+               }
+
+               if (PSI_T_BOOL == iarg->type->type) {
+                       Z_PARAM_BOOL(ival.zend.bval);
+               } else if (PSI_T_INT == iarg->type->type) {
+                       Z_PARAM_LONG(ival.zend.lval);
+               } else if (PSI_T_FLOAT == iarg->type->type || PSI_T_DOUBLE == iarg->type->type) {
+                       Z_PARAM_DOUBLE(ival.dval);
+               } else if (PSI_T_STRING == iarg->type->type) {
+                       Z_PARAM_STR_EX(ival.zend.str, 1, iarg->var->reference);
+                       if (ival.zend.str) {
+                               zend_string_addref(ival.zend.str);
+                       }
+               } else if (PSI_T_ARRAY == iarg->type->type) {
+                       zval *tmp;
+                       Z_PARAM_ARRAY_EX(tmp, _optional || iarg->var->reference,
+                                       iarg->var->reference);
+               } else if (PSI_T_OBJECT == iarg->type->type) {
+                       Z_PARAM_PROLOGUE(0);
+               } else if (PSI_T_MIXED == iarg->type->type) {
+                       Z_PARAM_PROLOGUE(0);
+               } else if (PSI_T_CALLABLE == iarg->type->type) {
+                       zend_fcall_info fci;
+                       zend_fcall_info_cache fcc;
+
+                       Z_PARAM_FUNC_EX(fci, fcc, 1, 0);
+
+                       if (fci.size) {
+                               ival.zend.cb = ecalloc(1, sizeof(zend_fcall));
+                               ival.zend.cb->fci = fci;
+                               ival.zend.cb->fcc = fcc;
+                       }
+               } else {
+                       error_code = ZPP_ERROR_FAILURE;
+                       break;
+               }
+
+               psi_call_frame_new_argument(frame,
+                               psi_call_frame_argument_init(iarg, &ival, _arg, _i > argc));
+
+               if (_i < _num_args) {
+                       goto nextarg;
+               }
+       }
+       ZEND_PARSE_PARAMETERS_END_EX(
+               zend_restore_error_handling(&zeh);
+               return FAILURE;
+       );
+
+       /* set up defaults */
+       for (i = EX_NUM_ARGS(); i < argc; ++i) {
+               struct psi_impl_arg *iarg;
+
+               psi_plist_get(frame->impl->func->args, i, &iarg);
+
+               assert(iarg->def);
+               psi_call_frame_new_argument(frame, psi_call_frame_argument_init(iarg,
+                               &iarg->def->ival, NULL, 0));
+       }
+
+       zend_restore_error_handling(&zeh);
+       return SUCCESS;
+}
+
+void psi_call_frame_enter(struct psi_call_frame *frame) {
+       size_t argc = psi_call_frame_num_fixed_args(frame);
+       size_t va_count = psi_call_frame_num_var_args(frame);
+       size_t rsize = psi_decl_arg_get_size(frame->decl->func);
+       struct psi_call_frame_symbol *rv_sym;
+
+       /* initialize ffi argument array */
+       frame->pointers = ecalloc(argc + va_count + 1, sizeof(void *));
+
+       /* initialize return value symbol */
+       rv_sym = psi_call_frame_fetch_symbol(frame, frame->decl->func->var);
+       if (rsize > sizeof(impl_val)) {
+               rv_sym->ival_ptr = ecalloc(1, rsize);
+       } else {
+               rv_sym->ival_ptr = &rv_sym->temp_val;
+       }
+       frame->rpointer = rv_sym->ptr = rv_sym->ival_ptr;
+}
+
+ZEND_RESULT_CODE psi_call_frame_do_let(struct psi_call_frame *frame) {
+       size_t i;
+       struct psi_let_stmt *let;
+       struct psi_decl_arg *arg;
+       size_t argc = psi_call_frame_num_fixed_args(frame);
+       size_t va_count = psi_call_frame_num_var_args(frame);
+
+       for (i = 0; psi_plist_get(frame->impl->stmts.let, i, &let); ++i) {
+               psi_let_stmt_exec(let, frame);
+       }
+       for (i = 0; psi_plist_get(frame->decl->args, i, &arg); ++i) {
+               struct psi_let_stmt *let;
+               struct psi_call_frame_symbol *frame_sym;
+
+               let = psi_impl_get_let(frame->impl, arg->var);
+               frame_sym = psi_call_frame_fetch_symbol(frame, let->exp->var);
+               frame->pointers[i] = frame_sym->ptr;
+       }
+       /* varargs */
+       if (va_count) {
+               for (i = 0; i < va_count; ++i) {
+                       struct psi_call_frame_argument *frame_arg;
+                       psi_marshal_let let_fn;
+                       void *temp = NULL;
+
+                       frame_arg = psi_call_frame_get_var_argument(frame, i);
+                       switch (frame_arg->va_type) {
+                       case PSI_T_BOOL:        let_fn = psi_let_boolval;       break;
+                       case PSI_T_INT:         let_fn = psi_let_intval;        break;
+                       case PSI_T_FLOAT:
+                       case PSI_T_DOUBLE:      let_fn = psi_let_floatval;      break;
+                       case PSI_T_STRING:      let_fn = psi_let_strval;        break;
+                       default:
+                               assert(0);
+                       }
+
+                       frame_arg->ival_ptr = let_fn(&frame_arg->temp_val, NULL, frame_arg->va_type,
+                                       &frame_arg->ival, frame_arg->zval_ptr, &temp);
+                       if (temp) {
+                               psi_call_frame_push_auto(frame, temp);
+                       }
+
+                       frame->pointers[argc + i] = frame_arg->ival_ptr;
+               }
+       }
+
+       return SUCCESS;
+}
+
+void psi_call_frame_do_call(struct psi_call_frame *frame) {
+       size_t va_count = psi_call_frame_num_var_args(frame);
+
+       if (va_count) {
+               void **va_types = ecalloc(va_count, sizeof(void *));
+               size_t i;
+
+               for (i = 0; i < va_count; ++i) {
+                       struct psi_call_frame_argument *frame_arg;
+
+                       frame_arg = psi_call_frame_get_var_argument(frame, i);
+                       va_types[i] = frame->context->ops->query(frame->context,
+                                       PSI_CONTEXT_QUERY_TYPE, &frame_arg->va_type);
+               }
+
+               frame->context->ops->call_va(frame->context,
+                               frame,
+                               frame->decl,
+                               frame->rpointer,
+                               frame->pointers,
+                               va_count,
+                               va_types);
+
+               efree(va_types);
+       } else {
+               frame->context->ops->call(frame->context,
+                               frame,
+                               frame->decl,
+                               frame->rpointer,
+                               frame->pointers);
+       }
+}
+
+void psi_call_frame_do_callback(struct psi_call_frame *frame, struct psi_call_frame_callback *cbdata)
+{
+       size_t i;
+       void *retptr;
+       ZEND_RESULT_CODE rc;
+       struct psi_let_callback *cb = cbdata->cb->data.callback;
+       zval return_value, *zargv = ecalloc(cbdata->argc, sizeof(*zargv));
+       struct psi_call_frame_argument *frame_arg;
+
+       assert(cbdata->argc == psi_plist_count(cb->decl->args));
+
+       /* prepare args for the userland call */
+       for (i = 0; i < cbdata->argc; ++i) {
+               struct psi_set_exp *set_exp;
+               struct psi_decl_var *set_var;
+               struct psi_call_frame_symbol *set_sym;
+
+               psi_plist_get(cb->args, i, &set_exp);
+               set_var = psi_set_exp_get_decl_var(set_exp);
+               set_sym = psi_call_frame_fetch_symbol(frame, set_var);
+
+               set_sym->ptr = cbdata->argv[i];
+               psi_set_exp_exec_ex(set_exp, &zargv[i], set_sym->ptr, frame);
+       }
+
+       frame_arg = psi_call_frame_get_argument(frame, cb->func->var->fqn);
+
+       /* callback into userland */
+       ZVAL_UNDEF(&return_value);
+       zend_fcall_info_argp(&frame_arg->ival_ptr->zend.cb->fci, cbdata->argc, zargv);
+       rc = zend_fcall_info_call(&frame_arg->ival_ptr->zend.cb->fci,
+                       &frame_arg->ival_ptr->zend.cb->fcc,     &return_value, NULL);
+       assert(rc == SUCCESS);
+
+       /* marshal return value of the userland call */
+       frame_arg->zval_ptr = &return_value;
+       retptr = psi_let_func_exec(cbdata->cb, cb->func, cb->decl->func, frame);
+       memcpy(cbdata->rval, retptr, psi_decl_arg_get_size(cb->decl->func));
+
+       /* cleanup */
+       zend_fcall_info_args_clear(&frame_arg->ival_ptr->zend.cb->fci, 0);
+       for (i = 0; i < cbdata->argc; ++i) {
+               zval_ptr_dtor(&zargv[i]);
+       }
+       efree(zargv);
+}
+
+void psi_call_frame_do_return(struct psi_call_frame *frame, zval *return_value) {
+       struct psi_return_stmt *ret;
+
+       psi_plist_get(frame->impl->stmts.ret, 0, &ret);
+       psi_return_stmt_exec(ret, return_value, frame);
+}
+
+void psi_call_frame_do_set(struct psi_call_frame *frame) {
+       size_t i = 0;
+       struct psi_set_stmt *set;
+
+       while (psi_plist_get(frame->impl->stmts.set, i++, &set)) {
+               psi_set_stmt_exec(set, frame);
+       }
+}
+
+void psi_call_frame_do_free(struct psi_call_frame *frame) {
+       size_t i = 0;
+       struct psi_free_stmt *fre;
+
+       while (psi_plist_get(frame->impl->stmts.fre, i++, &fre)) {
+               psi_free_stmt_exec(fre, frame);
+       }
+}
+
+void **psi_call_frame_push_auto_ex(struct psi_call_frame *frame, void *auto_free, void (*dtor)(void*)) {
+       struct psi_call_frame_auto_free f;
+
+       f.data = auto_free;
+       f.dtor = dtor;
+
+       zend_llist_add_element(&frame->temp, &f);
+       return &((struct psi_call_frame_auto_free *) zend_llist_get_last(&frame->temp))->data;
+}
+
+void **psi_call_frame_push_auto(struct psi_call_frame *frame, void *auto_free) {
+       return psi_call_frame_push_auto_ex(frame, auto_free, NULL);
+}
+
+static void psi_call_frame_local_auto_dtor(void *auto_list)
+{
+       zend_llist_destroy(auto_list);
+       efree(auto_list);
+}
+
+#include "php_psi.h"
+
+void psi_call_frame_free(struct psi_call_frame *frame) {
+       zend_hash_destroy(&frame->arguments);
+       zend_hash_destroy(&frame->symbols);
+       if (frame->impl && frame->impl->func->static_memory) {
+               zend_llist *temp = emalloc(sizeof(*temp));
+               zval zlocal;
+
+               memcpy(temp, &frame->temp, sizeof(*temp));
+               ZVAL_OBJ(&zlocal, psi_object_init_ex(NULL, temp, psi_call_frame_local_auto_dtor));
+               zend_set_local_var_str(frame->impl->func->name,
+                               strlen(frame->impl->func->name), &zlocal, /* force */ 1);
+       } else {
+               zend_llist_destroy(&frame->temp);
+       }
+       efree(frame->pointers);
+       efree(frame);
+}
+
+
+