summaryrefslogtreecommitdiff
path: root/zend
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-08-26 13:08:39 +0200
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-08-26 13:08:39 +0200
commitf526c4c7a7ada1ab534ca0976c7b66718b9a5660 (patch)
tree68b4db309ee00d3b7ebc1743b4825057b3db7f28 /zend
parenta286c34777e48d59410e7511f52f4a6f08000ba3 (diff)
parent97bc6757346d394a4b7d5898983be298e0b0ea98 (diff)
fixed conflict
Diffstat (limited to 'zend')
-rw-r--r--zend/classimpl.cpp164
-rw-r--r--zend/classimpl.h25
-rw-r--r--zend/eval.cpp68
-rw-r--r--zend/exists.cpp77
-rw-r--r--zend/extension.cpp12
-rw-r--r--zend/extensionimpl.cpp12
-rw-r--r--zend/extensionimpl.h23
-rw-r--r--zend/fatalerror.cpp31
-rw-r--r--zend/hashiterator.h16
-rw-r--r--zend/includes.h1
-rw-r--r--zend/namespace.cpp12
-rw-r--r--zend/object.cpp23
-rw-r--r--zend/objectimpl.h2
-rw-r--r--zend/origexception.h6
-rw-r--r--zend/streambuf.cpp3
-rw-r--r--zend/value.cpp185
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));
}
}