diff --git a/Makefile b/Makefile index 7f8f5ec6..d50e5de9 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ INSTALL_LIB = ${INSTALL_PREFIX}/lib # SONAME = 2.4 -VERSION = 2.4.3 +VERSION = 2.4.5 # diff --git a/include/value.h b/include/value.h index 8a56f87b..c9f48f1c 100644 --- a/include/value.h +++ b/include/value.h @@ -15,7 +15,7 @@ * this class. * * @author Emiel Bruijntjes - * @copyright 2013 - 2019 Copernica BV + * @copyright 2013 - 2024 Copernica BV */ /** @@ -1133,7 +1133,7 @@ class PHPCPP_EXPORT Value : private HashParent * @param argv The parameters * @return Value */ - Value exec(int argc, Value *argv) const; + Value exec(int argc, Value argv[]) const; /** * Call method with a number of parameters @@ -1142,8 +1142,8 @@ class PHPCPP_EXPORT Value : private HashParent * @param argv The parameters * @return Value */ - Value exec(const char *name, int argc, Value *argv) const; - Value exec(const char *name, int argc, Value *argv); + Value exec(const char *name, int argc, Value argv[]) const; + Value exec(const char *name, int argc, Value argv[]); /** * Refcount - the number of references to the value @@ -1242,6 +1242,7 @@ class PHPCPP_EXPORT Value : private HashParent friend class Script; friend class ConstantImpl; friend class Stream; + friend class ExecArguments; /** * Friend functions which have to access that zval directly diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index 2bcc37c1..0541ec24 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -4,7 +4,7 @@ * Implementation file for the ClassImpl class * * @author Emiel Bruijntjes - * @copyright 2014 - 2019 Copernica BV + * @copyright 2014 - 2024 Copernica BV */ #include "includes.h" #include @@ -207,7 +207,10 @@ zend_function *ClassImpl::getMethod(zend_object **object, zend_string *method, c // had an implementation here that used a static variable, and that worked too, // but we'll follow thread safe implementation of the Zend engine here, although // it is strange to allocate and free memory in one and the same method call (free() - // call happens in call_method()) + // call happens in call_method()) (2024-10-13 extra info: the method_exists() + // function and our own Value::isCallable() method expect this to be emalloc()- + // allocated buffer, because they both call zend_free_trampoline() (which is + // effectively an efree() call) on the returned function-structure) auto *data = (CallData *)emalloc(sizeof(CallData)); auto *function = &data->func; diff --git a/zend/execarguments.h b/zend/execarguments.h new file mode 100644 index 00000000..da5461f5 --- /dev/null +++ b/zend/execarguments.h @@ -0,0 +1,102 @@ +/** + * ExecArguments.h + * + * Helper class that we use to turn an array of Value objects into an + * array of zval parameters + * + * @author Emiel Bruijntjes + * @copyright 2024 Copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Begin of namespace + */ +namespace Php { + +/** + * Class definition + */ +class ExecArguments +{ +private: + /** + * Short-array-optimization (most exec calls do not have more than 10 parameters) + * @var zval[] + */ + zval _preallocated[10]; + + /** + * The actual arguments + * @var zval[] + */ + zval *_argv; + + /** + * The number of arguments + * @var size_t + */ + size_t _argc; + +public: + /** + * Default constructor + */ + ExecArguments() : _argv(_preallocated), _argc(0) {} + + /** + * Constructor + * @param argc + * @param argv + */ + ExecArguments(size_t argc, Value argv[]) : _argv(_preallocated), _argc(argc) + { + // if there are too many arguments, we allocate them right away + if (_argc > 10) _argv = (zval *)malloc(sizeof(zval) * _argc); + + // convert Value objects to zval array with references + for (size_t i = 0; i < argc; ++i) + { + // make sure the original variable is a reference so that our copy points to the same data + // @todo not sure if this is needed, do we need to turn the parameters into references to allow for pass-by-reference parameters? + //if (!Z_ISREF_P(argv[i]._val)) ZVAL_MAKE_REF(argv[i]._val); + + // copy the zval + ZVAL_COPY(&_argv[i], argv[i]._val); + } + } + + /** + * No copying (we could implement this later, but for now this is not needed) + * @param that + */ + ExecArguments(const ExecArguments &that) = delete; + + /** + * Destructor + */ + virtual ~ExecArguments() + { + // destruct all zval objects + for (size_t i = 0; i < _argc; ++i) zval_ptr_dtor(&_argv[i]); + + // deallocate memory + if (_argv != _preallocated) free(_argv); + } + + /** + * Convert to a argv[] + * @return zval[] + */ + zval *argv() { return _argv; } + int argc() { return _argc; } +}; + +/** + * End of namespace + */ +} diff --git a/zend/value.cpp b/zend/value.cpp index 45d65623..0ff917cc 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -22,12 +22,13 @@ * * * @author Emiel Bruijntjes - * @copyright 2013 - 2019 Copernica BV + * @copyright 2019 - 2024 Copernica BV */ #include "includes.h" #include "string.h" #include "lowercase.h" #include "macros.h" +#include "execarguments.h" /** * Set up namespace @@ -822,6 +823,19 @@ static Value do_exec(const zval *object, zval *method, int argc, zval *argv) } } +/** + * Helper function that runs the actual call + * @param object The object to call it on + * @param method The function or method to call + * @param args The parameters + * @return Value + */ +static Value do_exec(const zval *object, zval *method, ExecArguments &args) +{ + // pass on + return do_exec(object, method, args.argc(), args.argv()); +} + /** * Call the function in PHP * We have ten variants of this function, depending on the number of parameters @@ -877,8 +891,11 @@ bool Value::isCallable(const char *name) bool result = func->common.scope == zend_ce_closure && zend_string_equals_cstr(methodname.value(), ZEND_INVOKE_FUNC_NAME, ::strlen(ZEND_INVOKE_FUNC_NAME)); #endif - // free resources (still don't get this code, copied from zend_builtin_functions.c) - zend_string_release(func->common.function_name); + // in method_exists(), there is also a zend_string_release() call here, but I dont think we + // need it here, because the methodname is already cleanup by the destructor of the LowerCase class + //zend_string_release(func->common.function_name); + + // free resources, just like method_exists() does zend_free_trampoline(func); // done @@ -919,16 +936,13 @@ Value Value::call(const char *name) * @param argv The parameters * @return Value */ -Value Value::exec(int argc, Value *argv) const +Value Value::exec(int argc, Value argv[]) const { // array of zvals to execute - zval* params = static_cast(alloca(argc * sizeof(zval))); - - // convert all the values - for(int i = 0; i < argc; i++) { params[i] = *argv[i]._val; } - + ExecArguments args(argc, argv); + // call helper function - return do_exec(nullptr, _val, argc, params); + return do_exec(nullptr, _val, args); } /** @@ -944,13 +958,10 @@ Value Value::exec(const char *name, int argc, Value *argv) const Value method(name); // array of zvals to execute - zval* params = static_cast(alloca(argc * sizeof(zval))); - - // convert all the values - for(int i = 0; i < argc; i++) { params[i] = *argv[i]._val; } + ExecArguments args(argc, argv); // call helper function - return do_exec(_val, method._val, argc, params); + return do_exec(_val, method._val, args); } /** @@ -966,13 +977,10 @@ Value Value::exec(const char *name, int argc, Value *argv) Value method(name); // array of zvals to execute - zval* params = static_cast(alloca(argc * sizeof(zval))); - - // convert all the values - for(int i = 0; i < argc; i++) { params[i] = *argv[i]._val; } + ExecArguments args(argc, argv); // call helper function - return do_exec(_val, method._val, argc, params); + return do_exec(_val, method._val, args); } /**