summaryrefslogtreecommitdiff
path: root/zend
diff options
context:
space:
mode:
Diffstat (limited to 'zend')
-rw-r--r--zend/callable.cpp12
-rw-r--r--zend/callable.h2
-rw-r--r--zend/classimpl.cpp69
-rw-r--r--zend/classimpl.h6
-rw-r--r--zend/compileroptions.h57
-rw-r--r--zend/eval.cpp145
-rw-r--r--zend/exists.cpp77
-rw-r--r--zend/file.cpp160
-rw-r--r--zend/hashiterator.h16
-rw-r--r--zend/includes.h5
-rw-r--r--zend/object.cpp44
-rw-r--r--zend/objectimpl.h18
-rw-r--r--zend/opcodes.cpp131
-rw-r--r--zend/parametersimpl.h2
-rw-r--r--zend/script.cpp60
-rw-r--r--zend/super.cpp4
-rw-r--r--zend/value.cpp574
17 files changed, 902 insertions, 480 deletions
diff --git a/zend/callable.cpp b/zend/callable.cpp
index a85ab72..f0dc3e8 100644
--- a/zend/callable.cpp
+++ b/zend/callable.cpp
@@ -51,14 +51,14 @@ void Callable::invoke(INTERNAL_FUNCTION_PARAMETERS)
{
// get the result
Value result(callable->invoke(params));
-
- // detach the zval (we don't want it to be destructed)
- zval *val = result.detach();
-
+
+ // 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(val, 1, 0);
+ RETVAL_ZVAL(result._val, 1, 0);
}
catch (Exception &exception)
{
diff --git a/zend/callable.h b/zend/callable.h
index 9958a2a..bd74f27 100644
--- a/zend/callable.h
+++ b/zend/callable.h
@@ -144,7 +144,7 @@ protected:
{
// fill members
info->name = arg.name();
- info->name_len = ::strlen(arg.name());
+ info->name_len = ::strlen(arg.name());
#if PHP_VERSION_ID >= 50400
diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp
index b2acc24..e42605e 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
}
/**
@@ -536,7 +534,7 @@ zend_object_value ClassImpl::cloneObject(zval *val TSRMLS_DC)
result.handlers = impl->objectHandlers();
// store the object
- ObjectImpl *new_object = new ObjectImpl(entry, cpp TSRMLS_CC);
+ ObjectImpl *new_object = new ObjectImpl(entry, cpp, 1 TSRMLS_CC);
// store the object in the object cache
result.handle = new_object->handle();
@@ -1197,7 +1195,7 @@ zend_object_value ClassImpl::createObject(zend_class_entry *entry TSRMLS_DC)
result.handlers = impl->objectHandlers();
// create the object in the zend engine
- ObjectImpl *object = new ObjectImpl(entry, cpp TSRMLS_CC);
+ ObjectImpl *object = new ObjectImpl(entry, cpp, 1 TSRMLS_CC);
// store the object in the object cache
result.handle = object->handle();
@@ -1411,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 bd631b8..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
*/
diff --git a/zend/compileroptions.h b/zend/compileroptions.h
new file mode 100644
index 0000000..798cb59
--- /dev/null
+++ b/zend/compileroptions.h
@@ -0,0 +1,57 @@
+/**
+ * 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>
+ * @copyright 2014 Copernica BV
+ */
+
+/**
+ * Open PHP namespace
+ */
+namespace Php {
+
+/**
+ * Class definition
+ */
+class CompilerOptions
+{
+private:
+ /**
+ * The original compiler options
+ * @var int
+ */
+ zend_uint _original;
+
+public:
+ /**
+ * Constructor
+ * @param options
+ */
+ CompilerOptions(zend_uint options)
+ {
+ // 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;
+ }
+
+ /**
+ * Destructor
+ */
+ virtual ~CompilerOptions()
+ {
+ // restore original options
+ CG(compiler_options) = _original;
+ }
+};
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/zend/eval.cpp b/zend/eval.cpp
new file mode 100644
index 0000000..674d39d
--- /dev/null
+++ b/zend/eval.cpp
@@ -0,0 +1,145 @@
+/**
+ * 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 va
+ zval *retval = nullptr;
+ MAKE_STD_ZVAL(retval);
+
+ // evaluate the string
+ 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 of this code
+ // 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);
+
+ // wrap the return value in a value object
+ Value result(retval);
+
+ // the retval should now have two references: the value object and the
+ // retval itselves, so we can remove one of it (the zval_ptr_dtor only
+ // decrements refcount and won't destruct anything because there still
+ // is one reference left inside the Value object)
+ zval_ptr_dtor(&retval);
+
+ // done
+ return result;
+ }
+}
+
+/**
+ * Include a file
+ * @param filename
+ * @return Value
+ */
+Value include(const std::string &filename)
+{
+ // we can simply execute a file
+ return File(filename).execute();
+}
+
+/**
+ * Include a file only once
+ * @param filename
+ * @return Value
+ */
+Value include_once(const std::string &filename)
+{
+ // we can simply execute a file
+ return File(filename).execute();
+}
+
+/**
+ * Require a file
+ * This causes a fatal error if the file does not exist
+ * @param filename
+ * @return Value
+ */
+Value require(const std::string &filename)
+{
+ // create the file
+ File file(filename);
+
+ // execute if it exists
+ if (file.exists()) return file.execute();
+
+ // trigger fatal error
+ error << filename << " does not exist" << std::flush;
+
+ // unreachable
+ return nullptr;
+}
+
+/**
+ * Require a file only once
+ * This causes a fatal error if the file does not exist
+ * @param filename
+ * @return Value
+ */
+Value require_once(const std::string &filename)
+{
+ // create the file
+ File file(filename);
+
+ // execute if it exists
+ if (file.exists()) return file.once();
+
+ // trigger fatal error
+ error << filename << " does not exist" << std::flush;
+
+ // unreachable
+ return 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/file.cpp b/zend/file.cpp
new file mode 100644
index 0000000..43c2d7a
--- /dev/null
+++ b/zend/file.cpp
@@ -0,0 +1,160 @@
+/**
+ * File.cpp
+ *
+ * Implementation file for the File class
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+
+/**
+ * Dependencies
+ */
+#include "includes.h"
+
+/**
+ * Namespace
+ */
+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.
+ *
+ * @param name the filename
+ * @param size length of the filename
+ */
+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);
+}
+
+/**
+ * Destructor
+ */
+File::~File()
+{
+ // clean up path name
+ if (_path) efree(_path);
+
+ // clean up opcodes
+ if (_opcodes) delete _opcodes;
+}
+
+/**
+ * Compile the file
+ * @return bool
+ */
+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;
+
+ // open the file
+ if (zend_stream_open(_path, &fileHandle TSRMLS_CC) == FAILURE) return false;
+
+ // 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);
+
+ // we need the tsrm_ls variable
+ TSRMLS_FETCH();
+
+ // create the opcodes
+ _opcodes = new Opcodes(zend_compile_file(&fileHandle, ZEND_INCLUDE TSRMLS_CC));
+
+ // close the file handle
+ zend_destroy_file_handle(&fileHandle TSRMLS_CC);
+
+ // done
+ return _opcodes->valid();
+}
+
+/**
+ * Does the file exist?
+ * @return boolean
+ */
+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;
+}
+
+/**
+ * Is this a valid file?
+ * @return boolean
+ */
+bool File::valid()
+{
+ // check if file is compilable
+ return compile();
+}
+
+/**
+ * Execute the file
+ * @return Value
+ */
+Value File::execute()
+{
+ // do we already have the opcodes?
+ if (_opcodes) return _opcodes->execute();
+
+ // try compiling the file
+ if (!compile()) return nullptr;
+
+ // add the entry to the list of included files
+ zend_hash_add_empty_element(&EG(included_files), _path, ::strlen(_path) + 1);
+
+ // execute the opcodes
+ return _opcodes->execute();
+}
+
+/**
+ * Execute a file only once
+ * @return Value
+ */
+Value File::once()
+{
+ // skip if the path is invalid
+ if (!_path) return nullptr;
+
+ // check if this file was already included
+ if (zend_hash_exists(&EG(included_files), _path, ::strlen(_path) + 1)) return nullptr;
+
+ // execute the file
+ return execute();
+}
+
+/**
+ * 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 63b435e..d6f2062 100644
--- a/zend/includes.h
+++ b/zend/includes.h
@@ -20,6 +20,7 @@
#include <list>
#include <exception>
#include <type_traits>
+#include <functional>
// for debug
#include <iostream>
@@ -79,6 +80,9 @@
#include "../include/namespace.h"
#include "../include/extension.h"
#include "../include/call.h"
+#include "../include/opcodes.h"
+#include "../include/script.h"
+#include "../include/file.h"
/**
* Common header files for internal use only
@@ -112,6 +116,7 @@
#include "objectimpl.h"
#include "parametersimpl.h"
#include "extensionimpl.h"
+#include "compileroptions.h"
#ifndef ZVAL_COPY_VALUE
#define ZVAL_COPY_VALUE(z, v) \
diff --git a/zend/object.cpp b/zend/object.cpp
index dc548cc..22431fe 100644
--- a/zend/object.cpp
+++ b/zend/object.cpp
@@ -17,7 +17,7 @@ namespace Php {
* @param name Name of the class to instantiate
* @param base Implementation of the class
*/
-Object::Object(const char *name, Base *base)
+Object::Object(const char *name, Base *base) : Value()
{
// does the object already have a handle?
if (base->implementation())
@@ -29,7 +29,7 @@ Object::Object(const char *name, Base *base)
{
// we need the tsrm_ls variable
TSRMLS_FETCH();
-
+
// 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 (we use the FatalError class
@@ -38,24 +38,48 @@ Object::Object(const char *name, Base *base)
// 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)
- new ObjectImpl(entry, base TSRMLS_CC);
-
+ // 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);
+
// now we can store it
operator=(Value(base));
-
+
// install the object handlers
Z_OBJVAL_P(_val).handlers = ClassImpl::objectHandlers(entry);
}
}
/**
+ * Copy constructor is valid if the passed in object is also an object,
+ * or when it is a string holding a classname
+ * @param that An other object
+ */
+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())
+ {
+ // instantiate the object
+ if (instantiate(value)) call("__construct");
+ }
+ else
+ {
+ // this simply copies the other object
+ operator=(value);
+ }
+}
+
+/**
* Internal method to instantiate an object
- * @param name
+ * @param name Name of the class to instantiate
+ * @return bool True if there is a __construct function
*/
-void Object::instantiate(const char *name)
+bool Object::instantiate(const char *name)
{
// we need the tsrm_ls variable
TSRMLS_FETCH();
@@ -79,6 +103,8 @@ void Object::instantiate(const char *name)
// @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);
}
/**
diff --git a/zend/objectimpl.h b/zend/objectimpl.h
index 7f16320..4d20f3c 100644
--- a/zend/objectimpl.h
+++ b/zend/objectimpl.h
@@ -62,9 +62,10 @@ public:
*
* @param entry Zend class entry
* @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 TSRMLS_DC)
+ ObjectImpl(zend_class_entry *entry, Base *base, int refcount TSRMLS_DC)
{
// allocate a mixed object (for some reason this does not have to be deallocated)
_mixed = (MixedObject *)emalloc(sizeof(MixedObject));
@@ -98,25 +99,28 @@ public:
// 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***);
+ 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*);
+ 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;
+ 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);
+ // 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;
+
// the object may remember that we are its implementation object
base->_impl = this;
}
diff --git a/zend/opcodes.cpp b/zend/opcodes.cpp
new file mode 100644
index 0000000..9e4525a
--- /dev/null
+++ b/zend/opcodes.cpp
@@ -0,0 +1,131 @@
+/**
+ * Opcodes.cpp
+ *
+ * Implementation file for the opcodes class
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+
+/**
+ * Dependencies
+ */
+#include "includes.h"
+
+/**
+ * Set up namespace
+ */
+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
+ */
+class ExecuteState
+{
+private:
+ /**
+ * All the original settings
+ */
+ zend_op_array *_active_op_array;
+ zval **_return_value_ptr_ptr;
+ zend_op **_opline_ptr;
+ int _interactive;
+
+public:
+ /**
+ * Constructor
+ */
+ ExecuteState()
+ {
+ // 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);
+ }
+
+ /**
+ * Destructor
+ */
+ virtual ~ExecuteState()
+ {
+ // restore all settings
+ CG(interactive) = _interactive;
+ EG(no_extensions) = 0;
+ EG(opline_ptr) = _opline_ptr;
+ EG(active_op_array) = _active_op_array;
+ EG(return_value_ptr_ptr) = _return_value_ptr_ptr;
+ }
+};
+
+/**
+ * Destructor
+ */
+Opcodes::~Opcodes()
+{
+ // leap out if opcodes were not valid
+ if (!_opcodes) return;
+
+ // clean up opcodes
+ destroy_op_array(_opcodes TSRMLS_CC);
+ efree(_opcodes);
+}
+
+/**
+ * Execute the opcodes
+ * @return Value
+ */
+Value Opcodes::execute() const
+{
+ // if the script could not be compiled, we return null
+ if (!_opcodes) return nullptr;
+
+ // pointer that is going to hold the return value of the script
+ zval *retval_ptr = nullptr;
+
+ // 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 oldstate;
+
+ // old execute state has been saved (and will automatically be restured 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;
+ EG(no_extensions) = 1;
+ if (!EG(active_symbol_table)) zend_rebuild_symbol_table(TSRMLS_C);
+ CG(interactive) = 0;
+
+ // the current exception
+ zval* oldException = EG(exception);
+
+ // execute the code
+ zend_execute(_opcodes TSRMLS_CC);
+
+ // 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);
+
+ // we're ready if there is no return value
+ if (!retval_ptr) 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);
+
+ // copy the pointer into a value object, and return that
+ return result;
+}
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/zend/parametersimpl.h b/zend/parametersimpl.h
index fd14238..2841c75 100644
--- a/zend/parametersimpl.h
+++ b/zend/parametersimpl.h
@@ -36,7 +36,7 @@ public:
zval **arg = (zval **) (zend_vm_stack_top(TSRMLS_C) - 1 - (argc-i));
// append value
- push_back(Value(*arg));
+ emplace_back(*arg);
}
}
diff --git a/zend/script.cpp b/zend/script.cpp
new file mode 100644
index 0000000..58ee376
--- /dev/null
+++ b/zend/script.cpp
@@ -0,0 +1,60 @@
+/**
+ * Script.cpp
+ *
+ * Implementation file for the script class
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+
+/**
+ * Dependencies
+ */
+#include "includes.h"
+
+/**
+ * Open PHP namespace
+ */
+namespace Php {
+
+/**
+ * Helper function to compile the source code
+ * @param name name of the script
+ * @param script actual PHP code
+ * @param size length of the string
+ * @return opcodes
+ */
+zend_op_array *Script::compile(const char *name, const char *phpcode, size_t size)
+{
+ // Sadly, there is not a simple Zend function to compile a string into opcodes,
+ // so we basically copy the code that we found in zend_execute_API.c inside
+ // the zend_eval_stringl() function into this file here. However, the code
+ // found there is full of zval manipulation, for which we can use the much
+ // simpler Php::Value object
+ Php::Value source(phpcode, size);
+
+ // remember the old compiler options, and set new compiler options
+ CompilerOptions options(ZEND_COMPILE_DEFAULT_FOR_EVAL);
+
+ // we need the tsrm_ls variable
+ TSRMLS_FETCH();
+
+ // compile the string
+ return zend_compile_string(source._val, (char *)name TSRMLS_CC);
+}
+
+/**
+ * Constructor
+ * @param name name of the script
+ * @param script actual PHP code
+ * @param size length of the string
+ */
+Script::Script(const char *name, const char *phpcode, size_t size) : _opcodes(compile(name, phpcode, size))
+{
+}
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/zend/super.cpp b/zend/super.cpp
index c25efeb..a2fa0a9 100644
--- a/zend/super.cpp
+++ b/zend/super.cpp
@@ -40,7 +40,7 @@ Value Super::operator[](const std::string &key)
Value value(PG(http_globals)[_index]);
// pass on the call
- return value[key];
+ return value.get(key);
}
/**
@@ -61,7 +61,7 @@ Value Super::operator[](const char *key)
Value value(PG(http_globals)[_index]);
// pass on the call
- return value[key];
+ return value.get(key);
}
/**
diff --git a/zend/value.cpp b/zend/value.cpp
index 6890ffd..4a59d97 100644
--- a/zend/value.cpp
+++ b/zend/value.cpp
@@ -124,10 +124,10 @@ Value::Value(const std::string &value)
*/
Value::Value(const char *value, int size)
{
- // allocate the zval
+ // allocate the zval
MAKE_STD_ZVAL(_val);
-
- // do we have a valid value?
+
+ // is there a value?
if (value)
{
// create a string zval
@@ -157,10 +157,8 @@ Value::Value(double value)
* @param ref Force this to be a reference
*/
Value::Value(struct _zval_struct *val, bool ref)
+: _val(val)
{
- // just copy the zval into this object
- _val = val;
-
// 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
@@ -169,13 +167,13 @@ Value::Value(struct _zval_struct *val, bool ref)
// separate the zval
SEPARATE_ZVAL_IF_NOT_REF(&_val);
}
-
+
// we see ourselves as reference too
Z_ADDREF_P(_val);
-
+
// we're ready if we do not have to force it as a reference
if (!ref || Z_ISREF_P(_val)) return;
-
+
// make this a reference
Z_SET_ISREF_P(_val);
}
@@ -188,7 +186,9 @@ Value::Value(const Base *object)
{
// there are two options: the object was constructed from user space,
// and is already linked to a handle, or it was constructed from C++
- // space, and no handle does yet exist, find the implementation object
+ // space, and no handle does yet exist. But if it was constructed from
+ // C++ space and not yet wrapped, this Value constructor should not be
+ // called directly, but first via the derived Php::Object class.
auto *impl = object->implementation();
// do we have a handle?
@@ -205,14 +205,14 @@ Value::Value(const Base *object)
// 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;
+
// this is copy-pasted from zend_objects.c - and it is necessary too!
if (!obj_bucket->bucket.obj.handlers) obj_bucket->bucket.obj.handlers = &std_object_handlers;
// 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;
-
- // run the copy constructor
- zval_copy_ctor(_val);
}
/**
@@ -286,7 +286,7 @@ Value::Value(const Value &that)
* Move constructor
* @param value
*/
-Value::Value(Value &&that) : _val(that._val)
+Value::Value(Value &&that) noexcept: _val(that._val)
{
// clear the other object
that._val = nullptr;
@@ -387,7 +387,7 @@ void Value::attach(struct _hashtable *hashtable)
* Retrieve the refcount
* @return int
*/
-int Value::refcount()
+int Value::refcount() const
{
return Z_REFCOUNT_P(_val);
}
@@ -397,7 +397,7 @@ int Value::refcount()
* @param value
* @return Value
*/
-Value &Value::operator=(Value &&value)
+Value &Value::operator=(Value &&value) noexcept
{
// skip self assignment
if (this == &value) return *this;
@@ -854,191 +854,6 @@ Value Value::operator()() const
}
/**
- * Call the function - if the variable holds a callable thing
- * @param p0 The first parameter
- * @return Value
- */
-Value Value::operator()(Value p0) const
-{
- // array of parameters
- zval **params[1] = { &p0._val };
-
- // call the function
- return exec(1, params);
-}
-
-/**
- * Call the function - if the variable holds a callable thing
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @return Value
- */
-Value Value::operator()(Value p0, Value p1) const
-{
- // array of parameters
- zval **params[2] = { &p0._val, &p1._val };
-
- // call the function
- return exec(2, params);
-}
-
-/**
- * Call the function - if the variable holds a callable thing
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @return Value
- */
-Value Value::operator()(Value p0, Value p1, Value p2) const
-{
- // array of parameters
- zval **params[3] = { &p0._val, &p1._val, &p2._val };
-
- // call the function
- return exec(3, params);
-}
-
-/**
- * Call the function - if the variable holds a callable thing
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @return Value
- */
-Value Value::operator()(Value p0, Value p1, Value p2, Value p3) const
-{
- // array of parameters
- zval **params[4] = { &p0._val, &p1._val, &p2._val, &p3._val };
-
- // call the function
- return exec(4, params);
-}
-
-/**
- * Call the function - if the variable holds a callable thing
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @param p4 The fifth parameter
- * @return Value
- */
-Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4) const
-{
- // array of parameters
- zval **params[5] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val };
-
- // call the function
- return exec(5, params);
-}
-
-/**
- * Call the function - if the variable holds a callable thing
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @param p4 The fifth parameter
- * @param p5 The sixth parameter
- * @return Value
- */
-Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5) const
-{
- // array of parameters
- zval **params[6] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val };
-
- // call the function
- return exec(6, params);
-}
-
-/**
- * Call the function - if the variable holds a callable thing
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @param p4 The fifth parameter
- * @param p5 The sixth parameter
- * @param p6 The seventh parameter
- * @return Value
- */
-Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6) const
-{
- // array of parameters
- zval **params[7] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val };
-
- // call the function
- return exec(7, params);
-}
-
-/**
- * Call the function - if the variable holds a callable thing
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @param p4 The fifth parameter
- * @param p5 The sixth parameter
- * @param p6 The seventh parameter
- * @param p7 The eighth parameter
- * @return Value
- */
-Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7) const
-{
- // array of parameters
- zval **params[8] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val };
-
- // call the function
- return exec(8, params);
-}
-
-/**
- * Call the function - if the variable holds a callable thing
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @param p4 The fifth parameter
- * @param p5 The sixth parameter
- * @param p6 The seventh parameter
- * @param p7 The eighth parameter
- * @param p8 The ninth parameter
- * @return Value
- */
-Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8) const
-{
- // array of parameters
- zval **params[9] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val };
-
- // call the function
- return exec(9, params);
-}
-
-/**
- * Call the function - if the variable holds a callable thing
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @param p4 The fifth parameter
- * @param p5 The sixth parameter
- * @param p6 The seventh parameter
- * @param p7 The eighth parameter
- * @param p8 The ninth parameter
- * @param p9 The tenth parameter
- * @return Value
- */
-Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8, Value p9) const
-{
- // array of parameters
- zval **params[10] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val, &p9._val };
-
- // call the function
- return exec(10, params);
-}
-
-/**
* Call the method - if the variable holds an object with the given method
* @param name name of the method to call
* @return Value
@@ -1050,201 +865,6 @@ Value Value::call(const char *name)
}
/**
- * Call the method - if the variable holds an object with the given method
- * @param name name of the method to call
- * @param p0 The first parameter
- * @return Value
- */
-Value Value::call(const char *name, Value p0)
-{
- // array of parameters
- zval **params[] = { &p0._val };
-
- // call with zero parameters
- return exec(name, 1, params);
-}
-
-/**
- * Call the method - if the variable holds an object with the given method
- * @param name name of the method to call
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @return Value
- */
-Value Value::call(const char *name, Value p0, Value p1)
-{
- // array of parameters
- zval **params[] = { &p0._val, &p1._val };
-
- // call with zero parameters
- return exec(name, 2, params);
-}
-
-/**
- * Call the method - if the variable holds an object with the given method
- * @param name name of the method to call
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @return Value
- */
-Value Value::call(const char *name, Value p0, Value p1, Value p2)
-{
- // array of parameters
- zval **params[] = { &p0._val, &p1._val, &p2._val };
-
- // call with zero parameters
- return exec(name, 3, params);
-}
-
-/**
- * Call the method - if the variable holds an object with the given method
- * @param name name of the method to call
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @return Value
- */
-Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3)
-{
- // array of parameters
- zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val };
-
- // call with zero parameters
- return exec(name, 4, params);
-}
-
-/**
- * Call the method - if the variable holds an object with the given method
- * @param name name of the method to call
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @param p4 The fifth parameter
- * @return Value
- */
-Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4)
-{
- // array of parameters
- zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val };
-
- // call with zero parameters
- return exec(name, 5, params);
-}
-
-/**
- * Call the method - if the variable holds an object with the given method
- * @param name name of the method to call
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @param p4 The fifth parameter
- * @param p5 The sixth parameter
- * @return Value
- */
-Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5)
-{
- // array of parameters
- zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val };
-
- // call with zero parameters
- return exec(name, 6, params);
-}
-
-/**
- * Call the method - if the variable holds an object with the given method
- * @param name name of the method to call
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @param p4 The fifth parameter
- * @param p5 The sixth parameter
- * @param p6 The seventh parameter
- * @return Value
- */
-Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6)
-{
- // array of parameters
- zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val };
-
- // call with zero parameters
- return exec(name, 7, params);
-}
-
-/**
- * Call the method - if the variable holds an object with the given method
- * @param name name of the method to call
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @param p4 The fifth parameter
- * @param p5 The sixth parameter
- * @param p6 The seventh parameter
- * @param p7 The eighth parameter
- * @return Value
- */
-Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7)
-{
- // array of parameters
- zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val };
-
- // call with zero parameters
- return exec(name, 8, params);
-}
-
-/**
- * Call the method - if the variable holds an object with the given method
- * @param name name of the method to call
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @param p4 The fifth parameter
- * @param p5 The sixth parameter
- * @param p6 The seventh parameter
- * @param p7 The eighth parameter
- * @param p8 The ninth parameter
- * @return Value
- */
-Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8)
-{
- // array of parameters
- zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val };
-
- // call with zero parameters
- return exec(name, 9, params);
-}
-
-/**
- * Call the method - if the variable holds an object with the given method
- * @param name name of the method to call
- * @param p0 The first parameter
- * @param p1 The second parameter
- * @param p2 The third parameter
- * @param p3 The fourth parameter
- * @param p4 The fifth parameter
- * @param p5 The sixth parameter
- * @param p6 The seventh parameter
- * @param p7 The eighth parameter
- * @param p8 The ninth parameter
- * @param p9 The tenth parameter
- * @return Value
- */
-Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8, Value p9)
-{
- // array of parameters
- zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val, &p9._val };
-
- // call with zero parameters
- return exec(name, 10, params);
-}
-
-/**
* Helper function that runs the actual call
* @param object The object to call it on
* @param method The function or method to call
@@ -1409,6 +1029,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
*/
@@ -1416,18 +1146,24 @@ 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);
-
+
+ // 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);
+
// done
- return Value(copy);
+ return output;
}
/**
@@ -1610,10 +1346,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;
}
@@ -1626,9 +1362,9 @@ 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
+ // get access to the hash table
if (isObject())
{
// we need the TSRMLS_CC variable
@@ -1647,7 +1383,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));
}
}
@@ -1676,6 +1412,20 @@ ValueIterator Value::end() const
}
/**
+ * Iterate over key value pairs
+ * @param callback
+ */
+void Value::iterate(const std::function<void(const Php::Value &,const Php::Value &)> &callback) const
+{
+ // iterate over the object
+ for (const auto &iter : *this)
+ {
+ // call the callback
+ callback(iter.first, iter.second);
+ }
+}
+
+/**
* Does the array contain a certain index?
* @param index
* @return bool
@@ -1761,7 +1511,7 @@ Value Value::get(int index) const
*/
Value Value::get(const char *key, int size) const
{
- // must be an array
+ // must be an array or object
if (!isArray() && !isObject()) return Value();
// calculate size
@@ -1781,6 +1531,9 @@ Value Value::get(const char *key, int size) const
}
else
{
+ // key should not start with a null byte
+ if (size > 0 && key[0] == 0) return Value();
+
// we need the tsrm_ls variable
TSRMLS_FETCH();
@@ -1847,6 +1600,9 @@ void Value::set(int index, const Value &value)
*/
void Value::setRaw(const char *key, int size, const Value &value)
{
+ // does not work for empty keys
+ if (!key || (size > 0 && key[0] == 0)) return;
+
// is this an object?
if (isObject())
{
@@ -1858,7 +1614,7 @@ void Value::setRaw(const char *key, int size, const Value &value)
// retrieve the class entry
auto *entry = zend_get_class_entry(_val TSRMLS_CC);
-
+
// update the property (cast necessary for php 5.3)
zend_update_property(entry, _val, (char *)key, size, value._val TSRMLS_CC);
}