From 9f2e816c787c30ceeb139623c8dae594c4b4443d Mon Sep 17 00:00:00 2001 From: Martijn Otto Date: Wed, 11 May 2016 17:34:40 +0200 Subject: Work in progress on PHP7 compatibility --- include/parameters.h | 12 ++- zend/callable.h | 46 ++++------- zend/classimpl.cpp | 185 +++++++++++---------------------------------- zend/classimpl.h | 121 +++++++++++++---------------- zend/compileroptions.h | 16 ++-- zend/constantimpl.h | 82 ++++++++++---------- zend/exception_handler.cpp | 16 ++-- zend/executestate.h | 25 +++--- zend/hashiterator.h | 91 ++++++++++------------ zend/iteratorimpl.cpp | 20 ++--- zend/iteratorimpl.h | 30 ++++---- zend/object.cpp | 5 +- zend/objectimpl.h | 123 ++++++++++-------------------- zend/opcodes.h | 52 ++++++------- zend/origexception.h | 36 ++++----- zend/parametersimpl.h | 29 ++++--- zend/traverseiterator.h | 93 +++++++---------------- 17 files changed, 386 insertions(+), 596 deletions(-) diff --git a/include/parameters.h b/include/parameters.h index 3465863..b5731e5 100644 --- a/include/parameters.h +++ b/include/parameters.h @@ -42,9 +42,17 @@ protected: public: /** - * Destructor + * Do _not_ add a virtual destructor here. + * + * We are extending a vector, which does not itself + * have a virtual destructor, so destructing through + * a pointer to this vector has no effect. + * + * By adding a virtual destructor we create a vtable, + * which makes the class bigger, causing slicing and + * then we are actually introducing the problem that + * we are trying to avoid! */ - virtual ~Parameters() {} /** * The object that is being called diff --git a/zend/callable.h b/zend/callable.h index bd74f27..272b22e 100644 --- a/zend/callable.h +++ b/zend/callable.h @@ -1,7 +1,7 @@ /** * Callable.h * - * Object represents a callable function or method that is defined with the CPP + * Object represents a callable function or method that is defined with the CPP * API. * * @author Emiel Bruijntjes @@ -13,7 +13,7 @@ */ namespace Php { -/** +/** * Class definition */ class Callable @@ -29,22 +29,22 @@ public: // construct vector for arguments _argc = arguments.size(); _argv = new zend_arg_info[_argc+1]; - + // the first record is initialized with information about the function, // so we skip that here int i=1; - + // loop through the arguments for (auto it = arguments.begin(); it != arguments.end(); it++) { // increment counter with number of required parameters if (it->required()) _required++; - + // fill the arg info fill(&_argv[i++], *it); } } - + /** * Copy constructor * @param that @@ -55,7 +55,7 @@ public: _required(that._required), _argc(that._argc), _argv(nullptr) {} - + /** * Move constructor * @param that @@ -65,7 +65,7 @@ public: _return(that._return), _required(that._required), _argc(that._argc), - _argv(that._argv) + _argv(that._argv) { // invalidate other object that._argv = nullptr; @@ -78,14 +78,14 @@ public: { if (_argv) delete[] _argv; } - + /** * Method that gets called every time the function is executed * @param params The parameters that were passed * @return Variable Return value */ virtual Value invoke(Parameters ¶ms) = 0; - + /** * Fill a function entry * @param entry Entry to be filled @@ -134,7 +134,7 @@ protected: * @var zend_arg_info[] */ zend_arg_info *_argv = nullptr; - + /** * Private helper method to fill an argument object * @param info object from the zend engine @@ -143,10 +143,11 @@ protected: void fill(zend_arg_info *info, const Argument &arg) const { // fill members - info->name = arg.name(); - info->name_len = ::strlen(arg.name()); + info->name = zend_string_init(arg.name(), ::strlen(arg.name()), 1); -#if PHP_VERSION_ID >= 50400 + // are we filling an object + if (arg.type() == Type::Object) info->class_name = zend_string_init(arg.classname(), ::strlen(arg.classname()), 1); + else info->class_name = nullptr; // since php 5.4 there is a type-hint, but we only support arrays, objects and callables switch (arg.type()) { @@ -155,29 +156,14 @@ protected: case Type::Object: info->type_hint = IS_OBJECT; break; default: info->type_hint = IS_NULL; break; } - -# if PHP_VERSION_ID >= 50600 - // from PHP 5.6 and onwards, an is_variadic property can be set, this + // from PHP 5.6 and onwards, an is_variadic property can be set, this // specifies whether this argument is the first argument that specifies // the type for a variable length list of arguments. For now we only // support methods and functions with a fixed number of arguments. info->is_variadic = false; -# endif - -#else - - // php 5.3 code - info->array_type_hint = arg.type() == Type::Array; - info->return_reference = false; - info->required_num_args = 0; // @todo is this correct? - -#endif - // this parameter is a regular type - info->class_name = arg.type() == Type::Object ? arg.classname() : nullptr; - info->class_name_len = arg.type() == Type::Object && arg.classname() ? ::strlen(arg.classname()) : 0; info->allow_null = arg.allowNull(); info->pass_by_reference = arg.byReference(); } diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index 5c339e9..84e23ef 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -21,10 +21,8 @@ ClassImpl::~ClassImpl() // destruct the entries if (_entries) delete[] _entries; -#if PHP_VERSION_ID >= 50400 - // on newer php versions, we have allocated the command to hide a pointer in it - if (_self) free(_self); -#endif + // free the stored pointer + free(_self); } /** @@ -38,8 +36,6 @@ ClassImpl::~ClassImpl() */ static ClassImpl *self(zend_class_entry *entry) { -#if PHP_VERSION_ID >= 50400 - /** * somebody could have extended this class from PHP userland, in which * case trying to dereference the doc_comment would result in a disaster @@ -70,25 +66,6 @@ static ClassImpl *self(zend_class_entry *entry) // 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)); -#else - - /** - * This is likely not correct: If the class was extended using PHP-CPP - * itself, we should retrieve the ClassImpl directly, however there is - * no sane way to check this here, unlike in PHP 5.4. - * - * We therefore always go to the very base, of which we are sure that - * we are the implementers. This way - at least - we don't cause any - * segfaults. - */ - while (entry->parent) entry = entry->parent; - - // on php 5.3 we store the pointer to impl after the name in the entry - ClassImpl** impl = (ClassImpl**)(entry->name + 1 + entry->name_length); - - // return the actual implementation - return *impl; -#endif } /** @@ -203,17 +180,14 @@ void ClassImpl::callInvoke(INTERNAL_FUNCTION_PARAMETERS) /** * Method that returns the function definition of the __call function - * @param object_ptr - * @param method_name - * @param method_len + * + * @param object_ptr Pointer to the object from which we want to retrieve the member function + * @param method The method that we want information about + * @param key ??? * @param tsrm_ls * @return zend_function */ -#if PHP_VERSION_ID < 50399 -zend_function *ClassImpl::getMethod(zval **object_ptr, char *method_name, int method_len TSRMLS_DC) -#else -zend_function *ClassImpl::getMethod(zval **object_ptr, char *method_name, int method_len, const zend_literal *key TSRMLS_DC) -#endif +zend_function *ClassImpl::getMethod(zval **object_ptr, zend_string *method, const zval *key TSRMLS_DC) { // 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 @@ -224,11 +198,7 @@ zend_function *ClassImpl::getMethod(zval **object_ptr, char *method_name, int me // 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 -#if PHP_VERSION_ID < 50399 - auto *defaultFunction = std_object_handlers.get_method(object_ptr, method_name, method_len TSRMLS_CC); -#else - auto *defaultFunction = std_object_handlers.get_method(object_ptr, method_name, method_len, key TSRMLS_CC); -#endif + auto *defaultFunction = std_object_handlers.get_method(object_ptr, method, key TSRMLS_CC); // did the default implementation do anything? if (defaultFunction) return defaultFunction; @@ -254,7 +224,7 @@ zend_function *ClassImpl::getMethod(zval **object_ptr, char *method_name, int me function->required_num_args = 0; function->scope = entry; function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; - function->function_name = method_name; + function->function_name = method; // store pointer to ourselves data->self = self(entry); @@ -266,21 +236,18 @@ zend_function *ClassImpl::getMethod(zval **object_ptr, char *method_name, int me /** * Method that is called right before a static method call is attempted - * @param entry - * @param method - * @param method_len + * + * @param entry The class entry to find the static function in + * @param method The method to get information about + * @param key ??? * @param tsrm_ls * @return zend_function */ -zend_function *ClassImpl::getStaticMethod(zend_class_entry *entry, char* method, int method_len TSRMLS_DC) +zend_function *ClassImpl::getStaticMethod(zend_class_entry *entry, zend_string *method, const zval *key TSRMLS_DC) { // 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 -#if PHP_VERSION_ID < 50399 - auto *defaultFunction = zend_std_get_static_method(entry, method, method_len TSRMLS_CC); -#else - auto *defaultFunction = zend_std_get_static_method(entry, method, method_len, nullptr TSRMLS_CC); -#endif + auto *defaultFunction = zend_std_get_static_method(entry, method, key TSRMLS_CC); // did the default implementation do anything? if (defaultFunction) return defaultFunction; @@ -542,7 +509,7 @@ int ClassImpl::cast(zval *val, zval *retval, int type TSRMLS_DC) * @param val The object to be cloned * @return zend_obejct_value The object to be created */ -zend_object_value ClassImpl::cloneObject(zval *val TSRMLS_DC) +zend_object ClassImpl::cloneObject(zval *val TSRMLS_DC) { // retrieve the class entry linked to this object auto *entry = zend_get_class_entry(val TSRMLS_CC); @@ -564,17 +531,8 @@ zend_object_value ClassImpl::cloneObject(zval *val TSRMLS_DC) // an exception back to the Zend engine) if (!cpp) zend_error(E_ERROR, "Unable to clone %s", entry->name); - // the thing we're going to return - zend_object_value result; - - // set the handlers - result.handlers = impl->objectHandlers(); - // store the object - ObjectImpl *new_object = new ObjectImpl(entry, cpp, 1 TSRMLS_CC); - - // store the object in the object cache - result.handle = new_object->handle(); + auto *new_object = new ObjectImpl(entry, cpp, impl->objectHandlers, 1 TSRMLS_CC); // clone the members (this will also call the __clone() function if the user // had registered that as a visible method) @@ -584,7 +542,7 @@ zend_object_value ClassImpl::cloneObject(zval *val TSRMLS_DC) if (!entry->clone) meta->callClone(cpp); // done - return result; + return new_object->object(); } /** @@ -856,18 +814,16 @@ zval *ClassImpl::toZval(Value &&value, int type) /** * Function that is called when a property is read - * @param object - * @param name - * @param type - * @param key + * + * @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 The "return value" (for errors * @param tsrm_ls * @return val */ -#if PHP_VERSION_ID < 50399 -zval *ClassImpl::readProperty(zval *object, zval *name, int type TSRMLS_DC) -#else -zval *ClassImpl::readProperty(zval *object, zval *name, int type, const zend_literal *key TSRMLS_DC) -#endif +zval *ClassImpl::readProperty(zval *object, zval *name, int type, void **cache_slot, zval *rv TSRMLS_DC) { // what to do with the type? // @@ -924,11 +880,7 @@ zval *ClassImpl::readProperty(zval *object, zval *name, int type, const zend_lit if (!std_object_handlers.read_property) return nullptr; // call default -#if PHP_VERSION_ID < 50399 - return std_object_handlers.read_property(object, name, type TSRMLS_CC); -#else return std_object_handlers.read_property(object, name, type, key TSRMLS_CC); -#endif } catch (Exception &exception) { @@ -950,15 +902,11 @@ zval *ClassImpl::readProperty(zval *object, zval *name, int type, const zend_lit * @param object The object on which it is called * @param name The name of the property * @param value The new value - * @param key ??? + * @param cache_slot The cache slot used * @param tsrm_ls * @return zval */ -#if PHP_VERSION_ID < 50399 -void ClassImpl::writeProperty(zval *object, zval *name, zval *value TSRMLS_DC) -#else -void ClassImpl::writeProperty(zval *object, zval *name, zval *value, const zend_literal *key TSRMLS_DC) -#endif +void ClassImpl::writeProperty(zval *object, zval *name, zval *value, void **cache_slot TSRMLS_DC) { // retrieve the object and class Base *base = ObjectImpl::find(object TSRMLS_CC)->object(); @@ -1001,11 +949,7 @@ void ClassImpl::writeProperty(zval *object, zval *name, zval *value, const zend_ if (!std_object_handlers.write_property) return; // call the default -#if PHP_VERSION_ID < 50399 - std_object_handlers.write_property(object, name, value TSRMLS_CC); -#else std_object_handlers.write_property(object, name, value, key TSRMLS_CC); -#endif } catch (Exception &exception) { @@ -1031,15 +975,11 @@ void ClassImpl::writeProperty(zval *object, zval *name, zval *value, const zend_ * @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 key ??? + * @param cache_slot The cache slot used * @param tsrm_ls * @return bool */ -#if PHP_VERSION_ID < 50399 -int ClassImpl::hasProperty(zval *object, zval *name, int has_set_exists TSRMLS_DC) -#else -int ClassImpl::hasProperty(zval *object, zval *name, int has_set_exists, const zend_literal *key TSRMLS_DC) -#endif +int ClassImpl::hasProperty(zval *object, zval *name, int has_set_exists, void **cache_slot TSRMLS_DC) { // the default implementation throws an exception, if we catch that // we know for sure that the user has not overridden the __isset method @@ -1082,11 +1022,7 @@ int ClassImpl::hasProperty(zval *object, zval *name, int has_set_exists, const z if (!std_object_handlers.has_property) return 0; // call default -#if PHP_VERSION_ID < 50399 - return std_object_handlers.has_property(object, name, has_set_exists TSRMLS_CC); -#else return std_object_handlers.has_property(object, name, has_set_exists, key TSRMLS_CC); -#endif } catch (Exception &exception) { @@ -1106,14 +1042,10 @@ int ClassImpl::hasProperty(zval *object, zval *name, int has_set_exists, const z * * @param object The object on which it is called * @param member The member to remove - * @param key + * @param cache_slot The cache slot used * @param tsrm_ls */ -#if PHP_VERSION_ID < 50399 -void ClassImpl::unsetProperty(zval *object, zval *member TSRMLS_DC) -#else -void ClassImpl::unsetProperty(zval *object, zval *member, const zend_literal *key TSRMLS_DC) -#endif +void ClassImpl::unsetProperty(zval *object, zval *member, void **cache_slot TSRMLS_DC) { // the default implementation throws an exception, if we catch that // we know for sure that the user has not overridden the __unset method @@ -1143,11 +1075,7 @@ void ClassImpl::unsetProperty(zval *object, zval *member, const zend_literal *ke if (!std_object_handlers.unset_property) return; // call the default -#if PHP_VERSION_ID < 50399 - std_object_handlers.unset_property(object, member TSRMLS_CC); -#else std_object_handlers.unset_property(object, member, key TSRMLS_CC); -#endif } catch (Exception &exception) { @@ -1164,7 +1092,7 @@ void ClassImpl::unsetProperty(zval *object, zval *member, const zend_literal *ke * @param handle * @param tsrm_ls */ -void ClassImpl::destructObject(zend_object *object, zend_object_handle handle TSRMLS_DC) +void ClassImpl::destructObject(zend_object *object TSRMLS_DC) { // find object ObjectImpl *obj = ObjectImpl::find(object); @@ -1212,7 +1140,7 @@ void ClassImpl::freeObject(zend_object *object TSRMLS_DC) * @param tsrm_ls * @return zend_object_value The newly created object */ -zend_object_value ClassImpl::createObject(zend_class_entry *entry TSRMLS_DC) +zend_object ClassImpl::createObject(zend_class_entry *entry TSRMLS_DC) { // we need the C++ class meta-information object ClassImpl *impl = self(entry); @@ -1225,20 +1153,11 @@ zend_object_value ClassImpl::createObject(zend_class_entry *entry TSRMLS_DC) // the Zend engine) if (!cpp) zend_error(E_ERROR, "Unable to instantiate %s", entry->name); - // the thing we're going to return - zend_object_value result; - - // set the handlers - result.handlers = impl->objectHandlers(); - // create the object in the zend engine - ObjectImpl *object = new ObjectImpl(entry, cpp, 1 TSRMLS_CC); + auto *object = new ObjectImpl(entry, cpp, impl->objectHandlers(), 1 TSRMLS_CC); - // store the object in the object cache - result.handle = object->handle(); - - // done - return result; + // return the php object stored in the implementation + return object->object(); } /** @@ -1427,6 +1346,14 @@ zend_class_entry *ClassImpl::initialize(ClassBase *base, const std::string &pref // we need a special constructor entry.create_object = &ClassImpl::createObject; + // register destructor and deallocator + entry.dtor_obj = &ClassImpl::destructObject; + entry.free_obj = &ClassImpl::freeObject; + + // set the offset for the zend_object, to allow PHP to + // locate the original point of the allocated memory + entry.offset = ObjectImpl::offset(); + // register function that is called for static method calls entry.get_static_method = &ClassImpl::getStaticMethod; @@ -1478,30 +1405,6 @@ zend_class_entry *ClassImpl::initialize(ClassBase *base, const std::string &pref // this pointer has to be copied to temporary pointer, as &this causes compiler error ClassImpl *impl = this; -#if PHP_VERSION_ID >= 50400 - - // allocate doc comment to contain an empty string + a hidden pointer - _self = (char *)malloc(1 + sizeof(ClassImpl *)); - - // empty string on first position - _self[0] = '\0'; - - // copy the 'this' pointer to the doc-comment - memcpy(_self+1, &impl, sizeof(ClassImpl *)); - - // set our comment in the actual class entry - _entry->info.user.doc_comment = _self; - -#else - - // Reallocate some extra space in the name in the zend_class_entry so we can fit a pointer behind it - _entry->name = (char *) realloc(_entry->name, _entry->name_length + 1 + sizeof(ClassImpl *)); - - // Copy the pointer after it - memcpy(_entry->name + _entry->name_length + 1, &impl, sizeof(ClassImpl *)); - -#endif - // set access types flags for class _entry->ce_flags = (int)_type; diff --git a/zend/classimpl.h b/zend/classimpl.h index 18802e3..47e7785 100644 --- a/zend/classimpl.h +++ b/zend/classimpl.h @@ -37,12 +37,12 @@ private: */ ClassType _type = ClassType::Regular; - /** + /** * The class entry * @var zend_class_entry */ zend_class_entry *_entry = nullptr; - + /** * Pointer to the entries * @var zend_function_entry[] @@ -54,13 +54,13 @@ private: * @var std::list */ std::list> _methods; - + /** * All class members (class properties) * @var std::list */ std::list> _members; - + /** * Map of dynamically accessible properties * @var std::map @@ -72,19 +72,19 @@ private: * @var std::list */ std::list> _interfaces; - + /** * The parent/base class * @var std::shared_ptr */ std::shared_ptr _parent; - + /** * The object handlers for instances of this class * @var zend_object_handlers */ zend_object_handlers _handlers; - + /** * Are the handlers already initialized? * @var bool @@ -98,10 +98,10 @@ private: char *_self = nullptr; /** - * Retrieve an array of zend_function_entry objects that hold the + * 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[] */ @@ -155,16 +155,16 @@ public: /** * 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 extension C++ class + * + * @param base The extension C++ class * @param ns Namespace name - * @param tsrm_ls + * @param tsrm_ls * @return zend_class_entry */ struct _zend_class_entry *initialize(ClassBase *base, const std::string &ns TSRMLS_DC); @@ -174,11 +174,11 @@ public: * @param entry Pointer to class information * @param val The object to be cloned * @param tsrm_ls - * @return zend_object_value Object info + * @return zend_object Object info */ - static zend_object_value createObject(zend_class_entry *entry TSRMLS_DC); - static zend_object_value cloneObject(zval *val TSRMLS_DC); - static void destructObject(zend_object *object, unsigned int handle TSRMLS_DC); + static zend_object createObject(zend_class_entry *entry TSRMLS_DC); + static zend_object cloneObject(zval *val TSRMLS_DC); + static void destructObject(zend_object *object TSRMLS_DC); static void freeObject(zend_object *object TSRMLS_DC); /** @@ -195,7 +195,7 @@ public: /** * Function that is used to count the number of elements in the object - * If the user has implemented the Countable interface, this method will + * If the user has implemented the Countable interface, this method will * call the count() method * @param val * @param count @@ -230,7 +230,7 @@ public: * @return zend_object_handlers */ static zend_object_handlers *objectHandlers(zend_class_entry *entry); - + /** * Function to create a new iterator to iterate over an object * @param entry The class entry @@ -243,85 +243,72 @@ public: /** * Function that is called when a property is being 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 key ??? + * @param cache_slot The cache slot used + * @param rv The "return value" (for errors * @param tsrm_ls * @return zval */ -#if PHP_VERSION_ID >= 50400 - static zval *readProperty(zval *object, zval *name, int type, const zend_literal *key TSRMLS_DC); -#else - static zval *readProperty(zval *object, zval *name, int type TSRMLS_DC); -#endif + static zval *readProperty(zval *object, zval *name, int type, void **cache_slot, zval *rv TSRMLS_DC); /** * Function that is called when a property is set / updated + * * @param object The object on which it is called * @param name The name of the property * @param value The new value - * @param key ??? + * @param cache_slot The cache slot used * @param tsrm_ls * @return zval */ -#if PHP_VERSION_ID >= 50400 - static void writeProperty(zval *object, zval *name, zval *value, const zend_literal *key TSRMLS_DC); -#else - static void writeProperty(zval *object, zval *name, zval *value TSRMLS_DC); -#endif + static void writeProperty(zval *object, zval *name, zval *value, void **cache_slot TSRMLS_DC); /** * Function that is called to check whether a certain property is set + * * @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 * @param tsrm_ls * @return bool */ -#if PHP_VERSION_ID >= 50400 - static int hasProperty(zval *object, zval *name, int has_set_exists, const zend_literal *key TSRMLS_DC); -#else - static int hasProperty(zval *object, zval *name, int has_set_exists TSRMLS_DC); -#endif + static int hasProperty(zval *object, zval *name, int has_set_exists, void **cache_slot TSRMLS_DC); /** * Function that is called when a property is removed from the project + * * @param object The object on which it is called * @param member The member to remove + * @param cache_slot The cache slot used * @param tsrm_ls */ -#if PHP_VERSION_ID >= 50400 - static void unsetProperty(zval *object, zval *member, const zend_literal *key TSRMLS_DC); -#else - static void unsetProperty(zval *object, zval *member TSRMLS_DC); -#endif + static void unsetProperty(zval *object, zval *member, void **cache_slot TSRMLS_DC); /** * Method that returns information about the function signature of a undefined method - * @param object_ptr - * @param method - * @param method_len - * @param key + * + * @param object_ptr Pointer to the object from which we want to retrieve the member function + * @param method The method that we want information about + * @param key ??? * @param tsrm_ls * @return zend_function */ -#if PHP_VERSION_ID >= 50400 - static zend_function *getMethod(zval **object_ptr, char *method, int method_len, const zend_literal *key TSRMLS_DC); -#else - static zend_function *getMethod(zval **object_ptr, char *method, int method_len TSRMLS_DC); -#endif + static zend_function *getMethod(zval **object_ptr, zend_string *method, const zval *key TSRMLS_DC); /** * Method that returns information about the function signature of an undefined static method - * @param object_ptr - * @param method - * @param method_len - * @param key + * + * @param entry The class entry to find the static function in + * @param method The method that we want information about + * @param key ??? * @param tsrm_ls * @return zend_function */ - static zend_function *getStaticMethod(zend_class_entry *entry, char* method, int method_len TSRMLS_DC); + static zend_function *getStaticMethod(zend_class_entry *entry, zend_string *method, const zval *key TSRMLS_DC); /** * Method that returns information about the __invoke() method @@ -365,17 +352,17 @@ public: */ static int serialize(zval *object, unsigned char **buffer, unsigned int *buf_len, zend_serialize_data *data TSRMLS_DC); static int unserialize(zval **object, zend_class_entry *entry, const unsigned char *buffer, unsigned int buf_len, zend_unserialize_data *data TSRMLS_DC); - + /** * Add a method to the class - * zend_serialize_data + * zend_serialize_data * The method will be accessible as one of the class methods in your PHP * code. When the method is called, it will automatically be forwarded * to the C++ implementation. The flags can be Php::Public, Php::Protected * or Php::Private (using private methods can be useful if you for example * want to make the __construct() function private). The access-modified * flag can be bitwise combined with the flag Php::Final or Php::Abstract). - * + * * @param name Name of the method * @param method The actual method * @param flags Optional flags @@ -392,11 +379,11 @@ public: /** * Add a static method to the class - * + * * Because a C++ static method is just a regular function, that happens to * have access to the private variables of the class at compile time, you * can register any function that matches one of the function signatures - * + * * @param name Name of the method * @param method The actual method * @param flags Optional flags @@ -409,30 +396,30 @@ public: /** * Add an abstract method to the class - * + * * @param name Name of the method * @param flags Optional flags (like public or protected) * @param args Description of the supported arguments */ - void method(const char *name, int flags=0, const Arguments &args = {}) - { + void method(const char *name, int flags=0, const Arguments &args = {}) + { // the "MethodModifiers" holds all the valid modifiers for a method: Final + Public + Protected + Private. // The "Static" and "Abstract" properties are also valid modifiers in this context (in fact, you would // expect that we could even force adding "Abstract" here, because we're adding an abstract method -- but - // in a PHP interface the "Abstract" modifier is not allowed - even though it is of course abstract. + // in a PHP interface the "Abstract" modifier is not allowed - even though it is of course abstract. // So we only _allow_ abstract here, and expect the caller to _set_ it. _methods.push_back(std::make_shared(name, (flags & (MethodModifiers | Static | Abstract)), args)); } /** * Add a property to the class - * + * * Every instance of this class will have this property. The property * can be Php::Public, Php::Protected or Php::Private (altough setting * private properties is odd as the implementation of the class is in CPP, * so why use private properties while the whole implementation is already * hidden) - * + * * @param name Name of the property * @param value Actual property value * @param flags Optional flags @@ -459,7 +446,7 @@ public: void property(const char *name, const getter_callback_1 &getter, const setter_callback_0 &setter) { _properties[name] = std::make_shared(getter,setter); } void property(const char *name, const getter_callback_0 &getter, const setter_callback_1 &setter) { _properties[name] = std::make_shared(getter,setter); } void property(const char *name, const getter_callback_1 &getter, const setter_callback_1 &setter) { _properties[name] = std::make_shared(getter,setter); } - + /** * Add an interface that is implemented * @param interface The interface that is implemented diff --git a/zend/compileroptions.h b/zend/compileroptions.h index 74ba053..c56bf78 100644 --- a/zend/compileroptions.h +++ b/zend/compileroptions.h @@ -2,7 +2,7 @@ * CompilerOptions.h * * Helper class to temporarily set compiler options - * + * * When an object is destructed, it automatically restored the previous compiler settings * * @author Emiel Bruijntjes @@ -22,10 +22,10 @@ class CompilerOptions private: /** * The original compiler options - * @var int + * @var uint32_t */ - zend_uint _original; - + uint32_t _original; + #ifdef ZTS /** * When in thread safety mode, we also keep track of the TSRM_LS var @@ -39,20 +39,20 @@ public: * Constructor * @param options */ - CompilerOptions(zend_uint options TSRMLS_DC) + CompilerOptions(uint32_t options TSRMLS_DC) { // remember the old compiler options before we set temporary compile options _original = CG(compiler_options); - + // we're going to evaluate only once CG(compiler_options) = options; - + #ifdef ZTS // copy tsrm_ls param this->tsrm_ls = tsrm_ls; #endif } - + /** * Destructor */ diff --git a/zend/constantimpl.h b/zend/constantimpl.h index 46a16d2..9de8a31 100644 --- a/zend/constantimpl.h +++ b/zend/constantimpl.h @@ -1,8 +1,8 @@ /** * ConstantImpl.h - * + * * Implementation file for the constant class - * + * * @author Emiel Bruijntjes * @copyright 2015 Copernica BV */ @@ -11,7 +11,7 @@ * Set up namespace */ namespace Php { - + /** * Class definition */ @@ -28,7 +28,7 @@ public: // initialize the zval ZVAL_NULL(&_constant.value); } - + /** * Constructor * @param name @@ -39,7 +39,7 @@ public: // initialize the zval ZVAL_BOOL(&_constant.value, value); } - + /** * Constructor * @param name @@ -72,7 +72,7 @@ public: // initialize the zval ZVAL_DOUBLE(&_constant.value, value); } - + /** * Constructor * @param name @@ -82,9 +82,9 @@ public: ConstantImpl(const char *name, const char *value, size_t len) : _name(name) { // initialize the zval - ZVAL_STRINGL(&_constant.value, value, len, 0); + ZVAL_STRINGL(&_constant.value, value, len); } - + /** * Constructor * @param name @@ -93,9 +93,9 @@ public: ConstantImpl(const char *name, const char *value) : _name(name) { // initialize the zval - ZVAL_STRINGL(&_constant.value, value, ::strlen(value), 0); + ZVAL_STRINGL(&_constant.value, value, ::strlen(value)); } - + /** * Constructor * @param name @@ -104,9 +104,9 @@ public: ConstantImpl(const char *name, const std::string &value) : _name(name) { // initialize the zval - ZVAL_STRINGL(&_constant.value, value.c_str(), value.size(), 0); + ZVAL_STRINGL(&_constant.value, value.c_str(), value.size()); } - + /** * Destructor */ @@ -121,85 +121,87 @@ public: // check the zval type switch (Z_TYPE(_constant.value)) { - case IS_NULL: + case IS_NULL: // set a null constant clss.property(_name, nullptr, Php::Const); break; - + case IS_LONG: // set a long constant (cast is necessary because php uses longs, which // have a different size on different platforms) clss.property(_name, (int64_t)Z_LVAL(_constant.value), Php::Const); break; - + case IS_DOUBLE: // set a double constant clss.property(_name, Z_DVAL(_constant.value), Php::Const); break; - - case IS_BOOL: - // set a boolean constant - clss.property(_name, Z_BVAL(_constant.value), Php::Const); + + case IS_FALSE: + // set boolean false + clss.property(_name, false, Php::Const); break; - + + case IS_TRUE: + // set boolean true + clss.property(_name, true, Php::Const); + case IS_STRING: // set a string constant clss.property(_name, std::string(Z_STRVAL(_constant.value), Z_STRLEN(_constant.value)), Php::Const); break; - + default: // this should not happen, the constant can only be constructed as one // of the above types, so it should be impossible to end up here. But // for completeness, we are going to make a copy of the zval, and convert // that to a string zval copy = _constant.value; - + // run the copy constructor to make sure zval is correctly copied zval_copy_ctor(©); - + // convert the copy to a string convert_to_string(©); - + // set as string constant clss.property(_name, std::string(Z_STRVAL(copy), Z_STRLEN(copy)), Php::Const); break; } } - + /** * Initialize the constant * @param prefix Namespace prefix * @param module_number The module number * @param tsrmls Optional parameter when running in multi-threading context */ - void initialize(const std::string &prefix, int module_number TSRMLS_DC) + void initialize(const std::string &prefix, int module_number TSRMLS_DC) { // is there a namespace name involved? - if (prefix.size() > 0) + if (!prefix.empty()) { // size of the name auto namelen = ::strlen(_name); - - // include prefix in the full name (name_len should include '\0') - _constant.name_len = prefix.size() + 1 + namelen + 1; - _constant.name = (char *)emalloc(_constant.name_len); + + // allocate memory for the full name + _constant.name = zend_string_alloc(prefix.size() + 1 + namelen, 1); // copy the entire namespace name, separator and constant name - ::strncpy(_constant.name, prefix.c_str(), prefix.size()); - ::strncpy(_constant.name + prefix.size(), "\\", 1); - ::strncpy(_constant.name + prefix.size() + 1, _name, namelen + 1); + ::strncpy(ZSTR_VAL(_constant.name), prefix.c_str(), prefix.size()); + ::strncpy(ZSTR_VAL(_constant.name) + prefix.size(), "\\", 1); + ::strncpy(ZSTR_VAL(_constant.name) + prefix.size() + 1, _name, namelen + 1); } else { - // no namespace, we simply copy the name (name_len should include '\0') - _constant.name_len = ::strlen(_name) + 1; - _constant.name = zend_strndup(_name, _constant.name_len - 1); + // no namespace, we simply copy the name + _constant.name = zend_string_init(_name, ::strlen(_name), 1); } - + // set all the other constant properties _constant.flags = CONST_CS | CONST_PERSISTENT; _constant.module_number = module_number; - + // register the zval zend_register_constant(&_constant TSRMLS_CC); } @@ -217,7 +219,7 @@ private: */ zend_constant _constant; }; - + /** * End of namespace */ diff --git a/zend/exception_handler.cpp b/zend/exception_handler.cpp index 75faef9..66034e2 100644 --- a/zend/exception_handler.cpp +++ b/zend/exception_handler.cpp @@ -31,16 +31,13 @@ Value set_exception_handler(const std::function &hand Value output; // turn our user_exception_handler into a Value so we can return the original one later on - if (EG(user_exception_handler)) output = EG(user_exception_handler); + if (!Z_ISNULL(EG(user_exception_handler))) output = &EG(user_exception_handler); // detach so we have the zval auto value = functor.detach(true); - // allocate the user_exception_handler - ALLOC_ZVAL(EG(user_exception_handler)); - // copy our zval into the user_exception_handler - MAKE_COPY_ZVAL(&value, EG(user_exception_handler)); + ZVAL_COPY(value, &EG(user_exception_handler)); // return the original handler return output; @@ -61,16 +58,13 @@ Value set_error_handler(const std::function &handler, Value output; // turn our user_error_handler into a Value if we have one, just so we can return it later on - if (EG(user_error_handler)) output = EG(user_error_handler); + if (!Z_ISNULL(EG(user_error_handler))) output = &EG(user_error_handler); // detach so we have the zval auto value = functor.detach(true); - // alocate the user_error_handler - ALLOC_ZVAL(EG(user_error_handler)); - // copy our zval into the user_error_handler - MAKE_COPY_ZVAL(&value, EG(user_error_handler)); + ZVAL_COPY(value, &EG(user_error_handler)); EG(user_error_handler_error_reporting) = (int) error; // return the original handler @@ -98,7 +92,7 @@ Value error_reporting(Error error) if (size < 0) return false; // alter the ini on the fly - zend_alter_ini_entry("error_reporting", sizeof("error_reporting"), str, size, ZEND_INI_USER, ZEND_INI_STAGE_RUNTIME); + zend_alter_ini_entry(zend_string_init("error_reporting", sizeof("error_reporting"), 1), zend_string_init(str, size, 1), ZEND_INI_USER, ZEND_INI_STAGE_RUNTIME); // return the output return output; diff --git a/zend/executestate.h b/zend/executestate.h index d71a252..edd7d5e 100644 --- a/zend/executestate.h +++ b/zend/executestate.h @@ -16,7 +16,7 @@ namespace Php { /** * Helper class to store and restore the current opcode state - * + * * When we're going to execute a set of instructions, we need to store the * current state of the Zend engine. After the instructions have been processed, * we can switch back to the original instructions @@ -29,10 +29,9 @@ private: * @var mixed */ zend_op_array *_active_op_array; - zval **_return_value_ptr_ptr; - zend_op **_opline_ptr; - int _interactive; - + zval *_return_value; + const zend_op *_opline; + /** * The new value for 'no-extensions' * @var int @@ -46,7 +45,7 @@ private: */ void ***tsrm_ls; #endif - + public: /** * No trivial constructor @@ -60,10 +59,9 @@ public: ExecuteState(int no_extensions TSRMLS_DC) { // store all the original stuff - _active_op_array = EG(active_op_array); - _return_value_ptr_ptr = EG(return_value_ptr_ptr); - _opline_ptr = EG(opline_ptr); - _interactive = CG(interactive); + _active_op_array = CG(active_op_array); + _return_value = EG(current_execute_data)->return_value; + _opline = EG(current_execute_data)->opline; _no_extensions = no_extensions; #ifdef ZTS @@ -78,11 +76,10 @@ public: virtual ~ExecuteState() { // restore all settings - CG(interactive) = _interactive; EG(no_extensions) = _no_extensions; - EG(opline_ptr) = _opline_ptr; - EG(active_op_array) = _active_op_array; - EG(return_value_ptr_ptr) = _return_value_ptr_ptr; + EG(current_execute_data)->opline = _opline; + CG(active_op_array) = _active_op_array; + EG(current_execute_data)->return_value = _return_value; } }; diff --git a/zend/hashiterator.h b/zend/hashiterator.h index 36c3d6c..357ffa6 100644 --- a/zend/hashiterator.h +++ b/zend/hashiterator.h @@ -32,14 +32,17 @@ public: HashIterator(HashTable *hashtable, bool first, bool is_array = false) : _table(hashtable), _is_array(is_array) { // reset the hash pointer to the internal position - if (hashtable && first) + if (hashtable && first) { + // we should be valid (this is undone later if necessary) + _valid = true; + // move to first position zend_hash_internal_pointer_reset_ex(_table, &_position); - + // read current data if (read()) return; - + // data was private, move on increment(); } @@ -49,7 +52,7 @@ public: invalidate(); } } - + /** * Copy constructor * @param that @@ -61,12 +64,12 @@ public: // read current position read(); } - + /** * Destructor */ - virtual ~HashIterator() {} - + virtual ~HashIterator() = default; + /** * Clone the object * @param tsrm_ls @@ -84,15 +87,18 @@ public: */ virtual bool increment() override { + // leap out if we're not even iterating over a hash table + if (!_table) return false; + // leap out if already on an invalid pos (behind the last pos) - if (!_position) return false; - + if (!_valid) return false; + // move the iterator forward if (zend_hash_move_forward_ex(_table, &_position) == SUCCESS) { // read current key and value if (read()) return true; - + // data was private or invalid, move further return increment(); } @@ -102,7 +108,7 @@ public: return invalidate(); } } - + /** * Decrement position (pre-decrement) * @return bool @@ -111,9 +117,9 @@ public: { // leap out if we're not even iterating over a hash table if (!_table) return false; - + // if position is invalid, it is one position behind the last position - if (!_position) + if (!_valid) { // move to last position zend_hash_internal_pointer_end_ex(_table, &_position); @@ -126,7 +132,7 @@ public: // read current key and value if (read()) return true; - + // data was private, move on return decrement(); } @@ -140,9 +146,9 @@ public: { // this always is a hash iterator HashIterator *other = (HashIterator *)that; - - // compare the positions - return _position == other->_position; + + // compare the tables and positions + return _table == other->_table && _position == other->_position; } /** @@ -155,6 +161,12 @@ public: } private: + /** + * Are we at a possibly valid position? + * @var bool + */ + bool _valid = false; + /** * The hash table over which is being iterated * @var HashTable @@ -165,7 +177,7 @@ private: * The position in the hash table * @var HashPosition */ - Bucket *_position = nullptr; + HashPosition _position; /** * Is a hash interator in array @@ -188,44 +200,17 @@ private: // zval to read the current key in Value key; -#if PHP_VERSION_ID >= 50500 - // read in the current key zend_hash_get_current_key_zval_ex(_table, key._val, &_position); - + // if the key is set to NULL, it means that the object is not at a valid position if (key.isNull()) return invalidate(); - -#else - - // php 5.3 and php 5.4 need a different implementation because the function - // zend_hash_get_current_key_zval_ex is missing in php 5.3, declare variables - // we need for storing the key in - char *string_key; - unsigned int str_len; - unsigned long num_key; - - // get the current key - int type = zend_hash_get_current_key_ex(_table, &string_key, &str_len, &num_key, 0, &_position); - - // if key is not found, the iterator is at an invalid position - if (type == HASH_KEY_NON_EXISTANT) return invalidate(); - - // numeric keys are the easiest ones - if (type == HASH_KEY_IS_LONG) key = (int64_t)num_key; - else key = std::string(string_key, str_len - 1); - -#endif // iterator is at a valid position, go fetch the data - // this is the variable we need for fetching the data - zval **value; - - // retrieve data - zend_hash_get_current_data_ex(_table, (void **) &value, &_position); - + auto *value = zend_hash_get_current_data_ex(_table, &_position); + // we can now update the current data - _current = std::make_pair(std::move(key), *value); + _current = std::make_pair(std::move(key), value); // if the key is private (it starts with a null character) we should return // false to report that the object is not in a completely valid state @@ -238,12 +223,12 @@ private: */ bool invalidate() { - // forget current position - _position = nullptr; - + // no longer valid + _valid = false; + // make the data a pair of null ptrs _current = std::make_pair(nullptr,nullptr); - + // done return false; } diff --git a/zend/iteratorimpl.cpp b/zend/iteratorimpl.cpp index 49526b1..9d57df7 100644 --- a/zend/iteratorimpl.cpp +++ b/zend/iteratorimpl.cpp @@ -20,7 +20,7 @@ namespace Php { */ static IteratorImpl *self(zend_object_iterator *iter) { - return (IteratorImpl *)iter->data; + return (IteratorImpl *)Z_PTR(iter->data); } /** @@ -100,14 +100,14 @@ int IteratorImpl::key(zend_object_iterator *iter, char **str_key, uint *str_key_ { // retrieve the key Value retval(self(iter)->key()); - + // is this a numeric string? if (retval.isString()) { // copy the key and the from the value *str_key = estrndup(retval.rawValue(), retval.size()); *str_key_len = retval.size() + 1; - + // done return HASH_KEY_IS_STRING; } @@ -115,7 +115,7 @@ int IteratorImpl::key(zend_object_iterator *iter, char **str_key, uint *str_key_ { // convert to a numeric *int_key = retval.numericValue(); - + // done return HASH_KEY_IS_LONG; } @@ -151,13 +151,13 @@ 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; @@ -165,13 +165,13 @@ zend_object_iterator_funcs *IteratorImpl::functions() funcs.get_current_key = &IteratorImpl::key; funcs.move_forward = &IteratorImpl::next; funcs.rewind = &IteratorImpl::rewind; - + // invalidate is not yet supported funcs.invalidate_current = nullptr; - + // remember that functions are initialized initialized = true; - + // done return &funcs; } diff --git a/zend/iteratorimpl.h b/zend/iteratorimpl.h index 0a815e2..5286309 100644 --- a/zend/iteratorimpl.h +++ b/zend/iteratorimpl.h @@ -3,9 +3,9 @@ * * Base class for iterators. Extension writers that want to create traversable * classes, should override the Php::Traversable base class. This base class - * forces you to implement a getIterator() method that returns an instance of + * forces you to implement a getIterator() method that returns an instance of * a Php::Iterator class. - * + * * In this file you find the signature of the Php::Iterator class. It mostly has * pure virtual methods, which means that you should create a derived class * that implements all these methods. @@ -32,9 +32,9 @@ private: std::unique_ptr _iterator; /** - * The current() method that is called by the Zend engine wants a - * pointer-to-pointer-to-a-zval. Because of this, we have to keep the - * current value in memory after the current() method returns because + * The current() method that is called by the Zend engine wants a + * pointer-to-pointer-to-a-zval. Because of this, we have to keep the + * current value in memory after the current() method returns because * the pointer would otherwise fall out of scope. This is (once again) * odd behavior of the Zend engine, but we'll have to live with that * @var Value @@ -61,7 +61,7 @@ private: { return _iterator->valid(); } - + /** * The value at the current position * @return Value @@ -70,7 +70,7 @@ private: { return _iterator->current(); } - + /** * The key at the current position * @return Value @@ -79,7 +79,7 @@ private: { return _iterator->key(); } - + /** * Move to the next position */ @@ -87,7 +87,7 @@ private: { return _iterator->next(); } - + /** * Rewind the iterator to the front position */ @@ -95,7 +95,7 @@ private: { return _iterator->rewind(); } - + /** * Iterator destructor method * @param iter @@ -161,14 +161,16 @@ public: * Constructor * @param iterator The iterator that is implemented by the extension */ - IteratorImpl(Iterator *iterator) : _iterator(iterator) + IteratorImpl(Iterator *iterator) : _iterator(iterator) { + // wrap it in a zval + ZVAL_PTR(&_impl.data, this); + // initialize impl object - _impl.data = this; _impl.index = 0; _impl.funcs = functions(); } - + /** * Destructor */ @@ -183,7 +185,7 @@ public: return &_impl; } }; - + /** * End namespace */ diff --git a/zend/object.cpp b/zend/object.cpp index 7da8339..0f6307e 100644 --- a/zend/object.cpp +++ b/zend/object.cpp @@ -76,13 +76,10 @@ Object::Object(zend_class_entry *entry, Base *base) : Value() // member in the base object), this is a self-destructing object that // will be destructed when the last reference to it has been removed, // we already set the reference to zero - new ObjectImpl(entry, base, 0 TSRMLS_CC); + new ObjectImpl(entry, base, ClassImpl::objectHandlers(entry) 0 TSRMLS_CC); // now we can store it operator=(Value(base)); - - // install the object handlers - Z_OBJVAL_P(_val).handlers = ClassImpl::objectHandlers(entry); } } diff --git a/zend/objectimpl.h b/zend/objectimpl.h index 110491b..b014bb1 100644 --- a/zend/objectimpl.h +++ b/zend/objectimpl.h @@ -26,33 +26,26 @@ private: */ struct MixedObject { - /** - * The actual object is the first member, so that casting - * the MixedObject to a zend_object will also result in a valid pointer - * @var zend_object - */ - zend_object php; - /** * Pointer to ourselves * @var ObjectImpl */ ObjectImpl *self; + /** + * The actual object MUST be the last member, because PHP uses hackish + * tricks for optimization (we allocate more memory than sizeof(MixedObject)) + * @var zend_object + */ + zend_object php; } *_mixed; /** * Pointer to the C++ implementation - * @var Base + * @var std::unique_ptr */ - Base *_object; - - /** - * The object handle in the Zend engine - * @var int - */ - int _handle; + std::unique_ptr _object; public: /** @@ -61,65 +54,30 @@ public: * This will create a new object in the Zend engine. * * @param entry Zend class entry + * @param handler Zend object handlers * @param base C++ object that already exists * @param refcount The initial refcount for the object * @param tsrm_ls Optional threading data */ - ObjectImpl(zend_class_entry *entry, Base *base, int refcount TSRMLS_DC) + ObjectImpl(zend_class_entry *entry, Base *base, zend_object_handlers *handlers, int refcount TSRMLS_DC) : + _object(base) { // allocate a mixed object (for some reason this does not have to be deallocated) - _mixed = (MixedObject *)emalloc(sizeof(MixedObject)); + _mixed = (MixedObject *)ecalloc(1, sizeof(MixedObject) + zend_object_properties_size(entry)); // copy properties to the mixed object _mixed->php.ce = entry; _mixed->self = this; - // store the c++ object - _object = base; - - // initialize the object - zend_object_std_init(&_mixed->php, entry TSRMLS_CC); - -#if PHP_VERSION_ID < 50399 - - // tmp variable - zval *tmp; - - // initialize the properties, php 5.3 way - zend_hash_copy(_mixed->php.properties, &entry->default_properties, (copy_ctor_func_t) zval_property_ctor, &tmp, sizeof(zval*)); - -#else - - // version higher than 5.3 have an easier way to initialize + // initialize the object and its properties + zend_object_std_init (&_mixed->php, entry TSRMLS_CC); object_properties_init(&_mixed->php, entry); -#endif - -#ifdef ZTS - - // when in thread safety mode, the destruct method and free method have - // an extra parameter holding thread information - using DestructType = void(*)(zend_object*,unsigned int,void***); - using FreeType = void(*)(zend_object*,void***); - -#else - - // not in thread mode: no special parameter for the tsrm_ls variable - using DestructType = void(*)(zend_object*, unsigned int); - using FreeType = void(*)(zend_object*); - -#endif - - // store the two destruct methods in temporary vars - DestructType destructMethod = &ClassImpl::destructObject; - FreeType freeMethod = &ClassImpl::freeObject; - - // the destructor and clone handlers are set to NULL. I dont know why, but they do not - // seem to be necessary... - _handle = zend_objects_store_put(php(), (zend_objects_store_dtor_t)destructMethod, (zend_objects_free_object_storage_t)freeMethod, NULL TSRMLS_CC); + // install the handlers + _mixed->php.handlers = handlers; // set the initial refcount (if it is different than one, because one is the default) - if (refcount != 1) EG(objects_store).object_buckets[_handle].bucket.obj.refcount = refcount; + if (refcount != 1) php()->gc.refcount = refcount; // the object may remember that we are its implementation object base->_impl = this; @@ -128,11 +86,7 @@ public: /** * Destructor */ - virtual ~ObjectImpl() - { - // deallocate the cpp object - if (_object) delete _object; - } + virtual ~ObjectImpl() = default; /** * Destruct the object @@ -140,13 +94,23 @@ public: */ void destruct(TSRMLS_D) { - // pass on to the default destructor - zend_objects_free_object_storage(php() TSRMLS_CC); - // destruct the object delete this; } + /** + * The offset between the zend_object and the ObjectImpl + * in bytes. This can be used to find the other when only + * a pointer to one is available. + * + * @return The offset in bytes + */ + static constexpr size_t offset() + { + // calculate the offset in bytes + return offsetof(MixedObject, php); + } + /** * Find the object based on a zval * @param val Zval object @@ -155,11 +119,8 @@ public: */ static ObjectImpl *find(zval *val TSRMLS_DC) { - // retrieve the old object, which we are going to copy - MixedObject *object = (MixedObject *)zend_object_store_get_object(val TSRMLS_CC); - - // done - return object->self; + // retrieve the zend_object from the zval and use it to find the ObjectImpl + return find(Z_OBJ_P(val)); } /** @@ -169,8 +130,11 @@ public: */ static ObjectImpl *find(const zend_object *object) { - // retrieve the old object, which we are going to copy - const MixedObject *mixed = (const MixedObject *)object; + // the zend_object is the last pointer in the struct so we have to subtract the + // correct number of bytes from the pointer to get at the address at which the + // actual ObjectImpl starts. to be able to actually perform this pointer arithmetic + // we must first cast the pointer to a char (void pointer arithmetic is not allowed!) + auto *mixed = (const MixedObject*)((char*)object - offset()); // done return mixed->self; @@ -182,7 +146,7 @@ public: */ Base *object() const { - return _object; + return _object.get(); } /** @@ -193,15 +157,6 @@ public: { return &_mixed->php; } - - /** - * Retrieve the handle object - * @return int - */ - int handle() const - { - return _handle; - } }; /** diff --git a/zend/opcodes.h b/zend/opcodes.h index 4e78083..1901f1f 100644 --- a/zend/opcodes.h +++ b/zend/opcodes.h @@ -29,14 +29,14 @@ public: * Constructor * @param opcodes */ - Opcodes(struct _zend_op_array *opcodes TSRMLS_DC) : _opcodes(opcodes) + Opcodes(struct _zend_op_array *opcodes TSRMLS_DC) : _opcodes(opcodes) { #ifdef ZTS // copy tsrm_ls param this->tsrm_ls = tsrm_ls; #endif } - + /** * Destructor */ @@ -44,12 +44,12 @@ public: { // leap out if opcodes were not valid if (!_opcodes) return; - + // clean up opcodes destroy_op_array(_opcodes TSRMLS_CC); efree(_opcodes); } - + /** * Are the opcodes valid? * @return bool @@ -58,7 +58,7 @@ public: { return _opcodes != nullptr; } - + /** * Execute the opcodes * @return Value @@ -69,41 +69,39 @@ public: if (!_opcodes) return nullptr; // pointer that is going to hold the return value of the script - zval *retval_ptr = nullptr; - + zval retval; + + // initialize to null + ZVAL_NULL(&retval); + // the zend engine is probably already busy processing opcodes, so we store // the current execute state before we're going to switch the runtime to // our own set of opcodes ExecuteState execState(0 TSRMLS_CC); - - // old execute state has been saved (and will automatically be restured when + + // old execute state has been saved (and will automatically be restored when // the oldstate is destructed), so we can now safely overwrite all the settings - EG(return_value_ptr_ptr) = &retval_ptr; - EG(active_op_array) = _opcodes; + CG(active_op_array) = _opcodes; EG(no_extensions) = 1; - if (!EG(active_symbol_table)) zend_rebuild_symbol_table(TSRMLS_C); - CG(interactive) = 0; - + if (!EG(current_execute_data)->symbol_table) zend_rebuild_symbol_table(TSRMLS_C); + // the current exception - zval* oldException = EG(exception); + auto *oldException = EG(exception); // execute the code - zend_execute(_opcodes TSRMLS_CC); + zend_execute(_opcodes, &retval TSRMLS_CC); - // was an exception thrown inside the eval()'ed code? In that case we + // was an exception thrown inside the eval()'ed code? In that case we // throw a C++ new exception to give the C++ code the chance to catch it - if (oldException != EG(exception) && EG(exception)) throw OrigException(EG(exception) TSRMLS_CC); + // todo: OrigException with constructor for zend_object + // if (oldException != EG(exception) && EG(exception)) throw OrigException(EG(exception) TSRMLS_CC); // we're ready if there is no return value - if (!retval_ptr) return nullptr; - + if (ZVAL_IS_NULL(&retval)) return nullptr; + // wrap the return value - Value result(retval_ptr); - - // destruct the zval (this function will decrement the reference counter, - // and only destruct if there are no other references left) - zval_ptr_dtor(&retval_ptr); - + Value result(&retval); + // copy the pointer into a value object, and return that return result; } @@ -124,7 +122,7 @@ private: #endif }; - + /** * End of namespace */ diff --git a/zend/origexception.h b/zend/origexception.h index 55f89cf..f997608 100644 --- a/zend/origexception.h +++ b/zend/origexception.h @@ -21,12 +21,12 @@ class OrigException : public Value, public Exception private: /** * Is this a an exception that was caught by extension C++ code. - * + * * When the object is initially created, we assume that it will be caught * by C++ code. If it later turns out that the PHP-CPP can catch this * exception after the extension C++ code ran, the variable is set back * to false. - * + * * @var bool */ bool _handled = true; @@ -38,40 +38,40 @@ private: */ TSRMLS_D; #endif - + public: /** * Constructor * @param val */ - OrigException(zval *val TSRMLS_DC) : - Value(val), Exception("OrigException") + OrigException(zval *val TSRMLS_DC) : + Value(val), Exception("OrigException") { #ifdef ZTS // copy tsrm_ls this->TSRMLS_C = TSRMLS_C; #endif } - + /** * Copy constructor * @param exception */ - OrigException(const OrigException &exception) : - Value(exception), Exception("OrigException"), _handled(exception._handled) + OrigException(const OrigException &exception) : + Value(exception), Exception("OrigException"), _handled(exception._handled) { #ifdef ZTS // copy tsrm_ls TSRMLS_C = exception.TSRMLS_C; #endif } - + /** * Move constructor * @param exception */ OrigException(OrigException &&exception) : - Value(std::move(exception)), Exception("OrigException"), _handled(exception._handled) + Value(std::move(exception)), Exception("OrigException"), _handled(exception._handled) { // set other exception to handled so that it wont do anything on destruction exception._handled = true; @@ -81,7 +81,7 @@ public: TSRMLS_C = exception.TSRMLS_C; #endif } - + /** * Destructor */ @@ -90,11 +90,11 @@ public: // if the exception was not handled by C++ code, we're not going to do anything // and the exception stays active if (!_handled) return; - + // the exception was handled, so we should clean it up zend_clear_exception(TSRMLS_C); } - + /** * This is _not_ a native exception, it was thrown by a PHP script * @return bool @@ -103,7 +103,7 @@ public: { return false; } - + /** * Reactivate the exception */ @@ -127,15 +127,15 @@ inline void process(Exception &exception TSRMLS_DC) // the exception is native, call the zend throw method zend_throw_exception(zend_exception_get_default(TSRMLS_C), (char *)exception.what(), 0 TSRMLS_CC); } - + // or does it have its own report function? else if (!exception.report()) { - // this is not a native exception, so it was originally thrown by a - // php script, and then not caught by the c++ of the extension, we are + // this is not a native exception, so it was originally thrown by a + // php script, and then not caught by the c++ of the extension, we are // going to tell to the exception that it is still active OrigException &orig = static_cast(exception); - + // reactive the exception orig.reactivate(); } diff --git a/zend/parametersimpl.h b/zend/parametersimpl.h index 2841c75..178f1be 100644 --- a/zend/parametersimpl.h +++ b/zend/parametersimpl.h @@ -2,7 +2,7 @@ * ParametersImpl.h * * Extended parameters class that can be instantiated - * + * * @author Emiel Bruijntjes * @copyright 2013 Copernica BV */ @@ -28,22 +28,33 @@ public: { // reserve plenty of space reserve(argc); - + + // array to store all the arguments in + zval arguments[argc]; + + // retrieve the arguments + zend_get_parameters_array_ex(argc, arguments); + // loop through the arguments for (int i=0; iget_iterator(entry, object, false TSRMLS_CC); - + // rewind the iterator _iter->funcs->rewind(_iter TSRMLS_CC); - + // read the first key/value pair read(TSRMLS_C); } - + /** * Copy constructor * @param that @@ -54,7 +54,7 @@ public: // @todo this is a broken implementation, the copy is at the start // position, while we'd like to be at the same position } - + /** * Destructor */ @@ -62,10 +62,10 @@ public: { // do nothing if iterator is already invalid if (!_iter) return; - + // we need the tsrm pointer TSRMLS_FETCH(); - + // call the iterator destructor if (_iter) _iter->funcs->dtor(_iter TSRMLS_CC); } @@ -79,7 +79,7 @@ public: { // we need the tsrm_ls variable TSRMLS_FETCH(); - + // construct iterator return new TraverseIterator(*this TSRMLS_CC); } @@ -96,17 +96,17 @@ public: // we need the tsrm_ls variable TSRMLS_FETCH(); - + // movw it forward _iter->funcs->move_forward(_iter TSRMLS_CC); - + // and read current data read(TSRMLS_C); - + // done return true; } - + /** * Decrement position (pre-decrement) * @return bool @@ -115,7 +115,7 @@ public: { // not possible with PHP iterators throw Exception("Impossible to iterate backwards"); - + // unreachable return false; } @@ -129,13 +129,13 @@ public: { // of course if the objects are identical if (this == that) return true; - + // cast to traverse-iterator TraverseIterator *other = (TraverseIterator *)that; - + // if both objects are in an invalid state we consider them to be identical if (!_iter && !other->_iter) return true; - + // although the iterators could be at the same pos, for simplicity // we consider them different here return false; @@ -156,7 +156,7 @@ private: * @var _val */ zval *_object = nullptr; - + /** * The iterator from Zend * @var zend_object_iterator @@ -183,60 +183,25 @@ private: // is the iterator at a valid position? if (_iter->funcs->valid(_iter TSRMLS_CC) == FAILURE) return invalidate(TSRMLS_C); -#if PHP_VERSION_ID >= 50500 - // create a value object Value val; - + // call the function to get the key _iter->funcs->get_current_key(_iter, val._val TSRMLS_CC); - + // store the key _data.first = val; -#else - - // variable we need for fetching the key, and that will be assigned by - // the PHP engine (this is php 5.3 code) - char *str_key; unsigned int str_key_len; unsigned long int_key; - - // php 5.4 or php 5.3 code, fetch the current key - int type = _iter->funcs->get_current_key(_iter, &str_key, &str_key_len, &int_key TSRMLS_CC); - - // what sort of key do we have? - if (type == HASH_KEY_IS_LONG) - { - // we have an int key - _data.first = (int64_t)int_key; - } - else - { - // we have a string key that is already allocated - _data.first = str_key; - - // deallocate the data - efree(str_key); - } - -#endif - - // now we're going to fetch the value, for this we need a strange zval - // it is strange that this is a pointer-to-pointer, but that is how - // the Zend engine implements this. It is going to be filled with - // a pointer to a memory address that is guaranteed to hold a valid - // zval. - zval **zval; - // get the current value - _iter->funcs->get_current_data(_iter, &zval TSRMLS_CC); - + auto *zval = _iter->funcs->get_current_data(_iter); + // wrap the zval in a value object - _data.second = Value(*zval); - + _data.second = Value(zval); + // done return true; } - + /** * Invalidate the object * @param tsrm_ls @@ -246,13 +211,13 @@ private: { // skip if already invalid if (!_iter) return false; - + // reset the iterator _iter->funcs->dtor(_iter TSRMLS_CC); - + // set back to null _iter = nullptr; - + // done return false; } -- cgit v1.2.3