diff options
author | Emiel Bruijntjes <emiel.bruijntjes@copernica.com> | 2014-08-26 13:08:39 +0200 |
---|---|---|
committer | Emiel Bruijntjes <emiel.bruijntjes@copernica.com> | 2014-08-26 13:08:39 +0200 |
commit | f526c4c7a7ada1ab534ca0976c7b66718b9a5660 (patch) | |
tree | 68b4db309ee00d3b7ebc1743b4825057b3db7f28 /zend | |
parent | a286c34777e48d59410e7511f52f4a6f08000ba3 (diff) | |
parent | 97bc6757346d394a4b7d5898983be298e0b0ea98 (diff) |
fixed conflict
Diffstat (limited to 'zend')
-rw-r--r-- | zend/classimpl.cpp | 164 | ||||
-rw-r--r-- | zend/classimpl.h | 25 | ||||
-rw-r--r-- | zend/eval.cpp | 68 | ||||
-rw-r--r-- | zend/exists.cpp | 77 | ||||
-rw-r--r-- | zend/extension.cpp | 12 | ||||
-rw-r--r-- | zend/extensionimpl.cpp | 12 | ||||
-rw-r--r-- | zend/extensionimpl.h | 23 | ||||
-rw-r--r-- | zend/fatalerror.cpp | 31 | ||||
-rw-r--r-- | zend/hashiterator.h | 16 | ||||
-rw-r--r-- | zend/includes.h | 1 | ||||
-rw-r--r-- | zend/namespace.cpp | 12 | ||||
-rw-r--r-- | zend/object.cpp | 23 | ||||
-rw-r--r-- | zend/objectimpl.h | 2 | ||||
-rw-r--r-- | zend/origexception.h | 6 | ||||
-rw-r--r-- | zend/streambuf.cpp | 3 | ||||
-rw-r--r-- | zend/value.cpp | 185 |
16 files changed, 544 insertions, 116 deletions
diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index d63956a..9c28a0a 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -20,11 +20,6 @@ ClassImpl::~ClassImpl() { // destruct the entries if (_entries) delete[] _entries; - - // php 5.3 deallocates the doc_comment by iself -#if PHP_VERSION_ID >= 50400 - if (_comment) free(_comment); -#endif } /** @@ -41,18 +36,21 @@ static ClassImpl *self(zend_class_entry *entry) // we need the base class (in user space the class may have been overridden, // but we are not interested in these user space classes) while (entry->parent) entry = entry->parent; - + #if PHP_VERSION_ID >= 50400 // retrieve the comment (it has a pointer hidden in it to the ClassBase object) const char *comment = entry->info.user.doc_comment; -#else - // retrieve the comment php5.3 style (it has a pointer hidden in it to the ClassBase object) - const char *comment = entry->doc_comment; -#endif - + // 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 + // 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 } /** @@ -330,52 +328,56 @@ int ClassImpl::getClosure(zval *object, zend_class_entry **entry_ptr, zend_funct */ zend_object_handlers *ClassImpl::objectHandlers() { - // keep static structure - static zend_object_handlers handlers; - - // is the object already initialized? - static bool initialized = false; - // already initialized? - if (initialized) return &handlers; + if (_initialized) return &_handlers; // initialize the handlers - memcpy(&handlers, &std_object_handlers, sizeof(zend_object_handlers)); + memcpy(&_handlers, &std_object_handlers, sizeof(zend_object_handlers)); // install custom clone function - if (!_base->clonable()) handlers.clone_obj = nullptr; - else handlers.clone_obj = &ClassImpl::cloneObject; + if (!_base->clonable()) _handlers.clone_obj = nullptr; + else _handlers.clone_obj = &ClassImpl::cloneObject; // functions for the Countable interface - handlers.count_elements = &ClassImpl::countElements; + _handlers.count_elements = &ClassImpl::countElements; // functions for the ArrayAccess interface - handlers.write_dimension = &ClassImpl::writeDimension; - handlers.read_dimension = &ClassImpl::readDimension; - handlers.has_dimension = &ClassImpl::hasDimension; - handlers.unset_dimension = &ClassImpl::unsetDimension; + _handlers.write_dimension = &ClassImpl::writeDimension; + _handlers.read_dimension = &ClassImpl::readDimension; + _handlers.has_dimension = &ClassImpl::hasDimension; + _handlers.unset_dimension = &ClassImpl::unsetDimension; // functions for the magic properties handlers (__get, __set, __isset and __unset) - handlers.write_property = &ClassImpl::writeProperty; - handlers.read_property = &ClassImpl::readProperty; - handlers.has_property = &ClassImpl::hasProperty; - handlers.unset_property = &ClassImpl::unsetProperty; + _handlers.write_property = &ClassImpl::writeProperty; + _handlers.read_property = &ClassImpl::readProperty; + _handlers.has_property = &ClassImpl::hasProperty; + _handlers.unset_property = &ClassImpl::unsetProperty; // when a method is called (__call and __invoke) - handlers.get_method = &ClassImpl::getMethod; - handlers.get_closure = &ClassImpl::getClosure; + _handlers.get_method = &ClassImpl::getMethod; + _handlers.get_closure = &ClassImpl::getClosure; // handler to cast to a different type - handlers.cast_object = &ClassImpl::cast; + _handlers.cast_object = &ClassImpl::cast; // method to compare two objects - handlers.compare_objects = &ClassImpl::compare; + _handlers.compare_objects = &ClassImpl::compare; // remember that object is now initialized - initialized = true; + _initialized = true; // done - return &handlers; + return &_handlers; +} + +/** + * Alternative way to retrieve object handlers, given a class entry + * @param entry + * @return zend_object_handlers + */ +zend_object_handlers *ClassImpl::objectHandlers(zend_class_entry *entry) +{ + return self(entry)->objectHandlers(); } /** @@ -465,7 +467,7 @@ int ClassImpl::cast(zval *val, zval *retval, int type TSRMLS_DC) case Type::Float: result = meta->callToFloat(object).detach(); break; case Type::Bool: result = meta->callToBool(object).detach(); break; case Type::String: result = meta->callToString(object).detach(); break; - default: throw NotImplemented(); break; + default: throw NotImplemented(); break; } // @todo do we turn into endless conversion if the __toString object returns 'this' ?? @@ -520,8 +522,10 @@ zend_object_value ClassImpl::cloneObject(zval *val TSRMLS_DC) // report error on failure (this does not occur because the cloneObject() // method is only installed as handler when we have seen that there is indeed - // a copy constructor) - if (!cpp) throw Php::Exception(std::string("Unable to clone ") + entry->name); + // a copy constructor). Because this function is directly called from the + // Zend engine, we can call zend_error() (which does a longjmp()) to throw + // an exception back to the Zend engine) + if (!cpp) zend_error(E_ERROR, "Unable to clone %s", entry->name); // the thing we're going to return zend_object_value result; @@ -1179,8 +1183,10 @@ zend_object_value ClassImpl::createObject(zend_class_entry *entry TSRMLS_DC) // create a new base C++ object auto *cpp = impl->_base->construct(); - // report error on failure - if (!cpp) throw Php::Exception(std::string("Unable to instantiate ") + entry->name); + // report error on failure, because this function is called directly from the + // Zend engine, we can call zend_error() here (which does a longjmp() back to + // the Zend engine) + if (!cpp) zend_error(E_ERROR, "Unable to instantiate %s", entry->name); // the thing we're going to return zend_object_value result; @@ -1208,8 +1214,10 @@ zend_object_value ClassImpl::createObject(zend_class_entry *entry TSRMLS_DC) */ zend_object_iterator *ClassImpl::getIterator(zend_class_entry *entry, zval *object, int by_ref TSRMLS_DC) { - // by-ref is not possible (copied from SPL) - if (by_ref) throw Php::Exception("Foreach by ref is not possible"); + // by-ref is not possible (copied from SPL), this function is called directly + // from the Zend engine, so we can use zend_error() to longjmp() back to the + // Zend engine) + if (by_ref) zend_error(E_ERROR, "Foreach by ref is not possible"); // retrieve the traversable object Traversable *traversable = dynamic_cast<Traversable*>(ObjectImpl::find(object TSRMLS_CC)->object()); @@ -1372,14 +1380,25 @@ void ClassImpl::initialize(ClassBase *base, const std::string &prefix TSRMLS_DC) if (_parent) { // check if the base class was already defined - if (_parent->_entry) entry.parent = _parent->_entry; - - // otherwise an error is reported - else std::cerr << "Derived class " << name() << " is initialized before base class " << _parent->name() << ": base class is ignored" << std::endl; + if (_parent->_entry) + { + // register the class + _entry = zend_register_internal_class_ex(&entry, _parent->_entry, const_cast<char*>(_parent->name().c_str()) TSRMLS_CC); + } + else + { + // report an error - the extension programmer probably made an error + std::cerr << "Derived class " << name() << " is initialized before base class " << _parent->name() << ": base class is ignored" << std::endl; + + // register the class, but without the base class + _entry = zend_register_internal_class(&entry TSRMLS_CC); + } + } + else + { + // register the class + _entry = zend_register_internal_class(&entry TSRMLS_CC); } - - // register the class - _entry = zend_register_internal_class(&entry TSRMLS_CC); // register the classes for (auto &interface : _interfaces) @@ -1390,34 +1409,37 @@ void ClassImpl::initialize(ClassBase *base, const std::string &prefix TSRMLS_DC) // otherwise report an error else std::cerr << "Derived class " << name() << " is initialized before base class " << interface->name() << ": interface is ignored" << std::endl; } - + + // 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 - if (!_comment) - { - // allocate now - _comment = (char *)malloc(1 + sizeof(ClassBase *)); - - // empty string on first position - _comment[0] = '\0'; - - // this pointer has to be copied to temporary pointer, as &this causes compiler error - ClassImpl *impl = this; - - // copy the 'this' pointer to the doc-comment - memcpy(_comment+1, &impl, sizeof(ClassImpl *)); - } - - // store pointer to the class in the unused doc_comment member -#if PHP_VERSION_ID >= 50400 + char *_comment = (char *)malloc(1 + sizeof(ClassImpl *)); + + // empty string on first position + _comment[0] = '\0'; + + // copy the 'this' pointer to the doc-comment + memcpy(_comment+1, &impl, sizeof(ClassImpl *)); + + // set our comment in the actual class entry _entry->info.user.doc_comment = _comment; + #else - // and store the wrapper inside the comment - _entry->doc_comment = _comment; + + // Reallocate some extra space in the name in the zend_class_entry so we can fit a pointer behind it + _entry->name = (char *) realloc(_entry->name, _entry->name_length + 1 + sizeof(ClassImpl *)); + + // Copy the pointer after it + memcpy(_entry->name + _entry->name_length + 1, &impl, sizeof(ClassImpl *)); + #endif // set access types flags for class _entry->ce_flags = (int)_type; - + // declare all member variables for (auto &member : _members) member->initialize(_entry TSRMLS_CC); } diff --git a/zend/classimpl.h b/zend/classimpl.h index ec28322..26cf030 100644 --- a/zend/classimpl.h +++ b/zend/classimpl.h @@ -32,12 +32,6 @@ private: std::string _name; /** - * The comment for reflexion, with a stored pointer to ourselves - * @var char* - */ - char *_comment = nullptr; - - /** * The class type (this can be values like Php::Abstract and Php::Final) * @var ClassType */ @@ -84,6 +78,18 @@ private: * @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 + */ + bool _initialized = false; /** @@ -204,6 +210,13 @@ public: zend_object_handlers *objectHandlers(); /** + * Alternative way to retrieve object handlers, given a class entry + * @param entry + * @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 * @param object The object to iterate over diff --git a/zend/eval.cpp b/zend/eval.cpp new file mode 100644 index 0000000..4632fff --- /dev/null +++ b/zend/eval.cpp @@ -0,0 +1,68 @@ +/** + * Eval.cpp + * + * This file holds the implementation for the Php::eval() function + * + * @author andot <https://github.com/andot> + */ + +/** + * Dependencies + */ +#include "includes.h" + +/** + * Open PHP namespace + */ +namespace Php { + +/** + * Evaluate a PHP string + * @param phpCode The PHP code to evaluate + * @return Value The result of the evaluation + */ +Value eval(const std::string &phpCode) +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // the current exception + zval* oldException = EG(exception); + + // the return zval + zval* retval = nullptr; + if (zend_eval_stringl_ex((char *)phpCode.c_str(), (int32_t)phpCode.length(), retval, (char *)"", 1 TSRMLS_CC) != SUCCESS) + { + // Do we want to throw an exception here? The original author + // did, but there are some reasons not to: + // + // 1. the PHP eval() function also does not throw exceptions. + // + // 2. the zend_eval_string() function already triggers a + // 'PHP parse error' when an error occurs, which also has + // to be handled. If we also throw an exception here, the + // user will have to write two error checks: for the error + // and the exception. + // + // if we _do_ want to throw an exception, we will first have to + // prevent the original zend_error to occur, and then turn it + // into an exception. An exception would be nicer from a C++ + // point of view, but because of the extra complexity, we do not + // this for now. + return nullptr; + } + else + { + // 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); + + // no (additional) exception was thrown + return retval ? Value(retval) : nullptr; + } +} + +/** + * End of namespace + */ +} diff --git a/zend/exists.cpp b/zend/exists.cpp new file mode 100644 index 0000000..1177e47 --- /dev/null +++ b/zend/exists.cpp @@ -0,0 +1,77 @@ +/** + * Exists.cpp + * + * This file holds the implementation of all *_exists() functions, + * like class_exists(), et cetera + * + * @author andot <https://github.com/andot> + */ + +/** + * Dependencies + */ +#include "includes.h" + +/** + * On php 5.3 ZEND_ACC_TRAIT isn't defined, so we simply define it to 0 + * so all operations with it are basically no-ops. Currently unconfirmed + * if this actually works correctly on php 5.3, but it at least compiles. + */ +#ifndef ZEND_ACC_TRAIT +#define ZEND_ACC_TRAIT 0 +#endif + +/** + * Open the PHP namespace + */ +namespace Php { + +/** + * Check whether a class with a certain name exists + * @param classname + * @param len + * @param autoload + * @return bool + */ +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) + { + // no auto-load + if (SUCCESS != zend_lookup_class(classname, len, &ce TSRMLS_CC)) 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; + } + 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]); + + // copy the name to lowercase, but ignore the starting slash (if there is one) + zend_str_tolower_copy(lc_name.get(), 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); + } +} + +/** + * End of namespace + */ +} diff --git a/zend/extension.cpp b/zend/extension.cpp index 9685b32..1545f89 100644 --- a/zend/extension.cpp +++ b/zend/extension.cpp @@ -97,6 +97,18 @@ void *Extension::module() } /** + * Is the extension object in a locked state? This happens after the + * get_module() function was called for the first time ("apache reload" + * forces a new call to get_module()) + * + * @return bool + */ +bool Extension::locked() const +{ + return _impl->locked(); +} + +/** * End of namespace */ } diff --git a/zend/extensionimpl.cpp b/zend/extensionimpl.cpp index ea0c049..f073acf 100644 --- a/zend/extensionimpl.cpp +++ b/zend/extensionimpl.cpp @@ -142,6 +142,10 @@ int ExtensionImpl::processStartup(int type, int module_number TSRMLS_DC) // initialize the extension extension->initialize(TSRMLS_C); + // remember that we're initialized (when you use "apache reload" it is + // possible that the processStartup() method is called more than once) + extension->_locked = true; + // is the callback registered? if (extension->_onStartup) extension->_onStartup(); @@ -164,6 +168,12 @@ int ExtensionImpl::processShutdown(int type, int module_number TSRMLS_DC) // unregister the ini entries zend_unregister_ini_entries(module_number TSRMLS_CC); + // destruct the ini entries + if (extension->_ini) delete[] extension->_ini; + + // forget the ini entries + extension->_ini = nullptr; + // is the callback registered? if (extension->_onShutdown) extension->_onShutdown(); @@ -272,7 +282,7 @@ ExtensionImpl::~ExtensionImpl() */ zend_module_entry *ExtensionImpl::module() { - // check if functions we're already defined + // check if functions were already defined if (_entry.functions) return &_entry; // the number of functions diff --git a/zend/extensionimpl.h b/zend/extensionimpl.h index ba3e6c8..0e9a289 100644 --- a/zend/extensionimpl.h +++ b/zend/extensionimpl.h @@ -20,16 +20,18 @@ class ExtensionImpl : public ExtensionBase protected: /** * The information that is passed to the Zend engine - * - * Although it would be slightly faster to not make this a pointer, this - * would require that client code also includes the PHP header files, which - * we try to prevent with the PHP-CPP library, so we allocate it dynamically. - * * @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 @@ -56,6 +58,17 @@ public: */ virtual ~ExtensionImpl(); + /** + * Is the object locked (true) or is it still possible to add more functions, + * classes and other elements to it? + * @return bool + */ + bool locked() + { + // return member + return _locked; + } + /** * Retrieve the module entry * diff --git a/zend/fatalerror.cpp b/zend/fatalerror.cpp new file mode 100644 index 0000000..e57be73 --- /dev/null +++ b/zend/fatalerror.cpp @@ -0,0 +1,31 @@ +/** + * FatalError.cpp + * + * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Report this error as a fatal error + * @return bool + */ +bool FatalError::report() const +{ + // report the error + zend_error(E_ERROR, "%s", what()); + + // return true: it was reported + return true; +} + +/** + * End of namespace + */ +} + diff --git a/zend/hashiterator.h b/zend/hashiterator.h index b1f409f..36c3d6c 100644 --- a/zend/hashiterator.h +++ b/zend/hashiterator.h @@ -29,7 +29,7 @@ public: * @param first Should it start on the first position? * @param tsrm_ls */ - HashIterator(HashTable *hashtable, bool first) : _table(hashtable) + 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) @@ -56,7 +56,7 @@ public: * @param tsrm_ls */ HashIterator(const HashIterator &that TSRMLS_DC) : - _table(that._table), _position(that._position) + _table(that._table), _position(that._position), _is_array(that._is_array) { // read current position read(); @@ -166,7 +166,13 @@ private: * @var HashPosition */ Bucket *_position = nullptr; - + + /** + * Is a hash interator in array + * @var bool + */ + bool _is_array = false; + /** * The current key and value * @var std::pair @@ -207,7 +213,7 @@ private: // numeric keys are the easiest ones if (type == HASH_KEY_IS_LONG) key = (int64_t)num_key; - else key = string_key; + else key = std::string(string_key, str_len - 1); #endif @@ -223,7 +229,7 @@ private: // 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 - return !_current.first.isString() || _current.first.rawValue()[0]; + return _is_array || !_current.first.isString() || _current.first.rawValue()[0]; } /** diff --git a/zend/includes.h b/zend/includes.h index cabb096..63b435e 100644 --- a/zend/includes.h +++ b/zend/includes.h @@ -48,6 +48,7 @@ #include "../include/inivalue.h" #include "../include/ini.h" #include "../include/exception.h" +#include "../include/fatalerror.h" #include "../include/streams.h" #include "../include/type.h" #include "../include/hashparent.h" diff --git a/zend/namespace.cpp b/zend/namespace.cpp index 2b4b62a..9274bf0 100644 --- a/zend/namespace.cpp +++ b/zend/namespace.cpp @@ -22,6 +22,9 @@ namespace Php { */ Namespace &Namespace::add(const char *name, const native_callback_0 &function, const Arguments &arguments) { + // skip when locked + if (locked()) return *this; + // add a function _functions.push_back(std::make_shared<Function>(name, function, arguments)); @@ -38,6 +41,9 @@ Namespace &Namespace::add(const char *name, const native_callback_0 &function, c */ Namespace &Namespace::add(const char *name, const native_callback_1 &function, const Arguments &arguments) { + // skip when locked + if (locked()) return *this; + // add a function _functions.push_back(std::make_shared<Function>(name, function, arguments)); @@ -54,6 +60,9 @@ Namespace &Namespace::add(const char *name, const native_callback_1 &function, c */ Namespace &Namespace::add(const char *name, const native_callback_2 &function, const Arguments &arguments) { + // skip when locked + if (locked()) return *this; + // add a function _functions.push_back(std::make_shared<Function>(name, function, arguments)); @@ -70,6 +79,9 @@ Namespace &Namespace::add(const char *name, const native_callback_2 &function, c */ Namespace &Namespace::add(const char *name, const native_callback_3 &function, const Arguments &arguments) { + // skip when locked + if (locked()) return *this; + // add a function _functions.push_back(std::make_shared<Function>(name, function, arguments)); diff --git a/zend/object.cpp b/zend/object.cpp index 08553b5..dc548cc 100644 --- a/zend/object.cpp +++ b/zend/object.cpp @@ -22,7 +22,7 @@ Object::Object(const char *name, Base *base) // does the object already have a handle? if (base->implementation()) { - // the object is already instantiated, we can assign it the this object + // the object is already instantiated, we can assign it to this object operator=(Value(base)); } else @@ -32,9 +32,12 @@ Object::Object(const char *name, Base *base) // this is a brand new object that should be allocated, the C++ instance // is already there (created by the extension) but it is not yet stored - // in PHP, find out the classname first - auto *entry = zend_fetch_class(name, ::strlen(name), 0 TSRMLS_CC); - if (!entry) throw Php::Exception(std::string("Unknown class name ") + name); + // in PHP, find out the classname first (we use the FatalError class + // 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); + if (!entry) throw FatalError(std::string("Unknown class name ") + name); // construct an implementation (this will also set the implementation // member in the base object) @@ -42,6 +45,9 @@ Object::Object(const char *name, Base *base) // now we can store it operator=(Value(base)); + + // install the object handlers + Z_OBJVAL_P(_val).handlers = ClassImpl::objectHandlers(entry); } } @@ -54,9 +60,12 @@ void Object::instantiate(const char *name) // we need the tsrm_ls variable TSRMLS_FETCH(); - // convert the name into a class_entry - auto *entry = zend_fetch_class(name, ::strlen(name), 0 TSRMLS_CC); - if (!entry) throw Php::Exception(std::string("Unknown class name ") + name); + // convert the name into a class_entry (we use the FatalError class + // 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); + 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); diff --git a/zend/objectimpl.h b/zend/objectimpl.h index f66a6ab..7f16320 100644 --- a/zend/objectimpl.h +++ b/zend/objectimpl.h @@ -66,7 +66,7 @@ public: */ ObjectImpl(zend_class_entry *entry, Base *base TSRMLS_DC) { - // allocate a mixed object (for some reason this does not have to deallocated) + // allocate a mixed object (for some reason this does not have to be deallocated) _mixed = (MixedObject *)emalloc(sizeof(MixedObject)); // copy properties to the mixed object diff --git a/zend/origexception.h b/zend/origexception.h index 775e412..55f89cf 100644 --- a/zend/origexception.h +++ b/zend/origexception.h @@ -127,10 +127,12 @@ 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); } - else + + // 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 extensiont, we are + // 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); diff --git a/zend/streambuf.cpp b/zend/streambuf.cpp index 86e5f03..11d928d 100644 --- a/zend/streambuf.cpp +++ b/zend/streambuf.cpp @@ -33,7 +33,6 @@ int StreamBuf::sync() // not null terminated and (2) it could contain % signs and allow all // sorts of buffer overflows. zend_error(_error, "%.*s", (int)size, pbase()); - } else { @@ -52,4 +51,4 @@ int StreamBuf::sync() * End namespace */ } -
\ No newline at end of file + diff --git a/zend/value.cpp b/zend/value.cpp index d3ad06f..ac999c7 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -124,17 +124,18 @@ Value::Value(const std::string &value) */ Value::Value(const char *value, int size) { + // allocate the zval + MAKE_STD_ZVAL(_val); + // is there a value? if (value) { // create a string zval - MAKE_STD_ZVAL(_val); ZVAL_STRINGL(_val, value, size < 0 ? ::strlen(value) : size, 1); } else { - // create a null zval - MAKE_STD_ZVAL(_val); + // store null ZVAL_NULL(_val); } } @@ -191,7 +192,7 @@ Value::Value(const Base *object) auto *impl = object->implementation(); // do we have a handle? - if (!impl) throw Php::Exception("Assigning an unassigned object to a variable"); + 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); @@ -402,7 +403,7 @@ Value &Value::operator=(Value &&value) if (this == &value) return *this; // is the object a reference? - if (Z_ISREF_P(_val)) + if (_val && Z_ISREF_P(_val)) { // @todo difference if the other object is a reference or not? @@ -448,7 +449,7 @@ Value &Value::operator=(Value &&value) { // destruct the zval (this function will decrement the reference counter, // and only destruct if there are no other references left) - zval_ptr_dtor(&_val); + if (_val) zval_ptr_dtor(&_val); // just copy the zval completely _val = value._val; @@ -1311,6 +1312,45 @@ Value Value::exec(const char *name, int argc, struct _zval_struct ***params) } /** + * Comparison operators== for hardcoded Value + * @param value + */ +bool Value::operator==(const Value &value) const +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // zval that will hold the result of the comparison + zval result; + + // run the comparison + if (SUCCESS != compare_function(&result, _val, value._val TSRMLS_CC)) return false; + + // convert to boolean + return result.value.lval == 0; +} + +/** + * Comparison operators< for hardcoded Value + * @param value + * @return bool + */ +bool Value::operator<(const Value &value) const +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // zval that will hold the result of the comparison + zval result; + + // run the comparison + if (SUCCESS != compare_function(&result, _val, value._val TSRMLS_CC)) return false; + + // convert to boolean + return result.value.lval < 0; +} + +/** * The type of object * @return Type */ @@ -1333,7 +1373,10 @@ Value &Value::setType(Type type) // if this is not a reference variable, we should detach it to implement copy on write SEPARATE_ZVAL_IF_NOT_REF(&_val); - // run the conversion + // 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; @@ -1342,10 +1385,10 @@ Value &Value::setType(Type type) 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 Php::Exception("Resource types can not be handled by the PHP-CPP library"); break; - case Type::Constant: throw Php::Exception("Constant types can not be assigned to a PHP-CPP library variable"); break; - case Type::ConstantArray: throw Php::Exception("Constant types can not be assigned to a PHP-CPP library variable"); break; - case Type::Callable: throw Php::Exception("Callable types can not be assigned to a PHP-CPP library variable"); 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; } // done @@ -1366,6 +1409,116 @@ bool Value::isCallable() const } /** + * Retrieve the class entry + * @param allowString + * @return zend_class_entry + */ +zend_class_entry *Value::classEntry(bool allowString) const +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // 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); + } + else + { + // 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; + } +} + +/** + * Check whether this object is an instance of a certain class + * + * If you set the parameter 'allowString' to true, and the Value object + * holds a string, the string will be treated as class name. + * + * @param classname The class of which this should be an instance + * @param size Length of the classname string + * @param allowString Is it allowed for 'this' to be a string + * @return bool + */ +bool Value::instanceOf(const char *classname, size_t size, bool allowString) const +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // the class-entry of 'this' + 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 + + // check if this is a subclass + return instanceof_function(this_ce, *ce TSRMLS_CC); +} + +/** + * Check whether this object is derived from a certain class + * + * If you set the parameter 'allowString' to true, and the Value object + * holds a string, the string will be treated as class name. + * + * @param classname The class of which this should be an instance + * @param size Length of the classname string + * @param allowString Is it allowed for 'this' to be a string + * @return bool + */ +bool Value::derivedFrom(const char *classname, size_t size, bool allowString) const +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // the class-entry of 'this' + 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 + + // should not be identical, it must be a real derived object + if (this_ce == *ce) return false; + + // check if this is a subclass + return instanceof_function(this_ce, *ce TSRMLS_CC); +} + +/** * Make a clone of the type * @return Value */ @@ -1567,10 +1720,10 @@ std::map<std::string,Php::Value> Value::mapValue() const { // result variable std::map<std::string,Php::Value> result; - + // iterate over the object - for (auto &iter : *this) result[iter.first.rawValue()] = iter.second; - + for (auto &iter : *this) result[iter.first.stringValue()] = iter.second; + // done return result; } @@ -1583,7 +1736,7 @@ std::map<std::string,Php::Value> Value::mapValue() const ValueIterator Value::createIterator(bool begin) const { // check type - if (isArray()) return ValueIterator(new HashIterator(Z_ARRVAL_P(_val), begin)); + if (isArray()) return ValueIterator(new HashIterator(Z_ARRVAL_P(_val), begin, true)); // get access to the hast table if (isObject()) @@ -1604,7 +1757,7 @@ ValueIterator Value::createIterator(bool begin) const else { // construct a regular iterator - return ValueIterator(new HashIterator(Z_OBJ_HT_P(_val)->get_properties(_val TSRMLS_CC), begin)); + return ValueIterator(new HashIterator(Z_OBJPROP_P(_val), begin)); } } |