diff options
-rw-r--r-- | include/file.h | 76 | ||||
-rw-r--r-- | include/opcodes.h | 69 | ||||
-rw-r--r-- | include/script.h | 22 | ||||
-rw-r--r-- | phpcpp.h | 2 | ||||
-rw-r--r-- | zend/compileroptions.h | 57 | ||||
-rw-r--r-- | zend/eval.cpp | 2 | ||||
-rw-r--r-- | zend/file.cpp | 114 | ||||
-rw-r--r-- | zend/includes.h | 3 | ||||
-rw-r--r-- | zend/opcodes.cpp | 147 | ||||
-rw-r--r-- | zend/script.cpp | 164 |
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); }; @@ -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; } /** |