diff options
36 files changed, 1097 insertions, 1367 deletions
diff --git a/include/base.h b/include/base.h index 1a1643b..023d711 100644 --- a/include/base.h +++ b/include/base.h @@ -50,7 +50,7 @@ public: /** * Virtual destructor */ - virtual ~Base() {} + virtual ~Base() = default; /** * Get access to a property by name using the [] operator diff --git a/include/file.h b/include/file.h index fff3cd0..4fab1c5 100644 --- a/include/file.h +++ b/include/file.h @@ -9,6 +9,11 @@ */ /** + * Forward declarations + */ +struct _zend_string; + +/** * Set up namespace */ namespace Php { @@ -81,15 +86,15 @@ public: private: /** * The full resolved path name - * @var const char * + * @var struct _zend_string* */ - char *_path = nullptr; + struct _zend_string *_path = nullptr; /** * The opcodes of this file - * @var Opcodes + * @var std::unique_ptr<Opcodes> */ - Opcodes *_opcodes = nullptr; + std::unique_ptr<Opcodes> _opcodes; /** * Compile the file diff --git a/include/global.h b/include/global.h index 7a66997..605aeec 100644 --- a/include/global.h +++ b/include/global.h @@ -9,9 +9,10 @@ */ /** - * Forward definitions + * Forward declarations */ struct _zval_struct; +struct _zend_string; /** * Namespace @@ -34,12 +35,12 @@ public: * Move constructor * @param global */ - Global(Global &&global) _NOEXCEPT : Value(std::move(global)), _name(std::move(global._name)), _exists(global._exists) {} + Global(Global &&global) _NOEXCEPT; /** * Destructor */ - virtual ~Global() {} + virtual ~Global(); /** * Assignment operator @@ -143,35 +144,36 @@ protected: private: /** * Constructor for non-existing var - * @param name + * + * @param name Name for the variable that does not exist */ - Global(const char *name) : Value(), _name(name), _exists(false) {} + Global(const char *name); /** * Alternative constructor for non-existing var * @param name */ - Global(const std::string &name) : Value(), _name(name), _exists(false) {} + Global(const std::string &name); /** * Constructor to wrap zval for existing global bar * @param name * @param val */ - Global(const char *name, struct _zval_struct *val) : Value(val, true), _name(name), _exists(true) {} + Global(const char *name, struct _zval_struct *val); /** * Alternative constructor to wrap zval * @param name * @param val */ - Global(const std::string &name, struct _zval_struct *val) : Value(val, true), _name(name), _exists(true) {} + Global(const std::string &name, struct _zval_struct *val); /** * Name of the variable - * @var string + * @var struct _zend_string* */ - std::string _name; + struct _zend_string *_name; /** * Does it already exist? diff --git a/include/ini.h b/include/ini.h index 592fba3..a1899a4 100644 --- a/include/ini.h +++ b/include/ini.h @@ -47,68 +47,46 @@ public: * * @param name Name of the php.ini variable * @param value Default value - * @param orig Original value (if the user resets the variable, it is set back to this value) * @param place Place where the ini setting can be changed */ - Ini(const char *name, const char *value, const char *orig, const Place place = Place::All) : - _name(name), _value(value), _orig(orig), _place(place) {} - Ini(const char *name, const char *value, const Place place = Place::All) : - _name(name), _value(value), _orig_empty(true), _place(place) {} + _name(name), _value(value), _place(place) {} /** * Constructors for bool values * * @param name Name of the php.ini variable * @param value Default value - * @param orig Original value (if the user resets the variable, it is set back to this value) * @param place Place where the ini setting can be changed */ - Ini(const char *name,const bool value, const bool orig, const Place place = Place::All) : - _name(name), _value(bool2str(value)), _orig(bool2str(orig)), _place(place) {} - - Ini(const char *name, const bool value, const Place place = Place::All) : - _name(name), _value(bool2str(value)), _orig_empty(true), _place(place) {} + Ini(const char *name, bool value, const Place place = Place::All) : + _name(name), _value(bool2str(value)), _place(place) {} /** * Constructors for integer values * * @param name Name of the php.ini variable * @param value Default value - * @param orig Original value (if the user resets the variable, it is set back to this value) * @param place Place where the ini setting can be changed */ - Ini(const char *name, const int16_t value, const int16_t orig, const Place place = Place::All) : - _name(name), _value(std::to_string(value)), _orig(std::to_string(orig)), _place(place) {} - Ini(const char *name, const int16_t value, const Place place = Place::All) : - _name(name), _value(std::to_string(value)), _orig_empty(true), _place(place) {} - - Ini(const char *name, const int32_t value, const int32_t orig, const Place place = Place::All) : - _name(name), _value(std::to_string(value)), _orig(std::to_string(orig)), _place(place) {} + _name(name), _value(std::to_string(value)), _place(place) {} Ini(const char *name, const int32_t value, const Place place = Place::All) : - _name(name), _value(std::to_string(value)), _orig_empty(true), _place(place) {} - - Ini(const char *name, const int64_t value, const int64_t orig, const Place place = Place::All) : - _name(name), _value(std::to_string(value)), _orig(std::to_string(orig)), _place(place) {} + _name(name), _value(std::to_string(value)), _place(place) {} Ini(const char *name, const int64_t value, const Place place = Place::All) : - _name(name), _value(std::to_string(value)), _orig_empty(true), _place(place) {} + _name(name), _value(std::to_string(value)), _place(place) {} /** * Constructors for floating point values * * @param name Name of the php.ini variable * @param value Default value - * @param orig Original value (if the user resets the variable, it is set back to this value) * @param place Place where the ini setting can be changed */ - Ini(const char *name, const double value, const double orig, const Place place = Place::All) : - _name(name), _value(std::to_string(value)), _orig(std::to_string(orig)), _place(place) {} - Ini(const char *name, const double value, const Place place = Place::All) : - _name(name), _value(std::to_string(value)), _orig_empty(true), _place(place) {} + _name(name), _value(std::to_string(value)), _place(place) {} /** @@ -116,7 +94,7 @@ public: * @param ini_entry * @param module_number */ - void fill(struct _zend_ini_entry *ini_entry, int module_number); + void fill(struct _zend_ini_entry_def *ini_entry, int module_number); private: @@ -148,18 +126,6 @@ private: std::string _value; /** - * ini entry original value - * @var std::string - */ - std::string _orig; - - /** - * Is the orig value set or empty? - * @var bool - */ - bool _orig_empty = false; - - /** * Place where the configuration can be changed * @var Place */ 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/include/type.h b/include/type.h index 151919c..fbffb1f 100644 --- a/include/type.h +++ b/include/type.h @@ -18,17 +18,23 @@ namespace Php { * The values are the same as the ones used internally in Zend */ enum class PHPCPP_EXPORT Type : unsigned char { - Null = 0, // Null will allow any type - Numeric = 1, - Float = 2, - Bool = 3, - Array = 4, - Object = 5, - String = 6, - Resource = 7, - Constant = 8, - ConstantArray = 9, - Callable = 10 + Undefined = 0, // Variable is not set + Null = 1, // Null will allow any type + False = 2, // Boolean false + True = 3, // Boolean true + Numeric = 4, // Integer type + Float = 5, // Floating point type + String = 6, // A string obviously + Array = 7, // An array of things + Object = 8, // An object + Resource = 9, // A resource + Reference = 10, // Reference to another value (can be any type!) + Constant = 11, // A constant value + ConstantAST = 12, // I think an Abstract Syntax tree, not quite sure + + // "fake types", not quite sure what that means + Bool = 13, // You will never get this back as a type + Callable = 14, // I don't know why this is a "fake" type }; /** diff --git a/include/value.h b/include/value.h index 8c10bd8..afd532d 100644 --- a/include/value.h +++ b/include/value.h @@ -382,7 +382,7 @@ public: */ bool isNull() const { return type() == Type::Null; } bool isNumeric() const { return type() == Type::Numeric; } - bool isBool() const { return type() == Type::Bool; } + bool isBool() const { return type() == Type::False || type() == Type::True; } bool isString() const { return type() == Type::String; } bool isFloat() const { return type() == Type::Float; } bool isObject() const { return type() == Type::Object; } @@ -403,18 +403,6 @@ public: char *buffer() const; /** - * Resize buffer space. If you want to write directly to the buffer (which - * is returned by the buffer() method), you should first reserve enough - * space in it. This can be done with this reserve() method. This will also - * turn the Value object into a string (if it was not already a string). - * The writable buffer is returned. - * - * @param size - * @return char* - */ - char *reserve(size_t size); - - /** * Get access to the raw buffer for read operations. Note that this * only works for string variables - other variables return nullptr. * @@ -977,14 +965,9 @@ public: { // store arguments Value vargs[] = { static_cast<Value>(args)... }; - //Value vargs[] = { std::forward<Value>(args)... }; - - // array of parameters - _zval_struct **params[sizeof...(Args)]; - for(unsigned i=0; i < sizeof...(Args); i++) {params[i] = &vargs[i]._val;} // call the function - return exec(sizeof...(Args), params); + return exec(sizeof...(Args), vargs); } /** @@ -1021,12 +1004,8 @@ public: // store arguments Value vargs[] = { static_cast<Value>(args)... }; - // array of parameters - _zval_struct **params[sizeof...(Args)]; - for(unsigned i=0; i < sizeof...(Args); i++) {params[i] = &vargs[i]._val;} - // call the function - return exec(name, sizeof...(Args), params); + return exec(name, sizeof...(Args), vargs); } template <typename ...Args> @@ -1035,12 +1014,8 @@ public: // store arguments Value vargs[] = { static_cast<Value>(args)... }; - // array of parameters - _zval_struct **params[sizeof...(Args)]; - for(unsigned i=0; i < sizeof...(Args); i++) {params[i] = &vargs[i]._val;} - // call the function - return exec(name, sizeof...(Args), params); + return exec(name, sizeof...(Args), vargs); } /** @@ -1116,7 +1091,7 @@ private: * @param argv The parameters * @return Value */ - Value exec(int argc, struct _zval_struct ***params) const; + Value exec(int argc, Value *argv) const; /** * Call method with a number of parameters @@ -1125,8 +1100,8 @@ private: * @param argv The parameters * @return Value */ - Value exec(const char *name, int argc, struct _zval_struct ***params) const; - Value exec(const char *name, int argc, struct _zval_struct ***params); + Value exec(const char *name, int argc, Value *argv) const; + Value exec(const char *name, int argc, Value *argv); /** * Refcount - the number of references to the value @@ -1137,9 +1112,9 @@ private: protected: /** * The wrapped zval - * @var struct zval + * @var struct zval* */ - struct _zval_struct *_val; + struct _zval_struct *_val = nullptr; /** * Detach the zval diff --git a/zend/callable.cpp b/zend/callable.cpp index f0dc3e8..cf0dd64 100644 --- a/zend/callable.cpp +++ b/zend/callable.cpp @@ -14,6 +14,16 @@ namespace Php { /** + * Map function names to their implementation + * + * This is braindead, there should be a way to get this information + * from the "This" zval in the execute_data, I just can't find it + * @todo Find a better way for this + * @var std::map<std::string, Callable*> + */ +static std::map<std::string, Callable*> callables; + +/** * Function that is called by the Zend engine every time that a function gets called * @param ht * @param return_value @@ -27,9 +37,9 @@ void Callable::invoke(INTERNAL_FUNCTION_PARAMETERS) { // find the function name const char *name = get_active_function_name(TSRMLS_C); - - // uncover the hidden pointer inside the function name - Callable *callable = HiddenPointer<Callable>(name); + + // retrieve the callable from the map + auto *callable = callables.find(name)->second; // check if sufficient parameters were passed (for some reason this check // is not done by Zend, so we do it here ourselves) @@ -44,7 +54,7 @@ void Callable::invoke(INTERNAL_FUNCTION_PARAMETERS) else { // construct parameters - ParametersImpl params(this_ptr, ZEND_NUM_ARGS() TSRMLS_CC); + ParametersImpl params(getThis(), ZEND_NUM_ARGS() TSRMLS_CC); // the function could throw an exception try @@ -52,11 +62,6 @@ void Callable::invoke(INTERNAL_FUNCTION_PARAMETERS) // get the result Value result(callable->invoke(params)); - // we're ready if the return value is not even used - if (!return_value_used) return; - - // @todo php 5.6 has a RETVAL_ZVAL_FAST macro that can be used instead (and is faster) - // return a full copy of the zval, and do not destruct it RETVAL_ZVAL(result._val, 1, 0); } @@ -70,25 +75,28 @@ void Callable::invoke(INTERNAL_FUNCTION_PARAMETERS) /** * Fill a function entry - * - * This method is called when the extension is registering itself, when the - * function or method introces himself - * + * + * This method is called when the extension is registering itself, when the + * function or method introduces himself + * * @param entry Entry to be filled * @param classname Optional class name * @param flags Is this a public property? */ void Callable::initialize(zend_function_entry *entry, const char *classname, int flags) const { + // track the callable + callables[_name] = const_cast<Callable*>(this); + // fill the members of the entity, and hide a pointer to the current object in the name - entry->fname = (const char *)_ptr; + entry->fname = _name.data(); entry->handler = &Callable::invoke; - entry->arg_info = _argv; + entry->arg_info = _argv.get(); entry->num_args = _argc; entry->flags = flags; // we should fill the first argument as well - initialize((zend_arg_info *)entry->arg_info, classname); + initialize((zend_internal_function_info*)_argv.get(), classname); } /** @@ -96,51 +104,23 @@ void Callable::initialize(zend_function_entry *entry, const char *classname, int * @param info Info to be filled * @param classname Optional classname */ -void Callable::initialize(zend_arg_info *info, const char *classname) const +void Callable::initialize(zend_internal_function_info *info, const char *classname) const { -#if PHP_VERSION_ID >= 50400 - // up until php 5.3, the first info object is filled with alternative information, - // later it is casted to a zend_internal_function object - auto *finfo = (zend_internal_function_info *)info; - - // fill in all the members, note that return reference is false by default, - // because we do not support returning references in PHP-CPP, although Zend - // engine allows it. Inside the name we hide a pointer to the current object - finfo->_name = _ptr; - finfo->_name_len = ::strlen(_ptr); - finfo->_class_name = classname; + // store the classname + info->class_name = classname; // number of required arguments, and the expected return type - finfo->required_num_args = _required; - finfo->_type_hint = (unsigned char)_return; + info->required_num_args = _required; + info->type_hint = (unsigned char)_return; // we do not support return-by-reference - finfo->return_reference = false; - -# if PHP_VERSION_ID >= 50600 + info->return_reference = false; + // since php 5.6 there are _allow_null and _is_variadic properties. It's // not exactly clear what they do (@todo find this out) so for now we set // them to false - finfo->_allow_null = false; - finfo->_is_variadic = false; - -# else - // passing by reference is not used (only for php 5.4 and php 5.5) - finfo->pass_rest_by_reference = false; -# endif - -#else - // php 5.3 code - info->name = nullptr; - info->name_len = 0; - info->class_name = nullptr; - info->class_name_len = 0; - info->array_type_hint = false; info->allow_null = false; - info->pass_by_reference = false; - info->return_reference = false; - info->required_num_args = _required; -#endif + info->_is_variadic = false; } /** diff --git a/zend/callable.h b/zend/callable.h index bd74f27..f6a172f 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 <emiel.bruijntjes@copernica.com> @@ -9,11 +9,16 @@ */ /** + * Dependencies + */ +#include <cstring> + +/** * Set up namespace */ namespace Php { -/** +/** * Class definition */ class Callable @@ -24,68 +29,62 @@ public: * @param name Function or method name * @param arguments Information about the arguments */ - Callable(const char *name, const Arguments &arguments = {}) : _ptr(this, name) + Callable(const char *name, const Arguments &arguments = {}) : + _name(name), + _argc(arguments.size()), + _argv(new zend_internal_arg_info[_argc + 1]) { - // 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++) + for (auto &argument : arguments) { // increment counter with number of required parameters - if (it->required()) _required++; - + if (argument.required()) _required++; + // fill the arg info - fill(&_argv[i++], *it); + fill(&_argv[i++], argument); } } - + /** * Copy constructor * @param that */ Callable(const Callable &that) : - _ptr(that._ptr), + _name(that._name), _return(that._return), _required(that._required), _argc(that._argc), _argv(nullptr) {} - + // @todo: we have no arguments after copy? is this correct? + // we do have the argument count though... + /** * Move constructor * @param that */ Callable(Callable &&that) : - _ptr(std::move(that._ptr)), + _name(std::move(that._name)), _return(that._return), _required(that._required), _argc(that._argc), - _argv(that._argv) - { - // invalidate other object - that._argv = nullptr; - } + _argv(std::move(that._argv)) {} /** * Destructor */ - virtual ~Callable() - { - if (_argv) delete[] _argv; - } - + virtual ~Callable() = default; + /** * 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 @@ -101,27 +100,27 @@ public: * @param ns Active namespace * @param classname Optional class name */ - void initialize(zend_arg_info *info, const char *classname = nullptr) const; + void initialize(zend_internal_function_info *info, const char *classname = nullptr) const; protected: /** - * Hidden pointer to the name and the function - * @var HiddenPointer + * Name of the function + * @var std::string */ - HiddenPointer<Callable> _ptr; + std::string _name; /** * Suggestion for the return type * @var Type */ - Type _return = Type::Null; + Type _return = Type::Undefined; /** * Required number of arguments - * @var integer + * @var unsigned integer */ - int _required = 0; + unsigned int _required = 0; /** * Total number of arguments @@ -131,22 +130,23 @@ protected: /** * The arguments - * @var zend_arg_info[] + * @var std::unique_ptr<zend_internal_arg_info[]> */ - zend_arg_info *_argv = nullptr; - + std::unique_ptr<zend_internal_arg_info[]> _argv; + /** * Private helper method to fill an argument object * @param info object from the zend engine * @param arg original object */ - void fill(zend_arg_info *info, const Argument &arg) const + void fill(zend_internal_arg_info *info, const Argument &arg) const { // fill members info->name = arg.name(); - info->name_len = ::strlen(arg.name()); -#if PHP_VERSION_ID >= 50400 + // are we filling an object + if (arg.type() == Type::Object) info->class_name = arg.classname(); + 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 +155,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..75191f5 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -7,6 +7,7 @@ * @copyright 2014 Copernica BV */ #include "includes.h" +#include <cstring> /** * Set up namespace @@ -19,12 +20,10 @@ namespace Php { ClassImpl::~ClassImpl() { // destruct the entries - if (_entries) delete[] _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 + if (_self) zend_string_release(_self); } /** @@ -38,8 +37,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 @@ -58,37 +55,18 @@ static ClassImpl *self(zend_class_entry *entry) * 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->info.user.doc_comment == nullptr || entry->info.user.doc_comment_len > 0)) + while (entry->parent && (entry->info.user.doc_comment == nullptr || ZSTR_LEN(entry->info.user.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 = entry->info.user.doc_comment; + const char *comment = ZSTR_VAL(entry->info.user.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)); -#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 } /** @@ -113,13 +91,11 @@ struct CallData void ClassImpl::callMethod(INTERNAL_FUNCTION_PARAMETERS) { // retrieve the originally called (and by us allocated) function object - // (this was copied from the zend engine source code, code looks way too - // ugly to be made by me) - CallData *data = (CallData *)EG(current_execute_data)->function_state.function; + auto *data = (CallData *)execute_data->func; zend_internal_function *func = &data->func; // retrieve the function name - const char *name = func->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 @@ -134,7 +110,7 @@ void ClassImpl::callMethod(INTERNAL_FUNCTION_PARAMETERS) Value result(return_value, true); // construct parameters - ParametersImpl params(this_ptr, ZEND_NUM_ARGS() TSRMLS_CC); + ParametersImpl params(getThis(), ZEND_NUM_ARGS() TSRMLS_CC); // retrieve the base object Base *base = params.object(); @@ -162,9 +138,7 @@ void ClassImpl::callMethod(INTERNAL_FUNCTION_PARAMETERS) void ClassImpl::callInvoke(INTERNAL_FUNCTION_PARAMETERS) { // retrieve the originally called (and by us allocated) function object - // (this was copied from the zend engine source code, code looks way too - // ugly to be made by me) - CallData *data = (CallData *)EG(current_execute_data)->function_state.function; + auto *data = (CallData *)execute_data->func; // get self reference ClassBase *meta = data->self->_base; @@ -181,7 +155,7 @@ void ClassImpl::callInvoke(INTERNAL_FUNCTION_PARAMETERS) Value result(return_value, true); // construct parameters - ParametersImpl params(this_ptr, ZEND_NUM_ARGS() TSRMLS_CC); + ParametersImpl params(getThis(), ZEND_NUM_ARGS() TSRMLS_CC); // retrieve the base object Base *base = params.object(); @@ -203,17 +177,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 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(zend_object **object, 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,17 +195,13 @@ 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, method, key TSRMLS_CC); // did the default implementation do anything? if (defaultFunction) return defaultFunction; // retrieve the class entry linked to this object - auto *entry = zend_get_class_entry(*object_ptr TSRMLS_CC); + 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 @@ -254,7 +221,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 +233,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 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, nullptr TSRMLS_CC); // did the default implementation do anything? if (defaultFunction) return defaultFunction; @@ -318,7 +282,7 @@ zend_function *ClassImpl::getStaticMethod(zend_class_entry *entry, char* method, * @param tsrm_ls * @return int */ -int ClassImpl::getClosure(zval *object, zend_class_entry **entry_ptr, zend_function **func, zval **object_ptr TSRMLS_DC) +int ClassImpl::getClosure(zval *object, zend_class_entry **entry_ptr, zend_function **func, zend_object **object_ptr TSRMLS_DC) { // 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 @@ -327,9 +291,6 @@ int ClassImpl::getClosure(zval *object, zend_class_entry **entry_ptr, zend_funct // to fill the function parameter with all information about the invoke() // method that is going to get called - // retrieve the class entry linked to this object - auto *entry = zend_get_class_entry(object TSRMLS_CC); - // 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)); @@ -342,12 +303,12 @@ int ClassImpl::getClosure(zval *object, zend_class_entry **entry_ptr, zend_funct function->arg_info = nullptr; function->num_args = 0; function->required_num_args = 0; - function->scope = entry; + function->scope = *entry_ptr; function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; function->function_name = nullptr; // store pointer to ourselves - data->self = self(entry); + data->self = self(*entry_ptr); // assign this dynamically allocated variable to the func parameter // the cast is ok, because zend_internal_function is a member of the @@ -356,7 +317,7 @@ int ClassImpl::getClosure(zval *object, zend_class_entry **entry_ptr, zend_funct // 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) - *object_ptr = object; + *object_ptr = Z_OBJ_P(object); // done return SUCCESS; @@ -397,12 +358,20 @@ zend_object_handlers *ClassImpl::objectHandlers() _handlers.get_method = &ClassImpl::getMethod; _handlers.get_closure = &ClassImpl::getClosure; + // 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; // method to compare two objects _handlers.compare_objects = &ClassImpl::compare; + // 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; @@ -433,10 +402,10 @@ int ClassImpl::compare(zval *val1, zval *val2 TSRMLS_DC) try { // retrieve the class entry linked to this object - auto *entry = zend_get_class_entry(val1 TSRMLS_CC); + auto *entry = Z_OBJCE_P(val1); // other object must be of the same type - if (entry != zend_get_class_entry(val2 TSRMLS_CC)) throw NotImplemented(); + if (entry != Z_OBJCE_P(val2)) throw NotImplemented(); // we need the C++ class meta-information object ClassBase *meta = self(entry)->_base; @@ -481,7 +450,7 @@ int ClassImpl::cast(zval *val, zval *retval, int type TSRMLS_DC) Base *object = ObjectImpl::find(val TSRMLS_CC)->object(); // retrieve the class entry linked to this object - auto *entry = zend_get_class_entry(val TSRMLS_CC); + auto *entry = Z_OBJCE_P(val); // we need the C++ class meta-information object ClassBase *meta = self(entry)->_base; @@ -489,7 +458,10 @@ int ClassImpl::cast(zval *val, zval *retval, int type TSRMLS_DC) // retval is not yet initialized --- and again feelings of disbelief, // frustration, wonder and anger come up when you see that there are not two // functions in the Zend engine that have a comparable API - INIT_PZVAL(retval); + // + // this function was removed, because it was supposedly no longer necessary + // can we get away with removing it here too? + // INIT_PZVAL(retval); // when the magic function it not implemented, an exception will be thrown, // and the extension may throw a Php::Exception @@ -539,13 +511,14 @@ int ClassImpl::cast(zval *val, zval *retval, int type TSRMLS_DC) /** * Function that is called to create space for a cloned object - * @param val The object to be cloned - * @return zend_obejct_value The object to be created + * + * @param val The object to be cloned + * @return zend_object 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); + auto *entry = Z_OBJCE_P(val); // we need the C++ class meta-information object ClassImpl *impl = self(entry); @@ -564,27 +537,18 @@ 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) - zend_objects_clone_members(new_object->php(), result, old_object->php(), Z_OBJ_HANDLE_P(val) TSRMLS_CC); + zend_objects_clone_members(new_object->php(), old_object->php() TSRMLS_CC); // was a custom clone method installed? If not we call the magic c++ __clone method if (!entry->clone) meta->callClone(cpp); // done - return result; + return new_object->php(); } /** @@ -642,10 +606,11 @@ int ClassImpl::countElements(zval *object, long *count TSRMLS_DC) * @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 * @param tsrm_ls * @return zval */ -zval *ClassImpl::readDimension(zval *object, zval *offset, int type TSRMLS_DC) +zval *ClassImpl::readDimension(zval *object, zval *offset, int type, zval *rv TSRMLS_DC) { // what to do with the type? // @@ -675,7 +640,7 @@ zval *ClassImpl::readDimension(zval *object, zval *offset, int type TSRMLS_DC) try { // ArrayAccess is implemented, call function - return toZval(arrayaccess->offsetGet(offset), type); + return toZval(arrayaccess->offsetGet(offset), type, rv); } catch (Exception &exception) { @@ -692,7 +657,7 @@ zval *ClassImpl::readDimension(zval *object, zval *offset, int type TSRMLS_DC) if (!std_object_handlers.read_dimension) return nullptr; // call default - return std_object_handlers.read_dimension(object, offset, type TSRMLS_CC); + return std_object_handlers.read_dimension(object, offset, type, rv TSRMLS_CC); } } @@ -832,42 +797,66 @@ void ClassImpl::unsetDimension(zval *object, zval *member TSRMLS_DC) /** * Helper method to turn a property into a zval - * @param value - * @param type - * @return Value + * + * @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 *ClassImpl::toZval(Value &&value, int type, zval *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 - if (type == 0) return value.detach(false); - - // this is a more complicated read operation, the scripts wants to get - // deeper access to the returned value. This, however, is only possible - // if the value has more than once reference (if it has a refcount of one, - // the value object that we have here is the only instance of the zval, - // and it is simply impossible to return a reference or so - if (value.refcount() <= 1) return value.detach(false); - - // we're dealing with an editable zval, return a reference variable - return Value(value.detach(false), true).detach(false); + // the result zval that needs to be copied over + zval *result = nullptr; + + /** + * 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) + { + // first retrieve the value so we can copy it + result = value.detach(false); + } + // 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(false); + } + + // now copy the value over to the pointer + ZVAL_COPY_VALUE(rv, result); + + // if the zval has a reference count we must decrease it + Z_TRY_DELREF_P(result); + + // the pointer from the value may now be destroyed + // (it was allocated by the value and detached) + // we do not actually "destroy" the value here, + // even if the refcount reaches 0 here! + delete result; + + // return the pointer to the value + return rv; } /** * 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 Pointer to where to store the data * @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? // @@ -890,7 +879,7 @@ zval *ClassImpl::readProperty(zval *object, zval *name, int type, const zend_lit Base *base = ObjectImpl::find(object TSRMLS_CC)->object(); // retrieve the class entry linked to this object - auto *entry = zend_get_class_entry(object TSRMLS_CC); + auto *entry = Z_OBJCE_P(object); // we need the C++ class meta-information object ClassImpl *impl = self(entry); @@ -910,12 +899,12 @@ zval *ClassImpl::readProperty(zval *object, zval *name, int type, const zend_lit if (iter == impl->_properties.end()) { // retrieve value from the __get method - return toZval(meta->callGet(base, key), type); + return toZval(meta->callGet(base, key), type, rv); } else { // get the value - return toZval(iter->second->get(base), type); + return toZval(iter->second->get(base), type, rv); } } catch (const NotImplemented &exception) @@ -924,11 +913,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 + return std_object_handlers.read_property(object, name, type, cache_slot, rv TSRMLS_CC); } catch (Exception &exception) { @@ -950,21 +935,17 @@ 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(); // retrieve the class entry linked to this object - auto *entry = zend_get_class_entry(object TSRMLS_CC); + auto *entry = Z_OBJCE_P(object); // we need the C++ class meta-information object ClassImpl *impl = self(entry); @@ -1001,11 +982,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 + std_object_handlers.write_property(object, name, value, cache_slot TSRMLS_CC); } catch (Exception &exception) { @@ -1031,15 +1008,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 @@ -1049,7 +1022,7 @@ int ClassImpl::hasProperty(zval *object, zval *name, int has_set_exists, const z Base *base = ObjectImpl::find(object TSRMLS_CC)->object(); // retrieve the class entry linked to this object - auto *entry = zend_get_class_entry(object TSRMLS_CC); + auto *entry = Z_OBJCE_P(object); // we need the C++ class meta-information object ClassImpl *impl = self(entry); @@ -1082,11 +1055,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 + return std_object_handlers.has_property(object, name, has_set_exists, cache_slot TSRMLS_CC); } catch (Exception &exception) { @@ -1106,21 +1075,17 @@ 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 try { // retrieve the class entry linked to this object - auto *entry = zend_get_class_entry(object TSRMLS_CC); + auto *entry = Z_OBJCE_P(object); // we need the C++ class meta-information object ClassImpl *impl = self(entry); @@ -1143,11 +1108,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 + std_object_handlers.unset_property(object, member, cache_slot TSRMLS_CC); } catch (Exception &exception) { @@ -1161,10 +1122,9 @@ void ClassImpl::unsetProperty(zval *object, zval *member, const zend_literal *ke * Function that is called when an object is about to be destructed * This will call the magic __destruct method * @param object - * @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); @@ -1181,7 +1141,7 @@ void ClassImpl::destructObject(zend_object *object, zend_object_handle handle TS catch (const NotImplemented &exception) { // fallback on the default destructor call - zend_objects_destroy_object(object, handle TSRMLS_CC); + zend_objects_destroy_object(object TSRMLS_CC); } catch (Exception &exception) { @@ -1212,7 +1172,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 +1185,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->php(); } /** @@ -1288,7 +1239,7 @@ zend_object_iterator *ClassImpl::getIterator(zend_class_entry *entry, zval *obje * @param tsrm_ls * @return int */ -int ClassImpl::serialize(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC) +int ClassImpl::serialize(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data TSRMLS_DC) { // get the serializable object Serializable *serializable = dynamic_cast<Serializable*>(ObjectImpl::find(object TSRMLS_CC)->object()); @@ -1328,13 +1279,13 @@ int ClassImpl::serialize(zval *object, unsigned char **buffer, zend_uint *buf_le * @param tsrm_ls * @return int */ -int ClassImpl::unserialize(zval **object, zend_class_entry *entry, const unsigned char *buffer, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC) +int ClassImpl::unserialize(zval *object, zend_class_entry *entry, const unsigned char *buffer, size_t buf_len, zend_unserialize_data *data TSRMLS_DC) { // create the PHP object - object_init_ex(*object, entry); + object_init_ex(object, entry); // turn this into a serializale - Serializable *serializable = dynamic_cast<Serializable*>(ObjectImpl::find(*object TSRMLS_CC)->object()); + Serializable *serializable = dynamic_cast<Serializable*>(ObjectImpl::find(object TSRMLS_CC)->object()); // user may throw an exception in the serialize() function try @@ -1390,7 +1341,7 @@ const struct _zend_function_entry *ClassImpl::entries() zend_function_entry *last = &_entries[i]; // all should be set to zero - memset(last, 0, sizeof(zend_function_entry)); + memset(last, 0, sizeof(*last)); // done return _entries; @@ -1448,7 +1399,7 @@ zend_class_entry *ClassImpl::initialize(ClassBase *base, const std::string &pref if (_parent->_entry) { // register the class - _entry = zend_register_internal_class_ex(&entry, _parent->_entry, const_cast<char*>(_parent->name().c_str()) TSRMLS_CC); + _entry = zend_register_internal_class_ex(&entry, _parent->_entry TSRMLS_CC); } else { @@ -1478,29 +1429,15 @@ 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 *)); + // allocate memory for the doc_comment (which we abuse for storing a pointer to ourselves) + _self = zend_string_alloc(sizeof(this), 1); - // Copy the pointer after it - memcpy(_entry->name + _entry->name_length + 1, &impl, sizeof(ClassImpl *)); + // make the string appear empty + ZSTR_VAL(_self)[0] = '\0'; + ZSTR_LEN(_self) = 0; -#endif + // copy over the 'this'-pointer after the null-character + std::memcpy(ZSTR_VAL(_self) + 1, &impl, sizeof(impl)); // set access types flags for class _entry->ce_flags = (int)_type; diff --git a/zend/classimpl.h b/zend/classimpl.h index 18802e3..370fa81 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<std::shared_ptr<Method>> _methods; - + /** * All class members (class properties) * @var std::list */ std::list<std::shared_ptr<Member>> _members; - + /** * Map of dynamically accessible properties * @var std::map @@ -72,19 +72,19 @@ private: * @var std::list */ std::list<std::shared_ptr<ClassImpl>> _interfaces; - + /** * The parent/base class * @var std::shared_ptr */ std::shared_ptr<ClassImpl> _parent; - + /** * The object handlers for instances of this class * @var zend_object_handlers */ zend_object_handlers _handlers; - + /** * Are the handlers already initialized? * @var bool @@ -93,27 +93,28 @@ private: /** * Memory allocated by this object to hide a pointer - * @var char* + * @var zend_string* */ - char *_self = nullptr; + zend_string *_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[] */ const zend_function_entry *entries(); /** * Helper method to turn a property into a zval - * @param value - * @param type - * @return Value + * + * @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) */ - static zval *toZval(Value &&value, int type); + static zval *toZval(Value &&value, int type, zval *rv); public: /** @@ -155,16 +156,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 +175,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); /** @@ -190,12 +191,12 @@ public: * @param return_value_used Is the return value used or not? * @param tsrm_ls */ - static void callMethod(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC); - static void callInvoke(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC); + static void callMethod(zend_execute_data *execute_data, zval *return_value TSRMLS_DC); + static void callInvoke(zend_execute_data *execute_data, zval *return_value TSRMLS_DC); /** * 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 @@ -210,10 +211,11 @@ public: * @param offset The name of the property * @param value The new value * @param type The type of the variable??? + * @param rv Pointer to where to store the data * @param check_empty ???? * @return zval */ - static zval *readDimension(zval *object, zval *offset, int type TSRMLS_DC); + static zval *readDimension(zval *object, zval *offset, int type, zval *rv TSRMLS_DC); static void writeDimension(zval *object, zval *offset, zval *value TSRMLS_DC); static int hasDimension(zval *object, zval *offset, int check_empty TSRMLS_DC); static void unsetDimension(zval *object, zval *offset TSRMLS_DC); @@ -230,7 +232,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 +245,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 Pointer to where to store the data * @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 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(zend_object **object, 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 TSRMLS_DC); /** * Method that returns information about the __invoke() method @@ -332,7 +321,7 @@ public: * @param tsrm_ls * @return int */ - static int getClosure(zval *object, zend_class_entry **entry, zend_function **func, zval **object_ptr TSRMLS_DC); + static int getClosure(zval *object, zend_class_entry **entry, zend_function **func, zend_object **object_ptr TSRMLS_DC); /** * Function to cast the object to a different type @@ -363,19 +352,19 @@ public: * @param tsrm_ls * @return int */ - 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); - + static int serialize(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data TSRMLS_DC); + static int unserialize(zval *object, zend_class_entry *entry, const unsigned char *buffer, size_t 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 +381,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 +398,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<Method>(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 +448,7 @@ public: void property(const char *name, const getter_callback_1 &getter, const setter_callback_0 &setter) { _properties[name] = std::make_shared<Property>(getter,setter); } void property(const char *name, const getter_callback_0 &getter, const setter_callback_1 &setter) { _properties[name] = std::make_shared<Property>(getter,setter); } void property(const char *name, const getter_callback_1 &getter, const setter_callback_1 &setter) { _properties[name] = std::make_shared<Property>(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 <emiel.bruijntjes@copernica.com> @@ -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/constantfuncs.cpp b/zend/constantfuncs.cpp index 40ea20e..46886cf 100644 --- a/zend/constantfuncs.cpp +++ b/zend/constantfuncs.cpp @@ -39,13 +39,13 @@ Value constant(const char *constant, size_t size) // we need the tsrm_ls variable TSRMLS_FETCH(); - // the value that holds the result - Value result; - // retrieve the constant - if (!zend_get_constant(constant, size, result._val TSRMLS_CC)) return nullptr; - - // zval was correctly retrieved, wrap in value + auto *result = zend_get_constant(zend_string_init(constant, size, 1) TSRMLS_CC); + + // did the constant exist? + if (!result) return nullptr; + + // return the valid result return result; } @@ -71,14 +71,13 @@ bool define(const char *name, size_t size, const Value &value) { // we need the tsrm_ls variable TSRMLS_FETCH(); - + // the constant structure from the zend engine zend_constant constant; - - // copy the name (note that name_len also includes the end-of-string '\0' byte) - constant.name = zend_strndup(name, size); - constant.name_len = size + 1; - + + // copy the name + constant.name = zend_string_init(name, size, 1); + // only scalar values can be used for constants if (value.isScalar()) { @@ -90,19 +89,19 @@ bool define(const char *name, size_t size, const Value &value) { // we're going to convert the value object into a string, and use that Value str = value.clone(Type::String); - + // use the copied value constant.value = *str._val; zval_copy_ctor(&constant.value); } - + // constants are case sensitive (but not persistent, because this is a user // space constant!) constant.flags = CONST_CS; // as module number we use a fake module number constant.module_number = PHP_USER_CONSTANT; - + // register the constant return zend_register_constant(&constant TSRMLS_CC) == SUCCESS; } @@ -137,20 +136,21 @@ bool define(const std::string &name, const Value &value) * @param size * @return bool */ -bool defined(const char *name, size_t size) +bool defined(const char *name, size_t size) { // we need the tsrm_ls variable TSRMLS_FETCH(); - // result variable - zval c; - // retrieve the constant - if (!zend_get_constant_ex(name, size, &c, NULL, ZEND_FETCH_CLASS_SILENT TSRMLS_CC)) return false; + auto *value = zend_get_constant_ex(zend_string_init(name, size, 1), nullptr, ZEND_FETCH_CLASS_SILENT TSRMLS_CC); + + // check if the value was found + if (!value) return false; // constant exists, but the returned zval should first be destructed - zval_dtor(&c); - + // @todo: is this necessary in PHP 7? + zval_dtor(value); + // done return true; } 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 <emiel.bruijntjes@copernica.com> * @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<Value(Parameters ¶ms)> &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<Value(Parameters ¶ms)> &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/exists.cpp b/zend/exists.cpp index 1177e47..21d3184 100644 --- a/zend/exists.cpp +++ b/zend/exists.cpp @@ -1,7 +1,7 @@ /** * Exists.cpp * - * This file holds the implementation of all *_exists() functions, + * This file holds the implementation of all *_exists() functions, * like class_exists(), et cetera * * @author andot <https://github.com/andot> @@ -33,41 +33,42 @@ namespace Php { * @param autoload * @return bool */ -bool class_exists(const char *classname, size_t len, bool autoload) +bool class_exists(const char *classname, size_t len, bool autoload) { // we need the tsrm_ls variable TSRMLS_FETCH(); - // we're going to load a class-entry - zend_class_entry **ce; - // should we autoload the class? - if (autoload) + if (autoload) { + // retrieve class entry + auto *ce = zend_lookup_class(zend_string_init(classname, len, 1) TSRMLS_CC); + // no auto-load - if (SUCCESS != zend_lookup_class(classname, len, &ce TSRMLS_CC)) return false; + if (!ce) return false; // the found "class" could also be an interface or trait, which we do no want - return ((*ce)->ce_flags & (ZEND_ACC_INTERFACE | (ZEND_ACC_TRAIT - ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) == 0; + return (ce->ce_flags & (ZEND_ACC_INTERFACE | (ZEND_ACC_TRAIT - ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) == 0; } else { // starting slashes can be ignored if (len > 0 && classname[0] == '\\') { classname++; len--; } - - // all classes are in lowercase in the hash, so we make - // a temporary buffer for storing the lowercase class name - // (is this smart? memory allocation is expensive!) - std::unique_ptr<char[]> lc_name(new char[len + 1]); - + + // allocate a zend_string + auto *string = zend_string_alloc(len, 1); + // copy the name to lowercase, but ignore the starting slash (if there is one) - zend_str_tolower_copy(lc_name.get(), classname, len); + zend_str_tolower_copy(ZSTR_VAL(string), classname, len); // see if there is a class with this name - if (SUCCESS != zend_hash_find(EG(class_table), lc_name.get(), len + 1, (void **) &ce)) return false; - - // the found "class" could also be an interface or trait, which we do no want - return !(((*ce)->ce_flags & (ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT)) > ZEND_ACC_EXPLICIT_ABSTRACT_CLASS); + auto *val = zend_hash_find(EG(class_table), string); + + // check whether something was found + if (val == nullptr) return false; + + // the "something" could also be an interface or trait, which we do no want + return !(((Z_CE_P(val))->ce_flags & (ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT)) > ZEND_ACC_EXPLICIT_ABSTRACT_CLASS); } } diff --git a/zend/extensionimpl.cpp b/zend/extensionimpl.cpp index bacf80f..ef5d33d 100644 --- a/zend/extensionimpl.cpp +++ b/zend/extensionimpl.cpp @@ -41,14 +41,14 @@ static void init_globals(zend_phpcpp_globals *globals) {} * variable. However, there does not seem to be a decent API call in Zend to * get back the original module_entry linked to this number. So we have to * look up entries in a hash table to find the right module entry. To make things - * even worse, the records in this hash table are copies of the original + * even worse, the records in this hash table are copies of the original * zend_module_entry structure, so we can also not hide the C++ extension * object pointer in the entry that we created ourselves. - * + * * We have an ugly solution, we keep track of a map of all C++ extension names * and their associated extension object, and a map of all module number and * the linked extension object. - * + * * @var map */ static std::map<std::string,ExtensionImpl*> name2extension; @@ -56,22 +56,25 @@ static std::map<int,ExtensionImpl*> number2extension; /** * Handler function that is used in combination with zend_hash_apply() - * + * * This function is called when we need to find an extension object based on - * an extension number. We loop through the list of all registered modules, and + * an extension number. We loop through the list of all registered modules, and * for each module we check if we know the extension based on the name - * + * * @param zend_module_entry */ -static int match_module(zend_module_entry *entry) +static int match_module(zval *value TSRMLS_DC) { + // retrieve the module entry from the zval + auto *entry = (zend_module_entry*)Z_PTR_P(value); + // check if there is an extension with this name auto iter = name2extension.find(entry->name); if (iter == name2extension.end()) return ZEND_HASH_APPLY_KEEP; - + // we have the extension, store in combination with the number number2extension[entry->module_number] = iter->second; - + // done return ZEND_HASH_APPLY_KEEP; } @@ -87,14 +90,14 @@ static ExtensionImpl *find(int number TSRMLS_DC) // do we already have an extension with this number? auto iter = number2extension.find(number); if (iter != number2extension.end()) return iter->second; - + // no, not yet, loop through all modules - zend_hash_apply(&module_registry, (apply_func_t)match_module TSRMLS_CC); - + zend_hash_apply(&module_registry, match_module TSRMLS_CC); + // find again iter = number2extension.find(number); if (iter == number2extension.end()) return nullptr; - + // found! return iter->second; } @@ -109,7 +112,7 @@ static ExtensionImpl *find(int number TSRMLS_DC) int ExtensionImpl::processStartup(int type, int module_number TSRMLS_DC) { // initialize and allocate the "global" variables - ZEND_INIT_MODULE_GLOBALS(phpcpp, init_globals, NULL); + ZEND_INIT_MODULE_GLOBALS(phpcpp, init_globals, NULL); // get the extension auto *extension = find(module_number TSRMLS_CC); @@ -148,10 +151,10 @@ int ExtensionImpl::processRequest(int type, int module_number TSRMLS_DC) { // get the extension auto *extension = find(module_number TSRMLS_CC); - + // is the callback registered? if (extension->_onRequest) extension->_onRequest(); - + // done return BOOL2SUCCESS(true); } @@ -167,10 +170,10 @@ int ExtensionImpl::processIdle(int type, int module_number TSRMLS_DC) { // get the extension auto *extension = find(module_number TSRMLS_CC); - + // is the callback registered? if (extension->_onIdle) extension->_onIdle(); - + // done return BOOL2SUCCESS(true); } @@ -187,10 +190,10 @@ int ExtensionImpl::processMismatch(int type, int module_number TSRMLS_DC) { // get the extension auto *extension = find(module_number TSRMLS_CC); - + // report a warning warning << "Version mismatch between PHP-CPP and extension " << extension->name() << " " << extension->version() << " (recompile needed?)" << std::endl; - + // done return BOOL2SUCCESS(true); } @@ -202,12 +205,12 @@ int ExtensionImpl::processMismatch(int type, int module_number TSRMLS_DC) * @param version Version number * @param apiversion API version number */ -ExtensionImpl::ExtensionImpl(Extension *data, const char *name, const char *version, int apiversion) : +ExtensionImpl::ExtensionImpl(Extension *data, const char *name, const char *version, int apiversion) : ExtensionBase(data) { // keep extension pointer based on the name name2extension[name] = this; - + // assign all members (apart from the globals) _entry.size = sizeof(zend_module_entry); // size of the data _entry.zend_api = ZEND_MODULE_API_NO; // api number @@ -242,8 +245,8 @@ ExtensionImpl::ExtensionImpl(Extension *data, const char *name, const char *vers // everything is ok if the api versions match if (apiversion == PHPCPP_API_VERSION) return; - - // mismatch between api versions, the extension is invalid, we use a + + // mismatch between api versions, the extension is invalid, we use a // different startup function to report to the user _entry.module_startup_func = &ExtensionImpl::processMismatch; @@ -260,12 +263,9 @@ ExtensionImpl::~ExtensionImpl() { // remove from the array name2extension.erase(_entry.name); - - // deallocate the php.ini entries - if (_ini) delete[] _ini; - + // deallocate functions - if (_entry.functions) delete[] _entry.functions; + delete[] _entry.functions; } /** @@ -303,7 +303,7 @@ zend_module_entry *ExtensionImpl::module() // the number of functions int count = _data->functions(); - + // skip if there are no functions if (count == 0) return &_entry; @@ -315,10 +315,10 @@ zend_module_entry *ExtensionImpl::module() // apply a function to each function _data->functions([&i, entries](const std::string &prefix, NativeFunction &function) { - + // initialize the function function.initialize(prefix, &entries[i]); - + // move on to the next iteration i++; }); @@ -345,40 +345,40 @@ zend_module_entry *ExtensionImpl::module() bool ExtensionImpl::initialize(int module_number TSRMLS_DC) { // array contains ini settings - _ini = new zend_ini_entry[_data->iniVariables()+1]; + _ini.reset(new zend_ini_entry_def[_data->iniVariables()+1]); // the entry that we're filling int i = 0; // Fill the php.ini entries _data->iniVariables([this, &i, module_number](Ini &ini) { - + // initialize the function - zend_ini_entry *entry = &_ini[i]; - + zend_ini_entry_def *entry = &_ini[i]; + // fill the property ini.fill(entry, module_number); - + // move on to the next iteration - i++; + ++i; }); // last entry should be set to all zero's - memset(&_ini[i], 0, sizeof(zend_ini_entry)); + memset(&_ini[i], 0, sizeof(_ini[i])); // register ini entries in Zend core - zend_register_ini_entries(_ini, module_number TSRMLS_CC); + zend_register_ini_entries(_ini.get(), module_number TSRMLS_CC); // the constants are registered after the module is ready _data->constants([module_number TSRMLS_CC](const std::string &prefix, Constant &c) { - + // forward to implementation class c.implementation()->initialize(prefix, module_number TSRMLS_CC); }); - + // we also need to register each class, find out all classes _data->classes([TSRMLS_C](const std::string &prefix, ClassBase &c) { - + // forward to implementation class c.implementation()->initialize(&c, prefix TSRMLS_CC); }); @@ -386,10 +386,10 @@ bool ExtensionImpl::initialize(int module_number TSRMLS_DC) // initialize the PhpCpp::Functor class Functor::initialize(TSRMLS_C); - // remember that we're initialized (when you use "apache reload" it is + // remember that we're initialized (when you use "apache reload" it is // possible that the processStartup() method is called more than once) _locked = true; - + // is the callback registered? if (_onStartup) _onStartup(); @@ -409,17 +409,14 @@ bool ExtensionImpl::shutdown(int module_number TSRMLS_DC) zend_unregister_ini_entries(module_number TSRMLS_CC); // destruct the ini entries - if (_ini) delete[] _ini; - - // forget the ini entries - _ini = nullptr; + _ini.reset(); // shutdown the functor class Functor::shutdown(TSRMLS_C); // is the callback registered? if (_onShutdown) _onShutdown(); - + // done return true; } diff --git a/zend/extensionimpl.h b/zend/extensionimpl.h index 3a0fc63..aef4802 100644 --- a/zend/extensionimpl.h +++ b/zend/extensionimpl.h @@ -2,7 +2,7 @@ * ExtensionImpl.h * * Extension implementation for the Zend engine. - * + * * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> * @copyright 2013, 2014 Copernica BV */ @@ -23,21 +23,21 @@ protected: * @var zend_module_entry */ zend_module_entry _entry; - + /** * Is the object locked? This prevents crashes for 'apache reload' * because we then do not have to re-initialize the entire php engine * @var bool */ bool _locked = false; - + /** * The .ini entries - * - * @var zend_ini_entry + * + * @var std::unique_ptr<zend_ini_entry_def[]> */ - zend_ini_entry *_ini = nullptr; - + std::unique_ptr<zend_ini_entry_def[]> _ini = nullptr; + public: /** * Constructor @@ -47,31 +47,31 @@ public: * @param apiversion API version number */ ExtensionImpl(Extension *data, const char *name, const char *version, int apiversion); - + /** * No copy'ing and no moving */ ExtensionImpl(const ExtensionImpl &extension) = delete; ExtensionImpl(ExtensionImpl &&extension) = delete; - + /** * Destructor */ virtual ~ExtensionImpl(); - + /** * The extension name * @return const char * */ const char *name() const; - + /** * The extension version * @return const char * */ const char *version() const; - - /** + + /** * Is the object locked (true) or is it still possible to add more functions, * classes and other elements to it? * @return bool @@ -81,17 +81,17 @@ public: // return member return _locked; } - + /** * Retrieve the module entry - * + * * This is the memory address that should be exported by the get_module() * function. * * @return _zend_module_entry */ zend_module_entry *module(); - + /** * Cast to a module entry * @return _zend_module_entry* @@ -100,10 +100,10 @@ public: { return module(); } - + private: /** - * Initialize the namespace after it was registered + * Initialize the extension after it was registered * @param module_number * @param tsrm_ls * @return bool @@ -126,7 +126,7 @@ private: * @return int 0 on success */ static int processStartup(int type, int module_number TSRMLS_DC); - + /** * Function that is called when the extension is about to be stopped * @param type Module type @@ -135,7 +135,7 @@ private: * @return int */ static int processShutdown(int type, int module_number TSRMLS_DC); - + /** * Function that is called when a request starts * @param type Module type diff --git a/zend/file.cpp b/zend/file.cpp index 2b7e77f..77b96f5 100644 --- a/zend/file.cpp +++ b/zend/file.cpp @@ -19,10 +19,10 @@ namespace Php { /** * Constructor - * + * * The constructor receives a filename as parameter. It uses the normal - * PHP include path resolve algorithms to find the location of the file. - * + * PHP include path resolve algorithms to find the location of the file. + * * @param name the filename * @param size length of the filename */ @@ -30,16 +30,9 @@ File::File(const char *name, size_t size) { // we need the tsrm_ls variable TSRMLS_FETCH(); - + // resolve the path _path = zend_resolve_path(name, size TSRMLS_CC); - - // the resolve-path function sometimes returns the original pointer, we - // do not want that because we may have to store the pathname in this object - if (_path != name) return; - - // make a full copy of the pathname - _path = estrndup(name, size); } /** @@ -48,10 +41,7 @@ File::File(const char *name, size_t size) File::~File() { // clean up path name - if (_path) efree(_path); - - // clean up opcodes - if (_opcodes) delete _opcodes; + if (_path) zend_string_release(_path); } /** @@ -62,10 +52,10 @@ bool File::compile() { // never works if the path is invalid if (!_path) return false; - + // is the file already compiled? if (_opcodes) return _opcodes->valid(); - + // we are going to open the file zend_file_handle fileHandle; @@ -73,20 +63,20 @@ bool File::compile() TSRMLS_FETCH(); // open the file - if (zend_stream_open(_path, &fileHandle TSRMLS_CC) == FAILURE) return false; + if (zend_stream_open(ZSTR_VAL(_path), &fileHandle TSRMLS_CC) == FAILURE) return false; + + // make sure the path name is stored in the handle (@todo: is this necessary? do we need the copy?) + if (!fileHandle.opened_path) fileHandle.opened_path = zend_string_copy(_path); - // make sure the path name is stored in the handle - if (!fileHandle.opened_path) fileHandle.opened_path = estrdup(_path); - // we need temporary compiler options CompilerOptions options(ZEND_COMPILE_DEFAULT TSRMLS_CC); - + // create the opcodes - _opcodes = new Opcodes(zend_compile_file(&fileHandle, ZEND_INCLUDE TSRMLS_CC) TSRMLS_CC); + _opcodes.reset(new Opcodes(zend_compile_file(&fileHandle, ZEND_INCLUDE TSRMLS_CC) TSRMLS_CC)); // close the file handle zend_destroy_file_handle(&fileHandle TSRMLS_CC); - + // done return _opcodes->valid(); } @@ -99,13 +89,13 @@ bool File::exists() { // it is of course not valid if the path could not be resolved if (!_path) return false; - + // if we have valid opcodes, we're sure that it exists if (_opcodes && _opcodes->valid()) return true; - + // retrieve stats struct stat buf; - return stat(_path, &buf) == 0; + return stat(ZSTR_VAL(_path), &buf) == 0; } /** @@ -122,7 +112,7 @@ bool File::valid() * Execute the file * @return Value */ -Value File::execute() +Value File::execute() { // do we already have the opcodes? if (_opcodes) return _opcodes->execute(); @@ -130,12 +120,9 @@ Value File::execute() // try compiling the file if (!compile()) return nullptr; - // we need the tsrm_ls variable (@todo would it be better if this was a member?) - TSRMLS_FETCH(); - // add the entry to the list of included files - zend_hash_add_empty_element(&EG(included_files), _path, ::strlen(_path) + 1); - + zend_hash_add_empty_element(&EG(included_files), _path); + // execute the opcodes return _opcodes->execute(); } @@ -144,16 +131,16 @@ Value File::execute() * Execute a file only once * @return Value */ -Value File::once() +Value File::once() { // skip if the path is invalid if (!_path) return nullptr; // we need the tsrm_ls variable (@todo would it be better if this was a member?) TSRMLS_FETCH(); - + // check if this file was already included - if (zend_hash_exists(&EG(included_files), _path, ::strlen(_path) + 1)) return nullptr; + if (zend_hash_exists(&EG(included_files), _path)) return nullptr; // execute the file return execute(); diff --git a/zend/functor.cpp b/zend/functor.cpp index 7d0404b..866fcea 100644 --- a/zend/functor.cpp +++ b/zend/functor.cpp @@ -6,7 +6,7 @@ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> * @copyright 2015 Copernica BV */ - + /** * Dependencies */ @@ -31,10 +31,10 @@ void Functor::initialize(TSRMLS_D) { // leap out if the class entry is already set if (_entry) return; - + // construct functor object static std::unique_ptr<ClassBase> functor(new Class<Functor>("PhpCpp::Functor")); - + // initialize the functor class _entry = functor->implementation()->initialize(functor.get(), "" TSRMLS_CC); } @@ -48,7 +48,7 @@ void Functor::shutdown(TSRMLS_D) // we forget the entry _entry = nullptr; } - + /** * End of namespace */ diff --git a/zend/global.cpp b/zend/global.cpp index 7eea8e7..e7fef53 100644 --- a/zend/global.cpp +++ b/zend/global.cpp @@ -14,6 +14,67 @@ namespace Php { /** + * Move constructor + * @param global + */ +Global::Global(Global &&global) _NOEXCEPT : + Value(std::move(global)), + _name(global._name), + _exists(global._exists) +{ + // remove from other global to avoid double free + global._name = nullptr; +} + +/** + * Constructor for non-existing var + * + * @param name Name for the variable that does not exist + */ +Global::Global(const char *name) : + Value(), + _name(zend_string_init(name, ::strlen(name), 1)), + _exists(false) {} + +/** + * Alternative constructor for non-existing var + * @param name + */ +Global::Global(const std::string &name) : + Value(), + _name(zend_string_init(name.data(), name.size(), 1)), + _exists(false) {} + +/** + * Constructor to wrap zval for existing global bar + * @param name + * @param val + */ +Global::Global(const char *name, struct _zval_struct *val) : + Value(val, true), + _name(zend_string_init(name, ::strlen(name), 1)), + _exists(true) {} + +/** + * Alternative constructor to wrap zval + * @param name + * @param val + */ +Global::Global(const std::string &name, struct _zval_struct *val) : + Value(val, true), + _name(zend_string_init(name.data(), name.size(), 1)), + _exists(true) {} + +/** + * Destructor + */ +Global::~Global() +{ + // release the string + if (_name) zend_string_release(_name); +} + +/** * Function that is called when the value is updated * @return Value */ @@ -21,19 +82,19 @@ Global &Global::update() { // skip if the variable already exists if (_exists) return *this; - + // we need the TSRMLS variable TSRMLS_FETCH(); // add the variable to the globals - zend_hash_add(EG(active_symbol_table), _name.c_str(), _name.size()+1, &_val, sizeof(zval*), NULL); + zend_hash_add(EG(current_execute_data)->symbol_table, _name, _val); // add one extra reference because the variable now is a global var too - Z_ADDREF_P(_val); + Z_TRY_ADDREF_P(_val); // remember that the variable now exists _exists = true; - + // done return *this; } diff --git a/zend/globals.cpp b/zend/globals.cpp index 5299c90..ea4e65e 100644 --- a/zend/globals.cpp +++ b/zend/globals.cpp @@ -36,14 +36,14 @@ Globals &GLOBALS = Globals::instance(); */ Global Globals::operator[](const char *name) { - // pointer to a zval - zval **varvalue; - // we need the TSRMLS variable TSRMLS_FETCH(); - + + // retrieve the variable (if it exists) + auto *varvalue = zend_hash_find(&EG(symbol_table), zend_string_init(name, ::strlen(name), 0)); + // check if the variable already exists - if (zend_hash_find(&EG(symbol_table), name, ::strlen(name)+1, (void**)&varvalue) == FAILURE) + if (!varvalue) { // the variable does not already exist, return a global object // that will automatically set the value when it is updated @@ -53,7 +53,7 @@ Global Globals::operator[](const char *name) { // we are in the happy situation that the variable exists, we turn // this value into a reference value, and return that - return Global(name, *varvalue); + return Global(name, varvalue); } } @@ -64,14 +64,14 @@ Global Globals::operator[](const char *name) */ Global Globals::operator[](const std::string &name) { - // pointer to a zval - zval **varvalue; - // we need the TSRMLS variable TSRMLS_FETCH(); - + + // retrieve the variable (if it exists) + auto *varvalue = zend_hash_find(&EG(symbol_table), zend_string_init(name.data(), name.size(), 0)); + // check if the variable already exists - if (zend_hash_find(&EG(symbol_table), name.c_str(), name.size()+1, (void**)&varvalue) == FAILURE) + if (!varvalue) { // the variable does not already exist, return a global object // that will automatically set the value when it is updated @@ -81,7 +81,7 @@ Global Globals::operator[](const std::string &name) { // we are in the happy situation that the variable exists, we turn // this value into a reference value, and return that - return Global(name, *varvalue); + return Global(name, varvalue); } } 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; } /** @@ -156,6 +162,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<Value,Value>(std::move(key), *value); + _current = std::make_pair<Value,Value>(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<Value,Value>(nullptr,nullptr); - + // done return false; } diff --git a/zend/ini.cpp b/zend/ini.cpp index 889e388..849f6c4 100644 --- a/zend/ini.cpp +++ b/zend/ini.cpp @@ -17,35 +17,20 @@ namespace Php { * @param zend_ini_entry *ini_entry, int module_number * @param int module_number */ -void Ini::fill(zend_ini_entry *ini_entry, int module_number) +void Ini::fill(zend_ini_entry_def *ini_entry, int module_number) { - ini_entry->module_number = module_number; - ini_entry->modifiable = static_cast<int>(this->_place); - ini_entry->name = const_cast<char*>(this->_name.c_str()); - ini_entry->name_length = this->_name.size()+1; - ini_entry->on_modify = OnUpdateString; - ini_entry->mh_arg1 = nullptr; + ini_entry->modifiable = static_cast<int>(_place); + ini_entry->name = _name.data(); + ini_entry->on_modify = OnUpdateString; + ini_entry->mh_arg1 = nullptr; #ifdef ZTS - ini_entry->mh_arg2 = (void *) &phpcpp_globals_id; + ini_entry->mh_arg2 = (void *) &phpcpp_globals_id; #else - ini_entry->mh_arg2 = (void *) &phpcpp_globals; + ini_entry->mh_arg2 = (void *) &phpcpp_globals; #endif - ini_entry->mh_arg3 = nullptr; - ini_entry->value = const_cast<char*>(this->_value.c_str()); - ini_entry->value_length = this->_value.size(); - if( this->_orig_empty) - { - ini_entry->orig_value = nullptr; - ini_entry->orig_value_length = 0; - } - else - { - ini_entry->orig_value = const_cast<char*>(this->_orig.c_str()); - ini_entry->orig_value_length = this->_orig.size(); - } - ini_entry->orig_modifiable = 0; - ini_entry->modified = 0; - ini_entry->displayer = nullptr; + ini_entry->mh_arg3 = nullptr; + ini_entry->value = _value.data(); + ini_entry->displayer = nullptr; } diff --git a/zend/iteratorimpl.cpp b/zend/iteratorimpl.cpp index 49526b1..4861e9e 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); } /** @@ -49,21 +49,22 @@ int IteratorImpl::valid(zend_object_iterator *iter TSRMLS_DC) /** * Fetch the current item - * @param iter - * @param data - * @param tsrm_ls + * + * @param iter The iterator to retrieve the value from + * @param tsrm_ls Thread safety variable + * @return The current value of the iterator */ -void IteratorImpl::current(zend_object_iterator *iter, zval ***data TSRMLS_DC) +zval *IteratorImpl::current(zend_object_iterator *iter TSRMLS_DC) { // get the actual iterator - IteratorImpl *iterator = self(iter); + auto *iterator = self(iter); // retrieve the value (and store it in a member so that it is not // destructed when the function returns) iterator->_current = iterator->current(); - // copy the value - *data = &iterator->_current._val; + // return the value + return iterator->_current._val; } /** @@ -100,14 +101,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 +116,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 +152,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 +166,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..2199e6f 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> _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 @@ -114,11 +114,12 @@ private: /** * Fetch the current item - * @param iter - * @param data - * @param tsrm_ls + * + * @param iter The iterator used to retrieve the value from + * @param tsrm_ls Thread safety variable + * @return The current value of the iterator */ - static void current(zend_object_iterator *iter, zval ***data TSRMLS_DC); + static zval *current(zend_object_iterator *iter TSRMLS_DC); /** * Fetch the key for the current element (optional, may be NULL). The key @@ -161,14 +162,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 +186,7 @@ public: return &_impl; } }; - + /** * End namespace */ diff --git a/zend/nativefunction.h b/zend/nativefunction.h index 073e69b..e661112 100644 --- a/zend/nativefunction.h +++ b/zend/nativefunction.h @@ -70,8 +70,8 @@ public: void initialize(const std::string &prefix, zend_function_entry *entry) { // if there is a namespace prefix, we should adjust the name - if (prefix.size()) _ptr = HiddenPointer<Callable>(this, prefix+"\\"+(const char *)_ptr); - + if (!prefix.empty()) _name = prefix + '\\' + _name; + // call base initialize Callable::initialize(entry); } @@ -79,7 +79,7 @@ public: private: /** * Union of supported callbacks - * One of the callbacks will be set + * One of the callbacks will be set */ union { native_callback_0 f0; @@ -87,7 +87,7 @@ private: native_callback_2 f2; native_callback_3 f3; } _function; - + /** * The callback that is set * @var integer diff --git a/zend/object.cpp b/zend/object.cpp index 7da8339..e2345c1 100644 --- a/zend/object.cpp +++ b/zend/object.cpp @@ -13,7 +13,7 @@ namespace Php { /** * Constructor to create a new instance of a builtin class - * + * * @param name Name of the class to instantiate * @param base The C++ object to wrap */ @@ -36,26 +36,26 @@ Object::Object(const char *name, Base *base) : Value() // here because this function is called from C++ context, and zend_error() // would cause a longjmp() which does not clean up C++ objects created // by the extension). - auto *entry = zend_fetch_class(name, ::strlen(name), ZEND_FETCH_CLASS_SILENT TSRMLS_CC); + auto *entry = zend_fetch_class(zend_string_init(name, ::strlen(name), 0), ZEND_FETCH_CLASS_SILENT TSRMLS_CC); if (!entry) throw FatalError(std::string("Unknown class name ") + name); // construct an implementation (this will also set the implementation // 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); + Z_OBJ_P(_val)->handlers = ClassImpl::objectHandlers(entry); } } /** * Constructor in case the class entry is already known - * + * * @param entry Class entry * @param base The C++ object to wrap */ @@ -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); } } @@ -95,12 +92,12 @@ Object::Object(const Value &value) : Value() { // when a string is passed in, we are going to make a new instance of the // passed in string - if (value.isString()) + if (value.isString()) { // instantiate the object if (instantiate(value)) call("__construct"); } - else + else { // this simply copies the other object operator=(value); @@ -121,23 +118,23 @@ bool Object::instantiate(const char *name) // here because this function is called from C++ context, and zend_error() // would cause a longjmp() which does not clean up C++ objects created // by the extension). - auto *entry = zend_fetch_class(name, ::strlen(name), ZEND_FETCH_CLASS_SILENT TSRMLS_CC); + auto *entry = zend_fetch_class(zend_string_init(name, ::strlen(name), 0), ZEND_FETCH_CLASS_SILENT TSRMLS_CC); if (!entry) throw FatalError(std::string("Unknown class name ") + name); // initiate the zval (which was already allocated in the base constructor) object_init_ex(_val, entry); - + // @todo should we call methods like allocating hashtables, copying and // initializing properties, et cetera????? In all example you always // see such complicated and next-to-impossible-to-understand // sequences of functions being called, but this object_init_ex // also seems to work... - - // @todo is this a memory leak? the base class first initializes a stdClass, + + // @todo is this a memory leak? the base class first initializes a stdClass, // and then we overwrite it with a specific class - + // return whether there is a __construct function - return zend_hash_exists(&entry->function_table, "__construct", 12); + return zend_hash_exists(&entry->function_table, zend_string_init("__construct", 12, 1)); } /** diff --git a/zend/objectimpl.h b/zend/objectimpl.h index 110491b..ed48e26 100644 --- a/zend/objectimpl.h +++ b/zend/objectimpl.h @@ -27,32 +27,25 @@ 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> */ - Base *_object; - - /** - * The object handle in the Zend engine - * @var int - */ - int _handle; + std::unique_ptr<Base> _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) GC_REFCOUNT(php()) = 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,14 +94,24 @@ 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 * @param tsrm_ls Optional pointer to thread info @@ -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<OrigException&>(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 <emiel.bruijntjes@copernica.com> * @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; i<argc; i++) { - // get the argument - zval **arg = (zval **) (zend_vm_stack_top(TSRMLS_C) - 1 - (argc-i)); - // append value - emplace_back(*arg); + emplace_back(&arguments[i]); } } - + /** - * 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 ~ParametersImpl() {} }; /** diff --git a/zend/super.cpp b/zend/super.cpp index ea690fe..02a1e46 100644 --- a/zend/super.cpp +++ b/zend/super.cpp @@ -32,10 +32,16 @@ Value Super::value() TSRMLS_FETCH(); // call zend_is_auto_global to ensure that the just-in-time globals are loaded - if (_name) { zend_is_auto_global(_name, ::strlen(_name) TSRMLS_CC); _name = nullptr; } - + if (_name) { + // make the variable an auto global + zend_is_auto_global(zend_string_init(_name, ::strlen(_name), 1) TSRMLS_CC); + + // reset because we only need to do this once + _name = nullptr; + } + // create a value object that wraps around the actual zval - return Value(PG(http_globals)[_index]); + return &PG(http_globals)[_index]; } /** diff --git a/zend/traverseiterator.h b/zend/traverseiterator.h index a65f909..27c4832 100644 --- a/zend/traverseiterator.h +++ b/zend/traverseiterator.h @@ -30,20 +30,20 @@ public: { // leap out if this iterator starts at the end if (!begin) return; - + // we need the class entry - auto *entry = zend_get_class_entry(object TSRMLS_CC); - + auto *entry = Z_OBJCE_P(object); + // create the iterator _iter = entry->get_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; } diff --git a/zend/value.cpp b/zend/value.cpp index d22830a..9be4b24 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -34,31 +34,24 @@ namespace Php { /** * Constructor (value = NULL) */ -Value::Value() +Value::Value() : _val(new zval) { // create a null zval - MAKE_STD_ZVAL(_val); ZVAL_NULL(_val); } /** * Constructor for null ptr */ -Value::Value(std::nullptr_t value) -{ - // create a null zval - MAKE_STD_ZVAL(_val); - ZVAL_NULL(_val); -} +Value::Value(std::nullptr_t value) : Value() {} /** * Constructor based on integer value * @param value */ -Value::Value(int16_t value) +Value::Value(int16_t value) : _val(new zval) { // create an integer zval - MAKE_STD_ZVAL(_val); ZVAL_LONG(_val, value); } @@ -66,10 +59,9 @@ Value::Value(int16_t value) * Constructor based on integer value * @param value */ -Value::Value(int32_t value) +Value::Value(int32_t value) : _val(new zval) { // create an integer zval - MAKE_STD_ZVAL(_val); ZVAL_LONG(_val, value); } @@ -77,10 +69,9 @@ Value::Value(int32_t value) * Constructor based on int64_t value * @param value */ -Value::Value(int64_t value) +Value::Value(int64_t value) : _val(new zval) { // create an integer zval - MAKE_STD_ZVAL(_val); ZVAL_LONG(_val, value); } @@ -88,10 +79,9 @@ Value::Value(int64_t value) * Constructor based on boolean value * @param value */ -Value::Value(bool value) +Value::Value(bool value) : _val(new zval) { // create a boolean zval - MAKE_STD_ZVAL(_val); ZVAL_BOOL(_val, value); } @@ -99,22 +89,20 @@ Value::Value(bool value) * Constructor based on single character * @param value */ -Value::Value(char value) +Value::Value(char value) : _val(new zval) { // create a string zval - MAKE_STD_ZVAL(_val); - ZVAL_STRINGL(_val, &value, 1, 1); + ZVAL_STRINGL(_val, &value, 1); } /** * Constructor based on string value * @param value */ -Value::Value(const std::string &value) +Value::Value(const std::string &value) : _val(new zval) { // create a string zval - MAKE_STD_ZVAL(_val); - ZVAL_STRINGL(_val, value.c_str(), value.size(), 1); + ZVAL_STRINGL(_val, value.c_str(), value.size()); } /** @@ -122,16 +110,13 @@ Value::Value(const std::string &value) * @param value * @param size */ -Value::Value(const char *value, int size) +Value::Value(const char *value, int size) : _val(new zval) { - // allocate the zval - MAKE_STD_ZVAL(_val); - // is there a value? if (value) { // create a string zval - ZVAL_STRINGL(_val, value, size < 0 ? ::strlen(value) : size, 1); + ZVAL_STRINGL(_val, value, size < 0 ? ::strlen(value) : size); } else { @@ -144,10 +129,9 @@ Value::Value(const char *value, int size) * Constructor based on decimal value * @param value */ -Value::Value(double value) +Value::Value(double value) : _val(new zval) { // create a double zval - MAKE_STD_ZVAL(_val); ZVAL_DOUBLE(_val, value); } @@ -156,15 +140,22 @@ Value::Value(double value) * @param zval Value to wrap * @param ref Force this to be a reference */ -Value::Value(struct _zval_struct *val, bool ref) : _val(val) +Value::Value(struct _zval_struct *val, bool ref) : _val(new zval) { + // copy the value over (and add reference if relevant) + ZVAL_COPY(_val, val); + + // not refcounted? then there is nothing we can do here + // @todo: we should be able to force a reference somehow + if (!Z_REFCOUNTED_P(_val)) return; + // if the variable is not already a reference, and it has more than one // variable pointing to it, we should seperate it so that any changes // we're going to make will not change the other variable if (ref && Z_REFCOUNT_P(_val) > 1) { // separate the zval - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); } // we see ourselves as reference too @@ -174,7 +165,7 @@ Value::Value(struct _zval_struct *val, bool ref) : _val(val) if (!ref || Z_ISREF_P(_val)) return; // make this a reference - Z_SET_ISREF_P(_val); + ZVAL_MAKE_REF(_val); } /** @@ -193,34 +184,19 @@ Value::Value(const Base *object) // do we have a handle? if (!impl) throw FatalError("Assigning an unassigned object to a variable"); - // make a regular zval, and set it to an object - MAKE_STD_ZVAL(_val); - Z_TYPE_P(_val) = IS_OBJECT; - Z_OBJ_HANDLE_P(_val) = impl->handle(); - - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // we have to lookup the object in the object-table - zend_object_store_bucket *obj_bucket = &EG(objects_store).object_buckets[impl->handle()]; - - // there is one more reference to the object - obj_bucket->bucket.obj.refcount += 1; + // allocate variable and set it to an object + _val = new zval; + Z_TYPE_INFO_P(_val) = IS_OBJECT; - // this is copy-pasted from zend_objects.c - and it is necessary too! - if (!obj_bucket->bucket.obj.handlers) obj_bucket->bucket.obj.handlers = ClassImpl::objectHandlers(impl->php()->ce); - - // store the handlers in the zval too (cast is necessary for php 5.3) - Z_OBJ_HT_P(_val) = (zend_object_handlers*)obj_bucket->bucket.obj.handlers; + // increase refcount + GC_REFCOUNT(impl->php())++; } /** * Wrap around a php.ini value * @param value */ -Value::Value(const IniValue &value) : Value((const char *)value) -{ -} +Value::Value(const IniValue &value) : Value((const char *)value) {} /** * Copy constructor @@ -228,57 +204,22 @@ Value::Value(const IniValue &value) : Value((const char *)value) */ Value::Value(const Value &that) { - // is the other variable a reference? - if (Z_ISREF_P(that._val)) + // is the other variable a reference? or is this a simple scalar? + if (Z_ISREF_P(that._val) || !Z_REFCOUNTED_P(that._val)) { // because this is supposed to be a COPY, we can not add ourselves // to the variable but have to allocate a new variable - ALLOC_ZVAL(_val); - INIT_PZVAL_COPY(_val, that._val); - - // we have to call the copy constructor to copy the entire other zval - zval_copy_ctor(_val); + _val = new zval; + ZVAL_COPY_VALUE(_val, that._val); } else { // simply use the same zval _val = that._val; - } - // that zval has one more reference - Z_ADDREF_P(_val); - - -// Below the old implementation - I thought really hard about it and I though -// it was a correct and very smart implementation. However, it does not work -// when you swap two variables. I changed it to the implementation above, but -// maybe that implementation introduces other bugs??? Let's keep the old -// implementation for a while in this file, but commented out -// -// // how many references does the other object have? -// if (Z_REFCOUNT_P(that._val) > 1 && !Z_ISREF_P(that._val)) -// { -// // there are already multiple variables linked to this value, and it -// // is not a reference. this implies that we can not turn this variable -// // into a reference, otherwise strange things could happen, we're going -// // to create a new zval -// ALLOC_ZVAL(_val); -// INIT_PZVAL_COPY(_val, that._val); -// zval_copy_ctor(_val); -// } -// else -// { -// // simply use the same zval -// _val = that._val; -// } -// -// // the other object only has one variable using it, or it is already -// // a variable by reference, we can safely add one more reference to it -// // and make it a variable by reference if it was not already a ref -// Z_ADDREF_P(_val); -// -// // make reference -// Z_SET_ISREF_P(_val); + // and increment the reference count + Z_ADDREF_P(_val); + } } /** @@ -299,14 +240,29 @@ Value::~Value() // ignore if moved if (!_val) return; - // if there were two references or less, we're going to remove a reference - // and only one reference will remain, the object will then impossible be - // a reference - if (Z_REFCOUNT_P(_val) <= 2) Z_UNSET_ISREF_P(_val); + // copy to local variable + auto val = _val; - // destruct the zval (this function will decrement the reference counter, - // and only destruct if there are no other references left) - zval_ptr_dtor(&_val); + // reset local + _val = nullptr; + + // are we not a refcounted variable? + if (!Z_REFCOUNTED_P(val)) + { + // we can simply delete it + delete val; + } + else + { + // if there were two references or less, we're going to remove a reference + // and only one reference will remain, the object will then impossible be + // a reference + if (Z_REFCOUNT_P(val) <= 2) ZVAL_UNREF(val); + + // destruct the zval (this function will decrement the reference counter, + // and only destruct if there are no other references left) + zval_ptr_dtor(val); + } } /** @@ -317,7 +273,7 @@ Value::~Value() * deallocate the zval structure. This is used for functions that have to * return a zval pointer, that would otherwise be deallocated the moment * the function returns. - * + * * @param keeprefcount * @return zval */ @@ -331,12 +287,12 @@ zval *Value::detach(bool keeprefcount) // reset internal object _val = nullptr; - + // we're ready if we should keep the refcounter if (keeprefcount) return result; // decrement reference counter - Z_DELREF_P(result); + Z_TRY_DELREF_P(result); // done return result; @@ -348,6 +304,10 @@ zval *Value::detach(bool keeprefcount) */ int Value::refcount() const { + // are we not a refcounted variable? + if (!Z_REFCOUNTED_P(_val)) return 0; + + // we are, retrieve the count return Z_REFCOUNT_P(_val); } @@ -375,7 +335,7 @@ Value &Value::operator=(Value &&value) _NOEXCEPT *_val = *value._val; // restore reference and refcount setting - Z_SET_ISREF_TO_P(_val, true); + ZVAL_MAKE_REF(_val); Z_SET_REFCOUNT_P(_val, refcount); // how many references did the old variable have? @@ -398,7 +358,7 @@ Value &Value::operator=(Value &&value) _NOEXCEPT // the last and only reference to the other object was // removed, we no longer need it - FREE_ZVAL(value._val); + delete value._val; // the other object is no longer valid value._val = nullptr; @@ -408,7 +368,7 @@ Value &Value::operator=(Value &&value) _NOEXCEPT { // destruct the zval (this function will decrement the reference counter, // and only destruct if there are no other references left) - if (_val) zval_ptr_dtor(&_val); + if (_val) zval_ptr_dtor(_val); // just copy the zval completely _val = value._val; @@ -447,20 +407,20 @@ Value &Value::operator=(const Value &value) zval_copy_ctor(_val); // restore refcount and reference setting - Z_SET_ISREF_TO_P(_val, true); + ZVAL_MAKE_REF(_val); Z_SET_REFCOUNT_P(_val, refcount); } else { // destruct the zval (this function will decrement the reference counter, // and only destruct if there are no other references left) - zval_ptr_dtor(&_val); + zval_ptr_dtor(_val); // just copy the zval, and the refcounter _val = value._val; // and we have one more reference - Z_ADDREF_P(_val); + Z_TRY_ADDREF_P(_val); } // update the object @@ -476,7 +436,7 @@ Value &Value::operator=(const Value &value) Value &Value::operator=(std::nullptr_t value) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // deallocate current zval (without cleaning the zval structure) zval_dtor(_val); @@ -496,7 +456,7 @@ Value &Value::operator=(std::nullptr_t value) Value &Value::operator=(int16_t value) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // deallocate current zval (without cleaning the zval structure) zval_dtor(_val); @@ -516,7 +476,7 @@ Value &Value::operator=(int16_t value) Value &Value::operator=(int32_t value) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // deallocate current zval (without cleaning the zval structure) zval_dtor(_val); @@ -536,7 +496,7 @@ Value &Value::operator=(int32_t value) Value &Value::operator=(int64_t value) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // deallocate current zval (without cleaning the zval structure) zval_dtor(_val); @@ -556,7 +516,7 @@ Value &Value::operator=(int64_t value) Value &Value::operator=(bool value) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // deallocate current zval (without cleaning the zval structure) zval_dtor(_val); @@ -576,13 +536,13 @@ Value &Value::operator=(bool value) Value &Value::operator=(char value) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // deallocate current zval (without cleaning the zval structure) zval_dtor(_val); // set new value - ZVAL_STRINGL(_val, &value, 1, 1); + ZVAL_STRINGL(_val, &value, 1); // update the object return *this; @@ -596,13 +556,13 @@ Value &Value::operator=(char value) Value &Value::operator=(const std::string &value) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // deallocate current zval (without cleaning the zval structure) zval_dtor(_val); // set new value - ZVAL_STRINGL(_val, value.c_str(), value.size(), 1); + ZVAL_STRINGL(_val, value.c_str(), value.size()); // update the object return *this; @@ -616,13 +576,13 @@ Value &Value::operator=(const std::string &value) Value &Value::operator=(const char *value) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // deallocate current zval (without cleaning the zval structure) zval_dtor(_val); // set new value - ZVAL_STRING(_val, value, 1); + ZVAL_STRINGL(_val, value, ::strlen(value)); // update the object return *this; @@ -636,7 +596,7 @@ Value &Value::operator=(const char *value) Value &Value::operator=(double value) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // deallocate current zval (without cleaning the zval structure) zval_dtor(_val); @@ -844,6 +804,9 @@ Value Value::operator()() const */ bool Value::isCallable(const char *name) { + // this only makes sense if we are an object + if (!isObject()) return false; + // wrap the name in a Php::Value object to get a zval Value method(name); @@ -851,7 +814,7 @@ bool Value::isCallable(const char *name) TSRMLS_FETCH(); // ask zend nicely whether the function is callable - return zend_is_callable_ex(method._val, _val, IS_CALLABLE_CHECK_NO_ACCESS, nullptr, nullptr, nullptr, nullptr TSRMLS_CC); + return zend_is_callable_ex(method._val, Z_OBJ_P(_val), IS_CALLABLE_CHECK_NO_ACCESS, nullptr, nullptr, nullptr TSRMLS_CC); } /** @@ -880,25 +843,25 @@ Value Value::call(const char *name) * Helper function that runs the actual call * @param object The object to call it on * @param method The function or method to call - * @param args Number of arguments - * @param params The parameters + * @param argc Number of arguments + * @param argv The parameters * @return Value */ -static Value do_exec(zval *const *object, zval *method, int argc, zval ***params) +static Value do_exec(const zval *object, zval *method, int argc, zval *argv) { // the return zval - zval *retval = nullptr; + zval retval; // we need the tsrm_ls variable TSRMLS_FETCH(); // the current exception - zval *oldException = EG(exception); + // zend_object *oldException = EG(exception); // call the function // we're casting the const away here, object is only const so we can call this method // from const methods after all.. - if (call_user_function_ex(CG(function_table), (zval**) object, method, &retval, argc, params, 1, NULL TSRMLS_CC) != SUCCESS) + if (call_user_function_ex(CG(function_table), (zval*) object, method, &retval, argc, argv, 1, nullptr TSRMLS_CC) != SUCCESS) { // throw an exception, the function does not exist throw Exception("Invalid call to "+Value(method).stringValue()); @@ -910,14 +873,15 @@ static Value do_exec(zval *const *object, zval *method, int argc, zval ***params { // was an exception thrown inside the function? 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: make OrigException except a zend_object instance + // if (oldException != EG(exception) && EG(exception)) throw OrigException(EG(exception) TSRMLS_CC); + // leap out if nothing was returned - if (!retval) return nullptr; - + if (Z_ISUNDEF(retval)) return nullptr; + // wrap the retval in a value - Php::Value result(retval); - + Php::Value result(&retval); + // destruct the retval (this just decrements the refcounter, which is ok, because // it is already wrapped in a Php::Value so still has 1 reference) zval_ptr_dtor(&retval); @@ -933,8 +897,14 @@ static Value do_exec(zval *const *object, zval *method, int argc, zval ***params * @param argv The parameters * @return Value */ -Value Value::exec(int argc, zval ***params) const +Value Value::exec(int argc, Value *argv) const { + // array of zvals to execute + zval params[argc]; + + // convert all the values + for(int i = 0; i < argc; i++) { params[i] = *argv[i]._val; } + // call helper function return do_exec(nullptr, _val, argc, params); } @@ -946,13 +916,19 @@ Value Value::exec(int argc, zval ***params) const * @param argv The parameters * @return Value */ -Value Value::exec(const char *name, int argc, struct _zval_struct ***params) const +Value Value::exec(const char *name, int argc, Value *argv) const { // wrap the name in a Php::Value object to get a zval Value method(name); + // array of zvals to execute + zval params[argc]; + + // convert all the values + for(int i = 0; i < argc; i++) { params[i] = *argv[i]._val; } + // call helper function - return do_exec(&_val, method._val, argc, params); + return do_exec(_val, method._val, argc, params); } /** @@ -962,13 +938,19 @@ Value Value::exec(const char *name, int argc, struct _zval_struct ***params) con * @param argv The parameters * @return Value */ -Value Value::exec(const char *name, int argc, struct _zval_struct ***params) +Value Value::exec(const char *name, int argc, Value *argv) { // wrap the name in a Php::Value object to get a zval Value method(name); + // array of zvals to execute + zval params[argc]; + + // convert all the values + for(int i = 0; i < argc; i++) { params[i] = *argv[i]._val; } + // call helper function - return do_exec(&_val, method._val, argc, params); + return do_exec(_val, method._val, argc, params); } /** @@ -1031,24 +1013,29 @@ Value &Value::setType(Type type) if (this->type() == type) return *this; // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // run the conversion, when it fails we throw a fatal error which will // in the end result in a zend_error() call. This FatalError class is necessary // because a direct call to zend_error() will do a longjmp() which may not // clean up the C++ objects created by the extension switch (type) { - case Type::Null: convert_to_null(_val); break; - case Type::Numeric: convert_to_long(_val); break; - case Type::Float: convert_to_double(_val); break; - case Type::Bool: convert_to_boolean(_val); break; - case Type::Array: convert_to_array(_val); break; - case Type::Object: convert_to_object(_val); break; - case Type::String: convert_to_string(_val); break; - case Type::Resource: throw FatalError("Resource types can not be handled by the PHP-CPP library"); break; - case Type::Constant: throw FatalError("Constant types can not be assigned to a PHP-CPP library variable"); break; - case Type::ConstantArray: throw FatalError("Constant types can not be assigned to a PHP-CPP library variable"); break; - case Type::Callable: throw FatalError("Callable types can not be assigned to a PHP-CPP library variable"); break; + case Type::Undefined: throw FatalError{ "Cannot make a variable undefined" }; break; + case Type::Null: convert_to_null(_val); break; + case Type::Numeric: convert_to_long(_val); break; + case Type::Float: convert_to_double(_val); break; + case Type::Bool: convert_to_boolean(_val); break; + case Type::False: convert_to_boolean(_val); ZVAL_FALSE(_val); break; + case Type::True: convert_to_boolean(_val); ZVAL_TRUE(_val); break; + case Type::Array: convert_to_array(_val); break; + case Type::Object: convert_to_object(_val); break; + case Type::String: convert_to_string(_val); break; + case Type::Resource: throw FatalError{ "Resource types can not be handled by the PHP-CPP library" }; break; + case Type::Constant: throw FatalError{ "Constant types can not be assigned to a PHP-CPP library variable" }; break; + case Type::ConstantAST: throw FatalError{ "Constant types can not be assigned to a PHP-CPP library variable" }; break; + case Type::Callable: throw FatalError{ "Callable types can not be assigned to a PHP-CPP library variable" }; break; + case Type::Reference: throw FatalError{ "Reference types cannot be assigned to a PHP-CPP library variable" }; break; + } // done @@ -1081,9 +1068,6 @@ zend_class_entry *Value::classEntry(bool allowString) const // is this an object if (isObject()) { - // should have a class entry - if (!HAS_CLASS_ENTRY(*_val)) return nullptr; - // class entry can be easily found return Z_OBJCE_P(_val); } @@ -1092,14 +1076,8 @@ zend_class_entry *Value::classEntry(bool allowString) const // the value is not an object, is this allowed? if (!allowString || !isString()) return nullptr; - // temporary variable - zend_class_entry **ce; - // find the class entry - if (zend_lookup_class(Z_STRVAL_P(_val), Z_STRLEN_P(_val), &ce TSRMLS_CC) == FAILURE) return nullptr; - - // found the entry - return *ce; + return zend_lookup_class(Z_STR_P(_val) TSRMLS_CC); } } @@ -1123,20 +1101,14 @@ bool Value::instanceOf(const char *classname, size_t size, bool allowString) con zend_class_entry *this_ce = classEntry(allowString); if (!this_ce) return false; - // class entry of the parameter - zend_class_entry **ce; - // now we can look up the actual class - // the signature of zend_lookup_class_ex is slightly different since 5.4 - // TODO The signature of this changed once again as of 5.6! -#if PHP_VERSION_ID >= 50400 - if (zend_lookup_class_ex(classname, size, NULL, 0, &ce TSRMLS_CC) == FAILURE) return false; -#else - if (zend_lookup_class_ex(classname, size, 0, &ce TSRMLS_CC) == FAILURE) return false; -#endif + auto *ce = zend_lookup_class_ex(zend_string_init(classname, size, 1), nullptr, 0 TSRMLS_CC); + + // no such class, then we are not instanceof + if (!ce) return false; // check if this is a subclass - return instanceof_function(this_ce, *ce TSRMLS_CC); + return instanceof_function(this_ce, ce TSRMLS_CC); } /** @@ -1159,23 +1131,17 @@ bool Value::derivedFrom(const char *classname, size_t size, bool allowString) co zend_class_entry *this_ce = classEntry(allowString); if (!this_ce) return false; - // class entry of the parameter - zend_class_entry **ce; - // now we can look up the actual class - // the signature of zend_lookup_class_ex is slightly different since 5.4 - // TODO The signature of this changed once again as of 5.6! -#if PHP_VERSION_ID >= 50400 - if (zend_lookup_class_ex(classname, size, NULL, 0, &ce TSRMLS_CC) == FAILURE) return false; -#else - if (zend_lookup_class_ex(classname, size, 0, &ce TSRMLS_CC) == FAILURE) return false; -#endif + auto *ce = zend_lookup_class_ex(zend_string_init(classname, size, 1), nullptr, 0 TSRMLS_CC); + + // unable to find the class entry? + if (!ce) return false; // should not be identical, it must be a real derived object - if (this_ce == *ce) return false; + if (this_ce == ce) return false; // check if this is a subclass - return instanceof_function(this_ce, *ce TSRMLS_CC); + return instanceof_function(this_ce, ce TSRMLS_CC); } /** @@ -1184,23 +1150,11 @@ bool Value::derivedFrom(const char *classname, size_t size, bool allowString) co */ Value Value::clone() const { - // the zval that will hold the copy - zval *copy; - - // allocate memory - ALLOC_ZVAL(copy); - - // copy the data - INIT_PZVAL_COPY(copy, _val); - - // run the copy constructor to ensure that everything gets copied - zval_copy_ctor(copy); + // value to clone to + Value output; - // wrap it using the Value(zval*) constructor, this will +1 the refcount!!!! - Value output(copy); - - // -1 the refcount to avoid future leaks - Z_DELREF_P(copy); + // copy the value over to the output + ZVAL_COPY_VALUE(output._val, _val); // done return output; @@ -1213,11 +1167,14 @@ Value Value::clone() const */ Value Value::clone(Type type) const { - // regular clone if nothing changes - if (this->type() == type) return clone(); + // first create the clone + auto cloned = clone(); - // make a clone - return clone().setType(type); + // should we change the type + if (this->type() != type) cloned.setType(type); + + // return the finished clone + return cloned; } /** @@ -1239,11 +1196,19 @@ int64_t Value::numericValue() const */ bool Value::boolValue() const { - // already a bool? - if (isBool()) return Z_BVAL_P(_val); - - // make a clone - return clone(Type::Bool).boolValue(); + // what variable type do we hold? + switch (type()) + { + case Type::Undefined: return false; + case Type::Null: return false; + case Type::False: return false; + case Type::True: return true; + case Type::Numeric: return numericValue(); + case Type::Float: return floatValue(); + case Type::String: return size(); + case Type::Array: return size(); + default: return clone(Type::Bool).boolValue(); + } } /** @@ -1252,10 +1217,19 @@ bool Value::boolValue() const */ std::string Value::stringValue() const { - // already a string? - if (isString()) return std::string(Z_STRVAL_P(_val), Z_STRLEN_P(_val)); + // what kind of casting do we need to do? + switch (type()) + { + case Type::Null: return {}; + case Type::False: return "0"; + case Type::True: return "1"; + case Type::Numeric: return std::to_string(numericValue()); + case Type::Float: return std::to_string(floatValue()); + case Type::String: return { Z_STRVAL_P(_val), Z_STRLEN_P(_val) }; + default: break; + } - // make a clone + // clone the value and convert it to a string return clone(Type::String).stringValue(); } @@ -1273,36 +1247,6 @@ char *Value::buffer() const } /** - * Reserve enough space - * @param size - * @return char* - */ -char *Value::reserve(size_t size) -{ - // must be a string - setType(Type::String); - - // is the current buffer too small? - if (Z_STRLEN_P(_val) < (int)size) - { - // is there already a buffer? - if (!Z_STRVAL_P(_val)) Z_STRVAL_P(_val) = (char *)emalloc(size+1); - - // reallocate an existing buffer - else Z_STRVAL_P(_val) = (char *)erealloc(Z_STRVAL_P(_val), size+1); - - // last byte should be zero - Z_STRVAL_P(_val)[size] = 0; - } - - // store size - Z_STRLEN_P(_val) = size; - - // done - return Z_STRVAL_P(_val); -} - -/** * Get access to the raw buffer for read operations. Note that this * only works for string variables - other variables return nullptr. * @@ -1413,7 +1357,7 @@ ValueIterator Value::createIterator(bool begin) const TSRMLS_FETCH(); // is a special iterator method defined in the class entry? - auto *entry = zend_get_class_entry(_val TSRMLS_CC); + auto *entry = Z_OBJCE_P(_val); // check if there is an iterator if (entry->get_iterator) @@ -1476,14 +1420,12 @@ bool Value::contains(int index) const { // if we're an object implementing ArrayAccess it makes sense for this method to work as well, so we call offsetExists if (isObject() && instanceOf("ArrayAccess")) return call("offsetExists", index).boolValue(); + // must be an array else if (!isArray()) return false; - // unused variable - zval **result; - // check if this index is already in the array - return zend_hash_index_find(Z_ARRVAL_P(_val), index, (void**)&result) != FAILURE; + return zend_hash_index_find(Z_ARRVAL_P(_val), index) != nullptr; } /** @@ -1497,14 +1439,11 @@ bool Value::contains(const char *key, int size) const // calculate size if (size < 0) size = ::strlen(key); - // unused variable - zval **result; - // deal with arrays if (isArray()) { // check if index is already in the array - return zend_hash_find(Z_ARRVAL_P(_val), key, size+1, (void **)&result) != FAILURE; + return zend_hash_find(Z_ARRVAL_P(_val), zend_string_init(key, size, 1)) != nullptr; } else if (isObject()) { @@ -1513,7 +1452,7 @@ bool Value::contains(const char *key, int size) const // retrieve the object pointer and check whether the property we are trying to retrieve // is marked as private/protected (cast necessary for php 5.3) - if (zend_check_property_access(zend_objects_get_address(_val TSRMLS_CC), const_cast<char *>(key), size TSRMLS_CC) == FAILURE) return false; + if (zend_check_property_access(Z_OBJ_P(_val), zend_string_init(key, size, 1) TSRMLS_CC) == FAILURE) return false; // check if the 'has_property' method is available for this object auto *has_property = Z_OBJ_HT_P(_val)->has_property; @@ -1526,11 +1465,7 @@ bool Value::contains(const char *key, int size) const // call the has_property() method (0 means: check whether property exists and is not NULL, // this is not really what we want, but the closest to the possible values of that parameter) -#if PHP_VERSION_ID >= 50400 return has_property(_val, property._val, 0, nullptr TSRMLS_CC); -#else - return has_property(_val, property._val, 0 TSRMLS_CC); -#endif } else { @@ -1549,19 +1484,19 @@ Value Value::get(int index) const // if we're an actual normal array we just use the zend_hash_index_find method if (isArray()) { - // zval to retrieve - zval **result; + // retrieve the value + auto *result = zend_hash_index_find(Z_ARRVAL_P(_val), index); - // check if index is in the array - if (zend_hash_index_find(Z_ARRVAL_P(_val), index, (void **)&result) == FAILURE) return Value(); + // did the offset exist? + if (!result) return Type::Undefined; // wrap the value - return Value(*result); + return result; } // if we're an object implementing ArrayAccess it makes sense for this method to work as well, so we call offsetGet else if (isObject() && instanceOf("ArrayAccess")) return call("offsetGet", index); // if we're neither we return an empty value - else return Value(); + else return Type::Undefined; } /** @@ -1581,14 +1516,8 @@ Value Value::get(const char *key, int size) const // are we in an object or an array? if (isArray()) { - // the result value - zval **result; - - // check if this index is already in the array, otherwise we return NULL - if (zend_hash_find(Z_ARRVAL_P(_val), key, size + 1, (void **)&result) == FAILURE) return Value(); - - // wrap the value - return Value(*result); + // find the result and wrap it in a value + return Value{ zend_hash_find(Z_ARRVAL_P(_val), zend_string_init(key, size, 1)) }; } else { @@ -1598,8 +1527,11 @@ Value Value::get(const char *key, int size) const // we need the tsrm_ls variable TSRMLS_FETCH(); - // read the property (cast necessary for php 5.3) - zval *property = zend_read_property(nullptr, _val, const_cast<char *>(key), size, 0 TSRMLS_CC); + // temporary value for holding any error + zval rv; + + // read the property + zval *property = zend_read_property(nullptr, _val, key, size, 0, &rv TSRMLS_CC); // wrap in value return Value(property); @@ -1616,13 +1548,13 @@ Value Value::get(const char *key, int size) const void Value::setRaw(int index, const Value &value) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // add the value (this will decrement refcount on any current variable) add_index_zval(_val, index, value._val); // the variable has one more reference (the array entry) - Z_ADDREF_P(value._val); + Z_TRY_ADDREF_P(value._val); } /** @@ -1634,13 +1566,13 @@ void Value::setRaw(int index, const Value &value) void Value::set(int index, const Value &value) { // the current value - zval **current; + zval *current; // check if this index is already in the array, otherwise we return NULL - if (isArray() && zend_hash_index_find(Z_ARRVAL_P(_val), index, (void **)¤t) != FAILURE) + if (isArray() && (current = zend_hash_index_find(Z_ARRVAL_P(_val), index))) { // skip if nothing is going to change - if (value._val == *current) return; + if (value._val == current) return; } // must be an array @@ -1665,24 +1597,24 @@ void Value::setRaw(const char *key, int size, const Value &value) if (isObject()) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // we need the tsrm_ls variable TSRMLS_FETCH(); - // update the property (cast necessary for php 5.3) - zend_update_property(nullptr, _val, const_cast<char *>(key), size, value._val TSRMLS_CC); + // update the property + zend_update_property(nullptr, _val, key, size, value._val TSRMLS_CC); } else { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // add the value (this will reduce the refcount of the current value) add_assoc_zval_ex(_val, key, size+1, value._val); // the variable has one more reference (the array entry) - Z_ADDREF_P(value._val); + Z_TRY_ADDREF_P(value._val); } } @@ -1696,13 +1628,13 @@ void Value::setRaw(const char *key, int size, const Value &value) void Value::set(const char *key, int size, const Value &value) { // the current value - zval **current; + zval *current; // check if this index is already in the array, otherwise we return NULL - if (isArray() && zend_hash_find(Z_ARRVAL_P(_val), key, size + 1, (void **)¤t) != FAILURE) + if (isArray() && (current = zend_hash_find(Z_ARRVAL_P(_val), zend_string_init(key, size, 1)))) { // skip if nothing is going to change - if (value._val == *current) return; + if (value._val == current) return; } // this should be an object or an array @@ -1722,7 +1654,7 @@ void Value::unset(int index) if (!isArray()) return; // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // remove the index zend_hash_index_del(Z_ARRVAL_P(_val), index); @@ -1739,7 +1671,7 @@ void Value::unset(const char *key, int size) if (isObject()) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // we need the tsrm_ls variable TSRMLS_FETCH(); @@ -1750,10 +1682,10 @@ void Value::unset(const char *key, int size) else if (isArray()) { // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); + SEPARATE_ZVAL_IF_NOT_REF(_val); // remove the index - zend_hash_del(Z_ARRVAL_P(_val), key, size + 1); + zend_hash_del(Z_ARRVAL_P(_val), zend_string_init(key, size, 1)); } } |