diff --git a/Makefile b/Makefile index 2b9b4809..b788325b 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ INSTALL_LIB = ${INSTALL_PREFIX}/lib # SONAME = 2.4 -VERSION = 2.4.7 +VERSION = 2.4.8 # diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index 0541ec24..1d93cb51 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -10,275 +10,285 @@ #include #include -/** - * Set up namespace - */ -namespace Php { +#if PHP_VERSION_ID < 80400 +#define DOC_COMMENT info.user.doc_comment +#else +#define DOC_COMMENT doc_comment +#endif /** - * Destructor + * Set up namespace */ -ClassImpl::~ClassImpl() +namespace Php { - // destruct the entries - delete[] _entries; - // free the stored pointer - if (_self) zend_string_release(_self); -} + /** + * Destructor + */ + ClassImpl::~ClassImpl() + { + // destruct the entries + delete[] _entries; -/** - * @todo refactor so that methods become simpler - */ + // free the stored pointer + if (_self) + zend_string_release(_self); + } -/** - * Retrieve our C++ implementation object - * @param entry - * @return ClassImpl - */ -static ClassImpl *self(zend_class_entry *entry) -{ /** - * somebody could have extended this class from PHP userland, in which - * case trying to dereference the doc_comment would result in a disaster - * because it does not point to a class implemented by PHP-CPP at all! - * - * if this happens we need to keep going until we find the object that - * was implemented by us. For this we are going to make the assumption - * that we are the only ones misusing the doc_comment the way we do. - * - * Usually the doc_comment is not set (it equals the nullptr) and if it - * is set, the accompanying doc_comment_len should be non-zero to - * indicate the number of characters in it. - * - * When, however, we use the doc_comment from inside PHP-CPP to store - * the classimpl, we store a null-character (to immediately terminate - * the string, in case PHP tries to read it) and after that the pointer - * and we leave the doc_comment_len at 0. + * @todo refactor so that methods become simpler + */ + + /** + * Retrieve our C++ implementation object + * @param entry + * @return ClassImpl */ - while (entry->parent && (entry->info.user.doc_comment == nullptr || ZSTR_LEN(entry->info.user.doc_comment) > 0)) + static ClassImpl *self(zend_class_entry *entry) { - // we did not create this class entry, but luckily we have a parent - entry = entry->parent; - } + /** + * somebody could have extended this class from PHP userland, in which + * case trying to dereference the doc_comment would result in a disaster + * because it does not point to a class implemented by PHP-CPP at all! + * + * if this happens we need to keep going until we find the object that + * was implemented by us. For this we are going to make the assumption + * that we are the only ones misusing the doc_comment the way we do. + * + * Usually the doc_comment is not set (it equals the nullptr) and if it + * is set, the accompanying doc_comment_len should be non-zero to + * indicate the number of characters in it. + * + * When, however, we use the doc_comment from inside PHP-CPP to store + * the classimpl, we store a null-character (to immediately terminate + * the string, in case PHP tries to read it) and after that the pointer + * and we leave the doc_comment_len at 0. + */ + while (entry->parent && (entry->DOC_COMMENT == nullptr || ZSTR_LEN(entry->DOC_COMMENT) > 0)) + { + // we did not create this class entry, but luckily we have a parent + entry = entry->parent; + } - // retrieve the comment (it has a pointer hidden in it to the ClassBase object) - const char *comment = ZSTR_VAL(entry->info.user.doc_comment); + // retrieve the comment (it has a pointer hidden in it to the ClassBase object) + const char *comment = ZSTR_VAL(entry->DOC_COMMENT); - // the first byte of the comment is an empty string (null character), but - // the next bytes contain a pointer to the ClassBase class - return *((ClassImpl **)(comment + 1)); -} + // the first byte of the comment is an empty string (null character), but + // the next bytes contain a pointer to the ClassBase class + return *((ClassImpl **)(comment + 1)); + } -/** - * Extended zend_internal_function structure that we use to store an - * instance of the ClassBase object. We need this for static method calls - */ -struct CallData -{ - // the internal function is the first member, so - // that it is possible to cast an instance of this - // struct to a zend_internal_function - zend_internal_function func; + /** + * Extended zend_internal_function structure that we use to store an + * instance of the ClassBase object. We need this for static method calls + */ + struct CallData + { + // the internal function is the first member, so + // that it is possible to cast an instance of this + // struct to a zend_internal_function + zend_internal_function func; - // and a pointer to the ClassImpl object - ClassImpl *self; -}; + // and a pointer to the ClassImpl object + ClassImpl *self; + }; -/** - * Handler function that runs the __call function - * @param ... All normal parameters for function calls - */ -void ClassImpl::callMethod(INTERNAL_FUNCTION_PARAMETERS) -{ - // retrieve the originally called (and by us allocated) function object - auto *data = (CallData *)execute_data->func; - auto *func = &data->func; + /** + * Handler function that runs the __call function + * @param ... All normal parameters for function calls + */ + void ClassImpl::callMethod(INTERNAL_FUNCTION_PARAMETERS) + { + // retrieve the originally called (and by us allocated) function object + auto *data = (CallData *)execute_data->func; + auto *func = &data->func; - // retrieve the function name - const char *name = ZSTR_VAL(func->function_name); - ClassBase *meta = data->self->_base; + // retrieve the function name + const char *name = ZSTR_VAL(func->function_name); + ClassBase *meta = data->self->_base; - // the data structure was allocated by ourselves in the getMethod or - // getStaticMethod functions, we no longer need it when the function falls - // out of scope - DelayedFree df(data); + // the data structure was allocated by ourselves in the getMethod or + // getStaticMethod functions, we no longer need it when the function falls + // out of scope + DelayedFree df(data); - // the function could throw an exception - try - { - // construct parameters - ParametersImpl params(getThis(), ZEND_NUM_ARGS()); + // the function could throw an exception + try + { + // construct parameters + ParametersImpl params(getThis(), ZEND_NUM_ARGS()); - // retrieve the base object - Base *base = params.object(); + // retrieve the base object + Base *base = params.object(); - // is this a static, or a non-static call? - Php::Value result = base ? meta->callCall(base, name, params) : meta->callCallStatic(name, params); + // is this a static, or a non-static call? + Php::Value result = base ? meta->callCall(base, name, params) : meta->callCallStatic(name, params); - // return a full copy of the zval, and do not destruct it - RETVAL_ZVAL(result._val, 1, 0); - } - catch (const NotImplemented &exception) - { - // because of the two-step nature, we are going to report the error ourselves - zend_error(E_ERROR, "Undefined method %s", name); - } - catch (Throwable &throwable) - { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); + // return a full copy of the zval, and do not destruct it + RETVAL_ZVAL(result._val, 1, 0); + } + catch (const NotImplemented &exception) + { + // because of the two-step nature, we are going to report the error ourselves + zend_error(E_ERROR, "Undefined method %s", name); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); + } } -} -/** - * Handler function that runs the __invoke function - * @param ... All normal parameters for function calls - */ -void ClassImpl::callInvoke(INTERNAL_FUNCTION_PARAMETERS) -{ - // retrieve the originally called (and by us allocated) function object - auto *data = (CallData *)execute_data->func; + /** + * Handler function that runs the __invoke function + * @param ... All normal parameters for function calls + */ + void ClassImpl::callInvoke(INTERNAL_FUNCTION_PARAMETERS) + { + // retrieve the originally called (and by us allocated) function object + auto *data = (CallData *)execute_data->func; - // get self reference - ClassBase *meta = data->self->_base; + // get self reference + ClassBase *meta = data->self->_base; - // the data structure was allocated by ourselves in the getMethod or - // getStaticMethod functions, we no longer need it when the function falls - // out of scope - DelayedFree df(data); + // the data structure was allocated by ourselves in the getMethod or + // getStaticMethod functions, we no longer need it when the function falls + // out of scope + DelayedFree df(data); - // the function could throw an exception - try - { - // construct parameters - ParametersImpl params(getThis(), ZEND_NUM_ARGS()); + // the function could throw an exception + try + { + // construct parameters + ParametersImpl params(getThis(), ZEND_NUM_ARGS()); - // retrieve the base object - Base *base = params.object(); + // retrieve the base object + Base *base = params.object(); - // call the actual __invoke method on the base object - auto result = meta->callInvoke(base, params); + // call the actual __invoke method on the base object + auto result = meta->callInvoke(base, params); - // return a full copy of the zval, and do not destruct it - RETVAL_ZVAL(result._val, 1, 0); + // return a full copy of the zval, and do not destruct it + RETVAL_ZVAL(result._val, 1, 0); + } + catch (const NotImplemented &exception) + { + // because of the two-step nature, we are going to report the error ourselves + zend_error(E_ERROR, "Function name must be a string"); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); + } } - catch (const NotImplemented &exception) + + /** + * Method that returns the function definition of the __call function + * + * @param object Pointer to the object from which we want to retrieve the member function + * @param method The method that we want information about + * @param key ??? + * @return zend_function + */ + zend_function *ClassImpl::getMethod(zend_object **object, zend_string *method, const zval *key) { - // because of the two-step nature, we are going to report the error ourselves - zend_error(E_ERROR, "Function name must be a string"); + // something strange about the Zend engine (once more). The structure with + // object-handlers has a get_method and call_method member. When a function is + // called, the get_method function is called first, to retrieve information + // about the method (like the handler that should be called to execute it), + // after that, this returned handler is also called. The call_method property + // of the object_handlers structure however, never gets called. Typical. + + // first we'll check if the default handler does not have an implementation, + // in that case the method is probably already implemented as a regular method + auto *defaultFunction = std_object_handlers.get_method(object, method, key); + + // did the default implementation do anything? + if (defaultFunction) + return defaultFunction; + + // retrieve the class entry linked to this object + auto *entry = (*object)->ce; + + // this is peculiar behavior of the zend engine, we first are going to dynamically + // allocate memory holding all the properties of the __call method (we initially + // 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()) (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; + + // set all properties + function->type = ZEND_INTERNAL_FUNCTION; + function->arg_flags[0] = 0; + function->arg_flags[1] = 0; + function->arg_flags[2] = 0; + function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; + function->function_name = method; + function->scope = entry; + function->prototype = nullptr; + function->num_args = 0; + function->required_num_args = 0; + function->arg_info = nullptr; + function->handler = &ClassImpl::callMethod; + + // store pointer to ourselves + data->self = self(entry); + + // done (cast to zend_function* is allowed, because a zend_function is a union + // that has one member being a zend_internal_function) + return (zend_function *)data; } - catch (Throwable &throwable) + + /** + * Method that is called right before a static method call is attempted + * + * @param entry The class entry to find the static function in + * @param method The method to get information about + * @param key ??? + * @return zend_function + */ + zend_function *ClassImpl::getStaticMethod(zend_class_entry *entry, zend_string *method) { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); + // first we'll check if the default handler does not have an implementation, + // in that case the method is probably already implemented as a regular method + auto *defaultFunction = zend_std_get_static_method(entry, method, nullptr); + + // did the default implementation do anything? + if (defaultFunction) + return defaultFunction; + + // just like we did in getMethod() (see comment there) we are going to dynamically + // allocate data holding information about the function + auto *data = (CallData *)emalloc(sizeof(CallData)); + auto *function = &data->func; + + // set all properties for the function + function->type = ZEND_INTERNAL_FUNCTION; + function->arg_flags[0] = 0; + function->arg_flags[1] = 0; + function->arg_flags[2] = 0; + function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; + function->function_name = nullptr; + function->scope = nullptr; + function->prototype = nullptr; + function->num_args = 0; + function->required_num_args = 0; + function->arg_info = nullptr; + function->handler = &ClassImpl::callMethod; + + // store pointer to ourselves + data->self = self(entry); + + // done (cast to zend_function* is allowed, because a zend_function is a union + // that has one member being a zend_internal_function) + return (zend_function *)data; } -} - -/** - * Method that returns the function definition of the __call function - * - * @param object Pointer to the object from which we want to retrieve the member function - * @param method The method that we want information about - * @param key ??? - * @return zend_function - */ -zend_function *ClassImpl::getMethod(zend_object **object, zend_string *method, const zval *key) -{ - // something strange about the Zend engine (once more). The structure with - // object-handlers has a get_method and call_method member. When a function is - // called, the get_method function is called first, to retrieve information - // about the method (like the handler that should be called to execute it), - // after that, this returned handler is also called. The call_method property - // of the object_handlers structure however, never gets called. Typical. - - // first we'll check if the default handler does not have an implementation, - // in that case the method is probably already implemented as a regular method - auto *defaultFunction = std_object_handlers.get_method(object, method, key); - - // did the default implementation do anything? - if (defaultFunction) return defaultFunction; - - // retrieve the class entry linked to this object - auto *entry = (*object)->ce; - - // this is peculiar behavior of the zend engine, we first are going to dynamically - // allocate memory holding all the properties of the __call method (we initially - // 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()) (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; - - // set all properties - function->type = ZEND_INTERNAL_FUNCTION; - function->arg_flags[0] = 0; - function->arg_flags[1] = 0; - function->arg_flags[2] = 0; - function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; - function->function_name = method; - function->scope = entry; - function->prototype = nullptr; - function->num_args = 0; - function->required_num_args = 0; - function->arg_info = nullptr; - function->handler = &ClassImpl::callMethod; - - // store pointer to ourselves - data->self = self(entry); - - // done (cast to zend_function* is allowed, because a zend_function is a union - // that has one member being a zend_internal_function) - return (zend_function *)data; -} - -/** - * Method that is called right before a static method call is attempted - * - * @param entry The class entry to find the static function in - * @param method The method to get information about - * @param key ??? - * @return zend_function - */ -zend_function *ClassImpl::getStaticMethod(zend_class_entry *entry, zend_string *method) -{ - // first we'll check if the default handler does not have an implementation, - // in that case the method is probably already implemented as a regular method - auto *defaultFunction = zend_std_get_static_method(entry, method, nullptr); - - // did the default implementation do anything? - if (defaultFunction) return defaultFunction; - - // just like we did in getMethod() (see comment there) we are going to dynamically - // allocate data holding information about the function - auto *data = (CallData *)emalloc(sizeof(CallData)); - auto *function = &data->func; - - // set all properties for the function - function->type = ZEND_INTERNAL_FUNCTION; - function->arg_flags[0] = 0; - function->arg_flags[1] = 0; - function->arg_flags[2] = 0; - function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; - function->function_name = nullptr; - function->scope = nullptr; - function->prototype = nullptr; - function->num_args = 0; - function->required_num_args = 0; - function->arg_info = nullptr; - function->handler = &ClassImpl::callMethod; - - // store pointer to ourselves - data->self = self(entry); - - // done (cast to zend_function* is allowed, because a zend_function is a union - // that has one member being a zend_internal_function) - return (zend_function *)data; -} /** * Method that returns the closure -- this is the __invoke handler! @@ -289,190 +299,197 @@ zend_function *ClassImpl::getStaticMethod(zend_class_entry *entry, zend_string * * @return int */ #if PHP_VERSION_ID < 80000 -int ClassImpl::getClosure(ZEND_OBJECT_OR_ZVAL object, zend_class_entry **entry_ptr, zend_function **func, zend_object **object_ptr) + int ClassImpl::getClosure(ZEND_OBJECT_OR_ZVAL object, zend_class_entry **entry_ptr, zend_function **func, zend_object **object_ptr) #elif PHP_VERSION_ID < 80200 -int ClassImpl::getClosure(ZEND_OBJECT_OR_ZVAL object, zend_class_entry **entry_ptr, zend_function **func, zend_object **object_ptr, zend_bool check_only) + int ClassImpl::getClosure(ZEND_OBJECT_OR_ZVAL object, zend_class_entry **entry_ptr, zend_function **func, zend_object **object_ptr, zend_bool check_only) #else -zend_result ClassImpl::getClosure(ZEND_OBJECT_OR_ZVAL object, zend_class_entry **entry_ptr, zend_function **func, zend_object **object_ptr, zend_bool check_only) + zend_result ClassImpl::getClosure(ZEND_OBJECT_OR_ZVAL object, zend_class_entry **entry_ptr, zend_function **func, zend_object **object_ptr, zend_bool check_only) #endif -{ - // it is really unbelievable how the Zend engine manages to implement every feature - // in a complete different manner. You would expect the __invoke() and the - // __call() functions not to be very different from each other. However, they - // both have a completely different API. This getClosure method is supposed - // to fill the function parameter with all information about the invoke() - // method that is going to get called - - // just like we did for getMethod(), we're going to dynamically allocate memory - // with all information about the function - auto *data = (CallData *)emalloc(sizeof(CallData)); - auto *function = &data->func; - - // we're going to set all properties of the zend_internal_function struct - function->type = ZEND_INTERNAL_FUNCTION; - function->arg_flags[0] = 0; - function->arg_flags[1] = 0; - function->arg_flags[2] = 0; - function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; + { + // it is really unbelievable how the Zend engine manages to implement every feature + // in a complete different manner. You would expect the __invoke() and the + // __call() functions not to be very different from each other. However, they + // both have a completely different API. This getClosure method is supposed + // to fill the function parameter with all information about the invoke() + // method that is going to get called + + // just like we did for getMethod(), we're going to dynamically allocate memory + // with all information about the function + auto *data = (CallData *)emalloc(sizeof(CallData)); + auto *function = &data->func; + + // we're going to set all properties of the zend_internal_function struct + function->type = ZEND_INTERNAL_FUNCTION; + function->arg_flags[0] = 0; + function->arg_flags[1] = 0; + function->arg_flags[2] = 0; + function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; #if PHP_VERSION_ID < 70200 - zend_string *zend_empty_string = zend_string_alloc(sizeof("")-1, 1); - ZSTR_VAL(zend_empty_string)[0] = '\0'; - ZSTR_LEN(zend_empty_string) = 0; + zend_string *zend_empty_string = zend_string_alloc(sizeof("") - 1, 1); + ZSTR_VAL(zend_empty_string) + [0] = '\0'; + ZSTR_LEN(zend_empty_string) = 0; #endif - function->function_name = zend_empty_string; // should not be null, as this is free'ed by zend when doing exception handling - function->scope = *entry_ptr; - function->prototype = nullptr; - function->num_args = 0; - function->required_num_args = 0; - function->arg_info = nullptr; - function->handler = &ClassImpl::callInvoke; - - // store pointer to ourselves (note that the entry_ptr is useless - // inside this function as it is always uninitialized for some reason) + function->function_name = zend_empty_string; // should not be null, as this is free'ed by zend when doing exception handling + function->scope = *entry_ptr; + function->prototype = nullptr; + function->num_args = 0; + function->required_num_args = 0; + function->arg_info = nullptr; + function->handler = &ClassImpl::callInvoke; + + // store pointer to ourselves (note that the entry_ptr is useless + // inside this function as it is always uninitialized for some reason) #if PHP_VERSION_ID < 80000 - data->self = self(Z_OBJCE_P(object)); + data->self = self(Z_OBJCE_P(object)); #else - data->self = self(object->ce); + data->self = self(object->ce); #endif - // assign this dynamically allocated variable to the func parameter - // the cast is ok, because zend_internal_function is a member of the - // zend_function union - *func = (zend_function *)data; + // assign this dynamically allocated variable to the func parameter + // the cast is ok, because zend_internal_function is a member of the + // zend_function union + *func = (zend_function *)data; - // the object_ptr should be filled with the object on which the method is - // called (otherwise the Zend engine tries to call the method statically) + // the object_ptr should be filled with the object on which the method is + // called (otherwise the Zend engine tries to call the method statically) #if PHP_VERSION_ID < 80000 - *object_ptr = Z_OBJ_P(object); + *object_ptr = Z_OBJ_P(object); #else - *object_ptr = object; + *object_ptr = object; #endif - // done - return SUCCESS; -}; + // done + return SUCCESS; + }; -/** - * Retrieve pointer to our own object handlers - * @return zend_object_handlers - */ -zend_object_handlers *ClassImpl::objectHandlers() -{ - // already initialized? - if (_initialized) return &_handlers; + /** + * Retrieve pointer to our own object handlers + * @return zend_object_handlers + */ + zend_object_handlers *ClassImpl::objectHandlers() + { + // already initialized? + if (_initialized) + return &_handlers; - // initialize the handlers - memcpy(&_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + // initialize the handlers + memcpy(&_handlers, &std_object_handlers, sizeof(zend_object_handlers)); - // install custom clone function - if (!_base->clonable()) _handlers.clone_obj = nullptr; - else _handlers.clone_obj = &ClassImpl::cloneObject; + // install custom clone function + if (!_base->clonable()) + _handlers.clone_obj = nullptr; + else + _handlers.clone_obj = &ClassImpl::cloneObject; - // functions for the Countable interface - _handlers.count_elements = &ClassImpl::countElements; + // functions for the Countable interface + _handlers.count_elements = &ClassImpl::countElements; - // functions for the ArrayAccess interface - _handlers.write_dimension = &ClassImpl::writeDimension; - _handlers.read_dimension = &ClassImpl::readDimension; - _handlers.has_dimension = &ClassImpl::hasDimension; - _handlers.unset_dimension = &ClassImpl::unsetDimension; + // functions for the ArrayAccess interface + _handlers.write_dimension = &ClassImpl::writeDimension; + _handlers.read_dimension = &ClassImpl::readDimension; + _handlers.has_dimension = &ClassImpl::hasDimension; + _handlers.unset_dimension = &ClassImpl::unsetDimension; - // functions for the magic properties handlers (__get, __set, __isset and __unset) - _handlers.write_property = &ClassImpl::writeProperty; - _handlers.read_property = &ClassImpl::readProperty; - _handlers.has_property = &ClassImpl::hasProperty; - _handlers.unset_property = &ClassImpl::unsetProperty; + // functions for the magic properties handlers (__get, __set, __isset and __unset) + _handlers.write_property = &ClassImpl::writeProperty; + _handlers.read_property = &ClassImpl::readProperty; + _handlers.has_property = &ClassImpl::hasProperty; + _handlers.unset_property = &ClassImpl::unsetProperty; - // when a method is called (__call and __invoke) - _handlers.get_method = &ClassImpl::getMethod; - _handlers.get_closure = &ClassImpl::getClosure; + // when a method is called (__call and __invoke) + _handlers.get_method = &ClassImpl::getMethod; + _handlers.get_closure = &ClassImpl::getClosure; - // register destructor and deallocator - _handlers.dtor_obj = &ClassImpl::destructObject; - _handlers.free_obj = &ClassImpl::freeObject; + // register destructor and deallocator + _handlers.dtor_obj = &ClassImpl::destructObject; + _handlers.free_obj = &ClassImpl::freeObject; - // handler to cast to a different type - _handlers.cast_object = &ClassImpl::cast; + // handler to cast to a different type + _handlers.cast_object = &ClassImpl::cast; - // method to compare two objects + // method to compare two objects #if PHP_VERSION_ID < 80000 - _handlers.compare_objects = &ClassImpl::compare; + _handlers.compare_objects = &ClassImpl::compare; #else - _handlers.compare = &ClassImpl::compare; + _handlers.compare = &ClassImpl::compare; #endif - // set the offset between our class implementation and - // the zend_object member in the allocated structure - _handlers.offset = ObjectImpl::offset(); + // set the offset between our class implementation and + // the zend_object member in the allocated structure + _handlers.offset = ObjectImpl::offset(); - // remember that object is now initialized - _initialized = true; + // remember that object is now initialized + _initialized = true; - // done - return &_handlers; -} + // done + return &_handlers; + } -/** - * Alternative way to retrieve object handlers, given a class entry - * @param entry - * @return zend_object_handlers - */ -zend_object_handlers *ClassImpl::objectHandlers(zend_class_entry *entry) -{ - return self(entry)->objectHandlers(); -} + /** + * Alternative way to retrieve object handlers, given a class entry + * @param entry + * @return zend_object_handlers + */ + zend_object_handlers *ClassImpl::objectHandlers(zend_class_entry *entry) + { + return self(entry)->objectHandlers(); + } -/** - * Function to compare two objects - * @param val1 - * @param val2 - * @return int - */ -int ClassImpl::compare(zval *val1, zval *val2) -{ - // prevent exceptions - try + /** + * Function to compare two objects + * @param val1 + * @param val2 + * @return int + */ + int ClassImpl::compare(zval *val1, zval *val2) { - // retrieve the class entry linked to this object - auto *entry = Z_OBJCE_P(val1); + // prevent exceptions + try + { + // retrieve the class entry linked to this object + auto *entry = Z_OBJCE_P(val1); - // other object must be of the same type - if (entry != Z_OBJCE_P(val2)) throw NotImplemented(); + // other object must be of the same type + if (entry != Z_OBJCE_P(val2)) + throw NotImplemented(); - // we need the C++ class meta-information object - ClassBase *meta = self(entry)->_base; + // we need the C++ class meta-information object + ClassBase *meta = self(entry)->_base; - // get the base objects - Base *object1 = ObjectImpl::find(val1)->object(); - Base *object2 = ObjectImpl::find(val2)->object(); + // get the base objects + Base *object1 = ObjectImpl::find(val1)->object(); + Base *object2 = ObjectImpl::find(val2)->object(); - // run the compare method - return meta->callCompare(object1, object2); - } - catch (const NotImplemented &exception) - { - // it was not implemented, do we have a default? + // run the compare method + return meta->callCompare(object1, object2); + } + catch (const NotImplemented &exception) + { + // it was not implemented, do we have a default? #if PHP_VERSION_ID < 80000 - if (!std_object_handlers.compare_objects) return 1; + if (!std_object_handlers.compare_objects) + return 1; - // call default - return std_object_handlers.compare_objects(val1, val2); + // call default + return std_object_handlers.compare_objects(val1, val2); #else - if (!std_object_handlers.compare) return 1; + if (!std_object_handlers.compare) + return 1; - // call default - return std_object_handlers.compare(val1, val2); + // call default + return std_object_handlers.compare(val1, val2); #endif - } - catch (Throwable &throwable) - { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); - // what shall we return here... - return 1; + // what shall we return here... + return 1; + } } -} /** * Function to cast the object to a different type @@ -482,110 +499,124 @@ int ClassImpl::compare(zval *val1, zval *val2) * @return int */ #if PHP_VERSION_ID < 80200 -int ClassImpl::cast(ZEND_OBJECT_OR_ZVAL val, zval *retval, int type) + int ClassImpl::cast(ZEND_OBJECT_OR_ZVAL val, zval *retval, int type) #else -zend_result ClassImpl::cast(ZEND_OBJECT_OR_ZVAL val, zval *retval, int type) + zend_result ClassImpl::cast(ZEND_OBJECT_OR_ZVAL val, zval *retval, int type) #endif -{ - // get the base c++ object - Base *object = ObjectImpl::find(val)->object(); + { + // get the base c++ object + Base *object = ObjectImpl::find(val)->object(); - // retrieve the class entry linked to this object + // retrieve the class entry linked to this object #if PHP_VERSION_ID < 80000 - auto *entry = Z_OBJCE_P(val); + auto *entry = Z_OBJCE_P(val); #else - auto *entry = val->ce; + auto *entry = val->ce; #endif - // we need the C++ class meta-information object - ClassBase *meta = self(entry)->_base; + // we need the C++ class meta-information object + ClassBase *meta = self(entry)->_base; - // when the magic function it not implemented, an exception will be thrown, - // and the extension may throw a Php::Exception - try - { - // the result value - Value result; - - // check type - switch ((Type)type) { - case Type::Numeric: result = meta->callToInteger(object); break; - case Type::Float: result = meta->callToFloat(object); break; - case Type::Bool: result = meta->callToBool(object); break; - case Type::String: result = meta->callToString(object); break; - default: throw NotImplemented(); break; - } + // when the magic function it not implemented, an exception will be thrown, + // and the extension may throw a Php::Exception + try + { + // the result value + Value result; + + // check type + switch ((Type)type) + { + case Type::Numeric: + result = meta->callToInteger(object); + break; + case Type::Float: + result = meta->callToFloat(object); + break; + case Type::Bool: + result = meta->callToBool(object); + break; + case Type::String: + result = meta->callToString(object); + break; + default: + throw NotImplemented(); + break; + } - // @todo do we turn into endless conversion if the __toString object returns 'this' ?? - // (and if it does: who cares? If the extension programmer is stupid, why do we have to suffer?) + // @todo do we turn into endless conversion if the __toString object returns 'this' ?? + // (and if it does: who cares? If the extension programmer is stupid, why do we have to suffer?) - // overwrite the result - ZVAL_DUP(retval, result._val); + // overwrite the result + ZVAL_DUP(retval, result._val); - // done - return SUCCESS; - } - catch (const NotImplemented &exception) - { - // is there a default? - if (!std_object_handlers.cast_object) return FAILURE; + // done + return SUCCESS; + } + catch (const NotImplemented &exception) + { + // is there a default? + if (!std_object_handlers.cast_object) + return FAILURE; - // call default - return std_object_handlers.cast_object(val, retval, type); - } - catch (Throwable &throwable) - { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); + // call default + return std_object_handlers.cast_object(val, retval, type); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); - // done - return FAILURE; + // done + return FAILURE; + } } -} -/** - * Function that is called to create space for a cloned object - * - * @param val The object to be cloned - * @return zend_object The object to be created - */ -zend_object *ClassImpl::cloneObject(ZEND_OBJECT_OR_ZVAL val) -{ - // retrieve the class entry linked to this object + /** + * Function that is called to create space for a cloned object + * + * @param val The object to be cloned + * @return zend_object The object to be created + */ + zend_object *ClassImpl::cloneObject(ZEND_OBJECT_OR_ZVAL val) + { + // retrieve the class entry linked to this object #if PHP_VERSION_ID < 80000 - auto *entry = Z_OBJCE_P(val); + auto *entry = Z_OBJCE_P(val); #else - auto *entry = val->ce; + auto *entry = val->ce; #endif - // we need the C++ class meta-information object - ClassImpl *impl = self(entry); - ClassBase *meta = impl->_base; + // we need the C++ class meta-information object + ClassImpl *impl = self(entry); + ClassBase *meta = impl->_base; - // retrieve the old object, which we are going to copy - ObjectImpl *old_object = ObjectImpl::find(val); + // retrieve the old object, which we are going to copy + ObjectImpl *old_object = ObjectImpl::find(val); - // create a new base c++ object - auto *cpp = meta->clone(old_object->object()); + // create a new base c++ object + auto *cpp = meta->clone(old_object->object()); - // report error on failure (this does not occur because the cloneObject() - // method is only installed as handler when we have seen that there is indeed - // a copy constructor). Because this function is directly called from the - // Zend engine, we can call zend_error() (which does a longjmp()) to throw - // an exception back to the Zend engine) - if (!cpp) zend_error(E_ERROR, "Unable to clone %s", entry->name->val); + // report error on failure (this does not occur because the cloneObject() + // method is only installed as handler when we have seen that there is indeed + // a copy constructor). Because this function is directly called from the + // Zend engine, we can call zend_error() (which does a longjmp()) to throw + // an exception back to the Zend engine) + if (!cpp) + zend_error(E_ERROR, "Unable to clone %s", entry->name->val); - // store the object - auto *new_object = new ObjectImpl(entry, cpp, impl->objectHandlers(), 1); + // store the object + auto *new_object = new ObjectImpl(entry, cpp, impl->objectHandlers(), 1); - // clone the members (this will also call the __clone() function if the user - // had registered that as a visible method) - zend_objects_clone_members(new_object->php(), old_object->php()); + // clone the members (this will also call the __clone() function if the user + // had registered that as a visible method) + zend_objects_clone_members(new_object->php(), old_object->php()); - // was a custom clone method installed? If not we call the magic c++ __clone method - if (!entry->clone) meta->callClone(cpp); + // was a custom clone method installed? If not we call the magic c++ __clone method + if (!entry->clone) + meta->callClone(cpp); - // done - return new_object->php(); -} + // done + return new_object->php(); + } /** * Function that is used to count the number of elements in the object @@ -598,411 +629,435 @@ zend_object *ClassImpl::cloneObject(ZEND_OBJECT_OR_ZVAL val) * @return int */ #if PHP_VERSION_ID < 80200 -int ClassImpl::countElements(ZEND_OBJECT_OR_ZVAL object, zend_long *count) + int ClassImpl::countElements(ZEND_OBJECT_OR_ZVAL object, zend_long *count) #else -zend_result ClassImpl::countElements(ZEND_OBJECT_OR_ZVAL object, zend_long *count) + zend_result ClassImpl::countElements(ZEND_OBJECT_OR_ZVAL object, zend_long *count) #endif -{ - // does it implement the countable interface? - Countable *countable = dynamic_cast(ObjectImpl::find(object)->object()); - - // if it does not implement the Countable interface, we rely on the default implementation - if (countable) { - // the user function may throw an exception that needs to be processed - try + // does it implement the countable interface? + Countable *countable = dynamic_cast(ObjectImpl::find(object)->object()); + + // if it does not implement the Countable interface, we rely on the default implementation + if (countable) { - // call the count function - *count = countable->count(); + // the user function may throw an exception that needs to be processed + try + { + // call the count function + *count = countable->count(); + + // done + return SUCCESS; + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); - // done - return SUCCESS; + // unreachable + return FAILURE; + } } - catch (Throwable &throwable) + else { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); + // Countable interface was not implemented, check if there is a default + if (!std_object_handlers.count_elements) + return FAILURE; - // unreachable - return FAILURE; + // call default + return std_object_handlers.count_elements(object, count); } } - else - { - // Countable interface was not implemented, check if there is a default - if (!std_object_handlers.count_elements) return FAILURE; - - // call default - return std_object_handlers.count_elements(object, count); - } -} -/** - * Function that is called when the object is used as an array in PHP - * - * This is the [] operator in PHP, and mapped to the offsetGet() method - * of the ArrayAccess interface - * - * @param object The object on which it is called - * @param offset The name of the property - * @param type The type of the variable??? - * @param rv Pointer to where to store the data - * @return zval - */ -zval *ClassImpl::readDimension(ZEND_OBJECT_OR_ZVAL object, zval *offset, int type, zval *rv) -{ - // what to do with the type? - // - // the type parameter tells us whether the dimension was read in READ - // mode, WRITE mode, READWRITE mode or UNSET mode. - // - // In 99 out of 100 situations, it is called in regular READ mode (value 0), - // only when it is called from a PHP script that has statements like - // $x =& $object["x"], $object["x"]["y"] = "something" or unset($object["x"]["y"]), - // the type parameter is set to a different value. - // - // But we must ask ourselves the question what we should be doing with such - // cases. Internally, the object most likely has a full native implementation, - // and the property that is returned is just a string or integer or some - // other value, that is temporary WRAPPED into a zval to make it accessible - // from PHP. If someone wants to get a reference to such an internal variable, - // that is in most cases simply impossible. - - - // does it implement the arrayaccess interface? - ArrayAccess *arrayaccess = dynamic_cast(ObjectImpl::find(object)->object()); - - // if it does not implement the ArrayAccess interface, we rely on the default implementation - if (arrayaccess) + /** + * Function that is called when the object is used as an array in PHP + * + * This is the [] operator in PHP, and mapped to the offsetGet() method + * of the ArrayAccess interface + * + * @param object The object on which it is called + * @param offset The name of the property + * @param type The type of the variable??? + * @param rv Pointer to where to store the data + * @return zval + */ + zval *ClassImpl::readDimension(ZEND_OBJECT_OR_ZVAL object, zval *offset, int type, zval *rv) { - // the C++ code may throw an exception - try + // what to do with the type? + // + // the type parameter tells us whether the dimension was read in READ + // mode, WRITE mode, READWRITE mode or UNSET mode. + // + // In 99 out of 100 situations, it is called in regular READ mode (value 0), + // only when it is called from a PHP script that has statements like + // $x =& $object["x"], $object["x"]["y"] = "something" or unset($object["x"]["y"]), + // the type parameter is set to a different value. + // + // But we must ask ourselves the question what we should be doing with such + // cases. Internally, the object most likely has a full native implementation, + // and the property that is returned is just a string or integer or some + // other value, that is temporary WRAPPED into a zval to make it accessible + // from PHP. If someone wants to get a reference to such an internal variable, + // that is in most cases simply impossible. + + // does it implement the arrayaccess interface? + ArrayAccess *arrayaccess = dynamic_cast(ObjectImpl::find(object)->object()); + + // if it does not implement the ArrayAccess interface, we rely on the default implementation + if (arrayaccess) { - // ArrayAccess is implemented, call function - return toZval(arrayaccess->offsetGet(offset), type, rv); + // the C++ code may throw an exception + try + { + // ArrayAccess is implemented, call function + return toZval(arrayaccess->offsetGet(offset), type, rv); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); + + // unreachable + return Value(nullptr).detach(false); + } } - catch (Throwable &throwable) + else { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); + // ArrayAccess not implemented, check if there is a default handler + if (!std_object_handlers.read_dimension) + return nullptr; - // unreachable - return Value(nullptr).detach(false); + // call default + return std_object_handlers.read_dimension(object, offset, type, rv); } } - else - { - // ArrayAccess not implemented, check if there is a default handler - if (!std_object_handlers.read_dimension) return nullptr; - - // call default - return std_object_handlers.read_dimension(object, offset, type, rv); - } -} - -/** - * Function that is called when the object is used as an array in PHP - * - * This is the [] operator in PHP, and mapped to the offsetSet() method - * of the ArrayAccess interface - * - * @param object The object on which it is called - * @param offset The name of the property - * @param value The new value - * @return zval - */ -void ClassImpl::writeDimension(ZEND_OBJECT_OR_ZVAL object, zval *offset, zval *value) -{ - // does it implement the arrayaccess interface? - ArrayAccess *arrayaccess = dynamic_cast(ObjectImpl::find(object)->object()); - // if it does not implement the ArrayAccess interface, we rely on the default implementation - if (arrayaccess) + /** + * Function that is called when the object is used as an array in PHP + * + * This is the [] operator in PHP, and mapped to the offsetSet() method + * of the ArrayAccess interface + * + * @param object The object on which it is called + * @param offset The name of the property + * @param value The new value + * @return zval + */ + void ClassImpl::writeDimension(ZEND_OBJECT_OR_ZVAL object, zval *offset, zval *value) { - // method may throw an exception - try + // does it implement the arrayaccess interface? + ArrayAccess *arrayaccess = dynamic_cast(ObjectImpl::find(object)->object()); + + // if it does not implement the ArrayAccess interface, we rely on the default implementation + if (arrayaccess) { - // set the value - arrayaccess->offsetSet(offset, value); + // method may throw an exception + try + { + // set the value + arrayaccess->offsetSet(offset, value); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); + } } - catch (Throwable &throwable) + else { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); + // ArrayAccess not interface, check if there is a default handler + if (!std_object_handlers.write_dimension) + return; + + // call the default + std_object_handlers.write_dimension(object, offset, value); } } - else + + /** + * Function that is called when the object is used as an array in PHP + * + * This is the [] operator in PHP, and mapped to the offsetExists() method + * of the ArrayAccess interface + * + * @param object The object on which it is called + * @param member The member to check + * @param check_empty Was this an isset() call, or an empty() call? + * @return bool + */ + int ClassImpl::hasDimension(ZEND_OBJECT_OR_ZVAL object, zval *member, int check_empty) { - // ArrayAccess not interface, check if there is a default handler - if (!std_object_handlers.write_dimension) return; + // does it implement the arrayaccess interface? + ArrayAccess *arrayaccess = dynamic_cast(ObjectImpl::find(object)->object()); - // call the default - std_object_handlers.write_dimension(object, offset, value); - } -} - -/** - * Function that is called when the object is used as an array in PHP - * - * This is the [] operator in PHP, and mapped to the offsetExists() method - * of the ArrayAccess interface - * - * @param object The object on which it is called - * @param member The member to check - * @param check_empty Was this an isset() call, or an empty() call? - * @return bool - */ -int ClassImpl::hasDimension(ZEND_OBJECT_OR_ZVAL object, zval *member, int check_empty) -{ - // does it implement the arrayaccess interface? - ArrayAccess *arrayaccess = dynamic_cast(ObjectImpl::find(object)->object()); - - // if it does not implement the ArrayAccess interface, we rely on the default implementation - if (arrayaccess) - { - // user implemented callbacks could throw an exception - try + // if it does not implement the ArrayAccess interface, we rely on the default implementation + if (arrayaccess) { - // check if the member exists - if (!arrayaccess->offsetExists(member)) return false; - - // we know for certain that the offset exists, but should we check - // more, like whether the value is empty or not? - if (!check_empty) return true; + // user implemented callbacks could throw an exception + try + { + // check if the member exists + if (!arrayaccess->offsetExists(member)) + return false; + + // we know for certain that the offset exists, but should we check + // more, like whether the value is empty or not? + if (!check_empty) + return true; + + // the user wants to know if the property is empty + return empty(arrayaccess->offsetGet(member)); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); - // the user wants to know if the property is empty - return empty(arrayaccess->offsetGet(member)); + // unreachable + return false; + } } - catch (Throwable &throwable) + else { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); + // ArrayAccess interface is not implemented, check if there is a default handler + if (!std_object_handlers.has_dimension) + return 0; - // unreachable - return false; + // call default + return std_object_handlers.has_dimension(object, member, check_empty); } } - else + + /** + * Function that is called when the object is used as an array in PHP + * + * This is the [] operator in PHP, and mapped to the offsetUnset() method + * of the ArrayAccess interface + * + * @param object The object on which it is called + * @param member The member to remove + */ + void ClassImpl::unsetDimension(ZEND_OBJECT_OR_ZVAL object, zval *member) { - // ArrayAccess interface is not implemented, check if there is a default handler - if (!std_object_handlers.has_dimension) return 0; + // does it implement the arrayaccess interface? + ArrayAccess *arrayaccess = dynamic_cast(ObjectImpl::find(object)->object()); - // call default - return std_object_handlers.has_dimension(object, member, check_empty); - } -} + // if it does not implement the ArrayAccess interface, we rely on the default implementation + if (arrayaccess) + { + // user implemented code could throw an exception + try + { + // remove the member + arrayaccess->offsetUnset(member); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); + } + } + else + { + // ArrayAccess is not implemented, is a default handler available? + if (!std_object_handlers.unset_dimension) + return; -/** - * Function that is called when the object is used as an array in PHP - * - * This is the [] operator in PHP, and mapped to the offsetUnset() method - * of the ArrayAccess interface - * - * @param object The object on which it is called - * @param member The member to remove - */ -void ClassImpl::unsetDimension(ZEND_OBJECT_OR_ZVAL object, zval *member) -{ - // does it implement the arrayaccess interface? - ArrayAccess *arrayaccess = dynamic_cast(ObjectImpl::find(object)->object()); + // call the default + std_object_handlers.unset_dimension(object, member); + } + } - // if it does not implement the ArrayAccess interface, we rely on the default implementation - if (arrayaccess) + /** + * Helper method to turn a property into a zval + * + * @param value The value to convert to a zval + * @param type The type of operation (read or write) + * @param rv Pointer to where to store the data + * @return The result (same as the ptr input) + */ + zval *ClassImpl::toZval(Value &&value, int type, zval *rv) { - // user implemented code could throw an exception - try + // the result zval that needs to be copied over + Zval result; + + /** + * Because we do not want the value object to destruct the zval when + * it falls out of scope, we detach the zval from it, if this is a regular + * read operation we can do this right away. + * + * For write operations we need to check the refcount. If the refcount is + * only 1 (meaning the value object has the only reference) we cannot return + * a reference because there _is_ nothing to reference (the value will destruct) + */ + if (type == 0 || value.refcount() <= 1) { - // remove the member - arrayaccess->offsetUnset(member); + // first retrieve the value so we can copy it + result = value.detach(true); } - catch (Throwable &throwable) + // this is an editable zval, return a reference to it + else { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); + // we're dealing with an editable zval, retrieve a reference variable + result = Value(value.detach(false), true).detach(true); } - } - else - { - // ArrayAccess is not implemented, is a default handler available? - if (!std_object_handlers.unset_dimension) return; - // call the default - std_object_handlers.unset_dimension(object, member); - } -} + // now copy the value over to the pointer + ZVAL_COPY_VALUE(rv, result); -/** - * Helper method to turn a property into a zval - * - * @param value The value to convert to a zval - * @param type The type of operation (read or write) - * @param rv Pointer to where to store the data - * @return The result (same as the ptr input) - */ -zval *ClassImpl::toZval(Value &&value, int type, zval *rv) -{ - // the result zval that needs to be copied over - Zval result; + // return the pointer to the value + return rv; + } /** - * Because we do not want the value object to destruct the zval when - * it falls out of scope, we detach the zval from it, if this is a regular - * read operation we can do this right away. + * Function that is called when a property is read * - * For write operations we need to check the refcount. If the refcount is - * only 1 (meaning the value object has the only reference) we cannot return - * a reference because there _is_ nothing to reference (the value will destruct) + * @param object The object on which it is called + * @param offset The name of the property + * @param type The type of the variable??? + * @param cache_slot The cache slot used + * @param rv Pointer to where to store the data + * @return val */ - if (type == 0 || value.refcount() <= 1) + zval *ClassImpl::readProperty(ZEND_OBJECT_OR_ZVAL object, ZEND_STRING_OR_ZVAL name, int type, void **cache_slot, zval *rv) { - // first retrieve the value so we can copy it - result = value.detach(true); - } - // this is an editable zval, return a reference to it - else - { - // we're dealing with an editable zval, retrieve a reference variable - result = Value(value.detach(false), true).detach(true); - } - - // now copy the value over to the pointer - ZVAL_COPY_VALUE(rv, result); - - // return the pointer to the value - return rv; -} + // what to do with the type? + // + // the type parameter tells us whether the property was read in READ + // mode, WRITE mode, READWRITE mode or UNSET mode. + // + // In 99 out of 100 situations, it is called in regular READ mode (value 0), + // only when it is called from a PHP script that has statements like + // $x =& $object->x, $object->x->y = "something" or unset($object->x->y) + // the type parameter is set to a different value. + // + // But we must ask ourselves the question what we should be doing with such + // cases. Internally, the object most likely has a full native implementation, + // and the property that is returned is just a string or integer or some + // other value, that is temporary WRAPPED into a zval to make it accessible + // from PHP. If someone wants to get a reference to such an internal variable, + // that is in most cases simply impossible. + + // retrieve the object and class + Base *base = ObjectImpl::find(object)->object(); -/** - * Function that is called when a property is read - * - * @param object The object on which it is called - * @param offset The name of the property - * @param type The type of the variable??? - * @param cache_slot The cache slot used - * @param rv Pointer to where to store the data - * @return val - */ -zval *ClassImpl::readProperty(ZEND_OBJECT_OR_ZVAL object, ZEND_STRING_OR_ZVAL name, int type, void **cache_slot, zval *rv) -{ - // what to do with the type? - // - // the type parameter tells us whether the property was read in READ - // mode, WRITE mode, READWRITE mode or UNSET mode. - // - // In 99 out of 100 situations, it is called in regular READ mode (value 0), - // only when it is called from a PHP script that has statements like - // $x =& $object->x, $object->x->y = "something" or unset($object->x->y) - // the type parameter is set to a different value. - // - // But we must ask ourselves the question what we should be doing with such - // cases. Internally, the object most likely has a full native implementation, - // and the property that is returned is just a string or integer or some - // other value, that is temporary WRAPPED into a zval to make it accessible - // from PHP. If someone wants to get a reference to such an internal variable, - // that is in most cases simply impossible. - - // retrieve the object and class - Base *base = ObjectImpl::find(object)->object(); - - // retrieve the class entry linked to this object + // retrieve the class entry linked to this object #if PHP_VERSION_ID < 80000 - auto *entry = Z_OBJCE_P(object); + auto *entry = Z_OBJCE_P(object); #else - auto *entry = object->ce; + auto *entry = object->ce; #endif - // we need the C++ class meta-information object - ClassImpl *impl = self(entry); - ClassBase *meta = impl->_base; + // we need the C++ class meta-information object + ClassImpl *impl = self(entry); + ClassBase *meta = impl->_base; - // the default implementation throws an exception, so by catching - // the exception we know if the object was implemented by the user or not - try - { - // convert name to a Value object - Value key(name); + // the default implementation throws an exception, so by catching + // the exception we know if the object was implemented by the user or not + try + { + // convert name to a Value object + Value key(name); - // is it a property with a callback? - auto iter = impl->_properties.find(key); + // is it a property with a callback? + auto iter = impl->_properties.find(key); - // was it found? - if (iter == impl->_properties.end()) + // was it found? + if (iter == impl->_properties.end()) + { + // retrieve value from the __get method + return toZval(meta->callGet(base, key), type, rv); + } + else + { + // get the value + return toZval(iter->second->get(base), type, rv); + } + } + catch (const NotImplemented &exception) { - // retrieve value from the __get method - return toZval(meta->callGet(base, key), type, rv); + // __get() function was not overridden by the user + if (!std_object_handlers.read_property) + return nullptr; + + // call default + return std_object_handlers.read_property(object, name, type, cache_slot, rv); } - else + catch (Throwable &throwable) { - // get the value - return toZval(iter->second->get(base), type, rv); + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); + + // unreachable (or is it?) + return Value(nullptr).detach(false); } } - catch (const NotImplemented &exception) - { - // __get() function was not overridden by the user - if (!std_object_handlers.read_property) return nullptr; - // call default - return std_object_handlers.read_property(object, name, type, cache_slot, rv); - } - catch (Throwable &throwable) + /** + * Function that is called when a property is set / updated + * + * This is the handler for the __set() function, and is called when a property + * is updated. + * + * @param object The object on which it is called + * @param name The name of the property + * @param value The new value + * @param cache_slot The cache slot used + * @return zval + */ + PHP_WRITE_PROP_HANDLER_TYPE ClassImpl::writeProperty(ZEND_OBJECT_OR_ZVAL object, ZEND_STRING_OR_ZVAL name, zval *value, void **cache_slot) { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); - - // unreachable (or is it?) - return Value(nullptr).detach(false); - } -} - -/** - * Function that is called when a property is set / updated - * - * This is the handler for the __set() function, and is called when a property - * is updated. - * - * @param object The object on which it is called - * @param name The name of the property - * @param value The new value - * @param cache_slot The cache slot used - * @return zval - */ -PHP_WRITE_PROP_HANDLER_TYPE ClassImpl::writeProperty(ZEND_OBJECT_OR_ZVAL object, ZEND_STRING_OR_ZVAL name, zval *value, void **cache_slot) -{ - // retrieve the object and class - Base *base = ObjectImpl::find(object)->object(); + // retrieve the object and class + Base *base = ObjectImpl::find(object)->object(); - // retrieve the class entry linked to this object + // retrieve the class entry linked to this object #if PHP_VERSION_ID < 80000 - auto *entry = Z_OBJCE_P(object); + auto *entry = Z_OBJCE_P(object); #else - auto *entry = object->ce; + auto *entry = object->ce; #endif - // we need the C++ class meta-information object - ClassImpl *impl = self(entry); - ClassBase *meta = impl->_base; + // we need the C++ class meta-information object + ClassImpl *impl = self(entry); + ClassBase *meta = impl->_base; - // the default implementation throws an exception, if we catch that - // we know for sure that the user has not overridden the __set method - try - { - // wrap the name - Value key(name); + // the default implementation throws an exception, if we catch that + // we know for sure that the user has not overridden the __set method + try + { + // wrap the name + Value key(name); - // check if the property has a callback - auto iter = impl->_properties.find(key); + // check if the property has a callback + auto iter = impl->_properties.find(key); - // is it set? - if (iter == impl->_properties.end()) - { - // use the __set method - meta->callSet(base, key, value); + // is it set? + if (iter == impl->_properties.end()) + { + // use the __set method + meta->callSet(base, key, value); + } + else + { + // check if it could be set + if (iter->second->set(base, value)) + { +#if PHP_VERSION_ID < 70400 + return; +#else + return value; +#endif + } + + // read-only property + zend_error(E_ERROR, "Unable to write to read-only property %s", (const char *)key); + } } - else + catch (const NotImplemented &exception) { - // check if it could be set - if (iter->second->set(base, value)) { + // __set() function was not overridden by user, check if there is a default + if (!std_object_handlers.write_property) + { #if PHP_VERSION_ID < 70400 return; #else @@ -1010,611 +1065,623 @@ PHP_WRITE_PROP_HANDLER_TYPE ClassImpl::writeProperty(ZEND_OBJECT_OR_ZVAL object, #endif } - // read-only property - zend_error(E_ERROR, "Unable to write to read-only property %s", (const char *)key); - } - } - catch (const NotImplemented &exception) - { - // __set() function was not overridden by user, check if there is a default - if (!std_object_handlers.write_property) { + // call the default + std_object_handlers.write_property(object, name, value, cache_slot); #if PHP_VERSION_ID < 70400 return; #else return value; #endif } - - // call the default - std_object_handlers.write_property(object, name, value, cache_slot); + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); + } #if PHP_VERSION_ID < 70400 return; #else return value; #endif } - catch (Throwable &throwable) - { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); - } -#if PHP_VERSION_ID < 70400 - return; -#else - return value; -#endif -} -/** - * Function that is called to check whether a certain property is set - * for an object - * - * This is the handler for the __isset() function, and is called when a PHP - * script checks if a certain property is set. - * - * The has_set_exists parameter can have the following values: - * - * 0 (has) whether property exists and is not NULL - * 1 (set) whether property exists and is true - * 2 (exists) whether property exists - * - * @param object The object on which it is called - * @param name The name of the property to check - * @param has_set_exists See above - * @param cache_slot The cache slot used - * @return bool - */ -int ClassImpl::hasProperty(ZEND_OBJECT_OR_ZVAL object, ZEND_STRING_OR_ZVAL name, int has_set_exists, void **cache_slot) -{ - // the default implementation throws an exception, if we catch that - // we know for sure that the user has not overridden the __isset method - try + /** + * Function that is called to check whether a certain property is set + * for an object + * + * This is the handler for the __isset() function, and is called when a PHP + * script checks if a certain property is set. + * + * The has_set_exists parameter can have the following values: + * + * 0 (has) whether property exists and is not NULL + * 1 (set) whether property exists and is true + * 2 (exists) whether property exists + * + * @param object The object on which it is called + * @param name The name of the property to check + * @param has_set_exists See above + * @param cache_slot The cache slot used + * @return bool + */ + int ClassImpl::hasProperty(ZEND_OBJECT_OR_ZVAL object, ZEND_STRING_OR_ZVAL name, int has_set_exists, void **cache_slot) { - // get the cpp object - Base *base = ObjectImpl::find(object)->object(); + // the default implementation throws an exception, if we catch that + // we know for sure that the user has not overridden the __isset method + try + { + // get the cpp object + Base *base = ObjectImpl::find(object)->object(); - // retrieve the class entry linked to this object + // retrieve the class entry linked to this object #if PHP_VERSION_ID < 80000 - auto *entry = Z_OBJCE_P(object); + auto *entry = Z_OBJCE_P(object); #else - auto *entry = object->ce; + auto *entry = object->ce; #endif - // we need the C++ class meta-information object - ClassImpl *impl = self(entry); - ClassBase *meta = impl->_base; + // we need the C++ class meta-information object + ClassImpl *impl = self(entry); + ClassBase *meta = impl->_base; - // convert the name to a Value object - Value key(name); + // convert the name to a Value object + Value key(name); - // check if this is a callback property - if (impl->_properties.find(key) != impl->_properties.end()) return true; + // check if this is a callback property + if (impl->_properties.find(key) != impl->_properties.end()) + return true; - // call the C++ object - if (!meta->callIsset(base, key)) return false; + // call the C++ object + if (!meta->callIsset(base, key)) + return false; - // property exists, but what does the user want to know - if (has_set_exists == 2) return true; + // property exists, but what does the user want to know + if (has_set_exists == 2) + return true; - // we have to retrieve the property - Value value = meta->callGet(base, key); + // we have to retrieve the property + Value value = meta->callGet(base, key); - // should we check on NULL? - switch (has_set_exists) { - case 0: return value.type() != Type::Null; - default: return value.boolValue(); + // should we check on NULL? + switch (has_set_exists) + { + case 0: + return value.type() != Type::Null; + default: + return value.boolValue(); + } } - } - catch (const NotImplemented &exception) - { - // __isset was not implemented, do we have a default? - if (!std_object_handlers.has_property) return 0; + catch (const NotImplemented &exception) + { + // __isset was not implemented, do we have a default? + if (!std_object_handlers.has_property) + return 0; - // call default - return std_object_handlers.has_property(object, name, has_set_exists, cache_slot); - } - catch (Throwable &throwable) - { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); + // call default + return std_object_handlers.has_property(object, name, has_set_exists, cache_slot); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); - // unreachable - return false; + // unreachable + return false; + } } -} -/** - * Function that is called when a property is removed from the project - * - * This is the handler for the __unset() method - * - * @param object The object on which it is called - * @param member The member to remove - * @param cache_slot The cache slot used - */ -void ClassImpl::unsetProperty(ZEND_OBJECT_OR_ZVAL object, ZEND_STRING_OR_ZVAL member, void **cache_slot) -{ - // the default implementation throws an exception, if we catch that - // we know for sure that the user has not overridden the __unset method - try + /** + * Function that is called when a property is removed from the project + * + * This is the handler for the __unset() method + * + * @param object The object on which it is called + * @param member The member to remove + * @param cache_slot The cache slot used + */ + void ClassImpl::unsetProperty(ZEND_OBJECT_OR_ZVAL object, ZEND_STRING_OR_ZVAL member, void **cache_slot) { - // retrieve the class entry linked to this object + // the default implementation throws an exception, if we catch that + // we know for sure that the user has not overridden the __unset method + try + { + // retrieve the class entry linked to this object #if PHP_VERSION_ID < 80000 - auto *entry = Z_OBJCE_P(object); + auto *entry = Z_OBJCE_P(object); #else - auto *entry = object->ce; + auto *entry = object->ce; #endif - // we need the C++ class meta-information object - ClassImpl *impl = self(entry); + // we need the C++ class meta-information object + ClassImpl *impl = self(entry); - // property name - Value name(member); + // property name + Value name(member); - // is this a callback property? - auto iter = impl->_properties.find(name); + // is this a callback property? + auto iter = impl->_properties.find(name); - // if the property does not exist, we forward to the __unset - if (iter == impl->_properties.end()) impl->_base->callUnset(ObjectImpl::find(object)->object(), member); + // if the property does not exist, we forward to the __unset + if (iter == impl->_properties.end()) + impl->_base->callUnset(ObjectImpl::find(object)->object(), member); - // callback properties cannot be unset - zend_error(E_ERROR, "Property %s can not be unset", (const char *)name); - } - catch (const NotImplemented &exception) - { - // __unset was not implemented, do we have a default? - if (!std_object_handlers.unset_property) return; + // callback properties cannot be unset + zend_error(E_ERROR, "Property %s can not be unset", (const char *)name); + } + catch (const NotImplemented &exception) + { + // __unset was not implemented, do we have a default? + if (!std_object_handlers.unset_property) + return; - // call the default - std_object_handlers.unset_property(object, member, cache_slot); - } - catch (Throwable &throwable) - { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); + // call the default + std_object_handlers.unset_property(object, member, cache_slot); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); + } } -} -/** - * Function that is called when an object is about to be destructed - * This will call the magic __destruct method - * @param object - */ -void ClassImpl::destructObject(zend_object *object) -{ - // find object - ObjectImpl *obj = ObjectImpl::find(object); + /** + * Function that is called when an object is about to be destructed + * This will call the magic __destruct method + * @param object + */ + void ClassImpl::destructObject(zend_object *object) + { + // find object + ObjectImpl *obj = ObjectImpl::find(object); - // get meta info - ClassImpl *impl = self(object->ce); + // get meta info + ClassImpl *impl = self(object->ce); - // prevent exceptions - try - { - // call the destruct function - if (obj->object()) impl->_base->callDestruct(obj->object()); - } - catch (const NotImplemented &exception) - { - // fallback on the default destructor call in case a derived object - // of Base throws this. The default implementation will call this - // function in any case. - zend_objects_destroy_object(object); + // prevent exceptions + try + { + // call the destruct function + if (obj->object()) + impl->_base->callDestruct(obj->object()); + } + catch (const NotImplemented &exception) + { + // fallback on the default destructor call in case a derived object + // of Base throws this. The default implementation will call this + // function in any case. + zend_objects_destroy_object(object); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); + } } - catch (Throwable &throwable) + + /** + * Function that is called to clean up space that is occupied by the object + * @param object The object to be deallocated + */ + void ClassImpl::freeObject(zend_object *object) { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); - } -} + // allocate memory for the object + ObjectImpl *obj = ObjectImpl::find(object); -/** - * Function that is called to clean up space that is occupied by the object - * @param object The object to be deallocated - */ -void ClassImpl::freeObject(zend_object *object) -{ - // allocate memory for the object - ObjectImpl *obj = ObjectImpl::find(object); + // no longer need it + obj->destruct(); + } - // no longer need it - obj->destruct(); -} + /** + * Function that is called when an instance of the class needs to be created. + * This function will create the C++ class, and the PHP object + * @param entry Pointer to the class information + * @return zend_object_value The newly created object + */ + zend_object *ClassImpl::createObject(zend_class_entry *entry) + { + // we need the C++ class meta-information object + ClassImpl *impl = self(entry); -/** - * Function that is called when an instance of the class needs to be created. - * This function will create the C++ class, and the PHP object - * @param entry Pointer to the class information - * @return zend_object_value The newly created object - */ -zend_object *ClassImpl::createObject(zend_class_entry *entry) -{ - // we need the C++ class meta-information object - ClassImpl *impl = self(entry); + // create a new base C++ object + auto *cpp = impl->_base->construct(); - // create a new base C++ object - auto *cpp = impl->_base->construct(); + // report error on failure, because this function is called directly from the + // Zend engine, we can call zend_error() here (which does a longjmp() back to + // the Zend engine) + if (!cpp) + zend_error(E_ERROR, "Unable to instantiate %s", entry->name->val); - // report error on failure, because this function is called directly from the - // Zend engine, we can call zend_error() here (which does a longjmp() back to - // the Zend engine) - if (!cpp) zend_error(E_ERROR, "Unable to instantiate %s", entry->name->val); + // create the object in the zend engine + auto *object = new ObjectImpl(entry, cpp, impl->objectHandlers(), 1); - // create the object in the zend engine - auto *object = new ObjectImpl(entry, cpp, impl->objectHandlers(), 1); + // return the php object stored in the implementation + return object->php(); + } - // return the php object stored in the implementation - return object->php(); -} + /** + * Function to create a new iterator to iterate over an object + * @param entry The class entry + * @param object The object to iterate over + * @param by_ref ????? + * @return zend_object_iterator* Pointer to the iterator + */ + zend_object_iterator *ClassImpl::getIterator(zend_class_entry *entry, zval *object, int by_ref) + { + // by-ref is not possible (copied from SPL), this function is called directly + // from the Zend engine, so we can use zend_error() to longjmp() back to the + // Zend engine) + if (by_ref) + zend_error(E_ERROR, "Foreach by ref is not possible"); -/** - * Function to create a new iterator to iterate over an object - * @param entry The class entry - * @param object The object to iterate over - * @param by_ref ????? - * @return zend_object_iterator* Pointer to the iterator - */ -zend_object_iterator *ClassImpl::getIterator(zend_class_entry *entry, zval *object, int by_ref) -{ - // by-ref is not possible (copied from SPL), this function is called directly - // from the Zend engine, so we can use zend_error() to longjmp() back to the - // Zend engine) - if (by_ref) zend_error(E_ERROR, "Foreach by ref is not possible"); + // retrieve the traversable object + Traversable *traversable = dynamic_cast(ObjectImpl::find(object)->object()); - // retrieve the traversable object - Traversable *traversable = dynamic_cast(ObjectImpl::find(object)->object()); + // use might throw an exception in the getIterator() function + try + { + // get userspace iterator + auto *userspace = traversable->getIterator(); - // use might throw an exception in the getIterator() function - try - { - // get userspace iterator - auto *userspace = traversable->getIterator(); + // we are going to allocate an extended iterator (because php nowadays destructs + // the iteraters itself, we can no longer let c++ allocate the buffer + object + // directly, so we first allocate the buffer, which is going to be cleaned up by php) + auto *buffer = emalloc(sizeof(IteratorImpl)); - // we are going to allocate an extended iterator (because php nowadays destructs - // the iteraters itself, we can no longer let c++ allocate the buffer + object - // directly, so we first allocate the buffer, which is going to be cleaned up by php) - auto *buffer = emalloc(sizeof(IteratorImpl)); + // and then we use placement-new to allocate the implementation + auto *wrapper = new (buffer) IteratorImpl(object, userspace); - // and then we use placement-new to allocate the implementation - auto *wrapper = new(buffer)IteratorImpl(object, userspace); + // done + return wrapper->implementation(); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); - // done - return wrapper->implementation(); + // unreachable + return nullptr; + } } - catch (Throwable &throwable) + + /** + * Method that is called to serialize an object + * @param object The object to be serialized + * @param buffer Buffer in which to store the data + * @param buf_len Size of the bufffer + * @param data ?? + * @return int + */ + int ClassImpl::serialize(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data) { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); + // get the serializable object + Serializable *serializable = dynamic_cast(ObjectImpl::find(object)->object()); - // unreachable - return nullptr; - } -} + // user may throw an exception in the serialize() function + try + { + // call the serialize method on the object + auto value = serializable->serialize(); + + // allocate the buffer, and copy the data into it (the zend engine will + // (hopefully) clean up the data for us - the default serialize method does + // it like this too) + *buffer = (unsigned char *)estrndup(value.c_str(), value.size()); + *buf_len = value.size(); + } + catch (Throwable &throwable) + { + // object was not caught by the extension, let it end up in user space + throwable.rethrow(); -/** - * Method that is called to serialize an object - * @param object The object to be serialized - * @param buffer Buffer in which to store the data - * @param buf_len Size of the bufffer - * @param data ?? - * @return int - */ -int ClassImpl::serialize(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data) -{ - // get the serializable object - Serializable *serializable = dynamic_cast(ObjectImpl::find(object)->object()); + // unreachable + return FAILURE; + } - // user may throw an exception in the serialize() function - try - { - // call the serialize method on the object - auto value = serializable->serialize(); - - // allocate the buffer, and copy the data into it (the zend engine will - // (hopefully) clean up the data for us - the default serialize method does - // it like this too) - *buffer = (unsigned char*)estrndup(value.c_str(), value.size()); - *buf_len = value.size(); + // done + return SUCCESS; } - catch (Throwable &throwable) - { - // object was not caught by the extension, let it end up in user space - throwable.rethrow(); - // unreachable - return FAILURE; - } + /** + * Method that is called to unserialize an object + * @param object The object to be unserialized + * @param entry The class entry to which is belongs + * @param buffer Buffer holding the unserialized data + * @param data All the unserialize data + * @return int + */ + int ClassImpl::unserialize(zval *object, zend_class_entry *entry, const unsigned char *buffer, size_t buf_len, zend_unserialize_data *data) + { + // create the PHP object + object_init_ex(object, entry); - // done - return SUCCESS; -} + // turn this into a serializale + Serializable *serializable = dynamic_cast(ObjectImpl::find(object)->object()); -/** - * Method that is called to unserialize an object - * @param object The object to be unserialized - * @param entry The class entry to which is belongs - * @param buffer Buffer holding the unserialized data - * @param data All the unserialize data - * @return int - */ -int ClassImpl::unserialize(zval *object, zend_class_entry *entry, const unsigned char *buffer, size_t buf_len, zend_unserialize_data *data) -{ - // create the PHP object - object_init_ex(object, entry); + // user may throw an exception in the serialize() function + try + { + // call the unserialize method on it + serializable->unserialize((const char *)buffer, buf_len); + } + catch (Throwable &throwable) + { + // user threw an exception in its method + // implementation, send it to user space + php_error_docref(NULL, E_NOTICE, "Error while unserializing"); + // throwable.rethrow(); - // turn this into a serializale - Serializable *serializable = dynamic_cast(ObjectImpl::find(object)->object()); + // unreachable + return FAILURE; + } - // user may throw an exception in the serialize() function - try - { - // call the unserialize method on it - serializable->unserialize((const char *)buffer, buf_len); + // done + return SUCCESS; } - catch (Throwable &throwable) - { - // user threw an exception in its method - // implementation, send it to user space - php_error_docref(NULL, E_NOTICE, "Error while unserializing"); - //throwable.rethrow(); - // unreachable - return FAILURE; + /** + * Helper method to check if a function is registered for this instance + * @param name name of the function to check for + * @return bool Wether the function exists or not + */ + bool ClassImpl::hasMethod(const char *name) const + { + // find the method + auto result = std::find_if(_methods.begin(), _methods.end(), [name](std::shared_ptr method) + { return method->name() == name; }); + // return wether its found or not + return result != _methods.end(); } - // done - return SUCCESS; -} + /** + * Retrieve an array of zend_function_entry objects that hold the + * properties for each method. This method is called at extension + * startup time to register all methods. + * + * @param classname The class name + * @return zend_function_entry[] + */ + const struct _zend_function_entry *ClassImpl::entries() + { + // already initialized? + if (_entries) + return _entries; -/** - * Helper method to check if a function is registered for this instance - * @param name name of the function to check for - * @return bool Wether the function exists or not - */ -bool ClassImpl::hasMethod(const char* name) const -{ - // find the method - auto result = std::find_if(_methods.begin(), _methods.end(), [name](std::shared_ptr method){ return method->name() == name; }); - // return wether its found or not - return result != _methods.end(); -} + // the number of entries that need to be allocated + size_t entrycount = _methods.size(); -/** - * Retrieve an array of zend_function_entry objects that hold the - * properties for each method. This method is called at extension - * startup time to register all methods. - * - * @param classname The class name - * @return zend_function_entry[] - */ -const struct _zend_function_entry *ClassImpl::entries() -{ - // already initialized? - if (_entries) return _entries; + // if the class is countable, we might need some extra methods + if (_base->countable() && !hasMethod("count")) + entrycount += 1; - // the number of entries that need to be allocated - size_t entrycount = _methods.size(); + // if the class is serializable, we might need some extra methods + if (_base->serializable()) + { + // add the serialize method if the class does not have one defined yet + if (!hasMethod("serialize")) + entrycount += 1; + if (!hasMethod("unserialize")) + entrycount += 1; + } - // if the class is countable, we might need some extra methods - if (_base->countable() && !hasMethod("count")) entrycount += 1; + // if the class is iterable, we might need some extra methods + if (_base->traversable()) + { + // add the getIterator method if the class does not have one defined yet + if (!hasMethod("getIterator")) + entrycount += 1; + } - // if the class is serializable, we might need some extra methods - if (_base->serializable()) - { - // add the serialize method if the class does not have one defined yet - if (!hasMethod("serialize")) entrycount += 1; - if (!hasMethod("unserialize")) entrycount += 1; - } - - // if the class is iterable, we might need some extra methods - if (_base->traversable()) - { - // add the getIterator method if the class does not have one defined yet - if (!hasMethod("getIterator")) entrycount += 1; - } + // allocate memory for the functions + _entries = new zend_function_entry[entrycount + 1]; - // allocate memory for the functions - _entries = new zend_function_entry[entrycount + 1]; + // keep iterator counter + int i = 0; - // keep iterator counter - int i = 0; + // loop through the functions + for (auto &method : _methods) + { + // retrieve entry + zend_function_entry *entry = &_entries[i++]; - // loop through the functions - for (auto &method : _methods) - { - // retrieve entry - zend_function_entry *entry = &_entries[i++]; + // let the function fill the entry + method->initialize(entry, _name); + } - // let the function fill the entry - method->initialize(entry, _name); - } + // if the class is countable, we might need to add some extra methods + if (_base->countable()) + { + // the method objectneed to stay in scope for the lifetime of the script (because the register a pointer + // to an internal string buffer) -- so we create them as static variables + static Method count("count", &Base::__count, 0, {}); - // if the class is countable, we might need to add some extra methods - if (_base->countable()) - { - // the method objectneed to stay in scope for the lifetime of the script (because the register a pointer - // to an internal string buffer) -- so we create them as static variables - static Method count("count", &Base::__count, 0, {}); + // register the serialize and unserialize method in case this was not yet done in PHP user space + if (!hasMethod("count")) + count.initialize(&_entries[i++], _name); + } - // register the serialize and unserialize method in case this was not yet done in PHP user space - if (!hasMethod("count")) count.initialize(&_entries[i++], _name); - } + // if the class is serializable, we might need some extra methods + if (_base->serializable()) + { + // the method object need to stay in scope for the lifetime of the script (because the register a pointer + // to an internal string buffer) -- so we create them as static variables + static Method serialize("serialize", &Base::__serialize, 0, {}); + static Method unserialize("unserialize", &Base::__unserialize, 0, {ByVal("input", Type::Undefined, true)}); + + // register the serialize and unserialize method in case this was not yet done in PHP user space + if (!hasMethod("serialize")) + serialize.initialize(&_entries[i++], _name); + if (!hasMethod("unserialize")) + unserialize.initialize(&_entries[i++], _name); + } - // if the class is serializable, we might need some extra methods - if (_base->serializable()) - { - // the method object need to stay in scope for the lifetime of the script (because the register a pointer - // to an internal string buffer) -- so we create them as static variables - static Method serialize("serialize", &Base::__serialize, 0, {}); - static Method unserialize("unserialize", &Base::__unserialize, 0, { ByVal("input", Type::Undefined, true) }); - - // register the serialize and unserialize method in case this was not yet done in PHP user space - if (!hasMethod("serialize")) serialize.initialize(&_entries[i++], _name); - if (!hasMethod("unserialize")) unserialize.initialize(&_entries[i++], _name); - } - - // if the class is traverable, we might need extra methods too (especially on php 8.1, maybe also 8.0?) - if (_base->traversable()) - { - // the method object need to stay in scope for the lifetime of the script (because the register a pointer - // to an internal string buffer) -- so we create them as static variables - static Method getIterator("getIterator", &Base::__getIterator, 0, {}); + // if the class is traverable, we might need extra methods too (especially on php 8.1, maybe also 8.0?) + if (_base->traversable()) + { + // the method object need to stay in scope for the lifetime of the script (because the register a pointer + // to an internal string buffer) -- so we create them as static variables + static Method getIterator("getIterator", &Base::__getIterator, 0, {}); - // register the serialize and unserialize method in case this was not yet done in PHP user space - if (!hasMethod("getIterator")) getIterator.initialize(&_entries[i++], _name); - } + // register the serialize and unserialize method in case this was not yet done in PHP user space + if (!hasMethod("getIterator")) + getIterator.initialize(&_entries[i++], _name); + } - // last entry should be set to all zeros - zend_function_entry *last = &_entries[i]; + // last entry should be set to all zeros + zend_function_entry *last = &_entries[i]; - // all should be set to zero - memset(last, 0, sizeof(zend_function_entry)); + // all should be set to zero + memset(last, 0, sizeof(zend_function_entry)); - // done - return _entries; -} + // done + return _entries; + } -/** - * Initialize the class, given its name - * - * The module functions are registered on module startup, but classes are - * initialized afterwards. The Zend engine is a strange thing. Nevertheless, - * this means that this method is called after the module is already available. - * This function will inform the Zend engine about the existence of the - * class. - * - * @param base the c++ class object created in the extension - * @param prefix namespace prefix - * @return zend_class_entry - */ -zend_class_entry *ClassImpl::initialize(ClassBase *base, const std::string &prefix) -{ - // store base pointer - _base = base; + /** + * Initialize the class, given its name + * + * The module functions are registered on module startup, but classes are + * initialized afterwards. The Zend engine is a strange thing. Nevertheless, + * this means that this method is called after the module is already available. + * This function will inform the Zend engine about the existence of the + * class. + * + * @param base the c++ class object created in the extension + * @param prefix namespace prefix + * @return zend_class_entry + */ + zend_class_entry *ClassImpl::initialize(ClassBase *base, const std::string &prefix) + { + // store base pointer + _base = base; - // the class entry - zend_class_entry entry; + // the class entry + zend_class_entry entry; - // update the name - if (prefix.size() > 0) _name = prefix + "\\" + _name; + // update the name + if (prefix.size() > 0) + _name = prefix + "\\" + _name; - // initialize the class entry - INIT_CLASS_ENTRY_EX(entry, _name.c_str(), _name.size(), entries()); + // initialize the class entry + INIT_CLASS_ENTRY_EX(entry, _name.c_str(), _name.size(), entries()); - // we need a special constructor, but only for real classes, not for interfaces. - // (in fact: from php 7.4 onwards the create_object method is part of union - // together with the interface_gets_implemented method, which causes a crash - // when the create_object property is set for an interface) - if (_type != ClassType::Interface) entry.create_object = &ClassImpl::createObject; + // we need a special constructor, but only for real classes, not for interfaces. + // (in fact: from php 7.4 onwards the create_object method is part of union + // together with the interface_gets_implemented method, which causes a crash + // when the create_object property is set for an interface) + if (_type != ClassType::Interface) + entry.create_object = &ClassImpl::createObject; - // register function that is called for static method calls - entry.get_static_method = &ClassImpl::getStaticMethod; + // register function that is called for static method calls + entry.get_static_method = &ClassImpl::getStaticMethod; - // for traversable classes we install a special method to get the iterator - if (_base->traversable()) - { - // install iterator functions - entry.get_iterator = &ClassImpl::getIterator; + // for traversable classes we install a special method to get the iterator + if (_base->traversable()) + { + // install iterator functions + entry.get_iterator = &ClassImpl::getIterator; - // prior to 7.3, the iterator functions were statically allocated. + // prior to 7.3, the iterator functions were statically allocated. #if PHP_VERSION_ID < 70300 - entry.iterator_funcs.funcs = IteratorImpl::functions(); + entry.iterator_funcs.funcs = IteratorImpl::functions(); #else - // from 7.3 and up, we may have to allocate it ourself - //entry.iterator_funcs_ptr = calloc(1, sizeof(zend_class_iterator_funcs)); + // from 7.3 and up, we may have to allocate it ourself + // entry.iterator_funcs_ptr = calloc(1, sizeof(zend_class_iterator_funcs)); #endif - } + } - // for serializable classes, we install callbacks for serializing and unserializing - if (_base->serializable()) - { - // add handlers to serialize and unserialize - entry.serialize = &ClassImpl::serialize; - entry.unserialize = &ClassImpl::unserialize; - } + // for serializable classes, we install callbacks for serializing and unserializing + if (_base->serializable()) + { + // add handlers to serialize and unserialize + entry.serialize = &ClassImpl::serialize; + entry.unserialize = &ClassImpl::unserialize; + } - // do we have a base class? - if (_parent) - { - // check if the base class was already defined - if (_parent->_entry) + // do we have a base class? + if (_parent) { - // register the class - _entry = zend_register_internal_class_ex(&entry, _parent->_entry); + // check if the base class was already defined + if (_parent->_entry) + { + // register the class + _entry = zend_register_internal_class_ex(&entry, _parent->_entry); + } + else + { + // report an error - the extension programmer probably made an error + std::cerr << "Derived class " << name() << " is initialized before base class " << _parent->name() << ": base class is ignored" << std::endl; + + // register the class, but without the base class + _entry = zend_register_internal_class(&entry); + } } else { - // report an error - the extension programmer probably made an error - std::cerr << "Derived class " << name() << " is initialized before base class " << _parent->name() << ": base class is ignored" << std::endl; - - // register the class, but without the base class + // register the class _entry = zend_register_internal_class(&entry); } - } - else - { - // register the class - _entry = zend_register_internal_class(&entry); - } - // register the interfaces - for (auto &interface : _interfaces) - { - // register this interface - if (interface->_entry) zend_class_implements(_entry, 1, interface->_entry); + // register the interfaces + for (auto &interface : _interfaces) + { + // register this interface + if (interface->_entry) + zend_class_implements(_entry, 1, interface->_entry); - // otherwise report an error - else std::cerr << "Derived class " << name() << " is initialized before base class " << interface->name() << ": interface is ignored" << std::endl; - } + // otherwise report an error + else + std::cerr << "Derived class " << name() << " is initialized before base class " << interface->name() << ": interface is ignored" << std::endl; + } - // we may have to expose the Traversable - if (_base->traversable()) - { + // we may have to expose the Traversable + if (_base->traversable()) + { #if PHP_VERSION_ID < 80000 - // up to php 7.x we can implement just the Traversable method (strictly speaking, it does not seem - // to be a user-space concern whether a class is traversable because of "iterator" or "iteratoraggregate" - zend_class_implements(_entry, 1, zend_ce_traversable); + // up to php 7.x we can implement just the Traversable method (strictly speaking, it does not seem + // to be a user-space concern whether a class is traversable because of "iterator" or "iteratoraggregate" + zend_class_implements(_entry, 1, zend_ce_traversable); #else - // from php 8.0 an error occurs if a class just implements traversable, without being an explicit - // subclass of Iterator or IteratorAggregate -- so we implement the iteraroraggregate instead - zend_class_implements(_entry, 1, zend_ce_aggregate); + // from php 8.0 an error occurs if a class just implements traversable, without being an explicit + // subclass of Iterator or IteratorAggregate -- so we implement the iteraroraggregate instead + zend_class_implements(_entry, 1, zend_ce_aggregate); #endif - } + } - // check if the Serializable interface - if (_base->serializable()) - { - // add the Seriablable interface - zend_class_implements(_entry, 1, zend_ce_serializable); - } + // check if the Serializable interface + if (_base->serializable()) + { + // add the Seriablable interface + zend_class_implements(_entry, 1, zend_ce_serializable); + } - // instal the right modifier (to make the class an interface, abstract class, etc) - _entry->ce_flags |= uint64_t(_type); + // instal the right modifier (to make the class an interface, abstract class, etc) + _entry->ce_flags |= uint64_t(_type); - // this pointer has to be copied to temporary pointer, as &this causes compiler error - ClassImpl *impl = this; + // this pointer has to be copied to temporary pointer, as &this causes compiler error + ClassImpl *impl = this; - // allocate memory for the doc_comment (which we abuse for storing a pointer to ourselves) - _self = zend_string_alloc(sizeof(this), 1); + // allocate memory for the doc_comment (which we abuse for storing a pointer to ourselves) + _self = zend_string_alloc(sizeof(this), 1); - // make the string appear empty - ZSTR_VAL(_self)[0] = '\0'; - ZSTR_LEN(_self) = 0; + // make the string appear empty + ZSTR_VAL(_self) + [0] = '\0'; + ZSTR_LEN(_self) = 0; - // copy over the 'this'-pointer after the null-character - std::memcpy(ZSTR_VAL(_self) + 1, &impl, sizeof(impl)); + // copy over the 'this'-pointer after the null-character + std::memcpy(ZSTR_VAL(_self) + 1, &impl, sizeof(impl)); - // install the doc_comment - _entry->info.user.doc_comment = _self; + // install the doc_comment + _entry->DOC_COMMENT = _self; - // declare all member variables - for (auto &member : _members) member->initialize(_entry); + // declare all member variables + for (auto &member : _members) + member->initialize(_entry); - // done - return _entry; -} + // done + return _entry; + } -/** - * End namespace - */ + /** + * End namespace + */ } - diff --git a/zend/iteratorimpl.cpp b/zend/iteratorimpl.cpp index bac23c93..11f577f0 100644 --- a/zend/iteratorimpl.cpp +++ b/zend/iteratorimpl.cpp @@ -11,171 +11,172 @@ /** * Set up namespace */ -namespace Php { - -/** - * Constructor - * @param zval The object that is being iterated - * @param iterator The iterator that is implemented by the extension - */ -IteratorImpl::IteratorImpl(zval *object, Iterator *iterator) : _userspace(iterator) -{ - // initialize the iterator - zend_iterator_init(&_iterator); - - // copy the object to the iterator, and set the callbacks - ZVAL_COPY(&_iterator.data, object); - _iterator.funcs = functions(); -} - -/** - * Destructor - */ -IteratorImpl::~IteratorImpl() -{ - // invalidate current - invalidate(); - - // one reference less to the original object - zval_ptr_dtor(&_iterator.data); -} - -/** - * Helper method to get access to ourselves - * @param iter - * @return IteratorImpl - */ -IteratorImpl *IteratorImpl::self(zend_object_iterator *iter) -{ - // cast to the the other variable - return (IteratorImpl *)iter; -} - -/** - * Iterator destructor method - * @param iter - */ -void IteratorImpl::destructor(zend_object_iterator *iter) -{ - // we are not going to deallocate the memory, because the php engine - // seems to do that automagically nowadays, we just call the destructor explicitly - self(iter)->~IteratorImpl(); -} - -/** - * Iterator valid function - * Returns FAILURE or SUCCESS - * @param iter - * @return int - */ -int IteratorImpl::valid(zend_object_iterator *iter) -{ - // check if valid - return self(iter)->valid() ? SUCCESS : FAILURE; -} - -/** - * Fetch the current item - * - * @param iter The iterator to retrieve the value from - * @return The current value of the iterator - */ -zval *IteratorImpl::current(zend_object_iterator *iter) -{ - // get the actual iterator - auto *iterator = self(iter); - - // retrieve the value (and store it in a member so that it is not - // destructed when the function returns) - auto &value = iterator->current(); - - // return the internal zval - return value._val; -} - -/** - * Fetch the key for the current element (optional, may be NULL). The key - * should be written into the provided zval* using the ZVAL_* macros. If - * this handler is not provided auto-incrementing integer keys will be - * used. - * @param iter - * @param key - */ -void IteratorImpl::key(zend_object_iterator *iter, zval *key) -{ - // retrieve the key - Value retval(self(iter)->key()); - - // detach the underlying zval - Zval val = retval.detach(true); - - // copy it to the key - ZVAL_ZVAL(key, val, 1, 1); -} - -/** - * Step forwards to the next element - * @param iter - */ -void IteratorImpl::next(zend_object_iterator *iter) -{ - // call the next method - self(iter)->next(); -} - -/** - * Rewind the iterator back to the start - * @param iter - */ -void IteratorImpl::rewind(zend_object_iterator *iter) -{ - // call the rewind method - self(iter)->rewind(); -} - -/** - * Invalidate the current value of the object - * @param iter - */ -void IteratorImpl::invalidate(zend_object_iterator *iter) +namespace Php { - // call the rewind method - self(iter)->invalidate(); -} - -/** - * Get access to all iterator functions - * @return zend_object_iterator_funcs - */ -zend_object_iterator_funcs *IteratorImpl::functions() -{ - // static variable with all functions - static zend_object_iterator_funcs funcs; - - // static variable that knows if the funcs are already initialized - static bool initialized = false; - // no need to set anything if already initialized - if (initialized) return &funcs; - - // set the members - funcs.dtor = &IteratorImpl::destructor; - funcs.valid = &IteratorImpl::valid; - funcs.get_current_data = &IteratorImpl::current; - funcs.get_current_key = &IteratorImpl::key; - funcs.move_forward = &IteratorImpl::next; - funcs.rewind = &IteratorImpl::rewind; - funcs.invalidate_current = &IteratorImpl::invalidate; - - // remember that functions are initialized - initialized = true; - - // done - return &funcs; -} - -/** - * End namespace - */ + /** + * Constructor + * @param zval The object that is being iterated + * @param iterator The iterator that is implemented by the extension + */ + IteratorImpl::IteratorImpl(zval *object, Iterator *iterator) : _userspace(iterator) + { + // initialize the iterator + zend_iterator_init(&_iterator); + + // copy the object to the iterator, and set the callbacks + ZVAL_COPY(&_iterator.data, object); + _iterator.funcs = functions(); + } + + /** + * Destructor + */ + IteratorImpl::~IteratorImpl() + { + // invalidate current + invalidate(); + + // one reference less to the original object + zval_ptr_dtor(&_iterator.data); + } + + /** + * Helper method to get access to ourselves + * @param iter + * @return IteratorImpl + */ + IteratorImpl *IteratorImpl::self(zend_object_iterator *iter) + { + // cast to the the other variable + return (IteratorImpl *)iter; + } + + /** + * Iterator destructor method + * @param iter + */ + void IteratorImpl::destructor(zend_object_iterator *iter) + { + // we are not going to deallocate the memory, because the php engine + // seems to do that automagically nowadays, we just call the destructor explicitly + self(iter)->~IteratorImpl(); + } + + /** + * Iterator valid function + * Returns FAILURE or SUCCESS + * @param iter + * @return zend_result + */ + ZEND_RESULT_OR_INT_84 IteratorImpl::valid(zend_object_iterator *iter) + { + // check if valid + return self(iter)->valid() ? SUCCESS : FAILURE; + } + + /** + * Fetch the current item + * + * @param iter The iterator to retrieve the value from + * @return The current value of the iterator + */ + zval *IteratorImpl::current(zend_object_iterator *iter) + { + // get the actual iterator + auto *iterator = self(iter); + + // retrieve the value (and store it in a member so that it is not + // destructed when the function returns) + auto &value = iterator->current(); + + // return the internal zval + return value._val; + } + + /** + * Fetch the key for the current element (optional, may be NULL). The key + * should be written into the provided zval* using the ZVAL_* macros. If + * this handler is not provided auto-incrementing integer keys will be + * used. + * @param iter + * @param key + */ + void IteratorImpl::key(zend_object_iterator *iter, zval *key) + { + // retrieve the key + Value retval(self(iter)->key()); + + // detach the underlying zval + Zval val = retval.detach(true); + + // copy it to the key + ZVAL_ZVAL(key, val, 1, 1); + } + + /** + * Step forwards to the next element + * @param iter + */ + void IteratorImpl::next(zend_object_iterator *iter) + { + // call the next method + self(iter)->next(); + } + + /** + * Rewind the iterator back to the start + * @param iter + */ + void IteratorImpl::rewind(zend_object_iterator *iter) + { + // call the rewind method + self(iter)->rewind(); + } + + /** + * Invalidate the current value of the object + * @param iter + */ + void IteratorImpl::invalidate(zend_object_iterator *iter) + { + // call the rewind method + self(iter)->invalidate(); + } + + /** + * Get access to all iterator functions + * @return zend_object_iterator_funcs + */ + zend_object_iterator_funcs *IteratorImpl::functions() + { + // static variable with all functions + static zend_object_iterator_funcs funcs; + + // static variable that knows if the funcs are already initialized + static bool initialized = false; + + // no need to set anything if already initialized + if (initialized) + return &funcs; + + // set the members + funcs.dtor = &IteratorImpl::destructor; + funcs.valid = &IteratorImpl::valid; + funcs.get_current_data = &IteratorImpl::current; + funcs.get_current_key = &IteratorImpl::key; + funcs.move_forward = &IteratorImpl::next; + funcs.rewind = &IteratorImpl::rewind; + funcs.invalidate_current = &IteratorImpl::invalidate; + + // remember that functions are initialized + initialized = true; + + // done + return &funcs; + } + + /** + * End namespace + */ } - diff --git a/zend/iteratorimpl.h b/zend/iteratorimpl.h index 84c707a1..5e8f9f99 100644 --- a/zend/iteratorimpl.h +++ b/zend/iteratorimpl.h @@ -17,174 +17,181 @@ /** * Set up namespace */ -namespace Php { - -/** - * Class definition - */ -class IteratorImpl +namespace Php { -public: - /** - * Get access to all iterator functions - * @return zend_object_iterator_funcs - */ - static zend_object_iterator_funcs *functions(); - -private: - /** - * The normal zend_object_iterator - * @var zend_object_iterator - */ - zend_object_iterator _iterator; - - /** - * Unique pointer to the user space iterator that is returned by the extension - * @var std::unique_ptr - */ - std::unique_ptr _userspace; - - /** - * Current value - * @var Value - */ - Value _current; - - /** - * Is the iterator on a valid position - * @return bool - */ - bool valid() - { - return _userspace->valid(); - } - /** - * The value at the current position - * @return Value - */ - Value ¤t() - { - // get from the user space iterator, and store as member - return _current = _userspace->current(); - } +#if PHP_VERSION_ID < 80400 +#define ZEND_RESULT_OR_INT_84 int +#else +#define ZEND_RESULT_OR_INT_84 zend_result +#endif /** - * The key at the current position - * @return Value + * Class definition */ - Value key() + class IteratorImpl { - return _userspace->key(); - } - - /** - * Move to the next position - */ - void next() - { - return _userspace->next(); - } - - /** - * Rewind the iterator to the front position - */ - void rewind() - { - return _userspace->rewind(); - } - - /** - * Invalidate the current variable - */ - void invalidate() - { - _current.invalidate(); - } - - /** - * Helper method to get access to ourselves - * @param iter - * @return IteratorImpl + public: + /** + * Get access to all iterator functions + * @return zend_object_iterator_funcs + */ + static zend_object_iterator_funcs *functions(); + + private: + /** + * The normal zend_object_iterator + * @var zend_object_iterator + */ + zend_object_iterator _iterator; + + /** + * Unique pointer to the user space iterator that is returned by the extension + * @var std::unique_ptr + */ + std::unique_ptr _userspace; + + /** + * Current value + * @var Value + */ + Value _current; + + /** + * Is the iterator on a valid position + * @return bool + */ + bool valid() + { + return _userspace->valid(); + } + + /** + * The value at the current position + * @return Value + */ + Value ¤t() + { + // get from the user space iterator, and store as member + return _current = _userspace->current(); + } + + /** + * The key at the current position + * @return Value + */ + Value key() + { + return _userspace->key(); + } + + /** + * Move to the next position + */ + void next() + { + return _userspace->next(); + } + + /** + * Rewind the iterator to the front position + */ + void rewind() + { + return _userspace->rewind(); + } + + /** + * Invalidate the current variable + */ + void invalidate() + { + _current.invalidate(); + } + + /** + * Helper method to get access to ourselves + * @param iter + * @return IteratorImpl + */ + static IteratorImpl *self(zend_object_iterator *iter); + + /** + * Iterator destructor method + * @param iter + */ + static void destructor(zend_object_iterator *iter); + + /** + * Iterator valid function + * Returns FAILURE or SUCCESS + * @param iter + * @return int + */ + static ZEND_RESULT_OR_INT_84 valid(zend_object_iterator *iter); + + /** + * Fetch the current item + * + * @param iter The iterator used to retrieve the value from + * @return The current value of the iterator + */ + static zval *current(zend_object_iterator *iter); + + /** + * Fetch the key for the current element (optional, may be NULL). The key + * should be written into the provided zval* using the ZVAL_* macros. If + * this handler is not provided auto-incrementing integer keys will be + * used. + * @param iter + * @param data + */ + static void key(zend_object_iterator *iter, zval *data); + + /** + * Step forwards to the next element + * @param iter + */ + static void next(zend_object_iterator *iter); + + /** + * Rewind the iterator back to the start + * @param iter + */ + static void rewind(zend_object_iterator *iter); + + /** + * Invalidate current object + * @param iter + */ + static void invalidate(zend_object_iterator *iter); + + public: + /** + * Constructor + * @param zval The object that is being iterated + * @param iterator The iterator that is implemented by the extension + */ + IteratorImpl(zval *object, Iterator *iterator); + + /** + * Destructor + * Important: this should not be virtual because this object is not allowed + * to have a vtable because it replies of memory alignment of the first member. + */ + ~IteratorImpl(); + + /** + * Internal method that returns the implementation object + * @return zend_object_iterator + */ + zend_object_iterator *implementation() + { + return &_iterator; + } + }; + + /** + * End namespace */ - static IteratorImpl *self(zend_object_iterator *iter); - - /** - * Iterator destructor method - * @param iter - */ - static void destructor(zend_object_iterator *iter); - - /** - * Iterator valid function - * Returns FAILURE or SUCCESS - * @param iter - * @return int - */ - static int valid(zend_object_iterator *iter); - - /** - * Fetch the current item - * - * @param iter The iterator used to retrieve the value from - * @return The current value of the iterator - */ - static zval *current(zend_object_iterator *iter); - - /** - * Fetch the key for the current element (optional, may be NULL). The key - * should be written into the provided zval* using the ZVAL_* macros. If - * this handler is not provided auto-incrementing integer keys will be - * used. - * @param iter - * @param data - */ - static void key(zend_object_iterator *iter, zval *data); - - /** - * Step forwards to the next element - * @param iter - */ - static void next(zend_object_iterator *iter); - - /** - * Rewind the iterator back to the start - * @param iter - */ - static void rewind(zend_object_iterator *iter); - - /** - * Invalidate current object - * @param iter - */ - static void invalidate(zend_object_iterator *iter); - -public: - /** - * Constructor - * @param zval The object that is being iterated - * @param iterator The iterator that is implemented by the extension - */ - IteratorImpl(zval *object, Iterator *iterator); - - /** - * Destructor - * Important: this should not be virtual because this object is not allowed - * to have a vtable because it replies of memory alignment of the first member. - */ - ~IteratorImpl(); - - /** - * Internal method that returns the implementation object - * @return zend_object_iterator - */ - zend_object_iterator *implementation() - { - return &_iterator; - } -}; - -/** - * End namespace - */ } diff --git a/zend/module.h b/zend/module.h index 7902f8e9..df827ac2 100644 --- a/zend/module.h +++ b/zend/module.h @@ -8,218 +8,234 @@ * from an ~user directory to be able to make changes to the globalle running * apache process, and for example make changes to catch data from other websites * running on the same host. - * - * However, people who are in a position to write C++ and deploy code however, + * + * However, people who are in a position to write C++ and deploy code however, * already _ARE_ in a position to do dangerous things. For them we do not want * these safety checks. In fact, we want to offer raw access, so that they can * open modules - no matter what. So we had to make our own copy of the dl() * function, and because we're here in C++ context, we do it a little nicer * than the original C++ code. This module class is a utility class used by * our own dl() implementation. - * + * * @author Emiel Bruijntjes * @copyright 2015 Copernica BV */ +#define MODULE_PERSISTENT 1 +#define MODULE_TEMPORARY 2 + /** * Set up namespace */ -namespace Php { - -/** - * Class definition - */ -class Module +namespace Php { -private: - /** - * The handle of the module - * @var void* - */ - void *_handle; - - /** - * The module entry - * @var zend_module_entry - */ - zend_module_entry *_entry = nullptr; - /** - * Internal helper class with persistent modules + * Class definition */ - class Persistent + class Module { private: /** - * The set of handles - * @var std::set + * The handle of the module + * @var void* + */ + void *_handle; + + /** + * The module entry + * @var zend_module_entry + */ + zend_module_entry *_entry = nullptr; + + /** + * Internal helper class with persistent modules */ - std::set _handles; - + class Persistent + { + private: + /** + * The set of handles + * @var std::set + */ + std::set _handles; + + public: + /** + * Constructor + */ + Persistent() {} + + /** + * Destructor + */ + virtual ~Persistent() + { + // remove all handles + while (!_handles.empty()) + { + // get first handle + auto iter = _handles.begin(); + + // remove the handle + DL_UNLOAD(*iter); + + // remove from set + _handles.erase(iter); + } + } + + /** + * Check whether a handle is already persistently opened + * @param handle + * @return bool + */ + bool contains(void *handle) const + { + return _handles.find(handle) != _handles.end(); + } + + /** + * Add a library persistently + * @param module + */ + void add(const char *module) + { + // insert the handle + _handles.insert(DL_LOAD(module)); + } + }; + + /** + * All persistent modules + * @var Persistent + */ + static Persistent _persistent; + public: /** * Constructor + * + * A module can be loaded persistently. This means that the variables in + * the module will keep in scope for as long as Apache runs, even though + * the extension is not active in other page views + * + * @param module Name of the module + * @param persistent Should it be loaded persistently */ - Persistent() {} - + Module(const char *module, bool persistent) + { + // the path we're going to load + ExtensionPath path(module); + + // load the module + _handle = DL_LOAD(path); + + // handle should be valid + if (!_handle) + return; + + // if we have to open it persistently, we open it for a second time so that + // the refcounter always stays 1 or higher + if (persistent && !_persistent.contains(_handle)) + _persistent.add(module); + + // we have to call the get_module() function + Symbol get_module(_handle, "get_module"); + + // was the get_module() function found + if (!get_module) + return; + + // retrieve the module entry + _entry = get_module(); + } + /** * Destructor */ - virtual ~Persistent() + virtual ~Module() { - // remove all handles - while (!_handles.empty()) - { - // get first handle - auto iter = _handles.begin(); - - // remove the handle - DL_UNLOAD(*iter); - - // remove from set - _handles.erase(iter); - } + // if the handle is still valid, we have to unload it + if (_handle) + DL_UNLOAD((DL_HANDLE)_handle); } - + /** - * Check whether a handle is already persistently opened - * @param handle + * Check if the module is valid * @return bool */ - bool contains(void *handle) const + bool valid() const { - return _handles.find(handle) != _handles.end(); + // module-entry must exist + if (!_handle || !_entry) + return false; + + // check api compatibility + if (_entry->zend_api != ZEND_MODULE_API_NO) + return false; + + // and other stuff + return strcmp(_entry->build_id, ZEND_MODULE_BUILD_ID) == 0; } - + /** - * Add a library persistently - * @param module + * Start the module + * @return bool */ - void add(const char *module) + bool start() { - // insert the handle - _handles.insert(DL_LOAD(module)); + // this is not possible if the module is invalid in the first place + if (!valid()) + return false; + + // the Zend engine sets a number of properties in the entry class, we do that here too + // note that it would be better to call zend_next_free_module() to find the next module + // number, but some users complain that this function is not always available + _entry->type = MODULE_TEMPORARY; + _entry->module_number = zend_hash_num_elements(&module_registry) + 1; + _entry->handle = _handle; + + // @todo does loading an extension even work in a multi-threading setup? + + // register the module, this apparently returns a copied entry pointer +#if PHP_VERSION_ID < 80400 + auto *entry = zend_register_module_ex(_entry); +#else + auto *entry = zend_register_module_ex(_entry, MODULE_PERSISTENT); +#endif + + // forget the entry, so that a new call to start() will fail too + _entry = nullptr; + + // leap out on failure + if (entry == NULL) + return false; + + // startup the module + if (zend_startup_module_ex(entry) == FAILURE) + return false; + + // was a startup-function defined? if not + if (entry->request_startup_func) + { + // call the startup function + if (entry->request_startup_func(MODULE_TEMPORARY, entry->module_number) == FAILURE) + return false; + } + + // all is ok, we can forget about the handle now, so that is won't get destructed + _handle = nullptr; + + // enable full-table-cleanup, because inside the Zend-engine this is done too + EG(full_tables_cleanup) = 1; + + // we're ready + return true; } }; /** - * All persistent modules - * @var Persistent - */ - static Persistent _persistent; - -public: - /** - * Constructor - * - * A module can be loaded persistently. This means that the variables in - * the module will keep in scope for as long as Apache runs, even though - * the extension is not active in other page views - * - * @param module Name of the module - * @param persistent Should it be loaded persistently - */ - Module(const char *module, bool persistent) - { - // the path we're going to load - ExtensionPath path(module); - - // load the module - _handle = DL_LOAD(path); - - // handle should be valid - if (!_handle) return; - - // if we have to open it persistently, we open it for a second time so that - // the refcounter always stays 1 or higher - if (persistent && !_persistent.contains(_handle)) _persistent.add(module); - - // we have to call the get_module() function - Symbol get_module(_handle, "get_module"); - - // was the get_module() function found - if (!get_module) return; - - // retrieve the module entry - _entry = get_module(); - } - - /** - * Destructor - */ - virtual ~Module() - { - // if the handle is still valid, we have to unload it - if (_handle) DL_UNLOAD((DL_HANDLE)_handle); - } - - /** - * Check if the module is valid - * @return bool + * End of namespace */ - bool valid() const - { - // module-entry must exist - if (!_handle || !_entry) return false; - - // check api compatibility - if (_entry->zend_api != ZEND_MODULE_API_NO) return false; - - // and other stuff - return strcmp(_entry->build_id, ZEND_MODULE_BUILD_ID) == 0; - } - - /** - * Start the module - * @return bool - */ - bool start() - { - // this is not possible if the module is invalid in the first place - if (!valid()) return false; - - // the Zend engine sets a number of properties in the entry class, we do that here too - // note that it would be better to call zend_next_free_module() to find the next module - // number, but some users complain that this function is not always available - _entry->type = MODULE_TEMPORARY; - _entry->module_number = zend_hash_num_elements(&module_registry) + 1; - _entry->handle = _handle; - - // @todo does loading an extension even work in a multi-threading setup? - - // register the module, this apparently returns a copied entry pointer - auto *entry = zend_register_module_ex(_entry); - - // forget the entry, so that a new call to start() will fail too - _entry = nullptr; - - // leap out on failure - if (entry == NULL) return false; - - // startup the module - if (zend_startup_module_ex(entry) == FAILURE) return false; - - // was a startup-function defined? if not - if (entry->request_startup_func) - { - // call the startup function - if (entry->request_startup_func(MODULE_TEMPORARY, entry->module_number) == FAILURE) return false; - } - - // all is ok, we can forget about the handle now, so that is won't get destructed - _handle = nullptr; - - // enable full-table-cleanup, because inside the Zend-engine this is done too - EG(full_tables_cleanup) = 1; - - // we're ready - return true; - } -}; - -/** - * End of namespace - */ } -