1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
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
*/
}
|