summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2015-01-10 21:13:13 +0100
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2015-01-10 21:13:13 +0100
commit8edf958a8ddf6c13b8bba9b87f3d5b1296be1376 (patch)
treea3fdeeb1b82db662df95f3fbfc14e2e6f8e199d2
parentfb80367d418f9a9fb059c98d5aec80febeb3bd23 (diff)
added Script class to simplify parsing and executing php scripts (the Php::eval() call both compiles and executes a script, while the Script class splits these two steps, which allows you to run the same opcodes multiple times)
-rw-r--r--include/script.h95
-rw-r--r--include/value.h1
-rw-r--r--phpcpp.h1
-rw-r--r--zend/includes.h1
-rw-r--r--zend/script.cpp199
5 files changed, 297 insertions, 0 deletions
diff --git a/include/script.h b/include/script.h
new file mode 100644
index 0000000..d2eb575
--- /dev/null
+++ b/include/script.h
@@ -0,0 +1,95 @@
+/**
+ * Script.h
+ *
+ * Class that can be used to evaluate a PHP script in the current PHP context.
+ *
+ * The difference between directly calling eval() is that the script object
+ * will first evaluate the string, and then it can be executed multiple times.
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+
+/**
+ * Forward declarations
+ */
+struct _zend_op_array;
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * Class definition
+ */
+class Script
+{
+public:
+ /**
+ * Constructor
+ *
+ * The constructor will not throw any exceptions, even when invalid
+ * PHP code is passed to it that can not be evaluated. You should call
+ * the valid() to find out if the script was valid (could be parsed).
+ *
+ * @param name Name of the PHP script
+ * @param source PHP source code to be evaluated
+ * @param size Length of the source code
+ */
+ Script(const char *name, const char *source, size_t size) noexcept;
+
+ /**
+ * Alternative constructor without a size
+ * @param name Name of the PHP script
+ * @param source PHP source code to be evaluated
+ */
+ Script(const char *name, const char *source) noexcept : Script(name, source, ::strlen(source)) {}
+
+ /**
+ * Alternative constructor without a name
+ * @param source PHP source code to be evaluated
+ * @param size Length of the source code
+ */
+ Script(const char *source, size_t size) noexcept : Script("Unknown", source, size) {}
+
+ /**
+ * Alternative constructor without a name and without a size
+ * @param source PHP source code to be evaluated
+ */
+ Script(const char *source) noexcept : Script("Unknown", source, ::strlen(source)) {}
+
+ /**
+ * Destructor
+ */
+ virtual ~Script();
+
+ /**
+ * Is the script a valid PHP script without syntax errors?
+ * @return bool
+ */
+ bool valid() const
+ {
+ return _opcodes != nullptr;
+ }
+
+ /**
+ * Execute the script
+ * The return value of the script is returned
+ * @return Value
+ */
+ Value execute() const;
+
+private:
+ /**
+ * The opcodes
+ * @var zend_op_array
+ */
+ struct _zend_op_array *_opcodes;
+
+};
+
+/**
+ * End of namespace
+ */
+}
diff --git a/include/value.h b/include/value.h
index f0b7345..9447fc8 100644
--- a/include/value.h
+++ b/include/value.h
@@ -1170,6 +1170,7 @@ protected:
friend class HashMember<int>;
friend class HashMember<std::string>;
friend class Callable;
+ friend class Script;
};
/**
diff --git a/phpcpp.h b/phpcpp.h
index 23efe69..c22c5f6 100644
--- a/phpcpp.h
+++ b/phpcpp.h
@@ -61,6 +61,7 @@
#include <phpcpp/namespace.h>
#include <phpcpp/extension.h>
#include <phpcpp/call.h>
+#include <phpcpp/script.h>
/**
* Macro to export a function
diff --git a/zend/includes.h b/zend/includes.h
index 582530b..2f8990d 100644
--- a/zend/includes.h
+++ b/zend/includes.h
@@ -80,6 +80,7 @@
#include "../include/namespace.h"
#include "../include/extension.h"
#include "../include/call.h"
+#include "../include/script.h"
/**
* Common header files for internal use only
diff --git a/zend/script.cpp b/zend/script.cpp
new file mode 100644
index 0000000..6cebaeb
--- /dev/null
+++ b/zend/script.cpp
@@ -0,0 +1,199 @@
+/**
+ * Script.cpp
+ *
+ * Implementation file for the script class
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+
+/**
+ * Dependencies
+ */
+#include "includes.h"
+#include <mutex>
+
+/**
+ * Open PHP namespace
+ */
+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
+ * @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)
+{
+ // 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
+ _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);
+}
+
+/**
+ * Execute a script
+ * @return Value
+ */
+Value Script::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;
+
+ // 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
+ {
+ // execute the code
+ zend_execute(_opcodes 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;
+}
+
+/**
+ * End of namespace
+ */
+}
+