summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2015-01-11 00:08:41 +0100
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2015-01-11 00:08:41 +0100
commit1efade1a7667e4e1a34265fb3cf310faf8c80bb6 (patch)
treebd311ac3de5500daae633fbfdb585faf737607ba
parent825049026dd2b2e906cdb46279faa39580d931d1 (diff)
refactored script class to have a seperate opcodes class, added file class that uses this same opcodes class
-rw-r--r--include/file.h76
-rw-r--r--include/opcodes.h69
-rw-r--r--include/script.h22
-rw-r--r--phpcpp.h2
-rw-r--r--zend/compileroptions.h57
-rw-r--r--zend/eval.cpp2
-rw-r--r--zend/file.cpp114
-rw-r--r--zend/includes.h3
-rw-r--r--zend/opcodes.cpp147
-rw-r--r--zend/script.cpp164
10 files changed, 495 insertions, 161 deletions
diff --git a/include/file.h b/include/file.h
new file mode 100644
index 0000000..093431e
--- /dev/null
+++ b/include/file.h
@@ -0,0 +1,76 @@
+/**
+ * File.h
+ *
+ * Extended script, a PHP source file name can be passed to a Php::File object
+ * to have it evaluated.
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * Class definition
+ */
+class File
+{
+public:
+ /**
+ * 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 size of the filename
+ */
+ File(const char *name, size_t size);
+
+ /**
+ * Alternative constructor with just a filename
+ *
+ * @param name the filename
+ */
+ File(const char *name) : File(name, ::strlen(name)) {}
+
+ /**
+ * Destructor
+ */
+ virtual ~File();
+
+ /**
+ * Include the file once
+ * @return Php::Value
+ */
+ Value once();
+
+ /**
+ * Execute the file
+ * @return Php::Value
+ */
+ Value execute();
+
+private:
+ /**
+ * The full resolved path name
+ * @var const char *
+ */
+ char *_path = nullptr;
+
+ /**
+ * The opcodes of this file
+ * @var Opcodes
+ */
+ Opcodes *_opcodes = nullptr;
+
+};
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/include/opcodes.h b/include/opcodes.h
new file mode 100644
index 0000000..5a40d05
--- /dev/null
+++ b/include/opcodes.h
@@ -0,0 +1,69 @@
+/**
+ * Opcodes.h
+ *
+ * Class represents a set of opcodes of a PHP script that can be executed. This
+ * is an internal file that you normally do not have to instantiate yourself.
+ * Better use the Php::Script of Php::File classes.
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+
+/**
+ * Forward declarations
+ */
+struct _zend_op_array;
+
+/**
+ * Namespace
+ */
+namespace Php {
+
+/**
+ * Class definition
+ */
+class Opcodes
+{
+public:
+ /**
+ * Constructor
+ * @param opcodes
+ */
+ Opcodes(struct _zend_op_array *opcodes) : _opcodes(opcodes)
+ {
+ // no other initialisation is necessary
+ }
+
+ /**
+ * Destructor
+ */
+ virtual ~Opcodes();
+
+ /**
+ * Are the opcodes valid?
+ * @return bool
+ */
+ bool valid() const
+ {
+ return _opcodes != nullptr;
+ }
+
+ /**
+ * Execute the opcodes
+ * @return Value
+ */
+ Value execute() const;
+
+private:
+ /**
+ * The opcodes
+ * @var zend_op_array
+ */
+ struct _zend_op_array *_opcodes;
+};
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/include/script.h b/include/script.h
index d2eb575..a9c2921 100644
--- a/include/script.h
+++ b/include/script.h
@@ -62,7 +62,7 @@ public:
/**
* Destructor
*/
- virtual ~Script();
+ virtual ~Script() {}
/**
* Is the script a valid PHP script without syntax errors?
@@ -70,7 +70,7 @@ public:
*/
bool valid() const
{
- return _opcodes != nullptr;
+ return _opcodes.valid();
}
/**
@@ -78,14 +78,26 @@ public:
* The return value of the script is returned
* @return Value
*/
- Value execute() const;
+ Value execute() const
+ {
+ return _opcodes.execute();
+ }
private:
/**
* The opcodes
- * @var zend_op_array
+ * @var Opcodes
+ */
+ Opcodes _opcodes;
+
+ /**
+ * 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
*/
- struct _zend_op_array *_opcodes;
+ static struct _zend_op_array *compile(const char *name, const char *phpcode, size_t size);
};
diff --git a/phpcpp.h b/phpcpp.h
index c22c5f6..368018e 100644
--- a/phpcpp.h
+++ b/phpcpp.h
@@ -61,7 +61,9 @@
#include <phpcpp/namespace.h>
#include <phpcpp/extension.h>
#include <phpcpp/call.h>
+#include <phpcpp/opcodes.h>
#include <phpcpp/script.h>
+#include <phpcpp/file.h>
/**
* Macro to export a function
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
index 4632fff..32d807a 100644
--- a/zend/eval.cpp
+++ b/zend/eval.cpp
@@ -33,7 +33,7 @@ Value eval(const std::string &phpCode)
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
+ // 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.
diff --git a/zend/file.cpp b/zend/file.cpp
new file mode 100644
index 0000000..3ba53c9
--- /dev/null
+++ b/zend/file.cpp
@@ -0,0 +1,114 @@
+/**
+ * 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);
+}
+
+/**
+ * Destructor
+ */
+File::~File()
+{
+ // clean up path name
+ if (_path) efree(_path);
+
+ // clean up opcodes
+ if (_opcodes) delete _opcodes;
+}
+
+/**
+ * Execute the file
+ * @return Value
+ */
+Value File::execute()
+{
+ // do we already have the opcodes?
+ if (_opcodes) return _opcodes->execute();
+
+ // skip if there is no valid path
+ if (!_path) return nullptr;
+
+ // we are going to open the file
+ zend_file_handle fileHandle;
+
+ // open the file
+ if (zend_stream_open(_path, &fileHandle TSRMLS_CC) == FAILURE) return nullptr;
+
+ // 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_file_handle_dtor(&fileHandle TSRMLS_CC);
+
+ // execute the opcodes
+ return _opcodes->execute();
+}
+
+/**
+ * Execute a file only once
+ * @return Value
+ */
+Value File::once()
+{
+ // skip if the opcodes are already known (then we know for sure that the
+ // file was already executed)
+ if (_opcodes) return nullptr;
+
+ // also leap out if the filename was not even valid
+ if (!_path) return nullptr;
+
+ // check if this file was already included
+ if (zend_hash_exists(&EG(included_files), _path, ::strlen(_path) + 1)) return nullptr;
+
+ // add the entry to the list of included files
+ if (zend_hash_add_empty_element(&EG(included_files), _path, ::strlen(_path) + 1) == FAILURE) return nullptr;
+
+ // execute the file
+ return execute();
+}
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/zend/includes.h b/zend/includes.h
index 2f8990d..d6f2062 100644
--- a/zend/includes.h
+++ b/zend/includes.h
@@ -80,7 +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
@@ -114,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/opcodes.cpp b/zend/opcodes.cpp
new file mode 100644
index 0000000..0603d9b
--- /dev/null
+++ b/zend/opcodes.cpp
@@ -0,0 +1,147 @@
+/**
+ * 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;
+
+ // @todo should retval_ptr be allocated?
+
+
+ // 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;
+
+ // this code was copied from zend_execute_API.c. this is what I think it does:
+ // the zend_execute() call could result in all sort of disastrous things, one
+ // of them is a full crash of the executing script. if that happens, the zend
+ // engine does a long jump right up to some other point in the code. the
+ // 'zend_try' and 'zend_catch' macros prevent this: they install a new
+ // destination for the long jump, so that we can catch a failure
+ zend_try
+ {
+ // 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);
+ }
+ zend_catch
+ {
+ // the code could not be executed, and the zend engine is in big
+ // trouble now, in the original code the _opcodes are efree'd, but here
+ // we can just as well continue with the real bailout (the zend_try/
+ // zend_catch pair was maybe not even necessary???)
+ zend_bailout();
+ }
+ zend_end_try();
+
+ // @todo do we have to do something smart with zval copy'ing?
+
+ // we're ready if there is no return value
+ if (!retval_ptr) return nullptr;
+
+ // copy the pointer into a value object, and return that
+ return retval_ptr;
+}
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/zend/script.cpp b/zend/script.cpp
index 46a9ddd..58ee376 100644
--- a/zend/script.cpp
+++ b/zend/script.cpp
@@ -11,7 +11,6 @@
* Dependencies
*/
#include "includes.h"
-#include <mutex>
/**
* Open PHP namespace
@@ -19,95 +18,13 @@
namespace Php {
/**
- * Helper class to temporarily set compiler options
- *
- * When an object is destructed, it automatically restored the previous compiler settings
- */
-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;
- }
-};
-
-/**
- * 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;
- }
-};
-
-/**
- * Constructor
+ * 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
*/
-Script::Script(const char *name, const char *phpcode, size_t size)
+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
@@ -123,80 +40,17 @@ Script::Script(const char *name, const char *phpcode, size_t size)
TSRMLS_FETCH();
// compile the string
- _opcodes = zend_compile_string(source._val, (char *)name TSRMLS_CC);
-}
-
-/**
- * Destructor
- */
-Script::~Script()
-{
- // do nothing if there are no opcodes
- if (!_opcodes) return;
-
- // clean up opcodes
- destroy_op_array(_opcodes TSRMLS_CC);
- efree(_opcodes);
+ return zend_compile_string(source._val, (char *)name TSRMLS_CC);
}
/**
- * Execute a script
- * @return Value
+ * Constructor
+ * @param name name of the script
+ * @param script actual PHP code
+ * @param size length of the string
*/
-Value Script::execute() const
+Script::Script(const char *name, const char *phpcode, size_t size) : _opcodes(compile(name, phpcode, size))
{
- // 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;
-
- // this code was copied from zend_execute_API.c. this is what I think it does:
- // the zend_execute() call could result in all sort of disastrous things, one
- // of them is a full crash of the executing script. if that happens, the zend
- // engine does a long jump right up to some other point in the code. the
- // 'zend_try' and 'zend_catch' macros prevent this: they install a new
- // destination for the long jump, so that we can catch a failure
- zend_try
- {
- // 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);
- }
- zend_catch
- {
- // the code could not be executed, and the zend engine is in big
- // trouble now, in the original code the _opcodes are efree'd, but here
- // we can just as well continue with the real bailout (the zend_try/
- // zend_catch pair was maybe not even necessary???)
- zend_bailout();
- }
- zend_end_try();
-
- // we're ready if there is no return value
- if (!retval_ptr) return nullptr;
-
- // copy the pointer into a value object, and return that
- return retval_ptr;
}
/**